@tanstack/svelte-table 9.0.0-alpha.41 → 9.0.0-alpha.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,51 +1,31 @@
1
- import { constructReactivityFeature, constructTable, } from '@tanstack/table-core';
1
+ import { constructTable } from '@tanstack/table-core';
2
2
  import { useSelector } from '@tanstack/svelte-store';
3
3
  import { untrack } from 'svelte';
4
4
  import { mergeObjects } from './merge-objects';
5
+ import { svelteReactivity } from './reactivity.svelte';
5
6
  export function createTable(tableOptions, selector = () => ({})) {
6
- // 1. Create $state-based notifier for reactivity feature
7
- let notifierValue = $state(0);
8
- // 2. Construct reactivity feature (same pattern as solid/vue/angular)
9
- const svelteReactivityFeature = constructReactivityFeature({
10
- stateNotifier: () => notifierValue,
11
- optionsNotifier: () => notifierValue,
12
- });
13
- // 3. Merge reactivity feature into options using mergeObjects (preserves getters)
7
+ // 1. Merge reactivity into options using mergeObjects (preserves getters)
14
8
  const mergedOptions = mergeObjects(tableOptions, {
15
- _features: mergeObjects(tableOptions._features, {
16
- svelteReactivityFeature,
17
- }),
9
+ _features: {
10
+ coreReativityFeature: svelteReactivity(),
11
+ ...tableOptions._features,
12
+ },
18
13
  });
19
- // 4. Set up resolved options with mergeOptions handler
14
+ // 2. Set up resolved options with mergeOptions handler
20
15
  const resolvedOptions = mergeObjects({
21
16
  mergeOptions: (defaultOptions, newOptions) => {
22
17
  return mergeObjects(defaultOptions, newOptions);
23
18
  },
24
19
  }, mergedOptions);
25
- // 5. Construct table
20
+ // 3. Construct table
26
21
  const table = constructTable(resolvedOptions);
27
- // 6. Subscribe to all state and options via useSelector
28
- const allState = useSelector(table.store, (state) => state);
29
- const allOptions = useSelector(table.optionsStore, (options) => options);
30
- // 7. Sync store changes -> notifier.
31
- // Use $effect.pre so this runs before DOM updates (like Solid's createComputed).
32
- // Use untrack for the write so the effect only depends on allState/allOptions,
33
- // not on notifierValue itself (which would cause an infinite loop).
34
- $effect.pre(() => {
35
- allState.current;
36
- allOptions.current;
37
- untrack(() => {
38
- notifierValue++;
39
- });
40
- });
41
- // 8. Sync options reactively. When controlled state changes (e.g., $state
22
+ // 4. Sync options reactively. When controlled state changes (e.g., $state
42
23
  // inside createTableState), the effect re-runs and calls setOptions.
43
24
  // Use $effect.pre so the table sees updated options BEFORE the DOM renders,
44
25
  // ensuring getRowModel() returns current data (not stale, one-frame-behind data).
45
26
  // The reactive reads (state getters, data getter) happen OUTSIDE untrack
46
- // so they become dependencies. The setOptions call is INSIDE untrack to
47
- // prevent tracking notifierValue (read via store.state's stateNotifier
48
- // interceptor inside setOptions), which would cause an infinite loop.
27
+ // so they become dependencies. The setOptions call is INSIDE untrack so
28
+ // option writes do not subscribe this effect to table internals.
49
29
  $effect.pre(() => {
50
30
  // Read reactive getters to create $effect dependencies on external state
51
31
  const state = mergedOptions.state;
@@ -61,7 +41,7 @@ export function createTable(tableOptions, selector = () => ({})) {
61
41
  });
62
42
  });
63
43
  });
64
- // 9. State selector
44
+ // 5. State selector
65
45
  const stateStore = useSelector(table.store, selector);
66
46
  Object.defineProperty(table, 'state', {
67
47
  get() {
@@ -0,0 +1,2 @@
1
+ import type { TableReactivityBindings } from '@tanstack/table-core/reactivity';
2
+ export declare function svelteReactivity(): TableReactivityBindings;
@@ -0,0 +1,47 @@
1
+ import { flushSync, untrack } from 'svelte';
2
+ function observerToCallback(observerOrNext) {
3
+ return typeof observerOrNext === 'function'
4
+ ? observerOrNext
5
+ : (value) => observerOrNext.next?.(value);
6
+ }
7
+ function subscribeToRune(getValue, observerOrNext) {
8
+ const callback = observerToCallback(observerOrNext);
9
+ const unsubscribe = $effect.root(() => {
10
+ $effect(() => {
11
+ const value = getValue();
12
+ untrack(() => callback(value));
13
+ });
14
+ });
15
+ return { unsubscribe };
16
+ }
17
+ export function svelteReactivity() {
18
+ return {
19
+ createOptionsStore: true,
20
+ createReadonlyAtom: (fn, _options) => {
21
+ const value = $derived.by(fn);
22
+ return {
23
+ get: () => value,
24
+ subscribe: ((observerOrNext) => {
25
+ return subscribeToRune(() => value, observerOrNext);
26
+ }),
27
+ };
28
+ },
29
+ createWritableAtom: (initialValue, _options) => {
30
+ let value = $state(initialValue);
31
+ return {
32
+ set: (updater) => {
33
+ value =
34
+ typeof updater === 'function'
35
+ ? updater(value)
36
+ : updater;
37
+ },
38
+ get: () => value,
39
+ subscribe: ((observerOrNext) => {
40
+ return subscribeToRune(() => value, observerOrNext);
41
+ }),
42
+ };
43
+ },
44
+ untrack: untrack,
45
+ batch: (fn) => flushSync(fn),
46
+ };
47
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/svelte-table",
3
- "version": "9.0.0-alpha.41",
3
+ "version": "9.0.0-alpha.43",
4
4
  "description": "Headless UI for building powerful tables & datagrids for Svelte.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -51,7 +51,7 @@
51
51
  ],
52
52
  "dependencies": {
53
53
  "@tanstack/svelte-store": "^0.12.0",
54
- "@tanstack/table-core": "9.0.0-alpha.41"
54
+ "@tanstack/table-core": "9.0.0-alpha.43"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@sveltejs/package": "^2.5.7",
@@ -1,10 +1,8 @@
1
- import {
2
- constructReactivityFeature,
3
- constructTable,
4
- } from '@tanstack/table-core'
5
- import { shallow, useSelector } from '@tanstack/svelte-store'
1
+ import { constructTable } from '@tanstack/table-core'
2
+ import { useSelector } from '@tanstack/svelte-store'
6
3
  import { untrack } from 'svelte'
7
4
  import { mergeObjects } from './merge-objects'
5
+ import { svelteReactivity } from './reactivity.svelte'
8
6
  import type {
9
7
  RowData,
10
8
  Table,
@@ -38,23 +36,15 @@ export function createTable<
38
36
  selector: (state: TableState<TFeatures>) => TSelected = () =>
39
37
  ({}) as TSelected,
40
38
  ): SvelteTable<TFeatures, TData, TSelected> {
41
- // 1. Create $state-based notifier for reactivity feature
42
- let notifierValue = $state(0)
43
-
44
- // 2. Construct reactivity feature (same pattern as solid/vue/angular)
45
- const svelteReactivityFeature = constructReactivityFeature<TFeatures, TData>({
46
- stateNotifier: () => notifierValue,
47
- optionsNotifier: () => notifierValue,
48
- })
49
-
50
- // 3. Merge reactivity feature into options using mergeObjects (preserves getters)
39
+ // 1. Merge reactivity into options using mergeObjects (preserves getters)
51
40
  const mergedOptions = mergeObjects(tableOptions, {
52
- _features: mergeObjects(tableOptions._features, {
53
- svelteReactivityFeature,
54
- }),
55
- }) as any
41
+ _features: {
42
+ coreReativityFeature: svelteReactivity(),
43
+ ...tableOptions._features,
44
+ },
45
+ }) as TableOptions<TFeatures, TData>
56
46
 
57
- // 4. Set up resolved options with mergeOptions handler
47
+ // 2. Set up resolved options with mergeOptions handler
58
48
  const resolvedOptions = mergeObjects(
59
49
  {
60
50
  mergeOptions: (
@@ -67,40 +57,23 @@ export function createTable<
67
57
  mergedOptions,
68
58
  ) as TableOptions<TFeatures, TData>
69
59
 
70
- // 5. Construct table
60
+ // 3. Construct table
71
61
  const table = constructTable(resolvedOptions) as SvelteTable<
72
62
  TFeatures,
73
63
  TData,
74
64
  TSelected
75
65
  >
76
66
 
77
- // 6. Subscribe to all state and options via useSelector
78
- const allState = useSelector(table.store, (state) => state)
79
- const allOptions = useSelector(table.optionsStore, (options) => options)
80
-
81
- // 7. Sync store changes -> notifier.
82
- // Use $effect.pre so this runs before DOM updates (like Solid's createComputed).
83
- // Use untrack for the write so the effect only depends on allState/allOptions,
84
- // not on notifierValue itself (which would cause an infinite loop).
85
- $effect.pre(() => {
86
- allState.current
87
- allOptions.current
88
- untrack(() => {
89
- notifierValue++
90
- })
91
- })
92
-
93
- // 8. Sync options reactively. When controlled state changes (e.g., $state
67
+ // 4. Sync options reactively. When controlled state changes (e.g., $state
94
68
  // inside createTableState), the effect re-runs and calls setOptions.
95
69
  // Use $effect.pre so the table sees updated options BEFORE the DOM renders,
96
70
  // ensuring getRowModel() returns current data (not stale, one-frame-behind data).
97
71
  // The reactive reads (state getters, data getter) happen OUTSIDE untrack
98
- // so they become dependencies. The setOptions call is INSIDE untrack to
99
- // prevent tracking notifierValue (read via store.state's stateNotifier
100
- // interceptor inside setOptions), which would cause an infinite loop.
72
+ // so they become dependencies. The setOptions call is INSIDE untrack so
73
+ // option writes do not subscribe this effect to table internals.
101
74
  $effect.pre(() => {
102
75
  // Read reactive getters to create $effect dependencies on external state
103
- const state = mergedOptions.state
76
+ const state: Record<string, unknown> | undefined = mergedOptions.state
104
77
  if (state) {
105
78
  for (const key in state) {
106
79
  void state[key]
@@ -110,15 +83,12 @@ export function createTable<
110
83
 
111
84
  untrack(() => {
112
85
  table.setOptions((prev) => {
113
- return mergeObjects(prev, mergedOptions) as TableOptions<
114
- TFeatures,
115
- TData
116
- >
86
+ return mergeObjects(prev, mergedOptions)
117
87
  })
118
88
  })
119
89
  })
120
90
 
121
- // 9. State selector
91
+ // 5. State selector
122
92
  const stateStore = useSelector(table.store, selector)
123
93
 
124
94
  Object.defineProperty(table, 'state', {
@@ -0,0 +1,66 @@
1
+ import { flushSync, untrack } from 'svelte'
2
+ import type {
3
+ TableAtomOptions,
4
+ TableReactivityBindings,
5
+ } from '@tanstack/table-core/reactivity'
6
+ import type { Atom, Observer, ReadonlyAtom } from '@tanstack/svelte-store'
7
+
8
+ function observerToCallback<T>(
9
+ observerOrNext: Observer<T> | ((value: T) => void),
10
+ ): (value: T) => void {
11
+ return typeof observerOrNext === 'function'
12
+ ? observerOrNext
13
+ : (value) => observerOrNext.next?.(value)
14
+ }
15
+
16
+ function subscribeToRune<T>(
17
+ getValue: () => T,
18
+ observerOrNext: Observer<T> | ((value: T) => void),
19
+ ) {
20
+ const callback = observerToCallback(observerOrNext)
21
+ const unsubscribe = $effect.root(() => {
22
+ $effect(() => {
23
+ const value = getValue()
24
+ untrack(() => callback(value))
25
+ })
26
+ })
27
+
28
+ return { unsubscribe }
29
+ }
30
+
31
+ export function svelteReactivity(): TableReactivityBindings {
32
+ return {
33
+ createOptionsStore: true,
34
+ createReadonlyAtom: <T>(fn: () => T, _options?: TableAtomOptions<T>) => {
35
+ const value = $derived.by(fn)
36
+
37
+ return {
38
+ get: () => value,
39
+ subscribe: ((observerOrNext: Observer<T> | ((value: T) => void)) => {
40
+ return subscribeToRune(() => value, observerOrNext)
41
+ }) as ReadonlyAtom<T>['subscribe'],
42
+ }
43
+ },
44
+ createWritableAtom: <T>(
45
+ initialValue: T,
46
+ _options?: TableAtomOptions<T>,
47
+ ): Atom<T> => {
48
+ let value = $state(initialValue)
49
+
50
+ return {
51
+ set: (updater: T | ((prevVal: T) => T)) => {
52
+ value =
53
+ typeof updater === 'function'
54
+ ? (updater as (prevVal: T) => T)(value)
55
+ : updater
56
+ },
57
+ get: () => value,
58
+ subscribe: ((observerOrNext: Observer<T> | ((value: T) => void)) => {
59
+ return subscribeToRune(() => value, observerOrNext)
60
+ }) as Atom<T>['subscribe'],
61
+ }
62
+ },
63
+ untrack: untrack,
64
+ batch: (fn) => flushSync(fn),
65
+ }
66
+ }