diff --git a/examples/preact/basic-external-atoms/index.html b/examples/preact/basic-external-atoms/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/basic-external-atoms/index.html +++ b/examples/preact/basic-external-atoms/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/basic-external-state/index.html b/examples/preact/basic-external-state/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/basic-external-state/index.html +++ b/examples/preact/basic-external-state/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/basic-use-app-table/index.html b/examples/preact/basic-use-app-table/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/basic-use-app-table/index.html +++ b/examples/preact/basic-use-app-table/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/basic-use-table/index.html b/examples/preact/basic-use-table/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/basic-use-table/index.html +++ b/examples/preact/basic-use-table/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/column-groups/index.html b/examples/preact/column-groups/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/column-groups/index.html +++ b/examples/preact/column-groups/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/column-ordering/index.html b/examples/preact/column-ordering/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/column-ordering/index.html +++ b/examples/preact/column-ordering/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/column-pinning-split/index.html b/examples/preact/column-pinning-split/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/column-pinning-split/index.html +++ b/examples/preact/column-pinning-split/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/column-pinning-sticky/index.html b/examples/preact/column-pinning-sticky/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/column-pinning-sticky/index.html +++ b/examples/preact/column-pinning-sticky/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/column-pinning/index.html b/examples/preact/column-pinning/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/column-pinning/index.html +++ b/examples/preact/column-pinning/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/column-resizing-performant/index.html b/examples/preact/column-resizing-performant/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/column-resizing-performant/index.html +++ b/examples/preact/column-resizing-performant/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/column-resizing/index.html b/examples/preact/column-resizing/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/column-resizing/index.html +++ b/examples/preact/column-resizing/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/column-sizing/index.html b/examples/preact/column-sizing/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/column-sizing/index.html +++ b/examples/preact/column-sizing/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/column-visibility/index.html b/examples/preact/column-visibility/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/column-visibility/index.html +++ b/examples/preact/column-visibility/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/composable-tables/index.html b/examples/preact/composable-tables/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/composable-tables/index.html +++ b/examples/preact/composable-tables/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/composable-tables/src/components/header-components.tsx b/examples/preact/composable-tables/src/components/header-components.tsx index c86bc2842d..d3e7309f90 100644 --- a/examples/preact/composable-tables/src/components/header-components.tsx +++ b/examples/preact/composable-tables/src/components/header-components.tsx @@ -35,7 +35,7 @@ export function ColumnFilter() { + onInput={(e) => header.column.setFilterValue((e.target as HTMLInputElement).value) } placeholder={`Filter ${header.column.id}...`} diff --git a/examples/preact/custom-plugin/index.html b/examples/preact/custom-plugin/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/custom-plugin/index.html +++ b/examples/preact/custom-plugin/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/custom-plugin/src/main.tsx b/examples/preact/custom-plugin/src/main.tsx index e3e984b5ad..41fb2d075b 100644 --- a/examples/preact/custom-plugin/src/main.tsx +++ b/examples/preact/custom-plugin/src/main.tsx @@ -345,7 +345,7 @@ function Filter({ + onInput={(e) => column.setFilterValue((old: [number, number]) => [ (e.target as HTMLInputElement).value, old[1], @@ -357,7 +357,7 @@ function Filter({ + onInput={(e) => column.setFilterValue((old: [number, number]) => [ old[0], (e.target as HTMLInputElement).value, @@ -371,7 +371,7 @@ function Filter({ + onInput={(e) => column.setFilterValue((e.target as HTMLInputElement).value) } placeholder={`Search...`} diff --git a/examples/preact/expanding/index.html b/examples/preact/expanding/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/expanding/index.html +++ b/examples/preact/expanding/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/expanding/src/main.tsx b/examples/preact/expanding/src/main.tsx index bf2fd26a15..16ac1f9d7f 100644 --- a/examples/preact/expanding/src/main.tsx +++ b/examples/preact/expanding/src/main.tsx @@ -284,7 +284,7 @@ function Filter({ + onInput={(e) => column.setFilterValue((old: [number, number] | undefined) => [ (e.target as HTMLInputElement).value, old?.[1], @@ -296,7 +296,7 @@ function Filter({ + onInput={(e) => column.setFilterValue((old: [number, number] | undefined) => [ old?.[0], (e.target as HTMLInputElement).value, @@ -310,7 +310,7 @@ function Filter({ + onInput={(e) => column.setFilterValue((e.target as HTMLInputElement).value) } placeholder={`Search...`} diff --git a/examples/preact/filters-faceted/index.html b/examples/preact/filters-faceted/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/filters-faceted/index.html +++ b/examples/preact/filters-faceted/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/filters-faceted/src/main.tsx b/examples/preact/filters-faceted/src/main.tsx index e05dd41964..483928948d 100644 --- a/examples/preact/filters-faceted/src/main.tsx +++ b/examples/preact/filters-faceted/src/main.tsx @@ -375,7 +375,7 @@ function DebouncedInput({ setValue((e.target as HTMLInputElement).value)} + onInput={(e) => setValue((e.target as HTMLInputElement).value)} /> ) } diff --git a/examples/preact/filters-fuzzy/index.html b/examples/preact/filters-fuzzy/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/filters-fuzzy/index.html +++ b/examples/preact/filters-fuzzy/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/filters-fuzzy/src/main.tsx b/examples/preact/filters-fuzzy/src/main.tsx index 9ac6453d46..c8d31d641e 100644 --- a/examples/preact/filters-fuzzy/src/main.tsx +++ b/examples/preact/filters-fuzzy/src/main.tsx @@ -341,7 +341,7 @@ function DebouncedInput({ setValue((e.target as HTMLInputElement).value)} + onInput={(e) => setValue((e.target as HTMLInputElement).value)} /> ) } diff --git a/examples/preact/filters/index.html b/examples/preact/filters/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/filters/index.html +++ b/examples/preact/filters/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/filters/src/main.tsx b/examples/preact/filters/src/main.tsx index d7c070c810..dc1fecac8f 100644 --- a/examples/preact/filters/src/main.tsx +++ b/examples/preact/filters/src/main.tsx @@ -349,7 +349,7 @@ function DebouncedInput({ setValue((e.target as HTMLInputElement).value)} + onInput={(e) => setValue((e.target as HTMLInputElement).value)} /> ) } diff --git a/examples/preact/grouping/index.html b/examples/preact/grouping/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/grouping/index.html +++ b/examples/preact/grouping/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/pagination/index.html b/examples/preact/pagination/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/pagination/index.html +++ b/examples/preact/pagination/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/pagination/src/main.tsx b/examples/preact/pagination/src/main.tsx index ecf586f25d..5f738d6bc0 100644 --- a/examples/preact/pagination/src/main.tsx +++ b/examples/preact/pagination/src/main.tsx @@ -256,7 +256,7 @@ function Filter({ + onInput={(e) => column.setFilterValue((old: [number, number]) => [ (e.target as HTMLInputElement).value, old[1], @@ -268,7 +268,7 @@ function Filter({ + onInput={(e) => column.setFilterValue((old: [number, number]) => [ old[0], (e.target as HTMLInputElement).value, @@ -281,7 +281,7 @@ function Filter({ ) : ( + onInput={(e) => column.setFilterValue((e.target as HTMLInputElement).value) } onClick={(e) => e.stopPropagation()} diff --git a/examples/preact/row-pinning/index.html b/examples/preact/row-pinning/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/row-pinning/index.html +++ b/examples/preact/row-pinning/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/row-pinning/src/main.tsx b/examples/preact/row-pinning/src/main.tsx index 3afa2cbab6..1b9b139bff 100644 --- a/examples/preact/row-pinning/src/main.tsx +++ b/examples/preact/row-pinning/src/main.tsx @@ -406,7 +406,7 @@ function Filter({ + onInput={(e) => column.setFilterValue((old: any) => [ (e.target as HTMLInputElement).value, old?.[1], @@ -418,7 +418,7 @@ function Filter({ + onInput={(e) => column.setFilterValue((old: any) => [ old?.[0], (e.target as HTMLInputElement).value, @@ -432,7 +432,7 @@ function Filter({ + onInput={(e) => column.setFilterValue((e.target as HTMLInputElement).value) } placeholder={`Search...`} diff --git a/examples/preact/row-selection/index.html b/examples/preact/row-selection/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/row-selection/index.html +++ b/examples/preact/row-selection/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/row-selection/src/main.tsx b/examples/preact/row-selection/src/main.tsx index 69c988f510..fa79a39781 100644 --- a/examples/preact/row-selection/src/main.tsx +++ b/examples/preact/row-selection/src/main.tsx @@ -152,7 +152,7 @@ function App() {
+ onInput={(e) => table.setGlobalFilter((e.target as HTMLInputElement).value) } className="summary-panel" @@ -363,7 +363,7 @@ function Filter({ + onInput={(e) => column.setFilterValue((old: any) => [ (e.target as HTMLInputElement).value, old?.[1], @@ -375,7 +375,7 @@ function Filter({ + onInput={(e) => column.setFilterValue((old: any) => [ old?.[0], (e.target as HTMLInputElement).value, @@ -389,7 +389,7 @@ function Filter({ + onInput={(e) => column.setFilterValue((e.target as HTMLInputElement).value) } placeholder={`Search...`} diff --git a/examples/preact/sorting/index.html b/examples/preact/sorting/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/sorting/index.html +++ b/examples/preact/sorting/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/sub-components/index.html b/examples/preact/sub-components/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/sub-components/index.html +++ b/examples/preact/sub-components/index.html @@ -4,7 +4,6 @@ - Vite + Preact diff --git a/examples/preact/with-tanstack-query/index.html b/examples/preact/with-tanstack-query/index.html index 88776ec7ae..fff0f71352 100644 --- a/examples/preact/with-tanstack-query/index.html +++ b/examples/preact/with-tanstack-query/index.html @@ -4,7 +4,6 @@ - Vite + Preact 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/helpers/createTableHook.ts b/packages/angular-table/src/helpers/createTableHook.ts index f6c278655c..0d1ab14097 100644 --- a/packages/angular-table/src/helpers/createTableHook.ts +++ b/packages/angular-table/src/helpers/createTableHook.ts @@ -446,8 +446,11 @@ export function createTableHook< const options = { ...defaultTableOptions, ...tableOptions(), + _features: { + ...defaultTableOptions._features, + appTableFeatures, + }, } as TableOptions - options._features = { ...options._features, appTableFeatures } return options }, selector) as AngularTable } diff --git a/packages/angular-table/src/injectTable.ts b/packages/angular-table/src/injectTable.ts index 06a2bd6531..4a12ee11fe 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 './reactivity' 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,56 @@ 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: { + coreReativityFeature: angularReactivity(injector), ...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' }, - ) + }) 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/reactivity.ts b/packages/angular-table/src/reactivity.ts new file mode 100644 index 0000000000..71ed087f62 --- /dev/null +++ b/packages/angular-table/src/reactivity.ts @@ -0,0 +1,65 @@ +import { computed, signal, untracked } from '@angular/core' +import { toObservable } from '@angular/core/rxjs-interop' +import type { Atom, Observer, ReadonlyAtom } from '@tanstack/angular-store' +import type { + TableAtomOptions, + TableReactivityBindings, +} from '@tanstack/table-core/reactivity' +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..b759d572db 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 { constructReactivityBindings } from '@tanstack/table-core/reactivity' import { FlexRender } from './flexRender' import type { Atom, ReadonlyAtom } from '@tanstack/store' import type { @@ -146,18 +144,11 @@ export class TableController< ({}) as TSelected, ): LitTable { if (!this._table) { - const litReactivityFeature = constructReactivityFeature( - { - stateNotifier: () => this._notifier, - optionsNotifier: () => this._notifier, - }, - ) - const mergedOptions: TableOptions = { ...tableOptions, _features: { + coreReativityFeature: constructReactivityBindings(), ...tableOptions._features, - litReactivityFeature, }, mergeOptions: ( defaultOptions: TableOptions, 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..9d8e572681 --- /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/reactivity.ts b/packages/preact-table/src/reactivity.ts new file mode 100644 index 0000000000..08ac3488ea --- /dev/null +++ b/packages/preact-table/src/reactivity.ts @@ -0,0 +1,63 @@ +// TOTO - re-explore preact signals for reactivity +import { batch, computed, signal, untracked } from '@preact/signals' +import type { + TableAtomOptions, + TableReactivityBindings, +} from '@tanstack/table-core/reactivity' +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..977d7a7431 100644 --- a/packages/preact-table/src/useTable.ts +++ b/packages/preact-table/src/useTable.ts @@ -1,8 +1,10 @@ -import { useMemo, useState } from 'preact/hooks' +import { useEffect, useMemo, useState } from 'preact/hooks' import { constructTable } from '@tanstack/table-core' import { shallow, useSelector } from '@tanstack/preact-store' +import { constructReactivityBindings } from '@tanstack/table-core/reactivity' import { FlexRender } from './FlexRender' import { Subscribe } from './Subscribe' +// import { preactReactivity } from './reactivity' import type { CellData, RowData, @@ -92,11 +94,13 @@ export function useTable< ({}) as TSelected, ): PreactTable { const [table] = useState(() => { - const tableInstance = constructTable(tableOptions) as PreactTable< - TFeatures, - TData, - TSelected - > + const tableInstance = constructTable({ + ...tableOptions, + _features: { + coreReativityFeature: constructReactivityBindings(), // preactReactivity() currently causes infinite re-renders + ...tableOptions._features, + }, + }) as PreactTable tableInstance.Subscribe = ((props: any) => { return Subscribe({ @@ -110,27 +114,24 @@ 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 selectorWithDataAndColumns = (state: TableState) => ({ - columns: tableOptions.columns, - data: tableOptions.data, - ...selector(state), - }) - - const state = useSelector(table.store, selectorWithDataAndColumns, { + const state = useSelector(table.store, selector, { compare: shallow }) + const options = useSelector(table.optionsStore, (options) => options, { compare: shallow, }) return useMemo( () => ({ ...table, + options, state, }), - [state, table], + [table, options, state], ) as PreactTable } 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..6706b4b07c --- /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/reactivity' + +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/useLegacyTable.ts b/packages/react-table/src/useLegacyTable.ts index eec0d424da..daa0ad7474 100644 --- a/packages/react-table/src/useLegacyTable.ts +++ b/packages/react-table/src/useLegacyTable.ts @@ -15,7 +15,7 @@ import { sortFns, stockFeatures, } from '@tanstack/table-core' -import { useCallback, useMemo } from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTable } from './useTable' import type { AggregationFns, @@ -402,52 +402,55 @@ export function useLegacyTable( ...restOptions } = options - // Build the _rowModels object based on which legacy options were provided - const _rowModels: CreateRowModels_All = {} - - // Map v8 row model factories to v9 _rowModels - // Note: getCoreRowModel is handled automatically in v9, so we ignore it - - if (getFilteredRowModel) { - _rowModels.filteredRowModel = createFilteredRowModel({ - ...filterFns, - ...options.filterFns, - }) - } - - if (getSortedRowModel) { - _rowModels.sortedRowModel = createSortedRowModel({ - ...sortFns, - ...options.sortFns, - }) - } - - if (getPaginationRowModel) { - _rowModels.paginatedRowModel = createPaginatedRowModel() - } - - if (getExpandedRowModel) { - _rowModels.expandedRowModel = createExpandedRowModel() - } - - if (getGroupedRowModel) { - _rowModels.groupedRowModel = createGroupedRowModel({ - ...aggregationFns, - ...options.aggregationFns, - }) - } - - if (getFacetedRowModel) { - _rowModels.facetedRowModel = createFacetedRowModel() - } - - if (getFacetedMinMaxValues) { - _rowModels.facetedMinMaxValues = createFacetedMinMaxValues() - } - - if (getFacetedUniqueValues) { - _rowModels.facetedUniqueValues = createFacetedUniqueValues() - } + const [_rowModels] = useState(() => { + const rowModels: CreateRowModels_All = {} + + // Legacy row model options are setup-only. Capture the first render's + // marker options to match the table instance lifecycle. + + if (getFilteredRowModel) { + rowModels.filteredRowModel = createFilteredRowModel({ + ...filterFns, + ...options.filterFns, + }) + } + + if (getSortedRowModel) { + rowModels.sortedRowModel = createSortedRowModel({ + ...sortFns, + ...options.sortFns, + }) + } + + if (getPaginationRowModel) { + rowModels.paginatedRowModel = createPaginatedRowModel() + } + + if (getExpandedRowModel) { + rowModels.expandedRowModel = createExpandedRowModel() + } + + if (getGroupedRowModel) { + rowModels.groupedRowModel = createGroupedRowModel({ + ...aggregationFns, + ...options.aggregationFns, + }) + } + + if (getFacetedRowModel) { + rowModels.facetedRowModel = createFacetedRowModel() + } + + if (getFacetedMinMaxValues) { + rowModels.facetedMinMaxValues = createFacetedMinMaxValues() + } + + if (getFacetedUniqueValues) { + rowModels.facetedUniqueValues = createFacetedUniqueValues() + } + + return rowModels + }) // Call useTable with the v9 API, subscribing to all state changes const table = useTable>( @@ -460,12 +463,7 @@ export function useLegacyTable( ) const getState = useCallback(() => { - // all state except for columns and data - return { - ...table.state, - columns: undefined, - data: undefined, - } + return table.state }, [table]) const setState = useCallback( diff --git a/packages/react-table/src/useTable.ts b/packages/react-table/src/useTable.ts index 4fe97567f9..aea2b679a7 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 { shallow, useSelector } from '@tanstack/react-store' +import { constructReactivityBindings } from '@tanstack/table-core/reactivity' 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,13 @@ export function useTable< ({}) as TSelected, ): ReactTable { const [table] = useState(() => { - const tableInstance = constructTable(tableOptions) as ReactTable< - TFeatures, - TData, - TSelected - > + const tableInstance = constructTable({ + ...tableOptions, + _features: { + coreReativityFeature: constructReactivityBindings(), + ...tableOptions._features, + }, + }) as ReactTable tableInstance.Subscribe = ((props: any) => { return Subscribe({ @@ -139,22 +142,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..573bd34fa5 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 './reactivity' import type { Atom, ReadonlyAtom } from '@tanstack/solid-store' import type { Accessor, JSX } from 'solid-js' import type { @@ -73,17 +71,13 @@ 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, - }), + _features: { + coreReativityFeature: solidReactivity(owner), + ...tableOptions._features, + }, }) as any const resolvedOptions = mergeProps( @@ -104,9 +98,6 @@ export function createTable< TSelected > - const allState = useSelector(table.store) - const allOptions = useSelector(table.optionsStore) - createComputed(() => { const userState = tableOptions.state if (userState) { @@ -122,12 +113,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/reactivity.ts b/packages/solid-table/src/reactivity.ts new file mode 100644 index 0000000000..1b3cb44366 --- /dev/null +++ b/packages/solid-table/src/reactivity.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/reactivity' +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..9c24a4a1de 100644 --- a/packages/svelte-table/src/createTable.svelte.ts +++ b/packages/svelte-table/src/createTable.svelte.ts @@ -1,10 +1,8 @@ -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 './reactivity.svelte' import type { RowData, Table, @@ -38,23 +36,15 @@ 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 + _features: { + coreReativityFeature: svelteReactivity(), + ...tableOptions._features, + }, + }) as TableOptions - // 4. Set up resolved options with mergeOptions handler + // 2. Set up resolved options with mergeOptions handler const resolvedOptions = mergeObjects( { mergeOptions: ( @@ -67,40 +57,23 @@ export function createTable< mergedOptions, ) as TableOptions - // 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 +83,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/reactivity.svelte.ts b/packages/svelte-table/src/reactivity.svelte.ts new file mode 100644 index 0000000000..0238b914c2 --- /dev/null +++ b/packages/svelte-table/src/reactivity.svelte.ts @@ -0,0 +1,64 @@ +import { flushSync, untrack } from 'svelte' +import type { + TableAtomOptions, + TableReactivityBindings, +} from '@tanstack/table-core/reactivity' +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..0425f26c60 100644 --- a/packages/table-core/package.json +++ b/packages/table-core/package.json @@ -38,6 +38,10 @@ "import": "./dist/flex-render.js", "require": "./dist/flex-render.cjs" }, + "./reactivity": { + "import": "./dist/reactivity.js", + "require": "./dist/reactivity.cjs" + }, "./static-functions": { "import": "./dist/static-functions.js", "require": "./dist/static-functions.cjs" diff --git a/packages/table-core/src/core/coreFeatures.ts b/packages/table-core/src/core/coreFeatures.ts index 22d11f5762..656ff949de 100644 --- a/packages/table-core/src/core/coreFeatures.ts +++ b/packages/table-core/src/core/coreFeatures.ts @@ -4,8 +4,10 @@ import { coreHeadersFeature } from './headers/coreHeadersFeature' import { coreRowModelsFeature } from './row-models/coreRowModelsFeature' import { coreRowsFeature } from './rows/coreRowsFeature' import { coreTablesFeature } from './table/coreTablesFeature' +import type { TableReactivityBindings } from '../reactivity' export interface CoreFeatures { + coreReativityFeature?: TableReactivityBindings coreCellsFeature: typeof coreCellsFeature coreColumnsFeature: typeof coreColumnsFeature coreHeadersFeature: typeof coreHeadersFeature diff --git a/packages/table-core/src/core/reactivity/constructReactivityBindings.ts b/packages/table-core/src/core/reactivity/constructReactivityBindings.ts new file mode 100644 index 0000000000..2c94853904 --- /dev/null +++ b/packages/table-core/src/core/reactivity/constructReactivityBindings.ts @@ -0,0 +1,19 @@ +import { batch, createAtom } from '@tanstack/store' +import type { TableReactivityBindings } from './coreReactivityFeature.types' + +export function constructReactivityBindings(): 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/core/reactivity/coreReactivityFeature.types.ts b/packages/table-core/src/core/reactivity/coreReactivityFeature.types.ts new file mode 100644 index 0000000000..8e323c1c4c --- /dev/null +++ b/packages/table-core/src/core/reactivity/coreReactivityFeature.types.ts @@ -0,0 +1,40 @@ +import type { Atom, AtomOptions, ReadonlyAtom } 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 +} diff --git a/packages/table-core/src/core/reactivity/coreReactivityFeature.utils.ts b/packages/table-core/src/core/reactivity/coreReactivityFeature.utils.ts new file mode 100644 index 0000000000..54ef1f90dc --- /dev/null +++ b/packages/table-core/src/core/reactivity/coreReactivityFeature.utils.ts @@ -0,0 +1,29 @@ +import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' + +/** + * 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/core/table/constructTable.ts b/packages/table-core/src/core/table/constructTable.ts index de0eca24bc..be14a21389 100644 --- a/packages/table-core/src/core/table/constructTable.ts +++ b/packages/table-core/src/core/table/constructTable.ts @@ -1,6 +1,6 @@ -import { createAtom, createStore } from '@tanstack/store' import { coreFeatures } from '../coreFeatures' import { cloneState } from '../../utils' +import { atomToStore } from '../reactivity/coreReactivityFeature.utils' import type { RowData } from '../../types/type-utils' import type { TableFeature, TableFeatures } from '../../types/TableFeatures' import type { Table, Table_Internal } from '../../types/Table' @@ -21,15 +21,18 @@ export function constructTable< TFeatures extends TableFeatures, TData extends RowData, >(tableOptions: TableOptions): Table { + const _reactivity = tableOptions._features.coreReativityFeature! + const table = { + _reactivity, _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 +44,10 @@ export function constructTable< return Object.assign(obj, feature.getDefaultTableOptions?.(table)) }, {}) as TableOptions - table.optionsStore = createStore({ - ...defaultOptions, - ...tableOptions, - }) + // @ts-ignore - direct set + table.optionsStore = _reactivity.createWritableAtom< + TableOptions + >({ ...defaultOptions, ...tableOptions }, { debugName: 'table/optionsStore' }) table.initialState = getInitialTableState( table._features, @@ -57,31 +60,44 @@ export function constructTable< for (const key of stateKeys) { // create writable base atom - table.baseAtoms[key] = createAtom(table.initialState[key]) as any + table.baseAtoms[key] = _reactivity.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] = _reactivity.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( + _reactivity.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..5e0bcab3e3 100644 --- a/packages/table-core/src/core/table/coreTablesFeature.types.ts +++ b/packages/table-core/src/core/table/coreTablesFeature.types.ts @@ -1,4 +1,5 @@ -import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' +import type { TableReactivityBindings } from '../reactivity/coreReactivityFeature.types' +import type { Atom, ReadonlyAtom, ReadonlyStore } from '@tanstack/store' import type { CoreFeatures } from '../coreFeatures' import type { RowModelFns } from '../../types/RowModelFns' import type { RowData, Updater } from '../../types/type-utils' @@ -67,11 +68,11 @@ export interface TableOptions_Table< /** * The features that you want to enable for the table. */ - _features: TFeatures + readonly _features: TFeatures /** * The row model options that you want to enable for the table. */ - _rowModels?: CreateRowModels_All + readonly _rowModels?: CreateRowModels_All /** * Optionally, provide your own external writable atoms for individual state slices. * When an atom is provided for a given slice, it takes precedence over `options.state[key]` @@ -79,35 +80,35 @@ export interface TableOptions_Table< * still routed through the internal base atom; consumers are responsible for * mirroring changes back to their external atom via the corresponding `onXChange` callback. */ - atoms?: ExternalAtoms + readonly atoms?: ExternalAtoms /** * Set this option to override any of the `autoReset...` feature options. */ - autoResetAll?: boolean + readonly autoResetAll?: boolean /** * The data for the table to display. When the `data` option changes reference, the table will reprocess the data. */ - data: ReadonlyArray + readonly data: ReadonlyArray /** * Use this option to optionally pass initial state to the table. This state will be used when resetting various table states either automatically by the table (eg. `options.autoResetPageIndex`) or via functions like `table.resetRowSelection()`. Most reset function allow you optionally pass a flag to reset to a blank/default state instead of the initial state. * Table state will not be reset when this object changes, which also means that the initial state object does not need to be stable. */ - initialState?: Partial> + readonly initialState?: Partial> /** * This option is used to optionally implement the merging of table options. */ - mergeOptions?: ( + readonly mergeOptions?: ( defaultOptions: TableOptions, options: Partial>, ) => TableOptions /** * You can pass any object to `options.meta` and access it anywhere the `table` is available via `table.options.meta`. */ - meta?: TableMeta + readonly meta?: TableMeta /** * Pass in individual self-managed state to the table. */ - state?: Partial> + readonly state?: Partial> } export interface Table_CoreProperties< @@ -115,9 +116,9 @@ export interface Table_CoreProperties< TData extends RowData, > { /** - * The features that are enabled for the table. + * Table reactivity bindings for interacting with TanStack Store. */ - _features: Partial & TFeatures + readonly _reactivity: TableReactivityBindings /** * Prototype cache for Cell objects - shared by all cells in this table */ @@ -126,6 +127,10 @@ export interface Table_CoreProperties< * Prototype cache for Column objects - shared by all columns in this table */ _columnPrototype?: object + /** + * The features that are enabled for the table. + */ + readonly _features: Partial & TFeatures /** * Prototype cache for Header objects - shared by all headers in this table */ @@ -133,43 +138,43 @@ export interface Table_CoreProperties< /** * The row model processing functions that are used to process the data by features. */ - _rowModelFns: RowModelFns + readonly _rowModelFns: RowModelFns /** * The row models that are enabled for the table. */ - _rowModels: CachedRowModels + readonly _rowModels: CachedRowModels /** * Prototype cache for Row objects - shared by all rows in this table */ _rowPrototype?: object - /** - * The internal writable atoms for each `TableState` slice. This is the library's - * single write surface — all state mutations from features land here. - */ - baseAtoms: BaseAtoms /** * The readonly derived atoms for each `TableState` slice. Each derives from * its corresponding `baseAtom` plus, optionally, a per-slice external atom or * external state value (precedence: external atom > external state > base atom). */ - atoms: Atoms + readonly atoms: Atoms /** - * The base store for the table options. + * The internal writable atoms for each `TableState` slice. This is the library's + * single write surface — all state mutations from features land here. */ - optionsStore: Store> + readonly baseAtoms: BaseAtoms /** * This is the resolved initial state of the table. */ - initialState: TableState + readonly initialState: TableState /** * A read-only reference to the table's current options. */ readonly options: TableOptions + /** + * The base store for the table options. + */ + readonly optionsStore: Atom> /** * The readonly flat store for the table state. Derives from `table.atoms` * only; never reads external state directly. */ - store: ReadonlyStore> + readonly store: ReadonlyStore> } export interface Table_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..01872f17ed 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/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/index.ts b/packages/table-core/src/index.ts index 3ed19019b4..06805fe7ff 100755 --- a/packages/table-core/src/index.ts +++ b/packages/table-core/src/index.ts @@ -71,9 +71,6 @@ export * from './fns/sortFns' export * from './features/stockFeatures' -// tableReactivityFeature -export * from './features/table-reactivity/tableReactivityFeature' - // columnFacetingFeature export * from './features/column-faceting/columnFacetingFeature' export * from './features/column-faceting/columnFacetingFeature.types' diff --git a/packages/table-core/src/reactivity.ts b/packages/table-core/src/reactivity.ts new file mode 100644 index 0000000000..86ad4e0347 --- /dev/null +++ b/packages/table-core/src/reactivity.ts @@ -0,0 +1,3 @@ +export * from './core/reactivity/coreReactivityFeature.types' +export * from './core/reactivity/constructReactivityBindings' +export * from './core/reactivity/coreReactivityFeature.utils' 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/src/types/TableFeatures.ts b/packages/table-core/src/types/TableFeatures.ts index 85f23d3082..d55702f6e4 100644 --- a/packages/table-core/src/types/TableFeatures.ts +++ b/packages/table-core/src/types/TableFeatures.ts @@ -12,13 +12,13 @@ export type ExtractFeatureTypes< TFeatures extends TableFeatures, > = UnionToIntersection< { - [K in keyof TFeatures]: TFeatures[K] extends TableFeature< - infer FeatureConstructorOptions - > - ? TKey extends keyof FeatureConstructorOptions - ? FeatureConstructorOptions[TKey] - : never - : any + [K in keyof TFeatures]: K extends 'coreReativityFeature' + ? never + : TFeatures[K] extends TableFeature + ? TKey extends keyof FeatureConstructorOptions + ? FeatureConstructorOptions[TKey] + : never + : any }[keyof TFeatures] > diff --git a/packages/table-core/tests/helpers/generateTestTable.ts b/packages/table-core/tests/helpers/generateTestTable.ts index c89144871e..3f47ee2141 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 { constructReactivityBindings } from '../../src/core/reactivity/constructReactivityBindings' import type { Row, Table, @@ -31,6 +32,7 @@ export function generateTestTableWithData( _features: { ...coreFeatures, ...options?._features, + coreReativityFeature: constructReactivityBindings(), }, } as any) } @@ -47,6 +49,7 @@ export function generateTestTableFromData( _features: { ...coreFeatures, ...options?._features, + coreReativityFeature: constructReactivityBindings(), }, } as any) } diff --git a/packages/table-core/tests/helpers/rowPinningHelpers.ts b/packages/table-core/tests/helpers/rowPinningHelpers.ts index a74b25dcc9..8537623555 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 { constructReactivityBindings } from '../../src/core/reactivity/constructReactivityBindings' import { generateTestTableWithData } from './generateTestTable' import type { ColumnDef, RowPinningState, TableOptions } from '../../src' import type { Person } from '../fixtures/data/types' @@ -15,6 +16,7 @@ import type { Person } from '../fixtures/data/types' const _features = { ...coreFeatures, rowPinningFeature, + coreReativityFeature: constructReactivityBindings(), } as any type personKeys = keyof Person 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..c5e4570e71 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 { constructReactivityBindings } from '../../../../src/core/reactivity/constructReactivityBindings' import type { ColumnDef, Row } from '../../../../src' import type { Person } from '../../../fixtures/data/types' @@ -19,6 +20,7 @@ import type { Person } from '../../../fixtures/data/types' const _features = { ...coreFeatures, rowPinningFeature, + coreReativityFeature: constructReactivityBindings(), } type personKeys = keyof Person @@ -169,6 +171,7 @@ describe('table methods', () => { ...coreFeatures, rowPinningFeature, rowPaginationFeature, + coreReativityFeature: constructReactivityBindings(), } const table = constructTable({ @@ -178,7 +181,7 @@ describe('table methods', () => { }, data, columns, - getSubRows: (row) => row.subRows, + getSubRows: (originalRow: Person, _idx: number) => originalRow.subRows, enableRowPinning: true, renderFallbackValue: '', initialState: { @@ -208,6 +211,7 @@ describe('table methods', () => { ...coreFeatures, rowPinningFeature, rowPaginationFeature, + coreReativityFeature: constructReactivityBindings(), } const table = constructTable({ @@ -217,7 +221,7 @@ describe('table methods', () => { }, data, columns, - getSubRows: (row) => row.subRows, + getSubRows: (originalRow: Person, _idx: number) => originalRow.subRows, enableRowPinning: true, renderFallbackValue: '', initialState: { 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..e5d661d2d3 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 @@ -1,18 +1,22 @@ import { describe, expect, it } from 'vitest' import { constructTable, + coreFeatures, createColumnHelper, rowSelectionFeature, } from '../../../../src' import * as RowSelectionUtils from '../../../../src/features/row-selection/rowSelectionFeature.utils' import { generateTestData } from '../../../fixtures/data/generateTestData' +import { constructReactivityBindings } from '../../../../src/core/reactivity/constructReactivityBindings' import type { Person } from '../../../fixtures/data/types' -import type { ColumnDef } from '../../../../src' +import type { ColumnDef, Row } from '../../../../src' // TODO: bring up to new test structure const _features = { + ...coreFeatures, rowSelectionFeature, + coreReativityFeature: constructReactivityBindings(), } type personKeys = keyof Person @@ -44,7 +48,7 @@ describe('rowSelectionFeature', () => { enableRowSelection: true, renderFallbackValue: '', data, - getSubRows: (row) => row.subRows, + getSubRows: (originalRow: Person, _idx: number) => originalRow.subRows, initialState: { rowSelection: { '0': true, @@ -73,7 +77,7 @@ describe('rowSelectionFeature', () => { enableRowSelection: true, renderFallbackValue: '', data, - getSubRows: (row) => row.subRows, + getSubRows: (originalRow: Person, _idx: number) => originalRow.subRows, initialState: { rowSelection: { '0': true, @@ -102,7 +106,7 @@ describe('rowSelectionFeature', () => { enableRowSelection: true, renderFallbackValue: '', data, - getSubRows: (row) => row.subRows, + getSubRows: (originalRow: Person, _idx: number) => originalRow.subRows, initialState: { rowSelection: {}, }, @@ -245,7 +249,7 @@ describe('rowSelectionFeature', () => { enableRowSelection: true, renderFallbackValue: '', data, - getSubRows: (row) => row.subRows, + getSubRows: (originalRow: Person, _idx: number) => originalRow.subRows, initialState: { rowSelection: {}, }, @@ -269,7 +273,7 @@ describe('rowSelectionFeature', () => { enableRowSelection: true, renderFallbackValue: '', data, - getSubRows: (row) => row.subRows, + getSubRows: (originalRow: Person, _idx: number) => originalRow.subRows, initialState: { rowSelection: { '0.0': true, @@ -295,7 +299,7 @@ describe('rowSelectionFeature', () => { enableRowSelection: true, renderFallbackValue: '', data, - getSubRows: (row) => row.subRows, + getSubRows: (originalRow: Person, _idx: number) => originalRow.subRows, initialState: { rowSelection: { '0.0': true, @@ -318,10 +322,11 @@ describe('rowSelectionFeature', () => { const table = constructTable({ _features, _rowModels: {}, - enableRowSelection: (row) => row.index === 0, // only first row is selectable (of 2 sub-rows) + enableRowSelection: (row: Row) => + row.index === 0, // only first row is selectable (of 2 sub-rows) renderFallbackValue: '', data, - getSubRows: (row) => row.subRows, + getSubRows: (originalRow: Person, _idx: number) => originalRow.subRows, initialState: { rowSelection: { '0.0': true, // first sub-row @@ -346,7 +351,7 @@ describe('rowSelectionFeature', () => { enableRowSelection: true, renderFallbackValue: '', data, - getSubRows: (row) => row.subRows, + getSubRows: (originalRow: Person, _idx: number) => originalRow.subRows, initialState: { rowSelection: { '0.0.0': true, // first nested sub-row 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..89525337d3 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 { constructReactivityBindings } from '../../../../src/core/reactivity/constructReactivityBindings' import type { Person } from '../../../fixtures/data/types' import type { ColumnDef } from '../../../../src' @@ -42,7 +43,11 @@ describe('#getGroupedRowModel', () => { data.forEach((p) => (p.age = 123)) const table = constructTable({ - _features: { columnGroupingFeature, ...coreFeatures }, + _features: { + columnGroupingFeature, + ...coreFeatures, + coreReativityFeature: constructReactivityBindings(), + }, _rowModels: { groupedRowModel: createGroupedRowModel(aggregationFns), }, 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..cab80dec67 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,16 @@ 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 { constructReactivityBindings } from '../../../../src/core/reactivity/constructReactivityBindings' 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 }, + _features: { + coreColumnsFeature, + coreReativityFeature: constructReactivityBindings(), + }, 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..cf9a489d95 100644 --- a/packages/table-core/tests/unit/core/table/constructTable.test.ts +++ b/packages/table-core/tests/unit/core/table/constructTable.test.ts @@ -1,11 +1,13 @@ import { describe, expect, it } from 'vitest' import { constructTable, coreFeatures } from '../../../../src' +import { constructReactivityBindings } from '../../../../src/core/reactivity/constructReactivityBindings' describe('constructTable', () => { it('should create a table with all core table APIs and properties', () => { const table = constructTable({ _features: { ...coreFeatures, + coreReativityFeature: constructReactivityBindings(), }, 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..e14704da3e 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,14 @@ import { describe, expect, it } from 'vitest' import { constructTable, stockFeatures } from '../../../../src' +import { constructReactivityBindings } from '../../../../src/core/reactivity/constructReactivityBindings' describe('constructTable with stockFeatures', () => { it('should include all feature states in initial state', () => { const table = constructTable({ - _features: stockFeatures, + _features: { + ...stockFeatures, + coreReativityFeature: constructReactivityBindings(), + }, 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..b10ba9f200 100644 --- a/packages/table-core/tests/unit/core/tableAtoms.test.ts +++ b/packages/table-core/tests/unit/core/tableAtoms.test.ts @@ -2,12 +2,17 @@ import { describe, expect, it, vi } from 'vitest' import { createAtom } from '@tanstack/store' import { constructTable, + coreFeatures, rowPaginationFeature, rowSelectionFeature, rowSortingFeature, } from '../../../src' -import type { Table_Internal } from '../../../src' -import type { PaginationState, SortingState } from '../../../src' +import { constructReactivityBindings } from '../../../src/core/reactivity/constructReactivityBindings' +import type { + PaginationState, + SortingState, + Table_Internal, +} from '../../../src' const _features = { rowPaginationFeature, @@ -17,7 +22,11 @@ const _features = { function makeTable(options: any = {}) { return constructTable({ - _features, + _features: { + ...coreFeatures, + ..._features, + coreReativityFeature: constructReactivityBindings(), + }, _rowModels: {}, columns: [], data: [], diff --git a/packages/table-core/tsdown.config.ts b/packages/table-core/tsdown.config.ts index c305d9b999..ff0c1b884d 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/reactivity.ts', ], format: ['esm', 'cjs'], unbundle: true, diff --git a/packages/vue-table/src/reactivity.ts b/packages/vue-table/src/reactivity.ts new file mode 100644 index 0000000000..7c1ba3788c --- /dev/null +++ b/packages/vue-table/src/reactivity.ts @@ -0,0 +1,63 @@ +import { computed, shallowRef, watch } from 'vue' +import type { + TableAtomOptions, + TableReactivityBindings, +} from '@tanstack/table-core/reactivity' +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..f7ddef6c8b 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 './reactivity' import type { Atom, ReadonlyAtom } from '@tanstack/vue-store' import type { NoInfer, @@ -106,8 +104,6 @@ export function useTable< selector: (state: TableState) => TSelected = () => ({}) as TSelected, ): VueTable { - const notifier = ref(0) - const syncTableOptions = ( table: Table, options: TableOptionsWithReactiveData, @@ -118,16 +114,11 @@ export function useTable< ) } - const vueReactivityFeature = constructReactivityFeature({ - stateNotifier: () => notifier.value, - optionsNotifier: () => notifier.value, - }) - const mergedOptions = { ...tableOptions, _features: { + coreReativityFeature: vueReactivity(), ...tableOptions._features, - vueReactivityFeature, }, } @@ -152,15 +143,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..49698eb2ae --- /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/reactivity' + +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: {}