@dynamic-field-kit/angular 1.5.1 → 1.7.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 +224 -0
- package/README.md +146 -23
- package/dist/fesm2022/dynamic-field-kit-angular.mjs +665 -116
- package/dist/types/dynamic-field-kit-angular.d.ts +404 -0
- package/package.json +25 -26
- package/dist/components/BaseInput.d.ts +0 -34
- package/dist/components/DynamicFormDevTools.d.ts +0 -16
- package/dist/components/DynamicInput.d.ts +0 -36
- package/dist/components/FieldInput.d.ts +0 -28
- package/dist/components/MultiFieldInput.d.ts +0 -60
- package/dist/fieldRegistryToken.d.ts +0 -3
- package/dist/index.d.ts +0 -5
- package/dist/layout/defaultLayouts.d.ts +0 -31
- package/dist/layout/index.d.ts +0 -2
- package/dist/layout/layoutRegistry.d.ts +0 -12
- package/dist/lib/dynamic-field-kit.module.d.ts +0 -11
- package/dist/lib/dynamic-form.store.d.ts +0 -23
- package/dist/public-api.d.ts +0 -14
- package/dist/types/layout.d.ts +0 -4
|
@@ -2,8 +2,8 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { EventEmitter, Output, Input, Component, InjectionToken, inject, ViewContainerRef, ViewChild, ChangeDetectionStrategy, HostListener, signal, computed, NgModule } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
4
|
import { CommonModule, NgIf, NgClass, NgFor, JsonPipe } from '@angular/common';
|
|
5
|
-
import { fieldRegistry,
|
|
6
|
-
export { FieldRegistry, fieldRegistry, resolveDisabled, resolveOptions, resolveReadOnly, validateField, validateFieldAsync, validateFields, validateFieldsAsync, validators } from '@dynamic-field-kit/core';
|
|
5
|
+
import { fieldRegistry, makeErrorId, isAsyncOptions, createOptionsLoader, buildFieldRendererProps, makeFieldId, indexGroupPathMap, applyComputedValues, validateFields, canAddGroupItem, canRemoveGroupItem, createGroupItem, createMessageResolver, validateFieldsAsync, collectFieldPaths } from '@dynamic-field-kit/core';
|
|
6
|
+
export { FIELD_RENDERER_PROP_KEYS, FieldRegistry, buildFieldRendererProps, collectFieldPaths, fieldRegistry, indexGroupPathMap, makeErrorId, makeFieldId, resolveDisabled, resolveOptions, resolveReadOnly, validateField, validateFieldAsync, validateFields, validateFieldsAsync, validators } from '@dynamic-field-kit/core';
|
|
7
7
|
|
|
8
8
|
class BaseInputComponent {
|
|
9
9
|
cdr;
|
|
@@ -13,10 +13,31 @@ class BaseInputComponent {
|
|
|
13
13
|
required;
|
|
14
14
|
disabled;
|
|
15
15
|
readOnly;
|
|
16
|
+
/**
|
|
17
|
+
* Whether the field has been blurred (or marked touched by the form store).
|
|
18
|
+
* Renderers gate error display on this to avoid shouting at a user who has
|
|
19
|
+
* not reached the field yet.
|
|
20
|
+
*/
|
|
21
|
+
touched;
|
|
22
|
+
/** Whether the value differs from the one the form opened with. */
|
|
23
|
+
dirty;
|
|
16
24
|
error;
|
|
17
25
|
options;
|
|
26
|
+
optionsStatus;
|
|
27
|
+
optionsError;
|
|
28
|
+
/** Renderer-driven refetch for a search-remote field. */
|
|
29
|
+
onOptionsQuery;
|
|
18
30
|
className;
|
|
19
31
|
description;
|
|
32
|
+
id;
|
|
33
|
+
ariaInvalid;
|
|
34
|
+
ariaDescribedBy;
|
|
35
|
+
ariaRequired;
|
|
36
|
+
min;
|
|
37
|
+
max;
|
|
38
|
+
step;
|
|
39
|
+
accept;
|
|
40
|
+
multiple;
|
|
20
41
|
valueChange = new EventEmitter();
|
|
21
42
|
constructor(cdr) {
|
|
22
43
|
this.cdr = cdr;
|
|
@@ -30,10 +51,10 @@ class BaseInputComponent {
|
|
|
30
51
|
hasChanges(changes, prop) {
|
|
31
52
|
return changes[prop] !== undefined;
|
|
32
53
|
}
|
|
33
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
34
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
54
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: BaseInputComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
55
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.22", type: BaseInputComponent, isStandalone: true, selector: "ng-component", inputs: { value: "value", label: "label", placeholder: "placeholder", required: "required", disabled: "disabled", readOnly: "readOnly", touched: "touched", dirty: "dirty", error: "error", options: "options", optionsStatus: "optionsStatus", optionsError: "optionsError", onOptionsQuery: "onOptionsQuery", className: "className", description: "description", id: "id", ariaInvalid: "ariaInvalid", ariaDescribedBy: "ariaDescribedBy", ariaRequired: "ariaRequired", min: "min", max: "max", step: "step", accept: "accept", multiple: "multiple" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true });
|
|
35
56
|
}
|
|
36
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
57
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: BaseInputComponent, decorators: [{
|
|
37
58
|
type: Component,
|
|
38
59
|
args: [{
|
|
39
60
|
template: '',
|
|
@@ -50,14 +71,42 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
|
|
|
50
71
|
type: Input
|
|
51
72
|
}], readOnly: [{
|
|
52
73
|
type: Input
|
|
74
|
+
}], touched: [{
|
|
75
|
+
type: Input
|
|
76
|
+
}], dirty: [{
|
|
77
|
+
type: Input
|
|
53
78
|
}], error: [{
|
|
54
79
|
type: Input
|
|
55
80
|
}], options: [{
|
|
56
81
|
type: Input
|
|
82
|
+
}], optionsStatus: [{
|
|
83
|
+
type: Input
|
|
84
|
+
}], optionsError: [{
|
|
85
|
+
type: Input
|
|
86
|
+
}], onOptionsQuery: [{
|
|
87
|
+
type: Input
|
|
57
88
|
}], className: [{
|
|
58
89
|
type: Input
|
|
59
90
|
}], description: [{
|
|
60
91
|
type: Input
|
|
92
|
+
}], id: [{
|
|
93
|
+
type: Input
|
|
94
|
+
}], ariaInvalid: [{
|
|
95
|
+
type: Input
|
|
96
|
+
}], ariaDescribedBy: [{
|
|
97
|
+
type: Input
|
|
98
|
+
}], ariaRequired: [{
|
|
99
|
+
type: Input
|
|
100
|
+
}], min: [{
|
|
101
|
+
type: Input
|
|
102
|
+
}], max: [{
|
|
103
|
+
type: Input
|
|
104
|
+
}], step: [{
|
|
105
|
+
type: Input
|
|
106
|
+
}], accept: [{
|
|
107
|
+
type: Input
|
|
108
|
+
}], multiple: [{
|
|
109
|
+
type: Input
|
|
61
110
|
}], valueChange: [{
|
|
62
111
|
type: Output
|
|
63
112
|
}] } });
|
|
@@ -74,6 +123,10 @@ const FIELD_REGISTRY = new InjectionToken('dfk.FieldRegistry', {
|
|
|
74
123
|
|
|
75
124
|
// Only the framework-agnostic FieldRendererProps keys. Domain-specific props
|
|
76
125
|
// reach the renderer through `extraProps` (FieldDescription.props) instead.
|
|
126
|
+
// Must stay in step with core's FIELD_RENDERER_PROP_KEYS - a key missing here
|
|
127
|
+
// is a key no Angular renderer can ever receive, which is how `touched`,
|
|
128
|
+
// `dirty`, `id` and the aria flags used to be unreachable on this adapter
|
|
129
|
+
// alone. `packages/core/test/adapterParity.test.ts` asserts the two lists match.
|
|
77
130
|
const KNOWN_PROPS = [
|
|
78
131
|
'value',
|
|
79
132
|
'label',
|
|
@@ -81,10 +134,23 @@ const KNOWN_PROPS = [
|
|
|
81
134
|
'required',
|
|
82
135
|
'disabled',
|
|
83
136
|
'readOnly',
|
|
137
|
+
'touched',
|
|
138
|
+
'dirty',
|
|
84
139
|
'error',
|
|
85
140
|
'options',
|
|
141
|
+
'optionsStatus',
|
|
142
|
+
'optionsError',
|
|
86
143
|
'className',
|
|
87
144
|
'description',
|
|
145
|
+
'id',
|
|
146
|
+
'ariaInvalid',
|
|
147
|
+
'ariaDescribedBy',
|
|
148
|
+
'ariaRequired',
|
|
149
|
+
'min',
|
|
150
|
+
'max',
|
|
151
|
+
'step',
|
|
152
|
+
'accept',
|
|
153
|
+
'multiple',
|
|
88
154
|
];
|
|
89
155
|
class DynamicInput extends BaseInputComponent {
|
|
90
156
|
type;
|
|
@@ -132,6 +198,9 @@ class DynamicInput extends BaseInputComponent {
|
|
|
132
198
|
this.inputInstance[prop] = this[prop];
|
|
133
199
|
}
|
|
134
200
|
}
|
|
201
|
+
if (changes['onOptionsQuery']) {
|
|
202
|
+
this.applyCallbackProps(this.inputInstance);
|
|
203
|
+
}
|
|
135
204
|
if (changes['extraProps']) {
|
|
136
205
|
this.applyExtraProps(this.inputInstance);
|
|
137
206
|
}
|
|
@@ -150,12 +219,33 @@ class DynamicInput extends BaseInputComponent {
|
|
|
150
219
|
this.compRef = undefined;
|
|
151
220
|
this.inputInstance = undefined;
|
|
152
221
|
}
|
|
222
|
+
/** The id `ariaDescribedBy` points at. See core's `makeErrorId`. */
|
|
223
|
+
errorNodeId() {
|
|
224
|
+
return makeErrorId(this.id ?? '');
|
|
225
|
+
}
|
|
226
|
+
/** `error` may arrive as a bare string, so index 0 would be a character. */
|
|
227
|
+
firstError() {
|
|
228
|
+
return Array.isArray(this.error) ? this.error[0] : this.error;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Whether the adapter should render the validation message itself.
|
|
232
|
+
*
|
|
233
|
+
* Asks the registry directly rather than reading a flag set by `render()`:
|
|
234
|
+
* `render()` runs in `ngAfterViewInit`, by which point this template's
|
|
235
|
+
* bindings have already been checked for the pass, and under `OnPush`
|
|
236
|
+
* nothing would mark them dirty again. A registered renderer owns its own
|
|
237
|
+
* error presentation, so only the built-in fallback gets a message here.
|
|
238
|
+
*/
|
|
239
|
+
showDefaultError() {
|
|
240
|
+
return Boolean(!this.registry.get(this.type) && this.firstError() && this.id);
|
|
241
|
+
}
|
|
153
242
|
render() {
|
|
154
243
|
const Renderer = this.registry.get(this.type);
|
|
155
244
|
this.cleanup();
|
|
156
245
|
this.host.clear();
|
|
157
246
|
if (!Renderer) {
|
|
158
247
|
if (this.renderDefaultFallbackHTML5(this.type)) {
|
|
248
|
+
this.applyFallbackAria();
|
|
159
249
|
return;
|
|
160
250
|
}
|
|
161
251
|
this.renderError(`Unknown field type: ${this.type}`);
|
|
@@ -219,15 +309,27 @@ class DynamicInput extends BaseInputComponent {
|
|
|
219
309
|
...this.extraProps,
|
|
220
310
|
value: this.value,
|
|
221
311
|
onValueChange: (v) => this.emitValue(v),
|
|
312
|
+
onOptionsQuery: this.onOptionsQuery,
|
|
222
313
|
label: this.label ?? '',
|
|
223
314
|
placeholder: this.placeholder ?? '',
|
|
224
315
|
required: this.required ?? false,
|
|
225
316
|
disabled: this.disabled ?? false,
|
|
226
317
|
readOnly: this.readOnly ?? false,
|
|
318
|
+
touched: this.touched ?? false,
|
|
319
|
+
dirty: this.dirty ?? false,
|
|
227
320
|
error: this.error,
|
|
228
321
|
options: this.options ?? [],
|
|
229
322
|
className: this.className ?? '',
|
|
230
323
|
description: this.description ?? '',
|
|
324
|
+
id: this.id ?? '',
|
|
325
|
+
ariaInvalid: this.ariaInvalid ?? false,
|
|
326
|
+
ariaDescribedBy: this.ariaDescribedBy,
|
|
327
|
+
ariaRequired: this.ariaRequired ?? false,
|
|
328
|
+
min: this.min,
|
|
329
|
+
max: this.max,
|
|
330
|
+
step: this.step,
|
|
331
|
+
accept: this.accept,
|
|
332
|
+
multiple: this.multiple ?? false,
|
|
231
333
|
};
|
|
232
334
|
}
|
|
233
335
|
// Gates on `this.supplied` (populated from SimpleChanges in ngOnChanges),
|
|
@@ -255,8 +357,22 @@ class DynamicInput extends BaseInputComponent {
|
|
|
255
357
|
instanceObj[prop] = this[prop];
|
|
256
358
|
}
|
|
257
359
|
}
|
|
360
|
+
this.applyCallbackProps(instance);
|
|
258
361
|
this.applyExtraProps(instance);
|
|
259
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* Props that are callbacks rather than resolved values, so they are not in
|
|
365
|
+
* `KNOWN_PROPS` / `FIELD_RENDERER_PROP_KEYS` and the loop above never sees
|
|
366
|
+
* them. Without this a renderer declaring `onOptionsQuery` always got
|
|
367
|
+
* `undefined` and could never trigger a search-remote refetch.
|
|
368
|
+
*/
|
|
369
|
+
applyCallbackProps(instance) {
|
|
370
|
+
if (!instance) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
instance['onOptionsQuery'] =
|
|
374
|
+
this.onOptionsQuery;
|
|
375
|
+
}
|
|
260
376
|
applyExtraProps(instance) {
|
|
261
377
|
if (!instance || !this.extraProps) {
|
|
262
378
|
return;
|
|
@@ -283,6 +399,35 @@ class DynamicInput extends BaseInputComponent {
|
|
|
283
399
|
this.valueChange.emit(value);
|
|
284
400
|
this.onChange.emit(value);
|
|
285
401
|
}
|
|
402
|
+
/**
|
|
403
|
+
* Puts the aria flags on whatever control the HTML5 fallback just built.
|
|
404
|
+
*
|
|
405
|
+
* Applied here, once, rather than in each of the fallback's branches: they
|
|
406
|
+
* hand-build an input, textarea, checkbox or select, and every one of them
|
|
407
|
+
* would otherwise need the same four lines. Without this the fallback emits
|
|
408
|
+
* the `{id}-error` node with nothing pointing at it, and
|
|
409
|
+
* `focusFirstInvalidField` - which selects `[aria-invalid="true"]` - still
|
|
410
|
+
* finds nothing on this adapter.
|
|
411
|
+
*
|
|
412
|
+
* `render()` re-runs on every ngOnChanges while the fallback is in use (there
|
|
413
|
+
* is no component instance to sync props into), so this stays current as the
|
|
414
|
+
* field's error comes and goes.
|
|
415
|
+
*/
|
|
416
|
+
applyFallbackAria() {
|
|
417
|
+
const control = this.host.element.nativeElement.querySelector('input, select, textarea');
|
|
418
|
+
if (!control) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (this.ariaInvalid !== undefined) {
|
|
422
|
+
control.setAttribute('aria-invalid', String(this.ariaInvalid));
|
|
423
|
+
}
|
|
424
|
+
if (this.ariaRequired !== undefined) {
|
|
425
|
+
control.setAttribute('aria-required', String(this.ariaRequired));
|
|
426
|
+
}
|
|
427
|
+
if (this.ariaDescribedBy) {
|
|
428
|
+
control.setAttribute('aria-describedby', this.ariaDescribedBy);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
286
431
|
renderDefaultFallbackHTML5(type) {
|
|
287
432
|
const nativeEl = this.host.element.nativeElement;
|
|
288
433
|
if (type === 'text' ||
|
|
@@ -291,6 +436,9 @@ class DynamicInput extends BaseInputComponent {
|
|
|
291
436
|
type === 'number') {
|
|
292
437
|
const input = document.createElement('input');
|
|
293
438
|
input.type = type;
|
|
439
|
+
if (this.id) {
|
|
440
|
+
input.id = this.id;
|
|
441
|
+
}
|
|
294
442
|
if (this.className) {
|
|
295
443
|
input.className = this.className;
|
|
296
444
|
}
|
|
@@ -317,6 +465,9 @@ class DynamicInput extends BaseInputComponent {
|
|
|
317
465
|
}
|
|
318
466
|
if (type === 'textarea') {
|
|
319
467
|
const textarea = document.createElement('textarea');
|
|
468
|
+
if (this.id) {
|
|
469
|
+
textarea.id = this.id;
|
|
470
|
+
}
|
|
320
471
|
if (this.className) {
|
|
321
472
|
textarea.className = this.className;
|
|
322
473
|
}
|
|
@@ -342,6 +493,9 @@ class DynamicInput extends BaseInputComponent {
|
|
|
342
493
|
if (type === 'checkbox') {
|
|
343
494
|
const checkbox = document.createElement('input');
|
|
344
495
|
checkbox.type = 'checkbox';
|
|
496
|
+
if (this.id) {
|
|
497
|
+
checkbox.id = this.id;
|
|
498
|
+
}
|
|
345
499
|
if (this.className) {
|
|
346
500
|
checkbox.className = this.className;
|
|
347
501
|
}
|
|
@@ -360,6 +514,9 @@ class DynamicInput extends BaseInputComponent {
|
|
|
360
514
|
}
|
|
361
515
|
if (type === 'select') {
|
|
362
516
|
const select = document.createElement('select');
|
|
517
|
+
if (this.id) {
|
|
518
|
+
select.id = this.id;
|
|
519
|
+
}
|
|
363
520
|
if (this.className) {
|
|
364
521
|
select.className = this.className;
|
|
365
522
|
}
|
|
@@ -401,17 +558,35 @@ class DynamicInput extends BaseInputComponent {
|
|
|
401
558
|
el.style.color = 'red';
|
|
402
559
|
this.host.element.nativeElement.appendChild(el);
|
|
403
560
|
}
|
|
404
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
405
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
561
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicInput, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
562
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.22", 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>
|
|
563
|
+
<div
|
|
564
|
+
*ngIf="showDefaultError()"
|
|
565
|
+
[id]="errorNodeId()"
|
|
566
|
+
class="dfk-field-error"
|
|
567
|
+
role="alert"
|
|
568
|
+
>
|
|
569
|
+
{{ firstError() }}
|
|
570
|
+
</div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
406
571
|
}
|
|
407
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
572
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicInput, decorators: [{
|
|
408
573
|
type: Component,
|
|
409
574
|
args: [{
|
|
410
575
|
selector: 'dfk-dynamic-input',
|
|
411
576
|
standalone: true,
|
|
412
577
|
imports: [CommonModule],
|
|
413
578
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
414
|
-
|
|
579
|
+
// *ngIf rather than @if: the peer range starts at Angular 16, and the
|
|
580
|
+
// built-in control flow block syntax is 17+.
|
|
581
|
+
template: `<div #host style="display: contents;"></div>
|
|
582
|
+
<div
|
|
583
|
+
*ngIf="showDefaultError()"
|
|
584
|
+
[id]="errorNodeId()"
|
|
585
|
+
class="dfk-field-error"
|
|
586
|
+
role="alert"
|
|
587
|
+
>
|
|
588
|
+
{{ firstError() }}
|
|
589
|
+
</div>`,
|
|
415
590
|
}]
|
|
416
591
|
}], propDecorators: { type: [{
|
|
417
592
|
type: Input
|
|
@@ -429,11 +604,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
|
|
|
429
604
|
class FieldInput {
|
|
430
605
|
cdr;
|
|
431
606
|
fieldDescription;
|
|
607
|
+
/**
|
|
608
|
+
* Data at this field's own level. Preferred over `value`: the shared
|
|
609
|
+
* `buildFieldRendererProps` needs the surrounding data to resolve
|
|
610
|
+
* `appearCondition`, dynamic options and cross-field validation. When only
|
|
611
|
+
* `value` is bound (mounting this component directly), a one-key object is
|
|
612
|
+
* synthesised from it.
|
|
613
|
+
*/
|
|
614
|
+
data;
|
|
615
|
+
/** Top-level form data, for fields nested inside a repeatable group. */
|
|
616
|
+
rootData;
|
|
432
617
|
value;
|
|
433
618
|
options;
|
|
434
619
|
disabled;
|
|
435
620
|
readOnly;
|
|
436
621
|
error;
|
|
622
|
+
/** Distinguishes a controlled empty error from an omitted error input. */
|
|
623
|
+
validationControlled = false;
|
|
624
|
+
/** Whether the field has been blurred, or marked touched by a form store. */
|
|
625
|
+
touched;
|
|
626
|
+
/** Whether the value differs from the one the form opened with. */
|
|
627
|
+
dirty;
|
|
628
|
+
/** Per-form-instance id namespace; see core's `makeFieldId`. */
|
|
629
|
+
idPrefix = 'dfk-field';
|
|
437
630
|
onValueChangeField = new EventEmitter();
|
|
438
631
|
/**
|
|
439
632
|
* Emits this field's name when focus leaves it. Driven by `focusout`, which
|
|
@@ -441,50 +634,121 @@ class FieldInput {
|
|
|
441
634
|
* declare a blur output of its own.
|
|
442
635
|
*/
|
|
443
636
|
onBlurField = new EventEmitter();
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
return undefined;
|
|
451
|
-
}
|
|
452
|
-
if (typeof this.fieldDescription.options === 'function') {
|
|
453
|
-
return undefined;
|
|
454
|
-
}
|
|
455
|
-
return this.fieldDescription.options;
|
|
456
|
-
}
|
|
637
|
+
/**
|
|
638
|
+
* The full renderer prop bag, built once per change-detection pass rather
|
|
639
|
+
* than from one getter per binding - `buildFieldRendererProps` validates the
|
|
640
|
+
* field, and running that ~20 times per pass would be wasteful.
|
|
641
|
+
*/
|
|
642
|
+
rendererProps = null;
|
|
457
643
|
constructor(cdr) {
|
|
458
644
|
this.cdr = cdr;
|
|
459
645
|
}
|
|
646
|
+
// Async options only; a static or synchronous list allocates nothing here.
|
|
647
|
+
loader;
|
|
648
|
+
optionsState;
|
|
649
|
+
/** Bound into the template so a renderer can drive a search-remote refetch. */
|
|
650
|
+
onOptionsQuery = (query) => {
|
|
651
|
+
this.loader?.setQuery(query);
|
|
652
|
+
};
|
|
460
653
|
ngOnChanges(_changes) {
|
|
461
|
-
this.
|
|
654
|
+
this.syncOptionsLoader();
|
|
655
|
+
this.rendererProps = this.buildProps();
|
|
462
656
|
this.cdr.markForCheck();
|
|
463
657
|
}
|
|
464
|
-
|
|
465
|
-
|
|
658
|
+
ngOnDestroy() {
|
|
659
|
+
this.loader?.dispose();
|
|
660
|
+
}
|
|
661
|
+
syncOptionsLoader() {
|
|
662
|
+
const field = this.fieldDescription;
|
|
663
|
+
if (!field || !isAsyncOptions(field)) {
|
|
664
|
+
// A field can be swapped for one with synchronous options. Without this,
|
|
665
|
+
// the stale optionsState would keep winning in buildFieldRendererProps
|
|
666
|
+
// (`optionsState ? optionsState.options : resolveOptions(...)`) and the
|
|
667
|
+
// old async list would be served forever, with the loader never disposed.
|
|
668
|
+
this.loader?.dispose();
|
|
669
|
+
this.loader = undefined;
|
|
670
|
+
this.optionsState = undefined;
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (!this.loader) {
|
|
674
|
+
this.optionsState = { status: 'idle' };
|
|
675
|
+
this.loader = createOptionsLoader(field, (state) => {
|
|
676
|
+
this.optionsState = state;
|
|
677
|
+
this.rendererProps = this.buildProps();
|
|
678
|
+
// OnPush: an async arrival happens outside any event this view is
|
|
679
|
+
// checked for, so without this the options would load and never appear.
|
|
680
|
+
this.cdr.markForCheck();
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
this.loader.update(this.data ?? {}, this.rootData);
|
|
684
|
+
}
|
|
685
|
+
buildProps() {
|
|
686
|
+
const field = this.fieldDescription;
|
|
687
|
+
if (!field) {
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
690
|
+
const data = this.data ?? { [field.name]: this.value };
|
|
691
|
+
const base = buildFieldRendererProps({
|
|
692
|
+
fieldDescription: field,
|
|
693
|
+
data,
|
|
694
|
+
rootData: this.rootData,
|
|
695
|
+
id: makeFieldId(field, this.idPrefix),
|
|
696
|
+
touched: this.touched,
|
|
697
|
+
dirty: this.dirty,
|
|
698
|
+
optionsState: this.optionsState,
|
|
699
|
+
});
|
|
700
|
+
// Explicitly bound inputs override what the field description resolves to,
|
|
701
|
+
// so a host can still drive options/disabled/error itself.
|
|
702
|
+
const error = this.validationControlled
|
|
703
|
+
? this.error
|
|
704
|
+
: (this.error ?? base.error);
|
|
705
|
+
return {
|
|
706
|
+
...base,
|
|
707
|
+
options: this.options ?? base.options,
|
|
708
|
+
disabled: this.disabled ?? base.disabled,
|
|
709
|
+
readOnly: this.readOnly ?? base.readOnly,
|
|
710
|
+
error,
|
|
711
|
+
ariaInvalid: Boolean(error),
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FieldInput, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
715
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.22", type: FieldInput, isStandalone: true, selector: "dfk-field-input", inputs: { fieldDescription: "fieldDescription", data: "data", rootData: "rootData", value: "value", options: "options", disabled: "disabled", readOnly: "readOnly", error: "error", validationControlled: "validationControlled", touched: "touched", dirty: "dirty", idPrefix: "idPrefix" }, outputs: { onValueChangeField: "onValueChangeField", onBlurField: "onBlurField" }, usesOnChanges: true, ngImport: i0, template: `
|
|
466
716
|
<dfk-dynamic-input
|
|
467
|
-
*ngIf="
|
|
468
|
-
[type]="
|
|
469
|
-
[value]="value"
|
|
470
|
-
[label]="
|
|
471
|
-
[placeholder]="
|
|
472
|
-
[required]="
|
|
473
|
-
[description]="$any(
|
|
474
|
-
[options]="
|
|
475
|
-
[
|
|
717
|
+
*ngIf="rendererProps as p"
|
|
718
|
+
[type]="p.type"
|
|
719
|
+
[value]="p.value"
|
|
720
|
+
[label]="p.label"
|
|
721
|
+
[placeholder]="p.placeholder"
|
|
722
|
+
[required]="p.required"
|
|
723
|
+
[description]="$any(p.description)"
|
|
724
|
+
[options]="$any(p.options)"
|
|
725
|
+
[optionsStatus]="p.optionsStatus"
|
|
726
|
+
[optionsError]="p.optionsError"
|
|
727
|
+
[className]="p.className"
|
|
728
|
+
[disabled]="p.disabled"
|
|
729
|
+
[readOnly]="p.readOnly"
|
|
730
|
+
[touched]="p.touched"
|
|
731
|
+
[dirty]="p.dirty"
|
|
732
|
+
[error]="$any(p.error)"
|
|
733
|
+
[id]="p.id"
|
|
734
|
+
[ariaInvalid]="p.ariaInvalid"
|
|
735
|
+
[ariaDescribedBy]="p.ariaDescribedBy"
|
|
736
|
+
[ariaRequired]="p.ariaRequired"
|
|
737
|
+
[min]="p.min"
|
|
738
|
+
[max]="p.max"
|
|
739
|
+
[step]="p.step"
|
|
740
|
+
[accept]="p.accept"
|
|
741
|
+
[multiple]="p.multiple"
|
|
476
742
|
(valueChange)="
|
|
477
743
|
onValueChangeField.emit({ value: $event, key: fieldDescription!.name })
|
|
478
744
|
"
|
|
479
745
|
(focusout)="onBlurField.emit(fieldDescription!.name)"
|
|
480
|
-
[
|
|
481
|
-
[
|
|
482
|
-
[error]="$any(error)"
|
|
483
|
-
[extraProps]="fieldDescription!.props"
|
|
746
|
+
[extraProps]="p.extraProps"
|
|
747
|
+
[onOptionsQuery]="onOptionsQuery"
|
|
484
748
|
></dfk-dynamic-input>
|
|
485
749
|
`, 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 });
|
|
486
750
|
}
|
|
487
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
751
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FieldInput, decorators: [{
|
|
488
752
|
type: Component,
|
|
489
753
|
args: [{
|
|
490
754
|
selector: 'dfk-field-input',
|
|
@@ -493,28 +757,46 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
|
|
|
493
757
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
494
758
|
template: `
|
|
495
759
|
<dfk-dynamic-input
|
|
496
|
-
*ngIf="
|
|
497
|
-
[type]="
|
|
498
|
-
[value]="value"
|
|
499
|
-
[label]="
|
|
500
|
-
[placeholder]="
|
|
501
|
-
[required]="
|
|
502
|
-
[description]="$any(
|
|
503
|
-
[options]="
|
|
504
|
-
[
|
|
760
|
+
*ngIf="rendererProps as p"
|
|
761
|
+
[type]="p.type"
|
|
762
|
+
[value]="p.value"
|
|
763
|
+
[label]="p.label"
|
|
764
|
+
[placeholder]="p.placeholder"
|
|
765
|
+
[required]="p.required"
|
|
766
|
+
[description]="$any(p.description)"
|
|
767
|
+
[options]="$any(p.options)"
|
|
768
|
+
[optionsStatus]="p.optionsStatus"
|
|
769
|
+
[optionsError]="p.optionsError"
|
|
770
|
+
[className]="p.className"
|
|
771
|
+
[disabled]="p.disabled"
|
|
772
|
+
[readOnly]="p.readOnly"
|
|
773
|
+
[touched]="p.touched"
|
|
774
|
+
[dirty]="p.dirty"
|
|
775
|
+
[error]="$any(p.error)"
|
|
776
|
+
[id]="p.id"
|
|
777
|
+
[ariaInvalid]="p.ariaInvalid"
|
|
778
|
+
[ariaDescribedBy]="p.ariaDescribedBy"
|
|
779
|
+
[ariaRequired]="p.ariaRequired"
|
|
780
|
+
[min]="p.min"
|
|
781
|
+
[max]="p.max"
|
|
782
|
+
[step]="p.step"
|
|
783
|
+
[accept]="p.accept"
|
|
784
|
+
[multiple]="p.multiple"
|
|
505
785
|
(valueChange)="
|
|
506
786
|
onValueChangeField.emit({ value: $event, key: fieldDescription!.name })
|
|
507
787
|
"
|
|
508
788
|
(focusout)="onBlurField.emit(fieldDescription!.name)"
|
|
509
|
-
[
|
|
510
|
-
[
|
|
511
|
-
[error]="$any(error)"
|
|
512
|
-
[extraProps]="fieldDescription!.props"
|
|
789
|
+
[extraProps]="p.extraProps"
|
|
790
|
+
[onOptionsQuery]="onOptionsQuery"
|
|
513
791
|
></dfk-dynamic-input>
|
|
514
792
|
`,
|
|
515
793
|
}]
|
|
516
794
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { fieldDescription: [{
|
|
517
795
|
type: Input
|
|
796
|
+
}], data: [{
|
|
797
|
+
type: Input
|
|
798
|
+
}], rootData: [{
|
|
799
|
+
type: Input
|
|
518
800
|
}], value: [{
|
|
519
801
|
type: Input
|
|
520
802
|
}], options: [{
|
|
@@ -525,6 +807,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
|
|
|
525
807
|
type: Input
|
|
526
808
|
}], error: [{
|
|
527
809
|
type: Input
|
|
810
|
+
}], validationControlled: [{
|
|
811
|
+
type: Input
|
|
812
|
+
}], touched: [{
|
|
813
|
+
type: Input
|
|
814
|
+
}], dirty: [{
|
|
815
|
+
type: Input
|
|
816
|
+
}], idPrefix: [{
|
|
817
|
+
type: Input
|
|
528
818
|
}], onValueChangeField: [{
|
|
529
819
|
type: Output
|
|
530
820
|
}], onBlurField: [{
|
|
@@ -532,10 +822,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
|
|
|
532
822
|
}] } });
|
|
533
823
|
|
|
534
824
|
const DEFAULT_BREAKPOINT = 768;
|
|
825
|
+
// Per-instance id counter, so two forms rendering the same field name emit
|
|
826
|
+
// different DOM ids.
|
|
827
|
+
/**
|
|
828
|
+
* Shared empty maps. A fresh object literal in a template binding is a new
|
|
829
|
+
* identity on every change detection pass, which defeats the per-item caching
|
|
830
|
+
* below and trips dev-mode checkNoChanges (NG0100).
|
|
831
|
+
*/
|
|
832
|
+
const EMPTY_ERRORS = Object.freeze({});
|
|
833
|
+
const EMPTY_TOUCHED = Object.freeze({});
|
|
834
|
+
let instanceCounter = 0;
|
|
835
|
+
function nextInstanceId() {
|
|
836
|
+
instanceCounter += 1;
|
|
837
|
+
return instanceCounter;
|
|
838
|
+
}
|
|
535
839
|
class MultiFieldInput {
|
|
536
840
|
cdr;
|
|
537
841
|
fieldDescriptions = [];
|
|
538
842
|
properties;
|
|
843
|
+
/**
|
|
844
|
+
* The values per-field `dirty` is measured against. Defaults to the first
|
|
845
|
+
* non-`undefined` `properties` this component sees. This adapter has no
|
|
846
|
+
* `form` shorthand, so pass `store.baselineValues()` here to keep `dirty`
|
|
847
|
+
* correct across `store.reset(newValues)`.
|
|
848
|
+
*/
|
|
849
|
+
initialProperties;
|
|
539
850
|
onChange = new EventEmitter();
|
|
540
851
|
validityChange = new EventEmitter();
|
|
541
852
|
/**
|
|
@@ -545,14 +856,113 @@ class MultiFieldInput {
|
|
|
545
856
|
* `validateOnBlur` behaviour.
|
|
546
857
|
*/
|
|
547
858
|
onBlurField = new EventEmitter();
|
|
859
|
+
/**
|
|
860
|
+
* Controlled touched map. When bound it is the single source of truth and
|
|
861
|
+
* the internal tracker is bypassed, so `createDynamicFormStore`'s `touched`
|
|
862
|
+
* signal (updated by setFieldTouched/touchAll/handleSubmit, cleared by
|
|
863
|
+
* reset) is what renderers actually see. Leave it unbound to keep the
|
|
864
|
+
* internal, blur-only tracker.
|
|
865
|
+
*/
|
|
866
|
+
touched;
|
|
867
|
+
/** Controlled error map; bind the form store's `errors()` signal here. */
|
|
868
|
+
errors;
|
|
869
|
+
/** Emits the next touched map whenever a field is blurred. */
|
|
870
|
+
touchedChange = new EventEmitter();
|
|
871
|
+
/**
|
|
872
|
+
* Namespace for generated field ids: a field renders with
|
|
873
|
+
* `${idPrefix}-${name}`. Defaults to a value unique to this component
|
|
874
|
+
* instance, so two forms containing the same field name do not emit
|
|
875
|
+
* duplicate DOM ids. Bind a fixed string to pin ids (`idPrefix="dfk-field"`
|
|
876
|
+
* restores the pre-1.6 ids), or set `FieldDescription.id` per field.
|
|
877
|
+
*/
|
|
878
|
+
idPrefix;
|
|
548
879
|
touchedFields = {};
|
|
880
|
+
// Unique per component instance. Angular has no useId equivalent, and this
|
|
881
|
+
// component is client-rendered by the time ids matter, so a module counter
|
|
882
|
+
// is enough.
|
|
883
|
+
instanceId = nextInstanceId();
|
|
884
|
+
firstSeenProperties = {};
|
|
885
|
+
indexedErrorsSource;
|
|
886
|
+
indexedErrors = new Map();
|
|
887
|
+
indexedTouchedSource;
|
|
888
|
+
indexedTouched = new Map();
|
|
889
|
+
get effectiveIdPrefix() {
|
|
890
|
+
return this.idPrefix ?? `dfk-${this.instanceId}`;
|
|
891
|
+
}
|
|
892
|
+
/** The touched map in effect: the controlled input, else the internal one. */
|
|
893
|
+
get effectiveTouched() {
|
|
894
|
+
return this.touched ?? this.touchedFields;
|
|
895
|
+
}
|
|
549
896
|
handleBlurField(fieldName) {
|
|
550
|
-
|
|
897
|
+
if (this.touched === undefined) {
|
|
898
|
+
this.touchedFields = { ...this.touchedFields, [fieldName]: true };
|
|
899
|
+
}
|
|
900
|
+
this.touchedChange.emit({ ...this.effectiveTouched, [fieldName]: true });
|
|
551
901
|
this.onBlurField.emit(fieldName);
|
|
902
|
+
this.cdr.markForCheck();
|
|
552
903
|
}
|
|
553
904
|
/** Whether this field has been blurred at least once. */
|
|
554
905
|
isTouched(fieldName) {
|
|
555
|
-
return Boolean(this.
|
|
906
|
+
return Boolean(this.effectiveTouched[fieldName]);
|
|
907
|
+
}
|
|
908
|
+
/** Whether this field's value differs from the one the form opened with. */
|
|
909
|
+
isFieldDirty(fieldName) {
|
|
910
|
+
const baseline = this.initialProperties ?? this.firstSeenProperties;
|
|
911
|
+
return !Object.is(this.data[fieldName], baseline[fieldName]);
|
|
912
|
+
}
|
|
913
|
+
fieldErrors(fieldName) {
|
|
914
|
+
return this.errors?.[fieldName];
|
|
915
|
+
}
|
|
916
|
+
errorsForItem(fieldName, index) {
|
|
917
|
+
if (this.indexedErrorsSource !== this.errors) {
|
|
918
|
+
this.indexedErrorsSource = this.errors;
|
|
919
|
+
this.indexedErrors.clear();
|
|
920
|
+
}
|
|
921
|
+
if (!this.indexedErrors.has(fieldName)) {
|
|
922
|
+
this.indexedErrors.set(fieldName, indexGroupPathMap(this.errors, fieldName));
|
|
923
|
+
}
|
|
924
|
+
// An item with no errors still gets a map when the parent has one, so a
|
|
925
|
+
// nested input is never handed `undefined` for a controlled error map.
|
|
926
|
+
return this.errors === undefined
|
|
927
|
+
? undefined
|
|
928
|
+
: (this.indexedErrors.get(fieldName)?.[index] ?? EMPTY_ERRORS);
|
|
929
|
+
}
|
|
930
|
+
touchedForItem(fieldName, index) {
|
|
931
|
+
const current = this.effectiveTouched;
|
|
932
|
+
if (this.indexedTouchedSource !== current) {
|
|
933
|
+
this.indexedTouchedSource = current;
|
|
934
|
+
this.indexedTouched.clear();
|
|
935
|
+
}
|
|
936
|
+
if (!this.indexedTouched.has(fieldName)) {
|
|
937
|
+
this.indexedTouched.set(fieldName, indexGroupPathMap(current, fieldName));
|
|
938
|
+
}
|
|
939
|
+
// A fresh {} here would be a new binding identity on every change
|
|
940
|
+
// detection pass, re-running the nested input's ngOnChanges each time and
|
|
941
|
+
// risking NG0100 in dev mode.
|
|
942
|
+
return this.indexedTouched.get(fieldName)?.[index] ?? EMPTY_TOUCHED;
|
|
943
|
+
}
|
|
944
|
+
handleGroupBlur(fieldName, index, key) {
|
|
945
|
+
this.handleBlurField(`${fieldName}[${index}].${key}`);
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Clears the internally tracked touched state. Only meaningful in
|
|
949
|
+
* uncontrolled mode - when `touched` is bound, resetting the form store
|
|
950
|
+
* (e.g. `createDynamicFormStore().reset()`) already clears it.
|
|
951
|
+
*/
|
|
952
|
+
resetTouched() {
|
|
953
|
+
this.touchedFields = {};
|
|
954
|
+
this.touchedChange.emit(this.effectiveTouched);
|
|
955
|
+
this.cdr.markForCheck();
|
|
956
|
+
}
|
|
957
|
+
setFieldTouched(fieldName, isTouched = true) {
|
|
958
|
+
if (this.touched === undefined) {
|
|
959
|
+
this.touchedFields = { ...this.touchedFields, [fieldName]: isTouched };
|
|
960
|
+
}
|
|
961
|
+
this.touchedChange.emit({
|
|
962
|
+
...this.effectiveTouched,
|
|
963
|
+
[fieldName]: isTouched,
|
|
964
|
+
});
|
|
965
|
+
this.cdr.markForCheck();
|
|
556
966
|
}
|
|
557
967
|
layout = 'column';
|
|
558
968
|
// Top-level form data, threaded down through repeatable groups so a nested
|
|
@@ -561,6 +971,7 @@ class MultiFieldInput {
|
|
|
561
971
|
rootData;
|
|
562
972
|
data = {};
|
|
563
973
|
visibleFields = [];
|
|
974
|
+
initialised = false;
|
|
564
975
|
isMobile = false;
|
|
565
976
|
constructor(cdr) {
|
|
566
977
|
this.cdr = cdr;
|
|
@@ -637,6 +1048,12 @@ class MultiFieldInput {
|
|
|
637
1048
|
init() {
|
|
638
1049
|
if (this.properties) {
|
|
639
1050
|
this.data = applyComputedValues(this.fieldDescriptions, { ...this.properties }, this.rootData);
|
|
1051
|
+
// Baseline for the `dirty` flag: the values the form opened with, not
|
|
1052
|
+
// whatever `properties` happens to hold after later edits.
|
|
1053
|
+
if (!this.initialised) {
|
|
1054
|
+
this.firstSeenProperties = { ...this.properties };
|
|
1055
|
+
this.initialised = true;
|
|
1056
|
+
}
|
|
640
1057
|
}
|
|
641
1058
|
this.updateVisibleFields();
|
|
642
1059
|
this.validityChange.emit(validateFields(this.fieldDescriptions, this.data, this.rootData));
|
|
@@ -652,22 +1069,11 @@ class MultiFieldInput {
|
|
|
652
1069
|
const value = this.data[field.name];
|
|
653
1070
|
return Array.isArray(value) ? value : [];
|
|
654
1071
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
}
|
|
661
|
-
getReadOnly(field) {
|
|
662
|
-
return resolveReadOnly(field, this.data, this.rootData);
|
|
663
|
-
}
|
|
664
|
-
getError(field) {
|
|
665
|
-
if (this.getDisabled(field)) {
|
|
666
|
-
return undefined;
|
|
667
|
-
}
|
|
668
|
-
const errors = validateField(field, this.data[field.name], this.data, this.rootData);
|
|
669
|
-
return errors.length > 0 ? errors : undefined;
|
|
670
|
-
}
|
|
1072
|
+
// The per-field getResolvedOptions/getDisabled/getReadOnly/getError helpers
|
|
1073
|
+
// that used to live here are gone: FieldInput now resolves all of it through
|
|
1074
|
+
// core's shared buildFieldRendererProps. Keeping a second copy of that logic
|
|
1075
|
+
// beside the shared one is exactly how the adapters drifted apart in the
|
|
1076
|
+
// first place.
|
|
671
1077
|
canAddItem(field) {
|
|
672
1078
|
return canAddGroupItem(field, this.getItems(field));
|
|
673
1079
|
}
|
|
@@ -703,8 +1109,8 @@ class MultiFieldInput {
|
|
|
703
1109
|
this.validityChange.emit(validateFields(this.fieldDescriptions, this.data, this.rootData));
|
|
704
1110
|
this.cdr.markForCheck();
|
|
705
1111
|
}
|
|
706
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
707
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
1112
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MultiFieldInput, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
1113
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.22", type: MultiFieldInput, isStandalone: true, selector: "dfk-multi-field-input", inputs: { fieldDescriptions: "fieldDescriptions", properties: "properties", initialProperties: "initialProperties", touched: "touched", errors: "errors", idPrefix: "idPrefix", layout: "layout", rootData: "rootData" }, outputs: { onChange: "onChange", validityChange: "validityChange", onBlurField: "onBlurField", touchedChange: "touchedChange" }, host: { listeners: { "window:resize": "onWindowResize()" } }, usesOnChanges: true, ngImport: i0, template: `
|
|
708
1114
|
<div
|
|
709
1115
|
class="p-4 border rounded-lg bg-gray-50"
|
|
710
1116
|
[ngClass]="{
|
|
@@ -721,11 +1127,13 @@ class MultiFieldInput {
|
|
|
721
1127
|
<dfk-field-input
|
|
722
1128
|
*ngIf="!field.fields"
|
|
723
1129
|
[fieldDescription]="field"
|
|
724
|
-
[
|
|
725
|
-
[
|
|
726
|
-
[
|
|
727
|
-
[
|
|
728
|
-
[
|
|
1130
|
+
[data]="data"
|
|
1131
|
+
[rootData]="rootData"
|
|
1132
|
+
[idPrefix]="effectiveIdPrefix"
|
|
1133
|
+
[touched]="isTouched(field.name)"
|
|
1134
|
+
[dirty]="isFieldDirty(field.name)"
|
|
1135
|
+
[error]="fieldErrors(field.name)"
|
|
1136
|
+
[validationControlled]="errors !== undefined"
|
|
729
1137
|
(onValueChangeField)="onFieldChange($event)"
|
|
730
1138
|
(onBlurField)="handleBlurField($event)"
|
|
731
1139
|
></dfk-field-input>
|
|
@@ -745,6 +1153,9 @@ class MultiFieldInput {
|
|
|
745
1153
|
[fieldDescriptions]="field.fields"
|
|
746
1154
|
[properties]="item"
|
|
747
1155
|
[rootData]="rootData ?? data"
|
|
1156
|
+
[errors]="errorsForItem(field.name, i)"
|
|
1157
|
+
[touched]="touchedForItem(field.name, i)"
|
|
1158
|
+
(onBlurField)="handleGroupBlur(field.name, i, $event)"
|
|
748
1159
|
(onChange)="onGroupItemChange(field, i, $event)"
|
|
749
1160
|
></dfk-multi-field-input>
|
|
750
1161
|
</div>
|
|
@@ -776,9 +1187,9 @@ class MultiFieldInput {
|
|
|
776
1187
|
</div>
|
|
777
1188
|
</ng-container>
|
|
778
1189
|
</div>
|
|
779
|
-
`, isInline: true, dependencies: [{ kind: "component", type: MultiFieldInput, selector: "dfk-multi-field-input", inputs: ["fieldDescriptions", "properties", "layout", "rootData"], outputs: ["onChange", "validityChange", "onBlurField"] }, { 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", "onBlurField"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1190
|
+
`, isInline: true, dependencies: [{ kind: "component", type: MultiFieldInput, selector: "dfk-multi-field-input", inputs: ["fieldDescriptions", "properties", "initialProperties", "touched", "errors", "idPrefix", "layout", "rootData"], outputs: ["onChange", "validityChange", "onBlurField", "touchedChange"] }, { 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", "data", "rootData", "value", "options", "disabled", "readOnly", "error", "validationControlled", "touched", "dirty", "idPrefix"], outputs: ["onValueChangeField", "onBlurField"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
780
1191
|
}
|
|
781
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1192
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MultiFieldInput, decorators: [{
|
|
782
1193
|
type: Component,
|
|
783
1194
|
args: [{
|
|
784
1195
|
selector: 'dfk-multi-field-input',
|
|
@@ -808,11 +1219,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
|
|
|
808
1219
|
<dfk-field-input
|
|
809
1220
|
*ngIf="!field.fields"
|
|
810
1221
|
[fieldDescription]="field"
|
|
811
|
-
[
|
|
812
|
-
[
|
|
813
|
-
[
|
|
814
|
-
[
|
|
815
|
-
[
|
|
1222
|
+
[data]="data"
|
|
1223
|
+
[rootData]="rootData"
|
|
1224
|
+
[idPrefix]="effectiveIdPrefix"
|
|
1225
|
+
[touched]="isTouched(field.name)"
|
|
1226
|
+
[dirty]="isFieldDirty(field.name)"
|
|
1227
|
+
[error]="fieldErrors(field.name)"
|
|
1228
|
+
[validationControlled]="errors !== undefined"
|
|
816
1229
|
(onValueChangeField)="onFieldChange($event)"
|
|
817
1230
|
(onBlurField)="handleBlurField($event)"
|
|
818
1231
|
></dfk-field-input>
|
|
@@ -832,6 +1245,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
|
|
|
832
1245
|
[fieldDescriptions]="field.fields"
|
|
833
1246
|
[properties]="item"
|
|
834
1247
|
[rootData]="rootData ?? data"
|
|
1248
|
+
[errors]="errorsForItem(field.name, i)"
|
|
1249
|
+
[touched]="touchedForItem(field.name, i)"
|
|
1250
|
+
(onBlurField)="handleGroupBlur(field.name, i, $event)"
|
|
835
1251
|
(onChange)="onGroupItemChange(field, i, $event)"
|
|
836
1252
|
></dfk-multi-field-input>
|
|
837
1253
|
</div>
|
|
@@ -869,12 +1285,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
|
|
|
869
1285
|
type: Input
|
|
870
1286
|
}], properties: [{
|
|
871
1287
|
type: Input
|
|
1288
|
+
}], initialProperties: [{
|
|
1289
|
+
type: Input
|
|
872
1290
|
}], onChange: [{
|
|
873
1291
|
type: Output
|
|
874
1292
|
}], validityChange: [{
|
|
875
1293
|
type: Output
|
|
876
1294
|
}], onBlurField: [{
|
|
877
1295
|
type: Output
|
|
1296
|
+
}], touched: [{
|
|
1297
|
+
type: Input
|
|
1298
|
+
}], errors: [{
|
|
1299
|
+
type: Input
|
|
1300
|
+
}], touchedChange: [{
|
|
1301
|
+
type: Output
|
|
1302
|
+
}], idPrefix: [{
|
|
1303
|
+
type: Input
|
|
878
1304
|
}], layout: [{
|
|
879
1305
|
type: Input
|
|
880
1306
|
}], rootData: [{
|
|
@@ -894,16 +1320,16 @@ class DynamicFormDevToolsComponent {
|
|
|
894
1320
|
errorCount() {
|
|
895
1321
|
return Object.keys(this.errors || {}).length;
|
|
896
1322
|
}
|
|
897
|
-
isOpen = signal(false);
|
|
898
|
-
activeTab = signal('data');
|
|
1323
|
+
isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
1324
|
+
activeTab = signal('data', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
|
|
899
1325
|
tabs = [
|
|
900
1326
|
'data',
|
|
901
1327
|
'errors',
|
|
902
1328
|
'meta',
|
|
903
1329
|
'fields',
|
|
904
1330
|
];
|
|
905
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
906
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
1331
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFormDevToolsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1332
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.22", type: DynamicFormDevToolsComponent, isStandalone: true, selector: "dfk-dev-tools", inputs: { data: "data", errors: "errors", touched: "touched", isDirty: "isDirty", fields: "fields" }, ngImport: i0, template: `
|
|
907
1333
|
<button
|
|
908
1334
|
*ngIf="!isOpen()"
|
|
909
1335
|
type="button"
|
|
@@ -1037,7 +1463,7 @@ class DynamicFormDevToolsComponent {
|
|
|
1037
1463
|
</div>
|
|
1038
1464
|
`, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "pipe", type: JsonPipe, name: "json" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1039
1465
|
}
|
|
1040
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1466
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFormDevToolsComponent, decorators: [{
|
|
1041
1467
|
type: Component,
|
|
1042
1468
|
args: [{
|
|
1043
1469
|
selector: 'dfk-dev-tools',
|
|
@@ -1195,24 +1621,74 @@ function createDynamicFormStore(options) {
|
|
|
1195
1621
|
const initialValues = options.initialValues || {};
|
|
1196
1622
|
const validateOnBlur = options.validateOnBlur ?? true;
|
|
1197
1623
|
const validateOnChange = options.validateOnChange ?? false;
|
|
1198
|
-
const
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
const
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1624
|
+
const validationContext = {
|
|
1625
|
+
t: createMessageResolver(options.messages),
|
|
1626
|
+
};
|
|
1627
|
+
const data = signal(applyComputedValues(fields, initialValues), ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
1628
|
+
// The baseline `dirty` is measured against: the initialValues option until
|
|
1629
|
+
// reset(newValues) replaces it. Distinct from that option, which never
|
|
1630
|
+
// changes. See the React adapter for the full rationale.
|
|
1631
|
+
const baselineValues = signal({ ...data() }, ...(ngDevMode ? [{ debugName: "baselineValues" }] : /* istanbul ignore next */ []));
|
|
1632
|
+
const errors = signal({}, ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
|
|
1633
|
+
const isDirty = signal(false, ...(ngDevMode ? [{ debugName: "isDirty" }] : /* istanbul ignore next */ []));
|
|
1634
|
+
const touched = signal({}, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
|
|
1635
|
+
const isSubmitting = signal(false, ...(ngDevMode ? [{ debugName: "isSubmitting" }] : /* istanbul ignore next */ []));
|
|
1636
|
+
const isSubmitted = signal(false, ...(ngDevMode ? [{ debugName: "isSubmitted" }] : /* istanbul ignore next */ []));
|
|
1637
|
+
const validationResult = signal(validateFields(fields, data(), undefined, validationContext), ...(ngDevMode ? [{ debugName: "validationResult" }] : /* istanbul ignore next */ []));
|
|
1638
|
+
const isValidating = signal(false, ...(ngDevMode ? [{ debugName: "isValidating" }] : /* istanbul ignore next */ []));
|
|
1639
|
+
let validationRun = 0;
|
|
1640
|
+
let validationController;
|
|
1641
|
+
// A submit gets its own run counter and controller. Typing aborts the live
|
|
1642
|
+
// validation run, and a submit must not be collateral damage of that.
|
|
1643
|
+
let submitRun = 0;
|
|
1644
|
+
let submitController;
|
|
1645
|
+
const isValid = computed(() => validationResult().valid, ...(ngDevMode ? [{ debugName: "isValid" }] : /* istanbul ignore next */ []));
|
|
1646
|
+
const isValidationComplete = computed(() => validationResult().complete && !isValidating(), ...(ngDevMode ? [{ debugName: "isValidationComplete" }] : /* istanbul ignore next */ []));
|
|
1647
|
+
const validationStatus = computed(() => isValidating() ? 'pending' : validationResult().status, ...(ngDevMode ? [{ debugName: "validationStatus" }] : /* istanbul ignore next */ []));
|
|
1648
|
+
function commitSyncResult(res) {
|
|
1649
|
+
validationResult.set(res);
|
|
1650
|
+
return res.valid;
|
|
1651
|
+
}
|
|
1205
1652
|
function validate() {
|
|
1206
|
-
const res = validateFields(fields, data());
|
|
1653
|
+
const res = validateFields(fields, data(), undefined, validationContext);
|
|
1207
1654
|
errors.set(res.errors);
|
|
1208
|
-
return res
|
|
1655
|
+
return commitSyncResult(res);
|
|
1656
|
+
}
|
|
1657
|
+
async function validateAsync() {
|
|
1658
|
+
const run = ++validationRun;
|
|
1659
|
+
validationController?.abort();
|
|
1660
|
+
const controller = new AbortController();
|
|
1661
|
+
validationController = controller;
|
|
1662
|
+
const snapshot = data();
|
|
1663
|
+
isValidating.set(true);
|
|
1664
|
+
try {
|
|
1665
|
+
const res = await validateFieldsAsync(fields, snapshot, snapshot, {
|
|
1666
|
+
...validationContext,
|
|
1667
|
+
signal: controller.signal,
|
|
1668
|
+
});
|
|
1669
|
+
if (run !== validationRun || data() !== snapshot) {
|
|
1670
|
+
return res.valid;
|
|
1671
|
+
}
|
|
1672
|
+
errors.set(res.errors);
|
|
1673
|
+
validationResult.set(res);
|
|
1674
|
+
return res.valid;
|
|
1675
|
+
}
|
|
1676
|
+
finally {
|
|
1677
|
+
if (run === validationRun) {
|
|
1678
|
+
isValidating.set(false);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1209
1681
|
}
|
|
1210
1682
|
function handleChange(newData) {
|
|
1211
1683
|
const next = applyComputedValues(fields, newData);
|
|
1212
1684
|
data.set(next);
|
|
1213
1685
|
isDirty.set(true);
|
|
1686
|
+
validationController?.abort();
|
|
1687
|
+
validationRun += 1;
|
|
1688
|
+
isValidating.set(false);
|
|
1689
|
+
const res = validateFields(fields, next, undefined, validationContext);
|
|
1690
|
+
commitSyncResult(res);
|
|
1214
1691
|
if (validateOnChange) {
|
|
1215
|
-
const res = validateFields(fields, next);
|
|
1216
1692
|
errors.set(res.errors);
|
|
1217
1693
|
}
|
|
1218
1694
|
}
|
|
@@ -1222,22 +1698,51 @@ function createDynamicFormStore(options) {
|
|
|
1222
1698
|
function setFieldTouched(name, isTouched = true) {
|
|
1223
1699
|
touched.set({ ...touched(), [name]: isTouched });
|
|
1224
1700
|
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Marks every field touched at once. `handleSubmit` calls this for you, so
|
|
1703
|
+
* an invalid submit surfaces errors on fields the user never focused - bind
|
|
1704
|
+
* `[touched]` on `<dfk-multi-field-input>` for it to take effect.
|
|
1705
|
+
*/
|
|
1706
|
+
function touchAll() {
|
|
1707
|
+
touched.set(Object.fromEntries(collectFieldPaths(fields, data()).map((path) => [path, true])));
|
|
1708
|
+
}
|
|
1709
|
+
/** Clears the touched map without touching data, errors or dirty state. */
|
|
1710
|
+
function getDirtyValues() {
|
|
1711
|
+
const baseline = baselineValues();
|
|
1712
|
+
const current = data();
|
|
1713
|
+
const dirty = {};
|
|
1714
|
+
for (const key of Object.keys(current)) {
|
|
1715
|
+
if (!Object.is(current[key], baseline[key])) {
|
|
1716
|
+
dirty[key] = current[key];
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
return dirty;
|
|
1720
|
+
}
|
|
1721
|
+
function resetTouched() {
|
|
1722
|
+
touched.set({});
|
|
1723
|
+
}
|
|
1225
1724
|
function handleBlur(fieldName) {
|
|
1226
1725
|
setFieldTouched(fieldName, true);
|
|
1227
1726
|
if (validateOnBlur) {
|
|
1228
|
-
const res = validateFields(fields, data());
|
|
1727
|
+
const res = validateFields(fields, data(), undefined, validationContext);
|
|
1229
1728
|
errors.set(res.errors);
|
|
1729
|
+
commitSyncResult(res);
|
|
1230
1730
|
}
|
|
1231
1731
|
}
|
|
1232
1732
|
function reset(newValues) {
|
|
1233
1733
|
const seed = newValues ?? initialValues;
|
|
1234
1734
|
const next = applyComputedValues(fields, seed);
|
|
1235
1735
|
data.set(next);
|
|
1736
|
+
baselineValues.set({ ...next });
|
|
1236
1737
|
errors.set({});
|
|
1237
1738
|
isDirty.set(false);
|
|
1238
1739
|
touched.set({});
|
|
1239
1740
|
isSubmitting.set(false);
|
|
1240
1741
|
isSubmitted.set(false);
|
|
1742
|
+
validationController?.abort();
|
|
1743
|
+
validationRun += 1;
|
|
1744
|
+
isValidating.set(false);
|
|
1745
|
+
commitSyncResult(validateFields(fields, next, undefined, validationContext));
|
|
1241
1746
|
}
|
|
1242
1747
|
/**
|
|
1243
1748
|
* Returns a submit handler, mirroring the React and Vue `useDynamicForm`
|
|
@@ -1250,18 +1755,54 @@ function createDynamicFormStore(options) {
|
|
|
1250
1755
|
e.preventDefault();
|
|
1251
1756
|
}
|
|
1252
1757
|
isSubmitting.set(true);
|
|
1758
|
+
const thisSubmit = ++submitRun;
|
|
1253
1759
|
try {
|
|
1254
|
-
|
|
1255
|
-
|
|
1760
|
+
// Touch everything before validating: a submit is the user asserting
|
|
1761
|
+
// the form is finished, so a field they never focused should still
|
|
1762
|
+
// show its error. Without this, submitting an untouched form appears
|
|
1763
|
+
// to do nothing at all.
|
|
1764
|
+
touchAll();
|
|
1765
|
+
const run = ++validationRun;
|
|
1766
|
+
// Cancel any live run so its (older) result cannot land on top of this
|
|
1767
|
+
// one, but validate under a controller of the submit's own.
|
|
1768
|
+
validationController?.abort();
|
|
1769
|
+
submitController?.abort();
|
|
1770
|
+
const controller = new AbortController();
|
|
1771
|
+
submitController = controller;
|
|
1772
|
+
const snapshot = data();
|
|
1773
|
+
isValidating.set(true);
|
|
1774
|
+
const res = await validateFieldsAsync(fields, snapshot, snapshot, {
|
|
1775
|
+
...validationContext,
|
|
1776
|
+
signal: controller.signal,
|
|
1777
|
+
});
|
|
1778
|
+
if (thisSubmit !== submitRun) {
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
// Editing during the submit does not cancel it - the user submitted
|
|
1782
|
+
// this snapshot and is owed an answer for it. What the form *shows*
|
|
1783
|
+
// still has to describe the data on screen, so when it moved on, the
|
|
1784
|
+
// displayed state is re-derived instead of showing the old pass.
|
|
1785
|
+
if (data() === snapshot && run === validationRun) {
|
|
1786
|
+
errors.set(res.errors);
|
|
1787
|
+
validationResult.set(res);
|
|
1788
|
+
}
|
|
1789
|
+
else {
|
|
1790
|
+
const live = validateFields(fields, data(), undefined, validationContext);
|
|
1791
|
+
errors.set(live.errors);
|
|
1792
|
+
validationResult.set(live);
|
|
1793
|
+
}
|
|
1256
1794
|
isSubmitted.set(true);
|
|
1257
1795
|
if (res.valid) {
|
|
1258
|
-
await onValid(
|
|
1796
|
+
await onValid(snapshot);
|
|
1259
1797
|
}
|
|
1260
1798
|
else if (onInvalid) {
|
|
1261
1799
|
onInvalid(res.errors);
|
|
1262
1800
|
}
|
|
1263
1801
|
}
|
|
1264
1802
|
finally {
|
|
1803
|
+
if (thisSubmit === submitRun) {
|
|
1804
|
+
isValidating.set(false);
|
|
1805
|
+
}
|
|
1265
1806
|
isSubmitting.set(false);
|
|
1266
1807
|
}
|
|
1267
1808
|
};
|
|
@@ -1270,16 +1811,24 @@ function createDynamicFormStore(options) {
|
|
|
1270
1811
|
data,
|
|
1271
1812
|
errors,
|
|
1272
1813
|
isValid,
|
|
1814
|
+
isValidating,
|
|
1815
|
+
isValidationComplete,
|
|
1816
|
+
validationStatus,
|
|
1273
1817
|
isDirty,
|
|
1818
|
+
baselineValues,
|
|
1819
|
+
getDirtyValues,
|
|
1274
1820
|
touched,
|
|
1275
1821
|
isSubmitting,
|
|
1276
1822
|
isSubmitted,
|
|
1277
1823
|
setFieldValue,
|
|
1278
1824
|
setFieldTouched,
|
|
1825
|
+
touchAll,
|
|
1826
|
+
resetTouched,
|
|
1279
1827
|
handleChange,
|
|
1280
1828
|
handleBlur,
|
|
1281
1829
|
reset,
|
|
1282
1830
|
validate,
|
|
1831
|
+
validateAsync,
|
|
1283
1832
|
handleSubmit,
|
|
1284
1833
|
};
|
|
1285
1834
|
}
|
|
@@ -1304,8 +1853,8 @@ class ColumnLayout {
|
|
|
1304
1853
|
get gap() {
|
|
1305
1854
|
return this.config?.gap ?? 12;
|
|
1306
1855
|
}
|
|
1307
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1308
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
1856
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ColumnLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1857
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.22", type: ColumnLayout, isStandalone: true, selector: "dfk-column-layout", inputs: { config: "config", template: "template" }, ngImport: i0, template: `<div
|
|
1309
1858
|
[style.display]="'flex'"
|
|
1310
1859
|
[style.flexDirection]="'column'"
|
|
1311
1860
|
[style.gap]="gap + 'px'"
|
|
@@ -1313,7 +1862,7 @@ class ColumnLayout {
|
|
|
1313
1862
|
<ng-container *ngTemplateOutlet="template"></ng-container>
|
|
1314
1863
|
</div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
|
|
1315
1864
|
}
|
|
1316
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1865
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ColumnLayout, decorators: [{
|
|
1317
1866
|
type: Component,
|
|
1318
1867
|
args: [{
|
|
1319
1868
|
selector: 'dfk-column-layout',
|
|
@@ -1338,8 +1887,8 @@ class RowLayout {
|
|
|
1338
1887
|
get gap() {
|
|
1339
1888
|
return this.config?.gap ?? 12;
|
|
1340
1889
|
}
|
|
1341
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1342
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
1890
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RowLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1891
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.22", type: RowLayout, isStandalone: true, selector: "dfk-row-layout", inputs: { config: "config", template: "template" }, ngImport: i0, template: `<div
|
|
1343
1892
|
[style.display]="'flex'"
|
|
1344
1893
|
[style.flexDirection]="'row'"
|
|
1345
1894
|
[style.gap]="gap + 'px'"
|
|
@@ -1347,7 +1896,7 @@ class RowLayout {
|
|
|
1347
1896
|
<ng-container *ngTemplateOutlet="template"></ng-container>
|
|
1348
1897
|
</div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
|
|
1349
1898
|
}
|
|
1350
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1899
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RowLayout, decorators: [{
|
|
1351
1900
|
type: Component,
|
|
1352
1901
|
args: [{
|
|
1353
1902
|
selector: 'dfk-row-layout',
|
|
@@ -1375,8 +1924,8 @@ class GridLayout {
|
|
|
1375
1924
|
get gap() {
|
|
1376
1925
|
return this.config?.gap ?? 12;
|
|
1377
1926
|
}
|
|
1378
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1379
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
1927
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: GridLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1928
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.22", type: GridLayout, isStandalone: true, selector: "dfk-grid-layout", inputs: { config: "config", template: "template" }, ngImport: i0, template: `<div
|
|
1380
1929
|
[style.display]="'grid'"
|
|
1381
1930
|
[style.gridTemplateColumns]="'repeat(' + columns + ', 1fr)'"
|
|
1382
1931
|
[style.gap]="gap + 'px'"
|
|
@@ -1384,7 +1933,7 @@ class GridLayout {
|
|
|
1384
1933
|
<ng-container *ngTemplateOutlet="template"></ng-container>
|
|
1385
1934
|
</div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
|
|
1386
1935
|
}
|
|
1387
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1936
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: GridLayout, decorators: [{
|
|
1388
1937
|
type: Component,
|
|
1389
1938
|
args: [{
|
|
1390
1939
|
selector: 'dfk-grid-layout',
|
|
@@ -1408,8 +1957,8 @@ layoutRegistry.register('row', RowLayout);
|
|
|
1408
1957
|
layoutRegistry.register('grid', GridLayout);
|
|
1409
1958
|
|
|
1410
1959
|
class DynamicFieldKitModule {
|
|
1411
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1412
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "
|
|
1960
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFieldKitModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
1961
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.22", ngImport: i0, type: DynamicFieldKitModule, imports: [CommonModule,
|
|
1413
1962
|
DynamicInput,
|
|
1414
1963
|
FieldInput,
|
|
1415
1964
|
MultiFieldInput,
|
|
@@ -1421,7 +1970,7 @@ class DynamicFieldKitModule {
|
|
|
1421
1970
|
ColumnLayout,
|
|
1422
1971
|
RowLayout,
|
|
1423
1972
|
GridLayout] });
|
|
1424
|
-
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "
|
|
1973
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFieldKitModule, imports: [CommonModule,
|
|
1425
1974
|
DynamicInput,
|
|
1426
1975
|
FieldInput,
|
|
1427
1976
|
MultiFieldInput,
|
|
@@ -1429,7 +1978,7 @@ class DynamicFieldKitModule {
|
|
|
1429
1978
|
RowLayout,
|
|
1430
1979
|
GridLayout] });
|
|
1431
1980
|
}
|
|
1432
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1981
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFieldKitModule, decorators: [{
|
|
1433
1982
|
type: NgModule,
|
|
1434
1983
|
args: [{
|
|
1435
1984
|
imports: [
|