@dynamic-field-kit/angular 1.3.3 → 1.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/CHANGELOG.md +19 -0
- package/README.md +50 -3
- package/dist/README.md +50 -3
- package/dist/components/BaseInput.d.ts +5 -11
- package/dist/components/DynamicInput.d.ts +6 -2
- package/dist/components/FieldInput.d.ts +6 -2
- package/dist/components/MultiFieldInput.d.ts +10 -1
- package/dist/esm2022/components/BaseInput.mjs +8 -17
- package/dist/esm2022/components/DynamicInput.mjs +67 -14
- package/dist/esm2022/components/FieldInput.mjs +39 -20
- package/dist/esm2022/components/MultiFieldInput.mjs +87 -13
- package/dist/esm2022/fieldRegistryToken.mjs +12 -0
- package/dist/esm2022/public-api.mjs +6 -2
- package/dist/esm2022/types/layout.mjs +1 -1
- package/dist/fesm2022/dynamic-field-kit-angular.mjs +208 -61
- package/dist/fesm2022/dynamic-field-kit-angular.mjs.map +1 -1
- package/dist/fieldRegistryToken.d.ts +3 -0
- package/dist/public-api.d.ts +4 -1
- package/dist/types/layout.d.ts +4 -25
- package/package.json +14 -16
- package/src/components/BaseInput.ts +7 -10
- package/src/components/DynamicInput.ts +67 -11
- package/src/components/FieldInput.ts +23 -16
- package/src/components/MultiFieldInput.ts +101 -8
- package/src/fieldRegistryToken.ts +15 -0
- package/src/public-api.ts +40 -24
- package/src/types/layout.ts +14 -36
- package/test/DynamicInput.spec.ts +217 -17
- package/test/FieldInput.spec.ts +131 -29
- package/test/MultiFieldInput.spec.ts +256 -0
- package/test/helpers/renderers.ts +89 -0
- package/test/layout.spec.ts +119 -0
- package/test/publicApi.spec.ts +64 -0
- package/{test.ts → test/setup.ts} +2 -2
- package/test/smoke.spec.ts +27 -0
- package/tsconfig.json +13 -13
- package/tsconfig.spec.json +4 -16
- package/vitest.config.ts +30 -0
- package/angular.json +0 -27
- package/karma.conf.js +0 -37
- package/test/angular.spec.ts +0 -102
- package/test/integration.spec.ts +0 -57
- package/test/layouts.spec.ts +0 -26
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { EventEmitter, Output, Input, Component, ViewContainerRef, ViewChild, ChangeDetectionStrategy, HostListener, NgModule } from '@angular/core';
|
|
2
|
+
import { EventEmitter, Output, Input, Component, InjectionToken, inject, ViewContainerRef, ViewChild, ChangeDetectionStrategy, HostListener, NgModule } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
4
|
import { CommonModule, NgIf, NgClass, NgFor } from '@angular/common';
|
|
5
|
-
import { fieldRegistry, applyComputedValues, canAddGroupItem, canRemoveGroupItem, createGroupItem } from '@dynamic-field-kit/core';
|
|
6
|
-
export { fieldRegistry } from '@dynamic-field-kit/core';
|
|
5
|
+
import { fieldRegistry, applyComputedValues, validateFields, resolveOptions, resolveDisabled, resolveReadOnly, validateField, canAddGroupItem, canRemoveGroupItem, createGroupItem } from '@dynamic-field-kit/core';
|
|
6
|
+
export { FieldRegistry, fieldRegistry, resolveDisabled, resolveOptions, resolveReadOnly, validateField, validateFieldAsync, validateFields, validateFieldsAsync, validators } from '@dynamic-field-kit/core';
|
|
7
7
|
|
|
8
8
|
class BaseInputComponent {
|
|
9
9
|
cdr;
|
|
@@ -12,14 +12,11 @@ class BaseInputComponent {
|
|
|
12
12
|
placeholder;
|
|
13
13
|
required;
|
|
14
14
|
disabled;
|
|
15
|
+
readOnly;
|
|
16
|
+
error;
|
|
15
17
|
options;
|
|
16
18
|
className;
|
|
17
19
|
description;
|
|
18
|
-
errorMessage;
|
|
19
|
-
acceptFile;
|
|
20
|
-
maxLength;
|
|
21
|
-
minNumber;
|
|
22
|
-
maxNumber;
|
|
23
20
|
valueChange = new EventEmitter();
|
|
24
21
|
constructor(cdr) {
|
|
25
22
|
this.cdr = cdr;
|
|
@@ -34,7 +31,7 @@ class BaseInputComponent {
|
|
|
34
31
|
return changes[prop] !== undefined;
|
|
35
32
|
}
|
|
36
33
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: BaseInputComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
37
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: BaseInputComponent, isStandalone: true, selector: "ng-component", inputs: { value: "value", label: "label", placeholder: "placeholder", required: "required", disabled: "disabled",
|
|
34
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: BaseInputComponent, isStandalone: true, selector: "ng-component", inputs: { value: "value", label: "label", placeholder: "placeholder", required: "required", disabled: "disabled", readOnly: "readOnly", error: "error", options: "options", className: "className", description: "description" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true });
|
|
38
35
|
}
|
|
39
36
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: BaseInputComponent, decorators: [{
|
|
40
37
|
type: Component,
|
|
@@ -51,51 +48,65 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
51
48
|
type: Input
|
|
52
49
|
}], disabled: [{
|
|
53
50
|
type: Input
|
|
51
|
+
}], readOnly: [{
|
|
52
|
+
type: Input
|
|
53
|
+
}], error: [{
|
|
54
|
+
type: Input
|
|
54
55
|
}], options: [{
|
|
55
56
|
type: Input
|
|
56
57
|
}], className: [{
|
|
57
58
|
type: Input
|
|
58
59
|
}], description: [{
|
|
59
60
|
type: Input
|
|
60
|
-
}], errorMessage: [{
|
|
61
|
-
type: Input
|
|
62
|
-
}], acceptFile: [{
|
|
63
|
-
type: Input
|
|
64
|
-
}], maxLength: [{
|
|
65
|
-
type: Input
|
|
66
|
-
}], minNumber: [{
|
|
67
|
-
type: Input
|
|
68
|
-
}], maxNumber: [{
|
|
69
|
-
type: Input
|
|
70
61
|
}], valueChange: [{
|
|
71
62
|
type: Output
|
|
72
63
|
}] } });
|
|
73
64
|
|
|
65
|
+
// Injecting this token yields the process-wide singleton by default, so code
|
|
66
|
+
// that never provides it keeps working. Provide it on a component/route to give
|
|
67
|
+
// that subtree an isolated set of renderers:
|
|
68
|
+
//
|
|
69
|
+
// providers: [{ provide: FIELD_REGISTRY, useValue: myScopedRegistry }]
|
|
70
|
+
const FIELD_REGISTRY = new InjectionToken('dfk.FieldRegistry', {
|
|
71
|
+
providedIn: 'root',
|
|
72
|
+
factory: () => fieldRegistry,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Only the framework-agnostic FieldRendererProps keys. Domain-specific props
|
|
76
|
+
// reach the renderer through `extraProps` (FieldDescription.props) instead.
|
|
74
77
|
const KNOWN_PROPS = [
|
|
75
78
|
'value',
|
|
76
79
|
'label',
|
|
77
80
|
'placeholder',
|
|
78
81
|
'required',
|
|
79
82
|
'disabled',
|
|
83
|
+
'readOnly',
|
|
84
|
+
'error',
|
|
80
85
|
'options',
|
|
81
86
|
'className',
|
|
82
87
|
'description',
|
|
83
|
-
'errorMessage',
|
|
84
|
-
'acceptFile',
|
|
85
|
-
'maxLength',
|
|
86
|
-
'minNumber',
|
|
87
|
-
'maxNumber',
|
|
88
88
|
];
|
|
89
89
|
class DynamicInput extends BaseInputComponent {
|
|
90
90
|
type;
|
|
91
|
+
// Extra, framework-agnostic props forwarded verbatim to the renderer.
|
|
92
|
+
extraProps;
|
|
91
93
|
valueChange = new EventEmitter();
|
|
92
94
|
onChange = new EventEmitter();
|
|
95
|
+
registry = inject(FIELD_REGISTRY);
|
|
93
96
|
host;
|
|
94
97
|
compRef;
|
|
95
98
|
inputInstance;
|
|
96
99
|
subscriptions = [];
|
|
100
|
+
// Tracks which KNOWN_PROPS were actually supplied to DynamicInput (via
|
|
101
|
+
// SimpleChanges), independent of TS class-field emit. See applyProps.
|
|
102
|
+
supplied = new Set();
|
|
97
103
|
ngOnChanges(changes) {
|
|
98
104
|
super.ngOnChanges(changes);
|
|
105
|
+
for (const prop of KNOWN_PROPS) {
|
|
106
|
+
if (changes[prop]) {
|
|
107
|
+
this.supplied.add(prop);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
99
110
|
if (this.inputInstance) {
|
|
100
111
|
this.syncPropsToInstance(changes);
|
|
101
112
|
}
|
|
@@ -121,6 +132,9 @@ class DynamicInput extends BaseInputComponent {
|
|
|
121
132
|
this.inputInstance[prop] = this[prop];
|
|
122
133
|
}
|
|
123
134
|
}
|
|
135
|
+
if (changes['extraProps']) {
|
|
136
|
+
this.applyExtraProps(this.inputInstance);
|
|
137
|
+
}
|
|
124
138
|
this.compRef?.changeDetectorRef?.detectChanges();
|
|
125
139
|
}
|
|
126
140
|
cleanup() {
|
|
@@ -137,7 +151,7 @@ class DynamicInput extends BaseInputComponent {
|
|
|
137
151
|
this.inputInstance = undefined;
|
|
138
152
|
}
|
|
139
153
|
render() {
|
|
140
|
-
const Renderer =
|
|
154
|
+
const Renderer = this.registry.get(this.type);
|
|
141
155
|
this.cleanup();
|
|
142
156
|
this.host.clear();
|
|
143
157
|
if (!Renderer) {
|
|
@@ -154,8 +168,16 @@ class DynamicInput extends BaseInputComponent {
|
|
|
154
168
|
this.renderError(`Invalid renderer for type: ${this.type}`);
|
|
155
169
|
}
|
|
156
170
|
}
|
|
171
|
+
// Angular component classes carry a static ɵcmp. Checked instead of
|
|
172
|
+
// reflectComponentType() because the peer range starts at Angular 13 and
|
|
173
|
+
// reflectComponentType is v14+. Plain function renderers have no ɵcmp and
|
|
174
|
+
// fall through to renderFallback. Uses hasOwnProperty (not `in`) so a
|
|
175
|
+
// class that merely extends a component without its own @Component
|
|
176
|
+
// decorator - and would otherwise inherit the parent's static ɵcmp via the
|
|
177
|
+
// prototype chain - is not mistaken for a component in its own right.
|
|
157
178
|
isComponentType(renderer) {
|
|
158
|
-
return (typeof renderer === '
|
|
179
|
+
return (typeof renderer === 'function' &&
|
|
180
|
+
Object.prototype.hasOwnProperty.call(renderer, 'ɵcmp'));
|
|
159
181
|
}
|
|
160
182
|
renderComponent(compType) {
|
|
161
183
|
try {
|
|
@@ -191,25 +213,55 @@ class DynamicInput extends BaseInputComponent {
|
|
|
191
213
|
}
|
|
192
214
|
getFallbackProps() {
|
|
193
215
|
return {
|
|
216
|
+
...this.extraProps,
|
|
194
217
|
value: this.value,
|
|
195
218
|
onValueChange: (v) => this.emitValue(v),
|
|
196
219
|
label: this.label ?? '',
|
|
197
220
|
placeholder: this.placeholder ?? '',
|
|
198
221
|
required: this.required ?? false,
|
|
222
|
+
disabled: this.disabled ?? false,
|
|
223
|
+
readOnly: this.readOnly ?? false,
|
|
224
|
+
error: this.error,
|
|
199
225
|
options: this.options ?? [],
|
|
200
226
|
className: this.className ?? '',
|
|
201
227
|
description: this.description ?? '',
|
|
202
|
-
disabled: this.disabled ?? false,
|
|
203
|
-
errorMessage: this.errorMessage ?? '',
|
|
204
228
|
};
|
|
205
229
|
}
|
|
230
|
+
// Gates on `this.supplied` (populated from SimpleChanges in ngOnChanges),
|
|
231
|
+
// not on `prop in instanceObj` or `prop in this`. Both `in` checks test a
|
|
232
|
+
// TypeScript class-field emit artifact rather than intent: whether a
|
|
233
|
+
// property slot exists at runtime depends on the compile target
|
|
234
|
+
// (useDefineForClassFields is false below ES2022, true at ES2022+), so the
|
|
235
|
+
// same source can behave oppositely between this package's test build and
|
|
236
|
+
// its shipped ES2022 bundle, where every declared field is always an own
|
|
237
|
+
// property. `this.supplied` instead reflects only whether Angular ever
|
|
238
|
+
// fired a SimpleChange for that input, i.e. whether the prop was *bound*
|
|
239
|
+
// at all - it skips only props DynamicInput was never given a binding
|
|
240
|
+
// for. It is NOT a general "renderer defaults survive" guarantee: Angular
|
|
241
|
+
// fires a firstChange SimpleChange for every bound input even when the
|
|
242
|
+
// bound value is undefined, and FieldInput's template binds all
|
|
243
|
+
// KNOWN_PROPS unconditionally, so on the real
|
|
244
|
+
// MultiFieldInput -> FieldInput -> DynamicInput path every prop counts as
|
|
245
|
+
// supplied and renderer-side defaults (e.g. `@Input() label = 'None'`)
|
|
246
|
+
// are still overwritten with undefined. This only helps when DynamicInput
|
|
247
|
+
// is mounted directly with some inputs left unbound.
|
|
206
248
|
applyProps(instance) {
|
|
207
249
|
const instanceObj = instance;
|
|
208
250
|
for (const prop of KNOWN_PROPS) {
|
|
209
|
-
if (
|
|
251
|
+
if (this.supplied.has(prop)) {
|
|
210
252
|
instanceObj[prop] = this[prop];
|
|
211
253
|
}
|
|
212
254
|
}
|
|
255
|
+
this.applyExtraProps(instance);
|
|
256
|
+
}
|
|
257
|
+
applyExtraProps(instance) {
|
|
258
|
+
if (!instance || !this.extraProps) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const instanceObj = instance;
|
|
262
|
+
for (const [key, value] of Object.entries(this.extraProps)) {
|
|
263
|
+
instanceObj[key] = value;
|
|
264
|
+
}
|
|
213
265
|
}
|
|
214
266
|
bindOutputs(instance) {
|
|
215
267
|
const outputNames = [
|
|
@@ -235,7 +287,7 @@ class DynamicInput extends BaseInputComponent {
|
|
|
235
287
|
this.host.element.nativeElement.appendChild(el);
|
|
236
288
|
}
|
|
237
289
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DynamicInput, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
238
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: DynamicInput, isStandalone: true, selector: "dfk-dynamic-input", inputs: { type: "type" }, outputs: { valueChange: "valueChange", onChange: "onChange" }, viewQueries: [{ propertyName: "host", first: true, predicate: ["host"], descendants: true, read: ViewContainerRef }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: `<div #host style="display: contents;"></div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
290
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: DynamicInput, isStandalone: true, selector: "dfk-dynamic-input", inputs: { type: "type", extraProps: "extraProps" }, outputs: { valueChange: "valueChange", onChange: "onChange" }, viewQueries: [{ propertyName: "host", first: true, predicate: ["host"], descendants: true, read: ViewContainerRef }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: `<div #host style="display: contents;"></div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
239
291
|
}
|
|
240
292
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DynamicInput, decorators: [{
|
|
241
293
|
type: Component,
|
|
@@ -248,6 +300,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
248
300
|
}]
|
|
249
301
|
}], propDecorators: { type: [{
|
|
250
302
|
type: Input
|
|
303
|
+
}], extraProps: [{
|
|
304
|
+
type: Input
|
|
251
305
|
}], valueChange: [{
|
|
252
306
|
type: Output
|
|
253
307
|
}], onChange: [{
|
|
@@ -261,8 +315,24 @@ class FieldInput {
|
|
|
261
315
|
cdr;
|
|
262
316
|
fieldDescription;
|
|
263
317
|
value;
|
|
318
|
+
options;
|
|
319
|
+
disabled;
|
|
320
|
+
readOnly;
|
|
321
|
+
error;
|
|
264
322
|
onValueChangeField = new EventEmitter();
|
|
265
323
|
shouldRender = false;
|
|
324
|
+
get resolvedOptions() {
|
|
325
|
+
if (this.options) {
|
|
326
|
+
return this.options;
|
|
327
|
+
}
|
|
328
|
+
if (!this.fieldDescription?.options) {
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
if (typeof this.fieldDescription.options === 'function') {
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
return this.fieldDescription.options;
|
|
335
|
+
}
|
|
266
336
|
constructor(cdr) {
|
|
267
337
|
this.cdr = cdr;
|
|
268
338
|
}
|
|
@@ -270,34 +340,27 @@ class FieldInput {
|
|
|
270
340
|
this.shouldRender = !!this.fieldDescription;
|
|
271
341
|
this.cdr.markForCheck();
|
|
272
342
|
}
|
|
273
|
-
getFieldValue() {
|
|
274
|
-
if (!this.fieldDescription) {
|
|
275
|
-
return undefined;
|
|
276
|
-
}
|
|
277
|
-
if (this.value === undefined && this.fieldDescription.type === 'text') {
|
|
278
|
-
return '';
|
|
279
|
-
}
|
|
280
|
-
return this.value;
|
|
281
|
-
}
|
|
282
343
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: FieldInput, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
283
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: FieldInput, isStandalone: true, selector: "dfk-field-input", inputs: { fieldDescription: "fieldDescription", value: "value" }, outputs: { onValueChangeField: "onValueChangeField" }, usesOnChanges: true, ngImport: i0, template: `
|
|
344
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: FieldInput, isStandalone: true, selector: "dfk-field-input", inputs: { fieldDescription: "fieldDescription", value: "value", options: "options", disabled: "disabled", readOnly: "readOnly", error: "error" }, outputs: { onValueChangeField: "onValueChangeField" }, usesOnChanges: true, ngImport: i0, template: `
|
|
284
345
|
<dfk-dynamic-input
|
|
285
346
|
*ngIf="shouldRender"
|
|
286
347
|
[type]="fieldDescription!.type"
|
|
287
|
-
[value]="
|
|
348
|
+
[value]="value"
|
|
288
349
|
[label]="fieldDescription!.label"
|
|
289
350
|
[placeholder]="fieldDescription!.placeholder"
|
|
290
351
|
[required]="fieldDescription!.required"
|
|
291
352
|
[description]="$any(fieldDescription!.description)"
|
|
292
|
-
[options]="
|
|
353
|
+
[options]="resolvedOptions"
|
|
293
354
|
[className]="fieldDescription!.className"
|
|
294
355
|
(valueChange)="
|
|
295
356
|
onValueChangeField.emit({ value: $event, key: fieldDescription!.name })
|
|
296
357
|
"
|
|
297
|
-
[disabled]="
|
|
298
|
-
[
|
|
358
|
+
[disabled]="disabled"
|
|
359
|
+
[readOnly]="readOnly"
|
|
360
|
+
[error]="$any(error)"
|
|
361
|
+
[extraProps]="fieldDescription!.props"
|
|
299
362
|
></dfk-dynamic-input>
|
|
300
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: DynamicInput, selector: "dfk-dynamic-input", inputs: ["type"], outputs: ["valueChange", "onChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
363
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: DynamicInput, selector: "dfk-dynamic-input", inputs: ["type", "extraProps"], outputs: ["valueChange", "onChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
301
364
|
}
|
|
302
365
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: FieldInput, decorators: [{
|
|
303
366
|
type: Component,
|
|
@@ -310,18 +373,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
310
373
|
<dfk-dynamic-input
|
|
311
374
|
*ngIf="shouldRender"
|
|
312
375
|
[type]="fieldDescription!.type"
|
|
313
|
-
[value]="
|
|
376
|
+
[value]="value"
|
|
314
377
|
[label]="fieldDescription!.label"
|
|
315
378
|
[placeholder]="fieldDescription!.placeholder"
|
|
316
379
|
[required]="fieldDescription!.required"
|
|
317
380
|
[description]="$any(fieldDescription!.description)"
|
|
318
|
-
[options]="
|
|
381
|
+
[options]="resolvedOptions"
|
|
319
382
|
[className]="fieldDescription!.className"
|
|
320
383
|
(valueChange)="
|
|
321
384
|
onValueChangeField.emit({ value: $event, key: fieldDescription!.name })
|
|
322
385
|
"
|
|
323
|
-
[disabled]="
|
|
324
|
-
[
|
|
386
|
+
[disabled]="disabled"
|
|
387
|
+
[readOnly]="readOnly"
|
|
388
|
+
[error]="$any(error)"
|
|
389
|
+
[extraProps]="fieldDescription!.props"
|
|
325
390
|
></dfk-dynamic-input>
|
|
326
391
|
`,
|
|
327
392
|
}]
|
|
@@ -329,6 +394,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
329
394
|
type: Input
|
|
330
395
|
}], value: [{
|
|
331
396
|
type: Input
|
|
397
|
+
}], options: [{
|
|
398
|
+
type: Input
|
|
399
|
+
}], disabled: [{
|
|
400
|
+
type: Input
|
|
401
|
+
}], readOnly: [{
|
|
402
|
+
type: Input
|
|
403
|
+
}], error: [{
|
|
404
|
+
type: Input
|
|
332
405
|
}], onValueChangeField: [{
|
|
333
406
|
type: Output
|
|
334
407
|
}] } });
|
|
@@ -339,7 +412,12 @@ class MultiFieldInput {
|
|
|
339
412
|
fieldDescriptions = [];
|
|
340
413
|
properties;
|
|
341
414
|
onChange = new EventEmitter();
|
|
415
|
+
validityChange = new EventEmitter();
|
|
342
416
|
layout = 'column';
|
|
417
|
+
// Top-level form data, threaded down through repeatable groups so a nested
|
|
418
|
+
// field's appearCondition/computeValue can read the root form. Omitted at
|
|
419
|
+
// the top level, where the form's own data is the root.
|
|
420
|
+
rootData;
|
|
343
421
|
data = {};
|
|
344
422
|
visibleFields = [];
|
|
345
423
|
isMobile = false;
|
|
@@ -383,6 +461,18 @@ class MultiFieldInput {
|
|
|
383
461
|
trackByIndex(index) {
|
|
384
462
|
return index;
|
|
385
463
|
}
|
|
464
|
+
groupTrackByCache = new WeakMap();
|
|
465
|
+
// Returns a stable trackBy per group field: item[keyField] when configured,
|
|
466
|
+
// else the index. Cached by field reference so the template hands Angular the
|
|
467
|
+
// same function each change-detection pass.
|
|
468
|
+
groupTrackBy(field) {
|
|
469
|
+
let fn = this.groupTrackByCache.get(field);
|
|
470
|
+
if (!fn) {
|
|
471
|
+
fn = (index, item) => field.keyField ? item[field.keyField] ?? index : index;
|
|
472
|
+
this.groupTrackByCache.set(field, fn);
|
|
473
|
+
}
|
|
474
|
+
return fn;
|
|
475
|
+
}
|
|
386
476
|
ngOnInit() {
|
|
387
477
|
this.updateIsMobile();
|
|
388
478
|
this.init();
|
|
@@ -394,19 +484,25 @@ class MultiFieldInput {
|
|
|
394
484
|
const breakpoint = typeof this.layout === 'object' && this.layout.type === 'responsive'
|
|
395
485
|
? this.layout.breakpoint ?? DEFAULT_BREAKPOINT
|
|
396
486
|
: DEFAULT_BREAKPOINT;
|
|
397
|
-
|
|
398
|
-
typeof window
|
|
487
|
+
if (typeof window !== 'undefined' &&
|
|
488
|
+
typeof window.matchMedia === 'function') {
|
|
489
|
+
this.isMobile = window.matchMedia(`(max-width: ${breakpoint - 1}px)`).matches;
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
this.isMobile =
|
|
493
|
+
typeof window !== 'undefined' && window.innerWidth < breakpoint;
|
|
494
|
+
}
|
|
399
495
|
}
|
|
400
496
|
init() {
|
|
401
497
|
if (this.properties) {
|
|
402
|
-
this.data = applyComputedValues(this.fieldDescriptions, {
|
|
403
|
-
...this.properties,
|
|
404
|
-
});
|
|
498
|
+
this.data = applyComputedValues(this.fieldDescriptions, { ...this.properties }, this.rootData);
|
|
405
499
|
}
|
|
406
500
|
this.updateVisibleFields();
|
|
501
|
+
this.validityChange.emit(validateFields(this.fieldDescriptions, this.data, this.rootData));
|
|
407
502
|
}
|
|
408
503
|
updateVisibleFields() {
|
|
409
|
-
|
|
504
|
+
const root = this.rootData ?? this.data;
|
|
505
|
+
this.visibleFields = this.fieldDescriptions.filter((f) => !f.appearCondition || f.appearCondition(this.data, root));
|
|
410
506
|
}
|
|
411
507
|
onFieldChange(event) {
|
|
412
508
|
this.commitData({ ...this.data, [event.key]: event.value });
|
|
@@ -415,6 +511,22 @@ class MultiFieldInput {
|
|
|
415
511
|
const value = this.data[field.name];
|
|
416
512
|
return Array.isArray(value) ? value : [];
|
|
417
513
|
}
|
|
514
|
+
getResolvedOptions(field) {
|
|
515
|
+
return resolveOptions(field, this.data, this.rootData);
|
|
516
|
+
}
|
|
517
|
+
getDisabled(field) {
|
|
518
|
+
return resolveDisabled(field, this.data, this.rootData);
|
|
519
|
+
}
|
|
520
|
+
getReadOnly(field) {
|
|
521
|
+
return resolveReadOnly(field, this.data, this.rootData);
|
|
522
|
+
}
|
|
523
|
+
getError(field) {
|
|
524
|
+
if (this.getDisabled(field)) {
|
|
525
|
+
return undefined;
|
|
526
|
+
}
|
|
527
|
+
const errors = validateField(field, this.data[field.name], this.data, this.rootData);
|
|
528
|
+
return errors.length > 0 ? errors : undefined;
|
|
529
|
+
}
|
|
418
530
|
canAddItem(field) {
|
|
419
531
|
return canAddGroupItem(field, this.getItems(field));
|
|
420
532
|
}
|
|
@@ -444,13 +556,14 @@ class MultiFieldInput {
|
|
|
444
556
|
this.commitData({ ...this.data, [field.name]: items });
|
|
445
557
|
}
|
|
446
558
|
commitData(nextData) {
|
|
447
|
-
this.data = applyComputedValues(this.fieldDescriptions, nextData);
|
|
559
|
+
this.data = applyComputedValues(this.fieldDescriptions, nextData, this.rootData);
|
|
448
560
|
this.updateVisibleFields();
|
|
449
561
|
this.onChange.emit(this.data);
|
|
562
|
+
this.validityChange.emit(validateFields(this.fieldDescriptions, this.data, this.rootData));
|
|
450
563
|
this.cdr.markForCheck();
|
|
451
564
|
}
|
|
452
565
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MultiFieldInput, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
453
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: MultiFieldInput, isStandalone: true, selector: "dfk-multi-field-input", inputs: { fieldDescriptions: "fieldDescriptions", properties: "properties", layout: "layout" }, outputs: { onChange: "onChange" }, host: { listeners: { "window:resize": "onWindowResize()" } }, usesOnChanges: true, ngImport: i0, template: `
|
|
566
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: MultiFieldInput, isStandalone: true, selector: "dfk-multi-field-input", inputs: { fieldDescriptions: "fieldDescriptions", properties: "properties", layout: "layout", rootData: "rootData" }, outputs: { onChange: "onChange", validityChange: "validityChange" }, host: { listeners: { "window:resize": "onWindowResize()" } }, usesOnChanges: true, ngImport: i0, template: `
|
|
454
567
|
<div
|
|
455
568
|
class="p-4 border rounded-lg bg-gray-50"
|
|
456
569
|
[ngClass]="{
|
|
@@ -468,6 +581,10 @@ class MultiFieldInput {
|
|
|
468
581
|
*ngIf="!field.fields"
|
|
469
582
|
[fieldDescription]="field"
|
|
470
583
|
[value]="data[field.name]"
|
|
584
|
+
[options]="getResolvedOptions(field)"
|
|
585
|
+
[disabled]="getDisabled(field)"
|
|
586
|
+
[readOnly]="getReadOnly(field)"
|
|
587
|
+
[error]="getError(field)"
|
|
471
588
|
(onValueChangeField)="onFieldChange($event)"
|
|
472
589
|
></dfk-field-input>
|
|
473
590
|
|
|
@@ -477,7 +594,7 @@ class MultiFieldInput {
|
|
|
477
594
|
*ngFor="
|
|
478
595
|
let item of getItems(field);
|
|
479
596
|
let i = index;
|
|
480
|
-
trackBy:
|
|
597
|
+
trackBy: groupTrackBy(field)
|
|
481
598
|
"
|
|
482
599
|
style="display: flex; align-items: flex-start; gap: 8px;"
|
|
483
600
|
>
|
|
@@ -485,11 +602,19 @@ class MultiFieldInput {
|
|
|
485
602
|
<dfk-multi-field-input
|
|
486
603
|
[fieldDescriptions]="field.fields"
|
|
487
604
|
[properties]="item"
|
|
605
|
+
[rootData]="rootData ?? data"
|
|
488
606
|
(onChange)="onGroupItemChange(field, i, $event)"
|
|
489
607
|
></dfk-multi-field-input>
|
|
490
608
|
</div>
|
|
491
609
|
<button
|
|
492
610
|
type="button"
|
|
611
|
+
[attr.aria-label]="
|
|
612
|
+
(field.removeLabel || 'Remove') +
|
|
613
|
+
' ' +
|
|
614
|
+
(field.label || field.name) +
|
|
615
|
+
' ' +
|
|
616
|
+
(i + 1)
|
|
617
|
+
"
|
|
493
618
|
(click)="onGroupItemRemove(field, i)"
|
|
494
619
|
[disabled]="!canRemoveItem(field)"
|
|
495
620
|
>
|
|
@@ -498,6 +623,9 @@ class MultiFieldInput {
|
|
|
498
623
|
</div>
|
|
499
624
|
<button
|
|
500
625
|
type="button"
|
|
626
|
+
[attr.aria-label]="
|
|
627
|
+
(field.addLabel || 'Add') + ' ' + (field.label || field.name)
|
|
628
|
+
"
|
|
501
629
|
(click)="onGroupItemAdd(field)"
|
|
502
630
|
[disabled]="!canAddItem(field)"
|
|
503
631
|
>
|
|
@@ -506,7 +634,7 @@ class MultiFieldInput {
|
|
|
506
634
|
</div>
|
|
507
635
|
</ng-container>
|
|
508
636
|
</div>
|
|
509
|
-
`, isInline: true, dependencies: [{ kind: "component", type: MultiFieldInput, selector: "dfk-multi-field-input", inputs: ["fieldDescriptions", "properties", "layout"], outputs: ["onChange"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: FieldInput, selector: "dfk-field-input", inputs: ["fieldDescription", "value"], outputs: ["onValueChangeField"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
637
|
+
`, isInline: true, dependencies: [{ kind: "component", type: MultiFieldInput, selector: "dfk-multi-field-input", inputs: ["fieldDescriptions", "properties", "layout", "rootData"], outputs: ["onChange", "validityChange"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: FieldInput, selector: "dfk-field-input", inputs: ["fieldDescription", "value", "options", "disabled", "readOnly", "error"], outputs: ["onValueChangeField"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
510
638
|
}
|
|
511
639
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MultiFieldInput, decorators: [{
|
|
512
640
|
type: Component,
|
|
@@ -539,6 +667,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
539
667
|
*ngIf="!field.fields"
|
|
540
668
|
[fieldDescription]="field"
|
|
541
669
|
[value]="data[field.name]"
|
|
670
|
+
[options]="getResolvedOptions(field)"
|
|
671
|
+
[disabled]="getDisabled(field)"
|
|
672
|
+
[readOnly]="getReadOnly(field)"
|
|
673
|
+
[error]="getError(field)"
|
|
542
674
|
(onValueChangeField)="onFieldChange($event)"
|
|
543
675
|
></dfk-field-input>
|
|
544
676
|
|
|
@@ -548,7 +680,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
548
680
|
*ngFor="
|
|
549
681
|
let item of getItems(field);
|
|
550
682
|
let i = index;
|
|
551
|
-
trackBy:
|
|
683
|
+
trackBy: groupTrackBy(field)
|
|
552
684
|
"
|
|
553
685
|
style="display: flex; align-items: flex-start; gap: 8px;"
|
|
554
686
|
>
|
|
@@ -556,11 +688,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
556
688
|
<dfk-multi-field-input
|
|
557
689
|
[fieldDescriptions]="field.fields"
|
|
558
690
|
[properties]="item"
|
|
691
|
+
[rootData]="rootData ?? data"
|
|
559
692
|
(onChange)="onGroupItemChange(field, i, $event)"
|
|
560
693
|
></dfk-multi-field-input>
|
|
561
694
|
</div>
|
|
562
695
|
<button
|
|
563
696
|
type="button"
|
|
697
|
+
[attr.aria-label]="
|
|
698
|
+
(field.removeLabel || 'Remove') +
|
|
699
|
+
' ' +
|
|
700
|
+
(field.label || field.name) +
|
|
701
|
+
' ' +
|
|
702
|
+
(i + 1)
|
|
703
|
+
"
|
|
564
704
|
(click)="onGroupItemRemove(field, i)"
|
|
565
705
|
[disabled]="!canRemoveItem(field)"
|
|
566
706
|
>
|
|
@@ -569,6 +709,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
569
709
|
</div>
|
|
570
710
|
<button
|
|
571
711
|
type="button"
|
|
712
|
+
[attr.aria-label]="
|
|
713
|
+
(field.addLabel || 'Add') + ' ' + (field.label || field.name)
|
|
714
|
+
"
|
|
572
715
|
(click)="onGroupItemAdd(field)"
|
|
573
716
|
[disabled]="!canAddItem(field)"
|
|
574
717
|
>
|
|
@@ -585,8 +728,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
585
728
|
type: Input
|
|
586
729
|
}], onChange: [{
|
|
587
730
|
type: Output
|
|
731
|
+
}], validityChange: [{
|
|
732
|
+
type: Output
|
|
588
733
|
}], layout: [{
|
|
589
734
|
type: Input
|
|
735
|
+
}], rootData: [{
|
|
736
|
+
type: Input
|
|
590
737
|
}], onWindowResize: [{
|
|
591
738
|
type: HostListener,
|
|
592
739
|
args: ['window:resize']
|
|
@@ -767,5 +914,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
767
914
|
* Generated bundle index. Do not edit.
|
|
768
915
|
*/
|
|
769
916
|
|
|
770
|
-
export { BaseInputComponent, ColumnLayout, DynamicFieldKitModule, DynamicInput, FieldInput, GridLayout, LayoutRegistry, MultiFieldInput, RowLayout, layoutRegistry };
|
|
917
|
+
export { BaseInputComponent, ColumnLayout, DynamicFieldKitModule, DynamicInput, FIELD_REGISTRY, FieldInput, GridLayout, LayoutRegistry, MultiFieldInput, RowLayout, layoutRegistry };
|
|
771
918
|
//# sourceMappingURL=dynamic-field-kit-angular.mjs.map
|