diff --git a/examples/angular/row-selection/src/app/app.html b/examples/angular/row-selection/src/app/app.html index a9b5f0461e..35f6a1104b 100644 --- a/examples/angular/row-selection/src/app/app.html +++ b/examples/angular/row-selection/src/app/app.html @@ -1,4 +1,8 @@
+ +
diff --git a/examples/angular/row-selection/src/app/app.ts b/examples/angular/row-selection/src/app/app.ts index 756ab56f20..727c8081ee 100644 --- a/examples/angular/row-selection/src/app/app.ts +++ b/examples/angular/row-selection/src/app/app.ts @@ -32,6 +32,7 @@ export class App { private readonly rowSelection = signal({}) readonly globalFilter = signal('') readonly data = signal(makeData(10_000)) + readonly enableRowSelection = signal(true) readonly columns = columnHelper.columns([ columnHelper.display({ @@ -93,7 +94,8 @@ export class App { state: { rowSelection: this.rowSelection(), }, - enableRowSelection: true, // enable row selection for all rows + + enableRowSelection: this.enableRowSelection(), // enable row selection for all rows // enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row onRowSelectionChange: (updaterOrValue) => { this.rowSelection.set( @@ -136,4 +138,8 @@ export class App { refreshData(): void { this.data.set(makeData(10_000)) } + + toggleEnableRowSelection() { + this.enableRowSelection.update((value) => !value) + } } diff --git a/examples/angular/row-selection/src/app/selection-column/selection-column.ts b/examples/angular/row-selection/src/app/selection-column/selection-column.ts index 5be2238e13..a063cbef41 100644 --- a/examples/angular/row-selection/src/app/selection-column/selection-column.ts +++ b/examples/angular/row-selection/src/app/selection-column/selection-column.ts @@ -29,6 +29,7 @@ export class TableHeaderSelection { `, diff --git a/examples/solid/row-selection/package.json b/examples/solid/row-selection/package.json index 8fdf7ab281..8ad3256001 100644 --- a/examples/solid/row-selection/package.json +++ b/examples/solid/row-selection/package.json @@ -17,9 +17,9 @@ "vite-plugin-solid": "^2.11.10" }, "dependencies": { - "@tanstack/solid-table": "^9.0.0-alpha.10", "@tanstack/solid-devtools": "^0.7.26", - "@tanstack/solid-table-devtools": "9.0.0-alpha.11", + "@tanstack/solid-table": "^9.0.0-alpha.10", + "@tanstack/solid-table-devtools": "workspace:*", "solid-js": "^1.9.11" } } diff --git a/examples/solid/row-selection/src/App.tsx b/examples/solid/row-selection/src/App.tsx index 7e22049531..93efd2841a 100644 --- a/examples/solid/row-selection/src/App.tsx +++ b/examples/solid/row-selection/src/App.tsx @@ -33,6 +33,7 @@ export const _features = tableFeatures({ function App() { const [data, setData] = createSignal(makeData(1_000)) const refreshData = () => setData(makeData(100_000)) // stress test + const [enableRowSelection, setEnableRowSelection] = createSignal(true) // Create table first with a placeholder for columns let table: SolidTable @@ -117,8 +118,6 @@ function App() { }, ] - const [enableRowSelection, setEnableRowSelection] = createSignal(true) - table = createTable({ _features, _rowModels: { @@ -136,8 +135,6 @@ function App() { debugTable: true, }) - window.setEnable = setEnableRowSelection - return ( // ({ @@ -336,6 +333,12 @@ function App() { > Log table.getSelectedRowModel().flatRows +
diff --git a/examples/vue/row-selection/src/App.vue b/examples/vue/row-selection/src/App.vue index 58501f58c8..791e73ec67 100644 --- a/examples/vue/row-selection/src/App.vue +++ b/examples/vue/row-selection/src/App.vue @@ -88,18 +88,26 @@ const columns = columnHelper.columns([ ]) const data = ref(makeData(10)) +const enableRowSelection = ref(true) const rerender = () => { data.value = makeData(10) } +const toggleRowSelection = () => { + enableRowSelection.value = !enableRowSelection.value +} + const table = useTable( { _features, _rowModels: {}, data, columns, - enableRowSelection: true, //enable row selection for all rows + // enable row selection for all rows + get enableRowSelection() { + return enableRowSelection.value + }, // enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row }, (state) => ({ rowSelection: state.rowSelection }), @@ -152,6 +160,9 @@ const table = useTable(
+
diff --git a/knip.json b/knip.json index d3668b30bf..4c38f1ee81 100644 --- a/knip.json +++ b/knip.json @@ -1,9 +1,12 @@ { "$schema": "https://unpkg.com/knip@5/schema.json", "ignoreDependencies": ["@faker-js/faker"], - "ignoreWorkspaces": ["examples/**", "packages/table-core/tests/**"], + "ignoreWorkspaces": ["examples/**"], "ignore": ["**/*benchmark*", "**/benchmarks/**"], "workspaces": { + "packages/table-core": { + "ignore": ["**/tests/**"] + }, "packages/match-sorter-utils": { "ignoreDependencies": ["remove-accents"] }, diff --git a/packages/angular-table/src/injectTable.ts b/packages/angular-table/src/injectTable.ts index fec962fad1..0f9b53a4c1 100644 --- a/packages/angular-table/src/injectTable.ts +++ b/packages/angular-table/src/injectTable.ts @@ -2,8 +2,8 @@ import { Injector, assertInInjectionContext, computed, + effect, inject, - isSignal, signal, untracked, } from '@angular/core' @@ -31,6 +31,10 @@ export type AngularTable< * The selected state from the table store, based on the selector provided. */ readonly state: Signal> + /** + * A signal that returns the entire table instance. Will update on table/options change. + */ + readonly value: Signal> /** * Subscribe to changes in the table store with a custom selector. */ @@ -107,18 +111,18 @@ export function injectTable< ): AngularTable { assertInInjectionContext(injectTable) const injector = inject(Injector) - - const angularReactivityFeature = constructReactivityFeature({ - createSignal: (value) => { - return signal(value) as any - }, - createMemo: (fn) => { - return computed(() => fn()) - }, - isSignal: (value) => isSignal(value), - }) + const count = 0 return lazyInit(() => { + const stateNotifier = signal(0) + + const angularReactivityFeature = constructReactivityFeature({ + // optionsNotifier: () => stateNotifier(), + stateNotifier: () => { + return stateNotifier() + }, + }) + const resolvedOptions: TableOptions = { ...options(), _features: { @@ -127,14 +131,17 @@ export function injectTable< }, } as TableOptions - const table: AngularTable = constructTable( - resolvedOptions, - ) as AngularTable + const table = constructTable(resolvedOptions) as AngularTable< + TFeatures, + TData, + TSelected + > const updatedOptions = computed>(() => { const tableOptionsValue = options() + const currentOptions = table.latestOptions const result: TableOptions = { - ...table.options, + ...currentOptions, ...tableOptionsValue, _features: { ...tableOptionsValue._features, @@ -147,26 +154,34 @@ export function injectTable< return result }) - const tableState = injectStore( - table.store, - (state: TableState) => state, + effect( + () => { + const newOptions = updatedOptions() + untracked(() => table.setOptions(newOptions)) + }, { injector }, ) - const tableSignalNotifier = computed( + const tableState = injectStore(table.store, (state) => state, { injector }) + const tableOptions = injectStore(table.optionsStore, (state) => state, { + injector, + }) + + let firstRun = true + effect( () => { + tableOptions() tableState() - const newOptions = updatedOptions() - untracked(() => table.setOptions(newOptions)) - untracked(() => table.baseStore.setState((prev) => ({ ...prev }))) - return table + if (!firstRun) { + untracked(() => { + stateNotifier.update((n) => n + 1) + }) + } + firstRun = false }, - { equal: () => false }, + { injector }, ) - // @ts-ignore - table.setTableNotifier(tableSignalNotifier) - table.Subscribe = function Subscribe(props: { selector: (state: TableState) => TSubSelected equal?: ValueEqualityFn @@ -176,11 +191,17 @@ export function injectTable< equal: props.equal, }) } - Object.defineProperty(table, 'state', { value: injectStore(table.store, selector, { injector }), }) + Object.defineProperty(table, 'value', { + value: computed(() => { + stateNotifier() + return table + }), + }) + return table }) } diff --git a/packages/angular-table/tests/angularReactivityFeature.test.ts b/packages/angular-table/tests/angularReactivityFeature.test.ts index f2de587be1..d6626b7915 100644 --- a/packages/angular-table/tests/angularReactivityFeature.test.ts +++ b/packages/angular-table/tests/angularReactivityFeature.test.ts @@ -44,7 +44,7 @@ describe('angularReactivityFeature', () => { const table = createTestTable() const tablePropertyKeys = Object.keys(table) - describe('Table property reactivity', () => { + describe.skip('Table property reactivity', () => { test.each( tablePropertyKeys.map((property) => [ property, @@ -74,7 +74,7 @@ describe('angularReactivityFeature', () => { }) }) - describe('Header property reactivity', () => { + describe.skip('Header property reactivity', () => { const headers = table.getHeaderGroups() headers.forEach((headerGroup, index) => { const headerPropertyKeys = Object.keys(headerGroup) @@ -112,7 +112,7 @@ describe('angularReactivityFeature', () => { }) }) - describe('Column property reactivity', () => { + describe.skip('Column property reactivity', () => { const columns = table.getAllColumns() columns.forEach((column, index) => { const columnPropertyKeys = Object.keys(column).concat( @@ -133,7 +133,7 @@ describe('angularReactivityFeature', () => { }) }) - describe('Row and cells property reactivity', () => { + describe.skip('Row and cells property reactivity', () => { const flatRows = table.getRowModel().flatRows flatRows.forEach((row, index) => { const rowsPropertyKeys = Object.keys(row).concat( @@ -174,11 +174,12 @@ describe('angularReactivityFeature', () => { }) describe('Integration', () => { - test('methods works will be reactive effects', () => { + 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>() const cellGetValueCaptor = vi.fn<(val: unknown) => void>() + const cellGetValueMemoizedCaptor = vi.fn<(val: unknown) => void>() const columnIsVisibleCaptor = vi.fn<(val: boolean) => void>() // This will test a case where you put in the effect a single cell property method @@ -188,6 +189,8 @@ describe('angularReactivityFeature', () => { () => table.getRowModel().rows[0]!.getAllCells()[0]!, ) + const cellGetValue = computed(() => cell().getValue()) + TestBed.runInInjectionContext(() => { effect(() => { isSelectedRow1Captor(cell().row.getIsSelected()) @@ -195,6 +198,9 @@ describe('angularReactivityFeature', () => { effect(() => { cellGetValueCaptor(cell().getValue()) }) + effect(() => { + cellGetValueMemoizedCaptor(cellGetValue()) + }) effect(() => { columnIsVisibleCaptor(cell().column.getIsVisible()) }) @@ -202,6 +208,7 @@ describe('angularReactivityFeature', () => { TestBed.tick() expect(isSelectedRow1Captor).toHaveBeenCalledTimes(1) + expect(cellGetValueMemoizedCaptor).toHaveBeenCalledTimes(1) expect(cellGetValueCaptor).toHaveBeenCalledTimes(1) expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(1) @@ -209,7 +216,7 @@ describe('angularReactivityFeature', () => { TestBed.tick() expect(isSelectedRow1Captor).toHaveBeenCalledTimes(2) expect(cellGetValueCaptor).toHaveBeenCalledTimes(1) - expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(1) + expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(2) data.set([{ id: '1', title: 'Title 3' }]) TestBed.tick() @@ -217,17 +224,23 @@ describe('angularReactivityFeature', () => { // the cell instance will be recreated expect(isSelectedRow1Captor).toHaveBeenCalledTimes(3) expect(cellGetValueCaptor).toHaveBeenCalledTimes(2) - expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(2) + expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(3) cell().column.toggleVisibility(false) TestBed.tick() - expect(isSelectedRow1Captor).toHaveBeenCalledTimes(3) + expect(isSelectedRow1Captor).toHaveBeenCalledTimes(4) expect(cellGetValueCaptor).toHaveBeenCalledTimes(2) - expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(3) + expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(4) - expect(isSelectedRow1Captor.mock.calls).toEqual([[false], [true], [true]]) + expect(isSelectedRow1Captor.mock.calls).toEqual([ + [false], + [true], + [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/flex-render/flex-render-table.test.ts b/packages/angular-table/tests/flex-render/flex-render-table.test.ts index 4781734d6c..3312226c78 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 @@ -319,7 +319,7 @@ describe('FlexRenderDirective', () => { callExpandRender() return flexRenderComponent(ExpandCell, { bindings: [ - inputBinding('expanded', row.getIsExpanded), + inputBinding('expanded', () => row.getIsExpanded()), outputBinding('toggleExpand', () => row.toggleExpanded()), ], }) diff --git a/packages/angular-table/tests/injectTable.test.ts b/packages/angular-table/tests/injectTable.test.ts index e2700dcb8a..bd6e4d17e0 100644 --- a/packages/angular-table/tests/injectTable.test.ts +++ b/packages/angular-table/tests/injectTable.test.ts @@ -75,7 +75,7 @@ describe('injectTable', () => { }) test('supports "Object.keys"', () => { - const keys = Object.keys(table.value()).concat('state') + const keys = Object.keys(table.value()).concat('state', 'value') expect(Object.keys(table)).toEqual(keys) }) diff --git a/packages/react-table/src/useTable.ts b/packages/react-table/src/useTable.ts index 60d0685b0b..4f1deb83a0 100644 --- a/packages/react-table/src/useTable.ts +++ b/packages/react-table/src/useTable.ts @@ -2,7 +2,7 @@ import { useEffect, useLayoutEffect, useMemo, useState } from 'react' import { constructTable } from '@tanstack/table-core' -import { useStore } from '@tanstack/react-store' +import { shallow, useStore } from '@tanstack/react-store' import { FlexRender } from './FlexRender' import { Subscribe } from './Subscribe' import type { @@ -103,26 +103,15 @@ export function useTable< return tableInstance }) - // sync table options on every render - table.setOptions((prev) => ({ - ...prev, - ...tableOptions, - })) + useEffect(() => { + // sync table options on every render + table.setOptions((prev) => ({ + ...prev, + ...tableOptions, + })) + }, [table, tableOptions]) - useIsomorphicLayoutEffect(() => { - // prevent race condition between table.setOptions and table.baseStore.setState - queueMicrotask(() => { - table.baseStore.setState((prev) => ({ - ...prev, - })) - }) - }, [ - table.options.columns, // re-render when columns change - table.options.data, // re-render when data changes - table.options.state, // sync react state to the table store - ]) - - const state = useStore(table.store, selector) + const state = useStore(table.store, selector, shallow) return useMemo( () => ({ diff --git a/packages/solid-table-devtools/package.json b/packages/solid-table-devtools/package.json index eea909e0ab..b8bfaf0765 100644 --- a/packages/solid-table-devtools/package.json +++ b/packages/solid-table-devtools/package.json @@ -27,22 +27,22 @@ "test:lib:dev": "pnpm test:lib --watch", "test:types": "tsc", "test:build": "publint --strict", - "build": "tsc -p tsconfig.build.json" + "build": "vite build" }, "type": "module", - "types": "dist/index.d.ts", - "module": "dist/index.js", + "types": "dist/esm/index.d.ts", + "module": "dist/esm/index.js", "exports": { ".": { "import": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" } }, "./production": { "import": { - "types": "./dist/production.d.ts", - "default": "./dist/production.js" + "types": "./dist/esm/production.d.ts", + "default": "./dist/esm/production.js" } }, "./package.json": "./package.json" @@ -60,8 +60,10 @@ "@tanstack/table-core": "workspace:*", "@tanstack/table-devtools": "workspace:*" }, + "peerDependencies": { + "solid-js": "^1.9.11" + }, "devDependencies": { - "solid-js": "^1.9.11", "vite-plugin-solid": "^2.11.10" } } diff --git a/packages/solid-table-devtools/src/SolidTableDevtools.tsx b/packages/solid-table-devtools/src/TableDevtools.tsx similarity index 100% rename from packages/solid-table-devtools/src/SolidTableDevtools.tsx rename to packages/solid-table-devtools/src/TableDevtools.tsx index 2add8e7819..29c1a28712 100644 --- a/packages/solid-table-devtools/src/SolidTableDevtools.tsx +++ b/packages/solid-table-devtools/src/TableDevtools.tsx @@ -1,9 +1,9 @@ -import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/solid' import { createSolidPanel } from '@tanstack/devtools-utils/solid' import { - setTableDevtoolsTarget, TableDevtoolsCore, + setTableDevtoolsTarget, } from '@tanstack/table-devtools' +import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/solid' import type { RowData, Table, TableFeatures } from '@tanstack/table-core' diff --git a/packages/solid-table-devtools/src/index.ts b/packages/solid-table-devtools/src/index.ts index c34bc8c874..70cdd3bcf1 100644 --- a/packages/solid-table-devtools/src/index.ts +++ b/packages/solid-table-devtools/src/index.ts @@ -1,17 +1,14 @@ -'use client' - -import * as Devtools from './SolidTableDevtools' +import { isDev } from 'solid-js/web' import * as plugin from './plugin' +import * as Devtools from './TableDevtools' -export const TableDevtoolsPanel = - process.env.NODE_ENV !== 'development' - ? Devtools.TableDevtoolsPanelNoOp - : Devtools.TableDevtoolsPanel +export const TableDevtoolsPanel = !isDev + ? Devtools.TableDevtoolsPanelNoOp + : Devtools.TableDevtoolsPanel -export const tableDevtoolsPlugin = - process.env.NODE_ENV !== 'development' - ? plugin.tableDevtoolsNoOpPlugin - : plugin.tableDevtoolsPlugin +export const tableDevtoolsPlugin = !isDev + ? plugin.tableDevtoolsNoOpPlugin + : plugin.tableDevtoolsPlugin -export type { TableDevtoolsSolidInit } from './SolidTableDevtools' +export type { TableDevtoolsSolidInit } from './TableDevtools' export type { TableDevtoolsPluginOptions } from './plugin' diff --git a/packages/solid-table-devtools/src/plugin.tsx b/packages/solid-table-devtools/src/plugin.tsx index dfc00d2760..07561b9b35 100644 --- a/packages/solid-table-devtools/src/plugin.tsx +++ b/packages/solid-table-devtools/src/plugin.tsx @@ -1,5 +1,5 @@ -import React from 'react' -import { TableDevtoolsPanel } from './SolidTableDevtools' +import { createSolidPlugin } from '@tanstack/devtools-utils/solid' +import { TableDevtoolsPanel } from './TableDevtools' import type { RowData, Table, TableFeatures } from '@tanstack/table-core' export interface TableDevtoolsPluginOptions< diff --git a/packages/solid-table-devtools/src/production.ts b/packages/solid-table-devtools/src/production.ts index b197295f30..f683bce6b7 100644 --- a/packages/solid-table-devtools/src/production.ts +++ b/packages/solid-table-devtools/src/production.ts @@ -1,6 +1,5 @@ -'use client' +export { TableDevtoolsPanel } from './TableDevtools' -export { TableDevtoolsPanel } from './SolidTableDevtools' -export type { TableDevtoolsSolidInit } from './SolidTableDevtools' -export { tableDevtoolsPlugin } from './plugin' -export type { TableDevtoolsPluginOptions } from './plugin' +export type { TableDevtoolsSolidInit } from './production/TableDevtools' + +export { tableDevtoolsPlugin } from './production/plugin' diff --git a/packages/solid-table-devtools/src/production/TableDevtools.tsx b/packages/solid-table-devtools/src/production/TableDevtools.tsx new file mode 100644 index 0000000000..f0a5ef6f4c --- /dev/null +++ b/packages/solid-table-devtools/src/production/TableDevtools.tsx @@ -0,0 +1,10 @@ +import { createSolidPanel } from '@tanstack/devtools-utils/solid' +import { TableDevtoolsCore } from '@tanstack/table-devtools/production' + +import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/solid' + +const [TableDevtoolsPanel] = createSolidPanel(TableDevtoolsCore) + +export interface TableDevtoolsSolidInit extends DevtoolsPanelProps {} + +export { TableDevtoolsPanel } diff --git a/packages/solid-table-devtools/src/production/plugin.tsx b/packages/solid-table-devtools/src/production/plugin.tsx new file mode 100644 index 0000000000..403eedabc3 --- /dev/null +++ b/packages/solid-table-devtools/src/production/plugin.tsx @@ -0,0 +1,9 @@ +import { createSolidPlugin } from '@tanstack/devtools-utils/solid' +import { TableDevtoolsPanel } from './TableDevtools' + +const [tableDevtoolsPlugin] = createSolidPlugin({ + name: 'TanStack Table', + Component: TableDevtoolsPanel, +}) + +export { tableDevtoolsPlugin } diff --git a/packages/solid-table-devtools/vite.config.ts b/packages/solid-table-devtools/vite.config.ts index a872ffe359..896c24e885 100644 --- a/packages/solid-table-devtools/vite.config.ts +++ b/packages/solid-table-devtools/vite.config.ts @@ -1,6 +1,24 @@ -import { defineConfig } from 'vitest/config' +import { defineConfig, mergeConfig } from 'vitest/config' +import { tanstackViteConfig } from '@tanstack/vite-config' import solid from 'vite-plugin-solid' +import packageJson from './package.json' -export default defineConfig({ +const config = defineConfig({ plugins: [solid()], + test: { + name: packageJson.name, + dir: './', + watch: false, + environment: 'jsdom', + globals: true, + }, }) + +export default mergeConfig( + config, + tanstackViteConfig({ + entry: ['./src/index.ts', './src/production.ts'], + srcDir: './src', + cjs: false, + }), +) diff --git a/packages/solid-table/src/createTable.ts b/packages/solid-table/src/createTable.ts index 13f68366be..b4a9658357 100644 --- a/packages/solid-table/src/createTable.ts +++ b/packages/solid-table/src/createTable.ts @@ -3,14 +3,7 @@ import { constructTable, } from '@tanstack/table-core' import { useStore } from '@tanstack/solid-store' -import { - createComputed, - createMemo, - createSignal, - getOwner, - mergeProps, - runWithOwner, -} from 'solid-js' +import { createComputed, createSignal, mergeProps } from 'solid-js' import type { Accessor, JSX } from 'solid-js' import type { NoInfer, @@ -64,25 +57,14 @@ export function createTable< selector: (state: TableState) => TSelected = () => ({}) as TSelected, ): SolidTable { - const owner = getOwner() + const [notifier, setNotifier] = createSignal(void 0, { equals: false }) + const [optionsNotifier, setOptionsNotifier] = createSignal(void 0, { + equals: false, + }) const solidReactivityFeature = constructReactivityFeature({ - createSignal: (value) => { - const signal = createSignal(value) - function interoperableSignal() { - return signal[0]() - } - return Object.assign(interoperableSignal, { - set: (value: any) => signal[1](() => value), - }) - }, - createMemo: (fn) => { - if (owner) { - return runWithOwner(owner, () => createMemo(fn))! - } - return createMemo(fn) - }, - isSignal: (value) => typeof value === 'function', + stateNotifier: () => notifier(), + optionsNotifier: () => optionsNotifier(), }) const mergedOptions = mergeProps(tableOptions, { @@ -91,8 +73,6 @@ export function createTable< }), }) as any - const [renderVersion, setRenderVersion] = createSignal(0) - const resolvedOptions = mergeProps( { mergeOptions: ( @@ -111,17 +91,8 @@ export function createTable< TSelected > - // @ts-ignore - table.setTableNotifier(() => { - renderVersion() - return table - }) - - /** - * Temp force reactivity to all state changes on every table.get* method - */ const allState = useStore(table.store, (state) => state) - const allOptions = useStore(table.baseOptionsStore, (options) => options) + const allOptions = useStore(table.optionsStore, (options) => options) createComputed(() => { table.setOptions((prev) => { @@ -130,39 +101,11 @@ export function createTable< }) createComputed(() => { - // Access storeState to create reactive dependency allState() allOptions() - // Increment version to invalidate cached get* methods - setRenderVersion((v) => v + 1) - // Update options when store changes - // table.setOptions((prev) => { - // return mergeProps(prev, tableOptions) as TableOptions - // }) + setNotifier(void 0) }) - // Object.assign(table, { - // get options() { - // allOptions() - // return table.baseOptionsStore.get() - // }, - // }) - // - // Object.defineProperty(table.store, 'get', { - // value: () => { - // allState() - // allOptions() - // return table.store['atom'].get() - // }, - // }) - // Object.defineProperty(table.store, 'state', { - // get() { - // allState() - // allOptions() - // return this['atom'].get() - // }, - // }) - table.Subscribe = function Subscribe(props: { selector: (state: TableState) => TSelected children: ((state: Accessor) => JSX.Element) | JSX.Element diff --git a/packages/table-core/src/core/table/constructTable.ts b/packages/table-core/src/core/table/constructTable.ts index 0b94cc6f85..35f441c451 100644 --- a/packages/table-core/src/core/table/constructTable.ts +++ b/packages/table-core/src/core/table/constructTable.ts @@ -42,16 +42,20 @@ export function constructTable< return Object.assign(obj, feature.getDefaultTableOptions?.(table)) }, {}) as TableOptions - table.baseOptionsStore = createStore({ + table.latestOptions = { ...defaultOptions, ...tableOptions, - }) - + } + table.optionsStore = createStore(table.latestOptions) Object.defineProperty(table, 'options', { enumerable: true, configurable: true, get() { - return table.baseOptionsStore.state + return table.optionsStore.state + }, + set(value) { + table.latestOptions = value + table.optionsStore.setState(() => value) }, }) @@ -66,6 +70,8 @@ export function constructTable< const state = table.baseStore.state return { ...state, + // Merge state with non-reactive options. + // NOTE: Adapters should call `table.baseStore.setState` to trigger state updates ...(table.options.state ?? {}), } }) diff --git a/packages/table-core/src/core/table/coreTablesFeature.types.ts b/packages/table-core/src/core/table/coreTablesFeature.types.ts index ccb1224ec1..5b692c740f 100644 --- a/packages/table-core/src/core/table/coreTablesFeature.types.ts +++ b/packages/table-core/src/core/table/coreTablesFeature.types.ts @@ -95,13 +95,17 @@ export interface Table_CoreProperties< */ baseStore: Store> /** - * The base store for the table. This can be used to write to the table state. + * The base store for the table options. */ - baseOptionsStore: Store> + optionsStore: Store> /** * This is the resolved initial state of the table. */ initialState: TableState + /** + * The latest table options value (non reactive). + */ + latestOptions: TableOptions /** * A read-only reference to the table's current options. */ diff --git a/packages/table-core/src/core/table/coreTablesFeature.utils.ts b/packages/table-core/src/core/table/coreTablesFeature.utils.ts index 436a5f1e88..d897137513 100644 --- a/packages/table-core/src/core/table/coreTablesFeature.utils.ts +++ b/packages/table-core/src/core/table/coreTablesFeature.utils.ts @@ -18,12 +18,12 @@ export function table_mergeOptions< table: Table_Internal, newOptions: TableOptions, ) { - if (table.options.mergeOptions) { - return table.options.mergeOptions(table.options, newOptions) + if (table.latestOptions.mergeOptions) { + return table.latestOptions.mergeOptions(table.latestOptions, newOptions) } return { - ...table.options, + ...table.latestOptions, ...newOptions, } } @@ -35,8 +35,8 @@ export function table_setOptions< table: Table_Internal, updater: Updater>, ): void { - table.baseOptionsStore.setState((options) => { - const newOptions = functionalUpdate(updater, options) - return table_mergeOptions(table, newOptions) - }) + const newOptions = functionalUpdate(updater, table.latestOptions) + const mergedOptions = table_mergeOptions(table, newOptions) + table.latestOptions = mergedOptions + table.optionsStore.setState(() => mergedOptions) } diff --git a/packages/table-core/src/features/table-reactivity/tableReactivityFeature.ts b/packages/table-core/src/features/table-reactivity/tableReactivityFeature.ts index 230affbf23..fc63235138 100644 --- a/packages/table-core/src/features/table-reactivity/tableReactivityFeature.ts +++ b/packages/table-core/src/features/table-reactivity/tableReactivityFeature.ts @@ -1,250 +1,47 @@ -import { setReactivePropertiesOnObject } from './tableReactivityFeature.utils' import type { TableFeature, TableFeatures } from '../../types/TableFeatures' -import type { Accessor } from './tableReactivityFeature.utils' import type { RowData } from '../../types/type-utils' -import type { Table } from '../../types/Table' +import type { ReadonlyStore, Store } from '@tanstack/store' -/** - * Predicate used to skip/ignore a property name when applying Angular reactivity. - * - * Returning `true` means the property should NOT be wrapped/made reactive. - */ -type SkipPropertyFn = (property: string) => boolean - -/** - * Fine-grained configuration for Angular reactivity. - * - * Each key controls whether prototype methods/getters on the corresponding TanStack Table - * objects are wrapped with signal-aware access. - * - * - `true` enables wrapping using the default skip rules. - * - `false` disables wrapping entirely for that object type. - * - a function allows customizing the skip rules (see {@link SkipPropertyFn}). - * - * @example - * ```ts - * const table = injectTable(() => { - * // ...table options, - * reactivity: { - * // fine-grained control over which table objects have reactive properties, - * // and which properties are wrapped - * header: true, - * column: true, - * row: true, - * cell: true, - * } - * }) - * ``` - */ -export interface AngularReactivityFlags { - /** Controls reactive wrapping for `Header` instances. */ - header: boolean | SkipPropertyFn - /** Controls reactive wrapping for `Column` instances. */ - column: boolean | SkipPropertyFn - /** Controls reactive wrapping for `Row` instances. */ - row: boolean | SkipPropertyFn - /** Controls reactive wrapping for `Cell` instances. */ - cell: boolean | SkipPropertyFn -} - -/** - * Table option extension for Angular reactivity. - * - * Available on `createTable` options via module augmentation in this file. - */ -interface TableOptions_Reactivity { - /** - * Enables/disables and configures Angular reactivity on table-related prototypes. - * - * If omitted, defaults are provided by the feature. - */ - reactivity?: Partial -} - -/** - * Table API extension for Angular reactivity. - * - * Added to the table instance via module augmentation. - */ -interface Table_Reactivity< - TFeatures extends TableFeatures, - TData extends RowData, -> { - /** - * Returns a table signal that updates whenever the table state or options changes. - */ - value: Accessor> - /** - * Sets the reactive notifier that powers {@link get}. - * - * @internal Used by the Angular table adapter to connect its notifier to the core table. - */ - setTableNotifier: (signal: Accessor>) => void -} - -/** - * Type map describing what this feature adds to TanStack Table constructors. - */ interface TableReactivityFeatureConstructors< TFeatures extends TableFeatures, TData extends RowData, -> { - TableOptions: TableOptions_Reactivity - Table: Table_Reactivity -} - -/** - * Resolves the user-provided `reactivity.*` config to a skip predicate. - * - * - `false` is handled by callers (feature method returns early) - * - `true` selects the default predicate - * - a function overrides the default predicate - */ -const getUserSkipPropertyFn = ( - value: undefined | null | boolean | SkipPropertyFn, - defaultPropertyFn: SkipPropertyFn, -) => { - if (typeof value === 'boolean') { - return defaultPropertyFn - } - - return value ?? defaultPropertyFn -} - -export type InteroperableWritableSignal = { - (): T - set: (value: unknown) => void -} - -export interface ReactivityFeatureFactoryOptions { - createSignal: (value: T) => InteroperableWritableSignal - createMemo: (accessor: Accessor) => Accessor - isSignal: (v: unknown) => boolean -} +> {} export function constructReactivityFeature< TFeatures extends TableFeatures, TData extends RowData, ->( - factory: ReactivityFeatureFactoryOptions, -): TableFeature> { - const { createSignal, createMemo } = factory - +>(bindings: { + stateNotifier?: () => unknown + optionsNotifier?: () => unknown +}): TableFeature> { return { - getDefaultTableOptions(table) { - return { - reactivity: { - header: true, - column: true, - row: true, - cell: true, - }, - } - }, constructTableAPIs: (table) => { - const rootNotifier = createSignal - > | null>(null) - - table.setTableNotifier = (notifier) => { - rootNotifier.set(notifier) - } - - table.value = () => { - const notifier = rootNotifier() - void notifier?.() - return table as any - } - - setReactivePropertiesOnObject(table.value, table, { - overridePrototype: false, - skipProperty: skipBaseProperties, - factory, - }) - }, - - assignCellPrototype: (prototype, table) => { - // @ts-expect-error Internal - if (table.options.reactivity?.cell === false) { - return - } - // @ts-expect-error Internal - setReactivePropertiesOnObject(table.value, prototype, { - skipProperty: getUserSkipPropertyFn( - // @ts-expect-error Internal - table.options.reactivity?.cell, - skipBaseProperties, - ), - overridePrototype: true, - factory, - }) - }, - - assignColumnPrototype: (prototype, table) => { - // @ts-expect-error Internal - if (table.options.reactivity?.column === false) { - return - } - // @ts-expect-error Internal - setReactivePropertiesOnObject(table.value, prototype, { - skipProperty: getUserSkipPropertyFn( - // @ts-expect-error Internal - table.options.reactivity?.cell, - skipBaseProperties, - ), - overridePrototype: true, - factory, - }) - }, - - assignHeaderPrototype: (prototype, table) => { - // @ts-expect-error Internal - if (table.options.reactivity?.header === false) { - return - } - // @ts-expect-error Internal - setReactivePropertiesOnObject(table.value, prototype, { - skipProperty: getUserSkipPropertyFn( - // @ts-expect-error Internal - table.options.reactivity?.cell, - skipBaseProperties, - ), - overridePrototype: true, - factory, - }) - }, - - assignRowPrototype: (prototype, table) => { - // @ts-expect-error Internal - if (table.options.reactivity?.row === false) { - return - } - // @ts-expect-error Internal - setReactivePropertiesOnObject(table.value, prototype, { - skipProperty: getUserSkipPropertyFn( - // @ts-expect-error Internal - table.options.reactivity?.cell, - skipBaseProperties, - ), - overridePrototype: true, - factory, - }) + table.store = bindStore(table.store, bindings.stateNotifier) + table.optionsStore = bindStore( + table.optionsStore, + bindings.optionsNotifier, + ) }, } } -/** - * Default predicate used to skip base/non-reactive properties. - */ -function skipBaseProperties(property: string): boolean { - return ( - // equals `getContext` - property === 'getContext' || - // start with `_` - property[0] === '_' || - // doesn't start with `get`, but faster - !(property[0] === 'g' && property[1] === 'e' && property[2] === 't') || - // ends with `Handler` - property.endsWith('Handler') - ) +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 } diff --git a/packages/table-core/src/features/table-reactivity/tableReactivityFeature.utils.ts b/packages/table-core/src/features/table-reactivity/tableReactivityFeature.utils.ts deleted file mode 100644 index 43e1efbb20..0000000000 --- a/packages/table-core/src/features/table-reactivity/tableReactivityFeature.utils.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { $internalMemoFnMeta, getMemoFnMeta } from '../../utils' -import type { MemoFnMeta } from '../../utils' -import type { ReactivityFeatureFactoryOptions } from './tableReactivityFeature' - -const $TABLE_REACTIVE = Symbol('reactive') - -function markReactive(obj: T): void { - Object.defineProperty(obj, $TABLE_REACTIVE, { value: true }) -} - -function isReactive(obj: T): boolean { - return Reflect.get(obj as {}, $TABLE_REACTIVE) === true -} - -export type Accessor = () => T - -/** - * Defines a lazy computed property on an object. The property is initialized - * with a getter that computes its value only when accessed for the first time. - * After the first access, the computed value is cached, and the getter is - * replaced with a direct property assignment for efficiency. - * - * @internal should be used only internally - */ -export function defineLazyComputedProperty( - notifier: Accessor, - setObjectOptions: { - originalObject: T - property: keyof T & string - valueFn: (...args: any) => any - overridePrototype?: boolean - factory: ReactivityFeatureFactoryOptions - }, -) { - const { originalObject, property, overridePrototype, valueFn, factory } = - setObjectOptions - - if (overridePrototype) { - assignReactivePrototypeAPI(notifier, originalObject, property, factory) - } else { - Object.defineProperty(originalObject, property, { - enumerable: true, - configurable: true, - get() { - const computedValue = toComputed(notifier, valueFn, property, factory) - markReactive(computedValue) - // Once the property is set the first time, we don't need a getter anymore - // since we have a computed / cached fn value - Object.defineProperty(originalObject, property, { - value: computedValue, - configurable: true, - enumerable: true, - }) - return computedValue - }, - }) - } -} - -/** - * @internal should be used only internally - */ -type ComputedFunction = T extends () => infer TReturn - ? Accessor - : // 1+ args - T extends (arg0?: any, ...args: Array) => any - ? T - : never - -/** - * @description Transform a function into a computed that react to given notifier re-computations - * - * Here we'll handle all type of accessors: - * - 0 argument -> e.g. table.getCanNextPage()) - * - 0~1 arguments -> e.g. table.getIsSomeRowsPinned(position?) - * - 1 required argument -> e.g. table.getColumn(columnId) - * - 1+ argument -> e.g. table.getRow(id, searchAll?) - * - * Since we are not able to detect automatically the accessors parameters, - * we'll wrap all accessors into a cached function wrapping a computed - * that return it's value based on the given parameters - * - * @internal should be used only internally - */ -export function toComputed< - T, - TReturn, - TFunction extends (...args: any) => TReturn, ->( - notifier: Accessor, - fn: TFunction, - debugName: string, - factory: ReactivityFeatureFactoryOptions, -): ComputedFunction { - const { createMemo } = factory - const hasArgs = getFnArgsLength(fn) > 0 - if (!hasArgs) { - const computedFn = createMemo(() => { - void notifier() - return fn() - }) - Object.defineProperty(computedFn, 'name', { value: debugName }) - markReactive(computedFn) - return computedFn as ComputedFunction - } - - const computedFn: ((this: unknown, ...argsArray: Array) => unknown) & { - _reactiveCache?: Record> - } = function (this: unknown, ...argsArray: Array) { - const cacheable = - argsArray.length === 0 || - argsArray.every((arg) => { - return ( - arg === null || - arg === undefined || - typeof arg === 'string' || - typeof arg === 'number' || - typeof arg === 'boolean' || - typeof arg === 'symbol' - ) - }) - if (!cacheable) { - return fn.apply(this, argsArray) - } - const serializedArgs = serializeArgs(...argsArray) - if ((computedFn._reactiveCache ??= {})[serializedArgs]) { - return computedFn._reactiveCache[serializedArgs]() - } - const computedSignal = createMemo(() => { - void notifier() - return fn.apply(this, argsArray) - }) - - computedFn._reactiveCache[serializedArgs] = computedSignal - - return computedSignal() - } - - Object.defineProperty(computedFn, 'name', { value: debugName }) - markReactive(computedFn) - - return computedFn as ComputedFunction -} - -function serializeArgs(...args: Array) { - return JSON.stringify(args) -} - -function getFnArgsLength( - fn: ((...args: any) => any) & { originalArgsLength?: number }, -): number { - return Math.max(0, getMemoFnMeta(fn)?.originalArgsLength ?? fn.length) -} - -function assignReactivePrototypeAPI( - notifier: Accessor, - prototype: Record, - fnName: string, - factory: ReactivityFeatureFactoryOptions, -) { - if (isReactive(prototype[fnName])) return - - const { createMemo } = factory - - const fn = prototype[fnName] - const originalArgsLength = getFnArgsLength(fn) - - if (originalArgsLength <= 1) { - Object.defineProperty(prototype, fnName, { - enumerable: true, - configurable: true, - get(this) { - const self = this - // Create a cache in the current prototype to allow the signals - // to be garbage collected. Shorthand for a WeakMap implementation - self._reactiveCache ??= {} - const cached = (self._reactiveCache[`${self.id}${fnName}`] ??= - createMemo(() => { - notifier() - return fn.apply(self) - })) - markReactive(cached) - cached[$internalMemoFnMeta] = { - originalArgsLength, - } satisfies MemoFnMeta - return cached - }, - }) - } else { - prototype[fnName] = function (this: unknown, ...args: Array) { - notifier() - return fn.apply(this, args) - } - markReactive(prototype[fnName]) - prototype[fnName][$internalMemoFnMeta] = { - originalArgsLength, - } satisfies MemoFnMeta - } -} - -export function setReactivePropertiesOnObject( - notifier: Accessor, - obj: { [key: string]: any }, - options: { - overridePrototype?: boolean - skipProperty: (property: string) => boolean - factory: ReactivityFeatureFactoryOptions - }, -) { - const { skipProperty, factory } = options - if (isReactive(obj)) { - return - } - markReactive(obj) - - for (const property in obj) { - const value = obj[property] - if (typeof value !== 'function' || skipProperty(property)) { - continue - } - defineLazyComputedProperty(notifier, { - valueFn: value, - property, - originalObject: obj, - overridePrototype: options.overridePrototype, - factory: factory, - }) - } -} diff --git a/packages/table-core/src/index.ts b/packages/table-core/src/index.ts index 099953ad94..656947d3c4 100755 --- a/packages/table-core/src/index.ts +++ b/packages/table-core/src/index.ts @@ -80,7 +80,6 @@ export * from './features/stockFeatures' // tableReactivityFeature export * from './features/table-reactivity/tableReactivityFeature' -export * from './features/table-reactivity/tableReactivityFeature.utils' // columnFacetingFeature export * from './features/column-faceting/columnFacetingFeature' diff --git a/packages/table-core/src/utils.ts b/packages/table-core/src/utils.ts index d8325b6f72..18877a09f3 100755 --- a/packages/table-core/src/utils.ts +++ b/packages/table-core/src/utils.ts @@ -16,7 +16,7 @@ export function makeStateUpdater< K extends (string & {}) | keyof TableState_All | keyof TableState, >(key: K, instance: Table) { return (updater: Updater[K & keyof TableState]>) => { - ;(instance as Table_Internal).baseStore.setState( + instance.baseStore.setState( (old: TTableState) => { return { ...old, diff --git a/packages/table-devtools/package.json b/packages/table-devtools/package.json index 764f5c2e59..083b7ead37 100644 --- a/packages/table-devtools/package.json +++ b/packages/table-devtools/package.json @@ -59,7 +59,6 @@ "@tanstack/devtools-ui": "^0.4.4", "@tanstack/devtools-utils": "^0.3.0", "@tanstack/solid-store": "^0.9.1", - "clsx": "^2.1.1", "goober": "^2.1.18", "solid-js": "^1.9.11" }, diff --git a/packages/table-devtools/src/TableContextProvider.tsx b/packages/table-devtools/src/TableContextProvider.tsx index e2d8ad5f21..8d043e2b9f 100644 --- a/packages/table-devtools/src/TableContextProvider.tsx +++ b/packages/table-devtools/src/TableContextProvider.tsx @@ -10,15 +10,10 @@ import { subscribeTableDevtoolsTarget, } from './tableTarget' import type { Accessor, ParentComponent, Setter } from 'solid-js' -import type { RowData, Table, TableFeatures } from '@tanstack/table-core' +import type { RowData, Table, stockFeatures } from '@tanstack/table-core' -export type TableDevtoolsTabId = - | 'features' - | 'state' - | 'options' - | 'rows' - | 'columns' -type AnyTable = Table +type TableDevtoolsTabId = 'features' | 'state' | 'options' | 'rows' | 'columns' +type AnyTable = Table interface TableDevtoolsContextValue { table: Accessor diff --git a/packages/table-devtools/src/components/FeaturesPanel.tsx b/packages/table-devtools/src/components/FeaturesPanel.tsx index 06a5fb4f30..42954aa92a 100644 --- a/packages/table-devtools/src/components/FeaturesPanel.tsx +++ b/packages/table-devtools/src/components/FeaturesPanel.tsx @@ -188,15 +188,9 @@ export function FeaturesPanel() { {(rowModelName) => { const fns = getRowModelFunctions(rowModelName) - const count = getRowCountForModel( - tableInstance as { [key: string]: unknown }, - rowModelName, - ) return (
-
- {rowModelName} ({count.toLocaleString()} rows) -
+
{rowModelName}
{(fnName) => (
{fnName}
diff --git a/packages/table-devtools/src/components/OptionsPanel.tsx b/packages/table-devtools/src/components/OptionsPanel.tsx index 011be836e8..8bd669db21 100644 --- a/packages/table-devtools/src/components/OptionsPanel.tsx +++ b/packages/table-devtools/src/components/OptionsPanel.tsx @@ -11,7 +11,7 @@ export function OptionsPanel() { const tableInstance = table() const tableState = tableInstance ? useStore( - tableInstance.baseOptionsStore, + tableInstance.optionsStore, ({ state, data, _features, _rowModels, ...options }) => options, ) : undefined diff --git a/packages/vue-table/src/createTableHelper.ts b/packages/vue-table/src/createTableHelper.ts index fad1c0bd77..57582886ef 100644 --- a/packages/vue-table/src/createTableHelper.ts +++ b/packages/vue-table/src/createTableHelper.ts @@ -12,7 +12,7 @@ import type { export type TableHelper< TFeatures extends TableFeatures, TData extends RowData = any, -> = Omit, 'tableCreator'> & { +> = Omit, 'tableCreator'> & { useTable: ( tableOptions: Omit< TableOptionsWithReactiveData, @@ -26,9 +26,9 @@ export function createTableHelper< TFeatures extends TableFeatures, TData extends RowData, >( - tableHelperOptions: TableHelperOptions, + tableHelperOptions: TableHelperOptions, ): TableHelper { - const tableHelper = constructTableHelper(useTable, tableHelperOptions) + const tableHelper = constructTableHelper(useTable as any, tableHelperOptions) return { ...tableHelper, useTable: ( diff --git a/packages/vue-table/src/useTable.ts b/packages/vue-table/src/useTable.ts index 27d6dc7fc2..8d6ef2bfdf 100644 --- a/packages/vue-table/src/useTable.ts +++ b/packages/vue-table/src/useTable.ts @@ -1,5 +1,8 @@ -import { isRef, unref, watch } from 'vue' -import { constructTable } from '@tanstack/table-core' +import { isRef, ref, unref, watch, watchEffect } from 'vue' +import { + constructReactivityFeature, + constructTable, +} from '@tanstack/table-core' import { useStore } from '@tanstack/vue-store' import { mergeProxy } from './merge-proxy' import type { @@ -74,76 +77,76 @@ export function useTable< selector: (state: TableState) => TSelected = () => ({}) as TSelected, ): VueTable { + const notifier = ref(0) + + const vueReactivityFeature = constructReactivityFeature({ + stateNotifier: () => notifier.value, + optionsNotifier: () => notifier.value, + }) + const IS_REACTIVE = isRef(tableOptions.data) - const statefulOptions = mergeProxy( - IS_REACTIVE ? getOptionsWithReactiveData(tableOptions) : tableOptions, + const mergedOptions = { + ...tableOptions, + _features: { + ...tableOptions._features, + vueReactivityFeature, + }, + } + + const resolvedOptions = mergeProxy( + IS_REACTIVE + ? getOptionsWithReactiveData( + mergedOptions as TableOptions, + ) + : mergedOptions, { // Remove state and onStateChange - store handles it internally mergeOptions: ( defaultOptions: TableOptions, newOptions: Partial>, ) => { - return IS_REACTIVE - ? { - ...defaultOptions, - ...newOptions, - } - : mergeProxy(defaultOptions, newOptions) + return mergeProxy(defaultOptions, newOptions) }, }, ) as TableOptions - const table = constructTable(statefulOptions) as VueTable< + const table = constructTable(resolvedOptions) as VueTable< TFeatures, TData, TSelected > - function updateOptions() { - table.setOptions((prev) => { - return mergeProxy( - prev, - IS_REACTIVE ? getOptionsWithReactiveData(tableOptions) : tableOptions, - ) as TableOptions - }) - } - - updateOptions() - - // Add reactivity support for reactive data - if (IS_REACTIVE) { - watch( - () => tableOptions.data, - () => { - table.baseStore.setState((prev: TableState) => ({ - ...prev, - data: unref(tableOptions.data), - })) - }, - { immediate: true }, - ) - } - - /** - * Temp force reactivity to all state changes on every table.get* method - */ const allState = useStore(table.store, (state) => state) + const allOptions = useStore(table.optionsStore, (state) => state) - // Wrap all "get*" methods to make them reactive - // Access allState.value directly to create reactive dependency - Object.keys(table).forEach((key) => { - const value = (table as any)[key] - if (typeof value === 'function' && key.startsWith('get')) { - const originalMethod = value.bind(table) - ;(table as any)[key] = (...args: Array) => { - // Access state to create reactive dependency - allState.value - return originalMethod(...args) - } - } + watchEffect(() => { + allState.value + allOptions.value + notifier.value++ }) + watch( + () => + [ + IS_REACTIVE ? unref(tableOptions.data) : tableOptions.data, + tableOptions, + ] as const, + () => { + table.setOptions((prev) => { + return mergeProxy( + prev, + IS_REACTIVE + ? getOptionsWithReactiveData( + tableOptions as TableOptions, + ) + : tableOptions, + ) as TableOptions + }) + }, + { immediate: true }, + ) + table.Subscribe = function Subscribe(props: { selector: (state: TableState) => TSelected children: diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 768b85dbc1..93b751d2d3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2782,7 +2782,7 @@ importers: specifier: ^9.0.0-alpha.10 version: link:../../../packages/solid-table '@tanstack/solid-table-devtools': - specifier: 9.0.0-alpha.11 + specifier: workspace:* version: link:../../../packages/solid-table-devtools solid-js: specifier: ^1.9.11 @@ -3618,10 +3618,10 @@ importers: '@tanstack/table-devtools': specifier: workspace:* version: link:../table-devtools - devDependencies: solid-js: specifier: ^1.9.11 version: 1.9.11 + devDependencies: vite-plugin-solid: specifier: ^2.11.10 version: 2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2)) @@ -3671,9 +3671,6 @@ importers: '@tanstack/table-core': specifier: workspace:* version: link:../table-core - clsx: - specifier: ^2.1.1 - version: 2.1.1 goober: specifier: ^2.1.18 version: 2.1.18(csstype@3.2.3) @@ -15727,7 +15724,7 @@ snapshots: '@tanstack/react-router': 1.157.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4) vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2) vite-plugin-solid: 2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2)) - webpack: 5.104.1(esbuild@0.27.2) + webpack: 5.104.1 transitivePeerDependencies: - supports-color @@ -20585,6 +20582,16 @@ snapshots: optionalDependencies: esbuild: 0.27.2 + terser-webpack-plugin@5.3.16(webpack@5.104.1(esbuild@0.27.2)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + serialize-javascript: 6.0.2 + terser: 5.44.1 + webpack: 5.104.1 + optional: true + terser@5.44.1: dependencies: '@jridgewell/source-map': 0.3.6 @@ -21126,6 +21133,39 @@ snapshots: webpack-virtual-modules@0.6.2: {} + webpack@5.104.1: + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.15.0 + acorn-import-phases: 1.0.4(acorn@8.15.0) + browserslist: 4.28.1 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.18.4 + es-module-lexer: 2.0.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.1 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 4.3.3 + tapable: 2.3.0 + terser-webpack-plugin: 5.3.16(webpack@5.104.1(esbuild@0.27.2)) + watchpack: 2.5.0 + webpack-sources: 3.3.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + optional: true + webpack@5.104.1(esbuild@0.27.2): dependencies: '@types/eslint-scope': 3.7.7 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5add109e67..773f661ccf 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,7 +1,8 @@ +packages: + - examples/**/* + - packages/* + cleanupUnusedCatalogs: true + linkWorkspacePackages: true preferWorkspacePackages: true - -packages: - - 'examples/**/*' - - 'packages/*'