@tanstack/angular-table 9.0.0-alpha.1 → 9.0.0-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fesm2022/tanstack-angular-table.mjs +1370 -239
- package/dist/fesm2022/tanstack-angular-table.mjs.map +1 -1
- package/dist/types/tanstack-angular-table.d.ts +767 -0
- package/package.json +27 -17
- package/src/angularReactivityFeature.ts +210 -0
- package/src/flex-render/context.ts +14 -0
- package/src/flex-render/flags.ts +34 -0
- package/src/flex-render/flexRenderComponent.ts +288 -0
- package/src/flex-render/flexRenderComponentFactory.ts +241 -0
- package/src/flex-render/renderer.ts +376 -0
- package/src/flex-render/view.ts +165 -0
- package/src/flexRender.ts +124 -0
- package/src/helpers/cell.ts +104 -0
- package/src/helpers/createTableHook.ts +480 -0
- package/src/helpers/flexRenderCell.ts +136 -0
- package/src/helpers/header.ts +99 -0
- package/src/helpers/table.ts +87 -0
- package/src/index.ts +17 -70
- package/src/injectTable.ts +123 -0
- package/src/{lazy-signal-initializer.ts → lazySignalInitializer.ts} +2 -2
- package/src/reactivityUtils.ts +232 -0
- package/dist/esm2022/flex-render.mjs +0 -133
- package/dist/esm2022/index.mjs +0 -48
- package/dist/esm2022/lazy-signal-initializer.mjs +0 -43
- package/dist/esm2022/proxy.mjs +0 -83
- package/dist/esm2022/tanstack-angular-table.mjs +0 -5
- package/dist/flex-render.d.ts +0 -30
- package/dist/index.d.ts +0 -5
- package/dist/lazy-signal-initializer.d.ts +0 -5
- package/dist/proxy.d.ts +0 -3
- package/src/__tests__/createAngularTable.test.ts +0 -95
- package/src/__tests__/flex-render.test.ts +0 -156
- package/src/__tests__/lazy-init.test.ts +0 -124
- package/src/__tests__/test-setup.ts +0 -12
- package/src/__tests__/test-utils.ts +0 -62
- package/src/flex-render.ts +0 -164
- package/src/proxy.ts +0 -97
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Directive, InjectionToken, inject, input } from '@angular/core'
|
|
2
|
+
import { RowData, TableFeatures, TableState } from '@tanstack/table-core'
|
|
3
|
+
import { AngularTable } from '../injectTable'
|
|
4
|
+
import type { Signal } from '@angular/core'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Injection token that provides access to the current {@link AngularTable} instance.
|
|
8
|
+
*
|
|
9
|
+
* This token is provided by the {@link TanStackTable} directive.
|
|
10
|
+
*/
|
|
11
|
+
export const TanStackTableToken = new InjectionToken<
|
|
12
|
+
Signal<AngularTable<any, any>>
|
|
13
|
+
>('[TanStack Table] Table Context')
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Provides a TanStack Table instance (`AngularTable`) in Angular DI.
|
|
17
|
+
*
|
|
18
|
+
* The table can be injected by:
|
|
19
|
+
* - any descendant of an element using `[tanStackTable]="..."`
|
|
20
|
+
* - any component instantiated by `*flexRender` when the render props contains `table`
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```html
|
|
24
|
+
* <div [tanStackTable]="table">
|
|
25
|
+
* <app-pagination />
|
|
26
|
+
* </div>
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* @Component({
|
|
31
|
+
* selector: 'app-pagination',
|
|
32
|
+
* template: `
|
|
33
|
+
* <button (click)="prev()" [disabled]="!table().getCanPreviousPage()">Prev</button>
|
|
34
|
+
* <button (click)="next()" [disabled]="!table().getCanNextPage()">Next</button>
|
|
35
|
+
* `,
|
|
36
|
+
* })
|
|
37
|
+
* export class PaginationComponent {
|
|
38
|
+
* readonly table = injectTableContext()
|
|
39
|
+
*
|
|
40
|
+
* prev() {
|
|
41
|
+
* this.table().previousPage()
|
|
42
|
+
* }
|
|
43
|
+
* next() {
|
|
44
|
+
* this.table().nextPage()
|
|
45
|
+
* }
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
@Directive({
|
|
50
|
+
selector: '[tanStackTable]',
|
|
51
|
+
exportAs: 'table',
|
|
52
|
+
providers: [
|
|
53
|
+
{
|
|
54
|
+
provide: TanStackTableToken,
|
|
55
|
+
useFactory: () => inject(TanStackTable).table,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
})
|
|
59
|
+
export class TanStackTable<
|
|
60
|
+
TFeatures extends TableFeatures,
|
|
61
|
+
TData extends RowData,
|
|
62
|
+
TSelected extends {} = TableState<TFeatures>,
|
|
63
|
+
> {
|
|
64
|
+
/**
|
|
65
|
+
* The current TanStack Table instance.
|
|
66
|
+
*
|
|
67
|
+
* Provided as a required signal input so DI consumers always read the latest value.
|
|
68
|
+
*/
|
|
69
|
+
readonly table = input.required<AngularTable<TFeatures, TData, TSelected>>({
|
|
70
|
+
alias: 'tanStackTable',
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Injects the current TanStack Table instance signal.
|
|
76
|
+
*
|
|
77
|
+
* Available when:
|
|
78
|
+
* - there is a nearest `[tanStackTable]` directive in the DI tree, or
|
|
79
|
+
* - the caller is rendered via `*flexRender` with render props containing `table`
|
|
80
|
+
*/
|
|
81
|
+
export function injectTableContext<
|
|
82
|
+
TFeatures extends TableFeatures,
|
|
83
|
+
TData extends RowData,
|
|
84
|
+
TSelected extends {} = TableState<TFeatures>,
|
|
85
|
+
>(): Signal<AngularTable<TFeatures, TData, TSelected>> {
|
|
86
|
+
return inject(TanStackTableToken)
|
|
87
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,73 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
RowData,
|
|
4
|
-
TableOptions,
|
|
5
|
-
TableOptionsResolved,
|
|
6
|
-
TableState,
|
|
7
|
-
createTable,
|
|
8
|
-
type Table,
|
|
9
|
-
} from '@tanstack/table-core'
|
|
10
|
-
import { lazyInit } from './lazy-signal-initializer'
|
|
11
|
-
import { proxifyTable } from './proxy'
|
|
1
|
+
import { FlexRenderCell } from './helpers/flexRenderCell'
|
|
2
|
+
import { FlexRenderDirective } from './flexRender'
|
|
12
3
|
|
|
13
4
|
export * from '@tanstack/table-core'
|
|
14
5
|
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
...options(),
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const table = createTable(resolvedOptions)
|
|
34
|
-
|
|
35
|
-
// By default, manage table state here using the table's initial state
|
|
36
|
-
const state = signal<TableState>(table.initialState)
|
|
37
|
-
|
|
38
|
-
// Compose table options using computed.
|
|
39
|
-
// This is to allow `tableSignal` to listen and set table option
|
|
40
|
-
const updatedOptions = computed<TableOptionsResolved<TData>>(() => {
|
|
41
|
-
// listen to table state changed
|
|
42
|
-
const tableState = state()
|
|
43
|
-
// listen to input options changed
|
|
44
|
-
const tableOptions = options()
|
|
45
|
-
return {
|
|
46
|
-
...table.options,
|
|
47
|
-
...resolvedOptions,
|
|
48
|
-
...tableOptions,
|
|
49
|
-
state: { ...tableState, ...tableOptions.state },
|
|
50
|
-
onStateChange: updater => {
|
|
51
|
-
const value =
|
|
52
|
-
updater instanceof Function ? updater(tableState) : updater
|
|
53
|
-
state.set(value)
|
|
54
|
-
resolvedOptions.onStateChange?.(updater)
|
|
55
|
-
},
|
|
56
|
-
}
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
// convert table instance to signal for proxify to listen to any table state and options changes
|
|
60
|
-
const tableSignal = computed(
|
|
61
|
-
() => {
|
|
62
|
-
table.setOptions(updatedOptions())
|
|
63
|
-
return table
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
equal: () => false,
|
|
67
|
-
}
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
// proxify Table instance to provide ability for consumer to listen to any table state changes
|
|
71
|
-
return proxifyTable(tableSignal)
|
|
72
|
-
})
|
|
73
|
-
}
|
|
6
|
+
export * from './angularReactivityFeature'
|
|
7
|
+
export * from './flexRender'
|
|
8
|
+
export * from './injectTable'
|
|
9
|
+
export * from './flex-render/flexRenderComponent'
|
|
10
|
+
|
|
11
|
+
export * from './helpers/cell'
|
|
12
|
+
export * from './helpers/header'
|
|
13
|
+
export * from './helpers/table'
|
|
14
|
+
export * from './helpers/createTableHook'
|
|
15
|
+
export * from './helpers/flexRenderCell'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Constant helper to import FlexRender directives.
|
|
19
|
+
*/
|
|
20
|
+
export const FlexRender = [FlexRenderDirective, FlexRenderCell] as const
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Injector,
|
|
3
|
+
assertInInjectionContext,
|
|
4
|
+
computed,
|
|
5
|
+
effect,
|
|
6
|
+
inject,
|
|
7
|
+
untracked,
|
|
8
|
+
} from '@angular/core'
|
|
9
|
+
import { constructTable } from '@tanstack/table-core'
|
|
10
|
+
import { injectStore } from '@tanstack/angular-store'
|
|
11
|
+
import { lazyInit } from './lazySignalInitializer'
|
|
12
|
+
import { angularReactivityFeature } from './angularReactivityFeature'
|
|
13
|
+
import type {
|
|
14
|
+
RowData,
|
|
15
|
+
Table,
|
|
16
|
+
TableFeatures,
|
|
17
|
+
TableOptions,
|
|
18
|
+
TableState,
|
|
19
|
+
} from '@tanstack/table-core'
|
|
20
|
+
import type { Signal, ValueEqualityFn } from '@angular/core'
|
|
21
|
+
|
|
22
|
+
export type AngularTable<
|
|
23
|
+
TFeatures extends TableFeatures,
|
|
24
|
+
TData extends RowData,
|
|
25
|
+
TSelected = TableState<TFeatures>,
|
|
26
|
+
> = Table<TFeatures, TData> & {
|
|
27
|
+
/**
|
|
28
|
+
* The selected state from the table store, based on the selector provided.
|
|
29
|
+
*/
|
|
30
|
+
readonly state: Signal<Readonly<TSelected>>
|
|
31
|
+
/**
|
|
32
|
+
* Subscribe to changes in the table store with a custom selector.
|
|
33
|
+
*/
|
|
34
|
+
Subscribe: <TSubSelected = {}>(props: {
|
|
35
|
+
selector: (state: TableState<TFeatures>) => TSubSelected
|
|
36
|
+
equal?: ValueEqualityFn<TSubSelected>
|
|
37
|
+
}) => Signal<Readonly<TSubSelected>>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function injectTable<
|
|
41
|
+
TFeatures extends TableFeatures,
|
|
42
|
+
TData extends RowData,
|
|
43
|
+
TSelected = TableState<TFeatures>,
|
|
44
|
+
>(
|
|
45
|
+
options: () => TableOptions<TFeatures, TData>,
|
|
46
|
+
selector: (state: TableState<TFeatures>) => TSelected = (state) =>
|
|
47
|
+
state as TSelected,
|
|
48
|
+
): AngularTable<TFeatures, TData, TSelected> {
|
|
49
|
+
assertInInjectionContext(injectTable)
|
|
50
|
+
const injector = inject(Injector)
|
|
51
|
+
|
|
52
|
+
return lazyInit(() => {
|
|
53
|
+
const resolvedOptions: TableOptions<TFeatures, TData> = {
|
|
54
|
+
...options(),
|
|
55
|
+
_features: {
|
|
56
|
+
...options()._features,
|
|
57
|
+
angularReactivityFeature,
|
|
58
|
+
},
|
|
59
|
+
} as TableOptions<TFeatures, TData>
|
|
60
|
+
|
|
61
|
+
const table: AngularTable<TFeatures, TData, TSelected> = constructTable(
|
|
62
|
+
resolvedOptions,
|
|
63
|
+
) as AngularTable<TFeatures, TData, TSelected>
|
|
64
|
+
|
|
65
|
+
const updatedOptions = computed<TableOptions<TFeatures, TData>>(() => {
|
|
66
|
+
const tableOptionsValue = options()
|
|
67
|
+
const result: TableOptions<TFeatures, TData> = {
|
|
68
|
+
...table.options,
|
|
69
|
+
...tableOptionsValue,
|
|
70
|
+
_features: {
|
|
71
|
+
...tableOptionsValue._features,
|
|
72
|
+
angularReactivityFeature,
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
if (tableOptionsValue.state) {
|
|
76
|
+
result.state = tableOptionsValue.state
|
|
77
|
+
}
|
|
78
|
+
return result
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
effect(
|
|
82
|
+
(onCleanup) => {
|
|
83
|
+
const cleanup = table.store.mount()
|
|
84
|
+
onCleanup(() => cleanup())
|
|
85
|
+
},
|
|
86
|
+
{ injector },
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
const tableState = injectStore(
|
|
90
|
+
table.store,
|
|
91
|
+
(state: TableState<TFeatures>) => state,
|
|
92
|
+
{ injector },
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
const tableSignalNotifier = computed(
|
|
96
|
+
() => {
|
|
97
|
+
tableState()
|
|
98
|
+
table.setOptions(updatedOptions())
|
|
99
|
+
untracked(() => table.baseStore.setState((prev) => ({ ...prev })))
|
|
100
|
+
return table
|
|
101
|
+
},
|
|
102
|
+
{ equal: () => false },
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
table.setTableNotifier(tableSignalNotifier)
|
|
106
|
+
|
|
107
|
+
table.Subscribe = function Subscribe<TSubSelected = {}>(props: {
|
|
108
|
+
selector: (state: TableState<TFeatures>) => TSubSelected
|
|
109
|
+
equal?: ValueEqualityFn<TSubSelected>
|
|
110
|
+
}) {
|
|
111
|
+
return injectStore(table.store, props.selector, {
|
|
112
|
+
injector,
|
|
113
|
+
equal: props.equal,
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
Object.defineProperty(table, 'state', {
|
|
118
|
+
value: injectStore(table.store, selector, { injector }),
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
return table
|
|
122
|
+
})
|
|
123
|
+
}
|
|
@@ -2,7 +2,7 @@ import { untracked } from '@angular/core'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Implementation from @tanstack/angular-query
|
|
5
|
-
* {
|
|
5
|
+
* {https://github.com/TanStack/query/blob/main/packages/angular-query-experimental/src/util/lazy-init/lazy-init.ts}
|
|
6
6
|
*/
|
|
7
7
|
export function lazyInit<T extends object>(initializer: () => T): T {
|
|
8
8
|
let object: T | null = null
|
|
@@ -18,7 +18,7 @@ export function lazyInit<T extends object>(initializer: () => T): T {
|
|
|
18
18
|
const table = () => {}
|
|
19
19
|
|
|
20
20
|
return new Proxy<T>(table as T, {
|
|
21
|
-
apply(target: T, thisArg: any, argArray: any
|
|
21
|
+
apply(target: T, thisArg: any, argArray: Array<any>): any {
|
|
22
22
|
initializeObject()
|
|
23
23
|
if (typeof object === 'function') {
|
|
24
24
|
return Reflect.apply(object, thisArg, argArray)
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { computed, isSignal } from '@angular/core'
|
|
2
|
+
import { $internalMemoFnMeta, getMemoFnMeta } from '@tanstack/table-core'
|
|
3
|
+
import type { MemoFnMeta } from '@tanstack/table-core'
|
|
4
|
+
import type { Signal } from '@angular/core'
|
|
5
|
+
|
|
6
|
+
const $TABLE_REACTIVE = Symbol('reactive')
|
|
7
|
+
|
|
8
|
+
function markReactive<T extends object>(obj: T): void {
|
|
9
|
+
Object.defineProperty(obj, $TABLE_REACTIVE, { value: true })
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function isReactive<T>(obj: T): boolean {
|
|
13
|
+
return Reflect.get(obj as {}, $TABLE_REACTIVE) === true
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Defines a lazy computed property on an object. The property is initialized
|
|
18
|
+
* with a getter that computes its value only when accessed for the first time.
|
|
19
|
+
* After the first access, the computed value is cached, and the getter is
|
|
20
|
+
* replaced with a direct property assignment for efficiency.
|
|
21
|
+
*
|
|
22
|
+
* @internal should be used only internally
|
|
23
|
+
*/
|
|
24
|
+
export function defineLazyComputedProperty<T extends object>(
|
|
25
|
+
notifier: Signal<T>,
|
|
26
|
+
setObjectOptions: {
|
|
27
|
+
originalObject: T
|
|
28
|
+
property: keyof T & string
|
|
29
|
+
valueFn: (...args: any) => any
|
|
30
|
+
overridePrototype?: boolean
|
|
31
|
+
},
|
|
32
|
+
) {
|
|
33
|
+
const { originalObject, property, overridePrototype, valueFn } =
|
|
34
|
+
setObjectOptions
|
|
35
|
+
|
|
36
|
+
if (overridePrototype) {
|
|
37
|
+
assignReactivePrototypeAPI(notifier, originalObject, property)
|
|
38
|
+
} else {
|
|
39
|
+
Object.defineProperty(originalObject, property, {
|
|
40
|
+
enumerable: true,
|
|
41
|
+
configurable: true,
|
|
42
|
+
get() {
|
|
43
|
+
const computedValue = toComputed(notifier, valueFn, property)
|
|
44
|
+
markReactive(computedValue)
|
|
45
|
+
// Once the property is set the first time, we don't need a getter anymore
|
|
46
|
+
// since we have a computed / cached fn value
|
|
47
|
+
Object.defineProperty(originalObject, property, {
|
|
48
|
+
value: computedValue,
|
|
49
|
+
configurable: true,
|
|
50
|
+
enumerable: true,
|
|
51
|
+
})
|
|
52
|
+
return computedValue
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @internal should be used only internally
|
|
60
|
+
*/
|
|
61
|
+
type ComputedFunction<T> = T extends () => infer TReturn
|
|
62
|
+
? Signal<TReturn>
|
|
63
|
+
: // 1+ args
|
|
64
|
+
T extends (arg0?: any, ...args: Array<any>) => any
|
|
65
|
+
? T
|
|
66
|
+
: never
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @description Transform a function into a computed that react to given notifier re-computations
|
|
70
|
+
*
|
|
71
|
+
* Here we'll handle all type of accessors:
|
|
72
|
+
* - 0 argument -> e.g. table.getCanNextPage())
|
|
73
|
+
* - 0~1 arguments -> e.g. table.getIsSomeRowsPinned(position?)
|
|
74
|
+
* - 1 required argument -> e.g. table.getColumn(columnId)
|
|
75
|
+
* - 1+ argument -> e.g. table.getRow(id, searchAll?)
|
|
76
|
+
*
|
|
77
|
+
* Since we are not able to detect automatically the accessors parameters,
|
|
78
|
+
* we'll wrap all accessors into a cached function wrapping a computed
|
|
79
|
+
* that return it's value based on the given parameters
|
|
80
|
+
*
|
|
81
|
+
* @internal should be used only internally
|
|
82
|
+
*/
|
|
83
|
+
export function toComputed<
|
|
84
|
+
T,
|
|
85
|
+
TReturn,
|
|
86
|
+
TFunction extends (...args: any) => TReturn,
|
|
87
|
+
>(
|
|
88
|
+
notifier: Signal<T>,
|
|
89
|
+
fn: TFunction,
|
|
90
|
+
debugName: string,
|
|
91
|
+
): ComputedFunction<TFunction> {
|
|
92
|
+
const hasArgs = getFnArgsLength(fn) > 0
|
|
93
|
+
if (!hasArgs) {
|
|
94
|
+
const computedFn = computed(
|
|
95
|
+
() => {
|
|
96
|
+
void notifier()
|
|
97
|
+
return fn()
|
|
98
|
+
},
|
|
99
|
+
{ debugName },
|
|
100
|
+
)
|
|
101
|
+
Object.defineProperty(computedFn, 'name', { value: debugName })
|
|
102
|
+
markReactive(computedFn)
|
|
103
|
+
return computedFn as ComputedFunction<TFunction>
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const computedFn: ((this: unknown, ...argsArray: Array<any>) => unknown) & {
|
|
107
|
+
_reactiveCache?: Record<string, Signal<unknown>>
|
|
108
|
+
} = function (this: unknown, ...argsArray: Array<any>) {
|
|
109
|
+
const cacheable =
|
|
110
|
+
argsArray.length === 0 ||
|
|
111
|
+
argsArray.every((arg) => {
|
|
112
|
+
return (
|
|
113
|
+
arg === null ||
|
|
114
|
+
arg === undefined ||
|
|
115
|
+
typeof arg === 'string' ||
|
|
116
|
+
typeof arg === 'number' ||
|
|
117
|
+
typeof arg === 'boolean' ||
|
|
118
|
+
typeof arg === 'symbol'
|
|
119
|
+
)
|
|
120
|
+
})
|
|
121
|
+
if (!cacheable) {
|
|
122
|
+
return fn.apply(this, argsArray)
|
|
123
|
+
}
|
|
124
|
+
const serializedArgs = serializeArgs(...argsArray)
|
|
125
|
+
if ((computedFn._reactiveCache ??= {})[serializedArgs]) {
|
|
126
|
+
return computedFn._reactiveCache[serializedArgs]()
|
|
127
|
+
}
|
|
128
|
+
const computedSignal = computed(
|
|
129
|
+
() => {
|
|
130
|
+
void notifier()
|
|
131
|
+
return fn.apply(this, argsArray)
|
|
132
|
+
},
|
|
133
|
+
{ debugName },
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
computedFn._reactiveCache[serializedArgs] = computedSignal
|
|
137
|
+
|
|
138
|
+
return computedSignal()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
Object.defineProperty(computedFn, 'name', { value: debugName })
|
|
142
|
+
markReactive(computedFn)
|
|
143
|
+
|
|
144
|
+
return computedFn as ComputedFunction<TFunction>
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function serializeArgs(...args: Array<any>) {
|
|
148
|
+
return JSON.stringify(args)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function getFnArgsLength(
|
|
152
|
+
fn: ((...args: any) => any) & { originalArgsLength?: number },
|
|
153
|
+
): number {
|
|
154
|
+
return Math.max(0, getMemoFnMeta(fn)?.originalArgsLength ?? fn.length)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function assignReactivePrototypeAPI(
|
|
158
|
+
notifier: Signal<unknown>,
|
|
159
|
+
prototype: Record<string, any>,
|
|
160
|
+
fnName: string,
|
|
161
|
+
) {
|
|
162
|
+
if (isReactive(prototype[fnName])) return
|
|
163
|
+
|
|
164
|
+
const fn = prototype[fnName]
|
|
165
|
+
const originalArgsLength = getFnArgsLength(fn)
|
|
166
|
+
|
|
167
|
+
if (originalArgsLength <= 1) {
|
|
168
|
+
Object.defineProperty(prototype, fnName, {
|
|
169
|
+
enumerable: true,
|
|
170
|
+
configurable: true,
|
|
171
|
+
get(this) {
|
|
172
|
+
const self = this
|
|
173
|
+
// Create a cache in the current prototype to allow the signals
|
|
174
|
+
// to be garbage collected. Shorthand for a WeakMap implementation
|
|
175
|
+
self._reactiveCache ??= {}
|
|
176
|
+
const cached = (self._reactiveCache[`${self.id}${fnName}`] ??= computed(
|
|
177
|
+
() => {
|
|
178
|
+
notifier()
|
|
179
|
+
return fn.apply(self)
|
|
180
|
+
},
|
|
181
|
+
{},
|
|
182
|
+
))
|
|
183
|
+
markReactive(cached)
|
|
184
|
+
cached[$internalMemoFnMeta] = {
|
|
185
|
+
originalArgsLength,
|
|
186
|
+
} satisfies MemoFnMeta
|
|
187
|
+
return cached
|
|
188
|
+
},
|
|
189
|
+
})
|
|
190
|
+
} else {
|
|
191
|
+
prototype[fnName] = function (this: unknown, ...args: Array<any>) {
|
|
192
|
+
notifier()
|
|
193
|
+
return fn.apply(this, args)
|
|
194
|
+
}
|
|
195
|
+
markReactive(prototype[fnName])
|
|
196
|
+
prototype[fnName][$internalMemoFnMeta] = {
|
|
197
|
+
originalArgsLength,
|
|
198
|
+
} satisfies MemoFnMeta
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function setReactivePropertiesOnObject<T extends object>(
|
|
203
|
+
notifier: Signal<T>,
|
|
204
|
+
obj: { [key: string]: any },
|
|
205
|
+
options: {
|
|
206
|
+
overridePrototype?: boolean
|
|
207
|
+
skipProperty: (property: string) => boolean
|
|
208
|
+
},
|
|
209
|
+
) {
|
|
210
|
+
const { skipProperty } = options
|
|
211
|
+
if (isReactive(obj)) {
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
markReactive(obj)
|
|
215
|
+
|
|
216
|
+
for (const property in obj) {
|
|
217
|
+
const value = obj[property]
|
|
218
|
+
if (
|
|
219
|
+
isSignal(value) ||
|
|
220
|
+
typeof value !== 'function' ||
|
|
221
|
+
skipProperty(property)
|
|
222
|
+
) {
|
|
223
|
+
continue
|
|
224
|
+
}
|
|
225
|
+
defineLazyComputedProperty(notifier, {
|
|
226
|
+
valueFn: value,
|
|
227
|
+
property,
|
|
228
|
+
originalObject: obj,
|
|
229
|
+
overridePrototype: options.overridePrototype,
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
}
|