@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.
- package/dist/createTable.svelte.js +13 -33
- package/dist/reactivity.svelte.d.ts +2 -0
- package/dist/reactivity.svelte.js +47 -0
- package/package.json +2 -2
- package/src/createTable.svelte.ts +17 -47
- package/src/reactivity.svelte.ts +66 -0
|
@@ -1,51 +1,31 @@
|
|
|
1
|
-
import {
|
|
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.
|
|
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:
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
_features: {
|
|
10
|
+
coreReativityFeature: svelteReactivity(),
|
|
11
|
+
...tableOptions._features,
|
|
12
|
+
},
|
|
18
13
|
});
|
|
19
|
-
//
|
|
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
|
-
//
|
|
20
|
+
// 3. Construct table
|
|
26
21
|
const table = constructTable(resolvedOptions);
|
|
27
|
-
//
|
|
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
|
|
47
|
-
//
|
|
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
|
-
//
|
|
44
|
+
// 5. State selector
|
|
65
45
|
const stateStore = useSelector(table.store, selector);
|
|
66
46
|
Object.defineProperty(table, 'state', {
|
|
67
47
|
get() {
|
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
41
|
+
_features: {
|
|
42
|
+
coreReativityFeature: svelteReactivity(),
|
|
43
|
+
...tableOptions._features,
|
|
44
|
+
},
|
|
45
|
+
}) as TableOptions<TFeatures, TData>
|
|
56
46
|
|
|
57
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
99
|
-
//
|
|
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)
|
|
114
|
-
TFeatures,
|
|
115
|
-
TData
|
|
116
|
-
>
|
|
86
|
+
return mergeObjects(prev, mergedOptions)
|
|
117
87
|
})
|
|
118
88
|
})
|
|
119
89
|
})
|
|
120
90
|
|
|
121
|
-
//
|
|
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
|
+
}
|