@dynamic-field-kit/angular 1.2.4 → 1.3.3

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.
Files changed (67) hide show
  1. package/README.md +116 -46
  2. package/angular.json +27 -0
  3. package/dist/README.md +188 -0
  4. package/dist/components/BaseInput.d.ts +40 -0
  5. package/dist/components/DynamicInput.d.ts +31 -0
  6. package/dist/components/FieldInput.d.ts +18 -0
  7. package/dist/components/MultiFieldInput.d.ts +40 -0
  8. package/dist/esm2022/components/BaseInput.mjs +68 -0
  9. package/dist/esm2022/components/DynamicInput.mjs +191 -0
  10. package/dist/esm2022/components/FieldInput.mjs +80 -0
  11. package/dist/esm2022/components/MultiFieldInput.mjs +264 -0
  12. package/{esm2020 → dist/esm2022}/dynamic-field-kit-angular.mjs +4 -4
  13. package/dist/esm2022/layout/defaultLayouts.mjs +114 -0
  14. package/dist/esm2022/layout/index.mjs +3 -0
  15. package/dist/esm2022/layout/layoutRegistry.mjs +14 -0
  16. package/dist/esm2022/lib/dynamic-field-kit.module.mjs +52 -0
  17. package/{esm2020 → dist/esm2022}/public-api.mjs +14 -14
  18. package/dist/esm2022/types/layout.mjs +2 -0
  19. package/dist/fesm2022/dynamic-field-kit-angular.mjs +771 -0
  20. package/dist/fesm2022/dynamic-field-kit-angular.mjs.map +1 -0
  21. package/{index.d.ts → dist/index.d.ts} +5 -5
  22. package/dist/layout/defaultLayouts.d.ts +31 -0
  23. package/dist/layout/index.d.ts +2 -0
  24. package/dist/layout/layoutRegistry.d.ts +12 -0
  25. package/{lib → dist/lib}/dynamic-field-kit.module.d.ts +11 -11
  26. package/{public-api.d.ts → dist/public-api.d.ts} +9 -9
  27. package/dist/types/layout.d.ts +25 -0
  28. package/karma.conf.js +37 -0
  29. package/ng-package.json +7 -0
  30. package/package.json +44 -28
  31. package/src/components/BaseInput.ts +60 -0
  32. package/src/components/DynamicInput.ts +224 -0
  33. package/src/components/FieldInput.ts +67 -0
  34. package/src/components/MultiFieldInput.ts +238 -0
  35. package/src/layout/defaultLayouts.ts +70 -0
  36. package/src/layout/index.ts +2 -0
  37. package/src/layout/layoutRegistry.ts +25 -0
  38. package/src/lib/dynamic-field-kit.module.ts +29 -0
  39. package/src/public-api.ts +24 -0
  40. package/src/types/layout.ts +36 -0
  41. package/test/DynamicInput.spec.ts +30 -0
  42. package/test/FieldInput.spec.ts +44 -0
  43. package/test/angular.spec.ts +102 -0
  44. package/test/integration.spec.ts +57 -0
  45. package/test/layouts.spec.ts +26 -0
  46. package/test.ts +12 -0
  47. package/tsconfig.json +13 -0
  48. package/tsconfig.spec.json +21 -0
  49. package/components/BaseInput.d.ts +0 -22
  50. package/components/DynamicInput.d.ts +0 -28
  51. package/components/FieldInput.d.ts +0 -14
  52. package/components/MultiFieldInput.d.ts +0 -23
  53. package/esm2020/components/BaseInput.mjs +0 -46
  54. package/esm2020/components/DynamicInput.mjs +0 -146
  55. package/esm2020/components/FieldInput.mjs +0 -66
  56. package/esm2020/components/MultiFieldInput.mjs +0 -79
  57. package/esm2020/layout/defaultLayouts.mjs +0 -43
  58. package/esm2020/layout/index.mjs +0 -2
  59. package/esm2020/lib/dynamic-field-kit.module.mjs +0 -20
  60. package/esm2020/types/layout.mjs +0 -2
  61. package/fesm2015/dynamic-field-kit-angular.mjs +0 -393
  62. package/fesm2015/dynamic-field-kit-angular.mjs.map +0 -1
  63. package/fesm2020/dynamic-field-kit-angular.mjs +0 -390
  64. package/fesm2020/dynamic-field-kit-angular.mjs.map +0 -1
  65. package/layout/defaultLayouts.d.ts +0 -13
  66. package/layout/index.d.ts +0 -1
  67. package/types/layout.d.ts +0 -1
@@ -0,0 +1,771 @@
1
+ import * as i0 from '@angular/core';
2
+ import { EventEmitter, Output, Input, Component, ViewContainerRef, ViewChild, ChangeDetectionStrategy, HostListener, NgModule } from '@angular/core';
3
+ import * as i1 from '@angular/common';
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';
7
+
8
+ class BaseInputComponent {
9
+ cdr;
10
+ value;
11
+ label;
12
+ placeholder;
13
+ required;
14
+ disabled;
15
+ options;
16
+ className;
17
+ description;
18
+ errorMessage;
19
+ acceptFile;
20
+ maxLength;
21
+ minNumber;
22
+ maxNumber;
23
+ valueChange = new EventEmitter();
24
+ constructor(cdr) {
25
+ this.cdr = cdr;
26
+ }
27
+ ngOnChanges(_changes) {
28
+ this.detectChanges();
29
+ }
30
+ detectChanges() {
31
+ this.cdr.markForCheck();
32
+ }
33
+ hasChanges(changes, prop) {
34
+ return changes[prop] !== undefined;
35
+ }
36
+ 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", options: "options", className: "className", description: "description", errorMessage: "errorMessage", acceptFile: "acceptFile", maxLength: "maxLength", minNumber: "minNumber", maxNumber: "maxNumber" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true });
38
+ }
39
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: BaseInputComponent, decorators: [{
40
+ type: Component,
41
+ args: [{
42
+ template: '',
43
+ }]
44
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { value: [{
45
+ type: Input
46
+ }], label: [{
47
+ type: Input
48
+ }], placeholder: [{
49
+ type: Input
50
+ }], required: [{
51
+ type: Input
52
+ }], disabled: [{
53
+ type: Input
54
+ }], options: [{
55
+ type: Input
56
+ }], className: [{
57
+ type: Input
58
+ }], description: [{
59
+ 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
+ }], valueChange: [{
71
+ type: Output
72
+ }] } });
73
+
74
+ const KNOWN_PROPS = [
75
+ 'value',
76
+ 'label',
77
+ 'placeholder',
78
+ 'required',
79
+ 'disabled',
80
+ 'options',
81
+ 'className',
82
+ 'description',
83
+ 'errorMessage',
84
+ 'acceptFile',
85
+ 'maxLength',
86
+ 'minNumber',
87
+ 'maxNumber',
88
+ ];
89
+ class DynamicInput extends BaseInputComponent {
90
+ type;
91
+ valueChange = new EventEmitter();
92
+ onChange = new EventEmitter();
93
+ host;
94
+ compRef;
95
+ inputInstance;
96
+ subscriptions = [];
97
+ ngOnChanges(changes) {
98
+ super.ngOnChanges(changes);
99
+ if (this.inputInstance) {
100
+ this.syncPropsToInstance(changes);
101
+ }
102
+ if (changes['type'] && !changes['type'].firstChange && this.host) {
103
+ this.render();
104
+ }
105
+ else if (!this.inputInstance && this.host) {
106
+ this.render();
107
+ }
108
+ }
109
+ ngAfterViewInit() {
110
+ this.render();
111
+ }
112
+ ngOnDestroy() {
113
+ this.cleanup();
114
+ }
115
+ syncPropsToInstance(changes) {
116
+ if (!this.inputInstance) {
117
+ return;
118
+ }
119
+ for (const prop of KNOWN_PROPS) {
120
+ if (changes[prop] && this.inputInstance) {
121
+ this.inputInstance[prop] = this[prop];
122
+ }
123
+ }
124
+ this.compRef?.changeDetectorRef?.detectChanges();
125
+ }
126
+ cleanup() {
127
+ this.cleanupSubscriptions();
128
+ this.cleanupRenderedComponent();
129
+ }
130
+ cleanupSubscriptions() {
131
+ this.subscriptions.forEach((s) => s.unsubscribe());
132
+ this.subscriptions = [];
133
+ }
134
+ cleanupRenderedComponent() {
135
+ this.compRef?.destroy();
136
+ this.compRef = undefined;
137
+ this.inputInstance = undefined;
138
+ }
139
+ render() {
140
+ const Renderer = fieldRegistry.get(this.type);
141
+ this.cleanup();
142
+ this.host.clear();
143
+ if (!Renderer) {
144
+ this.renderError(`Unknown field type: ${this.type}`);
145
+ return;
146
+ }
147
+ if (this.isComponentType(Renderer)) {
148
+ this.renderComponent(Renderer);
149
+ }
150
+ else if (typeof Renderer === 'function') {
151
+ this.renderFallback(Renderer);
152
+ }
153
+ else {
154
+ this.renderError(`Invalid renderer for type: ${this.type}`);
155
+ }
156
+ }
157
+ isComponentType(renderer) {
158
+ return (typeof renderer === 'object' && renderer !== null && 'cmp' in renderer);
159
+ }
160
+ renderComponent(compType) {
161
+ try {
162
+ const compRef = this.host.createComponent(compType);
163
+ const instance = compRef.instance;
164
+ if (!instance) {
165
+ this.renderError(`Failed to create instance for ${this.type}`);
166
+ return;
167
+ }
168
+ this.applyProps(instance);
169
+ this.bindOutputs(instance);
170
+ this.compRef = compRef;
171
+ this.inputInstance = instance;
172
+ compRef.changeDetectorRef.detectChanges();
173
+ }
174
+ catch {
175
+ this.renderError(`Failed to render field: ${this.type}`);
176
+ }
177
+ }
178
+ renderFallback(renderer) {
179
+ try {
180
+ const props = this.getFallbackProps();
181
+ const result = renderer(props);
182
+ if (typeof result === 'string') {
183
+ const el = document.createElement('div');
184
+ el.innerHTML = result;
185
+ this.host.element.nativeElement.appendChild(el);
186
+ }
187
+ }
188
+ catch {
189
+ this.renderError(`Failed to render field: ${this.type}`);
190
+ }
191
+ }
192
+ getFallbackProps() {
193
+ return {
194
+ value: this.value,
195
+ onValueChange: (v) => this.emitValue(v),
196
+ label: this.label ?? '',
197
+ placeholder: this.placeholder ?? '',
198
+ required: this.required ?? false,
199
+ options: this.options ?? [],
200
+ className: this.className ?? '',
201
+ description: this.description ?? '',
202
+ disabled: this.disabled ?? false,
203
+ errorMessage: this.errorMessage ?? '',
204
+ };
205
+ }
206
+ applyProps(instance) {
207
+ const instanceObj = instance;
208
+ for (const prop of KNOWN_PROPS) {
209
+ if (prop in this && prop in instanceObj) {
210
+ instanceObj[prop] = this[prop];
211
+ }
212
+ }
213
+ }
214
+ bindOutputs(instance) {
215
+ const outputNames = [
216
+ 'valueChange',
217
+ 'onValueChange',
218
+ ];
219
+ for (const outputName of outputNames) {
220
+ const output = instance[outputName];
221
+ if (output && typeof output === 'object' && 'subscribe' in output) {
222
+ const sub = output.subscribe((value) => this.emitValue(value));
223
+ this.subscriptions.push(sub);
224
+ }
225
+ }
226
+ }
227
+ emitValue(value) {
228
+ this.valueChange.emit(value);
229
+ this.onChange.emit(value);
230
+ }
231
+ renderError(message) {
232
+ const el = document.createElement('div');
233
+ el.textContent = message;
234
+ el.style.color = 'red';
235
+ this.host.element.nativeElement.appendChild(el);
236
+ }
237
+ 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 });
239
+ }
240
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DynamicInput, decorators: [{
241
+ type: Component,
242
+ args: [{
243
+ selector: 'dfk-dynamic-input',
244
+ standalone: true,
245
+ imports: [CommonModule],
246
+ changeDetection: ChangeDetectionStrategy.OnPush,
247
+ template: `<div #host style="display: contents;"></div>`,
248
+ }]
249
+ }], propDecorators: { type: [{
250
+ type: Input
251
+ }], valueChange: [{
252
+ type: Output
253
+ }], onChange: [{
254
+ type: Output
255
+ }], host: [{
256
+ type: ViewChild,
257
+ args: ['host', { read: ViewContainerRef, static: false }]
258
+ }] } });
259
+
260
+ class FieldInput {
261
+ cdr;
262
+ fieldDescription;
263
+ value;
264
+ onValueChangeField = new EventEmitter();
265
+ shouldRender = false;
266
+ constructor(cdr) {
267
+ this.cdr = cdr;
268
+ }
269
+ ngOnChanges(_changes) {
270
+ this.shouldRender = !!this.fieldDescription;
271
+ this.cdr.markForCheck();
272
+ }
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
+ 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: `
284
+ <dfk-dynamic-input
285
+ *ngIf="shouldRender"
286
+ [type]="fieldDescription!.type"
287
+ [value]="getFieldValue()"
288
+ [label]="fieldDescription!.label"
289
+ [placeholder]="fieldDescription!.placeholder"
290
+ [required]="fieldDescription!.required"
291
+ [description]="$any(fieldDescription!.description)"
292
+ [options]="fieldDescription!.options"
293
+ [className]="fieldDescription!.className"
294
+ (valueChange)="
295
+ onValueChangeField.emit({ value: $event, key: fieldDescription!.name })
296
+ "
297
+ [disabled]="false"
298
+ [errorMessage]="''"
299
+ ></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 });
301
+ }
302
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: FieldInput, decorators: [{
303
+ type: Component,
304
+ args: [{
305
+ selector: 'dfk-field-input',
306
+ standalone: true,
307
+ imports: [NgIf, DynamicInput],
308
+ changeDetection: ChangeDetectionStrategy.OnPush,
309
+ template: `
310
+ <dfk-dynamic-input
311
+ *ngIf="shouldRender"
312
+ [type]="fieldDescription!.type"
313
+ [value]="getFieldValue()"
314
+ [label]="fieldDescription!.label"
315
+ [placeholder]="fieldDescription!.placeholder"
316
+ [required]="fieldDescription!.required"
317
+ [description]="$any(fieldDescription!.description)"
318
+ [options]="fieldDescription!.options"
319
+ [className]="fieldDescription!.className"
320
+ (valueChange)="
321
+ onValueChangeField.emit({ value: $event, key: fieldDescription!.name })
322
+ "
323
+ [disabled]="false"
324
+ [errorMessage]="''"
325
+ ></dfk-dynamic-input>
326
+ `,
327
+ }]
328
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { fieldDescription: [{
329
+ type: Input
330
+ }], value: [{
331
+ type: Input
332
+ }], onValueChangeField: [{
333
+ type: Output
334
+ }] } });
335
+
336
+ const DEFAULT_BREAKPOINT = 768;
337
+ class MultiFieldInput {
338
+ cdr;
339
+ fieldDescriptions = [];
340
+ properties;
341
+ onChange = new EventEmitter();
342
+ layout = 'column';
343
+ data = {};
344
+ visibleFields = [];
345
+ isMobile = false;
346
+ constructor(cdr) {
347
+ this.cdr = cdr;
348
+ }
349
+ onWindowResize() {
350
+ const wasMobile = this.isMobile;
351
+ this.updateIsMobile();
352
+ if (wasMobile !== this.isMobile) {
353
+ this.cdr.markForCheck();
354
+ }
355
+ }
356
+ get resolvedLayout() {
357
+ if (typeof this.layout === 'object' && this.layout.type === 'responsive') {
358
+ return this.isMobile ? this.layout.mobile : this.layout.desktop;
359
+ }
360
+ return this.layout;
361
+ }
362
+ get resolvedLayoutType() {
363
+ const resolved = this.resolvedLayout;
364
+ return typeof resolved === 'string' ? resolved : resolved.type;
365
+ }
366
+ get gap() {
367
+ const resolved = this.resolvedLayout;
368
+ if (typeof resolved === 'object' && resolved.gap !== undefined) {
369
+ return resolved.gap;
370
+ }
371
+ return 12;
372
+ }
373
+ get columns() {
374
+ const resolved = this.resolvedLayout;
375
+ if (typeof resolved === 'object' && resolved.type === 'grid') {
376
+ return resolved.columns ?? 2;
377
+ }
378
+ return 2;
379
+ }
380
+ trackByFn(index, field) {
381
+ return field.name || index;
382
+ }
383
+ trackByIndex(index) {
384
+ return index;
385
+ }
386
+ ngOnInit() {
387
+ this.updateIsMobile();
388
+ this.init();
389
+ }
390
+ ngOnChanges(_changes) {
391
+ this.init();
392
+ }
393
+ updateIsMobile() {
394
+ const breakpoint = typeof this.layout === 'object' && this.layout.type === 'responsive'
395
+ ? this.layout.breakpoint ?? DEFAULT_BREAKPOINT
396
+ : DEFAULT_BREAKPOINT;
397
+ this.isMobile =
398
+ typeof window !== 'undefined' && window.innerWidth < breakpoint;
399
+ }
400
+ init() {
401
+ if (this.properties) {
402
+ this.data = applyComputedValues(this.fieldDescriptions, {
403
+ ...this.properties,
404
+ });
405
+ }
406
+ this.updateVisibleFields();
407
+ }
408
+ updateVisibleFields() {
409
+ this.visibleFields = this.fieldDescriptions.filter((f) => !f.appearCondition || f.appearCondition(this.data));
410
+ }
411
+ onFieldChange(event) {
412
+ this.commitData({ ...this.data, [event.key]: event.value });
413
+ }
414
+ getItems(field) {
415
+ const value = this.data[field.name];
416
+ return Array.isArray(value) ? value : [];
417
+ }
418
+ canAddItem(field) {
419
+ return canAddGroupItem(field, this.getItems(field));
420
+ }
421
+ canRemoveItem(field) {
422
+ return canRemoveGroupItem(field, this.getItems(field));
423
+ }
424
+ onGroupItemAdd(field) {
425
+ if (!this.canAddItem(field)) {
426
+ return;
427
+ }
428
+ const items = this.getItems(field);
429
+ this.commitData({
430
+ ...this.data,
431
+ [field.name]: [...items, createGroupItem(field)],
432
+ });
433
+ }
434
+ onGroupItemRemove(field, index) {
435
+ if (!this.canRemoveItem(field)) {
436
+ return;
437
+ }
438
+ const items = this.getItems(field).filter((_, i) => i !== index);
439
+ this.commitData({ ...this.data, [field.name]: items });
440
+ }
441
+ onGroupItemChange(field, index, next) {
442
+ const items = this.getItems(field).slice();
443
+ items[index] = next;
444
+ this.commitData({ ...this.data, [field.name]: items });
445
+ }
446
+ commitData(nextData) {
447
+ this.data = applyComputedValues(this.fieldDescriptions, nextData);
448
+ this.updateVisibleFields();
449
+ this.onChange.emit(this.data);
450
+ this.cdr.markForCheck();
451
+ }
452
+ 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: `
454
+ <div
455
+ class="p-4 border rounded-lg bg-gray-50"
456
+ [ngClass]="{
457
+ 'flex flex-col': resolvedLayoutType === 'column',
458
+ 'flex flex-row': resolvedLayoutType === 'row',
459
+ grid: resolvedLayoutType === 'grid'
460
+ }"
461
+ [style.gap.px]="gap"
462
+ [style.gridTemplateColumns]="
463
+ resolvedLayoutType === 'grid' ? 'repeat(' + columns + ', 1fr)' : null
464
+ "
465
+ >
466
+ <ng-container *ngFor="let field of visibleFields; trackBy: trackByFn">
467
+ <dfk-field-input
468
+ *ngIf="!field.fields"
469
+ [fieldDescription]="field"
470
+ [value]="data[field.name]"
471
+ (onValueChangeField)="onFieldChange($event)"
472
+ ></dfk-field-input>
473
+
474
+ <div *ngIf="field.fields" [class]="field.className">
475
+ <div *ngIf="field.label">{{ field.label }}</div>
476
+ <div
477
+ *ngFor="
478
+ let item of getItems(field);
479
+ let i = index;
480
+ trackBy: trackByIndex
481
+ "
482
+ style="display: flex; align-items: flex-start; gap: 8px;"
483
+ >
484
+ <div style="flex: 1">
485
+ <dfk-multi-field-input
486
+ [fieldDescriptions]="field.fields"
487
+ [properties]="item"
488
+ (onChange)="onGroupItemChange(field, i, $event)"
489
+ ></dfk-multi-field-input>
490
+ </div>
491
+ <button
492
+ type="button"
493
+ (click)="onGroupItemRemove(field, i)"
494
+ [disabled]="!canRemoveItem(field)"
495
+ >
496
+ {{ field.removeLabel || 'Remove' }}
497
+ </button>
498
+ </div>
499
+ <button
500
+ type="button"
501
+ (click)="onGroupItemAdd(field)"
502
+ [disabled]="!canAddItem(field)"
503
+ >
504
+ {{ field.addLabel || 'Add' }}
505
+ </button>
506
+ </div>
507
+ </ng-container>
508
+ </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 });
510
+ }
511
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: MultiFieldInput, decorators: [{
512
+ type: Component,
513
+ args: [{
514
+ selector: 'dfk-multi-field-input',
515
+ standalone: true,
516
+ // Repeatable field groups render a nested <dfk-multi-field-input> per item
517
+ // (see the #groupTpl below), so this component imports itself. That's the
518
+ // supported way to give a standalone component recursive template usage
519
+ // without a cross-file circular import between FieldInput and a separate
520
+ // group component (whose `imports` arrays are evaluated eagerly at module
521
+ // load time and would deadlock on an actual circular module reference).
522
+ imports: [NgClass, NgFor, NgIf, FieldInput, MultiFieldInput],
523
+ changeDetection: ChangeDetectionStrategy.OnPush,
524
+ template: `
525
+ <div
526
+ class="p-4 border rounded-lg bg-gray-50"
527
+ [ngClass]="{
528
+ 'flex flex-col': resolvedLayoutType === 'column',
529
+ 'flex flex-row': resolvedLayoutType === 'row',
530
+ grid: resolvedLayoutType === 'grid'
531
+ }"
532
+ [style.gap.px]="gap"
533
+ [style.gridTemplateColumns]="
534
+ resolvedLayoutType === 'grid' ? 'repeat(' + columns + ', 1fr)' : null
535
+ "
536
+ >
537
+ <ng-container *ngFor="let field of visibleFields; trackBy: trackByFn">
538
+ <dfk-field-input
539
+ *ngIf="!field.fields"
540
+ [fieldDescription]="field"
541
+ [value]="data[field.name]"
542
+ (onValueChangeField)="onFieldChange($event)"
543
+ ></dfk-field-input>
544
+
545
+ <div *ngIf="field.fields" [class]="field.className">
546
+ <div *ngIf="field.label">{{ field.label }}</div>
547
+ <div
548
+ *ngFor="
549
+ let item of getItems(field);
550
+ let i = index;
551
+ trackBy: trackByIndex
552
+ "
553
+ style="display: flex; align-items: flex-start; gap: 8px;"
554
+ >
555
+ <div style="flex: 1">
556
+ <dfk-multi-field-input
557
+ [fieldDescriptions]="field.fields"
558
+ [properties]="item"
559
+ (onChange)="onGroupItemChange(field, i, $event)"
560
+ ></dfk-multi-field-input>
561
+ </div>
562
+ <button
563
+ type="button"
564
+ (click)="onGroupItemRemove(field, i)"
565
+ [disabled]="!canRemoveItem(field)"
566
+ >
567
+ {{ field.removeLabel || 'Remove' }}
568
+ </button>
569
+ </div>
570
+ <button
571
+ type="button"
572
+ (click)="onGroupItemAdd(field)"
573
+ [disabled]="!canAddItem(field)"
574
+ >
575
+ {{ field.addLabel || 'Add' }}
576
+ </button>
577
+ </div>
578
+ </ng-container>
579
+ </div>
580
+ `,
581
+ }]
582
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { fieldDescriptions: [{
583
+ type: Input
584
+ }], properties: [{
585
+ type: Input
586
+ }], onChange: [{
587
+ type: Output
588
+ }], layout: [{
589
+ type: Input
590
+ }], onWindowResize: [{
591
+ type: HostListener,
592
+ args: ['window:resize']
593
+ }] } });
594
+
595
+ class LayoutRegistry {
596
+ layouts = new Map();
597
+ register(type, component) {
598
+ if (this.layouts.has(type)) {
599
+ console.warn(`[dynamic-field-kit] Layout "${type}" already exists`);
600
+ }
601
+ this.layouts.set(type, component);
602
+ }
603
+ get(type) {
604
+ return this.layouts.get(type);
605
+ }
606
+ }
607
+ const layoutRegistry = new LayoutRegistry();
608
+
609
+ class ColumnLayout {
610
+ config;
611
+ template;
612
+ get gap() {
613
+ return this.config?.gap ?? 12;
614
+ }
615
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ColumnLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
616
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: ColumnLayout, isStandalone: true, selector: "dfk-column-layout", inputs: { config: "config", template: "template" }, ngImport: i0, template: `<div
617
+ [style.display]="'flex'"
618
+ [style.flexDirection]="'column'"
619
+ [style.gap]="gap + 'px'"
620
+ >
621
+ <ng-container *ngTemplateOutlet="template"></ng-container>
622
+ </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
623
+ }
624
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ColumnLayout, decorators: [{
625
+ type: Component,
626
+ args: [{
627
+ selector: 'dfk-column-layout',
628
+ standalone: true,
629
+ imports: [CommonModule],
630
+ template: `<div
631
+ [style.display]="'flex'"
632
+ [style.flexDirection]="'column'"
633
+ [style.gap]="gap + 'px'"
634
+ >
635
+ <ng-container *ngTemplateOutlet="template"></ng-container>
636
+ </div>`,
637
+ }]
638
+ }], propDecorators: { config: [{
639
+ type: Input
640
+ }], template: [{
641
+ type: Input
642
+ }] } });
643
+ class RowLayout {
644
+ config;
645
+ template;
646
+ get gap() {
647
+ return this.config?.gap ?? 12;
648
+ }
649
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: RowLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
650
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: RowLayout, isStandalone: true, selector: "dfk-row-layout", inputs: { config: "config", template: "template" }, ngImport: i0, template: `<div
651
+ [style.display]="'flex'"
652
+ [style.flexDirection]="'row'"
653
+ [style.gap]="gap + 'px'"
654
+ >
655
+ <ng-container *ngTemplateOutlet="template"></ng-container>
656
+ </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
657
+ }
658
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: RowLayout, decorators: [{
659
+ type: Component,
660
+ args: [{
661
+ selector: 'dfk-row-layout',
662
+ standalone: true,
663
+ imports: [CommonModule],
664
+ template: `<div
665
+ [style.display]="'flex'"
666
+ [style.flexDirection]="'row'"
667
+ [style.gap]="gap + 'px'"
668
+ >
669
+ <ng-container *ngTemplateOutlet="template"></ng-container>
670
+ </div>`,
671
+ }]
672
+ }], propDecorators: { config: [{
673
+ type: Input
674
+ }], template: [{
675
+ type: Input
676
+ }] } });
677
+ class GridLayout {
678
+ config;
679
+ template;
680
+ get columns() {
681
+ return this.config?.columns ?? 2;
682
+ }
683
+ get gap() {
684
+ return this.config?.gap ?? 12;
685
+ }
686
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GridLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
687
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: GridLayout, isStandalone: true, selector: "dfk-grid-layout", inputs: { config: "config", template: "template" }, ngImport: i0, template: `<div
688
+ [style.display]="'grid'"
689
+ [style.gridTemplateColumns]="'repeat(' + columns + ', 1fr)'"
690
+ [style.gap]="gap + 'px'"
691
+ >
692
+ <ng-container *ngTemplateOutlet="template"></ng-container>
693
+ </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
694
+ }
695
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GridLayout, decorators: [{
696
+ type: Component,
697
+ args: [{
698
+ selector: 'dfk-grid-layout',
699
+ standalone: true,
700
+ imports: [CommonModule],
701
+ template: `<div
702
+ [style.display]="'grid'"
703
+ [style.gridTemplateColumns]="'repeat(' + columns + ', 1fr)'"
704
+ [style.gap]="gap + 'px'"
705
+ >
706
+ <ng-container *ngTemplateOutlet="template"></ng-container>
707
+ </div>`,
708
+ }]
709
+ }], propDecorators: { config: [{
710
+ type: Input
711
+ }], template: [{
712
+ type: Input
713
+ }] } });
714
+ layoutRegistry.register('column', ColumnLayout);
715
+ layoutRegistry.register('row', RowLayout);
716
+ layoutRegistry.register('grid', GridLayout);
717
+
718
+ class DynamicFieldKitModule {
719
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DynamicFieldKitModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
720
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: DynamicFieldKitModule, imports: [CommonModule,
721
+ DynamicInput,
722
+ FieldInput,
723
+ MultiFieldInput,
724
+ ColumnLayout,
725
+ RowLayout,
726
+ GridLayout], exports: [DynamicInput,
727
+ FieldInput,
728
+ MultiFieldInput,
729
+ ColumnLayout,
730
+ RowLayout,
731
+ GridLayout] });
732
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DynamicFieldKitModule, imports: [CommonModule,
733
+ DynamicInput,
734
+ FieldInput,
735
+ MultiFieldInput,
736
+ ColumnLayout,
737
+ RowLayout,
738
+ GridLayout] });
739
+ }
740
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DynamicFieldKitModule, decorators: [{
741
+ type: NgModule,
742
+ args: [{
743
+ imports: [
744
+ CommonModule,
745
+ DynamicInput,
746
+ FieldInput,
747
+ MultiFieldInput,
748
+ ColumnLayout,
749
+ RowLayout,
750
+ GridLayout,
751
+ ],
752
+ exports: [
753
+ DynamicInput,
754
+ FieldInput,
755
+ MultiFieldInput,
756
+ ColumnLayout,
757
+ RowLayout,
758
+ GridLayout,
759
+ ],
760
+ }]
761
+ }] });
762
+
763
+ // public-api.ts
764
+ // Components
765
+
766
+ /**
767
+ * Generated bundle index. Do not edit.
768
+ */
769
+
770
+ export { BaseInputComponent, ColumnLayout, DynamicFieldKitModule, DynamicInput, FieldInput, GridLayout, LayoutRegistry, MultiFieldInput, RowLayout, layoutRegistry };
771
+ //# sourceMappingURL=dynamic-field-kit-angular.mjs.map