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