@tanstack/angular-table 9.0.0-alpha.5 → 9.0.0-alpha.52

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.
Files changed (59) hide show
  1. package/README.md +127 -0
  2. package/dist/README.md +127 -0
  3. package/dist/fesm2022/tanstack-angular-table-static-functions.mjs +6 -0
  4. package/dist/fesm2022/tanstack-angular-table-static-functions.mjs.map +1 -0
  5. package/dist/fesm2022/tanstack-angular-table.mjs +1357 -242
  6. package/dist/fesm2022/tanstack-angular-table.mjs.map +1 -1
  7. package/dist/types/tanstack-angular-table-static-functions.d.ts +1 -0
  8. package/dist/types/tanstack-angular-table.d.ts +793 -0
  9. package/package.json +39 -19
  10. package/skills/angular/angular-rendering-directives/SKILL.md +415 -0
  11. package/skills/angular/angular-rendering-directives/references/content-shapes.md +142 -0
  12. package/skills/angular/angular-rendering-directives/references/create-table-hook-registries.md +89 -0
  13. package/skills/angular/angular-rendering-directives/references/di-tokens.md +171 -0
  14. package/skills/angular/angular-rendering-directives/references/flex-render-component-options.md +64 -0
  15. package/skills/angular/client-to-server/SKILL.md +467 -0
  16. package/skills/angular/compose-with-tanstack-query/SKILL.md +482 -0
  17. package/skills/angular/compose-with-tanstack-store/SKILL.md +397 -0
  18. package/skills/angular/compose-with-tanstack-virtual/SKILL.md +400 -0
  19. package/skills/angular/getting-started/SKILL.md +496 -0
  20. package/skills/angular/getting-started/references/feature-row-model-mapping.md +48 -0
  21. package/skills/angular/migrate-v8-to-v9/SKILL.md +419 -0
  22. package/skills/angular/migrate-v8-to-v9/references/v8-to-v9-mapping.md +261 -0
  23. package/skills/angular/production-readiness/SKILL.md +469 -0
  24. package/skills/angular/table-state/SKILL.md +429 -0
  25. package/skills/angular/table-state/references/external-atoms-and-app-hook.md +152 -0
  26. package/src/flex-render/context.ts +14 -0
  27. package/src/flex-render/flags.ts +34 -0
  28. package/src/flex-render/flexRenderComponent.ts +288 -0
  29. package/src/flex-render/flexRenderComponentFactory.ts +251 -0
  30. package/src/flex-render/renderer.ts +393 -0
  31. package/src/flex-render/view.ts +226 -0
  32. package/src/flexRender.ts +124 -0
  33. package/src/helpers/cell.ts +104 -0
  34. package/src/helpers/createTableHook.ts +499 -0
  35. package/src/helpers/flexRenderCell.ts +136 -0
  36. package/src/helpers/header.ts +99 -0
  37. package/src/helpers/table.ts +85 -0
  38. package/src/index.ts +23 -70
  39. package/src/injectTable.ts +206 -0
  40. package/src/{lazy-signal-initializer.ts → lazySignalInitializer.ts} +2 -2
  41. package/src/reactivity.ts +147 -0
  42. package/static-functions/index.ts +1 -0
  43. package/static-functions/ng-package.json +6 -0
  44. package/dist/esm2022/flex-render.mjs +0 -150
  45. package/dist/esm2022/index.mjs +0 -48
  46. package/dist/esm2022/lazy-signal-initializer.mjs +0 -43
  47. package/dist/esm2022/proxy.mjs +0 -83
  48. package/dist/esm2022/tanstack-angular-table.mjs +0 -5
  49. package/dist/flex-render.d.ts +0 -31
  50. package/dist/index.d.ts +0 -5
  51. package/dist/lazy-signal-initializer.d.ts +0 -5
  52. package/dist/proxy.d.ts +0 -3
  53. package/src/__tests__/createAngularTable.test.ts +0 -95
  54. package/src/__tests__/flex-render.test.ts +0 -178
  55. package/src/__tests__/lazy-init.test.ts +0 -124
  56. package/src/__tests__/test-setup.ts +0 -12
  57. package/src/__tests__/test-utils.ts +0 -62
  58. package/src/flex-render.ts +0 -187
  59. package/src/proxy.ts +0 -97
@@ -1,178 +0,0 @@
1
- import { Component, ViewChild, input, type TemplateRef } from '@angular/core'
2
- import { TestBed, type ComponentFixture } from '@angular/core/testing'
3
- import { createColumnHelper } from '@tanstack/table-core'
4
- import { skip } from 'node:test'
5
- import { describe, expect, test } from 'vitest'
6
- import {
7
- FlexRenderComponent,
8
- FlexRenderDirective,
9
- injectFlexRenderContext,
10
- } from '../flex-render'
11
- import { setFixtureSignalInput, setFixtureSignalInputs } from './test-utils'
12
-
13
- interface Data {
14
- id: string
15
- title: string
16
- description: string
17
- status: 'success' | 'failed' | 'pending'
18
- favorite?: boolean
19
- }
20
-
21
- describe('FlexRenderDirective', () => {
22
- const helper = createColumnHelper<Data>()
23
-
24
- test('should render primitives', async () => {
25
- const fixture = TestBed.createComponent(TestRenderComponent)
26
-
27
- // String
28
- setFixtureSignalInputs(fixture, {
29
- content: 'My value',
30
- context: {},
31
- })
32
- expectPrimitiveValueIs(fixture, 'My value')
33
-
34
- // Numbers
35
- setFixtureSignalInputs(fixture, {
36
- content: 0,
37
- context: {},
38
- })
39
- expectPrimitiveValueIs(fixture, '0')
40
-
41
- // Functions that returns primitives
42
- setFixtureSignalInputs(fixture, {
43
- content: () => 'My value 2',
44
- context: {},
45
- })
46
- expectPrimitiveValueIs(fixture, 'My value 2')
47
-
48
- // Null
49
- setFixtureSignalInputs(fixture, {
50
- content: () => null,
51
- context: {},
52
- })
53
- expectPrimitiveValueIs(fixture, '')
54
-
55
- // Undefined
56
- setFixtureSignalInputs(fixture, {
57
- content: () => undefined,
58
- context: {},
59
- })
60
- expectPrimitiveValueIs(fixture, '')
61
- })
62
-
63
- test('should render TemplateRef', () => {
64
- @Component({
65
- template: `
66
- <ng-template #template let-context>{{ context.property }}</ng-template>
67
- `,
68
- standalone: true,
69
- })
70
- class FakeTemplateRefComponent {
71
- @ViewChild('template', { static: true })
72
- templateRef!: TemplateRef<any>
73
- }
74
-
75
- const templateRef = TestBed.createComponent(FakeTemplateRefComponent)
76
- .componentInstance.templateRef
77
-
78
- const fixture = TestBed.createComponent(TestRenderComponent)
79
- setFixtureSignalInputs(fixture, {
80
- content: () => templateRef,
81
- context: {
82
- property: 'Property context value',
83
- },
84
- })
85
-
86
- expect(fixture.nativeElement.textContent).toEqual('Property context value')
87
-
88
- setFixtureSignalInput(fixture, 'context', { property: 'Updated value' })
89
- fixture.detectChanges()
90
-
91
- expect(fixture.nativeElement.textContent).toEqual('Updated value')
92
- })
93
-
94
- test('should render components', () => {
95
- @Component({
96
- template: `{{ context.property }}`,
97
- standalone: true,
98
- })
99
- class FakeComponent {
100
- context = injectFlexRenderContext<{ property: string }>()
101
- }
102
-
103
- const fixture = TestBed.createComponent(TestRenderComponent)
104
- setFixtureSignalInputs(fixture, {
105
- content: () => new FlexRenderComponent(FakeComponent),
106
- context: {
107
- property: 'Context value',
108
- },
109
- })
110
-
111
- expect(fixture.nativeElement.textContent).toEqual('Context value')
112
-
113
- setFixtureSignalInput(fixture, 'context', { property: 'Updated value' })
114
- fixture.detectChanges()
115
-
116
- expect(fixture.nativeElement.textContent).toEqual('Updated value')
117
- })
118
-
119
- // Skip for now, test framework (using ComponentRef.setInput) cannot recognize signal inputs
120
- // as component inputs
121
- skip('should render custom components', () => {
122
- @Component({
123
- template: `{{ row().property }}`,
124
- standalone: true,
125
- })
126
- class FakeComponent {
127
- row = input.required<{ property: string }>()
128
- }
129
-
130
- const fixture = TestBed.createComponent(TestRenderComponent)
131
- setFixtureSignalInputs(fixture, {
132
- content: () => FakeComponent,
133
- context: {
134
- row: {
135
- property: 'Row value',
136
- },
137
- },
138
- })
139
-
140
- expect(fixture.nativeElement.textContent).toEqual('Row value')
141
-
142
- setFixtureSignalInput(fixture, 'context', {
143
- row: { property: 'Updated value' },
144
- })
145
- fixture.detectChanges()
146
-
147
- expect(fixture.nativeElement.textContent).toEqual('Updated value')
148
- })
149
- })
150
-
151
- @Component({
152
- selector: 'app-test-render',
153
- template: `
154
- <ng-container *flexRender="content(); props: context(); let renderValue">
155
- <span [innerHTML]="renderValue"></span>
156
- </ng-container>
157
- `,
158
- standalone: true,
159
- imports: [FlexRenderDirective],
160
- })
161
- class TestRenderComponent {
162
- readonly content = input.required<FlexRenderDirectiveAllowedContent>()
163
-
164
- readonly context = input.required<Record<string, unknown>>()
165
- }
166
-
167
- type FlexRenderDirectiveAllowedContent = FlexRenderDirective<
168
- NonNullable<unknown>
169
- >['content']
170
-
171
- function expectPrimitiveValueIs(
172
- fixture: ComponentFixture<unknown>,
173
- value: unknown
174
- ) {
175
- const span = fixture.nativeElement.querySelector('span')
176
- expect(span).toBeDefined()
177
- expect(span.innerHTML).toEqual(value)
178
- }
@@ -1,124 +0,0 @@
1
- import { describe, expect, test } from 'vitest'
2
- import {
3
- ChangeDetectionStrategy,
4
- Component,
5
- type WritableSignal,
6
- computed,
7
- effect,
8
- input,
9
- signal,
10
- } from '@angular/core'
11
- import { TestBed } from '@angular/core/testing'
12
- import { lazyInit } from '../lazy-signal-initializer'
13
- import { flushQueue, setFixtureSignalInputs } from './test-utils'
14
-
15
- describe('lazyInit', () => {
16
- test('should init lazily in next tick when not accessing manually', async () => {
17
- const mockFn = vi.fn()
18
-
19
- TestBed.runInInjectionContext(() => {
20
- lazyInit(() => {
21
- mockFn()
22
- return {
23
- data: signal(true),
24
- }
25
- })
26
- })
27
-
28
- expect(mockFn).not.toHaveBeenCalled()
29
-
30
- await new Promise(setImmediate)
31
-
32
- expect(mockFn).toHaveBeenCalled()
33
- })
34
-
35
- test('should init eagerly accessing manually', async () => {
36
- const mockFn = vi.fn()
37
-
38
- TestBed.runInInjectionContext(() => {
39
- const lazySignal = lazyInit(() => {
40
- mockFn()
41
- return {
42
- data: signal(true),
43
- }
44
- })
45
-
46
- lazySignal.data()
47
- })
48
-
49
- expect(mockFn).toHaveBeenCalled()
50
- })
51
-
52
- test('should init lazily and only once', async () => {
53
- const initCallFn = vi.fn()
54
- const registerDataValue = vi.fn<[number]>()
55
-
56
- let value!: { data: WritableSignal<number> }
57
- const outerSignal = signal(0)
58
-
59
- TestBed.runInInjectionContext(() => {
60
- value = lazyInit(() => {
61
- initCallFn()
62
-
63
- void outerSignal()
64
-
65
- return { data: signal(0) }
66
- })
67
-
68
- effect(() => registerDataValue(value.data()))
69
- })
70
-
71
- value.data()
72
-
73
- await flushQueue()
74
-
75
- expect(outerSignal).toBeDefined()
76
-
77
- expect(initCallFn).toHaveBeenCalledTimes(1)
78
-
79
- outerSignal.set(1)
80
- await flushQueue()
81
- outerSignal.set(2)
82
- await flushQueue()
83
- value.data.set(4)
84
- await flushQueue()
85
-
86
- expect(initCallFn).toHaveBeenCalledTimes(1)
87
- expect(registerDataValue).toHaveBeenCalledTimes(2)
88
- })
89
-
90
- test('should support required signal input', async () => {
91
- @Component({
92
- standalone: true,
93
- template: `{{ call }} - {{ lazySignal.data() }}`,
94
- changeDetection: ChangeDetectionStrategy.OnPush,
95
- })
96
- class Test {
97
- readonly title = input.required<string>()
98
- call = 0
99
-
100
- lazySignal = lazyInit(() => {
101
- this.call++
102
- return {
103
- data: computed(() => this.title()),
104
- }
105
- })
106
- }
107
-
108
- const fixture = TestBed.createComponent(Test)
109
-
110
- setFixtureSignalInputs(fixture, { title: 'newValue' })
111
- expect(fixture.debugElement.nativeElement.textContent).toBe('0 - newValue')
112
- await flushQueue()
113
-
114
- setFixtureSignalInputs(fixture, { title: 'updatedValue' })
115
- expect(fixture.debugElement.nativeElement.textContent).toBe(
116
- '1 - updatedValue'
117
- )
118
-
119
- setFixtureSignalInputs(fixture, { title: 'newUpdatedValue' })
120
- expect(fixture.debugElement.nativeElement.textContent).toBe(
121
- '1 - newUpdatedValue'
122
- )
123
- })
124
- })
@@ -1,12 +0,0 @@
1
- import '@analogjs/vite-plugin-angular/setup-vitest'
2
-
3
- import {
4
- BrowserDynamicTestingModule,
5
- platformBrowserDynamicTesting,
6
- } from '@angular/platform-browser-dynamic/testing'
7
- import { getTestBed } from '@angular/core/testing'
8
-
9
- getTestBed().initTestEnvironment(
10
- BrowserDynamicTestingModule,
11
- platformBrowserDynamicTesting()
12
- )
@@ -1,62 +0,0 @@
1
- import type { InputSignal } from '@angular/core'
2
- import { SIGNAL, signalSetFn } from '@angular/core/primitives/signals'
3
- import type { ComponentFixture } from '@angular/core/testing'
4
-
5
- type ToSignalInputUpdatableMap<T> = {
6
- [K in keyof T as T[K] extends InputSignal<any>
7
- ? K
8
- : never]: T[K] extends InputSignal<infer Value> ? Value : never
9
- }
10
-
11
- /**
12
- * Set required signal input value to component fixture
13
- * @see https://github.com/angular/angular/issues/54013
14
- */
15
- export function setSignalInputs<T extends NonNullable<unknown>>(
16
- component: T,
17
- inputs: ToSignalInputUpdatableMap<T>
18
- ) {
19
- for (const inputKey in inputs) {
20
- if (componentHasSignalInputProperty(component, inputKey)) {
21
- signalSetFn(component[inputKey][SIGNAL], inputs[inputKey])
22
- }
23
- }
24
- }
25
-
26
- export function setFixtureSignalInputs<T extends NonNullable<unknown>>(
27
- componentFixture: ComponentFixture<T>,
28
- inputs: ToSignalInputUpdatableMap<T>,
29
- options: { detectChanges: boolean } = { detectChanges: true }
30
- ) {
31
- setSignalInputs(componentFixture.componentInstance, inputs)
32
- if (options.detectChanges) {
33
- componentFixture.detectChanges()
34
- }
35
- }
36
-
37
- export function setFixtureSignalInput<
38
- T extends NonNullable<unknown>,
39
- InputMaps extends ToSignalInputUpdatableMap<T>,
40
- InputName extends keyof InputMaps,
41
- >(
42
- componentFixture: ComponentFixture<T>,
43
- inputName: InputName,
44
- value: InputMaps[InputName]
45
- ) {
46
- setSignalInputs(componentFixture.componentInstance, {
47
- [inputName]: value,
48
- } as ToSignalInputUpdatableMap<T>)
49
- }
50
-
51
- function componentHasSignalInputProperty<TProperty extends string>(
52
- component: object,
53
- property: TProperty
54
- ): component is { [key in TProperty]: InputSignal<unknown> } {
55
- return (
56
- component.hasOwnProperty(property) && (component as any)[property][SIGNAL]
57
- )
58
- }
59
-
60
- export async function flushQueue() {
61
- await new Promise(setImmediate)
62
- }
@@ -1,187 +0,0 @@
1
- import {
2
- ChangeDetectorRef,
3
- ComponentRef,
4
- Directive,
5
- EmbeddedViewRef,
6
- Inject,
7
- InjectionToken,
8
- Injector,
9
- Input,
10
- TemplateRef,
11
- Type,
12
- ViewContainerRef,
13
- inject,
14
- isSignal,
15
- type DoCheck,
16
- type OnInit,
17
- } from '@angular/core'
18
-
19
- export type FlexRenderContent<TProps extends NonNullable<unknown>> =
20
- | string
21
- | number
22
- | Type<TProps>
23
- | FlexRenderComponent<TProps>
24
- | TemplateRef<{ $implicit: TProps }>
25
- | null
26
- | undefined
27
-
28
- @Directive({
29
- selector: '[flexRender]',
30
- standalone: true,
31
- })
32
- export class FlexRenderDirective<TProps extends NonNullable<unknown>>
33
- implements OnInit, DoCheck
34
- {
35
- @Input({ required: true, alias: 'flexRender' })
36
- content:
37
- | number
38
- | string
39
- | ((props: TProps) => FlexRenderContent<TProps>)
40
- | undefined = undefined
41
-
42
- @Input({ required: true, alias: 'flexRenderProps' })
43
- props: TProps = {} as TProps
44
-
45
- @Input({ required: false, alias: 'flexRenderInjector' })
46
- injector: Injector = inject(Injector)
47
-
48
- constructor(
49
- @Inject(ViewContainerRef)
50
- private readonly viewContainerRef: ViewContainerRef,
51
- @Inject(TemplateRef)
52
- private readonly templateRef: TemplateRef<any>
53
- ) {}
54
-
55
- ref?: ComponentRef<unknown> | EmbeddedViewRef<unknown> | null = null
56
-
57
- ngOnInit(): void {
58
- this.ref = this.render()
59
- }
60
-
61
- ngDoCheck() {
62
- if (this.ref instanceof ComponentRef) {
63
- this.ref.injector.get(ChangeDetectorRef).markForCheck()
64
- } else if (this.ref instanceof EmbeddedViewRef) {
65
- this.ref.markForCheck()
66
- }
67
- }
68
-
69
- render() {
70
- this.viewContainerRef.clear()
71
- const { content, props } = this
72
- if (!this.content) {
73
- return null
74
- }
75
-
76
- if (typeof content === 'string' || typeof content === 'number') {
77
- return this.renderStringContent()
78
- }
79
- if (typeof content === 'function') {
80
- return this.renderContent(content(props))
81
- }
82
- return null
83
- }
84
-
85
- private renderContent(content: FlexRenderContent<TProps>) {
86
- if (typeof content === 'string' || typeof content === 'number') {
87
- return this.renderStringContent()
88
- }
89
- if (content instanceof TemplateRef) {
90
- return this.viewContainerRef.createEmbeddedView(
91
- content,
92
- this.getTemplateRefContext()
93
- )
94
- } else if (content instanceof FlexRenderComponent) {
95
- return this.renderComponent(content)
96
- } else if (content instanceof Type) {
97
- return this.renderCustomComponent(content)
98
- } else {
99
- return null
100
- }
101
- }
102
-
103
- private renderStringContent(): EmbeddedViewRef<unknown> {
104
- const context = () => {
105
- return typeof this.content === 'string' ||
106
- typeof this.content === 'number'
107
- ? this.content
108
- : this.content?.(this.props)
109
- }
110
- return this.viewContainerRef.createEmbeddedView(this.templateRef, {
111
- get $implicit() {
112
- return context()
113
- },
114
- })
115
- }
116
-
117
- private renderComponent(
118
- flexRenderComponent: FlexRenderComponent<TProps>
119
- ): ComponentRef<unknown> {
120
- const { component, inputs, injector } = flexRenderComponent
121
-
122
- const getContext = () => this.props
123
-
124
- const proxy = new Proxy(this.props, {
125
- get: (_, key) => getContext()?.[key as keyof typeof _],
126
- })
127
-
128
- const componentInjector = Injector.create({
129
- parent: injector ?? this.injector,
130
- providers: [{ provide: FlexRenderComponentProps, useValue: proxy }],
131
- })
132
-
133
- const componentRef = this.viewContainerRef.createComponent(component, {
134
- injector: componentInjector,
135
- })
136
- for (const prop in inputs) {
137
- if (componentRef.instance?.hasOwnProperty(prop)) {
138
- componentRef.setInput(prop, inputs[prop])
139
- }
140
- }
141
- return componentRef
142
- }
143
-
144
- private renderCustomComponent(
145
- component: Type<unknown>
146
- ): ComponentRef<unknown> {
147
- const componentRef = this.viewContainerRef.createComponent(component, {
148
- injector: this.injector,
149
- })
150
- for (const prop in this.props) {
151
- // Only signal based input can be added here
152
- if (
153
- componentRef.instance?.hasOwnProperty(prop) &&
154
- // @ts-ignore
155
- isSignal(componentRef.instance[prop])
156
- ) {
157
- componentRef.setInput(prop, this.props[prop])
158
- }
159
- }
160
- return componentRef
161
- }
162
-
163
- private getTemplateRefContext() {
164
- const getContext = () => this.props
165
- return {
166
- get $implicit() {
167
- return getContext()
168
- },
169
- }
170
- }
171
- }
172
-
173
- export class FlexRenderComponent<T extends NonNullable<unknown>> {
174
- constructor(
175
- readonly component: Type<unknown>,
176
- readonly inputs: T = {} as T,
177
- readonly injector?: Injector
178
- ) {}
179
- }
180
-
181
- const FlexRenderComponentProps = new InjectionToken<NonNullable<unknown>>(
182
- '[@tanstack/angular-table] Flex render component context props'
183
- )
184
-
185
- export function injectFlexRenderContext<T extends NonNullable<unknown>>(): T {
186
- return inject<T>(FlexRenderComponentProps)
187
- }
package/src/proxy.ts DELETED
@@ -1,97 +0,0 @@
1
- import { computed, type Signal, untracked } from '@angular/core'
2
- import { type Table } from '@tanstack/table-core'
3
-
4
- type TableSignal<T> = Table<T> & Signal<Table<T>>
5
-
6
- export function proxifyTable<T>(
7
- tableSignal: Signal<Table<T>>
8
- ): Table<T> & Signal<Table<T>> {
9
- const internalState = tableSignal as TableSignal<T>
10
-
11
- return new Proxy(internalState, {
12
- apply() {
13
- return tableSignal()
14
- },
15
- get(target, property: keyof Table<T>): any {
16
- if (target[property]) {
17
- return target[property]
18
- }
19
- const table = untracked(tableSignal)
20
- /**
21
- * Attempt to convert all accessors into computed ones,
22
- * excluding handlers as they do not retain any reactive value
23
- */
24
- if (
25
- property.startsWith('get') &&
26
- !property.endsWith('Handler') &&
27
- !property.endsWith('Model')
28
- ) {
29
- const maybeFn = table[property] as Function | never
30
- if (typeof maybeFn === 'function') {
31
- Object.defineProperty(target, property, {
32
- value: toComputed(tableSignal, maybeFn),
33
- configurable: true,
34
- enumerable: true,
35
- })
36
- return target[property]
37
- }
38
- }
39
- // @ts-expect-error
40
- return (target[property] = table[property])
41
- },
42
- has(_, prop: keyof Table<T>) {
43
- return !!untracked(tableSignal)[prop]
44
- },
45
- ownKeys() {
46
- return Reflect.ownKeys(untracked(tableSignal))
47
- },
48
- getOwnPropertyDescriptor() {
49
- return {
50
- enumerable: true,
51
- configurable: true,
52
- }
53
- },
54
- })
55
- }
56
-
57
- /**
58
- * Here we'll handle all type of accessors:
59
- * - 0 argument -> e.g. table.getCanNextPage())
60
- * - 0~1 arguments -> e.g. table.getIsSomeRowsPinned(position?)
61
- * - 1 required argument -> e.g. table.getColumn(columnId)
62
- * - 1+ argument -> e.g. table.getRow(id, searchAll?)
63
- *
64
- * Since we are not able to detect automatically the accessors parameters,
65
- * we'll wrap all accessors into a cached function wrapping a computed
66
- * that return it's value based on the given parameters
67
- */
68
- function toComputed<T>(signal: Signal<Table<T>>, fn: Function) {
69
- const hasArgs = fn.length > 0
70
- if (!hasArgs) {
71
- return computed(() => {
72
- void signal()
73
- return fn()
74
- })
75
- }
76
-
77
- const computedCache: Record<string, Signal<unknown>> = {}
78
-
79
- return (...argsArray: any[]) => {
80
- const serializedArgs = serializeArgs(...argsArray)
81
- if (computedCache.hasOwnProperty(serializedArgs)) {
82
- return computedCache[serializedArgs]?.()
83
- }
84
- const computedSignal = computed(() => {
85
- void signal()
86
- return fn(...argsArray)
87
- })
88
-
89
- computedCache[serializedArgs] = computedSignal
90
-
91
- return computedSignal()
92
- }
93
- }
94
-
95
- function serializeArgs(...args: any[]) {
96
- return JSON.stringify(args)
97
- }