@tanstack/angular-table 9.0.0-alpha.4 → 9.0.0-alpha.41

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 (42) hide show
  1. package/README.md +117 -0
  2. package/dist/README.md +117 -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 +1225 -258
  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 +795 -0
  9. package/package.json +37 -18
  10. package/src/flex-render/context.ts +14 -0
  11. package/src/flex-render/flags.ts +34 -0
  12. package/src/flex-render/flexRenderComponent.ts +288 -0
  13. package/src/flex-render/flexRenderComponentFactory.ts +241 -0
  14. package/src/flex-render/renderer.ts +393 -0
  15. package/src/flex-render/view.ts +207 -0
  16. package/src/flexRender.ts +124 -0
  17. package/src/helpers/cell.ts +104 -0
  18. package/src/helpers/createTableHook.ts +480 -0
  19. package/src/helpers/flexRenderCell.ts +136 -0
  20. package/src/helpers/header.ts +99 -0
  21. package/src/helpers/table.ts +87 -0
  22. package/src/index.ts +21 -70
  23. package/src/injectTable.ts +238 -0
  24. package/src/{lazy-signal-initializer.ts → lazySignalInitializer.ts} +2 -2
  25. package/static-functions/index.ts +1 -0
  26. package/static-functions/ng-package.json +6 -0
  27. package/dist/esm2022/flex-render.mjs +0 -150
  28. package/dist/esm2022/index.mjs +0 -48
  29. package/dist/esm2022/lazy-signal-initializer.mjs +0 -43
  30. package/dist/esm2022/proxy.mjs +0 -83
  31. package/dist/esm2022/tanstack-angular-table.mjs +0 -5
  32. package/dist/flex-render.d.ts +0 -31
  33. package/dist/index.d.ts +0 -5
  34. package/dist/lazy-signal-initializer.d.ts +0 -5
  35. package/dist/proxy.d.ts +0 -3
  36. package/src/__tests__/createAngularTable.test.ts +0 -95
  37. package/src/__tests__/flex-render.test.ts +0 -178
  38. package/src/__tests__/lazy-init.test.ts +0 -124
  39. package/src/__tests__/test-setup.ts +0 -12
  40. package/src/__tests__/test-utils.ts +0 -62
  41. package/src/flex-render.ts +0 -187
  42. package/src/proxy.ts +0 -97
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@tanstack/angular-table",
3
- "version": "9.0.0-alpha.4",
3
+ "version": "9.0.0-alpha.41",
4
4
  "description": "Headless UI for building powerful tables & datagrids for Angular.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/TanStack/table.git",
9
+ "url": "git+https://github.com/TanStack/table.git",
10
10
  "directory": "packages/angular-table"
11
11
  },
12
12
  "homepage": "https://tanstack.com/table",
@@ -21,13 +21,19 @@
21
21
  "datagrid"
22
22
  ],
23
23
  "type": "module",
24
- "module": "dist/esm2022/index.mjs",
25
- "types": "dist/index.d.ts",
24
+ "module": "dist/fesm2022/tanstack-angular-table.mjs",
25
+ "types": "dist/types/tanstack-angular-table.d.ts",
26
26
  "exports": {
27
27
  ".": {
28
- "types": "./dist/index.d.ts",
29
- "esm": "./dist/esm2022/index.mjs",
30
- "esm2022": "./dist/esm2022/index.mjs",
28
+ "types": "./dist/types/tanstack-angular-table.d.ts",
29
+ "default": "./dist/fesm2022/tanstack-angular-table.mjs"
30
+ },
31
+ "./static-functions": {
32
+ "types": "./dist/types/tanstack-angular-table-static-functions.d.ts",
33
+ "default": "./dist/fesm2022/tanstack-angular-table-static-functions.mjs"
34
+ },
35
+ "./flex-render": {
36
+ "types": "./dist/types/tanstack-angular-table.d.ts",
31
37
  "default": "./dist/fesm2022/tanstack-angular-table.mjs"
32
38
  },
33
39
  "./package.json": {
@@ -35,26 +41,39 @@
35
41
  }
36
42
  },
37
43
  "engines": {
38
- "node": ">=12"
44
+ "node": ">=18"
39
45
  },
40
46
  "files": [
41
47
  "dist",
42
- "src"
48
+ "src",
49
+ "static-functions"
43
50
  ],
44
51
  "dependencies": {
45
- "tslib": "^2.6.2",
46
- "@tanstack/table-core": "9.0.0-alpha.4"
52
+ "@tanstack/angular-store": "^0.11.0",
53
+ "tslib": "^2.8.1",
54
+ "@tanstack/table-core": "9.0.0-alpha.41"
47
55
  },
48
56
  "devDependencies": {
49
- "@analogjs/vite-plugin-angular": "^1.3.1",
50
- "@angular/core": "^17.3.9",
51
- "@angular/platform-browser": "^17.3.9",
52
- "@angular/platform-browser-dynamic": "^17.3.9",
53
- "ng-packagr": "^17.3.0"
57
+ "@analogjs/vite-plugin-angular": "^2.5.0",
58
+ "@analogjs/vitest-angular": "^2.5.0",
59
+ "@angular/core": "^21.2.11",
60
+ "@angular/platform-browser": "^21.2.11",
61
+ "ng-packagr": "^21.2.3",
62
+ "typescript": "6.0.3"
54
63
  },
55
64
  "peerDependencies": {
56
- "@angular/core": ">=17"
65
+ "@angular/core": ">=19"
57
66
  },
58
67
  "sideEffects": false,
59
- "scripts": {}
68
+ "scripts": {
69
+ "build": "ng-packagr -p ng-package.json -c tsconfig.build.json && rimraf ./dist/package.json",
70
+ "build:types": "tsc --emitDeclarationOnly",
71
+ "clean": "rimraf ./build && rimraf ./dist",
72
+ "test:build": "publint --strict",
73
+ "test:eslint": "eslint ./src",
74
+ "test:lib": "vitest",
75
+ "test:benchmark": "vitest bench",
76
+ "test:lib:dev": "vitest --watch",
77
+ "test:types": "tsc && vitest --typecheck"
78
+ }
60
79
  }
@@ -0,0 +1,14 @@
1
+ import { InjectionToken, inject } from '@angular/core'
2
+
3
+ export const FlexRenderComponentProps = new InjectionToken<
4
+ NonNullable<unknown>
5
+ >('[@tanstack/angular-table] Flex render component context props')
6
+
7
+ /**
8
+ * Inject the flex render context props.
9
+ *
10
+ * Can be used in components rendered via FlexRender directives.
11
+ */
12
+ export function injectFlexRenderContext<T extends NonNullable<unknown>>(): T {
13
+ return inject<T>(FlexRenderComponentProps)
14
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Flags used to manage and optimize the rendering lifecycle of the content of the cell
3
+ * while using {@link FlexViewRenderer}.
4
+ */
5
+ export const FlexRenderFlags = {
6
+ /**
7
+ * Indicates that the view is being created for the first time or will be cleared during the next update phase.
8
+ * This is the initial state and will transition after the first ngDoCheck.
9
+ */
10
+ ViewFirstRender: 1 << 0,
11
+ /**
12
+ * Indicates the `content` property has been modified or the view requires a complete re-render.
13
+ * When this flag is enabled, the view will be cleared and recreated from scratch.
14
+ */
15
+ ContentChanged: 1 << 1,
16
+ /**
17
+ * Indicates that the `props` property reference has changed.
18
+ * When this flag is enabled, the view context is updated based on the type of the content.
19
+ *
20
+ * For Component view, inputs will be updated and view will be marked as dirty.
21
+ * For TemplateRef and primitive values, view will be marked as dirty
22
+ */
23
+ PropsReferenceChanged: 1 << 2,
24
+ /**
25
+ * Indicates that the current rendered view needs to be checked for changes.
26
+ * This will be set to true when `content(props)` result has changed or during
27
+ * forced update
28
+ */
29
+ Dirty: 1 << 3,
30
+ /**
31
+ * Indicates that the first render effect has been checked at least one time.
32
+ */
33
+ RenderEffectChecked: 1 << 4,
34
+ } as const
@@ -0,0 +1,288 @@
1
+ import { reflectComponentType } from '@angular/core'
2
+ import type {
3
+ Binding,
4
+ ComponentMirror,
5
+ Injector,
6
+ InputSignal,
7
+ OutputEmitterRef,
8
+ Type,
9
+ createComponent,
10
+ } from '@angular/core'
11
+
12
+ type CreateComponentOptions = Parameters<typeof createComponent>[1]
13
+ type CreateComponentBindings = CreateComponentOptions['bindings']
14
+ type CreateComponentDirectives = CreateComponentOptions['directives']
15
+
16
+ interface FlexRenderOptions<
17
+ TInputs extends Record<string, any>,
18
+ TOutputs extends Record<string, any>,
19
+ > {
20
+ /**
21
+ * Native Angular bindings applied at component creation time via `createComponent`.
22
+ * Use this option to set inputs, outputs, or two-way bindings at creation time.
23
+ * Shouldn't be used together with {@link FlexRenderOptions#inputs} or {@link FlexRenderOptions#outputs} option.
24
+ *
25
+ * Binding input/outputs at creation time: {@link https://angular.dev/guide/components/programmatic-rendering#binding-inputs-outputs-and-setting-host-directives-at-creation}
26
+ *
27
+ * Two-way binding: {@link https://angular.dev/api/core/twoWayBinding}
28
+ *
29
+ * Output binding: {@link https://angular.dev/api/core/outputBinding}
30
+ *
31
+ * Input binding: {@link https://angular.dev/api/core/inputBinding}
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * import {flexRenderComponent} from '@tanstack/angular-table';
36
+ * flexRenderComponent(MyComponent, {
37
+ * bindings: [
38
+ * // Will update `value` input every time `mySignalValue` changes
39
+ * inputBinding('value', mySignalValue),
40
+ * // Set myProperty to 1 when the component is created
41
+ * inputBinding('myProperty', () => 1),
42
+ * // Callback called every time `valueChange` output emit
43
+ * outputBinding('valueChange', value => {
44
+ * console.log("my value changed to", value)
45
+ * }),
46
+ * // Two-way binding between `value` input and `valueChange` output
47
+ * // Useful while using `model` inputs.
48
+ * twoWayBinding('value', mySignal)
49
+ * ]
50
+ * })
51
+ * ```
52
+ */
53
+ readonly bindings?: Array<Binding>
54
+ /**
55
+ * Directives to apply to the component at creation time.
56
+ *
57
+ * Binding directives at creation time: {@link https://angular.dev/guide/components/programmatic-rendering#binding-inputs-outputs-and-setting-host-directives-at-creation}
58
+ *
59
+ * Two-way binding: {@link https://angular.dev/api/core/twoWayBinding}
60
+ *
61
+ * Output binding: {@link https://angular.dev/api/core/outputBinding}
62
+ *
63
+ * Input binding: {@link https://angular.dev/api/core/inputBinding}
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * import {flexRenderComponent} from '@tanstack/angular-table';
68
+ * flexRenderComponent(MyComponent, {
69
+ * bindings: [
70
+ * // ...
71
+ * ],
72
+ * directives: [
73
+ * DirectiveA,
74
+ * {
75
+ * type: DirectiveB,
76
+ * bindings: [
77
+ * inputBinding('value', mySignalValue),
78
+ * // ...
79
+ * ]
80
+ * }
81
+ * ]
82
+ * })
83
+ * ```
84
+ */
85
+ readonly directives?: CreateComponentDirectives
86
+ /**
87
+ * Component instance inputs.
88
+ *
89
+ * These values are assigned after the component has been created using
90
+ * [componentRef.setInput API](https://angular.dev/api/core/ComponentRef#setInput).
91
+ *
92
+ * Shouldn't be used together with {@link FlexRenderOptions#bindings} option
93
+ */
94
+ readonly inputs?: TInputs
95
+ /**
96
+ * Component instance outputs.
97
+ *
98
+ * Outputs are wired imperatively after component creation using {@link OutputEmitterRef#subscribe}.
99
+ *
100
+ * Shouldn't be used together with {@link FlexRenderOptions#bindings} option
101
+ */
102
+ readonly outputs?: TOutputs
103
+ /**
104
+ * Optional {@link Injector} that will be used when rendering the component
105
+ */
106
+ readonly injector?: Injector
107
+ }
108
+
109
+ type Inputs<T> = {
110
+ [K in keyof T as T[K] extends InputSignal<infer R>
111
+ ? K
112
+ : never]?: T[K] extends InputSignal<infer R> ? R : never
113
+ }
114
+
115
+ type Outputs<T> = {
116
+ [K in keyof T as T[K] extends OutputEmitterRef<infer R>
117
+ ? K
118
+ : never]?: T[K] extends OutputEmitterRef<infer R>
119
+ ? OutputEmitterRef<R>['emit']
120
+ : never
121
+ }
122
+
123
+ /**
124
+ * Helper function to create a {@link FlexRenderComponent} instance, with better type-safety.
125
+ *
126
+ * @example
127
+ * ```ts
128
+ * import {flexRenderComponent} from '@tanstack/angular-table'
129
+ * import {inputBinding, outputBinding} from '@angular/core';
130
+ *
131
+ * const columns = [
132
+ * {
133
+ * cell: ({ row }) => {
134
+ * return flexRenderComponent(MyComponent, {
135
+ * inputs: { value: mySignalValue() },
136
+ * outputs: { valueChange: (val) => {} }
137
+ * // or using angular native createComponent#binding api
138
+ * bindings: [
139
+ * inputBinding('value', mySignalValue),
140
+ * outputBinding('valueChange', value => {
141
+ * console.log("my value changed to", value)
142
+ * })
143
+ * ]
144
+ * })
145
+ * },
146
+ * },
147
+ * ]
148
+ * ```
149
+ */
150
+ export function flexRenderComponent<TComponent = any>(
151
+ component: Type<TComponent>,
152
+ options?: FlexRenderOptions<Inputs<TComponent>, Outputs<TComponent>>,
153
+ ): FlexRenderComponent<TComponent> {
154
+ const { inputs, injector, outputs, directives, bindings } = options ?? {}
155
+ return new FlexRenderComponentInstance(
156
+ component,
157
+ inputs,
158
+ injector,
159
+ outputs,
160
+ directives,
161
+ bindings,
162
+ )
163
+ }
164
+
165
+ /**
166
+ * Wrapper interface for a component that will be used as content for {@link FlexRenderDirective}.
167
+ * Can be created using {@link flexRenderComponent} helper.
168
+ *
169
+ * @example
170
+ *
171
+ * ```ts
172
+ * import {flexRenderComponent} from '@tanstack/angular-table'
173
+ *
174
+ * // Usage in cell/header/footer definition
175
+ * const columns = [
176
+ * {
177
+ * cell: ({ row }) => {
178
+ * return flexRenderComponent(MyComponent, {
179
+ * inputs: { value: mySignalValue() },
180
+ * outputs: { valueChange: (val) => {} }
181
+ * // or using angular createComponent#bindings api
182
+ * bindings: [
183
+ * inputBinding('value', mySignalValue),
184
+ * outputBinding('valueChange', value => {
185
+ * console.log("my value changed to", value)
186
+ * })
187
+ * ]
188
+ * })
189
+ * },
190
+ * },
191
+ * ]
192
+ *
193
+ * import {input, output} from '@angular/core';
194
+ *
195
+ * @Component({
196
+ * selector: 'my-component',
197
+ * })
198
+ * class MyComponent {
199
+ * readonly value = input(0);
200
+ * readonly valueChange = output<number>();
201
+ * }
202
+ *
203
+ * ```
204
+ */
205
+ export interface FlexRenderComponent<TComponent = any> {
206
+ /**
207
+ * The component type
208
+ */
209
+ readonly component: Type<TComponent>
210
+ /**
211
+ * Reflected metadata about the component.
212
+ */
213
+ readonly mirror: ComponentMirror<TComponent>
214
+ /**
215
+ * List of allowed input names.
216
+ */
217
+ readonly allowedInputNames: Array<string>
218
+ /**
219
+ * List of allowed output names.
220
+ */
221
+ readonly allowedOutputNames: Array<string>
222
+ /**
223
+ * Component instance outputs. Subscribed via {@link OutputEmitterRef#subscribe}
224
+ *
225
+ * @see {@link FlexRenderOptions#outputs}
226
+ */
227
+ readonly outputs?: Outputs<TComponent>
228
+ /**
229
+ * Component instance inputs. Set via [componentRef.setInput API](https://angular.dev/api/core/ComponentRef#setInput))
230
+ *
231
+ * @see {@link FlexRenderOptions#inputs}
232
+ */
233
+ readonly inputs?: Inputs<TComponent>
234
+ /**
235
+ * Optional {@link Injector} that will be used when rendering the component.
236
+ *
237
+ * @see {@link FlexRenderOptions#injector}
238
+ */
239
+ readonly injector?: Injector
240
+ /**
241
+ * Bindings to apply to the root component
242
+ *
243
+ * @see {@link FlexRenderOptions#bindings}
244
+ */
245
+ bindings?: CreateComponentBindings
246
+ /**
247
+ * Directives that should be applied to the component.
248
+ *
249
+ * @see {FlexRenderOptions#directives}
250
+ */
251
+ directives?: CreateComponentDirectives
252
+ }
253
+
254
+ /**
255
+ * Wrapper class for a component that will be used as content for {@link FlexRenderDirective}
256
+ *
257
+ * Prefer {@link flexRenderComponent} helper for better type-safety
258
+ */
259
+ export class FlexRenderComponentInstance<
260
+ TComponent = any,
261
+ > implements FlexRenderComponent<TComponent> {
262
+ readonly mirror: ComponentMirror<TComponent>
263
+ readonly allowedInputNames: Array<string> = []
264
+ readonly allowedOutputNames: Array<string> = []
265
+
266
+ constructor(
267
+ readonly component: Type<TComponent>,
268
+ readonly inputs?: Inputs<TComponent>,
269
+ readonly injector?: Injector,
270
+ readonly outputs?: Outputs<TComponent>,
271
+ readonly directives?: CreateComponentDirectives,
272
+ readonly bindings?: CreateComponentBindings,
273
+ ) {
274
+ const mirror = reflectComponentType(component)
275
+ if (!mirror) {
276
+ throw new Error(
277
+ `[@tanstack-table/angular] The provided symbol is not a component`,
278
+ )
279
+ }
280
+ this.mirror = mirror
281
+ for (const input of this.mirror.inputs) {
282
+ this.allowedInputNames.push(input.propName)
283
+ }
284
+ for (const output of this.mirror.outputs) {
285
+ this.allowedOutputNames.push(output.propName)
286
+ }
287
+ }
288
+ }
@@ -0,0 +1,241 @@
1
+ import {
2
+ ChangeDetectorRef,
3
+ ComponentRef,
4
+ Injectable,
5
+ Injector,
6
+ KeyValueDiffer,
7
+ KeyValueDiffers,
8
+ OutputEmitterRef,
9
+ OutputRefSubscription,
10
+ ViewContainerRef,
11
+ } from '@angular/core'
12
+ import { FlexRenderComponent } from './flexRenderComponent'
13
+
14
+ @Injectable()
15
+ export class FlexRenderComponentFactory {
16
+ readonly #viewContainerRef: ViewContainerRef
17
+
18
+ constructor(viewContainerRef: ViewContainerRef) {
19
+ this.#viewContainerRef = viewContainerRef
20
+ }
21
+
22
+ createComponent<T>(
23
+ flexRenderComponent: FlexRenderComponent<T>,
24
+ componentInjector: Injector,
25
+ ): FlexRenderComponentRef<T> {
26
+ const componentRef = this.#viewContainerRef.createComponent(
27
+ flexRenderComponent.component,
28
+ {
29
+ injector: componentInjector,
30
+ directives: flexRenderComponent.directives,
31
+ bindings: flexRenderComponent.bindings ?? [],
32
+ },
33
+ )
34
+ const view = new FlexRenderComponentRef(
35
+ componentRef,
36
+ flexRenderComponent,
37
+ componentInjector,
38
+ )
39
+
40
+ const { inputs, outputs } = flexRenderComponent
41
+
42
+ if (inputs) view.setInputs(inputs)
43
+ if (outputs) view.setOutputs(outputs)
44
+
45
+ return view
46
+ }
47
+ }
48
+
49
+ export class FlexRenderComponentRef<T> {
50
+ readonly #keyValueDiffersFactory: KeyValueDiffers
51
+ #componentData: FlexRenderComponent<T>
52
+ #inputValueDiffer: KeyValueDiffer<string, unknown>
53
+
54
+ readonly #outputRegistry: FlexRenderComponentOutputManager
55
+
56
+ constructor(
57
+ readonly componentRef: ComponentRef<T>,
58
+ componentData: FlexRenderComponent<T>,
59
+ readonly componentInjector: Injector,
60
+ ) {
61
+ this.#componentData = componentData
62
+ this.#keyValueDiffersFactory = componentInjector.get(KeyValueDiffers)
63
+
64
+ this.#outputRegistry = new FlexRenderComponentOutputManager(
65
+ this.#keyValueDiffersFactory,
66
+ this.outputs,
67
+ )
68
+
69
+ this.#inputValueDiffer = this.#keyValueDiffersFactory
70
+ .find(this.inputs)
71
+ .create()
72
+ this.#inputValueDiffer.diff(this.inputs)
73
+
74
+ this.componentRef.onDestroy(() => this.#outputRegistry.unsubscribeAll())
75
+ }
76
+
77
+ get component() {
78
+ return this.#componentData.component
79
+ }
80
+
81
+ get inputs() {
82
+ return this.#componentData.inputs ?? {}
83
+ }
84
+
85
+ get outputs() {
86
+ return this.#componentData.outputs ?? {}
87
+ }
88
+
89
+ /**
90
+ * Get component input and output diff by the given item
91
+ */
92
+ diff(item: FlexRenderComponent<T>) {
93
+ return {
94
+ inputDiff: this.#inputValueDiffer.diff(item.inputs ?? {}),
95
+ outputDiff: this.#outputRegistry.diff(item.outputs ?? {}),
96
+ }
97
+ }
98
+ /**
99
+ *
100
+ * @param compare Whether the current ref component instance is the same as the given one
101
+ */
102
+ eqType(compare: FlexRenderComponent<T>): boolean {
103
+ return compare.component === this.component
104
+ }
105
+
106
+ /**
107
+ * Tries to update current component refs input by the new given content component.
108
+ */
109
+ update(content: FlexRenderComponent<T>) {
110
+ const eq = this.eqType(content)
111
+ if (!eq) return
112
+ const { inputDiff, outputDiff } = this.diff(content)
113
+ if (inputDiff) {
114
+ inputDiff.forEachAddedItem((item) =>
115
+ this.setInput(item.key, item.currentValue),
116
+ )
117
+ inputDiff.forEachChangedItem((item) =>
118
+ this.setInput(item.key, item.currentValue),
119
+ )
120
+ inputDiff.forEachRemovedItem((item) => this.setInput(item.key, undefined))
121
+ }
122
+ if (outputDiff) {
123
+ outputDiff.forEachAddedItem((item) => {
124
+ this.setOutput(item.key, item.currentValue)
125
+ })
126
+ outputDiff.forEachChangedItem((item) => {
127
+ if (item.currentValue) {
128
+ this.#outputRegistry.setListener(item.key, item.currentValue)
129
+ } else {
130
+ this.#outputRegistry.unsubscribe(item.key)
131
+ }
132
+ })
133
+ outputDiff.forEachRemovedItem((item) => {
134
+ this.#outputRegistry.unsubscribe(item.key)
135
+ })
136
+ }
137
+
138
+ this.#componentData = content
139
+ }
140
+
141
+ markAsDirty(): void {
142
+ this.componentRef.injector.get(ChangeDetectorRef).markForCheck()
143
+ }
144
+
145
+ setInputs(inputs: Record<string, unknown>) {
146
+ for (const prop in inputs) {
147
+ this.setInput(prop, inputs[prop])
148
+ }
149
+ }
150
+
151
+ setInput(key: string, value: unknown) {
152
+ if (this.#componentData.allowedInputNames.includes(key)) {
153
+ this.componentRef.setInput(key, value)
154
+ }
155
+ }
156
+
157
+ setOutputs(
158
+ outputs: Record<
159
+ string,
160
+ OutputEmitterRef<unknown>['emit'] | null | undefined
161
+ >,
162
+ ) {
163
+ this.#outputRegistry.unsubscribeAll()
164
+ for (const prop in outputs) {
165
+ this.setOutput(prop, outputs[prop])
166
+ }
167
+ }
168
+
169
+ setOutput(
170
+ outputName: string,
171
+ emit: OutputEmitterRef<unknown>['emit'] | undefined | null,
172
+ ): void {
173
+ if (!this.#componentData.allowedOutputNames.includes(outputName)) return
174
+ if (!emit) {
175
+ this.#outputRegistry.unsubscribe(outputName)
176
+ return
177
+ }
178
+
179
+ const hasListener = this.#outputRegistry.hasListener(outputName)
180
+ this.#outputRegistry.setListener(outputName, emit)
181
+
182
+ if (hasListener) {
183
+ return
184
+ }
185
+
186
+ const instance = this.componentRef.instance
187
+ const output = instance[outputName as keyof typeof instance]
188
+ if (output && output instanceof OutputEmitterRef) {
189
+ output.subscribe((value) => {
190
+ this.#outputRegistry.getListener(outputName)?.(value)
191
+ })
192
+ }
193
+ }
194
+ }
195
+
196
+ class FlexRenderComponentOutputManager {
197
+ readonly #outputSubscribers: Record<string, OutputRefSubscription> = {}
198
+ readonly #outputListeners: Record<string, (...args: Array<any>) => void> = {}
199
+
200
+ readonly #valueDiffer: KeyValueDiffer<
201
+ string,
202
+ undefined | null | OutputEmitterRef<unknown>['emit']
203
+ >
204
+
205
+ constructor(keyValueDiffers: KeyValueDiffers, initialOutputs: any) {
206
+ this.#valueDiffer = keyValueDiffers.find(initialOutputs).create()
207
+ if (initialOutputs) {
208
+ this.#valueDiffer.diff(initialOutputs)
209
+ }
210
+ }
211
+
212
+ hasListener(outputName: string) {
213
+ return outputName in this.#outputListeners
214
+ }
215
+
216
+ setListener(outputName: string, callback: (...args: Array<any>) => void) {
217
+ this.#outputListeners[outputName] = callback
218
+ }
219
+
220
+ getListener(outputName: string) {
221
+ return this.#outputListeners[outputName]
222
+ }
223
+
224
+ unsubscribeAll(): void {
225
+ for (const prop in this.#outputSubscribers) {
226
+ this.unsubscribe(prop)
227
+ }
228
+ }
229
+
230
+ unsubscribe(outputName: string) {
231
+ if (outputName in this.#outputSubscribers) {
232
+ this.#outputSubscribers[outputName]?.unsubscribe()
233
+ delete this.#outputSubscribers[outputName]
234
+ delete this.#outputListeners[outputName]
235
+ }
236
+ }
237
+
238
+ diff(outputs: Record<string, OutputEmitterRef<unknown>['emit'] | undefined>) {
239
+ return this.#valueDiffer.diff(outputs)
240
+ }
241
+ }