@pyreon/table 0.0.1
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/LICENSE +21 -0
- package/README.md +173 -0
- package/lib/analysis/index.js.html +5406 -0
- package/lib/index.js +94 -0
- package/lib/index.js.map +1 -0
- package/lib/types/index.d.ts +93 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index2.d.ts +40 -0
- package/lib/types/index2.d.ts.map +1 -0
- package/package.json +55 -0
- package/src/flex-render.ts +45 -0
- package/src/index.ts +11 -0
- package/src/tests/table.test.ts +388 -0
- package/src/use-table.ts +105 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { h } from '@pyreon/core'
|
|
2
|
+
import { signal, computed, type Computed } from '@pyreon/reactivity'
|
|
3
|
+
import { mount } from '@pyreon/runtime-dom'
|
|
4
|
+
import {
|
|
5
|
+
useTable,
|
|
6
|
+
getCoreRowModel,
|
|
7
|
+
getSortedRowModel,
|
|
8
|
+
getFilteredRowModel,
|
|
9
|
+
getPaginationRowModel,
|
|
10
|
+
createColumnHelper,
|
|
11
|
+
flexRender,
|
|
12
|
+
} from '../index'
|
|
13
|
+
import type { ColumnDef } from '../index'
|
|
14
|
+
|
|
15
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
interface Person {
|
|
18
|
+
name: string
|
|
19
|
+
age: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const defaultData: Person[] = [
|
|
23
|
+
{ name: 'Alice', age: 30 },
|
|
24
|
+
{ name: 'Bob', age: 25 },
|
|
25
|
+
{ name: 'Charlie', age: 35 },
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
const defaultColumns: ColumnDef<Person, unknown>[] = [
|
|
29
|
+
{ accessorKey: 'name', header: 'Name' },
|
|
30
|
+
{ accessorKey: 'age', header: 'Age' },
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
function mountWithTable<T>(fn: () => T): { result: T; unmount: () => void } {
|
|
34
|
+
let result: T | undefined
|
|
35
|
+
const el = document.createElement('div')
|
|
36
|
+
document.body.appendChild(el)
|
|
37
|
+
const unmount = mount(
|
|
38
|
+
h(() => {
|
|
39
|
+
result = fn()
|
|
40
|
+
return null
|
|
41
|
+
}, null),
|
|
42
|
+
el,
|
|
43
|
+
)
|
|
44
|
+
return {
|
|
45
|
+
result: result!,
|
|
46
|
+
unmount: () => {
|
|
47
|
+
unmount()
|
|
48
|
+
el.remove()
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ─── useTable ─────────────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
describe('useTable', () => {
|
|
56
|
+
it('creates a table with core row model', () => {
|
|
57
|
+
const { result: table, unmount } = mountWithTable(() =>
|
|
58
|
+
useTable(() => ({
|
|
59
|
+
data: defaultData,
|
|
60
|
+
columns: defaultColumns,
|
|
61
|
+
getCoreRowModel: getCoreRowModel(),
|
|
62
|
+
})),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
const rows = table().getRowModel().rows
|
|
66
|
+
expect(rows).toHaveLength(3)
|
|
67
|
+
expect(rows[0]!.original.name).toBe('Alice')
|
|
68
|
+
expect(rows[1]!.original.name).toBe('Bob')
|
|
69
|
+
expect(rows[2]!.original.name).toBe('Charlie')
|
|
70
|
+
unmount()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('returns correct header groups', () => {
|
|
74
|
+
const { result: table, unmount } = mountWithTable(() =>
|
|
75
|
+
useTable(() => ({
|
|
76
|
+
data: defaultData,
|
|
77
|
+
columns: defaultColumns,
|
|
78
|
+
getCoreRowModel: getCoreRowModel(),
|
|
79
|
+
})),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
const headerGroups = table().getHeaderGroups()
|
|
83
|
+
expect(headerGroups).toHaveLength(1)
|
|
84
|
+
expect(headerGroups[0]!.headers).toHaveLength(2)
|
|
85
|
+
unmount()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('reactive data — table updates when data signal changes', () => {
|
|
89
|
+
const data = signal<Person[]>(defaultData)
|
|
90
|
+
const { result: table, unmount } = mountWithTable(() =>
|
|
91
|
+
useTable(() => ({
|
|
92
|
+
data: data(),
|
|
93
|
+
columns: defaultColumns,
|
|
94
|
+
getCoreRowModel: getCoreRowModel(),
|
|
95
|
+
})),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
expect(table().getRowModel().rows).toHaveLength(3)
|
|
99
|
+
|
|
100
|
+
data.set([...defaultData, { name: 'Diana', age: 28 }])
|
|
101
|
+
expect(table().getRowModel().rows).toHaveLength(4)
|
|
102
|
+
expect(table().getRowModel().rows[3]!.original.name).toBe('Diana')
|
|
103
|
+
unmount()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('reactive subscribers — computed derived from table re-evaluates on data change', () => {
|
|
107
|
+
const data = signal<Person[]>(defaultData)
|
|
108
|
+
let rowCount: Computed<number> | undefined
|
|
109
|
+
|
|
110
|
+
const { unmount } = mountWithTable(() => {
|
|
111
|
+
const table = useTable(() => ({
|
|
112
|
+
data: data(),
|
|
113
|
+
columns: defaultColumns,
|
|
114
|
+
getCoreRowModel: getCoreRowModel(),
|
|
115
|
+
}))
|
|
116
|
+
// A computed that depends on the table signal — should re-evaluate
|
|
117
|
+
// when data changes, proving the signal actually notifies subscribers.
|
|
118
|
+
rowCount = computed(() => table().getRowModel().rows.length)
|
|
119
|
+
return table
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
expect(rowCount!()).toBe(3)
|
|
123
|
+
|
|
124
|
+
data.set([...defaultData, { name: 'Diana', age: 28 }])
|
|
125
|
+
expect(rowCount!()).toBe(4)
|
|
126
|
+
|
|
127
|
+
data.set([defaultData[0]!])
|
|
128
|
+
expect(rowCount!()).toBe(1)
|
|
129
|
+
unmount()
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('reactive columns — table updates when columns signal changes', () => {
|
|
133
|
+
const cols = signal<ColumnDef<Person, unknown>[]>(defaultColumns)
|
|
134
|
+
const { result: table, unmount } = mountWithTable(() =>
|
|
135
|
+
useTable(() => ({
|
|
136
|
+
data: defaultData,
|
|
137
|
+
columns: cols(),
|
|
138
|
+
getCoreRowModel: getCoreRowModel(),
|
|
139
|
+
})),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
expect(table().getAllColumns()).toHaveLength(2)
|
|
143
|
+
|
|
144
|
+
cols.set([{ accessorKey: 'name', header: 'Name' }])
|
|
145
|
+
expect(table().getAllColumns()).toHaveLength(1)
|
|
146
|
+
unmount()
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('sorting — toggleSorting updates row order', () => {
|
|
150
|
+
const { result: table, unmount } = mountWithTable(() =>
|
|
151
|
+
useTable(() => ({
|
|
152
|
+
data: defaultData,
|
|
153
|
+
columns: defaultColumns,
|
|
154
|
+
getCoreRowModel: getCoreRowModel(),
|
|
155
|
+
getSortedRowModel: getSortedRowModel(),
|
|
156
|
+
})),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
// Sort by age ascending
|
|
160
|
+
table().getColumn('age')!.toggleSorting(false)
|
|
161
|
+
const rows = table().getRowModel().rows
|
|
162
|
+
expect(rows[0]!.original.age).toBe(25)
|
|
163
|
+
expect(rows[1]!.original.age).toBe(30)
|
|
164
|
+
expect(rows[2]!.original.age).toBe(35)
|
|
165
|
+
|
|
166
|
+
// Sort by age descending
|
|
167
|
+
table().getColumn('age')!.toggleSorting(true)
|
|
168
|
+
const desc = table().getRowModel().rows
|
|
169
|
+
expect(desc[0]!.original.age).toBe(35)
|
|
170
|
+
expect(desc[2]!.original.age).toBe(25)
|
|
171
|
+
unmount()
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('filtering — setFilterValue filters rows', () => {
|
|
175
|
+
const { result: table, unmount } = mountWithTable(() =>
|
|
176
|
+
useTable(() => ({
|
|
177
|
+
data: defaultData,
|
|
178
|
+
columns: defaultColumns,
|
|
179
|
+
getCoreRowModel: getCoreRowModel(),
|
|
180
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
181
|
+
})),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
table().getColumn('name')!.setFilterValue('Ali')
|
|
185
|
+
const filtered = table().getRowModel().rows
|
|
186
|
+
expect(filtered).toHaveLength(1)
|
|
187
|
+
expect(filtered[0]!.original.name).toBe('Alice')
|
|
188
|
+
unmount()
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('pagination — page size and navigation', () => {
|
|
192
|
+
const bigData: Person[] = Array.from({ length: 25 }, (_, i) => ({
|
|
193
|
+
name: `Person ${i}`,
|
|
194
|
+
age: 20 + i,
|
|
195
|
+
}))
|
|
196
|
+
|
|
197
|
+
const { result: table, unmount } = mountWithTable(() =>
|
|
198
|
+
useTable(() => ({
|
|
199
|
+
data: bigData,
|
|
200
|
+
columns: defaultColumns,
|
|
201
|
+
getCoreRowModel: getCoreRowModel(),
|
|
202
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
203
|
+
})),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
// Default page size is 10
|
|
207
|
+
expect(table().getRowModel().rows).toHaveLength(10)
|
|
208
|
+
expect(table().getCanNextPage()).toBe(true)
|
|
209
|
+
expect(table().getCanPreviousPage()).toBe(false)
|
|
210
|
+
|
|
211
|
+
table().nextPage()
|
|
212
|
+
expect(table().getRowModel().rows).toHaveLength(10)
|
|
213
|
+
expect(table().getRowModel().rows[0]!.original.name).toBe('Person 10')
|
|
214
|
+
|
|
215
|
+
table().nextPage()
|
|
216
|
+
expect(table().getRowModel().rows).toHaveLength(5)
|
|
217
|
+
expect(table().getCanNextPage()).toBe(false)
|
|
218
|
+
unmount()
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('row selection — toggleRowSelected updates selection state', () => {
|
|
222
|
+
const { result: table, unmount } = mountWithTable(() =>
|
|
223
|
+
useTable(() => ({
|
|
224
|
+
data: defaultData,
|
|
225
|
+
columns: defaultColumns,
|
|
226
|
+
getCoreRowModel: getCoreRowModel(),
|
|
227
|
+
enableRowSelection: true,
|
|
228
|
+
})),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
expect(table().getSelectedRowModel().rows).toHaveLength(0)
|
|
232
|
+
|
|
233
|
+
table().getRowModel().rows[0]!.toggleSelected(true)
|
|
234
|
+
expect(table().getSelectedRowModel().rows).toHaveLength(1)
|
|
235
|
+
expect(table().getSelectedRowModel().rows[0]!.original.name).toBe('Alice')
|
|
236
|
+
|
|
237
|
+
table().getRowModel().rows[0]!.toggleSelected(false)
|
|
238
|
+
expect(table().getSelectedRowModel().rows).toHaveLength(0)
|
|
239
|
+
unmount()
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
it('column visibility — toggleVisibility hides columns', () => {
|
|
243
|
+
const { result: table, unmount } = mountWithTable(() =>
|
|
244
|
+
useTable(() => ({
|
|
245
|
+
data: defaultData,
|
|
246
|
+
columns: defaultColumns,
|
|
247
|
+
getCoreRowModel: getCoreRowModel(),
|
|
248
|
+
})),
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
expect(table().getVisibleFlatColumns()).toHaveLength(2)
|
|
252
|
+
|
|
253
|
+
table().getColumn('age')!.toggleVisibility(false)
|
|
254
|
+
expect(table().getVisibleFlatColumns()).toHaveLength(1)
|
|
255
|
+
expect(table().getVisibleFlatColumns()[0]!.id).toBe('name')
|
|
256
|
+
|
|
257
|
+
table().getColumn('age')!.toggleVisibility(true)
|
|
258
|
+
expect(table().getVisibleFlatColumns()).toHaveLength(2)
|
|
259
|
+
unmount()
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it('getState returns merged state', () => {
|
|
263
|
+
const { result: table, unmount } = mountWithTable(() =>
|
|
264
|
+
useTable(() => ({
|
|
265
|
+
data: defaultData,
|
|
266
|
+
columns: defaultColumns,
|
|
267
|
+
getCoreRowModel: getCoreRowModel(),
|
|
268
|
+
getSortedRowModel: getSortedRowModel(),
|
|
269
|
+
})),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
expect(table().getState().sorting).toEqual([])
|
|
273
|
+
table().getColumn('name')!.toggleSorting(false)
|
|
274
|
+
expect(table().getState().sorting).toEqual([{ id: 'name', desc: false }])
|
|
275
|
+
unmount()
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
it('createColumnHelper works with useTable', () => {
|
|
279
|
+
const columnHelper = createColumnHelper<Person>()
|
|
280
|
+
const columns = [
|
|
281
|
+
columnHelper.accessor('name', { header: 'Full Name' }),
|
|
282
|
+
columnHelper.accessor('age', { header: 'Years' }),
|
|
283
|
+
]
|
|
284
|
+
|
|
285
|
+
const { result: table, unmount } = mountWithTable(() =>
|
|
286
|
+
useTable(() => ({
|
|
287
|
+
data: defaultData,
|
|
288
|
+
columns,
|
|
289
|
+
getCoreRowModel: getCoreRowModel(),
|
|
290
|
+
})),
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
const headers = table().getHeaderGroups()[0]!.headers
|
|
294
|
+
expect(headers).toHaveLength(2)
|
|
295
|
+
unmount()
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
// ─── flexRender ──────────────────────────────────────────────────────────────
|
|
300
|
+
|
|
301
|
+
describe('flexRender', () => {
|
|
302
|
+
it('renders a string directly', () => {
|
|
303
|
+
expect(flexRender('Hello', {})).toBe('Hello')
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('renders a number directly', () => {
|
|
307
|
+
expect(flexRender(42, {})).toBe(42)
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
it('renders null for undefined/null', () => {
|
|
311
|
+
expect(flexRender(undefined, {})).toBeNull()
|
|
312
|
+
expect(flexRender(null, {})).toBeNull()
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
it('calls a function with props', () => {
|
|
316
|
+
const fn = (props: { value: string }) => `Value: ${props.value}`
|
|
317
|
+
expect(flexRender(fn, { value: 'test' })).toBe('Value: test')
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('passes through VNodes as-is', () => {
|
|
321
|
+
const vnode = h('span', null, 'cell content')
|
|
322
|
+
const result = flexRender(vnode as unknown, {})
|
|
323
|
+
expect(result).toBe(vnode)
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
it('returns null for unsupported types', () => {
|
|
327
|
+
expect(flexRender(true as unknown, {})).toBeNull()
|
|
328
|
+
expect(flexRender({} as unknown, {})).toBeNull()
|
|
329
|
+
})
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
// ─── onStateChange with non-function updater ─────────────────────────────────
|
|
333
|
+
|
|
334
|
+
describe('useTable — onStateChange with direct state object', () => {
|
|
335
|
+
it('handles a non-function updater (plain state object) passed to onStateChange', () => {
|
|
336
|
+
const { result: table, unmount } = mountWithTable(() =>
|
|
337
|
+
useTable(() => ({
|
|
338
|
+
data: defaultData,
|
|
339
|
+
columns: defaultColumns,
|
|
340
|
+
getCoreRowModel: getCoreRowModel(),
|
|
341
|
+
getSortedRowModel: getSortedRowModel(),
|
|
342
|
+
})),
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
// Grab the current full state, then call onStateChange with a direct
|
|
346
|
+
// state object (not an updater function) to exercise the else-branch.
|
|
347
|
+
const currentState = table().getState()
|
|
348
|
+
const newState = {
|
|
349
|
+
...currentState,
|
|
350
|
+
sorting: [{ id: 'name', desc: true }],
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Access the resolved onStateChange and invoke it with a plain object
|
|
354
|
+
table().options.onStateChange(newState as any)
|
|
355
|
+
|
|
356
|
+
// The table should now reflect the new sorting state
|
|
357
|
+
expect(table().getState().sorting).toEqual([{ id: 'name', desc: true }])
|
|
358
|
+
unmount()
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
it('propagates non-function updater to user-provided onStateChange callback', () => {
|
|
362
|
+
const stateChanges: unknown[] = []
|
|
363
|
+
|
|
364
|
+
const { result: table, unmount } = mountWithTable(() =>
|
|
365
|
+
useTable(() => ({
|
|
366
|
+
data: defaultData,
|
|
367
|
+
columns: defaultColumns,
|
|
368
|
+
getCoreRowModel: getCoreRowModel(),
|
|
369
|
+
onStateChange: (updater) => {
|
|
370
|
+
stateChanges.push(updater)
|
|
371
|
+
},
|
|
372
|
+
})),
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
const currentState = table().getState()
|
|
376
|
+
const newState = { ...currentState, columnOrder: ['age', 'name'] }
|
|
377
|
+
|
|
378
|
+
table().options.onStateChange(newState as any)
|
|
379
|
+
|
|
380
|
+
// The user callback should have received the plain state object
|
|
381
|
+
expect(stateChanges.length).toBeGreaterThanOrEqual(1)
|
|
382
|
+
const lastChange = stateChanges[stateChanges.length - 1]
|
|
383
|
+
expect(lastChange).toEqual(
|
|
384
|
+
expect.objectContaining({ columnOrder: ['age', 'name'] }),
|
|
385
|
+
)
|
|
386
|
+
unmount()
|
|
387
|
+
})
|
|
388
|
+
})
|
package/src/use-table.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { onUnmount } from '@pyreon/core'
|
|
2
|
+
import { signal, computed, effect, batch } from '@pyreon/reactivity'
|
|
3
|
+
import type { Computed } from '@pyreon/reactivity'
|
|
4
|
+
import {
|
|
5
|
+
createTable,
|
|
6
|
+
type RowData,
|
|
7
|
+
type TableOptions,
|
|
8
|
+
type TableOptionsResolved,
|
|
9
|
+
type TableState,
|
|
10
|
+
type Table,
|
|
11
|
+
type Updater,
|
|
12
|
+
} from '@tanstack/table-core'
|
|
13
|
+
|
|
14
|
+
export type UseTableOptions<TData extends RowData> = () => TableOptions<TData>
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create a reactive TanStack Table instance. Returns a read-only signal
|
|
18
|
+
* that holds the Table instance — read it in effects or templates to
|
|
19
|
+
* track state changes.
|
|
20
|
+
*
|
|
21
|
+
* Options are passed as a function so reactive signals (e.g. data, columns)
|
|
22
|
+
* can be read inside, and the table updates automatically when they change.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* const data = signal([{ name: "Alice" }, { name: "Bob" }])
|
|
26
|
+
* const table = useTable(() => ({
|
|
27
|
+
* data: data(),
|
|
28
|
+
* columns: [{ accessorKey: "name", header: "Name" }],
|
|
29
|
+
* getCoreRowModel: getCoreRowModel(),
|
|
30
|
+
* }))
|
|
31
|
+
* // In template: () => table().getRowModel().rows
|
|
32
|
+
*/
|
|
33
|
+
export function useTable<TData extends RowData>(
|
|
34
|
+
options: UseTableOptions<TData>,
|
|
35
|
+
): Computed<Table<TData>> {
|
|
36
|
+
// Internal state managed by the adapter — merged with user-provided state.
|
|
37
|
+
const tableState = signal<TableState>({} as TableState)
|
|
38
|
+
|
|
39
|
+
// Version counter — Pyreon signals use Object.is for equality, so
|
|
40
|
+
// setting the same table reference is a no-op. We bump a version
|
|
41
|
+
// counter to force the computed to re-evaluate and notify consumers.
|
|
42
|
+
const version = signal(0)
|
|
43
|
+
|
|
44
|
+
// Resolve user options with adapter-required defaults.
|
|
45
|
+
const resolvedOptions: TableOptionsResolved<TData> = {
|
|
46
|
+
state: {},
|
|
47
|
+
onStateChange() {
|
|
48
|
+
/* default noop */
|
|
49
|
+
},
|
|
50
|
+
renderFallbackValue: null,
|
|
51
|
+
...options(),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Create the table instance once.
|
|
55
|
+
const table = createTable(resolvedOptions)
|
|
56
|
+
|
|
57
|
+
// Initialize internal state from the table's initial state.
|
|
58
|
+
tableState.set(table.initialState)
|
|
59
|
+
|
|
60
|
+
// The signal that consumers read — depends on `version` so it
|
|
61
|
+
// re-notifies whenever we bump the version after a state/option change.
|
|
62
|
+
const tableSig = computed(() => {
|
|
63
|
+
version()
|
|
64
|
+
return table
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// Sync options reactively: when signals inside options() change, or when
|
|
68
|
+
// internal state changes, update the table and notify consumers.
|
|
69
|
+
const cleanup = effect(() => {
|
|
70
|
+
const userOpts = options()
|
|
71
|
+
const currentState = tableState()
|
|
72
|
+
let stateChanged = false
|
|
73
|
+
|
|
74
|
+
table.setOptions((prev) => ({
|
|
75
|
+
...prev,
|
|
76
|
+
...userOpts,
|
|
77
|
+
state: {
|
|
78
|
+
...currentState,
|
|
79
|
+
...userOpts.state,
|
|
80
|
+
},
|
|
81
|
+
onStateChange: (updater: Updater<TableState>) => {
|
|
82
|
+
const newState =
|
|
83
|
+
typeof updater === 'function' ? updater(tableState.peek()) : updater
|
|
84
|
+
|
|
85
|
+
stateChanged = true
|
|
86
|
+
batch(() => {
|
|
87
|
+
tableState.set(newState)
|
|
88
|
+
version.update((n) => n + 1)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
userOpts.onStateChange?.(updater)
|
|
92
|
+
},
|
|
93
|
+
}))
|
|
94
|
+
|
|
95
|
+
// Only bump if setOptions didn't already trigger a state change
|
|
96
|
+
if (!stateChanged) {
|
|
97
|
+
version.update((n) => n + 1)
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// Clean up the effect when the component unmounts.
|
|
102
|
+
onUnmount(() => cleanup.dispose())
|
|
103
|
+
|
|
104
|
+
return tableSig
|
|
105
|
+
}
|