@tanstack/angular-table 8.20.5 → 8.21.2
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/build/lib/esm2022/flex-render/context.mjs +6 -0
- package/build/lib/esm2022/flex-render/flags.mjs +42 -0
- package/build/lib/esm2022/flex-render/flex-render-component-ref.mjs +173 -0
- package/build/lib/esm2022/flex-render/flex-render-component.mjs +44 -0
- package/build/lib/esm2022/flex-render/view.mjs +102 -0
- package/build/lib/esm2022/flex-render.mjs +133 -73
- package/build/lib/esm2022/index.mjs +3 -2
- package/build/lib/esm2022/proxy.mjs +7 -3
- package/build/lib/fesm2022/tanstack-angular-table.mjs +494 -76
- package/build/lib/fesm2022/tanstack-angular-table.mjs.map +1 -1
- package/build/lib/flex-render/context.d.ts +3 -0
- package/build/lib/flex-render/flags.d.ts +40 -0
- package/build/lib/flex-render/flex-render-component-ref.d.ts +39 -0
- package/build/lib/flex-render/flex-render-component.d.ts +60 -0
- package/build/lib/flex-render/view.d.ts +49 -0
- package/build/lib/flex-render.d.ts +13 -17
- package/build/lib/index.d.ts +2 -1
- package/package.json +7 -4
- package/src/flex-render/context.ts +9 -0
- package/src/flex-render/flags.ts +40 -0
- package/src/flex-render/flex-render-component-ref.ts +236 -0
- package/src/flex-render/flex-render-component.ts +119 -0
- package/src/flex-render/view.ts +153 -0
- package/src/flex-render.ts +196 -94
- package/src/index.ts +7 -1
- package/src/proxy.ts +5 -2
package/src/flex-render.ts
CHANGED
|
@@ -1,20 +1,40 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ChangeDetectorRef,
|
|
3
|
-
ComponentRef,
|
|
4
3
|
Directive,
|
|
5
|
-
|
|
4
|
+
DoCheck,
|
|
5
|
+
effect,
|
|
6
|
+
type EffectRef,
|
|
6
7
|
Inject,
|
|
7
8
|
inject,
|
|
8
|
-
InjectionToken,
|
|
9
9
|
Injector,
|
|
10
10
|
Input,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
OnChanges,
|
|
12
|
+
runInInjectionContext,
|
|
13
|
+
SimpleChanges,
|
|
14
14
|
TemplateRef,
|
|
15
15
|
Type,
|
|
16
16
|
ViewContainerRef,
|
|
17
17
|
} from '@angular/core'
|
|
18
|
+
import { FlexRenderComponentProps } from './flex-render/context'
|
|
19
|
+
import { FlexRenderFlags } from './flex-render/flags'
|
|
20
|
+
import {
|
|
21
|
+
flexRenderComponent,
|
|
22
|
+
FlexRenderComponent,
|
|
23
|
+
} from './flex-render/flex-render-component'
|
|
24
|
+
import { FlexRenderComponentFactory } from './flex-render/flex-render-component-ref'
|
|
25
|
+
import {
|
|
26
|
+
FlexRenderComponentView,
|
|
27
|
+
FlexRenderTemplateView,
|
|
28
|
+
type FlexRenderTypedContent,
|
|
29
|
+
FlexRenderView,
|
|
30
|
+
mapToFlexRenderTypedContent,
|
|
31
|
+
} from './flex-render/view'
|
|
32
|
+
import { memo } from '@tanstack/table-core'
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
injectFlexRenderContext,
|
|
36
|
+
type FlexRenderComponentProps,
|
|
37
|
+
} from './flex-render/context'
|
|
18
38
|
|
|
19
39
|
export type FlexRenderContent<TProps extends NonNullable<unknown>> =
|
|
20
40
|
| string
|
|
@@ -23,15 +43,20 @@ export type FlexRenderContent<TProps extends NonNullable<unknown>> =
|
|
|
23
43
|
| FlexRenderComponent<TProps>
|
|
24
44
|
| TemplateRef<{ $implicit: TProps }>
|
|
25
45
|
| null
|
|
46
|
+
| Record<any, any>
|
|
26
47
|
| undefined
|
|
27
48
|
|
|
28
49
|
@Directive({
|
|
29
50
|
selector: '[flexRender]',
|
|
30
51
|
standalone: true,
|
|
52
|
+
providers: [FlexRenderComponentFactory],
|
|
31
53
|
})
|
|
32
54
|
export class FlexRenderDirective<TProps extends NonNullable<unknown>>
|
|
33
|
-
implements OnChanges
|
|
55
|
+
implements OnChanges, DoCheck
|
|
34
56
|
{
|
|
57
|
+
readonly #flexRenderComponentFactory = inject(FlexRenderComponentFactory)
|
|
58
|
+
readonly #changeDetectorRef = inject(ChangeDetectorRef)
|
|
59
|
+
|
|
35
60
|
@Input({ required: true, alias: 'flexRender' })
|
|
36
61
|
content:
|
|
37
62
|
| number
|
|
@@ -46,6 +71,24 @@ export class FlexRenderDirective<TProps extends NonNullable<unknown>>
|
|
|
46
71
|
@Input({ required: false, alias: 'flexRenderInjector' })
|
|
47
72
|
injector: Injector = inject(Injector)
|
|
48
73
|
|
|
74
|
+
renderFlags = FlexRenderFlags.ViewFirstRender
|
|
75
|
+
renderView: FlexRenderView<any> | null = null
|
|
76
|
+
|
|
77
|
+
readonly #latestContent = () => {
|
|
78
|
+
const { content, props } = this
|
|
79
|
+
return typeof content !== 'function'
|
|
80
|
+
? content
|
|
81
|
+
: runInInjectionContext(this.injector, () => content(props))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#getContentValue = memo(
|
|
85
|
+
() => [this.#latestContent(), this.props, this.content],
|
|
86
|
+
latestContent => {
|
|
87
|
+
return mapToFlexRenderTypedContent(latestContent)
|
|
88
|
+
},
|
|
89
|
+
{ key: 'flexRenderContentValue', debug: () => false }
|
|
90
|
+
)
|
|
91
|
+
|
|
49
92
|
constructor(
|
|
50
93
|
@Inject(ViewContainerRef)
|
|
51
94
|
private readonly viewContainerRef: ViewContainerRef,
|
|
@@ -53,132 +96,191 @@ export class FlexRenderDirective<TProps extends NonNullable<unknown>>
|
|
|
53
96
|
private readonly templateRef: TemplateRef<any>
|
|
54
97
|
) {}
|
|
55
98
|
|
|
56
|
-
ref?: ComponentRef<unknown> | EmbeddedViewRef<unknown> | null = null
|
|
57
|
-
|
|
58
99
|
ngOnChanges(changes: SimpleChanges) {
|
|
59
|
-
if (
|
|
60
|
-
this.
|
|
100
|
+
if (changes['props']) {
|
|
101
|
+
this.renderFlags |= FlexRenderFlags.PropsReferenceChanged
|
|
61
102
|
}
|
|
62
|
-
if (
|
|
63
|
-
|
|
103
|
+
if (changes['content']) {
|
|
104
|
+
this.renderFlags |=
|
|
105
|
+
FlexRenderFlags.ContentChanged | FlexRenderFlags.ViewFirstRender
|
|
106
|
+
this.update()
|
|
64
107
|
}
|
|
65
|
-
this.render()
|
|
66
108
|
}
|
|
67
109
|
|
|
68
|
-
|
|
69
|
-
this.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.
|
|
110
|
+
ngDoCheck(): void {
|
|
111
|
+
if (this.renderFlags & FlexRenderFlags.ViewFirstRender) {
|
|
112
|
+
// On the initial render, the view is created during the `ngOnChanges` hook.
|
|
113
|
+
// Since `ngDoCheck` is called immediately afterward, there's no need to check for changes in this phase.
|
|
114
|
+
this.renderFlags &= ~FlexRenderFlags.ViewFirstRender
|
|
73
115
|
return
|
|
74
116
|
}
|
|
75
|
-
|
|
76
|
-
|
|
117
|
+
|
|
118
|
+
this.renderFlags |= FlexRenderFlags.DirtyCheck
|
|
119
|
+
|
|
120
|
+
const latestContent = this.#getContentValue()
|
|
121
|
+
if (latestContent.kind === 'null' || !this.renderView) {
|
|
122
|
+
this.renderFlags |= FlexRenderFlags.ContentChanged
|
|
77
123
|
} else {
|
|
78
|
-
|
|
124
|
+
this.renderView.content = latestContent
|
|
125
|
+
const { kind: previousKind } = this.renderView.previousContent
|
|
126
|
+
if (latestContent.kind !== previousKind) {
|
|
127
|
+
this.renderFlags |= FlexRenderFlags.ContentChanged
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
this.update()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
update() {
|
|
134
|
+
if (
|
|
135
|
+
this.renderFlags &
|
|
136
|
+
(FlexRenderFlags.ContentChanged | FlexRenderFlags.ViewFirstRender)
|
|
137
|
+
) {
|
|
138
|
+
this.render()
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
if (this.renderFlags & FlexRenderFlags.PropsReferenceChanged) {
|
|
142
|
+
if (this.renderView) this.renderView.updateProps(this.props)
|
|
143
|
+
this.renderFlags &= ~FlexRenderFlags.PropsReferenceChanged
|
|
144
|
+
}
|
|
145
|
+
if (
|
|
146
|
+
this.renderFlags &
|
|
147
|
+
(FlexRenderFlags.DirtyCheck | FlexRenderFlags.DirtySignal)
|
|
148
|
+
) {
|
|
149
|
+
if (this.renderView) this.renderView.dirtyCheck()
|
|
150
|
+
this.renderFlags &= ~(
|
|
151
|
+
FlexRenderFlags.DirtyCheck | FlexRenderFlags.DirtySignal
|
|
152
|
+
)
|
|
79
153
|
}
|
|
80
154
|
}
|
|
81
155
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
156
|
+
#currentEffectRef: EffectRef | null = null
|
|
157
|
+
|
|
158
|
+
render() {
|
|
159
|
+
if (this.#shouldRecreateEntireView() && this.#currentEffectRef) {
|
|
160
|
+
this.#currentEffectRef.destroy()
|
|
161
|
+
this.#currentEffectRef = null
|
|
162
|
+
this.renderFlags &= ~FlexRenderFlags.RenderEffectChecked
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
this.viewContainerRef.clear()
|
|
166
|
+
this.renderFlags =
|
|
167
|
+
FlexRenderFlags.Pristine |
|
|
168
|
+
(this.renderFlags & FlexRenderFlags.ViewFirstRender) |
|
|
169
|
+
(this.renderFlags & FlexRenderFlags.RenderEffectChecked)
|
|
170
|
+
|
|
171
|
+
const resolvedContent = this.#getContentValue()
|
|
172
|
+
if (resolvedContent.kind === 'null') {
|
|
173
|
+
this.renderView = null
|
|
174
|
+
} else {
|
|
175
|
+
this.renderView = this.#renderViewByContent(resolvedContent)
|
|
85
176
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
177
|
+
|
|
178
|
+
// If the content is a function `content(props)`, we initialize an effect
|
|
179
|
+
// in order to react to changes if the given definition use signals.
|
|
180
|
+
if (!this.#currentEffectRef && typeof this.content === 'function') {
|
|
181
|
+
this.#currentEffectRef = effect(
|
|
182
|
+
() => {
|
|
183
|
+
this.#latestContent()
|
|
184
|
+
if (!(this.renderFlags & FlexRenderFlags.RenderEffectChecked)) {
|
|
185
|
+
this.renderFlags |= FlexRenderFlags.RenderEffectChecked
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
this.renderFlags |= FlexRenderFlags.DirtySignal
|
|
189
|
+
// This will mark the view as changed,
|
|
190
|
+
// so we'll try to check for updates into ngDoCheck
|
|
191
|
+
this.#changeDetectorRef.markForCheck()
|
|
192
|
+
},
|
|
193
|
+
{ injector: this.viewContainerRef.injector }
|
|
90
194
|
)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
#shouldRecreateEntireView() {
|
|
199
|
+
return (
|
|
200
|
+
this.renderFlags &
|
|
201
|
+
FlexRenderFlags.ContentChanged &
|
|
202
|
+
FlexRenderFlags.ViewFirstRender
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
#renderViewByContent(
|
|
207
|
+
content: FlexRenderTypedContent
|
|
208
|
+
): FlexRenderView<any> | null {
|
|
209
|
+
if (content.kind === 'primitive') {
|
|
210
|
+
return this.#renderStringContent(content)
|
|
211
|
+
} else if (content.kind === 'templateRef') {
|
|
212
|
+
return this.#renderTemplateRefContent(content)
|
|
213
|
+
} else if (content.kind === 'flexRenderComponent') {
|
|
214
|
+
return this.#renderComponent(content)
|
|
215
|
+
} else if (content.kind === 'component') {
|
|
216
|
+
return this.#renderCustomComponent(content)
|
|
95
217
|
} else {
|
|
96
218
|
return null
|
|
97
219
|
}
|
|
98
220
|
}
|
|
99
221
|
|
|
100
|
-
|
|
222
|
+
#renderStringContent(
|
|
223
|
+
template: Extract<FlexRenderTypedContent, { kind: 'primitive' }>
|
|
224
|
+
): FlexRenderTemplateView {
|
|
101
225
|
const context = () => {
|
|
102
226
|
return typeof this.content === 'string' ||
|
|
103
227
|
typeof this.content === 'number'
|
|
104
228
|
? this.content
|
|
105
229
|
: this.content?.(this.props)
|
|
106
230
|
}
|
|
107
|
-
|
|
231
|
+
const ref = this.viewContainerRef.createEmbeddedView(this.templateRef, {
|
|
108
232
|
get $implicit() {
|
|
109
233
|
return context()
|
|
110
234
|
},
|
|
111
235
|
})
|
|
236
|
+
return new FlexRenderTemplateView(template, ref)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
#renderTemplateRefContent(
|
|
240
|
+
template: Extract<FlexRenderTypedContent, { kind: 'templateRef' }>
|
|
241
|
+
): FlexRenderTemplateView {
|
|
242
|
+
const latestContext = () => this.props
|
|
243
|
+
const view = this.viewContainerRef.createEmbeddedView(template.content, {
|
|
244
|
+
get $implicit() {
|
|
245
|
+
return latestContext()
|
|
246
|
+
},
|
|
247
|
+
})
|
|
248
|
+
return new FlexRenderTemplateView(template, view)
|
|
112
249
|
}
|
|
113
250
|
|
|
114
|
-
|
|
115
|
-
flexRenderComponent:
|
|
116
|
-
|
|
117
|
-
|
|
251
|
+
#renderComponent(
|
|
252
|
+
flexRenderComponent: Extract<
|
|
253
|
+
FlexRenderTypedContent,
|
|
254
|
+
{ kind: 'flexRenderComponent' }
|
|
255
|
+
>
|
|
256
|
+
): FlexRenderComponentView {
|
|
257
|
+
const { inputs, outputs, injector } = flexRenderComponent.content
|
|
118
258
|
|
|
119
259
|
const getContext = () => this.props
|
|
120
|
-
|
|
121
260
|
const proxy = new Proxy(this.props, {
|
|
122
|
-
get: (_, key) => getContext()
|
|
261
|
+
get: (_, key) => getContext()[key as keyof typeof _],
|
|
123
262
|
})
|
|
124
|
-
|
|
125
263
|
const componentInjector = Injector.create({
|
|
126
264
|
parent: injector ?? this.injector,
|
|
127
265
|
providers: [{ provide: FlexRenderComponentProps, useValue: proxy }],
|
|
128
266
|
})
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (componentRef.instance?.hasOwnProperty(prop)) {
|
|
135
|
-
componentRef.setInput(prop, inputs[prop])
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return componentRef
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
private renderCustomComponent(
|
|
142
|
-
component: Type<unknown>
|
|
143
|
-
): ComponentRef<unknown> {
|
|
144
|
-
const componentRef = this.viewContainerRef.createComponent(component, {
|
|
145
|
-
injector: this.injector,
|
|
146
|
-
})
|
|
147
|
-
for (const prop in this.props) {
|
|
148
|
-
// Only signal based input can be added here
|
|
149
|
-
if (
|
|
150
|
-
componentRef.instance?.hasOwnProperty(prop) &&
|
|
151
|
-
// @ts-ignore
|
|
152
|
-
isSignal(componentRef.instance[prop])
|
|
153
|
-
) {
|
|
154
|
-
componentRef.setInput(prop, this.props[prop])
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return componentRef
|
|
267
|
+
const view = this.#flexRenderComponentFactory.createComponent(
|
|
268
|
+
flexRenderComponent.content,
|
|
269
|
+
componentInjector
|
|
270
|
+
)
|
|
271
|
+
return new FlexRenderComponentView(flexRenderComponent, view)
|
|
158
272
|
}
|
|
159
273
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
274
|
+
#renderCustomComponent(
|
|
275
|
+
component: Extract<FlexRenderTypedContent, { kind: 'component' }>
|
|
276
|
+
): FlexRenderComponentView {
|
|
277
|
+
const view = this.#flexRenderComponentFactory.createComponent(
|
|
278
|
+
flexRenderComponent(component.content, {
|
|
279
|
+
inputs: this.props,
|
|
280
|
+
injector: this.injector,
|
|
281
|
+
}),
|
|
282
|
+
this.injector
|
|
283
|
+
)
|
|
284
|
+
return new FlexRenderComponentView(component, view)
|
|
167
285
|
}
|
|
168
286
|
}
|
|
169
|
-
|
|
170
|
-
export class FlexRenderComponent<T extends NonNullable<unknown>> {
|
|
171
|
-
constructor(
|
|
172
|
-
readonly component: Type<unknown>,
|
|
173
|
-
readonly inputs: T = {} as T,
|
|
174
|
-
readonly injector?: Injector
|
|
175
|
-
) {}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const FlexRenderComponentProps = new InjectionToken<NonNullable<unknown>>(
|
|
179
|
-
'[@tanstack/angular-table] Flex render component context props'
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
export function injectFlexRenderContext<T extends NonNullable<unknown>>(): T {
|
|
183
|
-
return inject<T>(FlexRenderComponentProps)
|
|
184
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -14,11 +14,17 @@ export * from '@tanstack/table-core'
|
|
|
14
14
|
|
|
15
15
|
export {
|
|
16
16
|
type FlexRenderContent,
|
|
17
|
-
FlexRenderComponent,
|
|
18
17
|
FlexRenderDirective,
|
|
18
|
+
FlexRenderDirective as FlexRender,
|
|
19
19
|
injectFlexRenderContext,
|
|
20
|
+
type FlexRenderComponentProps,
|
|
20
21
|
} from './flex-render'
|
|
21
22
|
|
|
23
|
+
export {
|
|
24
|
+
FlexRenderComponent,
|
|
25
|
+
flexRenderComponent,
|
|
26
|
+
} from './flex-render/flex-render-component'
|
|
27
|
+
|
|
22
28
|
export function createAngularTable<TData extends RowData>(
|
|
23
29
|
options: () => TableOptions<TData>
|
|
24
30
|
): Table<TData> & Signal<Table<TData>> {
|
package/src/proxy.ts
CHANGED
|
@@ -23,8 +23,11 @@ export function proxifyTable<T>(
|
|
|
23
23
|
*/
|
|
24
24
|
if (
|
|
25
25
|
property.startsWith('get') &&
|
|
26
|
-
!property.endsWith('Handler')
|
|
27
|
-
|
|
26
|
+
!property.endsWith('Handler')
|
|
27
|
+
// e.g. getCoreRowModel, getSelectedRowModel etc.
|
|
28
|
+
// We need that after a signal change even `rowModel` may mark the view as dirty.
|
|
29
|
+
// This allows to always get the latest `getContext` value while using flexRender
|
|
30
|
+
// && !property.endsWith('Model')
|
|
28
31
|
) {
|
|
29
32
|
const maybeFn = table[property] as Function | never
|
|
30
33
|
if (typeof maybeFn === 'function') {
|