@dynamic-field-kit/angular 1.2.10 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +113 -4
  3. package/dist/README.md +113 -4
  4. package/dist/components/BaseInput.d.ts +23 -11
  5. package/dist/components/DynamicInput.d.ts +19 -8
  6. package/dist/components/FieldInput.d.ts +15 -7
  7. package/dist/components/MultiFieldInput.d.ts +30 -4
  8. package/dist/esm2022/components/BaseInput.mjs +17 -18
  9. package/dist/esm2022/components/DynamicInput.mjs +166 -84
  10. package/dist/esm2022/components/FieldInput.mjs +54 -26
  11. package/dist/esm2022/components/MultiFieldInput.mjs +282 -29
  12. package/dist/esm2022/fieldRegistryToken.mjs +12 -0
  13. package/dist/esm2022/layout/defaultLayouts.mjs +70 -23
  14. package/dist/esm2022/layout/index.mjs +2 -1
  15. package/dist/esm2022/layout/layoutRegistry.mjs +14 -0
  16. package/dist/esm2022/public-api.mjs +6 -2
  17. package/dist/esm2022/types/layout.mjs +1 -1
  18. package/dist/fesm2022/dynamic-field-kit-angular.mjs +604 -173
  19. package/dist/fesm2022/dynamic-field-kit-angular.mjs.map +1 -1
  20. package/dist/fieldRegistryToken.d.ts +3 -0
  21. package/dist/layout/defaultLayouts.d.ts +21 -3
  22. package/dist/layout/index.d.ts +1 -0
  23. package/dist/layout/layoutRegistry.d.ts +12 -0
  24. package/dist/public-api.d.ts +5 -2
  25. package/dist/types/layout.d.ts +4 -1
  26. package/package.json +19 -8
  27. package/src/components/BaseInput.ts +36 -10
  28. package/src/components/DynamicInput.ts +196 -104
  29. package/src/components/FieldInput.ts +43 -18
  30. package/src/components/MultiFieldInput.ts +275 -19
  31. package/src/fieldRegistryToken.ts +15 -0
  32. package/src/layout/defaultLayouts.ts +42 -10
  33. package/src/layout/index.ts +1 -0
  34. package/src/layout/layoutRegistry.ts +25 -0
  35. package/src/public-api.ts +40 -24
  36. package/src/types/layout.ts +14 -1
  37. package/test/DynamicInput.spec.ts +230 -0
  38. package/test/FieldInput.spec.ts +146 -0
  39. package/test/MultiFieldInput.spec.ts +256 -0
  40. package/test/helpers/renderers.ts +89 -0
  41. package/test/layout.spec.ts +119 -0
  42. package/test/publicApi.spec.ts +64 -0
  43. package/test/setup.ts +12 -0
  44. package/test/smoke.spec.ts +27 -0
  45. package/tsconfig.json +13 -13
  46. package/tsconfig.spec.json +9 -0
  47. package/vitest.config.ts +30 -0
  48. package/src/adapters/tailwind.ts +0 -13
  49. package/src/components/imports.ts +0 -3
  50. package/src/examples/index.ts +0 -4
  51. package/src/examples/registerExample.ts +0 -7
  52. package/src/examples/text-field.component.ts +0 -18
  53. package/src/types.ts +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # @dynamic-field-kit/angular
2
+
3
+ ## 1.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 132f74b: Framework adapters: validation support, scoped field registries via dependency
8
+ injection, and unified cross-framework layout types.
9
+
10
+ - Validation wired through `DynamicInput` / `FieldInput` / `MultiFieldInput`.
11
+ - Scoped `FieldRegistry` injection (React context, Vue provide/inject, Angular
12
+ `FIELD_REGISTRY` token) so consumers can supply their own registry.
13
+ - Shared layout type definitions aligned across React, Vue, and Angular.
14
+
15
+ Requires `@dynamic-field-kit/core@^1.3.0` (peer dependency) for the new
16
+ validation and layout APIs.
17
+
18
+ Note: `@dynamic-field-kit/core` is a peer dependency (not bundled). Consumers
19
+ must install it alongside the adapter.
package/README.md CHANGED
@@ -12,9 +12,7 @@ Demo app: https://github.com/vannt-dev/dynamic-field-kit-demo
12
12
  npm install @dynamic-field-kit/core @dynamic-field-kit/angular
13
13
  ```
14
14
 
15
- Note: Core is shared runtime. Install core separately and ensure a single version is used across adapters to avoid duplicate registries.
16
-
17
- - Install with core: `npm install @dynamic-field-kit/core @dynamic-field-kit/angular`
15
+ Note: `@dynamic-field-kit/core`, `@angular/core`, and `@angular/common` are **peer dependencies** this adapter does not bundle or auto-install them, so add them to your app explicitly (as shown above). Keep a single `@dynamic-field-kit/core` version across all adapters so they share one registry.
18
16
 
19
17
  If you need to pin versions explicitly:
20
18
 
@@ -29,6 +27,9 @@ npm install @dynamic-field-kit/core@^1.0.12 @dynamic-field-kit/angular@^1.2.3
29
27
  - `MultiFieldInput`
30
28
  - `DynamicFieldKitModule`
31
29
  - `fieldRegistry`
30
+ - `FieldRegistry` (class, for scoped registries)
31
+ - `FIELD_REGISTRY` (injection token)
32
+ - `validateField` / `validateFields` / `resolveDisabled` / `resolveReadOnly` / `ValidationResult`
32
33
 
33
34
  ## Basic setup (Angular 19+)
34
35
 
@@ -88,6 +89,113 @@ export class AppComponent {
88
89
  ></dfk-multi-field-input>
89
90
  ```
90
91
 
92
+ ## Layouts
93
+
94
+ `MultiFieldInput` supports `column`, `row`, `grid`, and `responsive` (mobile/desktop with a custom breakpoint), matching the React and Vue adapters:
95
+
96
+ ```html
97
+ <dfk-multi-field-input
98
+ [fieldDescriptions]="fields"
99
+ [layout]="{ type: 'grid', columns: 3, gap: 16 }"
100
+ ></dfk-multi-field-input>
101
+
102
+ <dfk-multi-field-input
103
+ [fieldDescriptions]="fields"
104
+ [layout]="{
105
+ type: 'responsive',
106
+ mobile: 'column',
107
+ desktop: { type: 'grid', columns: 2, gap: 12 }
108
+ }"
109
+ ></dfk-multi-field-input>
110
+ ```
111
+
112
+ ## Derived fields with `computeValue`
113
+
114
+ Give a field a `computeValue` to derive its value from the rest of the form data whenever any field changes:
115
+
116
+ ```ts
117
+ fields: FieldDescription[] = [
118
+ { name: 'firstName', type: 'text' },
119
+ { name: 'lastName', type: 'text' },
120
+ {
121
+ name: 'fullName',
122
+ type: 'text',
123
+ computeValue: (data) => `${data.firstName ?? ''} ${data.lastName ?? ''}`.trim(),
124
+ },
125
+ ];
126
+ ```
127
+
128
+ ## Validation & conditions
129
+
130
+ Declare a `validate` hook and dynamic `disabledCondition`/`readOnlyCondition`;
131
+ your renderer component receives `error`, `disabled`, and `readOnly` inputs, and
132
+ `dfk-multi-field-input` emits `(validityChange)`:
133
+
134
+ ```html
135
+ <dfk-multi-field-input
136
+ [fieldDescriptions]="fields"
137
+ [properties]="data"
138
+ (onChange)="onChange($event)"
139
+ (validityChange)="canSubmit = $event.valid"
140
+ ></dfk-multi-field-input>
141
+ ```
142
+
143
+ For submit-time whole-form validation, call `validateFields(fields, data)`.
144
+
145
+ ## Repeatable field groups
146
+
147
+ A field with `fields` renders as a repeatable group: `data[name]` becomes an array of items, each shaped by the nested `fields`, with "Add"/"Remove" controls rendered automatically.
148
+
149
+ ```ts
150
+ fields: FieldDescription[] = [
151
+ {
152
+ name: 'contacts',
153
+ type: 'group',
154
+ label: 'Contacts',
155
+ fields: [
156
+ { name: 'email', type: 'text', label: 'Email' },
157
+ { name: 'phone', type: 'text', label: 'Phone' },
158
+ ],
159
+ defaultItem: { email: '', phone: '' },
160
+ keyField: 'id', // optional: stable trackBy key instead of the array index
161
+ minItems: 1,
162
+ maxItems: 5,
163
+ },
164
+ ];
165
+ ```
166
+
167
+ ```html
168
+ <dfk-multi-field-input [fieldDescriptions]="fields"></dfk-multi-field-input>
169
+ ```
170
+
171
+ ## Scoped registries
172
+
173
+ `fieldRegistry` is a process-wide singleton. To give a component or route its own renderers, provide the `FIELD_REGISTRY` token with an isolated `FieldRegistry`. Components without an override keep using the global singleton.
174
+
175
+ ```ts
176
+ import { FieldRegistry, FIELD_REGISTRY } from '@dynamic-field-kit/angular';
177
+
178
+ const registry = new FieldRegistry();
179
+ registry.register('text', TextFieldComponent as any);
180
+
181
+ @Component({
182
+ selector: 'app-scoped-form',
183
+ standalone: true,
184
+ imports: [MultiFieldInput],
185
+ providers: [{ provide: FIELD_REGISTRY, useValue: registry }],
186
+ template: `<dfk-multi-field-input [fieldDescriptions]="fields" />`,
187
+ })
188
+ export class ScopedFormComponent {}
189
+ ```
190
+
191
+ ## Passing renderer-specific props
192
+
193
+ The generic adapter forwards only the shared `FieldRendererProps`. For inputs specific to one renderer (e.g. `acceptFile`, `maxLength`), pass them per field via `FieldDescription.props`; they are set on the renderer instance verbatim.
194
+
195
+ ```ts
196
+ { name: 'avatar', type: 'file', props: { acceptFile: 'image/*' } }
197
+ ```
198
+
91
199
  ## Legacy setup (Angular 14 and earlier with NgModule)
92
200
 
93
201
  ```ts
@@ -118,7 +226,8 @@ declare module '@dynamic-field-kit/core' {
118
226
 
119
227
  - Register Angular component classes in `fieldRegistry`.
120
228
  - Do not register React or Vue renderers in the Angular adapter.
121
- - `MultiFieldInput` supports `column`, `row`, and `grid` layouts.
229
+ - `MultiFieldInput` supports `column`, `row`, `grid`, and `responsive` layouts, and derives fields using `computeValue`.
230
+ - Fields with `fields` render as repeatable groups instead of going through `fieldRegistry`.
122
231
  - The shared schema and field types still come from `@dynamic-field-kit/core`.
123
232
 
124
233
  ## License
package/dist/README.md CHANGED
@@ -12,9 +12,7 @@ Demo app: https://github.com/vannt-dev/dynamic-field-kit-demo
12
12
  npm install @dynamic-field-kit/core @dynamic-field-kit/angular
13
13
  ```
14
14
 
15
- Note: Core is shared runtime. Install core separately and ensure a single version is used across adapters to avoid duplicate registries.
16
-
17
- - Install with core: `npm install @dynamic-field-kit/core @dynamic-field-kit/angular`
15
+ Note: `@dynamic-field-kit/core`, `@angular/core`, and `@angular/common` are **peer dependencies** this adapter does not bundle or auto-install them, so add them to your app explicitly (as shown above). Keep a single `@dynamic-field-kit/core` version across all adapters so they share one registry.
18
16
 
19
17
  If you need to pin versions explicitly:
20
18
 
@@ -29,6 +27,9 @@ npm install @dynamic-field-kit/core@^1.0.12 @dynamic-field-kit/angular@^1.2.3
29
27
  - `MultiFieldInput`
30
28
  - `DynamicFieldKitModule`
31
29
  - `fieldRegistry`
30
+ - `FieldRegistry` (class, for scoped registries)
31
+ - `FIELD_REGISTRY` (injection token)
32
+ - `validateField` / `validateFields` / `resolveDisabled` / `resolveReadOnly` / `ValidationResult`
32
33
 
33
34
  ## Basic setup (Angular 19+)
34
35
 
@@ -88,6 +89,113 @@ export class AppComponent {
88
89
  ></dfk-multi-field-input>
89
90
  ```
90
91
 
92
+ ## Layouts
93
+
94
+ `MultiFieldInput` supports `column`, `row`, `grid`, and `responsive` (mobile/desktop with a custom breakpoint), matching the React and Vue adapters:
95
+
96
+ ```html
97
+ <dfk-multi-field-input
98
+ [fieldDescriptions]="fields"
99
+ [layout]="{ type: 'grid', columns: 3, gap: 16 }"
100
+ ></dfk-multi-field-input>
101
+
102
+ <dfk-multi-field-input
103
+ [fieldDescriptions]="fields"
104
+ [layout]="{
105
+ type: 'responsive',
106
+ mobile: 'column',
107
+ desktop: { type: 'grid', columns: 2, gap: 12 }
108
+ }"
109
+ ></dfk-multi-field-input>
110
+ ```
111
+
112
+ ## Derived fields with `computeValue`
113
+
114
+ Give a field a `computeValue` to derive its value from the rest of the form data whenever any field changes:
115
+
116
+ ```ts
117
+ fields: FieldDescription[] = [
118
+ { name: 'firstName', type: 'text' },
119
+ { name: 'lastName', type: 'text' },
120
+ {
121
+ name: 'fullName',
122
+ type: 'text',
123
+ computeValue: (data) => `${data.firstName ?? ''} ${data.lastName ?? ''}`.trim(),
124
+ },
125
+ ];
126
+ ```
127
+
128
+ ## Validation & conditions
129
+
130
+ Declare a `validate` hook and dynamic `disabledCondition`/`readOnlyCondition`;
131
+ your renderer component receives `error`, `disabled`, and `readOnly` inputs, and
132
+ `dfk-multi-field-input` emits `(validityChange)`:
133
+
134
+ ```html
135
+ <dfk-multi-field-input
136
+ [fieldDescriptions]="fields"
137
+ [properties]="data"
138
+ (onChange)="onChange($event)"
139
+ (validityChange)="canSubmit = $event.valid"
140
+ ></dfk-multi-field-input>
141
+ ```
142
+
143
+ For submit-time whole-form validation, call `validateFields(fields, data)`.
144
+
145
+ ## Repeatable field groups
146
+
147
+ A field with `fields` renders as a repeatable group: `data[name]` becomes an array of items, each shaped by the nested `fields`, with "Add"/"Remove" controls rendered automatically.
148
+
149
+ ```ts
150
+ fields: FieldDescription[] = [
151
+ {
152
+ name: 'contacts',
153
+ type: 'group',
154
+ label: 'Contacts',
155
+ fields: [
156
+ { name: 'email', type: 'text', label: 'Email' },
157
+ { name: 'phone', type: 'text', label: 'Phone' },
158
+ ],
159
+ defaultItem: { email: '', phone: '' },
160
+ keyField: 'id', // optional: stable trackBy key instead of the array index
161
+ minItems: 1,
162
+ maxItems: 5,
163
+ },
164
+ ];
165
+ ```
166
+
167
+ ```html
168
+ <dfk-multi-field-input [fieldDescriptions]="fields"></dfk-multi-field-input>
169
+ ```
170
+
171
+ ## Scoped registries
172
+
173
+ `fieldRegistry` is a process-wide singleton. To give a component or route its own renderers, provide the `FIELD_REGISTRY` token with an isolated `FieldRegistry`. Components without an override keep using the global singleton.
174
+
175
+ ```ts
176
+ import { FieldRegistry, FIELD_REGISTRY } from '@dynamic-field-kit/angular';
177
+
178
+ const registry = new FieldRegistry();
179
+ registry.register('text', TextFieldComponent as any);
180
+
181
+ @Component({
182
+ selector: 'app-scoped-form',
183
+ standalone: true,
184
+ imports: [MultiFieldInput],
185
+ providers: [{ provide: FIELD_REGISTRY, useValue: registry }],
186
+ template: `<dfk-multi-field-input [fieldDescriptions]="fields" />`,
187
+ })
188
+ export class ScopedFormComponent {}
189
+ ```
190
+
191
+ ## Passing renderer-specific props
192
+
193
+ The generic adapter forwards only the shared `FieldRendererProps`. For inputs specific to one renderer (e.g. `acceptFile`, `maxLength`), pass them per field via `FieldDescription.props`; they are set on the renderer instance verbatim.
194
+
195
+ ```ts
196
+ { name: 'avatar', type: 'file', props: { acceptFile: 'image/*' } }
197
+ ```
198
+
91
199
  ## Legacy setup (Angular 14 and earlier with NgModule)
92
200
 
93
201
  ```ts
@@ -118,7 +226,8 @@ declare module '@dynamic-field-kit/core' {
118
226
 
119
227
  - Register Angular component classes in `fieldRegistry`.
120
228
  - Do not register React or Vue renderers in the Angular adapter.
121
- - `MultiFieldInput` supports `column`, `row`, and `grid` layouts.
229
+ - `MultiFieldInput` supports `column`, `row`, `grid`, and `responsive` layouts, and derives fields using `computeValue`.
230
+ - Fields with `fields` render as repeatable groups instead of going through `fieldRegistry`.
122
231
  - The shared schema and field types still come from `@dynamic-field-kit/core`.
123
232
 
124
233
  ## License
@@ -1,22 +1,34 @@
1
- import { ChangeDetectorRef, EventEmitter } from '@angular/core';
1
+ import { ChangeDetectorRef, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
2
2
  import * as i0 from "@angular/core";
3
- export declare abstract class BaseInputComponent {
3
+ export interface FieldInputProps {
4
+ value?: unknown;
5
+ label?: string;
6
+ placeholder?: string;
7
+ required?: boolean;
8
+ disabled?: boolean;
9
+ readOnly?: boolean;
10
+ error?: string | string[];
11
+ options?: unknown[];
12
+ className?: string;
13
+ description?: string;
14
+ }
15
+ export declare abstract class BaseInputComponent implements OnChanges {
4
16
  protected cdr: ChangeDetectorRef;
5
- value?: any;
17
+ value?: unknown;
6
18
  label?: string;
7
19
  placeholder?: string;
8
20
  required?: boolean;
9
21
  disabled?: boolean;
10
- options?: any[];
22
+ readOnly?: boolean;
23
+ error?: string | string[];
24
+ options?: unknown[];
11
25
  className?: string;
12
26
  description?: string;
13
- errorMessage?: string;
14
- acceptFile?: string;
15
- maxLength?: number;
16
- minNumber?: number;
17
- maxNumber?: number;
18
- valueChange: EventEmitter<any>;
27
+ valueChange: EventEmitter<unknown>;
19
28
  constructor(cdr: ChangeDetectorRef);
29
+ ngOnChanges(_changes: SimpleChanges): void;
30
+ protected detectChanges(): void;
31
+ protected hasChanges(changes: SimpleChanges, prop: string): boolean;
20
32
  static ɵfac: i0.ɵɵFactoryDeclaration<BaseInputComponent, never>;
21
- static ɵcmp: i0.ɵɵComponentDeclaration<BaseInputComponent, "ng-component", never, { "value": { "alias": "value"; "required": false; }; "label": { "alias": "label"; "required": false; }; "placeholder": { "alias": "placeholder"; "required": false; }; "required": { "alias": "required"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "options": { "alias": "options"; "required": false; }; "className": { "alias": "className"; "required": false; }; "description": { "alias": "description"; "required": false; }; "errorMessage": { "alias": "errorMessage"; "required": false; }; "acceptFile": { "alias": "acceptFile"; "required": false; }; "maxLength": { "alias": "maxLength"; "required": false; }; "minNumber": { "alias": "minNumber"; "required": false; }; "maxNumber": { "alias": "maxNumber"; "required": false; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
33
+ static ɵcmp: i0.ɵɵComponentDeclaration<BaseInputComponent, "ng-component", never, { "value": { "alias": "value"; "required": false; }; "label": { "alias": "label"; "required": false; }; "placeholder": { "alias": "placeholder"; "required": false; }; "required": { "alias": "required"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "readOnly": { "alias": "readOnly"; "required": false; }; "error": { "alias": "error"; "required": false; }; "options": { "alias": "options"; "required": false; }; "className": { "alias": "className"; "required": false; }; "description": { "alias": "description"; "required": false; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
22
34
  }
@@ -1,24 +1,35 @@
1
- import { EventEmitter, ViewContainerRef, OnChanges, AfterViewInit, OnDestroy, SimpleChanges } from '@angular/core';
2
- import { FieldTypeKey } from '@dynamic-field-kit/core';
1
+ import { AfterViewInit, EventEmitter, OnChanges, OnDestroy, SimpleChanges, ViewContainerRef } from '@angular/core';
2
+ import { FieldTypeKey, Properties } from '@dynamic-field-kit/core';
3
3
  import { BaseInputComponent } from './BaseInput';
4
4
  import * as i0 from "@angular/core";
5
5
  export declare class DynamicInput extends BaseInputComponent implements OnChanges, AfterViewInit, OnDestroy {
6
6
  type: FieldTypeKey;
7
- valueChange: EventEmitter<any>;
8
- onChange: EventEmitter<any>;
7
+ extraProps?: Properties;
8
+ valueChange: EventEmitter<unknown>;
9
+ onChange: EventEmitter<unknown>;
10
+ private registry;
9
11
  host: ViewContainerRef;
10
12
  private compRef?;
11
13
  private inputInstance?;
12
14
  private subscriptions;
15
+ private supplied;
13
16
  ngOnChanges(changes: SimpleChanges): void;
14
17
  ngAfterViewInit(): void;
15
18
  ngOnDestroy(): void;
19
+ private syncPropsToInstance;
20
+ private cleanup;
16
21
  private cleanupSubscriptions;
22
+ private cleanupRenderedComponent;
17
23
  private render;
18
- private applyKnownProps;
19
- private bindOutput;
24
+ private isComponentType;
25
+ private renderComponent;
26
+ private renderFallback;
27
+ private getFallbackProps;
28
+ private applyProps;
29
+ private applyExtraProps;
30
+ private bindOutputs;
20
31
  private emitValue;
21
- private cleanupRenderedComponent;
32
+ private renderError;
22
33
  static ɵfac: i0.ɵɵFactoryDeclaration<DynamicInput, never>;
23
- static ɵcmp: i0.ɵɵComponentDeclaration<DynamicInput, "dfk-dynamic-input", never, { "type": { "alias": "type"; "required": false; }; }, { "valueChange": "valueChange"; "onChange": "onChange"; }, never, never, true, never>;
34
+ static ɵcmp: i0.ɵɵComponentDeclaration<DynamicInput, "dfk-dynamic-input", never, { "type": { "alias": "type"; "required": false; }; "extraProps": { "alias": "extraProps"; "required": false; }; }, { "valueChange": "valueChange"; "onChange": "onChange"; }, never, never, true, never>;
24
35
  }
@@ -1,14 +1,22 @@
1
- import { EventEmitter } from '@angular/core';
2
- import { FieldDescription, Properties } from '@dynamic-field-kit/core';
1
+ import { ChangeDetectorRef, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
2
+ import { FieldDescription } from '@dynamic-field-kit/core';
3
3
  import * as i0 from "@angular/core";
4
- export declare class FieldInput {
4
+ export declare class FieldInput implements OnChanges {
5
+ private cdr;
5
6
  fieldDescription?: FieldDescription;
6
- renderInfos?: Properties;
7
+ value?: unknown;
8
+ options?: Record<string, unknown>[];
9
+ disabled?: boolean;
10
+ readOnly?: boolean;
11
+ error?: string | string[];
7
12
  onValueChangeField: EventEmitter<{
8
- value: any;
13
+ value: unknown;
9
14
  key: string;
10
15
  }>;
11
- getFieldValue(): unknown;
16
+ shouldRender: boolean;
17
+ get resolvedOptions(): Record<string, unknown>[] | undefined;
18
+ constructor(cdr: ChangeDetectorRef);
19
+ ngOnChanges(_changes: SimpleChanges): void;
12
20
  static ɵfac: i0.ɵɵFactoryDeclaration<FieldInput, never>;
13
- static ɵcmp: i0.ɵɵComponentDeclaration<FieldInput, "dfk-field-input", never, { "fieldDescription": { "alias": "fieldDescription"; "required": false; }; "renderInfos": { "alias": "renderInfos"; "required": false; }; }, { "onValueChangeField": "onValueChangeField"; }, never, never, true, never>;
21
+ static ɵcmp: i0.ɵɵComponentDeclaration<FieldInput, "dfk-field-input", never, { "fieldDescription": { "alias": "fieldDescription"; "required": false; }; "value": { "alias": "value"; "required": false; }; "options": { "alias": "options"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "readOnly": { "alias": "readOnly"; "required": false; }; "error": { "alias": "error"; "required": false; }; }, { "onValueChangeField": "onValueChangeField"; }, never, never, true, never>;
14
22
  }
@@ -1,23 +1,49 @@
1
- import { EventEmitter, OnChanges, OnInit, SimpleChanges } from '@angular/core';
1
+ import { ChangeDetectorRef, EventEmitter, OnChanges, OnInit, SimpleChanges } from '@angular/core';
2
2
  import { FieldDescription, Properties } from '@dynamic-field-kit/core';
3
- import { LayoutConfig } from '../types/layout';
3
+ import type { ValidationResult } from '@dynamic-field-kit/core';
4
+ import { BaseLayoutConfig, LayoutConfig } from '../types/layout';
4
5
  import * as i0 from "@angular/core";
5
6
  export declare class MultiFieldInput implements OnInit, OnChanges {
7
+ private cdr;
6
8
  fieldDescriptions: FieldDescription[];
7
9
  properties?: Properties;
8
10
  onChange: EventEmitter<Properties>;
11
+ validityChange: EventEmitter<ValidationResult>;
9
12
  layout: LayoutConfig;
13
+ rootData?: Properties;
10
14
  data: Properties;
11
15
  visibleFields: FieldDescription[];
16
+ private isMobile;
17
+ constructor(cdr: ChangeDetectorRef);
18
+ onWindowResize(): void;
19
+ get resolvedLayout(): BaseLayoutConfig;
20
+ get resolvedLayoutType(): string;
21
+ get gap(): number;
22
+ get columns(): number;
12
23
  trackByFn(index: number, field: FieldDescription): string | number;
24
+ trackByIndex(index: number): number;
25
+ private groupTrackByCache;
26
+ groupTrackBy(field: FieldDescription): (index: number, item: Properties) => unknown;
13
27
  ngOnInit(): void;
14
28
  ngOnChanges(_changes: SimpleChanges): void;
29
+ private updateIsMobile;
15
30
  private init;
16
31
  private updateVisibleFields;
17
32
  onFieldChange(event: {
18
- value: any;
33
+ value: unknown;
19
34
  key: string;
20
35
  }): void;
36
+ getItems(field: FieldDescription): Properties[];
37
+ getResolvedOptions(field: FieldDescription): Properties[] | undefined;
38
+ getDisabled(field: FieldDescription): boolean;
39
+ getReadOnly(field: FieldDescription): boolean;
40
+ getError(field: FieldDescription): string[] | undefined;
41
+ canAddItem(field: FieldDescription): boolean;
42
+ canRemoveItem(field: FieldDescription): boolean;
43
+ onGroupItemAdd(field: FieldDescription): void;
44
+ onGroupItemRemove(field: FieldDescription, index: number): void;
45
+ onGroupItemChange(field: FieldDescription, index: number, next: Properties): void;
46
+ private commitData;
21
47
  static ɵfac: i0.ɵɵFactoryDeclaration<MultiFieldInput, never>;
22
- static ɵcmp: i0.ɵɵComponentDeclaration<MultiFieldInput, "dfk-multi-field-input", never, { "fieldDescriptions": { "alias": "fieldDescriptions"; "required": false; }; "properties": { "alias": "properties"; "required": false; }; "layout": { "alias": "layout"; "required": false; }; }, { "onChange": "onChange"; }, never, never, true, never>;
48
+ static ɵcmp: i0.ɵɵComponentDeclaration<MultiFieldInput, "dfk-multi-field-input", never, { "fieldDescriptions": { "alias": "fieldDescriptions"; "required": false; }; "properties": { "alias": "properties"; "required": false; }; "layout": { "alias": "layout"; "required": false; }; "rootData": { "alias": "rootData"; "required": false; }; }, { "onChange": "onChange"; "validityChange": "validityChange"; }, never, never, true, never>;
23
49
  }
@@ -7,21 +7,26 @@ export class BaseInputComponent {
7
7
  placeholder;
8
8
  required;
9
9
  disabled;
10
+ readOnly;
11
+ error;
10
12
  options;
11
13
  className;
12
14
  description;
13
- errorMessage;
14
- acceptFile;
15
- maxLength;
16
- minNumber;
17
- maxNumber;
18
- // Add more from mplis as needed
19
15
  valueChange = new EventEmitter();
20
16
  constructor(cdr) {
21
17
  this.cdr = cdr;
22
18
  }
19
+ ngOnChanges(_changes) {
20
+ this.detectChanges();
21
+ }
22
+ detectChanges() {
23
+ this.cdr.markForCheck();
24
+ }
25
+ hasChanges(changes, prop) {
26
+ return changes[prop] !== undefined;
27
+ }
23
28
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: BaseInputComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
24
- 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" }, ngImport: i0, template: '', isInline: true });
29
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: BaseInputComponent, isStandalone: true, selector: "ng-component", inputs: { value: "value", label: "label", placeholder: "placeholder", required: "required", disabled: "disabled", readOnly: "readOnly", error: "error", options: "options", className: "className", description: "description" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true });
25
30
  }
26
31
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: BaseInputComponent, decorators: [{
27
32
  type: Component,
@@ -38,23 +43,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
38
43
  type: Input
39
44
  }], disabled: [{
40
45
  type: Input
46
+ }], readOnly: [{
47
+ type: Input
48
+ }], error: [{
49
+ type: Input
41
50
  }], options: [{
42
51
  type: Input
43
52
  }], className: [{
44
53
  type: Input
45
54
  }], description: [{
46
55
  type: Input
47
- }], errorMessage: [{
48
- type: Input
49
- }], acceptFile: [{
50
- type: Input
51
- }], maxLength: [{
52
- type: Input
53
- }], minNumber: [{
54
- type: Input
55
- }], maxNumber: [{
56
- type: Input
57
56
  }], valueChange: [{
58
57
  type: Output
59
58
  }] } });
60
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQmFzZUlucHV0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvQmFzZUlucHV0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFFTCxTQUFTLEVBQ1QsWUFBWSxFQUNaLEtBQUssRUFDTCxNQUFNLEdBQ1AsTUFBTSxlQUFlLENBQUM7O0FBS3ZCLE1BQU0sT0FBZ0Isa0JBQWtCO0lBa0JoQjtJQWpCYixLQUFLLENBQU87SUFDWixLQUFLLENBQVU7SUFDZixXQUFXLENBQVU7SUFDckIsUUFBUSxDQUFXO0lBQ25CLFFBQVEsQ0FBVztJQUNuQixPQUFPLENBQVM7SUFDaEIsU0FBUyxDQUFVO0lBQ25CLFdBQVcsQ0FBVTtJQUNyQixZQUFZLENBQVU7SUFDdEIsVUFBVSxDQUFVO0lBQ3BCLFNBQVMsQ0FBVTtJQUNuQixTQUFTLENBQVU7SUFDbkIsU0FBUyxDQUFVO0lBQzVCLGdDQUFnQztJQUV0QixXQUFXLEdBQUcsSUFBSSxZQUFZLEVBQU8sQ0FBQztJQUVoRCxZQUFzQixHQUFzQjtRQUF0QixRQUFHLEdBQUgsR0FBRyxDQUFtQjtJQUFHLENBQUM7d0dBbEI1QixrQkFBa0I7NEZBQWxCLGtCQUFrQiw2YUFGNUIsRUFBRTs7NEZBRVEsa0JBQWtCO2tCQUh2QyxTQUFTO21CQUFDO29CQUNULFFBQVEsRUFBRSxFQUFFO2lCQUNiO3NGQUVVLEtBQUs7c0JBQWIsS0FBSztnQkFDRyxLQUFLO3NCQUFiLEtBQUs7Z0JBQ0csV0FBVztzQkFBbkIsS0FBSztnQkFDRyxRQUFRO3NCQUFoQixLQUFLO2dCQUNHLFFBQVE7c0JBQWhCLEtBQUs7Z0JBQ0csT0FBTztzQkFBZixLQUFLO2dCQUNHLFNBQVM7c0JBQWpCLEtBQUs7Z0JBQ0csV0FBVztzQkFBbkIsS0FBSztnQkFDRyxZQUFZO3NCQUFwQixLQUFLO2dCQUNHLFVBQVU7c0JBQWxCLEtBQUs7Z0JBQ0csU0FBUztzQkFBakIsS0FBSztnQkFDRyxTQUFTO3NCQUFqQixLQUFLO2dCQUNHLFNBQVM7c0JBQWpCLEtBQUs7Z0JBR0ksV0FBVztzQkFBcEIsTUFBTSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIENoYW5nZURldGVjdG9yUmVmLFxuICBDb21wb25lbnQsXG4gIEV2ZW50RW1pdHRlcixcbiAgSW5wdXQsXG4gIE91dHB1dCxcbn0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5cbkBDb21wb25lbnQoe1xuICB0ZW1wbGF0ZTogJycsXG59KVxuZXhwb3J0IGFic3RyYWN0IGNsYXNzIEJhc2VJbnB1dENvbXBvbmVudCB7XG4gIEBJbnB1dCgpIHZhbHVlPzogYW55O1xuICBASW5wdXQoKSBsYWJlbD86IHN0cmluZztcbiAgQElucHV0KCkgcGxhY2Vob2xkZXI/OiBzdHJpbmc7XG4gIEBJbnB1dCgpIHJlcXVpcmVkPzogYm9vbGVhbjtcbiAgQElucHV0KCkgZGlzYWJsZWQ/OiBib29sZWFuO1xuICBASW5wdXQoKSBvcHRpb25zPzogYW55W107XG4gIEBJbnB1dCgpIGNsYXNzTmFtZT86IHN0cmluZztcbiAgQElucHV0KCkgZGVzY3JpcHRpb24/OiBzdHJpbmc7XG4gIEBJbnB1dCgpIGVycm9yTWVzc2FnZT86IHN0cmluZztcbiAgQElucHV0KCkgYWNjZXB0RmlsZT86IHN0cmluZztcbiAgQElucHV0KCkgbWF4TGVuZ3RoPzogbnVtYmVyO1xuICBASW5wdXQoKSBtaW5OdW1iZXI/OiBudW1iZXI7XG4gIEBJbnB1dCgpIG1heE51bWJlcj86IG51bWJlcjtcbiAgLy8gQWRkIG1vcmUgZnJvbSBtcGxpcyBhcyBuZWVkZWRcblxuICBAT3V0cHV0KCkgdmFsdWVDaGFuZ2UgPSBuZXcgRXZlbnRFbWl0dGVyPGFueT4oKTtcblxuICBjb25zdHJ1Y3Rvcihwcm90ZWN0ZWQgY2RyOiBDaGFuZ2VEZXRlY3RvclJlZikge31cbn1cbiJdfQ==
59
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQmFzZUlucHV0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvQmFzZUlucHV0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFFTCxTQUFTLEVBQ1QsWUFBWSxFQUNaLEtBQUssRUFFTCxNQUFNLEdBRVAsTUFBTSxlQUFlLENBQUM7O0FBcUJ2QixNQUFNLE9BQWdCLGtCQUFrQjtJQWNoQjtJQWJiLEtBQUssQ0FBVztJQUNoQixLQUFLLENBQVU7SUFDZixXQUFXLENBQVU7SUFDckIsUUFBUSxDQUFXO0lBQ25CLFFBQVEsQ0FBVztJQUNuQixRQUFRLENBQVc7SUFDbkIsS0FBSyxDQUFxQjtJQUMxQixPQUFPLENBQWE7SUFDcEIsU0FBUyxDQUFVO0lBQ25CLFdBQVcsQ0FBVTtJQUVwQixXQUFXLEdBQUcsSUFBSSxZQUFZLEVBQVcsQ0FBQztJQUVwRCxZQUFzQixHQUFzQjtRQUF0QixRQUFHLEdBQUgsR0FBRyxDQUFtQjtJQUFHLENBQUM7SUFFaEQsV0FBVyxDQUFDLFFBQXVCO1FBQ2pDLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztJQUN2QixDQUFDO0lBRVMsYUFBYTtRQUNyQixJQUFJLENBQUMsR0FBRyxDQUFDLFlBQVksRUFBRSxDQUFDO0lBQzFCLENBQUM7SUFFUyxVQUFVLENBQUMsT0FBc0IsRUFBRSxJQUFZO1FBQ3ZELE9BQU8sT0FBTyxDQUFDLElBQUksQ0FBQyxLQUFLLFNBQVMsQ0FBQztJQUNyQyxDQUFDO3dHQTFCbUIsa0JBQWtCOzRGQUFsQixrQkFBa0Isd1dBRjVCLEVBQUU7OzRGQUVRLGtCQUFrQjtrQkFIdkMsU0FBUzttQkFBQztvQkFDVCxRQUFRLEVBQUUsRUFBRTtpQkFDYjtzRkFFVSxLQUFLO3NCQUFiLEtBQUs7Z0JBQ0csS0FBSztzQkFBYixLQUFLO2dCQUNHLFdBQVc7c0JBQW5CLEtBQUs7Z0JBQ0csUUFBUTtzQkFBaEIsS0FBSztnQkFDRyxRQUFRO3NCQUFoQixLQUFLO2dCQUNHLFFBQVE7c0JBQWhCLEtBQUs7Z0JBQ0csS0FBSztzQkFBYixLQUFLO2dCQUNHLE9BQU87c0JBQWYsS0FBSztnQkFDRyxTQUFTO3NCQUFqQixLQUFLO2dCQUNHLFdBQVc7c0JBQW5CLEtBQUs7Z0JBRUksV0FBVztzQkFBcEIsTUFBTSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIENoYW5nZURldGVjdG9yUmVmLFxuICBDb21wb25lbnQsXG4gIEV2ZW50RW1pdHRlcixcbiAgSW5wdXQsXG4gIE9uQ2hhbmdlcyxcbiAgT3V0cHV0LFxuICBTaW1wbGVDaGFuZ2VzLFxufSBmcm9tICdAYW5ndWxhci9jb3JlJztcblxuLy8gTWlycm9ycyB0aGUgZnJhbWV3b3JrLWFnbm9zdGljIEZpZWxkUmVuZGVyZXJQcm9wcy4gRG9tYWluLXNwZWNpZmljIGlucHV0c1xuLy8gKGFjY2VwdEZpbGUsIG1heExlbmd0aCwgLi4uKSBpbnRlbnRpb25hbGx5IGxpdmUgb24gdGhlIGluZGl2aWR1YWwgcmVuZGVyZXIsXG4vLyBub3QgaGVyZSAtIHBhc3MgdGhlbSBwZXIgZmllbGQgdmlhIEZpZWxkRGVzY3JpcHRpb24ucHJvcHMgaW5zdGVhZC5cbmV4cG9ydCBpbnRlcmZhY2UgRmllbGRJbnB1dFByb3BzIHtcbiAgdmFsdWU/OiB1bmtub3duO1xuICBsYWJlbD86IHN0cmluZztcbiAgcGxhY2Vob2xkZXI/OiBzdHJpbmc7XG4gIHJlcXVpcmVkPzogYm9vbGVhbjtcbiAgZGlzYWJsZWQ/OiBib29sZWFuO1xuICByZWFkT25seT86IGJvb2xlYW47XG4gIGVycm9yPzogc3RyaW5nIHwgc3RyaW5nW107XG4gIG9wdGlvbnM/OiB1bmtub3duW107XG4gIGNsYXNzTmFtZT86IHN0cmluZztcbiAgZGVzY3JpcHRpb24/OiBzdHJpbmc7XG59XG5cbkBDb21wb25lbnQoe1xuICB0ZW1wbGF0ZTogJycsXG59KVxuZXhwb3J0IGFic3RyYWN0IGNsYXNzIEJhc2VJbnB1dENvbXBvbmVudCBpbXBsZW1lbnRzIE9uQ2hhbmdlcyB7XG4gIEBJbnB1dCgpIHZhbHVlPzogdW5rbm93bjtcbiAgQElucHV0KCkgbGFiZWw/OiBzdHJpbmc7XG4gIEBJbnB1dCgpIHBsYWNlaG9sZGVyPzogc3RyaW5nO1xuICBASW5wdXQoKSByZXF1aXJlZD86IGJvb2xlYW47XG4gIEBJbnB1dCgpIGRpc2FibGVkPzogYm9vbGVhbjtcbiAgQElucHV0KCkgcmVhZE9ubHk/OiBib29sZWFuO1xuICBASW5wdXQoKSBlcnJvcj86IHN0cmluZyB8IHN0cmluZ1tdO1xuICBASW5wdXQoKSBvcHRpb25zPzogdW5rbm93bltdO1xuICBASW5wdXQoKSBjbGFzc05hbWU/OiBzdHJpbmc7XG4gIEBJbnB1dCgpIGRlc2NyaXB0aW9uPzogc3RyaW5nO1xuXG4gIEBPdXRwdXQoKSB2YWx1ZUNoYW5nZSA9IG5ldyBFdmVudEVtaXR0ZXI8dW5rbm93bj4oKTtcblxuICBjb25zdHJ1Y3Rvcihwcm90ZWN0ZWQgY2RyOiBDaGFuZ2VEZXRlY3RvclJlZikge31cblxuICBuZ09uQ2hhbmdlcyhfY2hhbmdlczogU2ltcGxlQ2hhbmdlcyk6IHZvaWQge1xuICAgIHRoaXMuZGV0ZWN0Q2hhbmdlcygpO1xuICB9XG5cbiAgcHJvdGVjdGVkIGRldGVjdENoYW5nZXMoKTogdm9pZCB7XG4gICAgdGhpcy5jZHIubWFya0ZvckNoZWNrKCk7XG4gIH1cblxuICBwcm90ZWN0ZWQgaGFzQ2hhbmdlcyhjaGFuZ2VzOiBTaW1wbGVDaGFuZ2VzLCBwcm9wOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgICByZXR1cm4gY2hhbmdlc1twcm9wXSAhPT0gdW5kZWZpbmVkO1xuICB9XG59XG4iXX0=