@tanstack/angular-table 9.0.0-alpha.5 → 9.0.0-alpha.51
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/README.md +127 -0
- package/dist/README.md +127 -0
- package/dist/fesm2022/tanstack-angular-table-static-functions.mjs +6 -0
- package/dist/fesm2022/tanstack-angular-table-static-functions.mjs.map +1 -0
- package/dist/fesm2022/tanstack-angular-table.mjs +1357 -242
- package/dist/fesm2022/tanstack-angular-table.mjs.map +1 -1
- package/dist/types/tanstack-angular-table-static-functions.d.ts +1 -0
- package/dist/types/tanstack-angular-table.d.ts +793 -0
- package/package.json +39 -19
- package/skills/angular/angular-rendering-directives/SKILL.md +415 -0
- package/skills/angular/angular-rendering-directives/references/content-shapes.md +142 -0
- package/skills/angular/angular-rendering-directives/references/create-table-hook-registries.md +89 -0
- package/skills/angular/angular-rendering-directives/references/di-tokens.md +171 -0
- package/skills/angular/angular-rendering-directives/references/flex-render-component-options.md +64 -0
- package/skills/angular/client-to-server/SKILL.md +467 -0
- package/skills/angular/compose-with-tanstack-query/SKILL.md +482 -0
- package/skills/angular/compose-with-tanstack-store/SKILL.md +397 -0
- package/skills/angular/compose-with-tanstack-virtual/SKILL.md +400 -0
- package/skills/angular/getting-started/SKILL.md +496 -0
- package/skills/angular/getting-started/references/feature-row-model-mapping.md +48 -0
- package/skills/angular/migrate-v8-to-v9/SKILL.md +419 -0
- package/skills/angular/migrate-v8-to-v9/references/v8-to-v9-mapping.md +261 -0
- package/skills/angular/production-readiness/SKILL.md +469 -0
- package/skills/angular/table-state/SKILL.md +429 -0
- package/skills/angular/table-state/references/external-atoms-and-app-hook.md +152 -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 +251 -0
- package/src/flex-render/renderer.ts +393 -0
- package/src/flex-render/view.ts +226 -0
- package/src/flexRender.ts +124 -0
- package/src/helpers/cell.ts +104 -0
- package/src/helpers/createTableHook.ts +499 -0
- package/src/helpers/flexRenderCell.ts +136 -0
- package/src/helpers/header.ts +99 -0
- package/src/helpers/table.ts +85 -0
- package/src/index.ts +23 -70
- package/src/injectTable.ts +206 -0
- package/src/{lazy-signal-initializer.ts → lazySignalInitializer.ts} +2 -2
- package/src/reactivity.ts +147 -0
- package/static-functions/index.ts +1 -0
- package/static-functions/ng-package.json +6 -0
- package/dist/esm2022/flex-render.mjs +0 -150
- 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 -31
- 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 -178
- 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 -187
- package/src/proxy.ts +0 -97
|
@@ -1,11 +1,1079 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { InjectionToken, input, inject, Directive, reflectComponentType, Injectable, KeyValueDiffers, ChangeDetectorRef, OutputEmitterRef, TemplateRef, Type, runInInjectionContext, computed, effect, untracked, Injector, ViewContainerRef, DestroyRef, NgZone, signal, assertInInjectionContext } from '@angular/core';
|
|
3
|
+
import { constructTable, createColumnHelper } from '@tanstack/table-core';
|
|
4
4
|
export * from '@tanstack/table-core';
|
|
5
|
+
import { batch, createAtom } from '@tanstack/angular-store';
|
|
6
|
+
export { shallow } from '@tanstack/angular-store';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Injection token that provides access to the current cell.
|
|
10
|
+
*
|
|
11
|
+
* This token is provided by the {@link TanStackTableCell} directive.
|
|
12
|
+
*/
|
|
13
|
+
const TanStackTableCellToken = new InjectionToken('[TanStack Table] CellContext');
|
|
14
|
+
/**
|
|
15
|
+
* Provides a TanStack Table `Cell` instance in Angular DI.
|
|
16
|
+
*
|
|
17
|
+
* The cell can be injected by:
|
|
18
|
+
* - any descendant of an element using `[tanStackTableCell]="..."`
|
|
19
|
+
* - any component instantiated by `*flexRender` when the render props contains `cell`
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* Inject from the nearest `[tanStackTableCell]`:
|
|
23
|
+
* ```html
|
|
24
|
+
* <td [tanStackTableCell]="cell">
|
|
25
|
+
* <app-cell-actions />
|
|
26
|
+
* </td>
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* @Component({
|
|
31
|
+
* selector: 'app-cell-actions',
|
|
32
|
+
* template: `{{ cell().id }}`,
|
|
33
|
+
* })
|
|
34
|
+
* export class CellActionsComponent {
|
|
35
|
+
* readonly cell = injectTableCellContext()
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* Inject inside a component rendered via `flexRender`:
|
|
41
|
+
* ```ts
|
|
42
|
+
* @Component({
|
|
43
|
+
* selector: 'app-price-cell',
|
|
44
|
+
* template: `{{ cell().getValue() }}`,
|
|
45
|
+
* })
|
|
46
|
+
* export class PriceCellComponent {
|
|
47
|
+
* readonly cell = injectTableCellContext()
|
|
48
|
+
* }
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
class TanStackTableCell {
|
|
52
|
+
/**
|
|
53
|
+
* The current TanStack Table cell.
|
|
54
|
+
*
|
|
55
|
+
* Provided as a required signal input so DI consumers always read the latest value.
|
|
56
|
+
*/
|
|
57
|
+
cell = input.required({ ...(ngDevMode ? { debugName: "cell" } : /* istanbul ignore next */ {}), alias: 'tanStackTableCell' });
|
|
58
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: TanStackTableCell, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
59
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.13", type: TanStackTableCell, isStandalone: true, selector: "[tanStackTableCell]", inputs: { cell: { classPropertyName: "cell", publicName: "tanStackTableCell", isSignal: true, isRequired: true, transformFunction: null } }, providers: [
|
|
60
|
+
{
|
|
61
|
+
provide: TanStackTableCellToken,
|
|
62
|
+
useFactory: () => inject(TanStackTableCell).cell,
|
|
63
|
+
},
|
|
64
|
+
], exportAs: ["cell"], ngImport: i0 });
|
|
65
|
+
}
|
|
66
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: TanStackTableCell, decorators: [{
|
|
67
|
+
type: Directive,
|
|
68
|
+
args: [{
|
|
69
|
+
selector: '[tanStackTableCell]',
|
|
70
|
+
exportAs: 'cell',
|
|
71
|
+
providers: [
|
|
72
|
+
{
|
|
73
|
+
provide: TanStackTableCellToken,
|
|
74
|
+
useFactory: () => inject(TanStackTableCell).cell,
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
}]
|
|
78
|
+
}], propDecorators: { cell: [{ type: i0.Input, args: [{ isSignal: true, alias: "tanStackTableCell", required: true }] }] } });
|
|
79
|
+
/**
|
|
80
|
+
* Injects the current TanStack Table cell signal.
|
|
81
|
+
*
|
|
82
|
+
* Available when:
|
|
83
|
+
* - there is a nearest `[tanStackTableCell]` directive in the DI tree, or
|
|
84
|
+
* - the caller is rendered via `*flexRender` with render props containing `cell`
|
|
85
|
+
*/
|
|
86
|
+
function injectTableCellContext() {
|
|
87
|
+
return inject(TanStackTableCellToken);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Injection token that provides access to the current header.
|
|
92
|
+
*
|
|
93
|
+
* This token is provided by the {@link TanStackTableHeader} directive.
|
|
94
|
+
*/
|
|
95
|
+
const TanStackTableHeaderToken = new InjectionToken('[TanStack Table] HeaderContext');
|
|
96
|
+
/**
|
|
97
|
+
* Provides a TanStack Table `Header` instance in Angular DI.
|
|
98
|
+
*
|
|
99
|
+
* The header can be injected by:
|
|
100
|
+
* - any descendant of an element using `[tanStackTableHeader]="..."`
|
|
101
|
+
* - any component instantiated by `*flexRender` when the render props contains `header`
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```html
|
|
105
|
+
* <th [tanStackTableHeader]="header">
|
|
106
|
+
* <app-sort-indicator />
|
|
107
|
+
* </th>
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* ```ts
|
|
111
|
+
* @Component({
|
|
112
|
+
* selector: 'app-sort-indicator',
|
|
113
|
+
* template: `
|
|
114
|
+
* <button (click)="toggle()">
|
|
115
|
+
* {{ header().column.id }}
|
|
116
|
+
* </button>
|
|
117
|
+
* `,
|
|
118
|
+
* })
|
|
119
|
+
* export class SortIndicatorComponent {
|
|
120
|
+
* readonly header = injectTableHeaderContext()
|
|
121
|
+
*
|
|
122
|
+
* toggle() {
|
|
123
|
+
* this.header().column.toggleSorting()
|
|
124
|
+
* }
|
|
125
|
+
* }
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
class TanStackTableHeader {
|
|
129
|
+
/**
|
|
130
|
+
* The current TanStack Table header.
|
|
131
|
+
*
|
|
132
|
+
* Provided as a required signal input so DI consumers always read the latest value.
|
|
133
|
+
*/
|
|
134
|
+
header = input.required({ ...(ngDevMode ? { debugName: "header" } : /* istanbul ignore next */ {}), alias: 'tanStackTableHeader' });
|
|
135
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: TanStackTableHeader, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
136
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.13", type: TanStackTableHeader, isStandalone: true, selector: "[tanStackTableHeader]", inputs: { header: { classPropertyName: "header", publicName: "tanStackTableHeader", isSignal: true, isRequired: true, transformFunction: null } }, providers: [
|
|
137
|
+
{
|
|
138
|
+
provide: TanStackTableHeaderToken,
|
|
139
|
+
useFactory: () => inject(TanStackTableHeader).header,
|
|
140
|
+
},
|
|
141
|
+
], exportAs: ["header"], ngImport: i0 });
|
|
142
|
+
}
|
|
143
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: TanStackTableHeader, decorators: [{
|
|
144
|
+
type: Directive,
|
|
145
|
+
args: [{
|
|
146
|
+
selector: '[tanStackTableHeader]',
|
|
147
|
+
exportAs: 'header',
|
|
148
|
+
providers: [
|
|
149
|
+
{
|
|
150
|
+
provide: TanStackTableHeaderToken,
|
|
151
|
+
useFactory: () => inject(TanStackTableHeader).header,
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
}]
|
|
155
|
+
}], propDecorators: { header: [{ type: i0.Input, args: [{ isSignal: true, alias: "tanStackTableHeader", required: true }] }] } });
|
|
156
|
+
/**
|
|
157
|
+
* Injects the current TanStack Table header signal.
|
|
158
|
+
*
|
|
159
|
+
* Available when:
|
|
160
|
+
* - there is a nearest `[tanStackTableHeader]` directive in the DI tree, or
|
|
161
|
+
* - the caller is rendered via `*flexRender` with render props containing `header`
|
|
162
|
+
*/
|
|
163
|
+
function injectTableHeaderContext() {
|
|
164
|
+
return inject(TanStackTableHeaderToken);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Injection token that provides access to the current {@link AngularTable} instance.
|
|
169
|
+
*
|
|
170
|
+
* This token is provided by the {@link TanStackTable} directive.
|
|
171
|
+
*/
|
|
172
|
+
const TanStackTableToken = new InjectionToken('[TanStack Table] Table Context');
|
|
173
|
+
/**
|
|
174
|
+
* Provides a TanStack Table instance (`AngularTable`) in Angular DI.
|
|
175
|
+
*
|
|
176
|
+
* The table can be injected by:
|
|
177
|
+
* - any descendant of an element using `[tanStackTable]="..."`
|
|
178
|
+
* - any component instantiated by `*flexRender` when the render props contains `table`
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```html
|
|
182
|
+
* <div [tanStackTable]="table">
|
|
183
|
+
* <app-pagination />
|
|
184
|
+
* </div>
|
|
185
|
+
* ```
|
|
186
|
+
*
|
|
187
|
+
* ```ts
|
|
188
|
+
* @Component({
|
|
189
|
+
* selector: 'app-pagination',
|
|
190
|
+
* template: `
|
|
191
|
+
* <button (click)="prev()" [disabled]="!table().getCanPreviousPage()">Prev</button>
|
|
192
|
+
* <button (click)="next()" [disabled]="!table().getCanNextPage()">Next</button>
|
|
193
|
+
* `,
|
|
194
|
+
* })
|
|
195
|
+
* export class PaginationComponent {
|
|
196
|
+
* readonly table = injectTableContext()
|
|
197
|
+
*
|
|
198
|
+
* prev() {
|
|
199
|
+
* this.table().previousPage()
|
|
200
|
+
* }
|
|
201
|
+
* next() {
|
|
202
|
+
* this.table().nextPage()
|
|
203
|
+
* }
|
|
204
|
+
* }
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
class TanStackTable {
|
|
208
|
+
/**
|
|
209
|
+
* The current TanStack Table instance.
|
|
210
|
+
*
|
|
211
|
+
* Provided as a required signal input so DI consumers always read the latest value.
|
|
212
|
+
*/
|
|
213
|
+
table = input.required({ ...(ngDevMode ? { debugName: "table" } : /* istanbul ignore next */ {}), alias: 'tanStackTable' });
|
|
214
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: TanStackTable, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
215
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.13", type: TanStackTable, isStandalone: true, selector: "[tanStackTable]", inputs: { table: { classPropertyName: "table", publicName: "tanStackTable", isSignal: true, isRequired: true, transformFunction: null } }, providers: [
|
|
216
|
+
{
|
|
217
|
+
provide: TanStackTableToken,
|
|
218
|
+
useFactory: () => inject(TanStackTable).table,
|
|
219
|
+
},
|
|
220
|
+
], exportAs: ["table"], ngImport: i0 });
|
|
221
|
+
}
|
|
222
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: TanStackTable, decorators: [{
|
|
223
|
+
type: Directive,
|
|
224
|
+
args: [{
|
|
225
|
+
selector: '[tanStackTable]',
|
|
226
|
+
exportAs: 'table',
|
|
227
|
+
providers: [
|
|
228
|
+
{
|
|
229
|
+
provide: TanStackTableToken,
|
|
230
|
+
useFactory: () => inject(TanStackTable).table,
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
}]
|
|
234
|
+
}], propDecorators: { table: [{ type: i0.Input, args: [{ isSignal: true, alias: "tanStackTable", required: true }] }] } });
|
|
235
|
+
/**
|
|
236
|
+
* Injects the current TanStack Table instance signal.
|
|
237
|
+
*
|
|
238
|
+
* Available when:
|
|
239
|
+
* - there is a nearest `[tanStackTable]` directive in the DI tree, or
|
|
240
|
+
* - the caller is rendered via `*flexRender` with render props containing `table`
|
|
241
|
+
*/
|
|
242
|
+
function injectTableContext() {
|
|
243
|
+
return inject(TanStackTableToken);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const FlexRenderComponentProps = new InjectionToken('[@tanstack/angular-table] Flex render component context props');
|
|
247
|
+
/**
|
|
248
|
+
* Inject the flex render context props.
|
|
249
|
+
*
|
|
250
|
+
* Can be used in components rendered via FlexRender directives.
|
|
251
|
+
*/
|
|
252
|
+
function injectFlexRenderContext() {
|
|
253
|
+
return inject(FlexRenderComponentProps);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Flags used to manage and optimize the rendering lifecycle of the content of the cell
|
|
258
|
+
* while using {@link FlexViewRenderer}.
|
|
259
|
+
*/
|
|
260
|
+
const FlexRenderFlags = {
|
|
261
|
+
/**
|
|
262
|
+
* Indicates that the view is being created for the first time or will be cleared during the next update phase.
|
|
263
|
+
* This is the initial state and will transition after the first ngDoCheck.
|
|
264
|
+
*/
|
|
265
|
+
ViewFirstRender: 1 << 0,
|
|
266
|
+
/**
|
|
267
|
+
* Indicates the `content` property has been modified or the view requires a complete re-render.
|
|
268
|
+
* When this flag is enabled, the view will be cleared and recreated from scratch.
|
|
269
|
+
*/
|
|
270
|
+
ContentChanged: 1 << 1,
|
|
271
|
+
/**
|
|
272
|
+
* Indicates that the `props` property reference has changed.
|
|
273
|
+
* When this flag is enabled, the view context is updated based on the type of the content.
|
|
274
|
+
*
|
|
275
|
+
* For Component view, inputs will be updated and view will be marked as dirty.
|
|
276
|
+
* For TemplateRef and primitive values, view will be marked as dirty
|
|
277
|
+
*/
|
|
278
|
+
PropsReferenceChanged: 1 << 2,
|
|
279
|
+
/**
|
|
280
|
+
* Indicates that the current rendered view needs to be checked for changes.
|
|
281
|
+
* This will be set to true when `content(props)` result has changed or during
|
|
282
|
+
* forced update
|
|
283
|
+
*/
|
|
284
|
+
Dirty: 1 << 3,
|
|
285
|
+
/**
|
|
286
|
+
* Indicates that the first render effect has been checked at least one time.
|
|
287
|
+
*/
|
|
288
|
+
RenderEffectChecked: 1 << 4,
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Helper function to create a {@link FlexRenderComponent} instance, with better type-safety.
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* ```ts
|
|
296
|
+
* import {flexRenderComponent} from '@tanstack/angular-table'
|
|
297
|
+
* import {inputBinding, outputBinding} from '@angular/core';
|
|
298
|
+
*
|
|
299
|
+
* const columns = [
|
|
300
|
+
* {
|
|
301
|
+
* cell: ({ row }) => {
|
|
302
|
+
* return flexRenderComponent(MyComponent, {
|
|
303
|
+
* inputs: { value: mySignalValue() },
|
|
304
|
+
* outputs: { valueChange: (val) => {} }
|
|
305
|
+
* // or using angular native createComponent#binding api
|
|
306
|
+
* bindings: [
|
|
307
|
+
* inputBinding('value', mySignalValue),
|
|
308
|
+
* outputBinding('valueChange', value => {
|
|
309
|
+
* console.log("my value changed to", value)
|
|
310
|
+
* })
|
|
311
|
+
* ]
|
|
312
|
+
* })
|
|
313
|
+
* },
|
|
314
|
+
* },
|
|
315
|
+
* ]
|
|
316
|
+
* ```
|
|
317
|
+
*/
|
|
318
|
+
function flexRenderComponent(component, options) {
|
|
319
|
+
const { inputs, injector, outputs, directives, bindings } = options ?? {};
|
|
320
|
+
return new FlexRenderComponentInstance(component, inputs, injector, outputs, directives, bindings);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Wrapper class for a component that will be used as content for {@link FlexRenderDirective}
|
|
324
|
+
*
|
|
325
|
+
* Prefer {@link flexRenderComponent} helper for better type-safety
|
|
326
|
+
*/
|
|
327
|
+
class FlexRenderComponentInstance {
|
|
328
|
+
component;
|
|
329
|
+
inputs;
|
|
330
|
+
injector;
|
|
331
|
+
outputs;
|
|
332
|
+
directives;
|
|
333
|
+
bindings;
|
|
334
|
+
mirror;
|
|
335
|
+
allowedInputNames = [];
|
|
336
|
+
allowedOutputNames = [];
|
|
337
|
+
constructor(component, inputs, injector, outputs, directives, bindings) {
|
|
338
|
+
this.component = component;
|
|
339
|
+
this.inputs = inputs;
|
|
340
|
+
this.injector = injector;
|
|
341
|
+
this.outputs = outputs;
|
|
342
|
+
this.directives = directives;
|
|
343
|
+
this.bindings = bindings;
|
|
344
|
+
const mirror = reflectComponentType(component);
|
|
345
|
+
if (!mirror) {
|
|
346
|
+
throw new Error(`[@tanstack-table/angular] The provided symbol is not a component`);
|
|
347
|
+
}
|
|
348
|
+
this.mirror = mirror;
|
|
349
|
+
for (const input of this.mirror.inputs) {
|
|
350
|
+
this.allowedInputNames.push(input.propName);
|
|
351
|
+
}
|
|
352
|
+
for (const output of this.mirror.outputs) {
|
|
353
|
+
this.allowedOutputNames.push(output.propName);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Creates and manages Angular component instances used by flex-rendered table
|
|
360
|
+
* content.
|
|
361
|
+
*/
|
|
362
|
+
class FlexRenderComponentFactory {
|
|
363
|
+
#viewContainerRef;
|
|
364
|
+
constructor(viewContainerRef) {
|
|
365
|
+
this.#viewContainerRef = viewContainerRef;
|
|
366
|
+
}
|
|
367
|
+
createComponent(flexRenderComponent, componentInjector) {
|
|
368
|
+
const componentRef = this.#viewContainerRef.createComponent(flexRenderComponent.component, {
|
|
369
|
+
injector: componentInjector,
|
|
370
|
+
directives: flexRenderComponent.directives,
|
|
371
|
+
bindings: flexRenderComponent.bindings ?? [],
|
|
372
|
+
});
|
|
373
|
+
const view = new FlexRenderComponentRef(componentRef, flexRenderComponent, componentInjector);
|
|
374
|
+
const { inputs, outputs } = flexRenderComponent;
|
|
375
|
+
if (inputs)
|
|
376
|
+
view.setInputs(inputs);
|
|
377
|
+
if (outputs)
|
|
378
|
+
view.setOutputs(outputs);
|
|
379
|
+
return view;
|
|
380
|
+
}
|
|
381
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: FlexRenderComponentFactory, deps: [{ token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
382
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: FlexRenderComponentFactory });
|
|
383
|
+
}
|
|
384
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: FlexRenderComponentFactory, decorators: [{
|
|
385
|
+
type: Injectable
|
|
386
|
+
}], ctorParameters: () => [{ type: i0.ViewContainerRef }] });
|
|
387
|
+
/**
|
|
388
|
+
* Runtime wrapper around an Angular component rendered by `FlexRenderDirective`.
|
|
389
|
+
*
|
|
390
|
+
* It diffs inputs and outputs across table updates so component renderers can
|
|
391
|
+
* be reused instead of recreated on every cell/header render.
|
|
392
|
+
*/
|
|
393
|
+
class FlexRenderComponentRef {
|
|
394
|
+
componentRef;
|
|
395
|
+
componentInjector;
|
|
396
|
+
#keyValueDiffersFactory;
|
|
397
|
+
#componentData;
|
|
398
|
+
#inputValueDiffer;
|
|
399
|
+
#outputRegistry;
|
|
400
|
+
constructor(componentRef, componentData, componentInjector) {
|
|
401
|
+
this.componentRef = componentRef;
|
|
402
|
+
this.componentInjector = componentInjector;
|
|
403
|
+
this.#componentData = componentData;
|
|
404
|
+
this.#keyValueDiffersFactory = componentInjector.get(KeyValueDiffers);
|
|
405
|
+
this.#outputRegistry = new FlexRenderComponentOutputManager(this.#keyValueDiffersFactory, this.outputs);
|
|
406
|
+
this.#inputValueDiffer = this.#keyValueDiffersFactory
|
|
407
|
+
.find(this.inputs)
|
|
408
|
+
.create();
|
|
409
|
+
this.#inputValueDiffer.diff(this.inputs);
|
|
410
|
+
this.componentRef.onDestroy(() => this.#outputRegistry.unsubscribeAll());
|
|
411
|
+
}
|
|
412
|
+
get component() {
|
|
413
|
+
return this.#componentData.component;
|
|
414
|
+
}
|
|
415
|
+
get inputs() {
|
|
416
|
+
return this.#componentData.inputs ?? {};
|
|
417
|
+
}
|
|
418
|
+
get outputs() {
|
|
419
|
+
return this.#componentData.outputs ?? {};
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Get component input and output diff by the given item
|
|
423
|
+
*/
|
|
424
|
+
diff(item) {
|
|
425
|
+
return {
|
|
426
|
+
inputDiff: this.#inputValueDiffer.diff(item.inputs ?? {}),
|
|
427
|
+
outputDiff: this.#outputRegistry.diff(item.outputs ?? {}),
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
*
|
|
432
|
+
* @param compare Whether the current ref component instance is the same as the given one
|
|
433
|
+
*/
|
|
434
|
+
eqType(compare) {
|
|
435
|
+
return compare.component === this.component;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Tries to update current component refs input by the new given content component.
|
|
439
|
+
*/
|
|
440
|
+
update(content) {
|
|
441
|
+
const eq = this.eqType(content);
|
|
442
|
+
if (!eq)
|
|
443
|
+
return;
|
|
444
|
+
const { inputDiff, outputDiff } = this.diff(content);
|
|
445
|
+
if (inputDiff) {
|
|
446
|
+
inputDiff.forEachAddedItem((item) => this.setInput(item.key, item.currentValue));
|
|
447
|
+
inputDiff.forEachChangedItem((item) => this.setInput(item.key, item.currentValue));
|
|
448
|
+
inputDiff.forEachRemovedItem((item) => this.setInput(item.key, undefined));
|
|
449
|
+
}
|
|
450
|
+
if (outputDiff) {
|
|
451
|
+
outputDiff.forEachAddedItem((item) => {
|
|
452
|
+
this.setOutput(item.key, item.currentValue);
|
|
453
|
+
});
|
|
454
|
+
outputDiff.forEachChangedItem((item) => {
|
|
455
|
+
if (item.currentValue) {
|
|
456
|
+
this.#outputRegistry.setListener(item.key, item.currentValue);
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
this.#outputRegistry.unsubscribe(item.key);
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
outputDiff.forEachRemovedItem((item) => {
|
|
463
|
+
this.#outputRegistry.unsubscribe(item.key);
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
this.#componentData = content;
|
|
467
|
+
}
|
|
468
|
+
markAsDirty() {
|
|
469
|
+
this.componentRef.injector.get(ChangeDetectorRef).markForCheck();
|
|
470
|
+
}
|
|
471
|
+
setInputs(inputs) {
|
|
472
|
+
for (const prop in inputs) {
|
|
473
|
+
this.setInput(prop, inputs[prop]);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
setInput(key, value) {
|
|
477
|
+
if (this.#componentData.allowedInputNames.includes(key)) {
|
|
478
|
+
this.componentRef.setInput(key, value);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
setOutputs(outputs) {
|
|
482
|
+
this.#outputRegistry.unsubscribeAll();
|
|
483
|
+
for (const prop in outputs) {
|
|
484
|
+
this.setOutput(prop, outputs[prop]);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
setOutput(outputName, emit) {
|
|
488
|
+
if (!this.#componentData.allowedOutputNames.includes(outputName))
|
|
489
|
+
return;
|
|
490
|
+
if (!emit) {
|
|
491
|
+
this.#outputRegistry.unsubscribe(outputName);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const hasListener = this.#outputRegistry.hasListener(outputName);
|
|
495
|
+
this.#outputRegistry.setListener(outputName, emit);
|
|
496
|
+
if (hasListener) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const instance = this.componentRef.instance;
|
|
500
|
+
const output = instance[outputName];
|
|
501
|
+
if (output && output instanceof OutputEmitterRef) {
|
|
502
|
+
output.subscribe((value) => {
|
|
503
|
+
this.#outputRegistry.getListener(outputName)?.(value);
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
class FlexRenderComponentOutputManager {
|
|
509
|
+
#outputSubscribers = {};
|
|
510
|
+
#outputListeners = {};
|
|
511
|
+
#valueDiffer;
|
|
512
|
+
constructor(keyValueDiffers, initialOutputs) {
|
|
513
|
+
this.#valueDiffer = keyValueDiffers.find(initialOutputs).create();
|
|
514
|
+
if (initialOutputs) {
|
|
515
|
+
this.#valueDiffer.diff(initialOutputs);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
hasListener(outputName) {
|
|
519
|
+
return outputName in this.#outputListeners;
|
|
520
|
+
}
|
|
521
|
+
setListener(outputName, callback) {
|
|
522
|
+
this.#outputListeners[outputName] = callback;
|
|
523
|
+
}
|
|
524
|
+
getListener(outputName) {
|
|
525
|
+
return this.#outputListeners[outputName];
|
|
526
|
+
}
|
|
527
|
+
unsubscribeAll() {
|
|
528
|
+
for (const prop in this.#outputSubscribers) {
|
|
529
|
+
this.unsubscribe(prop);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
unsubscribe(outputName) {
|
|
533
|
+
if (outputName in this.#outputSubscribers) {
|
|
534
|
+
this.#outputSubscribers[outputName]?.unsubscribe();
|
|
535
|
+
delete this.#outputSubscribers[outputName];
|
|
536
|
+
delete this.#outputListeners[outputName];
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
diff(outputs) {
|
|
540
|
+
return this.#valueDiffer.diff(outputs);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Normalizes arbitrary Angular flex-render content into the renderer's internal
|
|
546
|
+
* tagged representation.
|
|
547
|
+
*
|
|
548
|
+
* This lets the directive decide whether to reuse, update, or recreate an
|
|
549
|
+
* embedded view or component view.
|
|
550
|
+
*/
|
|
551
|
+
function mapToFlexRenderTypedContent(content) {
|
|
552
|
+
if (content === null || content === undefined) {
|
|
553
|
+
return { kind: 'null' };
|
|
554
|
+
}
|
|
555
|
+
if (typeof content === 'string' || typeof content === 'number') {
|
|
556
|
+
return { kind: 'primitive', content };
|
|
557
|
+
}
|
|
558
|
+
if (content instanceof FlexRenderComponentInstance) {
|
|
559
|
+
return { kind: 'flexRenderComponent', content };
|
|
560
|
+
}
|
|
561
|
+
else if (content instanceof TemplateRef) {
|
|
562
|
+
return { kind: 'templateRef', content };
|
|
563
|
+
}
|
|
564
|
+
else if (content instanceof Type) {
|
|
565
|
+
return { kind: 'component', content };
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
return { kind: 'primitive', content };
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
class FlexRenderView {
|
|
572
|
+
view;
|
|
573
|
+
#previousContent;
|
|
574
|
+
#content;
|
|
575
|
+
constructor(initialContent, view) {
|
|
576
|
+
this.#content = initialContent;
|
|
577
|
+
this.view = view;
|
|
578
|
+
}
|
|
579
|
+
get previousContent() {
|
|
580
|
+
return this.#previousContent ?? { kind: 'null' };
|
|
581
|
+
}
|
|
582
|
+
get content() {
|
|
583
|
+
return this.#content;
|
|
584
|
+
}
|
|
585
|
+
set content(content) {
|
|
586
|
+
this.#previousContent = this.#content;
|
|
587
|
+
this.#content = content;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Tracks an Angular embedded template view rendered by `FlexRenderDirective`.
|
|
592
|
+
*
|
|
593
|
+
* Template views receive updated props through their proxied context and can be
|
|
594
|
+
* reused while the rendered content kind stays compatible.
|
|
595
|
+
*/
|
|
596
|
+
class FlexRenderTemplateView extends FlexRenderView {
|
|
597
|
+
constructor(initialContent, view) {
|
|
598
|
+
super(initialContent, view);
|
|
599
|
+
}
|
|
600
|
+
updateProps(props) {
|
|
601
|
+
this.view.markForCheck();
|
|
602
|
+
}
|
|
603
|
+
dirtyCheck() {
|
|
604
|
+
// Basically a no-op. When the view is created via EmbeddedViewRef, we don't need to do any manual update
|
|
605
|
+
// since this type of content has a proxy as a context, then every time the root component is checked for changes,
|
|
606
|
+
// the property getter will be re-evaluated.
|
|
607
|
+
//
|
|
608
|
+
// If in a future we need to manually mark the view as dirty, just uncomment next line
|
|
609
|
+
// this.view.markForCheck()
|
|
610
|
+
}
|
|
611
|
+
unmount() {
|
|
612
|
+
this.view.destroy();
|
|
613
|
+
}
|
|
614
|
+
onDestroy(callback) {
|
|
615
|
+
this.view.onDestroy(callback);
|
|
616
|
+
}
|
|
617
|
+
eq(compare) {
|
|
618
|
+
return ((this.content.kind === 'primitive' &&
|
|
619
|
+
compare.kind === 'primitive' &&
|
|
620
|
+
this.content.content === compare.content) ||
|
|
621
|
+
(this.content.kind === 'templateRef' &&
|
|
622
|
+
compare.kind === 'templateRef' &&
|
|
623
|
+
this.content.content === compare.content));
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Tracks an Angular component view rendered by `FlexRenderDirective`.
|
|
628
|
+
*
|
|
629
|
+
* Component views own input/output updates for `flexRenderComponent(...)`
|
|
630
|
+
* results and component classes rendered directly from column definitions.
|
|
631
|
+
*/
|
|
632
|
+
class FlexRenderComponentView extends FlexRenderView {
|
|
633
|
+
constructor(initialContent, view) {
|
|
634
|
+
super(initialContent, view);
|
|
635
|
+
}
|
|
636
|
+
updateProps(props) {
|
|
637
|
+
switch (this.content.kind) {
|
|
638
|
+
case 'component': {
|
|
639
|
+
this.view.setInputs(props);
|
|
640
|
+
break;
|
|
641
|
+
}
|
|
642
|
+
case 'flexRenderComponent': {
|
|
643
|
+
// No-op. When FlexRenderFlags.PropsReferenceChanged is set,
|
|
644
|
+
// FlexRenderComponent will be updated into `dirtyCheck`.
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
dirtyCheck() {
|
|
650
|
+
switch (this.content.kind) {
|
|
651
|
+
case 'component': {
|
|
652
|
+
// Component context is currently valuated with the cell context. Since it's reference
|
|
653
|
+
// shouldn't change, we force mark the component as dirty in order to re-evaluate function invocation in view.
|
|
654
|
+
// NOTE: this should behave like having a component with ChangeDetectionStrategy.Default
|
|
655
|
+
this.view.markAsDirty();
|
|
656
|
+
break;
|
|
657
|
+
}
|
|
658
|
+
case 'flexRenderComponent': {
|
|
659
|
+
// Given context instance will always have a different reference than the previous one,
|
|
660
|
+
// so instead of recreating the entire view, we will only update the current view
|
|
661
|
+
if (this.view.eqType(this.content.content)) {
|
|
662
|
+
this.view.update(this.content.content);
|
|
663
|
+
}
|
|
664
|
+
this.view.markAsDirty();
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
unmount() {
|
|
670
|
+
this.view.componentRef.destroy();
|
|
671
|
+
}
|
|
672
|
+
onDestroy(callback) {
|
|
673
|
+
this.view.componentRef.onDestroy(callback);
|
|
674
|
+
}
|
|
675
|
+
eq(compare) {
|
|
676
|
+
return ((this.content.kind === 'component' &&
|
|
677
|
+
compare.kind === 'component' &&
|
|
678
|
+
this.content.content === compare.content) ||
|
|
679
|
+
(this.content.kind === 'flexRenderComponent' &&
|
|
680
|
+
compare.kind === 'flexRenderComponent' &&
|
|
681
|
+
this.content.content.component === compare.content.component));
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Internal view renderer used by Angular TanStack Table to implement `flexRender` directives.
|
|
687
|
+
*
|
|
688
|
+
* @internal Use FlexRender directives instead.
|
|
689
|
+
*/
|
|
690
|
+
class FlexViewRenderer {
|
|
691
|
+
#renderFlags = FlexRenderFlags.ViewFirstRender;
|
|
692
|
+
#renderView = null;
|
|
693
|
+
#currentRenderEffectRef = null;
|
|
694
|
+
#content;
|
|
695
|
+
#props;
|
|
696
|
+
#injector;
|
|
697
|
+
#viewContainerRef;
|
|
698
|
+
#templateRef;
|
|
699
|
+
#flexRenderComponentFactory;
|
|
700
|
+
#getLatestContentValue = () => {
|
|
701
|
+
const content = this.#content();
|
|
702
|
+
const props = this.#props();
|
|
703
|
+
return typeof content !== 'function'
|
|
704
|
+
? content
|
|
705
|
+
: runInInjectionContext(this.#injector(), () => content(props));
|
|
706
|
+
};
|
|
707
|
+
#latestContent = computed(() => this.#getLatestContentValue(), ...(ngDevMode ? [{ debugName: "#latestContent" }] : /* istanbul ignore next */ []));
|
|
708
|
+
#getContentValue = computed(() => {
|
|
709
|
+
const latestContent = this.#latestContent();
|
|
710
|
+
return mapToFlexRenderTypedContent(latestContent);
|
|
711
|
+
}, ...(ngDevMode ? [{ debugName: "#getContentValue" }] : /* istanbul ignore next */ []));
|
|
712
|
+
constructor(options) {
|
|
713
|
+
this.#content = options.content;
|
|
714
|
+
this.#props = options.props;
|
|
715
|
+
this.#injector = options.injector;
|
|
716
|
+
this.#templateRef = options.templateRef;
|
|
717
|
+
this.#viewContainerRef = options.viewContainerRef;
|
|
718
|
+
this.#flexRenderComponentFactory = new FlexRenderComponentFactory(this.#viewContainerRef);
|
|
719
|
+
}
|
|
720
|
+
mount() {
|
|
721
|
+
let previousContent;
|
|
722
|
+
let previousProps;
|
|
723
|
+
return effect(() => {
|
|
724
|
+
const props = this.#props();
|
|
725
|
+
const content = this.#content();
|
|
726
|
+
if (!(this.#renderFlags & FlexRenderFlags.ViewFirstRender)) {
|
|
727
|
+
if (previousContent !== content) {
|
|
728
|
+
this.#renderFlags |= FlexRenderFlags.ContentChanged;
|
|
729
|
+
}
|
|
730
|
+
if (previousProps !== props) {
|
|
731
|
+
this.#renderFlags |= FlexRenderFlags.PropsReferenceChanged;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
untracked(() => this.#update());
|
|
735
|
+
if (FlexRenderFlags.ViewFirstRender & this.#renderFlags) {
|
|
736
|
+
this.#renderFlags &= ~FlexRenderFlags.ViewFirstRender;
|
|
737
|
+
}
|
|
738
|
+
previousContent = content;
|
|
739
|
+
previousProps = props;
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
destroy() {
|
|
743
|
+
if (this.#currentRenderEffectRef) {
|
|
744
|
+
this.#currentRenderEffectRef.destroy();
|
|
745
|
+
this.#currentRenderEffectRef = null;
|
|
746
|
+
}
|
|
747
|
+
if (this.#renderView) {
|
|
748
|
+
this.#renderView.unmount();
|
|
749
|
+
this.#renderView = null;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
#update() {
|
|
753
|
+
if (this.#renderFlags &
|
|
754
|
+
(FlexRenderFlags.ContentChanged | FlexRenderFlags.ViewFirstRender)) {
|
|
755
|
+
this.#render();
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
if (this.#renderFlags & FlexRenderFlags.PropsReferenceChanged) {
|
|
759
|
+
if (this.#renderView)
|
|
760
|
+
this.#renderView.updateProps(this.#props());
|
|
761
|
+
this.#renderFlags &= ~FlexRenderFlags.PropsReferenceChanged;
|
|
762
|
+
}
|
|
763
|
+
if (this.#renderFlags & FlexRenderFlags.Dirty) {
|
|
764
|
+
if (this.#renderView)
|
|
765
|
+
this.#renderView.dirtyCheck();
|
|
766
|
+
this.#renderFlags &= ~FlexRenderFlags.Dirty;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
#render() {
|
|
770
|
+
// When the view is recreated from scratch (content change or first render),
|
|
771
|
+
// we have to destroy the current effect listener since it will be recreated
|
|
772
|
+
// skipping the first call (FlexRenderFlags.RenderEffectChecked)
|
|
773
|
+
if (this.#shouldRecreateEntireView() && this.#currentRenderEffectRef) {
|
|
774
|
+
this.#currentRenderEffectRef.destroy();
|
|
775
|
+
this.#currentRenderEffectRef = null;
|
|
776
|
+
this.#renderFlags &= ~FlexRenderFlags.RenderEffectChecked;
|
|
777
|
+
}
|
|
778
|
+
this.#viewContainerRef.clear();
|
|
779
|
+
if (this.#renderView) {
|
|
780
|
+
this.#renderView.unmount();
|
|
781
|
+
this.#renderView = null;
|
|
782
|
+
}
|
|
783
|
+
this.#renderFlags =
|
|
784
|
+
(this.#renderFlags & FlexRenderFlags.ViewFirstRender) |
|
|
785
|
+
(this.#renderFlags & FlexRenderFlags.RenderEffectChecked);
|
|
786
|
+
const resolvedContent = this.#getContentValue();
|
|
787
|
+
this.#renderView = this.#renderViewByContent(resolvedContent);
|
|
788
|
+
// If the content is a function `content(props)`, we initialize an effect
|
|
789
|
+
// to react to changes. If the current fn uses signals, we will set the DirtySignal flag
|
|
790
|
+
// to re-schedule the component updates
|
|
791
|
+
if (!this.#currentRenderEffectRef &&
|
|
792
|
+
typeof untracked(this.#content) === 'function') {
|
|
793
|
+
this.#currentRenderEffectRef = effect(() => {
|
|
794
|
+
this.#latestContent();
|
|
795
|
+
if (!(this.#renderFlags & FlexRenderFlags.RenderEffectChecked)) {
|
|
796
|
+
this.#renderFlags |= FlexRenderFlags.RenderEffectChecked;
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
this.#renderFlags |= FlexRenderFlags.Dirty;
|
|
800
|
+
this.#doCheck();
|
|
801
|
+
}, { ...(ngDevMode ? { debugName: "#currentRenderEffectRef" } : /* istanbul ignore next */ {}), injector: this.#viewContainerRef.injector });
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
#shouldRecreateEntireView() {
|
|
805
|
+
return (this.#renderFlags &
|
|
806
|
+
FlexRenderFlags.ContentChanged &
|
|
807
|
+
FlexRenderFlags.ViewFirstRender);
|
|
808
|
+
}
|
|
809
|
+
#doCheck() {
|
|
810
|
+
const latestContent = this.#getContentValue();
|
|
811
|
+
if (latestContent.kind === 'null' || !this.#renderView) {
|
|
812
|
+
this.#renderFlags |= FlexRenderFlags.ContentChanged;
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
const { kind: currentKind } = this.#renderView.content;
|
|
816
|
+
if (latestContent.kind !== currentKind ||
|
|
817
|
+
!this.#renderView.eq(latestContent)) {
|
|
818
|
+
this.#renderFlags |= FlexRenderFlags.ContentChanged;
|
|
819
|
+
}
|
|
820
|
+
this.#renderView.content = latestContent;
|
|
821
|
+
}
|
|
822
|
+
this.#update();
|
|
823
|
+
}
|
|
824
|
+
#renderViewByContent(content) {
|
|
825
|
+
if (content.kind === 'primitive') {
|
|
826
|
+
return this.#renderStringContent(content);
|
|
827
|
+
}
|
|
828
|
+
else if (content.kind === 'templateRef') {
|
|
829
|
+
return this.#renderTemplateRefContent(content);
|
|
830
|
+
}
|
|
831
|
+
else if (content.kind === 'flexRenderComponent') {
|
|
832
|
+
return this.#renderComponent(content);
|
|
833
|
+
}
|
|
834
|
+
else if (content.kind === 'component') {
|
|
835
|
+
return this.#renderCustomComponent(content);
|
|
836
|
+
}
|
|
837
|
+
else {
|
|
838
|
+
return null;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
#renderStringContent(template) {
|
|
842
|
+
const context = () => {
|
|
843
|
+
const content = this.#content();
|
|
844
|
+
return typeof content === 'string' || typeof content === 'number'
|
|
845
|
+
? content
|
|
846
|
+
: runInInjectionContext(this.#injector(), () => content?.(this.#props()));
|
|
847
|
+
};
|
|
848
|
+
const ref = this.#viewContainerRef.createEmbeddedView(this.#templateRef, {
|
|
849
|
+
get $implicit() {
|
|
850
|
+
return context();
|
|
851
|
+
},
|
|
852
|
+
});
|
|
853
|
+
return new FlexRenderTemplateView(template, ref);
|
|
854
|
+
}
|
|
855
|
+
#renderTemplateRefContent(template) {
|
|
856
|
+
const latestContext = () => this.#props();
|
|
857
|
+
const view = this.#viewContainerRef.createEmbeddedView(template.content, {
|
|
858
|
+
get $implicit() {
|
|
859
|
+
return latestContext();
|
|
860
|
+
},
|
|
861
|
+
}, { injector: this.#getInjector() });
|
|
862
|
+
return new FlexRenderTemplateView(template, view);
|
|
863
|
+
}
|
|
864
|
+
#renderComponent(flexRenderComponent) {
|
|
865
|
+
const { injector } = flexRenderComponent.content;
|
|
866
|
+
const componentInjector = this.#getInjector(injector);
|
|
867
|
+
const view = this.#flexRenderComponentFactory.createComponent(flexRenderComponent.content, componentInjector);
|
|
868
|
+
return new FlexRenderComponentView(flexRenderComponent, view);
|
|
869
|
+
}
|
|
870
|
+
#renderCustomComponent(component) {
|
|
871
|
+
const instance = flexRenderComponent(component.content, {
|
|
872
|
+
inputs: this.#props(),
|
|
873
|
+
});
|
|
874
|
+
const injector = this.#getInjector(instance.injector);
|
|
875
|
+
const view = this.#flexRenderComponentFactory.createComponent(instance, injector);
|
|
876
|
+
return new FlexRenderComponentView(component, view);
|
|
877
|
+
}
|
|
878
|
+
#getInjector(parentInjector) {
|
|
879
|
+
const getContext = () => this.#props();
|
|
880
|
+
const proxy = new Proxy(this.#props(), {
|
|
881
|
+
get: (_, key) => getContext()[key],
|
|
882
|
+
});
|
|
883
|
+
const staticProviders = [];
|
|
884
|
+
if ('table' in proxy) {
|
|
885
|
+
staticProviders.push({
|
|
886
|
+
provide: TanStackTableToken,
|
|
887
|
+
useValue: () => proxy.table,
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
if ('cell' in proxy) {
|
|
891
|
+
staticProviders.push({
|
|
892
|
+
provide: TanStackTableCellToken,
|
|
893
|
+
useValue: () => proxy.cell,
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
if ('header' in proxy) {
|
|
897
|
+
staticProviders.push({
|
|
898
|
+
provide: TanStackTableHeaderToken,
|
|
899
|
+
useValue: () => proxy.header,
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
return Injector.create({
|
|
903
|
+
parent: parentInjector ?? this.#injector(),
|
|
904
|
+
providers: [
|
|
905
|
+
...staticProviders,
|
|
906
|
+
{ provide: FlexRenderComponentProps, useValue: proxy },
|
|
907
|
+
],
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Simplified directive wrapper of `*flexRender`.
|
|
914
|
+
*
|
|
915
|
+
* Use this utility component to render headers, cells, or footers with custom markup.
|
|
916
|
+
*
|
|
917
|
+
* Only one prop (`cell`, `header`, or `footer`) may be passed based on the used selector.
|
|
918
|
+
*
|
|
919
|
+
* @example
|
|
920
|
+
* ```html
|
|
921
|
+
* <td *flexRenderCell="cell; let cell">{{cell}}</td>
|
|
922
|
+
* <th *flexRenderHeader="header; let header">{{header}}</th>
|
|
923
|
+
* <th *flexRenderFooter="footer; let footer">{{footer}}</th>
|
|
924
|
+
* ```
|
|
925
|
+
*
|
|
926
|
+
* This replaces calling `*flexRender` directly like this:
|
|
927
|
+
* ```html
|
|
928
|
+
* <td *flexRender="cell.column.columnDef.cell; props: cell.getContext(); let cell">{{cell}}</td>
|
|
929
|
+
* <td *flexRender="header.column.columnDef.header; props: header.getContext(); let header">{{header}}</td>
|
|
930
|
+
* <td *flexRender="footer.column.columnDef.footer; props: footer.getContext(); let footer">{{footer}}</td>
|
|
931
|
+
* ```
|
|
932
|
+
*
|
|
933
|
+
* Can be imported through {@link FlexRenderCell} or {@link FlexRender} import,
|
|
934
|
+
* which the latter is preferred.
|
|
935
|
+
*
|
|
936
|
+
* @example
|
|
937
|
+
* ```ts
|
|
938
|
+
* import {FlexRender} from '@tanstack/angular-table
|
|
939
|
+
*
|
|
940
|
+
* @Component({
|
|
941
|
+
* // ...
|
|
942
|
+
* imports: [
|
|
943
|
+
* FlexRender
|
|
944
|
+
* ]
|
|
945
|
+
* })
|
|
946
|
+
* ```
|
|
947
|
+
*/
|
|
948
|
+
class FlexRenderCell {
|
|
949
|
+
cell = input(undefined, { ...(ngDevMode ? { debugName: "cell" } : /* istanbul ignore next */ {}), alias: 'flexRenderCell' });
|
|
950
|
+
header = input(undefined, { ...(ngDevMode ? { debugName: "header" } : /* istanbul ignore next */ {}), alias: 'flexRenderHeader' });
|
|
951
|
+
footer = input(undefined, { ...(ngDevMode ? { debugName: "footer" } : /* istanbul ignore next */ {}), alias: 'flexRenderFooter' });
|
|
952
|
+
#renderData = computed(() => {
|
|
953
|
+
const cell = this.cell();
|
|
954
|
+
const header = this.header();
|
|
955
|
+
const footer = this.footer();
|
|
956
|
+
if (cell) {
|
|
957
|
+
return [cell.column.columnDef.cell, cell.getContext()];
|
|
958
|
+
}
|
|
959
|
+
if (header) {
|
|
960
|
+
return [header.column.columnDef.header, header.getContext()];
|
|
961
|
+
}
|
|
962
|
+
if (footer) {
|
|
963
|
+
return [footer.column.columnDef.footer, footer.getContext()];
|
|
964
|
+
}
|
|
965
|
+
return [null, null];
|
|
966
|
+
}, { ...(ngDevMode ? { debugName: "#renderData" } : /* istanbul ignore next */ {}), equal: (a, b) => {
|
|
967
|
+
return a[0] === b[0] && a[1] === b[1];
|
|
968
|
+
} });
|
|
969
|
+
#injector = inject(Injector);
|
|
970
|
+
#templateRef = inject(TemplateRef);
|
|
971
|
+
#viewContainerRef = inject(ViewContainerRef);
|
|
972
|
+
constructor() {
|
|
973
|
+
const content = computed(() => this.#renderData()[0], ...(ngDevMode ? [{ debugName: "content" }] : /* istanbul ignore next */ []));
|
|
974
|
+
const props = computed(() => this.#renderData()[1], ...(ngDevMode ? [{ debugName: "props" }] : /* istanbul ignore next */ []));
|
|
975
|
+
const renderer = new FlexViewRenderer({
|
|
976
|
+
content: content,
|
|
977
|
+
props: props,
|
|
978
|
+
injector: () => this.#injector,
|
|
979
|
+
templateRef: this.#templateRef,
|
|
980
|
+
viewContainerRef: this.#viewContainerRef,
|
|
981
|
+
});
|
|
982
|
+
renderer.mount();
|
|
983
|
+
inject(DestroyRef).onDestroy(() => {
|
|
984
|
+
renderer.destroy();
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: FlexRenderCell, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
988
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.13", type: FlexRenderCell, isStandalone: true, selector: "ng-template[flexRenderCell], ng-template[flexRenderFooter], ng-template[flexRenderHeader]", inputs: { cell: { classPropertyName: "cell", publicName: "flexRenderCell", isSignal: true, isRequired: false, transformFunction: null }, header: { classPropertyName: "header", publicName: "flexRenderHeader", isSignal: true, isRequired: false, transformFunction: null }, footer: { classPropertyName: "footer", publicName: "flexRenderFooter", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
989
|
+
}
|
|
990
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: FlexRenderCell, decorators: [{
|
|
991
|
+
type: Directive,
|
|
992
|
+
args: [{
|
|
993
|
+
selector: 'ng-template[flexRenderCell], ng-template[flexRenderFooter], ng-template[flexRenderHeader]',
|
|
994
|
+
}]
|
|
995
|
+
}], ctorParameters: () => [], propDecorators: { cell: [{ type: i0.Input, args: [{ isSignal: true, alias: "flexRenderCell", required: false }] }], header: [{ type: i0.Input, args: [{ isSignal: true, alias: "flexRenderHeader", required: false }] }], footer: [{ type: i0.Input, args: [{ isSignal: true, alias: "flexRenderFooter", required: false }] }] } });
|
|
996
|
+
|
|
997
|
+
/**
|
|
998
|
+
* Use this utility directive to render headers, cells, or footers with custom markup.
|
|
999
|
+
*
|
|
1000
|
+
* Note: If you are rendering cell, header, or footer without custom context or other props,
|
|
1001
|
+
* you can use the {@link FlexRenderCell} directive as shorthand instead .
|
|
1002
|
+
*
|
|
1003
|
+
* @example
|
|
1004
|
+
* ```ts
|
|
1005
|
+
* import {FlexRender} from '@tanstack/angular-table';
|
|
1006
|
+
*
|
|
1007
|
+
* @Component({
|
|
1008
|
+
* imports: [FlexRender],
|
|
1009
|
+
* template: `
|
|
1010
|
+
* <td
|
|
1011
|
+
* *flexRender="
|
|
1012
|
+
* cell.column.columnDef.cell;
|
|
1013
|
+
* props: cell.getContext();
|
|
1014
|
+
* let cell"
|
|
1015
|
+
* >
|
|
1016
|
+
* {{cell}}
|
|
1017
|
+
* </td>
|
|
1018
|
+
*
|
|
1019
|
+
* <th
|
|
1020
|
+
* *flexRender="
|
|
1021
|
+
* header.column.columnDef.header;
|
|
1022
|
+
* props: header.getContext();
|
|
1023
|
+
* let header"
|
|
1024
|
+
* >
|
|
1025
|
+
* {{header}}
|
|
1026
|
+
* </td>
|
|
1027
|
+
*
|
|
1028
|
+
* <td
|
|
1029
|
+
* *flexRender="
|
|
1030
|
+
* footer.column.columnDef.footer;
|
|
1031
|
+
* props: footer.getContext();
|
|
1032
|
+
* let footer"
|
|
1033
|
+
* >
|
|
1034
|
+
* {{footer}}
|
|
1035
|
+
* </td>
|
|
1036
|
+
* `,
|
|
1037
|
+
* })
|
|
1038
|
+
* class App {
|
|
1039
|
+
* }
|
|
1040
|
+
* ```
|
|
1041
|
+
*
|
|
1042
|
+
* Can be imported through {@link FlexRenderDirective} or {@link FlexRender} import,
|
|
1043
|
+
* which the latter is preferred.
|
|
1044
|
+
*/
|
|
1045
|
+
class FlexRenderDirective {
|
|
1046
|
+
content = input(undefined, { ...(ngDevMode ? { debugName: "content" } : /* istanbul ignore next */ {}), alias: 'flexRender' });
|
|
1047
|
+
props = input({}, { ...(ngDevMode ? { debugName: "props" } : /* istanbul ignore next */ {}), alias: 'flexRenderProps' });
|
|
1048
|
+
injector = input(inject(Injector), { ...(ngDevMode ? { debugName: "injector" } : /* istanbul ignore next */ {}), alias: 'flexRenderInjector' });
|
|
1049
|
+
#viewContainerRef = inject(ViewContainerRef);
|
|
1050
|
+
#templateRef = inject(TemplateRef);
|
|
1051
|
+
constructor() {
|
|
1052
|
+
const renderer = new FlexViewRenderer({
|
|
1053
|
+
content: this.content,
|
|
1054
|
+
props: this.props,
|
|
1055
|
+
injector: this.injector,
|
|
1056
|
+
templateRef: this.#templateRef,
|
|
1057
|
+
viewContainerRef: this.#viewContainerRef,
|
|
1058
|
+
});
|
|
1059
|
+
renderer.mount();
|
|
1060
|
+
inject(DestroyRef).onDestroy(() => {
|
|
1061
|
+
renderer.destroy();
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: FlexRenderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1065
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.13", type: FlexRenderDirective, isStandalone: true, selector: "ng-template[flexRender]", inputs: { content: { classPropertyName: "content", publicName: "flexRender", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "flexRenderProps", isSignal: true, isRequired: false, transformFunction: null }, injector: { classPropertyName: "injector", publicName: "flexRenderInjector", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
1066
|
+
}
|
|
1067
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: FlexRenderDirective, decorators: [{
|
|
1068
|
+
type: Directive,
|
|
1069
|
+
args: [{
|
|
1070
|
+
selector: 'ng-template[flexRender]',
|
|
1071
|
+
}]
|
|
1072
|
+
}], ctorParameters: () => [], propDecorators: { content: [{ type: i0.Input, args: [{ isSignal: true, alias: "flexRender", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "flexRenderProps", required: false }] }], injector: [{ type: i0.Input, args: [{ isSignal: true, alias: "flexRenderInjector", required: false }] }] } });
|
|
5
1073
|
|
|
6
1074
|
/**
|
|
7
1075
|
* Implementation from @tanstack/angular-query
|
|
8
|
-
* {
|
|
1076
|
+
* {https://github.com/TanStack/query/blob/main/packages/angular-query-experimental/src/util/lazy-init/lazy-init.ts}
|
|
9
1077
|
*/
|
|
10
1078
|
function lazyInit(initializer) {
|
|
11
1079
|
let object = null;
|
|
@@ -45,44 +1113,133 @@ function lazyInit(initializer) {
|
|
|
45
1113
|
});
|
|
46
1114
|
}
|
|
47
1115
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
|
|
1116
|
+
const optionsStoreDebugName = 'table/optionsStore';
|
|
1117
|
+
function observerToCallback(observerOrNext) {
|
|
1118
|
+
return typeof observerOrNext === 'function'
|
|
1119
|
+
? observerOrNext
|
|
1120
|
+
: (value) => observerOrNext.next?.(value);
|
|
1121
|
+
}
|
|
1122
|
+
function signalToReadonlyAtom(source, getSource, subscribeSource) {
|
|
1123
|
+
return Object.assign(source, {
|
|
1124
|
+
get: () => {
|
|
1125
|
+
const value = getSource();
|
|
1126
|
+
source();
|
|
1127
|
+
return value;
|
|
1128
|
+
},
|
|
1129
|
+
subscribe: subscribeSource,
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
function signalToWritableAtom(source, injector) {
|
|
1133
|
+
const observers = new Set();
|
|
1134
|
+
let observed = false;
|
|
1135
|
+
effect(() => {
|
|
1136
|
+
const value = source();
|
|
1137
|
+
if (!observed) {
|
|
1138
|
+
observed = true;
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
observers.forEach((observer) => observer(value));
|
|
1142
|
+
}, { injector });
|
|
1143
|
+
return Object.assign(source.asReadonly(), {
|
|
1144
|
+
set: (updater) => {
|
|
1145
|
+
typeof updater === 'function'
|
|
1146
|
+
? source.update(updater)
|
|
1147
|
+
: source.set(updater);
|
|
1148
|
+
},
|
|
1149
|
+
get: () => source(),
|
|
1150
|
+
subscribe: ((observerOrNext) => {
|
|
1151
|
+
const observer = observerToCallback(observerOrNext);
|
|
1152
|
+
observers.add(observer);
|
|
1153
|
+
return {
|
|
1154
|
+
unsubscribe: () => observers.delete(observer),
|
|
1155
|
+
};
|
|
1156
|
+
}),
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Creates the table-core reactivity bindings used by the Angular adapter.
|
|
1161
|
+
*
|
|
1162
|
+
* Table state atoms are backed by TanStack Store atoms. The options store stays
|
|
1163
|
+
* framework-native because row-model APIs read `table.options` directly during
|
|
1164
|
+
* render. Readonly table atoms bridge Store dependency tracking into Angular
|
|
1165
|
+
* computed signals.
|
|
1166
|
+
*/
|
|
1167
|
+
function angularReactivity(injector) {
|
|
1168
|
+
const ngZone = injector.get(NgZone);
|
|
1169
|
+
const destroyRef = injector.get(DestroyRef);
|
|
1170
|
+
return {
|
|
1171
|
+
createOptionsStore: true,
|
|
1172
|
+
schedule: (fn) => ngZone.runOutsideAngular(() => queueMicrotask(fn)),
|
|
1173
|
+
createReadonlyAtom: (fn, options) => {
|
|
1174
|
+
const storeAtom = createAtom(() => fn(), {
|
|
1175
|
+
compare: options?.compare,
|
|
1176
|
+
});
|
|
1177
|
+
const version = signal(0, { ...(ngDevMode ? { debugName: "version" } : /* istanbul ignore next */ {}), equal: () => false });
|
|
1178
|
+
const subscription = storeAtom.subscribe(() => {
|
|
1179
|
+
version.update((value) => value + 1);
|
|
1180
|
+
});
|
|
1181
|
+
destroyRef.onDestroy(() => subscription.unsubscribe());
|
|
1182
|
+
const value = computed(() => {
|
|
1183
|
+
version();
|
|
1184
|
+
return storeAtom.get();
|
|
1185
|
+
}, {
|
|
1186
|
+
equal: options?.compare,
|
|
1187
|
+
debugName: options?.debugName,
|
|
1188
|
+
});
|
|
1189
|
+
return signalToReadonlyAtom(value, () => storeAtom.get(), (observerOrNext) => {
|
|
1190
|
+
const observer = observerToCallback(observerOrNext);
|
|
1191
|
+
return storeAtom.subscribe(() => {
|
|
1192
|
+
observer(storeAtom.get());
|
|
1193
|
+
});
|
|
1194
|
+
});
|
|
53
1195
|
},
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
|
|
1196
|
+
createWritableAtom: (value, options) => {
|
|
1197
|
+
if (options?.debugName === optionsStoreDebugName) {
|
|
1198
|
+
const writableSignal = signal(value, {
|
|
1199
|
+
equal: options.compare,
|
|
1200
|
+
debugName: options.debugName,
|
|
1201
|
+
});
|
|
1202
|
+
return signalToWritableAtom(writableSignal, injector);
|
|
57
1203
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
1204
|
+
return createAtom(value, {
|
|
1205
|
+
compare: options?.compare,
|
|
1206
|
+
});
|
|
1207
|
+
},
|
|
1208
|
+
untrack: untracked,
|
|
1209
|
+
batch,
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
function createStateProxy(table) {
|
|
1214
|
+
const getSnapshot = () => {
|
|
1215
|
+
const snapshot = {};
|
|
1216
|
+
const stateKeys = Object.keys(table.initialState);
|
|
1217
|
+
for (const key of stateKeys) {
|
|
1218
|
+
snapshot[key] = table.atoms[key].get();
|
|
1219
|
+
}
|
|
1220
|
+
return snapshot;
|
|
1221
|
+
};
|
|
1222
|
+
const target = {};
|
|
1223
|
+
return new Proxy(target, {
|
|
1224
|
+
get(target, prop, receiver) {
|
|
1225
|
+
if (prop === 'toJSON') {
|
|
1226
|
+
return getSnapshot;
|
|
75
1227
|
}
|
|
76
|
-
|
|
77
|
-
|
|
1228
|
+
if (typeof prop === 'string' && prop in table.initialState) {
|
|
1229
|
+
return table.atoms[prop]?.get();
|
|
1230
|
+
}
|
|
1231
|
+
return Reflect.get(target, prop, receiver);
|
|
78
1232
|
},
|
|
79
1233
|
has(_, prop) {
|
|
80
|
-
return
|
|
1234
|
+
return typeof prop === 'string' && prop in table.initialState;
|
|
81
1235
|
},
|
|
82
1236
|
ownKeys() {
|
|
83
|
-
return Reflect.ownKeys(
|
|
1237
|
+
return Reflect.ownKeys(table.initialState);
|
|
84
1238
|
},
|
|
85
|
-
getOwnPropertyDescriptor() {
|
|
1239
|
+
getOwnPropertyDescriptor(_, prop) {
|
|
1240
|
+
if (typeof prop !== 'string' || !(prop in table.initialState)) {
|
|
1241
|
+
return undefined;
|
|
1242
|
+
}
|
|
86
1243
|
return {
|
|
87
1244
|
enumerable: true,
|
|
88
1245
|
configurable: true,
|
|
@@ -91,235 +1248,193 @@ function proxifyTable(tableSignal) {
|
|
|
91
1248
|
});
|
|
92
1249
|
}
|
|
93
1250
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* -
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
1251
|
+
* Creates and returns an Angular-reactive table instance.
|
|
1252
|
+
*
|
|
1253
|
+
* The initializer is intentionally re-evaluated whenever any signal read inside it changes.
|
|
1254
|
+
* This is how the adapter keeps the table in sync with Angular's reactivity model.
|
|
1255
|
+
*
|
|
1256
|
+
* Because of that behavior, keep expensive/static values (for example `columns`, feature setup, row models)
|
|
1257
|
+
* as stable references outside the initializer, and only read reactive state (`data()`, pagination/filter/sorting signals, etc.)
|
|
1258
|
+
* inside it.
|
|
1259
|
+
*
|
|
1260
|
+
* The returned table is also signal-reactive: table state and table APIs are wired for Angular signals, so you can safely consume table methods inside `computed(...)` and `effect(...)`.
|
|
1261
|
+
*
|
|
1262
|
+
* @example
|
|
1263
|
+
* 1. Register the table features you need
|
|
1264
|
+
* ```ts
|
|
1265
|
+
* // Register only the features you need
|
|
1266
|
+
* import {tableFeatures, rowPaginationFeature} from '@tanstack/angular-table';
|
|
1267
|
+
* const _features = tableFeatures({
|
|
1268
|
+
* rowPaginationFeature,
|
|
1269
|
+
* // ...all other features you need
|
|
1270
|
+
* })
|
|
1271
|
+
*
|
|
1272
|
+
* // Use all table core features
|
|
1273
|
+
* import {stockFeatures} from '@tanstack/angular-table';
|
|
1274
|
+
* const _features = tableFeatures(stockFeatures);
|
|
1275
|
+
* ```
|
|
1276
|
+
* 2. Prepare the table columns
|
|
1277
|
+
* ```ts
|
|
1278
|
+
* import {ColumnDef} from '@tanstack/angular-table';
|
|
1279
|
+
*
|
|
1280
|
+
* type MyData = {}
|
|
1281
|
+
*
|
|
1282
|
+
* const columns: ColumnDef<typeof _features, MyData>[] = [
|
|
1283
|
+
* // ...column definitions
|
|
1284
|
+
* ]
|
|
1285
|
+
*
|
|
1286
|
+
* // or using createColumnHelper
|
|
1287
|
+
* import {createColumnHelper} from '@tanstack/angular-table';
|
|
1288
|
+
* const columnHelper = createColumnHelper<typeof _features, MyData>();
|
|
1289
|
+
* const columns = columnHelper.columns([
|
|
1290
|
+
* columnHelper.accessor(...),
|
|
1291
|
+
* // ...other columns
|
|
1292
|
+
* ])
|
|
1293
|
+
* ```
|
|
1294
|
+
* 3. Create the table instance with `injectTable`
|
|
1295
|
+
* ```ts
|
|
1296
|
+
* const table = injectTable(() => {
|
|
1297
|
+
* // ...table options,
|
|
1298
|
+
* _features,
|
|
1299
|
+
* columns: columns,
|
|
1300
|
+
* data: myDataSignal(),
|
|
1301
|
+
* })
|
|
1302
|
+
* ```
|
|
1303
|
+
*
|
|
1304
|
+
* @returns An Angular-reactive TanStack Table instance.
|
|
103
1305
|
*/
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
1306
|
+
function injectTable(options) {
|
|
1307
|
+
assertInInjectionContext(injectTable);
|
|
1308
|
+
const injector = inject(Injector);
|
|
1309
|
+
const ngZone = inject(NgZone);
|
|
1310
|
+
return ngZone.runOutsideAngular(() => lazyInit(() => {
|
|
1311
|
+
const table = constructTable({
|
|
1312
|
+
...options(),
|
|
1313
|
+
_features: {
|
|
1314
|
+
coreReativityFeature: angularReactivity(injector),
|
|
1315
|
+
...options()._features,
|
|
1316
|
+
},
|
|
110
1317
|
});
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const computedSignal = computed(() => {
|
|
119
|
-
void signal();
|
|
120
|
-
return fn(...argsArray);
|
|
1318
|
+
const stateProxy = createStateProxy(table);
|
|
1319
|
+
Object.defineProperty(table, 'state', {
|
|
1320
|
+
get() {
|
|
1321
|
+
return stateProxy;
|
|
1322
|
+
},
|
|
1323
|
+
configurable: true,
|
|
1324
|
+
enumerable: true,
|
|
121
1325
|
});
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
1326
|
+
let isMount = true;
|
|
1327
|
+
effect(() => {
|
|
1328
|
+
const newOptions = options();
|
|
1329
|
+
if (isMount) {
|
|
1330
|
+
isMount = false;
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
untracked(() => table.setOptions((previous) => ({
|
|
1334
|
+
...previous,
|
|
1335
|
+
...newOptions,
|
|
1336
|
+
})));
|
|
1337
|
+
}, { injector, debugName: 'tableOptionsUpdate' });
|
|
1338
|
+
return table;
|
|
1339
|
+
}));
|
|
128
1340
|
}
|
|
129
1341
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
render() {
|
|
153
|
-
this.viewContainerRef.clear();
|
|
154
|
-
const { content, props } = this;
|
|
155
|
-
if (!this.content) {
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
if (typeof content === 'string' || typeof content === 'number') {
|
|
159
|
-
return this.renderStringContent();
|
|
160
|
-
}
|
|
161
|
-
if (typeof content === 'function') {
|
|
162
|
-
return this.renderContent(content(props));
|
|
163
|
-
}
|
|
164
|
-
return null;
|
|
1342
|
+
/**
|
|
1343
|
+
* Creates app-scoped Angular table helpers with features, row models, and
|
|
1344
|
+
* renderable component maps pre-bound.
|
|
1345
|
+
*
|
|
1346
|
+
* Use this when an app or design system wants typed `injectAppTable`,
|
|
1347
|
+
* pre-bound column helpers, and typed table/cell/header context injection
|
|
1348
|
+
* helpers without repeating the same feature and component generics.
|
|
1349
|
+
*
|
|
1350
|
+
* @example
|
|
1351
|
+
* ```ts
|
|
1352
|
+
* const { injectAppTable, createAppColumnHelper } = createTableHook({
|
|
1353
|
+
* _features,
|
|
1354
|
+
* _rowModels: {},
|
|
1355
|
+
* tableComponents: {},
|
|
1356
|
+
* cellComponents: {},
|
|
1357
|
+
* headerComponents: {},
|
|
1358
|
+
* })
|
|
1359
|
+
* ```
|
|
1360
|
+
*/
|
|
1361
|
+
function createTableHook({ tableComponents, cellComponents, headerComponents, ...defaultTableOptions }) {
|
|
1362
|
+
function injectTableContext$1() {
|
|
1363
|
+
return injectTableContext();
|
|
165
1364
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return this.renderStringContent();
|
|
169
|
-
}
|
|
170
|
-
if (content instanceof TemplateRef) {
|
|
171
|
-
return this.viewContainerRef.createEmbeddedView(content, this.getTemplateRefContext());
|
|
172
|
-
}
|
|
173
|
-
else if (content instanceof FlexRenderComponent) {
|
|
174
|
-
return this.renderComponent(content);
|
|
175
|
-
}
|
|
176
|
-
else if (content instanceof Type) {
|
|
177
|
-
return this.renderCustomComponent(content);
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
1365
|
+
function injectTableHeaderContext$1() {
|
|
1366
|
+
return injectTableHeaderContext();
|
|
182
1367
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return typeof this.content === 'string' ||
|
|
186
|
-
typeof this.content === 'number'
|
|
187
|
-
? this.content
|
|
188
|
-
: this.content?.(this.props);
|
|
189
|
-
};
|
|
190
|
-
return this.viewContainerRef.createEmbeddedView(this.templateRef, {
|
|
191
|
-
get $implicit() {
|
|
192
|
-
return context();
|
|
193
|
-
},
|
|
194
|
-
});
|
|
1368
|
+
function injectTableCellContext$1() {
|
|
1369
|
+
return injectTableCellContext();
|
|
195
1370
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const getContext = () => this.props;
|
|
199
|
-
const proxy = new Proxy(this.props, {
|
|
200
|
-
get: (_, key) => getContext()?.[key],
|
|
201
|
-
});
|
|
202
|
-
const componentInjector = Injector.create({
|
|
203
|
-
parent: injector ?? this.injector,
|
|
204
|
-
providers: [{ provide: FlexRenderComponentProps, useValue: proxy }],
|
|
205
|
-
});
|
|
206
|
-
const componentRef = this.viewContainerRef.createComponent(component, {
|
|
207
|
-
injector: componentInjector,
|
|
208
|
-
});
|
|
209
|
-
for (const prop in inputs) {
|
|
210
|
-
if (componentRef.instance?.hasOwnProperty(prop)) {
|
|
211
|
-
componentRef.setInput(prop, inputs[prop]);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return componentRef;
|
|
1371
|
+
function injectFlexRenderHeaderContext() {
|
|
1372
|
+
return injectFlexRenderContext();
|
|
215
1373
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
injector: this.injector,
|
|
219
|
-
});
|
|
220
|
-
for (const prop in this.props) {
|
|
221
|
-
// Only signal based input can be added here
|
|
222
|
-
if (componentRef.instance?.hasOwnProperty(prop) &&
|
|
223
|
-
// @ts-ignore
|
|
224
|
-
isSignal(componentRef.instance[prop])) {
|
|
225
|
-
componentRef.setInput(prop, this.props[prop]);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
return componentRef;
|
|
1374
|
+
function injectFlexRenderCellContext() {
|
|
1375
|
+
return injectFlexRenderContext();
|
|
229
1376
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
1377
|
+
function injectAppTable(tableOptions) {
|
|
1378
|
+
function appCell(cell) {
|
|
1379
|
+
return cell;
|
|
1380
|
+
}
|
|
1381
|
+
function appHeader(header) {
|
|
1382
|
+
return header;
|
|
1383
|
+
}
|
|
1384
|
+
function appFooter(footer) {
|
|
1385
|
+
return footer;
|
|
1386
|
+
}
|
|
1387
|
+
const appTableFeatures = {
|
|
1388
|
+
constructTableAPIs: (table) => {
|
|
1389
|
+
Object.assign(table, tableComponents, { appCell, appHeader, appFooter });
|
|
1390
|
+
},
|
|
1391
|
+
assignCellPrototype(prototype) {
|
|
1392
|
+
Object.assign(prototype, cellComponents);
|
|
1393
|
+
},
|
|
1394
|
+
assignHeaderPrototype(prototype) {
|
|
1395
|
+
Object.assign(prototype, headerComponents);
|
|
235
1396
|
},
|
|
236
1397
|
};
|
|
237
|
-
|
|
238
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.3", ngImport: i0, type: FlexRenderDirective, deps: [{ token: ViewContainerRef }, { token: TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
|
|
239
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.0.3", type: FlexRenderDirective, isStandalone: true, selector: "[flexRender]", inputs: { content: ["flexRender", "content"], props: ["flexRenderProps", "props"], injector: ["flexRenderInjector", "injector"] }, ngImport: i0 });
|
|
240
|
-
}
|
|
241
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.3", ngImport: i0, type: FlexRenderDirective, decorators: [{
|
|
242
|
-
type: Directive,
|
|
243
|
-
args: [{
|
|
244
|
-
selector: '[flexRender]',
|
|
245
|
-
standalone: true,
|
|
246
|
-
}]
|
|
247
|
-
}], ctorParameters: () => [{ type: i0.ViewContainerRef, decorators: [{
|
|
248
|
-
type: Inject,
|
|
249
|
-
args: [ViewContainerRef]
|
|
250
|
-
}] }, { type: i0.TemplateRef, decorators: [{
|
|
251
|
-
type: Inject,
|
|
252
|
-
args: [TemplateRef]
|
|
253
|
-
}] }], propDecorators: { content: [{
|
|
254
|
-
type: Input,
|
|
255
|
-
args: [{ required: true, alias: 'flexRender' }]
|
|
256
|
-
}], props: [{
|
|
257
|
-
type: Input,
|
|
258
|
-
args: [{ required: true, alias: 'flexRenderProps' }]
|
|
259
|
-
}], injector: [{
|
|
260
|
-
type: Input,
|
|
261
|
-
args: [{ required: false, alias: 'flexRenderInjector' }]
|
|
262
|
-
}] } });
|
|
263
|
-
class FlexRenderComponent {
|
|
264
|
-
component;
|
|
265
|
-
inputs;
|
|
266
|
-
injector;
|
|
267
|
-
constructor(component, inputs = {}, injector) {
|
|
268
|
-
this.component = component;
|
|
269
|
-
this.inputs = inputs;
|
|
270
|
-
this.injector = injector;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
const FlexRenderComponentProps = new InjectionToken('[@tanstack/angular-table] Flex render component context props');
|
|
274
|
-
function injectFlexRenderContext() {
|
|
275
|
-
return inject(FlexRenderComponentProps);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function createAngularTable(options) {
|
|
279
|
-
return lazyInit(() => {
|
|
280
|
-
const resolvedOptions = {
|
|
281
|
-
state: {},
|
|
282
|
-
onStateChange: () => { },
|
|
283
|
-
renderFallbackValue: null,
|
|
284
|
-
...options(),
|
|
285
|
-
};
|
|
286
|
-
const table = createTable(resolvedOptions);
|
|
287
|
-
// By default, manage table state here using the table's initial state
|
|
288
|
-
const state = signal(table.initialState);
|
|
289
|
-
// Compose table options using computed.
|
|
290
|
-
// This is to allow `tableSignal` to listen and set table option
|
|
291
|
-
const updatedOptions = computed(() => {
|
|
292
|
-
// listen to table state changed
|
|
293
|
-
const tableState = state();
|
|
294
|
-
// listen to input options changed
|
|
295
|
-
const tableOptions = options();
|
|
1398
|
+
return injectTable(() => {
|
|
296
1399
|
return {
|
|
297
|
-
...
|
|
298
|
-
...
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const value = updater instanceof Function ? updater(tableState) : updater;
|
|
303
|
-
state.set(value);
|
|
304
|
-
resolvedOptions.onStateChange?.(updater);
|
|
1400
|
+
...defaultTableOptions,
|
|
1401
|
+
...tableOptions(),
|
|
1402
|
+
_features: {
|
|
1403
|
+
...defaultTableOptions._features,
|
|
1404
|
+
appTableFeatures,
|
|
305
1405
|
},
|
|
306
1406
|
};
|
|
307
1407
|
});
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
1408
|
+
}
|
|
1409
|
+
function createAppColumnHelper() {
|
|
1410
|
+
// The runtime implementation is the same - components are attached at render time
|
|
1411
|
+
// This cast provides the enhanced types for column definitions
|
|
1412
|
+
return createColumnHelper();
|
|
1413
|
+
}
|
|
1414
|
+
return {
|
|
1415
|
+
createAppColumnHelper,
|
|
1416
|
+
injectTableContext: injectTableContext$1,
|
|
1417
|
+
injectTableHeaderContext: injectTableHeaderContext$1,
|
|
1418
|
+
injectTableCellContext: injectTableCellContext$1,
|
|
1419
|
+
injectFlexRenderHeaderContext,
|
|
1420
|
+
injectFlexRenderCellContext,
|
|
1421
|
+
injectAppTable,
|
|
1422
|
+
};
|
|
318
1423
|
}
|
|
319
1424
|
|
|
1425
|
+
/**
|
|
1426
|
+
* Constant helper to import FlexRender directives.
|
|
1427
|
+
*
|
|
1428
|
+
* You should prefer to use this constant over importing the directives separately,
|
|
1429
|
+
* as it ensures you always have the correct set of directives over library updates.
|
|
1430
|
+
*
|
|
1431
|
+
* @see {@link FlexRenderDirective} and {@link FlexRenderCell} for more details on the directives included in this export.
|
|
1432
|
+
*/
|
|
1433
|
+
const FlexRender = [FlexRenderDirective, FlexRenderCell];
|
|
1434
|
+
|
|
320
1435
|
/**
|
|
321
1436
|
* Generated bundle index. Do not edit.
|
|
322
1437
|
*/
|
|
323
1438
|
|
|
324
|
-
export {
|
|
1439
|
+
export { FlexRender, FlexRenderCell, FlexRenderComponentInstance, FlexRenderDirective, TanStackTable, TanStackTableCell, TanStackTableCellToken, TanStackTableHeader, TanStackTableHeaderToken, TanStackTableToken, createTableHook, flexRenderComponent, injectFlexRenderContext, injectTable, injectTableCellContext, injectTableContext, injectTableHeaderContext };
|
|
325
1440
|
//# sourceMappingURL=tanstack-angular-table.mjs.map
|