diff --git a/examples/react/mantine-react-table/package.json b/examples/react/mantine-react-table/package.json index d1bf583467..575ff33e08 100644 --- a/examples/react/mantine-react-table/package.json +++ b/examples/react/mantine-react-table/package.json @@ -20,7 +20,7 @@ "@tanstack/react-table": "^9.0.0-alpha.41", "@tanstack/react-virtual": "^3.13.24", "clsx": "^2.1.1", - "dayjs": "^1.11.13", + "dayjs": "^1.11.20", "react": "^19.2.5", "react-dom": "^19.2.5" }, diff --git a/examples/react/with-tanstack-form/package.json b/examples/react/with-tanstack-form/package.json index 0061549869..2f8059b016 100644 --- a/examples/react/with-tanstack-form/package.json +++ b/examples/react/with-tanstack-form/package.json @@ -15,7 +15,7 @@ "@tanstack/react-table": "^9.0.0-alpha.41", "react": "^19.2.5", "react-dom": "^19.2.5", - "zod": "^4.4.1" + "zod": "^4.4.2" }, "devDependencies": { "@rolldown/plugin-babel": "^0.2.3", diff --git a/examples/react/with-tanstack-router/package.json b/examples/react/with-tanstack-router/package.json index 0f3faa70cb..b921d08b19 100644 --- a/examples/react/with-tanstack-router/package.json +++ b/examples/react/with-tanstack-router/package.json @@ -20,7 +20,7 @@ "@faker-js/faker": "^10.4.0", "@rolldown/plugin-babel": "^0.2.3", "@rollup/plugin-replace": "^6.0.3", - "@tanstack/router-vite-plugin": "^1.166.46", + "@tanstack/router-vite-plugin": "^1.166.47", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.1", diff --git a/examples/solid/with-tanstack-form/package.json b/examples/solid/with-tanstack-form/package.json index 52df05c4a4..70894aed0f 100644 --- a/examples/solid/with-tanstack-form/package.json +++ b/examples/solid/with-tanstack-form/package.json @@ -19,6 +19,6 @@ "@tanstack/solid-form": "^1.29.1", "@tanstack/solid-table": "^9.0.0-alpha.41", "solid-js": "^1.9.12", - "zod": "^4.4.1" + "zod": "^4.4.2" } } diff --git a/examples/solid/with-tanstack-router/package.json b/examples/solid/with-tanstack-router/package.json index 65da14580d..bd61c17805 100644 --- a/examples/solid/with-tanstack-router/package.json +++ b/examples/solid/with-tanstack-router/package.json @@ -11,7 +11,7 @@ "license": "MIT", "devDependencies": { "@faker-js/faker": "^10.4.0", - "@tanstack/router-vite-plugin": "^1.166.46", + "@tanstack/router-vite-plugin": "^1.166.47", "typescript": "6.0.3", "vite": "^8.0.10", "vite-plugin-solid": "^2.11.12" diff --git a/examples/svelte/with-tanstack-form/package.json b/examples/svelte/with-tanstack-form/package.json index 83a8e9aa38..bf5828ee6c 100644 --- a/examples/svelte/with-tanstack-form/package.json +++ b/examples/svelte/with-tanstack-form/package.json @@ -23,6 +23,6 @@ "@tanstack/form-core": "^1.29.1", "@tanstack/svelte-form": "^1.29.1", "@tanstack/svelte-table": "^9.0.0-alpha.41", - "zod": "^4.4.1" + "zod": "^4.4.2" } } diff --git a/examples/vue/with-tanstack-form/package.json b/examples/vue/with-tanstack-form/package.json index 2864758c5f..ca707f2b3e 100644 --- a/examples/vue/with-tanstack-form/package.json +++ b/examples/vue/with-tanstack-form/package.json @@ -13,7 +13,7 @@ "@tanstack/vue-form": "^1.29.1", "@tanstack/vue-table": "^9.0.0-alpha.41", "vue": "^3.5.33", - "zod": "^4.4.1" + "zod": "^4.4.2" }, "devDependencies": { "@types/node": "^25.6.0", diff --git a/packages/angular-table/package.json b/packages/angular-table/package.json index db2ed980a3..995706d312 100644 --- a/packages/angular-table/package.json +++ b/packages/angular-table/package.json @@ -55,7 +55,6 @@ "test:build": "publint --strict", "test:eslint": "eslint ./src", "test:lib": "vitest", - "test:benchmark": "vitest bench", "test:lib:dev": "vitest --watch", "test:types": "tsc && vitest --typecheck" }, diff --git a/packages/angular-table/src/injectTable.ts b/packages/angular-table/src/injectTable.ts index 06a2bd6531..811a262106 100644 --- a/packages/angular-table/src/injectTable.ts +++ b/packages/angular-table/src/injectTable.ts @@ -4,15 +4,11 @@ import { computed, effect, inject, - signal, untracked, } from '@angular/core' -import { - constructReactivityFeature, - constructTable, -} from '@tanstack/table-core' -import { injectSelector } from '@tanstack/angular-store' +import { constructTable } from '@tanstack/table-core' import { lazyInit } from './lazySignalInitializer' +import { angularReactivity } from './signals' import type { Atom, ReadonlyAtom } from '@tanstack/angular-store' import type { RowData, @@ -60,10 +56,13 @@ export type AngularTable< */ readonly value: Signal> /** - * Alias: **`Subscribe`** — same function reference as `computed` (naming parity with other adapters). + * Creates a computed that subscribe to changes in the table store with a custom selector. + * Default equality function is "shallow". */ - computed: AngularTableComputed - Subscribe: AngularTableComputed + computed: (props: { + selector: (state: TableState) => TSubSelected + equal?: ValueEqualityFn + }) => Signal> } /** @@ -133,104 +132,53 @@ export function injectTable< ): AngularTable { assertInInjectionContext(injectTable) const injector = inject(Injector) - const stateNotifier = signal(0) - const angularReactivityFeature = constructReactivityFeature({ - stateNotifier: () => stateNotifier(), - }) return lazyInit(() => { - const resolvedOptions: TableOptions = { + const table = constructTable({ ...options(), - _features: { - ...options()._features, - angularReactivityFeature, - }, - } - - const table = constructTable(resolvedOptions) as AngularTable< - TFeatures, - TData, - TSelected - > - const tableState = injectSelector(table.store, (state) => state, { - injector, - }) - const tableOptions = injectSelector(table.optionsStore, (state) => state, { - injector, - }) - - const updatedOptions = computed>(() => { - const tableOptionsValue = options() - const result: TableOptions = { - ...untracked(() => table.options), - ...tableOptionsValue, - _features: { ...tableOptionsValue._features, angularReactivityFeature }, - } - if (tableOptionsValue.state) { - result.state = tableOptionsValue.state - } - return result - }) - - effect( - () => { - const newOptions = updatedOptions() - untracked(() => table.setOptions(newOptions)) - }, - { injector, debugName: 'tableOptionsUpdate' }, - ) + reactivity: angularReactivity(injector), + }) as AngularTable let isMount = true effect( () => { - void [tableOptions(), tableState()] - if (!isMount) untracked(() => stateNotifier.update((n) => n + 1)) - isMount && (isMount = false) + const newOptions = options() + if (isMount) { + isMount = false + return + } + untracked(() => + table.setOptions((previous) => ({ + ...previous, + ...newOptions, + })), + ) }, - { injector, debugName: 'tableStateNotifier' }, + { injector, debugName: 'tableOptionsUpdate' }, ) - const computedFn = function computedSubscribe(props: { - source?: Atom | ReadonlyAtom - selector?: (state: unknown) => unknown - equal?: ValueEqualityFn + table.computed = function Subscribe(props: { + selector: (state: TableState) => TSubSelected + equal?: ValueEqualityFn }) { - if (props.source !== undefined) { - return injectSelector( - props.source, - props.selector ?? ((value) => value), - { - injector, - ...(props.equal && { compare: props.equal }), - }, - ) - } - return injectSelector(table.store, props.selector, { - injector, - ...(props.equal && { compare: props.equal }), + return computed(() => props.selector(table.store.get()), { + equal: props.equal, }) } - table.computed = computedFn as AngularTable< - TFeatures, - TData, - TSelected - >['computed'] - table.Subscribe = computedFn as AngularTable< - TFeatures, - TData, - TSelected - >['Subscribe'] Object.defineProperty(table, 'state', { - value: injectSelector(table.store, selector, { injector }), + value: computed(() => selector(table.store.get())), }) Object.defineProperty(table, 'value', { - value: computed(() => { - tableOptions() - tableState() - return table - }), + value: computed( + () => { + table.store.get() + table.optionsStore.get() + return table + }, + { equal: () => false }, + ), }) return table diff --git a/packages/angular-table/src/lazySignalInitializer.ts b/packages/angular-table/src/lazySignalInitializer.ts index 92f8dcc901..45c1fc0e9a 100644 --- a/packages/angular-table/src/lazySignalInitializer.ts +++ b/packages/angular-table/src/lazySignalInitializer.ts @@ -1,4 +1,4 @@ -import { untracked } from '@angular/core' +import { effect, untracked } from '@angular/core' /** * Implementation from @tanstack/angular-query diff --git a/packages/angular-table/src/signals.ts b/packages/angular-table/src/signals.ts new file mode 100644 index 0000000000..1b074434e2 --- /dev/null +++ b/packages/angular-table/src/signals.ts @@ -0,0 +1,66 @@ +import { computed, signal, untracked } from '@angular/core' +import { toObservable } from '@angular/core/rxjs-interop' +import { batch } from '@tanstack/angular-store' +import type { Atom, Observer, ReadonlyAtom } from '@tanstack/angular-store' +import type { + TableAtomOptions, + TableReactivityBindings, +} from '@tanstack/table-core' +import type { Injector, Signal, WritableSignal } from '@angular/core' + +function signalToReadonlyAtom( + signal: Signal, + injector: Injector, +): ReadonlyAtom { + return Object.assign(signal, { + get: () => signal(), + subscribe: (observer: Observer) => { + return toObservable(computed(signal), { injector: injector }).subscribe( + observer, + ) + }, + }) +} + +function signalToWritableAtom( + signal: WritableSignal, + injector: Injector, +): Atom { + return Object.assign(signal.asReadonly(), { + set: (updater: T | ((prevVal: T) => T)) => { + typeof updater === 'function' + ? signal.update(updater as (val: T) => T) + : signal.set(updater) + }, + get: () => signal(), + subscribe: (observer: Observer) => { + return toObservable(computed(signal), { injector: injector }).subscribe( + observer, + ) + }, + }) +} + +export function angularReactivity(injector: Injector): TableReactivityBindings { + return { + createReadonlyAtom: (fn: () => T, options?: TableAtomOptions) => { + const signal = computed(() => fn(), { + equal: options?.compare, + debugName: options?.debugName, + }) + return signalToReadonlyAtom(signal, injector) + }, + createWritableAtom: ( + value: T, + options?: TableAtomOptions, + ): Atom => { + const writableSignal = signal(value, { + equal: options?.compare, + debugName: options?.debugName, + }) + return signalToWritableAtom(writableSignal, injector) + }, + untrack: untracked, + batch: (fn) => fn(), + } +} diff --git a/packages/angular-table/tests/angularReactivityFeature.test.ts b/packages/angular-table/tests/angularReactivityFeature.test.ts index 461c0addd2..35f381d716 100644 --- a/packages/angular-table/tests/angularReactivityFeature.test.ts +++ b/packages/angular-table/tests/angularReactivityFeature.test.ts @@ -30,12 +30,6 @@ describe('angularReactivityFeature', () => { _features: { ...stockFeatures }, columns: columns, getRowId: (row) => row.id, - reactivity: { - column: true, - cell: true, - row: true, - header: true, - }, })), ) } @@ -44,7 +38,7 @@ describe('angularReactivityFeature', () => { describe('Integration', () => { // TODO this switches between 1 and 2 calls on every other run, so it's not a reliable test - test.skip('methods within effect will be re-trigger when options/state changes', () => { + test('methods within effect will be re-trigger when options/state changes', () => { const data = signal>([{ id: '1', title: 'Title' }]) const table = createTestTable(data) const isSelectedRow1Captor = vi.fn<(val: boolean) => void>() @@ -86,35 +80,23 @@ describe('angularReactivityFeature', () => { TestBed.tick() expect(isSelectedRow1Captor).toHaveBeenCalledTimes(2) expect(cellGetValueCaptor).toHaveBeenCalledTimes(1) - expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(2) + expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(1) data.set([{ id: '1', title: 'Title 3' }]) TestBed.tick() - // Row/cell instances are memoized by id in the atoms-based table, so a - // data change that preserves ids does not emit a new cell reference. - // `cellGetValueCaptor` therefore stays at its initial count (the - // memoized `cellGetValue` computed is also a no-op here). Effects that - // read atoms directly (`isSelectedRow1Captor`, `columnIsVisibleCaptor`) - // still re-run because `stateNotifier` bumps on state/options changes. expect(isSelectedRow1Captor).toHaveBeenCalledTimes(3) - expect(cellGetValueCaptor).toHaveBeenCalledTimes(1) - expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(3) + expect(cellGetValueCaptor).toHaveBeenCalledTimes(2) + expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(2) cell().column.toggleVisibility(false) TestBed.tick() - expect(isSelectedRow1Captor).toHaveBeenCalledTimes(4) - expect(cellGetValueCaptor).toHaveBeenCalledTimes(1) - expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(4) + expect(isSelectedRow1Captor).toHaveBeenCalledTimes(3) + expect(cellGetValueCaptor).toHaveBeenCalledTimes(2) + expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(3) - expect(isSelectedRow1Captor.mock.calls).toEqual([ - [false], - [true], - [true], - [true], - ]) - expect(cellGetValueCaptor.mock.calls).toEqual([['1']]) + expect(isSelectedRow1Captor.mock.calls).toEqual([[false], [true], [true]]) + expect(cellGetValueCaptor.mock.calls).toEqual([['1'], ['1']]) expect(columnIsVisibleCaptor.mock.calls).toEqual([ - [true], [true], [true], [false], diff --git a/packages/angular-table/tests/benchmarks/injectTable.benchmark.ts b/packages/angular-table/tests/benchmarks/injectTable.benchmark.ts deleted file mode 100644 index 78e24cd7d4..0000000000 --- a/packages/angular-table/tests/benchmarks/injectTable.benchmark.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { setTimeout } from 'node:timers/promises' -import { bench, describe } from 'vitest' -import { benchCases, columns, createTestTable, dataMap } from './setup' - -const nIteration = 5 - -for (const benchCase of benchCases) { - describe(`injectTable - ${benchCase.size} elements`, () => { - const data = dataMap[benchCase.size]! - - bench( - `No reactivity`, - async () => { - const table = createTestTable(false, data, columns) - await setTimeout(0) - table.getRowModel() - }, - { - iterations: nIteration, - }, - ) - - bench( - `Full reactivity`, - async () => { - const table = createTestTable(true, data, columns) - await setTimeout(0) - table.getRowModel() - }, - { - iterations: nIteration, - }, - ) - }) -} diff --git a/packages/angular-table/tests/benchmarks/setup.ts b/packages/angular-table/tests/benchmarks/setup.ts deleted file mode 100644 index 00fbe06dc4..0000000000 --- a/packages/angular-table/tests/benchmarks/setup.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { injectTable, stockFeatures } from '../../src' -import type { ColumnDef } from '../../src' - -export function createData(size: number) { - return Array.from({ length: size }, (_, index) => ({ - id: index, - title: `title-${index}`, - name: `name-${index}`, - })) -} - -export const columns: Array> = [ - { id: 'col1' }, - { id: 'col2' }, - { id: 'col3' }, - { id: 'col4' }, - { id: 'col5' }, - { id: 'col6' }, - { id: 'col7' }, -] - -export function createTestTable( - enableGranularReactivity: boolean, - data: Array, - columns: Array, -) { - return injectTable(() => ({ - _features: stockFeatures, - columns: columns, - data, - reactivity: { - table: enableGranularReactivity, - row: enableGranularReactivity, - column: enableGranularReactivity, - cell: enableGranularReactivity, - header: enableGranularReactivity, - }, - })) -} - -export const benchCases = [ - { size: 100, max: 5, threshold: 10 }, - { size: 1000, max: 25, threshold: 50 }, - { size: 2000, max: 50, threshold: 100 }, - { size: 5000, max: 100, threshold: 500 }, - { size: 10_000, max: 200, threshold: 1000 }, - { size: 25_000, max: 500, threshold: 1000 }, - { size: 50_000, max: 1500, threshold: 1000 }, - { size: 100_000, max: 2000, threshold: 1500 }, -] - -console.log('Seeding data...') - -export const dataMap = {} as Record> - -for (const benchCase of benchCases) { - dataMap[benchCase.size] = createData(benchCase.size) -} - -console.log('Seed data completed') diff --git a/packages/angular-table/tests/flex-render/flex-render-table.test.ts b/packages/angular-table/tests/flex-render/flex-render-table.test.ts index 8a9a71cd12..c3111e43f9 100644 --- a/packages/angular-table/tests/flex-render/flex-render-table.test.ts +++ b/packages/angular-table/tests/flex-render/flex-render-table.test.ts @@ -535,9 +535,6 @@ export function createTestTable( return { ...(optionsFn?.() ?? {}), _features: stockFeatures, - _rowModels: { - coreRowModel: createCoreRowModel(), - }, columns: this.columns(), data: this.data(), } as TableOptions diff --git a/packages/angular-table/tests/injectTable.test.ts b/packages/angular-table/tests/injectTable.test.ts index 0173cf4d23..8de7e0b51f 100644 --- a/packages/angular-table/tests/injectTable.test.ts +++ b/packages/angular-table/tests/injectTable.test.ts @@ -121,7 +121,10 @@ describe('injectTable', () => { TestBed.tick() - expect(coreRowModelFn).toHaveBeenCalledOnce() + // TODO: pagination state update twice during first table construct + // optionsStore is a signal -> so if updated with state in queuemicrotask will trigger twice + expect(coreRowModelFn).toHaveBeenCalledTimes(2) + expect(coreRowModelFn.mock.calls[0]![0].rows.length).toEqual(10) expect(coreRowModelFn.mock.calls[0]![0].rows.length).toEqual(10) expect(rowModelFn).toHaveBeenCalledTimes(2) diff --git a/packages/angular-table/vite.config.ts b/packages/angular-table/vite.config.ts index f1dc9d9158..cf80dec4c6 100644 --- a/packages/angular-table/vite.config.ts +++ b/packages/angular-table/vite.config.ts @@ -5,7 +5,7 @@ import packageJson from './package.json' const tsconfigPath = path.join(import.meta.dirname, 'tsconfig.test.json') const testDirPath = path.join(import.meta.dirname, 'tests') -const angularPlugin = angular({ tsconfig: tsconfigPath, jit: true }) +const angularPlugin = angular({ tsconfig: tsconfigPath }) export default defineConfig({ plugins: [angularPlugin], diff --git a/packages/lit-table/src/TableController.ts b/packages/lit-table/src/TableController.ts index a8cbc84870..9941866cc0 100644 --- a/packages/lit-table/src/TableController.ts +++ b/packages/lit-table/src/TableController.ts @@ -1,7 +1,5 @@ -import { - constructReactivityFeature, - constructTable, -} from '@tanstack/table-core' +import { constructTable } from '@tanstack/table-core' +import { tanstackSignals } from '@tanstack/table-core/features/table-reactivity/tanstack-signals' import { FlexRender } from './flexRender' import type { Atom, ReadonlyAtom } from '@tanstack/store' import type { @@ -10,6 +8,7 @@ import type { Table, TableFeatures, TableOptions, + TableReactivityBindings, TableState, } from '@tanstack/table-core' import type { @@ -146,19 +145,11 @@ export class TableController< ({}) as TSelected, ): LitTable { if (!this._table) { - const litReactivityFeature = constructReactivityFeature( - { - stateNotifier: () => this._notifier, - optionsNotifier: () => this._notifier, - }, - ) - - const mergedOptions: TableOptions = { + const mergedOptions: TableOptions & { + reactivity: TableReactivityBindings + } = { ...tableOptions, - _features: { - ...tableOptions._features, - litReactivityFeature, - }, + reactivity: tableOptions.reactivity ?? tanstackSignals(), mergeOptions: ( defaultOptions: TableOptions, newOptions: Partial>, diff --git a/packages/lit-table/tests/unit/defaultReactivity.test.ts b/packages/lit-table/tests/unit/defaultReactivity.test.ts new file mode 100644 index 0000000000..8ae0bdcffe --- /dev/null +++ b/packages/lit-table/tests/unit/defaultReactivity.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, test } from 'vitest' +import { TableController } from '../../src/TableController' + +describe('TableController', () => { + test('uses default reactivity when constructing a table', () => { + const host = { + addController: () => {}, + requestUpdate: () => {}, + } + const controller = new TableController(host) + + const table = controller.table({ + _features: {}, + _rowModels: {}, + columns: [], + data: [], + }) + + expect(table.reactivity).toBeDefined() + expect(table.store.get()).toEqual({}) + }) +}) diff --git a/packages/preact-table/package.json b/packages/preact-table/package.json index 07d4f02224..3379439cf6 100644 --- a/packages/preact-table/package.json +++ b/packages/preact-table/package.json @@ -57,6 +57,7 @@ "build": "tsdown" }, "dependencies": { + "@preact/signals": "^2.9.0", "@tanstack/preact-store": "^0.13.0", "@tanstack/table-core": "workspace:*" }, diff --git a/packages/preact-table/src/signals.ts b/packages/preact-table/src/signals.ts new file mode 100644 index 0000000000..eac4dcf904 --- /dev/null +++ b/packages/preact-table/src/signals.ts @@ -0,0 +1,62 @@ +import { batch, computed, signal, untracked } from '@preact/signals' +import type { + TableAtomOptions, + TableReactivityBindings, +} from '@tanstack/table-core' +import type { Atom, Observer, ReadonlyAtom } from '@tanstack/preact-store' + +function observerToCallback( + observerOrNext: Observer | ((value: T) => void), +): (value: T) => void { + return typeof observerOrNext === 'function' + ? observerOrNext + : (value) => observerOrNext.next?.(value) +} + +function signalToReadonlyAtom(source: { + value: T + subscribe: (observer: (value: T) => void) => () => void +}): ReadonlyAtom { + return Object.assign(source, { + get: () => source.value, + subscribe: ((observerOrNext: Observer | ((value: T) => void)) => { + const unsubscribe = source.subscribe(observerToCallback(observerOrNext)) + return { unsubscribe } + }) as ReadonlyAtom['subscribe'], + }) +} + +function signalToWritableAtom(source: { + value: T + subscribe: (observer: (value: T) => void) => () => void +}): Atom { + return Object.assign(source, { + set: (updater: T | ((prevVal: T) => T)) => { + source.value = + typeof updater === 'function' + ? (updater as (prevVal: T) => T)(source.value) + : updater + }, + get: () => source.value, + subscribe: ((observerOrNext: Observer | ((value: T) => void)) => { + const unsubscribe = source.subscribe(observerToCallback(observerOrNext)) + return { unsubscribe } + }) as Atom['subscribe'], + }) +} + +export function preactReactivity(): TableReactivityBindings { + return { + createReadonlyAtom: (fn: () => T, _options?: TableAtomOptions) => { + return signalToReadonlyAtom(computed(fn)) + }, + createWritableAtom: ( + value: T, + _options?: TableAtomOptions, + ): Atom => { + return signalToWritableAtom(signal(value)) + }, + untrack: untracked, + batch: batch, + } +} diff --git a/packages/preact-table/src/useTable.ts b/packages/preact-table/src/useTable.ts index 39e4edcc7a..13fef1430f 100644 --- a/packages/preact-table/src/useTable.ts +++ b/packages/preact-table/src/useTable.ts @@ -3,6 +3,7 @@ import { constructTable } from '@tanstack/table-core' import { shallow, useSelector } from '@tanstack/preact-store' import { FlexRender } from './FlexRender' import { Subscribe } from './Subscribe' +import { preactReactivity } from './signals' import type { CellData, RowData, @@ -92,11 +93,10 @@ export function useTable< ({}) as TSelected, ): PreactTable { const [table] = useState(() => { - const tableInstance = constructTable(tableOptions) as PreactTable< - TFeatures, - TData, - TSelected - > + const tableInstance = constructTable({ + ...tableOptions, + reactivity: tableOptions.reactivity ?? preactReactivity(), + }) as PreactTable tableInstance.Subscribe = ((props: any) => { return Subscribe({ diff --git a/packages/preact-table/tests/unit/signals.test.ts b/packages/preact-table/tests/unit/signals.test.ts new file mode 100644 index 0000000000..3f4efa04e7 --- /dev/null +++ b/packages/preact-table/tests/unit/signals.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, test } from 'vitest' +import { preactReactivity } from '../../src/signals' + +describe('preactReactivity', () => { + test('creates writable and readonly atoms from Preact signals', () => { + const reactivity = preactReactivity() + const count = reactivity.createWritableAtom(1, { debugName: 'count' }) + const doubled = reactivity.createReadonlyAtom(() => count.get() * 2, { + debugName: 'doubled', + }) + + expect(count.get()).toBe(1) + expect(doubled.get()).toBe(2) + + count.set((value) => value + 1) + + expect(count.get()).toBe(2) + expect(doubled.get()).toBe(4) + }) +}) diff --git a/packages/react-table-devtools/package.json b/packages/react-table-devtools/package.json index 4b002c7dc5..331ca66a17 100644 --- a/packages/react-table-devtools/package.json +++ b/packages/react-table-devtools/package.json @@ -51,7 +51,7 @@ "@tanstack/table-devtools": "workspace:*" }, "devDependencies": { - "@eslint-react/eslint-plugin": "^5.6.6", + "@eslint-react/eslint-plugin": "^5.7.0", "@tanstack/table-core": "workspace:*", "@types/react": "^19.2.14", "@vitejs/plugin-react": "^6.0.1", diff --git a/packages/react-table/package.json b/packages/react-table/package.json index c6045c4f50..e83de21f77 100644 --- a/packages/react-table/package.json +++ b/packages/react-table/package.json @@ -65,7 +65,7 @@ "@tanstack/table-core": "workspace:*" }, "devDependencies": { - "@eslint-react/eslint-plugin": "^5.6.6", + "@eslint-react/eslint-plugin": "^5.7.0", "@types/react": "^19.2.14", "@vitejs/plugin-react": "^6.0.1", "eslint-plugin-react-compiler": "19.1.0-rc.2", diff --git a/packages/react-table/src/useTable.ts b/packages/react-table/src/useTable.ts index 4fe97567f9..06064f03c0 100644 --- a/packages/react-table/src/useTable.ts +++ b/packages/react-table/src/useTable.ts @@ -1,10 +1,14 @@ 'use client' -import { useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { constructTable } from '@tanstack/table-core' +import { tanstackSignals } from '@tanstack/table-core/features/table-reactivity/tanstack-signals' import { shallow, useSelector } from '@tanstack/react-store' import { FlexRender } from './FlexRender' import { Subscribe } from './Subscribe' +import type { Atom, ReadonlyAtom } from '@tanstack/react-store' +import type { FlexRenderProps } from './FlexRender' +import type { SubscribePropsWithStore } from './Subscribe' import type { CellData, RowData, @@ -13,10 +17,7 @@ import type { TableOptions, TableState, } from '@tanstack/table-core' -import type { Atom, ReadonlyAtom } from '@tanstack/react-store' import type { FunctionComponent, ReactNode } from 'react' -import type { FlexRenderProps } from './FlexRender' -import type { SubscribePropsWithStore } from './Subscribe' export type ReactTable< TFeatures extends TableFeatures, @@ -121,11 +122,10 @@ export function useTable< ({}) as TSelected, ): ReactTable { const [table] = useState(() => { - const tableInstance = constructTable(tableOptions) as ReactTable< - TFeatures, - TData, - TSelected - > + const tableInstance = constructTable({ + ...tableOptions, + reactivity: tableOptions.reactivity ?? tanstackSignals(), + }) as ReactTable tableInstance.Subscribe = ((props: any) => { return Subscribe({ @@ -139,22 +139,26 @@ export function useTable< return tableInstance }) - // sync table options on every render - table.setOptions((prev) => ({ - ...prev, - ...tableOptions, - })) + useEffect(() => { + table.setOptions((prev) => ({ + ...prev, + ...tableOptions, + })) + }, [table, tableOptions]) const state = useSelector(table.store, selector, { compare: shallow }) + const options = useSelector(table.optionsStore, (options) => options, { + compare: shallow, + }) // we know this is not the most efficient way to return the table, // but it is required for the react compiler to work return useMemo( () => ({ ...table, - options: tableOptions, + options, state, }), - [table, tableOptions, state], + [table, options, state], ) as ReactTable } diff --git a/packages/solid-table/src/createTable.ts b/packages/solid-table/src/createTable.ts index 412efe6e7a..fe62fde300 100644 --- a/packages/solid-table/src/createTable.ts +++ b/packages/solid-table/src/createTable.ts @@ -1,10 +1,8 @@ -import { - constructReactivityFeature, - constructTable, -} from '@tanstack/table-core' -import { createComputed, createSignal, mergeProps, untrack } from 'solid-js' +import { constructTable } from '@tanstack/table-core' +import { createComputed, getOwner, mergeProps, untrack } from 'solid-js' import { shallow, useSelector } from '@tanstack/solid-store' import { FlexRender } from './FlexRender' +import { solidReactivity } from './signals' import type { Atom, ReadonlyAtom } from '@tanstack/solid-store' import type { Accessor, JSX } from 'solid-js' import type { @@ -12,6 +10,7 @@ import type { Table, TableFeatures, TableOptions, + TableReactivityBindings, TableState, } from '@tanstack/table-core' @@ -73,17 +72,10 @@ export function createTable< selector: (state: TableState) => TSelected = () => ({}) as TSelected, ): SolidTable { - const [notifier, setNotifier] = createSignal(void 0, { equals: false }) - - const solidReactivityFeature = constructReactivityFeature({ - stateNotifier: () => notifier(), - optionsNotifier: () => notifier(), - }) + const owner = getOwner()! const mergedOptions = mergeProps(tableOptions, { - _features: mergeProps(tableOptions._features, { - solidReactivityFeature, - }), + reactivity: solidReactivity(owner), }) as any const resolvedOptions = mergeProps( @@ -96,7 +88,7 @@ export function createTable< }, }, mergedOptions, - ) as TableOptions + ) as TableOptions & { reactivity: TableReactivityBindings } const table = constructTable(resolvedOptions) as SolidTable< TFeatures, @@ -104,9 +96,6 @@ export function createTable< TSelected > - const allState = useSelector(table.store) - const allOptions = useSelector(table.optionsStore) - createComputed(() => { const userState = tableOptions.state if (userState) { @@ -122,12 +111,6 @@ export function createTable< }) }) - createComputed(() => { - allState() - allOptions() - untrack(() => setNotifier(void 0)) - }) - table.Subscribe = ((props: { source?: Atom | ReadonlyAtom selector?: ((state: unknown) => unknown) | undefined diff --git a/packages/solid-table/src/signals.ts b/packages/solid-table/src/signals.ts new file mode 100644 index 0000000000..473bef5842 --- /dev/null +++ b/packages/solid-table/src/signals.ts @@ -0,0 +1,68 @@ +import { + batch, + createMemo, + createSignal, + observable, + runWithOwner, + untrack, +} from 'solid-js' +import type { Accessor, Owner, Setter } from 'solid-js' +import type { + TableAtomOptions, + TableReactivityBindings, +} from '@tanstack/table-core' +import type { Atom, Observer, ReadonlyAtom } from '@tanstack/solid-store' + +function signalToReadonlyAtom( + signal: Accessor, + owner: Owner, +): ReadonlyAtom { + return Object.assign(signal, { + get: () => signal(), + subscribe: (observer: Observer) => { + return runWithOwner(owner, () => observable(signal))!.subscribe(observer) + }, + }) +} + +function signalToWritableAtom( + signalTuple: [Accessor, Setter], + owner: Owner, +): Atom { + const [signal, setSignal] = signalTuple + return Object.assign(signal, { + set: (updater: T | ((prevVal: T) => T)) => { + typeof updater === 'function' + ? setSignal(updater as unknown as (prev: T) => T) + : setSignal(updater as Exclude) + }, + get: () => signal(), + subscribe: (observer: Observer) => { + return runWithOwner(owner, () => observable(signal))!.subscribe(observer) + }, + }) +} + +export function solidReactivity(owner: Owner): TableReactivityBindings { + return { + createReadonlyAtom: (fn: () => T, options?: TableAtomOptions) => { + const signal = createMemo(() => fn(), { + equals: options?.compare, + name: options?.debugName, + }) + return signalToReadonlyAtom(signal, owner) + }, + createWritableAtom: ( + value: T, + options?: TableAtomOptions, + ): Atom => { + const writableSignal = createSignal(value, { + equals: options?.compare, + name: options?.debugName, + }) + return signalToWritableAtom(writableSignal, owner) + }, + untrack: untrack, + batch: batch, + } +} diff --git a/packages/svelte-table/src/createTable.svelte.ts b/packages/svelte-table/src/createTable.svelte.ts index 0171065618..81a3be43fc 100644 --- a/packages/svelte-table/src/createTable.svelte.ts +++ b/packages/svelte-table/src/createTable.svelte.ts @@ -1,15 +1,14 @@ -import { - constructReactivityFeature, - constructTable, -} from '@tanstack/table-core' -import { shallow, useSelector } from '@tanstack/svelte-store' +import { constructTable } from '@tanstack/table-core' +import { useSelector } from '@tanstack/svelte-store' import { untrack } from 'svelte' import { mergeObjects } from './merge-objects' +import { svelteReactivity } from './signals.svelte' import type { RowData, Table, TableFeatures, TableOptions, + TableReactivityBindings, TableState, } from '@tanstack/table-core' @@ -38,23 +37,12 @@ export function createTable< selector: (state: TableState) => TSelected = () => ({}) as TSelected, ): SvelteTable { - // 1. Create $state-based notifier for reactivity feature - let notifierValue = $state(0) - - // 2. Construct reactivity feature (same pattern as solid/vue/angular) - const svelteReactivityFeature = constructReactivityFeature({ - stateNotifier: () => notifierValue, - optionsNotifier: () => notifierValue, - }) - - // 3. Merge reactivity feature into options using mergeObjects (preserves getters) + // 1. Merge reactivity into options using mergeObjects (preserves getters) const mergedOptions = mergeObjects(tableOptions, { - _features: mergeObjects(tableOptions._features, { - svelteReactivityFeature, - }), - }) as any + reactivity: tableOptions.reactivity ?? svelteReactivity(), + }) as TableOptions & { reactivity: TableReactivityBindings } - // 4. Set up resolved options with mergeOptions handler + // 2. Set up resolved options with mergeOptions handler const resolvedOptions = mergeObjects( { mergeOptions: ( @@ -65,42 +53,25 @@ export function createTable< }, }, mergedOptions, - ) as TableOptions + ) as TableOptions & { reactivity: TableReactivityBindings } - // 5. Construct table + // 3. Construct table const table = constructTable(resolvedOptions) as SvelteTable< TFeatures, TData, TSelected > - // 6. Subscribe to all state and options via useSelector - const allState = useSelector(table.store, (state) => state) - const allOptions = useSelector(table.optionsStore, (options) => options) - - // 7. Sync store changes -> notifier. - // Use $effect.pre so this runs before DOM updates (like Solid's createComputed). - // Use untrack for the write so the effect only depends on allState/allOptions, - // not on notifierValue itself (which would cause an infinite loop). - $effect.pre(() => { - allState.current - allOptions.current - untrack(() => { - notifierValue++ - }) - }) - - // 8. Sync options reactively. When controlled state changes (e.g., $state + // 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). // The reactive reads (state getters, data getter) happen OUTSIDE untrack - // so they become dependencies. The setOptions call is INSIDE untrack to - // prevent tracking notifierValue (read via store.state's stateNotifier - // interceptor inside setOptions), which would cause an infinite loop. + // so they become dependencies. The setOptions call is INSIDE untrack so + // option writes do not subscribe this effect to table internals. $effect.pre(() => { // Read reactive getters to create $effect dependencies on external state - const state = mergedOptions.state + const state: Record | undefined = mergedOptions.state if (state) { for (const key in state) { void state[key] @@ -110,15 +81,12 @@ export function createTable< untrack(() => { table.setOptions((prev) => { - return mergeObjects(prev, mergedOptions) as TableOptions< - TFeatures, - TData - > + return mergeObjects(prev, mergedOptions) }) }) }) - // 9. State selector + // 5. State selector const stateStore = useSelector(table.store, selector) Object.defineProperty(table, 'state', { diff --git a/packages/svelte-table/src/signals.svelte.ts b/packages/svelte-table/src/signals.svelte.ts new file mode 100644 index 0000000000..de06ef472a --- /dev/null +++ b/packages/svelte-table/src/signals.svelte.ts @@ -0,0 +1,64 @@ +import { flushSync, untrack } from 'svelte' +import type { + TableAtomOptions, + TableReactivityBindings, +} from '@tanstack/table-core' +import type { Atom, Observer, ReadonlyAtom } from '@tanstack/svelte-store' + +function observerToCallback( + observerOrNext: Observer | ((value: T) => void), +): (value: T) => void { + return typeof observerOrNext === 'function' + ? observerOrNext + : (value) => observerOrNext.next?.(value) +} + +function subscribeToRune( + getValue: () => T, + observerOrNext: Observer | ((value: T) => void), +) { + const callback = observerToCallback(observerOrNext) + const unsubscribe = $effect.root(() => { + $effect(() => { + callback(getValue()) + }) + }) + + return { unsubscribe } +} + +export function svelteReactivity(): TableReactivityBindings { + return { + createReadonlyAtom: (fn: () => T, _options?: TableAtomOptions) => { + const value = $derived.by(fn) + + return { + get: () => value, + subscribe: ((observerOrNext: Observer | ((value: T) => void)) => { + return subscribeToRune(() => value, observerOrNext) + }) as ReadonlyAtom['subscribe'], + } + }, + createWritableAtom: ( + initialValue: T, + _options?: TableAtomOptions, + ): Atom => { + let value = $state(initialValue) + + return { + set: (updater: T | ((prevVal: T) => T)) => { + value = + typeof updater === 'function' + ? (updater as (prevVal: T) => T)(value) + : updater + }, + get: () => value, + subscribe: ((observerOrNext: Observer | ((value: T) => void)) => { + return subscribeToRune(() => value, observerOrNext) + }) as Atom['subscribe'], + } + }, + untrack: untrack, + batch: (fn) => flushSync(fn), + } +} diff --git a/packages/table-core/package.json b/packages/table-core/package.json index 5263f3d45a..b2e35ebccd 100644 --- a/packages/table-core/package.json +++ b/packages/table-core/package.json @@ -34,6 +34,10 @@ "import": "./dist/index.js", "require": "./dist/index.cjs" }, + "./features/table-reactivity/tanstack-signals": { + "import": "./dist/features/table-reactivity/tanstack-signals.js", + "require": "./dist/features/table-reactivity/tanstack-signals.cjs" + }, "./flex-render": { "import": "./dist/flex-render.js", "require": "./dist/flex-render.cjs" diff --git a/packages/table-core/src/core/table/constructTable.ts b/packages/table-core/src/core/table/constructTable.ts index de0eca24bc..07eee93214 100644 --- a/packages/table-core/src/core/table/constructTable.ts +++ b/packages/table-core/src/core/table/constructTable.ts @@ -1,6 +1,7 @@ -import { createAtom, createStore } from '@tanstack/store' import { coreFeatures } from '../coreFeatures' import { cloneState } from '../../utils' +import { atomToStore } from '../../features/table-reactivity/table-reactivity' +import type { TableReactivityBindings } from '../../features/table-reactivity/table-reactivity' import type { RowData } from '../../types/type-utils' import type { TableFeature, TableFeatures } from '../../types/TableFeatures' import type { Table, Table_Internal } from '../../types/Table' @@ -17,19 +18,29 @@ export function getInitialTableState( return cloneState(initialState) as TableState } +export type ConstructTable< + TFeatures extends TableFeatures, + TData extends RowData, +> = Omit, 'reactivity'> & { + reactivity: TableReactivityBindings +} + export function constructTable< TFeatures extends TableFeatures, TData extends RowData, ->(tableOptions: TableOptions): Table { +>(tableOptions: ConstructTable): Table { + const signals = tableOptions.reactivity + const table = { + reactivity: signals, _features: { ...coreFeatures, ...tableOptions._features }, _rowModels: {}, _rowModelFns: {}, get options() { - return this.optionsStore.state + return this.optionsStore.get() }, set options(value) { - this.optionsStore.setState(() => value) + this.optionsStore.set(() => value) }, baseAtoms: {}, atoms: {}, @@ -41,10 +52,9 @@ export function constructTable< return Object.assign(obj, feature.getDefaultTableOptions?.(table)) }, {}) as TableOptions - table.optionsStore = createStore({ - ...defaultOptions, - ...tableOptions, - }) + table.optionsStore = signals.createWritableAtom< + TableOptions + >({ ...defaultOptions, ...tableOptions }, { debugName: 'table/optionsStore' }) table.initialState = getInitialTableState( table._features, @@ -57,31 +67,41 @@ export function constructTable< for (const key of stateKeys) { // create writable base atom - table.baseAtoms[key] = createAtom(table.initialState[key]) as any + table.baseAtoms[key] = signals.createWritableAtom(table.initialState[key], { + debugName: `table/baseAtoms/${key}`, + }) as any // create readonly derived atom: on each get(), read current options (state, then external atom, then base) - ;(table.atoms as any)[key] = createAtom(() => { - // Reading optionsStore.state keeps this reactive to setOptions - const opts = table.optionsStore.state - const state = opts.state - if (key in (state ?? {})) { - return state![key] - } - const externalAtom = opts.atoms?.[key] - if (externalAtom) { - return externalAtom.get() - } - return table.baseAtoms[key].get() - }) + ;(table.atoms as any)[key] = signals.createReadonlyAtom( + () => { + // Reading optionsStore.get() keeps this reactive to setOptions + const opts = table.optionsStore.get() + const state = opts.state + if (key in (state ?? {})) { + return state![key] + } + const externalAtom = opts.atoms?.[key] + if (externalAtom) { + return externalAtom.get() + } + return table.baseAtoms[key].get() + }, + { debugName: `table/atoms/${key}` }, + ) } - table.store = createStore(() => { - const snapshot = {} as TableState - for (const key of stateKeys) { - snapshot[key] = table.atoms[key].get() - } - return snapshot - }) + table.store = atomToStore( + signals.createReadonlyAtom( + () => { + const snapshot = {} as TableState + for (const key of stateKeys) { + snapshot[key] = table.atoms[key].get() + } + return snapshot + }, + { debugName: 'table/store' }, + ), + ) if ( process.env.NODE_ENV === 'development' && diff --git a/packages/table-core/src/core/table/coreTablesFeature.types.ts b/packages/table-core/src/core/table/coreTablesFeature.types.ts index 62fde58013..ab598ee296 100644 --- a/packages/table-core/src/core/table/coreTablesFeature.types.ts +++ b/packages/table-core/src/core/table/coreTablesFeature.types.ts @@ -5,6 +5,7 @@ import type { RowData, Updater } from '../../types/type-utils' import type { TableFeatures } from '../../types/TableFeatures' import type { CachedRowModels, CreateRowModels_All } from '../../types/RowModel' import type { TableOptions } from '../../types/TableOptions' +import type { TableReactivityBindings } from '../../features/table-reactivity/table-reactivity' import type { TableState, TableState_All } from '../../types/TableState' export interface TableMeta< @@ -108,12 +109,20 @@ export interface TableOptions_Table< * Pass in individual self-managed state to the table. */ state?: Partial> + /** + * Table custom reactibity bindings. + */ + readonly reactivity?: TableReactivityBindings } export interface Table_CoreProperties< TFeatures extends TableFeatures, TData extends RowData, > { + /** + * Table custom reactivity bindings. + */ + reactivity: TableReactivityBindings /** * The features that are enabled for the table. */ @@ -156,7 +165,7 @@ export interface Table_CoreProperties< /** * The base store for the table options. */ - optionsStore: Store> + optionsStore: Atom> /** * This is the resolved initial state of the table. */ diff --git a/packages/table-core/src/core/table/coreTablesFeature.utils.ts b/packages/table-core/src/core/table/coreTablesFeature.utils.ts index 800d08afec..2d93b16d9d 100644 --- a/packages/table-core/src/core/table/coreTablesFeature.utils.ts +++ b/packages/table-core/src/core/table/coreTablesFeature.utils.ts @@ -1,4 +1,3 @@ -import { batch } from '@tanstack/store' import { cloneState, functionalUpdate } from '../../utils' import type { RowData, Updater } from '../../types/type-utils' import type { TableFeatures } from '../../types/TableFeatures' @@ -10,7 +9,7 @@ export function table_reset< TData extends RowData, >(table: Table_Internal): void { const snap = cloneState(table.initialState) - batch(() => { + table.reactivity.batch(() => { for (const key of Object.keys(snap) as Array) { ;(table.baseAtoms as any)[key].set(snap[key] as any) } @@ -43,5 +42,5 @@ export function table_setOptions< ): void { const newOptions = functionalUpdate(updater, table.options) const mergedOptions = table_mergeOptions(table, newOptions) - table.optionsStore.setState(() => mergedOptions) + table.optionsStore.set(() => mergedOptions) } diff --git a/packages/table-core/src/features/table-reactivity/table-reactivity.ts b/packages/table-core/src/features/table-reactivity/table-reactivity.ts new file mode 100644 index 0000000000..a0432af753 --- /dev/null +++ b/packages/table-core/src/features/table-reactivity/table-reactivity.ts @@ -0,0 +1,74 @@ +import type { + Atom, + AtomOptions, + ReadonlyAtom, + ReadonlyStore, + Store, +} from '@tanstack/store' + +export interface TableAtomOptions extends AtomOptions { + /** + * A debug name for the atom, useful for debugging. + */ + debugName: string +} + +/** + * Framework reactivity bindings used by table-core. + * + * Adapters (React, Solid, Vue, etc.) provide concrete implementations so + * core features can create derived/writable atoms and integrate with their + * scheduling primitives. + */ +export interface TableReactivityBindings { + /** + * Creates a writable atom with an initial value. + */ + createWritableAtom: ( + initialValue: T, + options?: TableAtomOptions, + ) => Atom + /** + * Creates a readonly/derived atom from a compute function. + */ + createReadonlyAtom: ( + fn: () => T, + options?: TableAtomOptions, + ) => ReadonlyAtom + /** + * Evaluates a function without tracking reactive dependencies. + */ + untrack: (fn: () => T) => T + /** + * Batches reactive updates to avoid intermediate recomputation. + */ + batch: (fn: () => void) => void +} + +/** + * Converts a writable atom to the store-compatible shape expected by core. + */ +export function atomToStore(atom: Atom): Store +/** + * Converts a readonly atom to a readonly store-compatible shape. + */ +export function atomToStore(atom: ReadonlyAtom): ReadonlyStore +/** + * Bridges atom instances to the `Store`/`ReadonlyStore` API by exposing + * a `state` getter backed by `atom.get()`, and wiring `setState` for + * writable atoms. + */ +export function atomToStore( + atom: Atom | ReadonlyAtom, +): Store | ReadonlyStore { + const store: Store = atom as Store + Object.defineProperty(atom, 'state', { + get() { + return atom.get() + }, + }) + if ('set' in atom) { + store.setState = atom.set.bind(atom) + } + return store +} diff --git a/packages/table-core/src/features/table-reactivity/tableReactivityFeature.ts b/packages/table-core/src/features/table-reactivity/tableReactivityFeature.ts deleted file mode 100644 index 62ec2bd0c4..0000000000 --- a/packages/table-core/src/features/table-reactivity/tableReactivityFeature.ts +++ /dev/null @@ -1,92 +0,0 @@ -import type { ReadonlyStore, Store } from '@tanstack/store' -import type { TableFeature, TableFeatures } from '../../types/TableFeatures' -import type { RowData } from '../../types/type-utils' - -export interface TableReactivityFeatureConstructors< - TFeatures extends TableFeatures, - TData extends RowData, -> {} - -export function constructReactivityFeature< - TFeatures extends TableFeatures, - TData extends RowData, ->(bindings: { - stateNotifier?: () => unknown - optionsNotifier?: () => unknown -}): TableFeature> { - return { - constructTableAPIs: (table) => { - table.optionsStore = bindStore( - table.optionsStore, - bindings.optionsNotifier, - ) - table.atoms = bindAtoms(table.atoms, bindings.stateNotifier) - }, - } -} - -const bindStore = | ReadonlyStore>( - store: T, - notifier?: () => unknown, -): T => { - const stateDescriptor = Object.getOwnPropertyDescriptor( - Object.getPrototypeOf(store), - 'state', - )! - - Object.defineProperty(store, 'state', { - configurable: true, - enumerable: true, - get() { - notifier?.() - return stateDescriptor.get!.call(store) - }, - }) - - return store -} - -// Wraps an atoms/baseAtoms map so that `.get()` on any individual atom -// calls the framework notifier first — matching how `bindStore` wraps -// `store.state`. The proxy also transparently forwards missing slices -// (atoms for features not registered on this table) as `undefined`. -const bindAtoms = (atoms: T, notifier?: () => unknown): T => { - if (!notifier) return atoms - // Cache wrapped atoms so referential identity is stable per slice. - const wrappedCache = new Map() - return new Proxy(atoms, { - get(target, prop, receiver) { - const atom = Reflect.get(target, prop, receiver) as unknown - if (!atom || typeof prop !== 'string' || !isAtomLike(atom)) { - return atom - } - if (wrappedCache.has(prop)) return wrappedCache.get(prop) - const originalGet = atom.get.bind(atom) - const wrapped = new Proxy(atom, { - get(atomTarget, atomProp, atomReceiver) { - if (atomProp === 'get') { - return () => { - notifier() - return originalGet() - } - } - return Reflect.get(atomTarget, atomProp, atomReceiver) - }, - }) - wrappedCache.set(prop, wrapped) - return wrapped - }, - }) -} - -interface AtomLike { - get: () => unknown -} - -function isAtomLike(value: unknown): value is AtomLike { - return ( - typeof value === 'object' && - value !== null && - typeof (value as { get?: unknown }).get === 'function' - ) -} diff --git a/packages/table-core/src/features/table-reactivity/tanstack-signals.ts b/packages/table-core/src/features/table-reactivity/tanstack-signals.ts new file mode 100644 index 0000000000..d043568068 --- /dev/null +++ b/packages/table-core/src/features/table-reactivity/tanstack-signals.ts @@ -0,0 +1,19 @@ +import { batch, createAtom } from '@tanstack/store' +import type { TableReactivityBindings } from './table-reactivity' + +export function tanstackSignals(): TableReactivityBindings { + return { + batch: batch, + untrack: (fn) => fn(), + createReadonlyAtom: (fn, options) => { + return createAtom(() => fn(), { + compare: options?.compare, + }) + }, + createWritableAtom: (value, options) => { + return createAtom(value, { + compare: options?.compare, + }) + }, + } +} diff --git a/packages/table-core/src/index.ts b/packages/table-core/src/index.ts index 3ed19019b4..5b3d05ccc0 100755 --- a/packages/table-core/src/index.ts +++ b/packages/table-core/src/index.ts @@ -72,7 +72,7 @@ export * from './fns/sortFns' export * from './features/stockFeatures' // tableReactivityFeature -export * from './features/table-reactivity/tableReactivityFeature' +export * from './features/table-reactivity/table-reactivity' // columnFacetingFeature export * from './features/column-faceting/columnFacetingFeature' diff --git a/packages/table-core/src/types/Table.ts b/packages/table-core/src/types/Table.ts index 6c27d25a17..e2be6d1d29 100644 --- a/packages/table-core/src/types/Table.ts +++ b/packages/table-core/src/types/Table.ts @@ -1,4 +1,4 @@ -import type { ReadonlyStore } from '@tanstack/store' +import type { ReadonlyAtom, ReadonlyStore } from '@tanstack/store' import type { Table_ColumnFaceting } from '../features/column-faceting/columnFacetingFeature.types' import type { Table_ColumnResizing } from '../features/column-resizing/columnResizingFeature.types' import type { Table_ColumnFiltering } from '../features/column-filtering/columnFilteringFeature.types' diff --git a/packages/table-core/tests/helpers/generateTestTable.ts b/packages/table-core/tests/helpers/generateTestTable.ts index c89144871e..3ebb3860b4 100644 --- a/packages/table-core/tests/helpers/generateTestTable.ts +++ b/packages/table-core/tests/helpers/generateTestTable.ts @@ -1,6 +1,7 @@ import { constructTable, coreFeatures } from '../../src' import { generateTestColumnDefs } from '../fixtures/data/generateTestColumnDefs' import { generateTestData } from '../fixtures/data/generateTestData' +import { tanstackSignals } from '../../src/features/table-reactivity/tanstack-signals' import type { Row, Table, @@ -26,6 +27,7 @@ export function generateTestTableWithData( return constructTable({ data, columns, + reactivity: tanstackSignals(), getSubRows: (row: Row) => row.subRows, ...options, _features: { @@ -43,6 +45,7 @@ export function generateTestTableFromData( return constructTable({ data, columns, + reactivity: tanstackSignals(), ...options, _features: { ...coreFeatures, diff --git a/packages/table-core/tests/helpers/rowPinningHelpers.ts b/packages/table-core/tests/helpers/rowPinningHelpers.ts index a74b25dcc9..4f13ed7660 100644 --- a/packages/table-core/tests/helpers/rowPinningHelpers.ts +++ b/packages/table-core/tests/helpers/rowPinningHelpers.ts @@ -2,11 +2,12 @@ import { vi } from 'vitest' import { getDefaultRowPinningState } from '../../src/features/row-pinning/rowPinningFeature.utils' import { constructTable, - createColumnHelper, coreFeatures, + createColumnHelper, rowPinningFeature, } from '../../src' import { generateTestData } from '../fixtures/data/generateTestData' +import { tanstackSignals } from '../../src/features/table-reactivity/tanstack-signals' import { generateTestTableWithData } from './generateTestTable' import type { ColumnDef, RowPinningState, TableOptions } from '../../src' import type { Person } from '../fixtures/data/types' @@ -77,6 +78,7 @@ export function createRowPinningTable( const table = constructTable({ _features, _rowModels: {}, + reactivity: tanstackSignals(), data, columns, getSubRows: (row: any) => row.subRows, diff --git a/packages/table-core/tests/implementation/features/row-pinning/rowPinningFeature.test.ts b/packages/table-core/tests/implementation/features/row-pinning/rowPinningFeature.test.ts index eee05a687c..b688e272d7 100644 --- a/packages/table-core/tests/implementation/features/row-pinning/rowPinningFeature.test.ts +++ b/packages/table-core/tests/implementation/features/row-pinning/rowPinningFeature.test.ts @@ -12,6 +12,7 @@ import { createTableWithMockOnPinningChange, } from '../../../helpers/rowPinningHelpers' import { generateTestData } from '../../../fixtures/data/generateTestData' +import { tanstackSignals } from '../../../../src/features/table-reactivity/tanstack-signals' import type { ColumnDef, Row } from '../../../../src' import type { Person } from '../../../fixtures/data/types' @@ -176,6 +177,7 @@ describe('table methods', () => { _rowModels: { paginatedRowModel: createPaginatedRowModel(), }, + reactivity: tanstackSignals(), data, columns, getSubRows: (row) => row.subRows, @@ -215,6 +217,7 @@ describe('table methods', () => { _rowModels: { paginatedRowModel: createPaginatedRowModel(), }, + reactivity: tanstackSignals(), data, columns, getSubRows: (row) => row.subRows, diff --git a/packages/table-core/tests/implementation/features/row-selection/rowSelectionFeature.test.ts b/packages/table-core/tests/implementation/features/row-selection/rowSelectionFeature.test.ts index 65a510eb19..6634941df8 100644 --- a/packages/table-core/tests/implementation/features/row-selection/rowSelectionFeature.test.ts +++ b/packages/table-core/tests/implementation/features/row-selection/rowSelectionFeature.test.ts @@ -6,6 +6,7 @@ import { } from '../../../../src' import * as RowSelectionUtils from '../../../../src/features/row-selection/rowSelectionFeature.utils' import { generateTestData } from '../../../fixtures/data/generateTestData' +import { tanstackSignals } from '../../../../src/features/table-reactivity/tanstack-signals' import type { Person } from '../../../fixtures/data/types' import type { ColumnDef } from '../../../../src' @@ -41,6 +42,7 @@ describe('rowSelectionFeature', () => { const table = constructTable({ _features, _rowModels: {}, + reactivity: tanstackSignals(), enableRowSelection: true, renderFallbackValue: '', data, @@ -70,6 +72,7 @@ describe('rowSelectionFeature', () => { const table = constructTable({ _features, _rowModels: {}, + reactivity: tanstackSignals(), enableRowSelection: true, renderFallbackValue: '', data, @@ -99,6 +102,7 @@ describe('rowSelectionFeature', () => { const table = constructTable({ _features, _rowModels: {}, + reactivity: tanstackSignals(), enableRowSelection: true, renderFallbackValue: '', data, @@ -125,6 +129,7 @@ describe('rowSelectionFeature', () => { const table = constructTable({ _features, _rowModels: {}, + reactivity: tanstackSignals(), enableRowSelection: true, renderFallbackValue: '', data, @@ -150,6 +155,7 @@ describe('rowSelectionFeature', () => { const table = constructTable({ _features, _rowModels: {}, + reactivity: tanstackSignals(), enableRowSelection: true, renderFallbackValue: '', data, @@ -175,6 +181,7 @@ describe('rowSelectionFeature', () => { const table = constructTable({ _features, _rowModels: {}, + reactivity: tanstackSignals(), enableRowSelection: true, renderFallbackValue: '', data, @@ -200,6 +207,7 @@ describe('rowSelectionFeature', () => { const table = constructTable({ _features, _rowModels: {}, + reactivity: tanstackSignals(), enableRowSelection: true, renderFallbackValue: '', data, @@ -221,6 +229,7 @@ describe('rowSelectionFeature', () => { const table = constructTable({ _features, _rowModels: {}, + reactivity: tanstackSignals(), enableRowSelection: true, renderFallbackValue: '', data, @@ -242,6 +251,7 @@ describe('rowSelectionFeature', () => { const table = constructTable({ _features, _rowModels: {}, + reactivity: tanstackSignals(), enableRowSelection: true, renderFallbackValue: '', data, @@ -266,6 +276,7 @@ describe('rowSelectionFeature', () => { const table = constructTable({ _features, _rowModels: {}, + reactivity: tanstackSignals(), enableRowSelection: true, renderFallbackValue: '', data, @@ -292,6 +303,7 @@ describe('rowSelectionFeature', () => { const table = constructTable({ _features, _rowModels: {}, + reactivity: tanstackSignals(), enableRowSelection: true, renderFallbackValue: '', data, @@ -318,6 +330,7 @@ describe('rowSelectionFeature', () => { const table = constructTable({ _features, _rowModels: {}, + reactivity: tanstackSignals(), enableRowSelection: (row) => row.index === 0, // only first row is selectable (of 2 sub-rows) renderFallbackValue: '', data, @@ -343,6 +356,7 @@ describe('rowSelectionFeature', () => { const table = constructTable({ _features, _rowModels: {}, + reactivity: tanstackSignals(), enableRowSelection: true, renderFallbackValue: '', data, diff --git a/packages/table-core/tests/performance/features/column-grouping/columnGroupingFeature.test.ts b/packages/table-core/tests/performance/features/column-grouping/columnGroupingFeature.test.ts index 89c45cc525..1f374786f8 100644 --- a/packages/table-core/tests/performance/features/column-grouping/columnGroupingFeature.test.ts +++ b/packages/table-core/tests/performance/features/column-grouping/columnGroupingFeature.test.ts @@ -8,6 +8,7 @@ import { } from '../../../../src' import { createColumnHelper } from '../../../../src/helpers/columnHelper' import { generateTestData } from '../../../fixtures/data/generateTestData' +import { tanstackSignals } from '../../../../src/features/table-reactivity/tanstack-signals' import type { Person } from '../../../fixtures/data/types' import type { ColumnDef } from '../../../../src' @@ -46,6 +47,7 @@ describe('#getGroupedRowModel', () => { _rowModels: { groupedRowModel: createGroupedRowModel(aggregationFns), }, + reactivity: tanstackSignals(), onStateChange() {}, renderFallbackValue: '', data, diff --git a/packages/table-core/tests/unit/core/columns/constructColumn.test.ts b/packages/table-core/tests/unit/core/columns/constructColumn.test.ts index 13f1e190cd..063f192bff 100644 --- a/packages/table-core/tests/unit/core/columns/constructColumn.test.ts +++ b/packages/table-core/tests/unit/core/columns/constructColumn.test.ts @@ -2,12 +2,14 @@ import { describe, expect, it } from 'vitest' import { coreColumnsFeature } from '../../../../src/core/columns/coreColumnsFeature' import { constructColumn } from '../../../../src/core/columns/constructColumn' import { constructTable } from '../../../../src' +import { tanstackSignals } from '../../../../src/features/table-reactivity/tanstack-signals' import type { ColumnDef } from '../../../../src/types/ColumnDef' describe('constructColumn', () => { it('should create a column with all core column APIs and properties', () => { const table = constructTable({ _features: { coreColumnsFeature }, + reactivity: tanstackSignals(), columns: [] as Array, data: [] as Array, }) diff --git a/packages/table-core/tests/unit/core/table/constructTable.test.ts b/packages/table-core/tests/unit/core/table/constructTable.test.ts index 0846fa18bd..da10f2048e 100644 --- a/packages/table-core/tests/unit/core/table/constructTable.test.ts +++ b/packages/table-core/tests/unit/core/table/constructTable.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest' import { constructTable, coreFeatures } from '../../../../src' +import { tanstackSignals } from '../../../../src/features/table-reactivity/tanstack-signals' describe('constructTable', () => { it('should create a table with all core table APIs and properties', () => { @@ -7,6 +8,7 @@ describe('constructTable', () => { _features: { ...coreFeatures, }, + reactivity: tanstackSignals(), columns: [], data: [], }) diff --git a/packages/table-core/tests/unit/core/table/stockFeaturesInitialState.test.ts b/packages/table-core/tests/unit/core/table/stockFeaturesInitialState.test.ts index 88d66abfd6..3af707bdb9 100644 --- a/packages/table-core/tests/unit/core/table/stockFeaturesInitialState.test.ts +++ b/packages/table-core/tests/unit/core/table/stockFeaturesInitialState.test.ts @@ -1,10 +1,12 @@ import { describe, expect, it } from 'vitest' import { constructTable, stockFeatures } from '../../../../src' +import { tanstackSignals } from '../../../../src/features/table-reactivity/tanstack-signals' describe('constructTable with stockFeatures', () => { it('should include all feature states in initial state', () => { const table = constructTable({ _features: stockFeatures, + reactivity: tanstackSignals(), columns: [], data: [], }) diff --git a/packages/table-core/tests/unit/core/tableAtoms.test.ts b/packages/table-core/tests/unit/core/tableAtoms.test.ts index e3c3312135..77699be9d2 100644 --- a/packages/table-core/tests/unit/core/tableAtoms.test.ts +++ b/packages/table-core/tests/unit/core/tableAtoms.test.ts @@ -6,8 +6,12 @@ import { rowSelectionFeature, rowSortingFeature, } from '../../../src' -import type { Table_Internal } from '../../../src' -import type { PaginationState, SortingState } from '../../../src' +import { tanstackSignals } from '../../../src/features/table-reactivity/tanstack-signals' +import type { + PaginationState, + SortingState, + Table_Internal, +} from '../../../src' const _features = { rowPaginationFeature, @@ -19,6 +23,7 @@ function makeTable(options: any = {}) { return constructTable({ _features, _rowModels: {}, + reactivity: tanstackSignals(), columns: [], data: [], ...options, diff --git a/packages/table-core/tsdown.config.ts b/packages/table-core/tsdown.config.ts index c305d9b999..533e215e93 100644 --- a/packages/table-core/tsdown.config.ts +++ b/packages/table-core/tsdown.config.ts @@ -5,6 +5,7 @@ export default defineConfig({ './src/index.ts', './src/static-functions.ts', './src/flex-render.ts', + './src/features/table-reactivity/tanstack-signals', ], format: ['esm', 'cjs'], unbundle: true, diff --git a/packages/vue-table/src/signals.ts b/packages/vue-table/src/signals.ts new file mode 100644 index 0000000000..af3b09cfaa --- /dev/null +++ b/packages/vue-table/src/signals.ts @@ -0,0 +1,63 @@ +import { computed, shallowRef, watch } from 'vue' +import type { + TableAtomOptions, + TableReactivityBindings, +} from '@tanstack/table-core' +import type { Atom, Observer, ReadonlyAtom } from '@tanstack/vue-store' +import type { ComputedRef, ShallowRef } from 'vue' + +function observerToCallback( + observerOrNext: Observer | ((value: T) => void), +): (value: T) => void { + return typeof observerOrNext === 'function' + ? observerOrNext + : (value) => observerOrNext.next?.(value) +} + +function refToReadonlyAtom( + source: ComputedRef | ShallowRef, +): ReadonlyAtom { + return Object.assign(source, { + get: () => source.value, + subscribe: ((observerOrNext: Observer | ((value: T) => void)) => { + const stop = watch(source, observerToCallback(observerOrNext), { + flush: 'sync', + }) + return { unsubscribe: stop } + }) as ReadonlyAtom['subscribe'], + }) +} + +function refToWritableAtom(source: ShallowRef): Atom { + return Object.assign(source, { + set: (updater: T | ((prevVal: T) => T)) => { + source.value = + typeof updater === 'function' + ? (updater as (prevVal: T) => T)(source.value) + : updater + }, + get: () => source.value, + subscribe: ((observerOrNext: Observer | ((value: T) => void)) => { + const stop = watch(source, observerToCallback(observerOrNext), { + flush: 'sync', + }) + return { unsubscribe: stop } + }) as Atom['subscribe'], + }) +} + +export function vueReactivity(): TableReactivityBindings { + return { + createReadonlyAtom: (fn: () => T, _options?: TableAtomOptions) => { + return refToReadonlyAtom(computed(fn)) + }, + createWritableAtom: ( + value: T, + _options?: TableAtomOptions, + ): Atom => { + return refToWritableAtom(shallowRef(value) as ShallowRef) + }, + untrack: (fn) => fn(), + batch: (fn) => fn(), + } +} diff --git a/packages/vue-table/src/useTable.ts b/packages/vue-table/src/useTable.ts index 4cb84d7d94..3ff7822c10 100644 --- a/packages/vue-table/src/useTable.ts +++ b/packages/vue-table/src/useTable.ts @@ -1,10 +1,8 @@ -import { isRef, ref, unref, watch, watchEffect } from 'vue' -import { - constructReactivityFeature, - constructTable, -} from '@tanstack/table-core' +import { unref, watch } from 'vue' +import { constructTable } from '@tanstack/table-core' import { shallow, useSelector } from '@tanstack/vue-store' import { mergeProxy } from './merge-proxy' +import { vueReactivity } from './signals' import type { Atom, ReadonlyAtom } from '@tanstack/vue-store' import type { NoInfer, @@ -12,6 +10,7 @@ import type { Table, TableFeatures, TableOptions, + TableReactivityBindings, TableState, } from '@tanstack/table-core' import type { MaybeRef, VNode } from 'vue' @@ -106,8 +105,6 @@ export function useTable< selector: (state: TableState) => TSelected = () => ({}) as TSelected, ): VueTable { - const notifier = ref(0) - const syncTableOptions = ( table: Table, options: TableOptionsWithReactiveData, @@ -118,17 +115,9 @@ export function useTable< ) } - const vueReactivityFeature = constructReactivityFeature({ - stateNotifier: () => notifier.value, - optionsNotifier: () => notifier.value, - }) - const mergedOptions = { ...tableOptions, - _features: { - ...tableOptions._features, - vueReactivityFeature, - }, + reactivity: tableOptions.reactivity ?? vueReactivity(), } const resolvedOptions = mergeProxy( @@ -144,7 +133,7 @@ export function useTable< return mergeProxy(defaultOptions, newOptions) }, }, - ) as TableOptions + ) as TableOptions & { reactivity: TableReactivityBindings } const table = constructTable(resolvedOptions) as VueTable< TFeatures, @@ -152,15 +141,6 @@ export function useTable< TSelected > - const allState = useSelector(table.store, (state) => state) - const allOptions = useSelector(table.optionsStore, (state) => state) - - watchEffect(() => { - allState.value - allOptions.value - notifier.value++ - }) - watch( () => getReactiveOptionDeps( diff --git a/packages/vue-table/tests/unit/signals.test.ts b/packages/vue-table/tests/unit/signals.test.ts new file mode 100644 index 0000000000..49b8ddde34 --- /dev/null +++ b/packages/vue-table/tests/unit/signals.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, test } from 'vitest' +import { nextTick } from 'vue' +import { vueReactivity } from '../../src/signals' + +describe('vueReactivity', () => { + test('creates writable and readonly atoms from Vue refs', async () => { + const reactivity = vueReactivity() + const count = reactivity.createWritableAtom(1, { debugName: 'count' }) + const doubled = reactivity.createReadonlyAtom(() => count.get() * 2, { + debugName: 'doubled', + }) + + expect(count.get()).toBe(1) + expect(doubled.get()).toBe(2) + + count.set((value) => value + 1) + await nextTick() + + expect(count.get()).toBe(2) + expect(doubled.get()).toBe(4) + }) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa9d0ff920..fa78e6b54e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3715,7 +3715,7 @@ importers: specifier: ^2.1.1 version: 2.1.1 dayjs: - specifier: ^1.11.13 + specifier: ^1.11.20 version: 1.11.20 react: specifier: ^19.2.5 @@ -4325,8 +4325,8 @@ importers: specifier: ^19.2.5 version: 19.2.5(react@19.2.5) zod: - specifier: ^4.4.1 - version: 4.4.1 + specifier: ^4.4.2 + version: 4.4.2 devDependencies: '@rolldown/plugin-babel': specifier: ^0.2.3 @@ -4427,8 +4427,8 @@ importers: specifier: ^6.0.3 version: 6.0.3(rollup@4.60.2) '@tanstack/router-vite-plugin': - specifier: ^1.166.46 - version: 1.166.46(@tanstack/react-router@1.169.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite-plugin-solid@2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3)))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3))(webpack@5.105.2(esbuild@0.28.0)) + specifier: ^1.166.47 + version: 1.166.47(@tanstack/react-router@1.169.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite-plugin-solid@2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3)))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3))(webpack@5.105.2(esbuild@0.28.0)) '@types/react': specifier: ^19.2.14 version: 19.2.14 @@ -5081,8 +5081,8 @@ importers: specifier: ^1.9.12 version: 1.9.12 zod: - specifier: ^4.4.1 - version: 4.4.1 + specifier: ^4.4.2 + version: 4.4.2 devDependencies: '@faker-js/faker': specifier: ^10.4.0 @@ -5144,8 +5144,8 @@ importers: specifier: ^10.4.0 version: 10.4.0 '@tanstack/router-vite-plugin': - specifier: ^1.166.46 - version: 1.166.46(@tanstack/react-router@1.169.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite-plugin-solid@2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3)))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3))(webpack@5.105.2(esbuild@0.28.0)) + specifier: ^1.166.47 + version: 1.166.47(@tanstack/react-router@1.169.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite-plugin-solid@2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3)))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3))(webpack@5.105.2(esbuild@0.28.0)) typescript: specifier: 6.0.3 version: 6.0.3 @@ -6027,8 +6027,8 @@ importers: specifier: ^9.0.0-alpha.41 version: link:../../../packages/svelte-table zod: - specifier: ^4.4.1 - version: 4.4.1 + specifier: ^4.4.2 + version: 4.4.2 devDependencies: '@faker-js/faker': specifier: ^10.4.0 @@ -7025,8 +7025,8 @@ importers: specifier: ^3.5.33 version: 3.5.33(typescript@6.0.3) zod: - specifier: ^4.4.1 - version: 4.4.1 + specifier: ^4.4.2 + version: 4.4.2 devDependencies: '@types/node': specifier: ^25.6.0 @@ -7139,6 +7139,9 @@ importers: packages/preact-table: dependencies: + '@preact/signals': + specifier: ^2.9.0 + version: 2.9.0(preact@10.29.1) '@tanstack/preact-store': specifier: ^0.13.0 version: 0.13.0(preact@10.29.1) @@ -7182,8 +7185,8 @@ importers: version: link:../table-core devDependencies: '@eslint-react/eslint-plugin': - specifier: ^5.6.6 - version: 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + specifier: ^5.7.0 + version: 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) '@types/react': specifier: ^19.2.14 version: 19.2.14 @@ -7219,8 +7222,8 @@ importers: version: 19.2.5(react@19.2.5) devDependencies: '@eslint-react/eslint-plugin': - specifier: ^5.6.6 - version: 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + specifier: ^5.7.0 + version: 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) '@tanstack/table-core': specifier: workspace:* version: link:../table-core @@ -8939,50 +8942,50 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint-react/ast@5.6.6': - resolution: {integrity: sha512-/JQtRvhfYXs3mUbwXROezatFNR6icnU0nLOagNMseg+zkXsUvaITJyWqcRMz4xXXzvoAGwUjCwiuT+iWAVg5zg==} + '@eslint-react/ast@5.7.0': + resolution: {integrity: sha512-axxvjF/ExVyhXcyS5IUWtz/jP3mXzDt416tr+rWmlb6RPPqdG8tnyNgPXX0rLXHsC2yjfc7LmRuDwUO61hbG2w==} engines: {node: '>=22.0.0'} peerDependencies: eslint: ^10.2.1 typescript: '*' - '@eslint-react/core@5.6.6': - resolution: {integrity: sha512-bSswvxQlwXJ2vnPm/OkgNS3gVbRC4nL/ZpuLi67qGFb1/ORk1OilzO0bSMVVdp9j1v0H2cNxq6pOJVPGbS7xsw==} + '@eslint-react/core@5.7.0': + resolution: {integrity: sha512-PoEXxwa5ob/uuE8y/rXZX047ysd0bsdNwsumqU0fLZmdWjOrV+ZS7tmu83HmqMIRmkHKrKTEisMqFCaNZujKIQ==} engines: {node: '>=22.0.0'} peerDependencies: eslint: ^10.2.1 typescript: '*' - '@eslint-react/eslint-plugin@5.6.6': - resolution: {integrity: sha512-xxeB32eATjdyBb6WMBQlQc1w5NftahLBhz0jWdbyPKHIpYJZXtJ997tNuOTFnvAayRMjWlzl6tCTfhyInu2Uaw==} + '@eslint-react/eslint-plugin@5.7.0': + resolution: {integrity: sha512-3JgMd9TudbaUF5AyAY+4xnL5lw3+Nadfh6fIEENc6IXJEQkYKegudfJQtWnUke4Ux2prl34GcVtlJfUP63Uurg==} engines: {node: '>=22.0.0'} peerDependencies: eslint: ^10.2.1 typescript: '*' - '@eslint-react/eslint@5.6.6': - resolution: {integrity: sha512-LhOKn0QsHukmfwsKEVS/bdnpzT3/XW1Tb8Yx22IiwuK56evx+5szFSCBy7qXSEvjKvgDl1CznujS70IcdpsJvA==} + '@eslint-react/eslint@5.7.0': + resolution: {integrity: sha512-U0DJvQqTAHok4yjIzSHp2Eg4fTSAH32pAzOYJTpfe2K0dV7XryXgQxYaHlrPRr9nzTeTTdQNTk7JywhsOQRnvQ==} engines: {node: '>=22.0.0'} peerDependencies: eslint: ^10.2.1 typescript: '*' - '@eslint-react/jsx@5.6.6': - resolution: {integrity: sha512-sJTUmCXEnJ6Fm1Cdu+a/zVuTXQys+9HVSVLKrgxgTp9XMBIl1v5dz+axiIeQ5VxfWxvFQJVdUS7CKGoivu/CRg==} + '@eslint-react/jsx@5.7.0': + resolution: {integrity: sha512-dUqohdmq8Jvzz7I4DN8uPfMyKNRRaRs2s0EluSq9WiRu8BT64VH11wwLj9HJTTtx4PJDmRuUZClnd7jENz0N6g==} engines: {node: '>=22.0.0'} peerDependencies: eslint: ^10.2.1 typescript: '*' - '@eslint-react/shared@5.6.6': - resolution: {integrity: sha512-gh3xE3rusNuxUxv0OIVzpPp1mQy5KvKGGZ0oQ44mtZwKiGN25MTaY/HzLqN5SsSudxF/bDr6uKDChPct59s+qQ==} + '@eslint-react/shared@5.7.0': + resolution: {integrity: sha512-YNI7Mqo4aGbwXOR3mvIL4+RVmiHilQa8btvAu9uHgac8KPqPWng5dtoWVEy2NsaA3lUDq6j0W8AItYb4YsjgIA==} engines: {node: '>=22.0.0'} peerDependencies: eslint: ^10.2.1 typescript: '*' - '@eslint-react/var@5.6.6': - resolution: {integrity: sha512-i42rRnDCQ2SrNAo0fMRj/X5ycvpsOZ860mhVbj64sqAOSfrgbh0IdMXMazz1bv1wwfR8Q2QD1K+GM08WzvvmpA==} + '@eslint-react/var@5.7.0': + resolution: {integrity: sha512-aEOcxuXojdKXRKRo2GIko5Je5AAKjwPkNy1czX4IbL3c+HRoQ2dK6UPgRiCXR0lfq3HBv0ArUTN4z4E23ZF+JQ==} engines: {node: '>=22.0.0'} peerDependencies: eslint: ^10.2.1 @@ -10378,6 +10381,14 @@ packages: '@babel/core': 7.x vite: 2.x || 3.x || 4.x || 5.x || 6.x || 7.x || 8.x + '@preact/signals-core@1.14.1': + resolution: {integrity: sha512-vxPpfXqrwUe9lpjqfYNjAF/0RF/eFGeLgdJzdmIIZjpOnTmGmAB4BjWone562mJGMRP4frU6iZ6ei3PDsu52Ng==} + + '@preact/signals@2.9.0': + resolution: {integrity: sha512-hYrY0KyUqkDgOl1qba/JGn6y81pXnurn21PMaxfcMwdncdZ3M/oVdmpTvEnsGjh48dIwDVc7bjWHqIsngSjYug==} + peerDependencies: + preact: '>= 10.25.0 || >=11.0.0-0' + '@prefresh/babel-plugin@0.5.3': resolution: {integrity: sha512-57LX2SHs4BX2s1IwCjNzTE2OJeEepRCNf1VTEpbNcUyHfMO68eeOWGDIt4ob9aYlW6PEWZ1SuwNikuoIXANDtQ==} @@ -11932,8 +11943,8 @@ packages: resolution: {integrity: sha512-j2OW/UvpjM/DT9tHVmuhWW1k6UOezTRrPqBPZFFmIth0fY7iTPqK+Erqpo8r5yGTRGCbMvOS4sL3H2MldnIZew==} engines: {node: '>=20.19'} - '@tanstack/router-plugin@1.167.31': - resolution: {integrity: sha512-WP+joQGNKXIKDvitX7KydHtTVQyzrnPwqSo2e/IwF5arD31Rwg1arlbze7ZSdEJCOrUhHrBP1/WmqbmeENFTTA==} + '@tanstack/router-plugin@1.167.32': + resolution: {integrity: sha512-i9BA6GzUCoM20UYZ77orXzHwD5zM0OQTtLuPNbqTTSG38CvR6viRFP/d+QFo2aRNyCvun8PR7zSa49bslSggEQ==} engines: {node: '>=20.19'} hasBin: true peerDependencies: @@ -11958,8 +11969,8 @@ packages: resolution: {integrity: sha512-VkY0u7ax/GD0qU6ZLLnfPC+UMxVzxRbvZp4yV4iUSXjgJZ/siAT5/QlLm9FEDJ9QDoC0VD9W7f00tKKreUI7Ng==} engines: {node: '>=20.19'} - '@tanstack/router-vite-plugin@1.166.46': - resolution: {integrity: sha512-m6G5VTzlOvXuVb8p1EEOHs9pZ714Q8UeveVHQ51PX0XiOh4M54i0cRUYTtwa+mRSIComVHOk997SrfFibPNJcw==} + '@tanstack/router-vite-plugin@1.166.47': + resolution: {integrity: sha512-9/SElKRWMP8qVR7+cU+LZpPu9P0bp+Ph+xf/idde3D6Uid+EieFWjH6WrMx+I3k7fsbsGP/bxFF89Xmi5D0IGg==} engines: {node: '>=20.19'} '@tanstack/solid-devtools@0.8.2': @@ -13561,8 +13572,8 @@ packages: peerDependencies: eslint: '>=7' - eslint-plugin-react-dom@5.6.6: - resolution: {integrity: sha512-2feT8VFFVUFiW6XGi43+YuF70+sEljA4jdqhOt30urmEfLQzxjlaV1EV2AeQSmIYd8Xp39GnHrTDvePbt79YDg==} + eslint-plugin-react-dom@5.7.0: + resolution: {integrity: sha512-aA3d27vFUqhfNnmBB9/b8INvBcJcVfIwrpdagBtzr5baHsznw8+VM02lCJeRW2l/0fHBKRctWQn3lRo1fPNCgQ==} engines: {node: '>=22.0.0'} peerDependencies: eslint: ^10.2.1 @@ -13574,36 +13585,36 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 - eslint-plugin-react-jsx@5.6.6: - resolution: {integrity: sha512-iCn5FMzerdCERMv4+Orpn7/E3WJnnu10PmAtyqgTrLQgy+UvWIPvEC2qRpnez/9cZsu1AtLL/xtYa+R55jGozg==} + eslint-plugin-react-jsx@5.7.0: + resolution: {integrity: sha512-tTeXNK1QP6YvfMyTCWg8i1ZHHFM67KKBWBNsayI/WAQ9micZ9m2vprbk/EPFJ/RgWqUgT7swIzt4powxiGqTqw==} engines: {node: '>=22.0.0'} peerDependencies: eslint: ^10.2.1 typescript: '*' - eslint-plugin-react-naming-convention@5.6.6: - resolution: {integrity: sha512-T9ys5uzp0a4Rg5oh71L52uWyGPzv21adNXb2rB+RO/o+2we7oCv2sxW5hUMu8F3d2zZmDPJtz42ASghaxGOo4w==} + eslint-plugin-react-naming-convention@5.7.0: + resolution: {integrity: sha512-s71ZdDG8sPlsP0H64Dy4FHgD/11FpsXWhKjkSo1rcTs5i9bONJwxRzUQV/99cTtigYSojaEb/fi/r7plo3ezmw==} engines: {node: '>=22.0.0'} peerDependencies: eslint: ^10.2.1 typescript: '*' - eslint-plugin-react-rsc@5.6.6: - resolution: {integrity: sha512-tBm5dtr8b755kLP5W/giV/0t4j9SVTEMxvZvVwtY8uD45Jn6TynNIH02jv9fRfuCsNsWehTT4ZEjbopiRPtgmw==} + eslint-plugin-react-rsc@5.7.0: + resolution: {integrity: sha512-fMZyrb8mW5EiQzrJRjM083+3EwAGgsTBXo7B7LFEUDcjV+CwH98hwjNRqLKN0GYPiGK5XVmMXg19P2sv5kI9MA==} engines: {node: '>=22.0.0'} peerDependencies: eslint: ^10.2.1 typescript: '*' - eslint-plugin-react-web-api@5.6.6: - resolution: {integrity: sha512-3SRULmUUi/Gt9I655pkNJeB+YVGw+Bc25dWoLPgis+Sfw+H4fjyXyrofcN6SdumcxU5RMiSO2yuw6v8O6Sj3RA==} + eslint-plugin-react-web-api@5.7.0: + resolution: {integrity: sha512-fiPrh9V/WuRGM+ie0IaLMj9mE/dLhkqZ9PLBJ9MWVH/NMZLigGX5uSDshrR7Hafm/b58XkM9Af3tt0wH99RC5w==} engines: {node: '>=22.0.0'} peerDependencies: eslint: ^10.2.1 typescript: '*' - eslint-plugin-react-x@5.6.6: - resolution: {integrity: sha512-Szx1VkEEell+b3j99dNHY0OJqCtDldfFaQLS0BmvlUOfV27cn/4yFqal9/0Tq8lU0T4Gvi0iiDhSmxib6KsyPA==} + eslint-plugin-react-x@5.7.0: + resolution: {integrity: sha512-bt0q1X1eK8fz0wJsBPWQvuNexDzM6jWjADM7qpdVEHm6uYnIiJaW0GIgrLecwt1YCmNyAcdh3kEeNFHEmeXfdw==} engines: {node: '>=22.0.0'} peerDependencies: eslint: ^10.2.1 @@ -16775,8 +16786,8 @@ packages: zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} - zod@4.4.1: - resolution: {integrity: sha512-a6ENMBBGZBsnlSebQ/eKCguSBeGKSf4O7BPnqVPmYGtpBYI7VSqoVqw+QcB7kPRjbqPwhYTpFbVj/RqNz/CT0Q==} + zod@4.4.2: + resolution: {integrity: sha512-IynmDyxsEsb9RKzO3J9+4SxXnl2FTFSzNBaKKaMV6tsSk0rw9gYw9gs+JFCq/qk2LCZ78KDwyj+Z289TijSkUw==} snapshots: @@ -18688,7 +18699,7 @@ snapshots: '@eslint-community/regexpp@4.12.2': {} - '@eslint-react/ast@5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3)': + '@eslint-react/ast@5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.59.1 '@typescript-eslint/typescript-estree': 8.59.1(typescript@6.0.3) @@ -18699,13 +18710,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint-react/core@5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3)': + '@eslint-react/core@5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3)': dependencies: - '@eslint-react/ast': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/eslint': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/jsx': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/shared': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/var': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/ast': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/eslint': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/jsx': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/shared': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/var': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.59.1 '@typescript-eslint/types': 8.59.1 '@typescript-eslint/utils': 8.59.1(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) @@ -18715,21 +18726,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint-react/eslint-plugin@5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3)': + '@eslint-react/eslint-plugin@5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3)': dependencies: - '@eslint-react/shared': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/shared': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) eslint: 10.3.0(jiti@2.6.1) - eslint-plugin-react-dom: 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - eslint-plugin-react-jsx: 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - eslint-plugin-react-naming-convention: 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - eslint-plugin-react-rsc: 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - eslint-plugin-react-web-api: 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - eslint-plugin-react-x: 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + eslint-plugin-react-dom: 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + eslint-plugin-react-jsx: 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + eslint-plugin-react-naming-convention: 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + eslint-plugin-react-rsc: 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + eslint-plugin-react-web-api: 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + eslint-plugin-react-x: 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@eslint-react/eslint@5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3)': + '@eslint-react/eslint@5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3)': dependencies: '@typescript-eslint/utils': 8.59.1(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) eslint: 10.3.0(jiti@2.6.1) @@ -18737,12 +18748,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint-react/jsx@5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3)': + '@eslint-react/jsx@5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3)': dependencies: - '@eslint-react/ast': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/eslint': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/shared': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/var': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/ast': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/eslint': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/shared': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/var': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/types': 8.59.1 '@typescript-eslint/utils': 8.59.1(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) eslint: 10.3.0(jiti@2.6.1) @@ -18751,21 +18762,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint-react/shared@5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3)': + '@eslint-react/shared@5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3)': dependencies: - '@eslint-react/eslint': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/eslint': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/utils': 8.59.1(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) eslint: 10.3.0(jiti@2.6.1) ts-pattern: 5.9.0 typescript: 6.0.3 - zod: 4.4.1 + zod: 4.4.2 transitivePeerDependencies: - supports-color - '@eslint-react/var@5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3)': + '@eslint-react/var@5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3)': dependencies: - '@eslint-react/ast': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/eslint': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/ast': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/eslint': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.59.1 '@typescript-eslint/types': 8.59.1 '@typescript-eslint/utils': 8.59.1(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) @@ -19957,6 +19968,13 @@ snapshots: - rollup - supports-color + '@preact/signals-core@1.14.1': {} + + '@preact/signals@2.9.0(preact@10.29.1)': + dependencies: + '@preact/signals-core': 1.14.1 + preact: 10.29.1 + '@prefresh/babel-plugin@0.5.3': {} '@prefresh/core@1.5.9(preact@10.29.1)': @@ -21457,7 +21475,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.167.31(@tanstack/react-router@1.169.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite-plugin-solid@2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3)))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3))(webpack@5.105.2(esbuild@0.28.0))': + '@tanstack/router-plugin@1.167.32(@tanstack/react-router@1.169.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite-plugin-solid@2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3)))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3))(webpack@5.105.2(esbuild@0.28.0))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) @@ -21494,9 +21512,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-vite-plugin@1.166.46(@tanstack/react-router@1.169.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite-plugin-solid@2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3)))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3))(webpack@5.105.2(esbuild@0.28.0))': + '@tanstack/router-vite-plugin@1.166.47(@tanstack/react-router@1.169.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite-plugin-solid@2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3)))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3))(webpack@5.105.2(esbuild@0.28.0))': dependencies: - '@tanstack/router-plugin': 1.167.31(@tanstack/react-router@1.169.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite-plugin-solid@2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3)))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3))(webpack@5.105.2(esbuild@0.28.0)) + '@tanstack/router-plugin': 1.167.32(@tanstack/react-router@1.169.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite-plugin-solid@2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3)))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(sugarss@5.0.1(postcss@8.5.13))(terser@5.46.0)(yaml@2.8.3))(webpack@5.105.2(esbuild@0.28.0)) transitivePeerDependencies: - '@rsbuild/core' - '@tanstack/react-router' @@ -23362,12 +23380,12 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-dom@5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3): + eslint-plugin-react-dom@5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3): dependencies: - '@eslint-react/ast': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/eslint': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/jsx': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/shared': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/ast': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/eslint': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/jsx': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/shared': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/types': 8.59.1 '@typescript-eslint/utils': 8.59.1(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) compare-versions: 6.1.1 @@ -23382,18 +23400,18 @@ snapshots: '@babel/parser': 7.29.2 eslint: 10.3.0(jiti@2.6.1) hermes-parser: 0.25.1 - zod: 4.4.1 - zod-validation-error: 4.0.2(zod@4.4.1) + zod: 4.4.2 + zod-validation-error: 4.0.2(zod@4.4.2) transitivePeerDependencies: - supports-color - eslint-plugin-react-jsx@5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3): + eslint-plugin-react-jsx@5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3): dependencies: - '@eslint-react/ast': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/core': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/eslint': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/jsx': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/shared': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/ast': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/core': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/eslint': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/jsx': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/shared': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/types': 8.59.1 '@typescript-eslint/utils': 8.59.1(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) eslint: 10.3.0(jiti@2.6.1) @@ -23401,12 +23419,12 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-naming-convention@5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3): + eslint-plugin-react-naming-convention@5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3): dependencies: - '@eslint-react/ast': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/core': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/eslint': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/var': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/ast': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/core': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/eslint': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/var': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/types': 8.59.1 '@typescript-eslint/utils': 8.59.1(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) eslint: 10.3.0(jiti@2.6.1) @@ -23415,13 +23433,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-rsc@5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3): + eslint-plugin-react-rsc@5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3): dependencies: - '@eslint-react/ast': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/core': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/eslint': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/shared': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/var': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/ast': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/core': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/eslint': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/shared': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/var': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/types': 8.59.1 '@typescript-eslint/utils': 8.59.1(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) eslint: 10.3.0(jiti@2.6.1) @@ -23429,13 +23447,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-web-api@5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3): + eslint-plugin-react-web-api@5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3): dependencies: - '@eslint-react/ast': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/core': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/eslint': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/shared': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/var': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/ast': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/core': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/eslint': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/shared': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/var': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/types': 8.59.1 '@typescript-eslint/utils': 8.59.1(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) birecord: 0.1.1 @@ -23445,14 +23463,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-x@5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3): + eslint-plugin-react-x@5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3): dependencies: - '@eslint-react/ast': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/core': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/eslint': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/jsx': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/shared': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) - '@eslint-react/var': 5.6.6(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/ast': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/core': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/eslint': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/jsx': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/shared': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/var': 5.7.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.59.1 '@typescript-eslint/type-utils': 8.59.1(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/types': 8.59.1 @@ -24270,7 +24288,7 @@ snapshots: tinyglobby: 0.2.16 unbash: 3.0.0 yaml: 2.8.3 - zod: 4.4.1 + zod: 4.4.2 transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -27100,12 +27118,12 @@ snapshots: dependencies: zod: 3.25.76 - zod-validation-error@4.0.2(zod@4.4.1): + zod-validation-error@4.0.2(zod@4.4.2): dependencies: - zod: 4.4.1 + zod: 4.4.2 zod@3.25.76: {} zod@4.3.6: {} - zod@4.4.1: {} + zod@4.4.2: {}