xt-components 0.4.0
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 +24 -0
- package/fesm2022/xt-components.mjs +1053 -0
- package/fesm2022/xt-components.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/lib/angular/xt-resolver.service.d.ts +26 -0
- package/lib/angular/xt-tokens.d.ts +7 -0
- package/lib/output/xt-base-output.d.ts +6 -0
- package/lib/plugin/xt-plugin-info.d.ts +15 -0
- package/lib/registry/xt-plugin-registry.d.ts +34 -0
- package/lib/render/xt-render-sub.component.d.ts +22 -0
- package/lib/render/xt-render.component.d.ts +30 -0
- package/lib/resolver/xt-registry-resolver.d.ts +11 -0
- package/lib/resolver/xt-resolver.d.ts +5 -0
- package/lib/test/xt-test-helper-components.d.ts +71 -0
- package/lib/test/xt-unit-test-helper.d.ts +2 -0
- package/lib/type/xt-type-resolver.d.ts +42 -0
- package/lib/xt-component.d.ts +20 -0
- package/lib/xt-composite/xt-composite.component.d.ts +21 -0
- package/lib/xt-context.d.ts +84 -0
- package/lib/xt-resolved-component.d.ts +8 -0
- package/lib/xt-simple/xt-simple.component.d.ts +42 -0
- package/package.json +32 -0
- package/public-api.d.ts +15 -0
|
@@ -0,0 +1,1053 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { computed, signal, InjectionToken, inject, Injectable, input, model, output, viewChild, Component } from '@angular/core';
|
|
3
|
+
import * as i1 from '@angular/forms';
|
|
4
|
+
import { FormControl, FormGroup, ReactiveFormsModule, FormBuilder, FormArray } from '@angular/forms';
|
|
5
|
+
import { NgComponentOutlet, CommonModule } from '@angular/common';
|
|
6
|
+
|
|
7
|
+
class XtBaseContext {
|
|
8
|
+
/**
|
|
9
|
+
*
|
|
10
|
+
* @param displayMode
|
|
11
|
+
* @param readOnly
|
|
12
|
+
* @param parentGroup
|
|
13
|
+
* @param controlName
|
|
14
|
+
*/
|
|
15
|
+
constructor(displayMode, subName, parentGroup, parentContext) {
|
|
16
|
+
this.displayMode = 'FULL_VIEW';
|
|
17
|
+
this.displayValue = computed(() => {
|
|
18
|
+
if (this.nonFormValue !== undefined) {
|
|
19
|
+
return this.nonFormValue();
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
throw new Error("Cannot display a value that does not exist. Are you sure you're not using Reactive Form with this context? " + this.toString());
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
this.displayMode = displayMode;
|
|
26
|
+
this.parentFormGroup = parentGroup;
|
|
27
|
+
this.parentContext = parentContext;
|
|
28
|
+
this.subName = subName;
|
|
29
|
+
}
|
|
30
|
+
setDisplayValue(newValue, type, updateParent = true) {
|
|
31
|
+
if (newValue !== undefined) {
|
|
32
|
+
const oldValue = this.nonFormValue ? this.nonFormValue() : null;
|
|
33
|
+
if (this.nonFormValue == null) {
|
|
34
|
+
if ((this.childContexts != null) && (this.childContexts.size > 0))
|
|
35
|
+
throw new Error('An XtContext with no values cannot have children ', { cause: this });
|
|
36
|
+
this.nonFormValue = signal(newValue);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
this.nonFormValue.set(newValue);
|
|
40
|
+
}
|
|
41
|
+
// Change the children values if needed
|
|
42
|
+
if (this.childContexts != null) {
|
|
43
|
+
if (newValue == null) {
|
|
44
|
+
for (const [subName, child] of this.childContexts) {
|
|
45
|
+
child.setDisplayValue(null, undefined, false);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else if (oldValue != null) {
|
|
49
|
+
for (const [subName, child] of this.childContexts) {
|
|
50
|
+
if (newValue[subName] != oldValue[subName]) {
|
|
51
|
+
// The value has changed, let's update
|
|
52
|
+
child.setDisplayValue(newValue[subName], undefined, false);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if ((updateParent) && (this.subName != null))
|
|
58
|
+
this.parentContext?.updateSubDisplayValue(this.subName, newValue);
|
|
59
|
+
}
|
|
60
|
+
if (type !== undefined)
|
|
61
|
+
this.valueType = type;
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
isInForm() {
|
|
65
|
+
return ((this.subName != null) && (this.formGroup() != null));
|
|
66
|
+
}
|
|
67
|
+
formControlNameOrNull() {
|
|
68
|
+
return this.subName ?? null;
|
|
69
|
+
}
|
|
70
|
+
value() {
|
|
71
|
+
if (this.nonFormValue != null)
|
|
72
|
+
return this.nonFormValue() ?? this.formControlValue();
|
|
73
|
+
else
|
|
74
|
+
return this.formControlValue();
|
|
75
|
+
}
|
|
76
|
+
subValue(subsubName) {
|
|
77
|
+
const value = this.nonFormValue ? this.nonFormValue() : this.formControlValue();
|
|
78
|
+
if ((subsubName != null) && (value != null)) {
|
|
79
|
+
return value[subsubName];
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Enable child contexts to update its own value in the parent context whenever it's value changes
|
|
87
|
+
*/
|
|
88
|
+
updateSubDisplayValue(subName, subValue) {
|
|
89
|
+
if (this.nonFormValue != null) {
|
|
90
|
+
let newValue = this.nonFormValue();
|
|
91
|
+
if (newValue == null) {
|
|
92
|
+
const newValue = {};
|
|
93
|
+
newValue[subName] = subValue;
|
|
94
|
+
this.nonFormValue.set(newValue);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
newValue[subName] = subValue; // Discretly update the subValue without triggering parent signal
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
throw new Error("No nonFormValue to update subDisplayValue" + this.toString());
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
formControlValue() {
|
|
105
|
+
let ret = undefined;
|
|
106
|
+
if (this.isInForm()) {
|
|
107
|
+
if (this.subName != null) {
|
|
108
|
+
//console.debug ("formControlValue parentGroup value is ", this.parentFormGroup?.value);
|
|
109
|
+
ret = this.parentFormGroup?.value[this.subName];
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
ret = this.parentFormGroup?.value;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
ret = undefined;
|
|
117
|
+
}
|
|
118
|
+
//console.debug("formControlValue of "+this.subName+ " is ",ret);
|
|
119
|
+
return ret;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Returns the context associated with a specific element in a set.
|
|
123
|
+
* Value must be an array.
|
|
124
|
+
* @param elementIndex
|
|
125
|
+
*/
|
|
126
|
+
elementSetContext(elementIndex) {
|
|
127
|
+
const value = this.value();
|
|
128
|
+
if (!Array.isArray(this.value())) {
|
|
129
|
+
throw new Error("The value must be an Array / Set to create a subElement context.");
|
|
130
|
+
}
|
|
131
|
+
const ret = new XtBaseContext(this.displayMode, undefined, undefined, this);
|
|
132
|
+
ret.setDisplayValue(value[elementIndex]);
|
|
133
|
+
if (this.valueType != null) {
|
|
134
|
+
// Convert potential array type into single type
|
|
135
|
+
ret.valueType = this.valueType.endsWith('[]') ? this.valueType.substring(0, this.valueType.length - 2) : this.valueType;
|
|
136
|
+
}
|
|
137
|
+
return ret;
|
|
138
|
+
}
|
|
139
|
+
subContext(subName, subType, typeResolver) {
|
|
140
|
+
if ((subName == null) || (subName.length == 0)) {
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
else if (this.childContexts?.has(subName)) {
|
|
144
|
+
return this.childContexts?.get(subName);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
let subValue = null;
|
|
148
|
+
let parentGroup = this.formGroup();
|
|
149
|
+
// Recalculate parentGroup and formControlName and value if needed.
|
|
150
|
+
if (parentGroup == null) {
|
|
151
|
+
let curValue = this.nonFormValue;
|
|
152
|
+
if (curValue != null) {
|
|
153
|
+
if (curValue() != null) {
|
|
154
|
+
subValue = signal(curValue()[subName]);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (subValue == null) {
|
|
158
|
+
subValue = signal(null);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const ret = new XtBaseContext(this.displayMode, subName, parentGroup, this);
|
|
162
|
+
if (subValue != null)
|
|
163
|
+
ret.nonFormValue = subValue;
|
|
164
|
+
if (subType != null) {
|
|
165
|
+
ret.valueType = subType;
|
|
166
|
+
}
|
|
167
|
+
else if ((this.valueType != null) && (typeResolver != null)) {
|
|
168
|
+
ret.valueType = typeResolver.findType(this, subName, this.value()) ?? undefined;
|
|
169
|
+
}
|
|
170
|
+
if (this.childContexts == null)
|
|
171
|
+
this.childContexts = new Map();
|
|
172
|
+
this.childContexts.set(subName, ret);
|
|
173
|
+
return ret;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
formGroup() {
|
|
177
|
+
return this.localFormGroup ?? this.parentFormGroup;
|
|
178
|
+
}
|
|
179
|
+
toString() {
|
|
180
|
+
let ret = 'XtContext named ';
|
|
181
|
+
ret += this.subName ?? 'None';
|
|
182
|
+
ret += ' with type ';
|
|
183
|
+
ret += this.valueType ?? 'None';
|
|
184
|
+
ret += ' with value ';
|
|
185
|
+
ret += this.nonFormValue ? this.nonFormValue() : this.formControlValue();
|
|
186
|
+
return ret;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
class XtResolvedComponent {
|
|
191
|
+
constructor(componantName, componentClass, outputs = false) {
|
|
192
|
+
this.componentName = componantName;
|
|
193
|
+
this.componentClass = componentClass;
|
|
194
|
+
this.outputs = outputs;
|
|
195
|
+
}
|
|
196
|
+
static from(info) {
|
|
197
|
+
const ret = new XtResolvedComponent(info.componentName, info.componentClass);
|
|
198
|
+
if ((info.outputs != null) && (info.outputs.length > 0)) {
|
|
199
|
+
ret.outputs = true;
|
|
200
|
+
}
|
|
201
|
+
return ret;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
class XtPluginRegistry {
|
|
206
|
+
constructor() {
|
|
207
|
+
this.pluginRegistry = new Map();
|
|
208
|
+
this.componentRegistry = new Map();
|
|
209
|
+
this.componentByTypeCache = new Map();
|
|
210
|
+
this.listComponents = signal(new Array());
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* The component can manage any standard javascript primitives types. That's usually the default whenever we don't know any particular type
|
|
214
|
+
* string
|
|
215
|
+
* number
|
|
216
|
+
* bigint
|
|
217
|
+
* boolean
|
|
218
|
+
* undefined
|
|
219
|
+
* null
|
|
220
|
+
* symbol is not managed
|
|
221
|
+
* Date, while an object and not a primitive, is managed
|
|
222
|
+
*/
|
|
223
|
+
static { this.ANY_PRIMITIVE_TYPE = "ANY_PRIMITIVE_TYPE"; }
|
|
224
|
+
/**
|
|
225
|
+
* The components can manage any composite javascript type. Default when no type has been defined and it's a user defined javascript object (not a data type)
|
|
226
|
+
*/
|
|
227
|
+
static { this.ANY_OBJECT_TYPE = "ANY_OBJECT_TYPE"; }
|
|
228
|
+
static { this.ANY_PRIMITIVE_SET = "ANY_PRIMITIVE_SET"; }
|
|
229
|
+
static { this.ANY_OBJECT_SET = "ANY_OBJECT_SET"; }
|
|
230
|
+
registerPlugin(info) {
|
|
231
|
+
this.pluginRegistry.set(info.name, info);
|
|
232
|
+
if (info.components != null) {
|
|
233
|
+
let updated = false;
|
|
234
|
+
for (const comp of info.components) {
|
|
235
|
+
updated = true;
|
|
236
|
+
this.registerComponent(comp);
|
|
237
|
+
}
|
|
238
|
+
if (updated)
|
|
239
|
+
this.componentByTypeCache.clear(); // Force recalculation of type
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
registerComponent(info) {
|
|
243
|
+
this.componentRegistry.set(info.componentName, info);
|
|
244
|
+
this.listComponents.update((array) => {
|
|
245
|
+
let found = false;
|
|
246
|
+
for (let i = 0; i < array.length; i++) {
|
|
247
|
+
if (array[i].componentName == info.componentName) {
|
|
248
|
+
found = true;
|
|
249
|
+
array[i] = info;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (!found)
|
|
253
|
+
array.push(info);
|
|
254
|
+
return array;
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
findComponentsForType(valueType, value) {
|
|
258
|
+
let originalType = valueType;
|
|
259
|
+
//console.debug('Finding type from '+valueType+' with value ',value);
|
|
260
|
+
// We don't know the value type, let's try to guess if it's a primitive or object based on the value
|
|
261
|
+
if (valueType == null) {
|
|
262
|
+
valueType = XtPluginRegistry.ANY_OBJECT_TYPE;
|
|
263
|
+
if ((value == null) || (typeof value != 'object')) {
|
|
264
|
+
valueType = XtPluginRegistry.ANY_PRIMITIVE_TYPE;
|
|
265
|
+
}
|
|
266
|
+
else if (value instanceof Date) {
|
|
267
|
+
valueType = XtPluginRegistry.ANY_PRIMITIVE_TYPE;
|
|
268
|
+
}
|
|
269
|
+
if (Array.isArray(value)) {
|
|
270
|
+
valueType = (valueType === XtPluginRegistry.ANY_PRIMITIVE_TYPE) ? XtPluginRegistry.ANY_PRIMITIVE_SET : XtPluginRegistry.ANY_OBJECT_SET;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else { // originalType has been defined.
|
|
274
|
+
if (Array.isArray(value)) {
|
|
275
|
+
valueType = valueType.endsWith('[]') ? valueType : valueType + '[]';
|
|
276
|
+
originalType = valueType;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
//console.debug('Type found is '+valueType);
|
|
280
|
+
let ret = this.componentByTypeCache.get(valueType);
|
|
281
|
+
if (ret == null) {
|
|
282
|
+
ret = new Array();
|
|
283
|
+
for (const comp of this.componentRegistry) {
|
|
284
|
+
const info = comp[1];
|
|
285
|
+
if (info.typesHandled.includes(valueType)) {
|
|
286
|
+
ret.push(info);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if ((ret.length == 0) && (originalType != null)) {
|
|
290
|
+
// Couldn't find a specific component, let's try the generic ones, so we don't pass any type
|
|
291
|
+
ret = this.findComponentsForType(null, value);
|
|
292
|
+
// Cache the component only if we were able to assert its type.
|
|
293
|
+
// If no type has been given and value is null, then we cannot assess the real type
|
|
294
|
+
if (value != null) {
|
|
295
|
+
this.componentByTypeCache.set(originalType, ret);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
// Cache the component only if we were able to assert its type.
|
|
300
|
+
// If no type has been given and value is null, then we cannot assess the real type
|
|
301
|
+
if ((value != null) || (originalType != null)) {
|
|
302
|
+
this.componentByTypeCache.set(originalType ?? valueType, ret);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return ret;
|
|
307
|
+
}
|
|
308
|
+
static registry() {
|
|
309
|
+
return XT_REGISTRY;
|
|
310
|
+
}
|
|
311
|
+
findComponentInfo(type) {
|
|
312
|
+
// Search for the component registered with this class
|
|
313
|
+
for (const info of this.componentRegistry.values()) {
|
|
314
|
+
if (info.componentClass == type) {
|
|
315
|
+
return info;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
getComponentInfo(type) {
|
|
321
|
+
const ret = this.findComponentInfo(type);
|
|
322
|
+
if (ret == null) {
|
|
323
|
+
throw new Error("No component found with class " + type);
|
|
324
|
+
}
|
|
325
|
+
return ret;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const XT_REGISTRY = new XtPluginRegistry();
|
|
329
|
+
|
|
330
|
+
const XT_RESOLVER_TOKEN = new InjectionToken('Enable providing a custom component resolver.');
|
|
331
|
+
const XT_TYPE_RESOLVER_TOKEN = new InjectionToken('Enable providing a custom type resolver.');
|
|
332
|
+
const XT_REGISTRY_TOKEN = new InjectionToken("Injects the Plugin Registry right into your angular component", {
|
|
333
|
+
factory: () => {
|
|
334
|
+
return XT_REGISTRY;
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
class XtRegistryResolver {
|
|
339
|
+
constructor(registry, typeResolver) {
|
|
340
|
+
this.registry = registry;
|
|
341
|
+
this.typeResolver = typeResolver;
|
|
342
|
+
}
|
|
343
|
+
resolve(baseContext, subName) {
|
|
344
|
+
const ret = this.registry.findComponentsForType(this.typeResolver.findType(baseContext, subName), baseContext.subValue(subName));
|
|
345
|
+
if (ret != null && ret.length > 0) {
|
|
346
|
+
return XtResolvedComponent.from(ret[0]);
|
|
347
|
+
}
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
class XtTypeHierarchyResolver {
|
|
353
|
+
constructor() {
|
|
354
|
+
this.types = new Map();
|
|
355
|
+
}
|
|
356
|
+
addType(typeName, type) {
|
|
357
|
+
this.types.set(typeName, fromDescription(type));
|
|
358
|
+
}
|
|
359
|
+
canUpdate() {
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
findType(typeInfo, subName, value) {
|
|
363
|
+
if (typeInfo == null)
|
|
364
|
+
return typeInfo;
|
|
365
|
+
if (typeInfo.valueType == null)
|
|
366
|
+
return typeInfo.valueType;
|
|
367
|
+
if (subName == null) {
|
|
368
|
+
return typeInfo.valueType;
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
const selectedType = this.types.get(typeInfo.valueType);
|
|
372
|
+
if ((selectedType != null) && (selectedType.children != null)) {
|
|
373
|
+
const type = selectedType.children[subName].type;
|
|
374
|
+
if (type == null) {
|
|
375
|
+
throw new Error('SubType named ' + subName + ' of ' + typeInfo.valueType + ' doesn\'t have a type name.');
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
return type;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return undefined;
|
|
383
|
+
}
|
|
384
|
+
listSubNames(context, value) {
|
|
385
|
+
let ret = [];
|
|
386
|
+
if ((context != null) && (context.valueType != null)) {
|
|
387
|
+
const typeInfo = this.types.get(context.valueType);
|
|
388
|
+
if (typeInfo?.children != null) {
|
|
389
|
+
ret = Object.keys(typeInfo.children);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (ret.length == 0) {
|
|
393
|
+
// We will use the value to extract properties
|
|
394
|
+
if (value != null) {
|
|
395
|
+
if (Array.isArray(value)) {
|
|
396
|
+
if (value.length > 0) {
|
|
397
|
+
const setOfKeys = new Set();
|
|
398
|
+
for (const element of value) {
|
|
399
|
+
const elementKeys = Object.keys(element);
|
|
400
|
+
for (const key of elementKeys) {
|
|
401
|
+
setOfKeys.add(key);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
ret = Array.from(setOfKeys.values());
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
ret = Object.keys(value);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return ret;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
class XtBaseTypeHierarchy {
|
|
416
|
+
constructor(type, parent) {
|
|
417
|
+
this.type = type;
|
|
418
|
+
this.parent = parent;
|
|
419
|
+
}
|
|
420
|
+
addChild(key, child) {
|
|
421
|
+
if (this.children == null)
|
|
422
|
+
this.children = {};
|
|
423
|
+
this.children[key] = child;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function fromDescription(typeHierarchy, name, parent) {
|
|
427
|
+
let ret = null;
|
|
428
|
+
if (typeof typeHierarchy == 'string') {
|
|
429
|
+
ret = new XtBaseTypeHierarchy(typeHierarchy, parent);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
ret = new XtBaseTypeHierarchy(undefined, parent);
|
|
433
|
+
for (const key of Object.keys(typeHierarchy)) {
|
|
434
|
+
const value = typeHierarchy[key];
|
|
435
|
+
fromDescription(value, key, ret);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
if ((parent != null) && (name != null))
|
|
439
|
+
parent.addChild(name, ret);
|
|
440
|
+
else if ((parent != null) && (name == null)) {
|
|
441
|
+
throw new Error("Cannot add type to parent without a name.");
|
|
442
|
+
}
|
|
443
|
+
return ret;
|
|
444
|
+
}
|
|
445
|
+
function updateFormGroupWithValue(formGroup, value) {
|
|
446
|
+
const toDelete = new Set(Object.keys(formGroup.controls));
|
|
447
|
+
for (const valueKey in value) {
|
|
448
|
+
const primitive = isPrimitive(value[valueKey]);
|
|
449
|
+
if (toDelete.delete(valueKey)) {
|
|
450
|
+
// Already a control
|
|
451
|
+
const oldControl = formGroup.get(valueKey);
|
|
452
|
+
// Is it the right type ?
|
|
453
|
+
if (primitive) {
|
|
454
|
+
// Must be an FormControl2
|
|
455
|
+
if (oldControl.controls === undefined) {
|
|
456
|
+
// It's ok, just set the value
|
|
457
|
+
oldControl.setValue(value[valueKey]);
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
formGroup.setControl(valueKey, new FormControl(value[valueKey]));
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
// Must be a FormGroup
|
|
465
|
+
if (oldControl.controls === undefined) {
|
|
466
|
+
const newFormGroup = new FormGroup({});
|
|
467
|
+
formGroup.setControl(valueKey, newFormGroup);
|
|
468
|
+
updateFormGroupWithValue(newFormGroup, value[valueKey]);
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
// It was already a formgroup, so just update it
|
|
472
|
+
updateFormGroupWithValue(oldControl, value[valueKey]);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
if (primitive) {
|
|
478
|
+
formGroup.addControl(valueKey, new FormControl(value[valueKey]));
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
const newFormGroup = new FormGroup({});
|
|
482
|
+
formGroup.addControl(valueKey, newFormGroup);
|
|
483
|
+
updateFormGroupWithValue(newFormGroup, value[valueKey]);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
// Delete controls that are no more used
|
|
488
|
+
for (const delName of toDelete) {
|
|
489
|
+
formGroup.removeControl(delName);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function isPrimitive(valueElement) {
|
|
493
|
+
if (typeof valueElement == 'object') {
|
|
494
|
+
if (valueElement == null)
|
|
495
|
+
return true;
|
|
496
|
+
else {
|
|
497
|
+
return valueElement instanceof Date;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
else
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
class XtResolverService {
|
|
505
|
+
constructor() {
|
|
506
|
+
this.pluginRegistry = inject(XT_REGISTRY_TOKEN);
|
|
507
|
+
this.baseResolver = inject(XT_RESOLVER_TOKEN, { optional: true });
|
|
508
|
+
this.baseTypeResolver = inject(XT_TYPE_RESOLVER_TOKEN, { optional: true });
|
|
509
|
+
this.listComponents = computed(() => {
|
|
510
|
+
return this.pluginRegistry.listComponents();
|
|
511
|
+
});
|
|
512
|
+
if (this.baseTypeResolver == null) {
|
|
513
|
+
this.typeResolver = new XtTypeHierarchyResolver();
|
|
514
|
+
}
|
|
515
|
+
else
|
|
516
|
+
this.typeResolver = this.baseTypeResolver;
|
|
517
|
+
if (this.baseResolver == null) {
|
|
518
|
+
this.resolver = new XtRegistryResolver(this.pluginRegistry, this.typeResolver);
|
|
519
|
+
}
|
|
520
|
+
else
|
|
521
|
+
this.resolver = this.baseResolver;
|
|
522
|
+
}
|
|
523
|
+
findBestComponent(baseContext, subName) {
|
|
524
|
+
const ret = this.resolver.resolve(baseContext, subName);
|
|
525
|
+
if (ret != null)
|
|
526
|
+
return ret;
|
|
527
|
+
else
|
|
528
|
+
throw new Error("No components found for this context " + baseContext.toString());
|
|
529
|
+
}
|
|
530
|
+
findTypeOf(baseContext, subName, value) {
|
|
531
|
+
const ret = this.typeResolver.findType(baseContext, subName, value);
|
|
532
|
+
return ret;
|
|
533
|
+
}
|
|
534
|
+
listSubNamesOf(baseContext, value) {
|
|
535
|
+
return this.typeResolver.listSubNames(baseContext, value);
|
|
536
|
+
}
|
|
537
|
+
registerPlugin(info) {
|
|
538
|
+
this.pluginRegistry.registerPlugin(info);
|
|
539
|
+
this.registerTypes(info.types);
|
|
540
|
+
}
|
|
541
|
+
registerTypes(types) {
|
|
542
|
+
if ((types != null) && (this.typeResolver.canUpdate())) {
|
|
543
|
+
for (const newType in types) {
|
|
544
|
+
this.typeResolver.addType(newType, types[newType]);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
getComponentInfo(type) {
|
|
549
|
+
return XtResolvedComponent.from(this.pluginRegistry.getComponentInfo(type));
|
|
550
|
+
}
|
|
551
|
+
findComponentInfo(type) {
|
|
552
|
+
const ret = this.pluginRegistry.findComponentInfo(type);
|
|
553
|
+
if (ret == null)
|
|
554
|
+
return null;
|
|
555
|
+
else
|
|
556
|
+
return XtResolvedComponent.from(ret);
|
|
557
|
+
}
|
|
558
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: XtResolverService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
559
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: XtResolverService, providedIn: 'root' }); }
|
|
560
|
+
}
|
|
561
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: XtResolverService, decorators: [{
|
|
562
|
+
type: Injectable,
|
|
563
|
+
args: [{
|
|
564
|
+
providedIn: 'root'
|
|
565
|
+
}]
|
|
566
|
+
}], ctorParameters: () => [] });
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Offers a nice and easy to dynamically embed a component.
|
|
570
|
+
* You set the type, the display mode, and either the value or the formgroup & subName to use.
|
|
571
|
+
* XtRender will then instantiate the component, bind it to the value or form, and display it.
|
|
572
|
+
*/
|
|
573
|
+
class XtRenderComponent {
|
|
574
|
+
constructor() {
|
|
575
|
+
this.resolverService = inject(XtResolverService);
|
|
576
|
+
this.componentType = input();
|
|
577
|
+
this.displayMode = input.required();
|
|
578
|
+
this.valueType = input();
|
|
579
|
+
// Either we set the value directly
|
|
580
|
+
this.value = model();
|
|
581
|
+
// Or we are inside a Form
|
|
582
|
+
this.formGroup = input();
|
|
583
|
+
this.subName = input();
|
|
584
|
+
this.outputs = output();
|
|
585
|
+
this.hasOutputs = false;
|
|
586
|
+
this.outlet = viewChild.required(NgComponentOutlet);
|
|
587
|
+
this.context = computed(() => {
|
|
588
|
+
let form = this.formGroup();
|
|
589
|
+
const ret = new XtBaseContext(this.displayMode(), this.subName(), form);
|
|
590
|
+
ret.valueType = this.valueType();
|
|
591
|
+
if (!ret.isInForm()) {
|
|
592
|
+
const subName = this.subName();
|
|
593
|
+
const value = this.value();
|
|
594
|
+
if ((subName == null) || (value == null)) {
|
|
595
|
+
ret.setDisplayValue(value);
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
ret.setDisplayValue(value[subName]);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return ret;
|
|
602
|
+
});
|
|
603
|
+
this.type = computed(() => {
|
|
604
|
+
//console.debug("Calculating type in XtRenderSubComponent");
|
|
605
|
+
let type = this.componentType();
|
|
606
|
+
let compFound = null;
|
|
607
|
+
if (type == null) {
|
|
608
|
+
//console.debug('XtRender, using component set '+ type);
|
|
609
|
+
//compFound = this.resolverService.findComponentInfo (type);
|
|
610
|
+
//} else {
|
|
611
|
+
compFound = this.resolverService.findBestComponent(this.context());
|
|
612
|
+
//console.debug('XtRender, found component ',compFound.componentName);
|
|
613
|
+
type = compFound.componentClass;
|
|
614
|
+
}
|
|
615
|
+
return type ?? null;
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
ngAfterViewInit() {
|
|
619
|
+
const instance = this.outlet().componentInstance;
|
|
620
|
+
if ((instance != null) && (instance.hasOutputs) && (instance.outputs != null)) {
|
|
621
|
+
instance.outputs.subscribe((out) => this.outputs.emit(out));
|
|
622
|
+
this.hasOutputs = true;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: XtRenderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
626
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.2.4", type: XtRenderComponent, isStandalone: true, selector: "xt-render", inputs: { componentType: { classPropertyName: "componentType", publicName: "componentType", isSignal: true, isRequired: false, transformFunction: null }, displayMode: { classPropertyName: "displayMode", publicName: "displayMode", isSignal: true, isRequired: true, transformFunction: null }, valueType: { classPropertyName: "valueType", publicName: "valueType", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, formGroup: { classPropertyName: "formGroup", publicName: "formGroup", isSignal: true, isRequired: false, transformFunction: null }, subName: { classPropertyName: "subName", publicName: "subName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", outputs: "outputs" }, viewQueries: [{ propertyName: "outlet", first: true, predicate: NgComponentOutlet, descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *ngComponentOutlet=\"type(); inputs: {context:context ()}\" />\n", styles: [""], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: ReactiveFormsModule }] }); }
|
|
627
|
+
}
|
|
628
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: XtRenderComponent, decorators: [{
|
|
629
|
+
type: Component,
|
|
630
|
+
args: [{ selector: 'xt-render', standalone: true, imports: [
|
|
631
|
+
NgComponentOutlet,
|
|
632
|
+
ReactiveFormsModule
|
|
633
|
+
], template: "<ng-container *ngComponentOutlet=\"type(); inputs: {context:context ()}\" />\n" }]
|
|
634
|
+
}], ctorParameters: () => [] });
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Dynamically render a component that will display the given subValue.
|
|
638
|
+
* To be used only inside an XtSimpleComponent or XtCompositeComponent
|
|
639
|
+
*/
|
|
640
|
+
class XtRenderSubComponent {
|
|
641
|
+
constructor() {
|
|
642
|
+
this.context = input.required();
|
|
643
|
+
this.componentType = input();
|
|
644
|
+
this.outputs = output();
|
|
645
|
+
this.hasOutputs = false;
|
|
646
|
+
this.outlet = viewChild.required(NgComponentOutlet);
|
|
647
|
+
this.resolverService = inject(XtResolverService);
|
|
648
|
+
this.type = computed(() => {
|
|
649
|
+
//console.debug("Calculating type in XtRenderSubComponent");
|
|
650
|
+
let type = this.componentType();
|
|
651
|
+
let compFound = null;
|
|
652
|
+
if (type == null) {
|
|
653
|
+
//console.debug('XtRender, using component set '+ type);
|
|
654
|
+
//compFound = this.resolverService.findComponentInfo (type);
|
|
655
|
+
//} else {
|
|
656
|
+
compFound = this.resolverService.findBestComponent(this.context());
|
|
657
|
+
//console.debug('XtRender, found component ',compFound.componentName);
|
|
658
|
+
type = compFound.componentClass;
|
|
659
|
+
}
|
|
660
|
+
return type ?? null;
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
ngAfterViewInit() {
|
|
664
|
+
const instance = this.outlet().componentInstance;
|
|
665
|
+
if ((instance != null) && (instance.hasOutputs) && (instance.outputs != null)) {
|
|
666
|
+
instance.outputs.subscribe((out) => this.outputs.emit(out));
|
|
667
|
+
this.hasOutputs = true;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: XtRenderSubComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
671
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.2.4", type: XtRenderSubComponent, isStandalone: true, selector: "xt-render-sub", inputs: { context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: true, transformFunction: null }, componentType: { classPropertyName: "componentType", publicName: "componentType", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { outputs: "outputs" }, viewQueries: [{ propertyName: "outlet", first: true, predicate: NgComponentOutlet, descendants: true, isSignal: true }], ngImport: i0, template: "{{componentType()}}\n<ng-container *ngComponentOutlet=\"type(); inputs: {context:context ()}\" />\n", styles: [""], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: ReactiveFormsModule }] }); }
|
|
672
|
+
}
|
|
673
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: XtRenderSubComponent, decorators: [{
|
|
674
|
+
type: Component,
|
|
675
|
+
args: [{ selector: 'xt-render-sub', standalone: true, imports: [
|
|
676
|
+
NgComponentOutlet,
|
|
677
|
+
ReactiveFormsModule
|
|
678
|
+
], template: "{{componentType()}}\n<ng-container *ngComponentOutlet=\"type(); inputs: {context:context ()}\" />\n" }]
|
|
679
|
+
}] });
|
|
680
|
+
|
|
681
|
+
class XtBaseOutput {
|
|
682
|
+
setNewOutput(name, value) {
|
|
683
|
+
let ret = false;
|
|
684
|
+
if (this[name] == null) {
|
|
685
|
+
this[name] = signal(value);
|
|
686
|
+
ret = true;
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
this[name].set(value);
|
|
690
|
+
}
|
|
691
|
+
return ret;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* An XtSimpleComponent just displays the given value or element in a form.
|
|
697
|
+
* If you need to dynamically embed other XtComponents to display sub elements, then please use the XtCompositeComponent
|
|
698
|
+
*/
|
|
699
|
+
class XtSimpleComponent {
|
|
700
|
+
constructor() {
|
|
701
|
+
this.context = input.required();
|
|
702
|
+
this.outputs = output();
|
|
703
|
+
/**
|
|
704
|
+
* Does the component provides Output or not ?
|
|
705
|
+
* @protected
|
|
706
|
+
*/
|
|
707
|
+
this.hasOutputs = false;
|
|
708
|
+
this.isInForm = computed(() => {
|
|
709
|
+
return this.context()?.isInForm() ?? false;
|
|
710
|
+
});
|
|
711
|
+
this.formControlNameIfAny = computed(() => {
|
|
712
|
+
return this.context()?.subName;
|
|
713
|
+
});
|
|
714
|
+
this.formGroupIfAny = computed(() => {
|
|
715
|
+
return this.context()?.formGroup();
|
|
716
|
+
});
|
|
717
|
+
this.formGroup = computed(() => {
|
|
718
|
+
const ret = this.context()?.formGroup();
|
|
719
|
+
if (ret == null)
|
|
720
|
+
throw new Error('No form groups in this component of type ' + this.componentDescriptor());
|
|
721
|
+
return ret;
|
|
722
|
+
});
|
|
723
|
+
/**
|
|
724
|
+
* Returns the component form name, which is for now the subName
|
|
725
|
+
*/
|
|
726
|
+
this.componentNameInForm = computed(() => {
|
|
727
|
+
return this.safelyGetSubName();
|
|
728
|
+
});
|
|
729
|
+
this.safelyGetSubName = computed(() => {
|
|
730
|
+
const ret = this.context()?.subName;
|
|
731
|
+
if (ret == null)
|
|
732
|
+
throw new Error('This component has no name in the form ' + this.componentDescriptor());
|
|
733
|
+
return ret;
|
|
734
|
+
});
|
|
735
|
+
/**
|
|
736
|
+
* Returns the form control name and create a form control behind the scene
|
|
737
|
+
*/
|
|
738
|
+
this.formControlName = computed(() => {
|
|
739
|
+
const ret = this.safelyGetSubName();
|
|
740
|
+
this.manageFormControl(ret); // Creates the form control
|
|
741
|
+
return ret;
|
|
742
|
+
});
|
|
743
|
+
this.formControl = computed(() => {
|
|
744
|
+
const subName = this.safelyGetSubName();
|
|
745
|
+
const formControl = this.manageFormControl(subName);
|
|
746
|
+
if (formControl == null)
|
|
747
|
+
throw new Error("Calling formControl for subName " + subName + " when none exist.");
|
|
748
|
+
return formControl;
|
|
749
|
+
});
|
|
750
|
+
this.getValue = computed(() => {
|
|
751
|
+
return this.context().value();
|
|
752
|
+
});
|
|
753
|
+
this.displayValue = computed(() => {
|
|
754
|
+
return this.context().displayValue();
|
|
755
|
+
});
|
|
756
|
+
if (this.hasOutputs) {
|
|
757
|
+
if (this.outputElement == null) {
|
|
758
|
+
this.outputElement = new XtBaseOutput();
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
manageFormControl(ctrlName) {
|
|
763
|
+
const formGroup = this.formGroupIfAny();
|
|
764
|
+
if (formGroup == null) {
|
|
765
|
+
// You can call manageFormControl even in not a form, it just get ignored
|
|
766
|
+
//console.debug('FormGroup is undefined when declaring managedcontrol '+ctrlName);
|
|
767
|
+
return undefined;
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
let ctrl = formGroup.get(ctrlName);
|
|
771
|
+
if (ctrl == null) {
|
|
772
|
+
ctrl = new FormControl(undefined);
|
|
773
|
+
formGroup.setControl(ctrlName, ctrl);
|
|
774
|
+
}
|
|
775
|
+
return ctrl;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
componentDescriptor() {
|
|
779
|
+
return "Component with type " + this.constructor.name + " with context " + this.context().toString();
|
|
780
|
+
}
|
|
781
|
+
emitOutput(outputName, newValue) {
|
|
782
|
+
if (!this.hasOutputs) {
|
|
783
|
+
throw new Error("Component without outputs cannot emit output");
|
|
784
|
+
}
|
|
785
|
+
let newOutput = false;
|
|
786
|
+
if (this.outputElement == null) {
|
|
787
|
+
this.outputElement = new XtBaseOutput();
|
|
788
|
+
newOutput = true;
|
|
789
|
+
}
|
|
790
|
+
newOutput = this.outputElement.setNewOutput(outputName, newValue) || newOutput;
|
|
791
|
+
if (newOutput) {
|
|
792
|
+
this.outputs?.emit(this.outputElement);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: XtSimpleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
796
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.4", type: XtSimpleComponent, isStandalone: true, selector: "ng-component", inputs: { context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { outputs: "outputs" }, ngImport: i0, template: '', isInline: true }); }
|
|
797
|
+
}
|
|
798
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: XtSimpleComponent, decorators: [{
|
|
799
|
+
type: Component,
|
|
800
|
+
args: [{
|
|
801
|
+
standalone: true,
|
|
802
|
+
imports: [],
|
|
803
|
+
template: ''
|
|
804
|
+
}]
|
|
805
|
+
}], ctorParameters: () => [] });
|
|
806
|
+
|
|
807
|
+
class XtCompositeComponent extends XtSimpleComponent {
|
|
808
|
+
constructor() {
|
|
809
|
+
super(...arguments);
|
|
810
|
+
this.resolverService = inject(XtResolverService);
|
|
811
|
+
this.formGroupIfAny = computed(() => {
|
|
812
|
+
const context = this.context();
|
|
813
|
+
if (context == null)
|
|
814
|
+
return undefined;
|
|
815
|
+
let ret = context.localFormGroup;
|
|
816
|
+
if ((ret == null) && (context.parentFormGroup != null) && (context.subName != null)) {
|
|
817
|
+
if (context.parentFormGroup.contains(context.subName)) {
|
|
818
|
+
context.localFormGroup = context.parentFormGroup.get(context.subName);
|
|
819
|
+
}
|
|
820
|
+
else {
|
|
821
|
+
context.localFormGroup = new FormGroup({});
|
|
822
|
+
context.parentFormGroup.addControl(context.subName, context.localFormGroup);
|
|
823
|
+
}
|
|
824
|
+
ret = context.localFormGroup;
|
|
825
|
+
}
|
|
826
|
+
return ret;
|
|
827
|
+
});
|
|
828
|
+
/**
|
|
829
|
+
* We need to create a new form group to manage the sub elements.
|
|
830
|
+
*/
|
|
831
|
+
this.formGroup = computed(() => {
|
|
832
|
+
const ret = this.formGroupIfAny();
|
|
833
|
+
if (ret == null)
|
|
834
|
+
throw new Error('No form groups in this component of type ' + this.componentDescriptor());
|
|
835
|
+
return ret;
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Helper function to calculate the sub context
|
|
840
|
+
* @param subName
|
|
841
|
+
* @param subType
|
|
842
|
+
*/
|
|
843
|
+
subContext(subName, subType) {
|
|
844
|
+
this.formGroupIfAny(); // Ensure the context is properly initialized
|
|
845
|
+
return this.context().subContext(subName, subType, this.resolverService.typeResolver);
|
|
846
|
+
}
|
|
847
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: XtCompositeComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
848
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: XtCompositeComponent, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: '', isInline: true, styles: [""] }); }
|
|
849
|
+
}
|
|
850
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: XtCompositeComponent, decorators: [{
|
|
851
|
+
type: Component,
|
|
852
|
+
args: [{ standalone: true, imports: [], template: '' }]
|
|
853
|
+
}] });
|
|
854
|
+
|
|
855
|
+
class XtUnitTestHelper {
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Component that can be used to bootstrap tests.
|
|
860
|
+
* Just set the value and component type, and it will be injected in your test.
|
|
861
|
+
*/
|
|
862
|
+
class HostTestSimpleComponent {
|
|
863
|
+
constructor() {
|
|
864
|
+
this.type = input.required();
|
|
865
|
+
this.displayMode = input('FULL_VIEW');
|
|
866
|
+
this.value = input(undefined);
|
|
867
|
+
}
|
|
868
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HostTestSimpleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
869
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.4", type: HostTestSimpleComponent, isStandalone: true, selector: "test-host", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: true, transformFunction: null }, displayMode: { classPropertyName: "displayMode", publicName: "displayMode", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '<h1>Test Simple Component</h1> <xt-render [componentType]="type()" [displayMode]="displayMode()" [value]="value()" ></xt-render> ', isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: XtRenderComponent, selector: "xt-render", inputs: ["componentType", "displayMode", "valueType", "value", "formGroup", "subName"], outputs: ["valueChange", "outputs"] }] }); }
|
|
870
|
+
}
|
|
871
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HostTestSimpleComponent, decorators: [{
|
|
872
|
+
type: Component,
|
|
873
|
+
args: [{
|
|
874
|
+
selector: 'test-host',
|
|
875
|
+
standalone: true,
|
|
876
|
+
imports: [CommonModule, XtRenderComponent],
|
|
877
|
+
template: '<h1>Test Simple Component</h1> <xt-render [componentType]="type()" [displayMode]="displayMode()" [value]="value()" ></xt-render> '
|
|
878
|
+
}]
|
|
879
|
+
}] });
|
|
880
|
+
/**
|
|
881
|
+
* Same as HostTestSimpleComponent but it includes everything in a form.
|
|
882
|
+
* Just set the component type, the formGroup and the component name, and your component will be run.
|
|
883
|
+
* You can as well easily read and set the value.
|
|
884
|
+
*/
|
|
885
|
+
class HostTestFormComponent {
|
|
886
|
+
constructor() {
|
|
887
|
+
this.builder = inject(FormBuilder);
|
|
888
|
+
this.type = input.required();
|
|
889
|
+
this.controlName = input.required();
|
|
890
|
+
// You can send the description to be used in a FormBuilder to create the formgroup;
|
|
891
|
+
this.formDescription = input({});
|
|
892
|
+
// Or set the FormGroup directly
|
|
893
|
+
this.formGroup = input();
|
|
894
|
+
this.createdFormGroup = null;
|
|
895
|
+
}
|
|
896
|
+
computedFormGroup() {
|
|
897
|
+
if (this.createdFormGroup == null) {
|
|
898
|
+
const formGroup = this.formGroup();
|
|
899
|
+
this.createdFormGroup = formGroup ?? generateFormGroup(this.formDescription());
|
|
900
|
+
}
|
|
901
|
+
return this.createdFormGroup;
|
|
902
|
+
}
|
|
903
|
+
patchValue(newVal) {
|
|
904
|
+
const patch = {};
|
|
905
|
+
patch[this.controlName()] = newVal;
|
|
906
|
+
if (this.createdFormGroup != null)
|
|
907
|
+
this.createdFormGroup.patchValue(patch);
|
|
908
|
+
else
|
|
909
|
+
throw new Error("FormGroup not yet created. Did you set formGroup or formDescription property ?");
|
|
910
|
+
}
|
|
911
|
+
retrieveValue() {
|
|
912
|
+
if (this.createdFormGroup != null)
|
|
913
|
+
return this.createdFormGroup.value[this.controlName()];
|
|
914
|
+
else
|
|
915
|
+
throw new Error("FormGroup not yet created. Did you set formGroup or formDescription property ?");
|
|
916
|
+
}
|
|
917
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HostTestFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
918
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.4", type: HostTestFormComponent, isStandalone: true, selector: "test-form-host", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: true, transformFunction: null }, controlName: { classPropertyName: "controlName", publicName: "controlName", isSignal: true, isRequired: true, transformFunction: null }, formDescription: { classPropertyName: "formDescription", publicName: "formDescription", isSignal: true, isRequired: false, transformFunction: null }, formGroup: { classPropertyName: "formGroup", publicName: "formGroup", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '<h1>Test Form Component</h1> <form [formGroup]="computedFormGroup()"> <xt-render [componentType]="type()" displayMode="FULL_EDITABLE" [subName]="controlName()" [formGroup]="computedFormGroup()"></xt-render></form>', isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: XtRenderComponent, selector: "xt-render", inputs: ["componentType", "displayMode", "valueType", "value", "formGroup", "subName"], outputs: ["valueChange", "outputs"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] }); }
|
|
919
|
+
}
|
|
920
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HostTestFormComponent, decorators: [{
|
|
921
|
+
type: Component,
|
|
922
|
+
args: [{
|
|
923
|
+
selector: 'test-form-host',
|
|
924
|
+
standalone: true,
|
|
925
|
+
imports: [CommonModule, XtRenderComponent, ReactiveFormsModule],
|
|
926
|
+
template: '<h1>Test Form Component</h1> <form [formGroup]="computedFormGroup()"> <xt-render [componentType]="type()" displayMode="FULL_EDITABLE" [subName]="controlName()" [formGroup]="computedFormGroup()"></xt-render></form>'
|
|
927
|
+
}]
|
|
928
|
+
}] });
|
|
929
|
+
/**
|
|
930
|
+
* Component that can be used to test your component based on the type it handles
|
|
931
|
+
* Just set the type hierarchy to register, the value, and it will instantiate the right component in your plugin
|
|
932
|
+
*/
|
|
933
|
+
class HostTestTypedComponent {
|
|
934
|
+
constructor() {
|
|
935
|
+
this.displayMode = input('FULL_VIEW');
|
|
936
|
+
this.value = input();
|
|
937
|
+
this.valueType = input();
|
|
938
|
+
this.context = computed(() => {
|
|
939
|
+
const ret = new XtBaseContext(this.displayMode());
|
|
940
|
+
ret.valueType = this.valueType();
|
|
941
|
+
ret.setDisplayValue(this.value());
|
|
942
|
+
return ret;
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HostTestTypedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
946
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.4", type: HostTestTypedComponent, isStandalone: true, selector: "test-typed-host", inputs: { displayMode: { classPropertyName: "displayMode", publicName: "displayMode", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, valueType: { classPropertyName: "valueType", publicName: "valueType", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '<h1>Test Typed Component</h1> <xt-render-sub [context]="context()" ></xt-render-sub> ', isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: XtRenderSubComponent, selector: "xt-render-sub", inputs: ["context", "componentType"], outputs: ["outputs"] }] }); }
|
|
947
|
+
}
|
|
948
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HostTestTypedComponent, decorators: [{
|
|
949
|
+
type: Component,
|
|
950
|
+
args: [{
|
|
951
|
+
selector: 'test-typed-host',
|
|
952
|
+
standalone: true,
|
|
953
|
+
imports: [CommonModule, XtRenderSubComponent],
|
|
954
|
+
template: '<h1>Test Typed Component</h1> <xt-render-sub [context]="context()" ></xt-render-sub> '
|
|
955
|
+
}]
|
|
956
|
+
}] });
|
|
957
|
+
/**
|
|
958
|
+
* Same as HostTestSimpleComponent but it includes everything in a form.
|
|
959
|
+
* Just set the component type, the formGroup and the component name, and your component will be run.
|
|
960
|
+
* You can as well easily read and set the value.
|
|
961
|
+
*/
|
|
962
|
+
class HostTestTypedFormComponent {
|
|
963
|
+
constructor() {
|
|
964
|
+
this.builder = inject(FormBuilder);
|
|
965
|
+
this.resolver = inject(XtResolverService);
|
|
966
|
+
this.valueType = input();
|
|
967
|
+
this.controlName = input();
|
|
968
|
+
// You can send the description to be used in a FormBuilder to create the formgroup;
|
|
969
|
+
this.formDescription = input({});
|
|
970
|
+
// Or set the FormGroup directly
|
|
971
|
+
this.formGroup = input();
|
|
972
|
+
this.parentFormGroup = this.builder.group({});
|
|
973
|
+
this.createdFormGroup = null;
|
|
974
|
+
this.computedFormGroup = computed(() => {
|
|
975
|
+
if (this.createdFormGroup == null) {
|
|
976
|
+
const formGroup = this.formGroup();
|
|
977
|
+
this.createdFormGroup = formGroup ?? generateFormGroup(this.formDescription());
|
|
978
|
+
this.parentFormGroup.addControl(this.controlName() ?? HostTestTypedFormComponent.CONTROL_NAME, this.createdFormGroup);
|
|
979
|
+
}
|
|
980
|
+
return this.createdFormGroup;
|
|
981
|
+
});
|
|
982
|
+
this.subContext = computed(() => {
|
|
983
|
+
this.computedFormGroup(); // Make sure the subformgroups are created
|
|
984
|
+
const ctrlName = this.controlName();
|
|
985
|
+
let ret = null;
|
|
986
|
+
if (ctrlName == null) {
|
|
987
|
+
ret = new XtBaseContext('FULL_EDITABLE', HostTestTypedFormComponent.CONTROL_NAME, this.parentFormGroup);
|
|
988
|
+
}
|
|
989
|
+
else {
|
|
990
|
+
ret = new XtBaseContext('FULL_EDITABLE', ctrlName, this.createdFormGroup);
|
|
991
|
+
}
|
|
992
|
+
ret.valueType = this.valueType();
|
|
993
|
+
return ret;
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
static { this.CONTROL_NAME = 'ForTest'; }
|
|
997
|
+
patchValue(controlName, newVal) {
|
|
998
|
+
const patch = {};
|
|
999
|
+
patch[controlName] = newVal;
|
|
1000
|
+
this.computedFormGroup().patchValue(patch);
|
|
1001
|
+
}
|
|
1002
|
+
retrieveValue(controlName) {
|
|
1003
|
+
return this.computedFormGroup().value[controlName];
|
|
1004
|
+
}
|
|
1005
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HostTestTypedFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1006
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.4", type: HostTestTypedFormComponent, isStandalone: true, selector: "test-typed-form-host", inputs: { valueType: { classPropertyName: "valueType", publicName: "valueType", isSignal: true, isRequired: false, transformFunction: null }, controlName: { classPropertyName: "controlName", publicName: "controlName", isSignal: true, isRequired: false, transformFunction: null }, formDescription: { classPropertyName: "formDescription", publicName: "formDescription", isSignal: true, isRequired: false, transformFunction: null }, formGroup: { classPropertyName: "formGroup", publicName: "formGroup", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '<h1>Test Typed Form Component</h1> <form [formGroup]="parentFormGroup"> <xt-render-sub [context]="subContext()"></xt-render-sub></form>', isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: XtRenderSubComponent, selector: "xt-render-sub", inputs: ["context", "componentType"], outputs: ["outputs"] }] }); }
|
|
1007
|
+
}
|
|
1008
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: HostTestTypedFormComponent, decorators: [{
|
|
1009
|
+
type: Component,
|
|
1010
|
+
args: [{
|
|
1011
|
+
selector: 'test-typed-form-host',
|
|
1012
|
+
standalone: true,
|
|
1013
|
+
imports: [CommonModule, ReactiveFormsModule, XtRenderSubComponent],
|
|
1014
|
+
template: '<h1>Test Typed Form Component</h1> <form [formGroup]="parentFormGroup"> <xt-render-sub [context]="subContext()"></xt-render-sub></form>'
|
|
1015
|
+
}]
|
|
1016
|
+
}] });
|
|
1017
|
+
function generateFormGroup(formDescription) {
|
|
1018
|
+
if (typeof formDescription != 'object') {
|
|
1019
|
+
throw new Error('Form Description should be an object of values');
|
|
1020
|
+
}
|
|
1021
|
+
return generateFormControl(formDescription);
|
|
1022
|
+
}
|
|
1023
|
+
function generateFormControl(formDescription) {
|
|
1024
|
+
if (formDescription == null) {
|
|
1025
|
+
return new FormControl(formDescription);
|
|
1026
|
+
}
|
|
1027
|
+
if (Array.isArray(formDescription)) {
|
|
1028
|
+
const retArray = new FormArray([]);
|
|
1029
|
+
for (const val of formDescription) {
|
|
1030
|
+
retArray.push(generateFormControl(val), { emitEvent: false });
|
|
1031
|
+
}
|
|
1032
|
+
return retArray;
|
|
1033
|
+
}
|
|
1034
|
+
if ((typeof formDescription == 'object') && (!(formDescription instanceof Date))) {
|
|
1035
|
+
const retObject = new FormGroup({});
|
|
1036
|
+
for (const key of Object.keys(formDescription)) {
|
|
1037
|
+
retObject.addControl(key, generateFormControl(formDescription[key]));
|
|
1038
|
+
}
|
|
1039
|
+
return retObject;
|
|
1040
|
+
}
|
|
1041
|
+
return new FormControl(formDescription);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/*
|
|
1045
|
+
* Public API Surface of xt-components
|
|
1046
|
+
*/
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* Generated bundle index. Do not edit.
|
|
1050
|
+
*/
|
|
1051
|
+
|
|
1052
|
+
export { HostTestFormComponent, HostTestSimpleComponent, HostTestTypedComponent, HostTestTypedFormComponent, XT_REGISTRY, XT_REGISTRY_TOKEN, XT_RESOLVER_TOKEN, XT_TYPE_RESOLVER_TOKEN, XtBaseContext, XtBaseTypeHierarchy, XtCompositeComponent, XtPluginRegistry, XtRenderComponent, XtRenderSubComponent, XtResolvedComponent, XtResolverService, XtSimpleComponent, XtTypeHierarchyResolver, XtUnitTestHelper, fromDescription, updateFormGroupWithValue };
|
|
1053
|
+
//# sourceMappingURL=xt-components.mjs.map
|