@dynamic-field-kit/angular 1.4.0 → 1.5.1
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 +190 -0
- package/LICENSE +21 -0
- package/README.md +144 -5
- package/dist/components/DynamicFormDevTools.d.ts +16 -0
- package/dist/components/DynamicInput.d.ts +2 -1
- package/dist/components/FieldInput.d.ts +7 -1
- package/dist/components/MultiFieldInput.d.ts +13 -2
- package/dist/fesm2022/dynamic-field-kit-angular.mjs +578 -34
- package/dist/index.d.ts +1 -1
- package/dist/layout/index.d.ts +2 -2
- package/dist/lib/dynamic-field-kit.module.d.ts +4 -4
- package/dist/lib/dynamic-form.store.d.ts +23 -0
- package/dist/public-api.d.ts +10 -8
- package/package.json +58 -24
- package/dist/README.md +0 -235
- package/dist/esm2022/components/BaseInput.mjs +0 -59
- package/dist/esm2022/components/DynamicInput.mjs +0 -244
- package/dist/esm2022/components/FieldInput.mjs +0 -99
- package/dist/esm2022/components/MultiFieldInput.mjs +0 -338
- package/dist/esm2022/dynamic-field-kit-angular.mjs +0 -5
- package/dist/esm2022/fieldRegistryToken.mjs +0 -12
- package/dist/esm2022/layout/defaultLayouts.mjs +0 -114
- package/dist/esm2022/layout/index.mjs +0 -3
- package/dist/esm2022/layout/layoutRegistry.mjs +0 -14
- package/dist/esm2022/lib/dynamic-field-kit.module.mjs +0 -52
- package/dist/esm2022/public-api.mjs +0 -18
- package/dist/esm2022/types/layout.mjs +0 -2
- package/dist/fesm2022/dynamic-field-kit-angular.mjs.map +0 -1
- package/ng-package.json +0 -7
- package/src/components/BaseInput.ts +0 -57
- package/src/components/DynamicInput.ts +0 -280
- package/src/components/FieldInput.ts +0 -74
- package/src/components/MultiFieldInput.ts +0 -331
- package/src/fieldRegistryToken.ts +0 -15
- package/src/layout/defaultLayouts.ts +0 -70
- package/src/layout/index.ts +0 -2
- package/src/layout/layoutRegistry.ts +0 -25
- package/src/lib/dynamic-field-kit.module.ts +0 -29
- package/src/public-api.ts +0 -40
- package/src/types/layout.ts +0 -14
- package/test/DynamicInput.spec.ts +0 -230
- package/test/FieldInput.spec.ts +0 -146
- package/test/MultiFieldInput.spec.ts +0 -256
- package/test/helpers/renderers.ts +0 -89
- package/test/layout.spec.ts +0 -119
- package/test/publicApi.spec.ts +0 -64
- package/test/setup.ts +0 -12
- package/test/smoke.spec.ts +0 -27
- package/tsconfig.json +0 -13
- package/tsconfig.spec.json +0 -9
- package/vitest.config.ts +0 -30
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
import { NgClass, NgFor, NgIf } from '@angular/common';
|
|
2
|
-
import {
|
|
3
|
-
ChangeDetectionStrategy,
|
|
4
|
-
ChangeDetectorRef,
|
|
5
|
-
Component,
|
|
6
|
-
EventEmitter,
|
|
7
|
-
HostListener,
|
|
8
|
-
Input,
|
|
9
|
-
OnChanges,
|
|
10
|
-
OnInit,
|
|
11
|
-
Output,
|
|
12
|
-
SimpleChanges,
|
|
13
|
-
} from '@angular/core';
|
|
14
|
-
import {
|
|
15
|
-
applyComputedValues,
|
|
16
|
-
canAddGroupItem,
|
|
17
|
-
canRemoveGroupItem,
|
|
18
|
-
createGroupItem,
|
|
19
|
-
resolveDisabled,
|
|
20
|
-
resolveOptions,
|
|
21
|
-
resolveReadOnly,
|
|
22
|
-
validateField,
|
|
23
|
-
validateFields,
|
|
24
|
-
FieldDescription,
|
|
25
|
-
Properties,
|
|
26
|
-
} from '@dynamic-field-kit/core';
|
|
27
|
-
import type { ValidationResult } from '@dynamic-field-kit/core';
|
|
28
|
-
import { BaseLayoutConfig, LayoutConfig } from '../types/layout';
|
|
29
|
-
import { FieldInput } from './FieldInput';
|
|
30
|
-
|
|
31
|
-
const DEFAULT_BREAKPOINT = 768;
|
|
32
|
-
|
|
33
|
-
@Component({
|
|
34
|
-
selector: 'dfk-multi-field-input',
|
|
35
|
-
standalone: true,
|
|
36
|
-
// Repeatable field groups render a nested <dfk-multi-field-input> per item
|
|
37
|
-
// (see the #groupTpl below), so this component imports itself. That's the
|
|
38
|
-
// supported way to give a standalone component recursive template usage
|
|
39
|
-
// without a cross-file circular import between FieldInput and a separate
|
|
40
|
-
// group component (whose `imports` arrays are evaluated eagerly at module
|
|
41
|
-
// load time and would deadlock on an actual circular module reference).
|
|
42
|
-
imports: [NgClass, NgFor, NgIf, FieldInput, MultiFieldInput],
|
|
43
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
44
|
-
template: `
|
|
45
|
-
<div
|
|
46
|
-
class="p-4 border rounded-lg bg-gray-50"
|
|
47
|
-
[ngClass]="{
|
|
48
|
-
'flex flex-col': resolvedLayoutType === 'column',
|
|
49
|
-
'flex flex-row': resolvedLayoutType === 'row',
|
|
50
|
-
grid: resolvedLayoutType === 'grid'
|
|
51
|
-
}"
|
|
52
|
-
[style.gap.px]="gap"
|
|
53
|
-
[style.gridTemplateColumns]="
|
|
54
|
-
resolvedLayoutType === 'grid' ? 'repeat(' + columns + ', 1fr)' : null
|
|
55
|
-
"
|
|
56
|
-
>
|
|
57
|
-
<ng-container *ngFor="let field of visibleFields; trackBy: trackByFn">
|
|
58
|
-
<dfk-field-input
|
|
59
|
-
*ngIf="!field.fields"
|
|
60
|
-
[fieldDescription]="field"
|
|
61
|
-
[value]="data[field.name]"
|
|
62
|
-
[options]="getResolvedOptions(field)"
|
|
63
|
-
[disabled]="getDisabled(field)"
|
|
64
|
-
[readOnly]="getReadOnly(field)"
|
|
65
|
-
[error]="getError(field)"
|
|
66
|
-
(onValueChangeField)="onFieldChange($event)"
|
|
67
|
-
></dfk-field-input>
|
|
68
|
-
|
|
69
|
-
<div *ngIf="field.fields" [class]="field.className">
|
|
70
|
-
<div *ngIf="field.label">{{ field.label }}</div>
|
|
71
|
-
<div
|
|
72
|
-
*ngFor="
|
|
73
|
-
let item of getItems(field);
|
|
74
|
-
let i = index;
|
|
75
|
-
trackBy: groupTrackBy(field)
|
|
76
|
-
"
|
|
77
|
-
style="display: flex; align-items: flex-start; gap: 8px;"
|
|
78
|
-
>
|
|
79
|
-
<div style="flex: 1">
|
|
80
|
-
<dfk-multi-field-input
|
|
81
|
-
[fieldDescriptions]="field.fields"
|
|
82
|
-
[properties]="item"
|
|
83
|
-
[rootData]="rootData ?? data"
|
|
84
|
-
(onChange)="onGroupItemChange(field, i, $event)"
|
|
85
|
-
></dfk-multi-field-input>
|
|
86
|
-
</div>
|
|
87
|
-
<button
|
|
88
|
-
type="button"
|
|
89
|
-
[attr.aria-label]="
|
|
90
|
-
(field.removeLabel || 'Remove') +
|
|
91
|
-
' ' +
|
|
92
|
-
(field.label || field.name) +
|
|
93
|
-
' ' +
|
|
94
|
-
(i + 1)
|
|
95
|
-
"
|
|
96
|
-
(click)="onGroupItemRemove(field, i)"
|
|
97
|
-
[disabled]="!canRemoveItem(field)"
|
|
98
|
-
>
|
|
99
|
-
{{ field.removeLabel || 'Remove' }}
|
|
100
|
-
</button>
|
|
101
|
-
</div>
|
|
102
|
-
<button
|
|
103
|
-
type="button"
|
|
104
|
-
[attr.aria-label]="
|
|
105
|
-
(field.addLabel || 'Add') + ' ' + (field.label || field.name)
|
|
106
|
-
"
|
|
107
|
-
(click)="onGroupItemAdd(field)"
|
|
108
|
-
[disabled]="!canAddItem(field)"
|
|
109
|
-
>
|
|
110
|
-
{{ field.addLabel || 'Add' }}
|
|
111
|
-
</button>
|
|
112
|
-
</div>
|
|
113
|
-
</ng-container>
|
|
114
|
-
</div>
|
|
115
|
-
`,
|
|
116
|
-
})
|
|
117
|
-
export class MultiFieldInput implements OnInit, OnChanges {
|
|
118
|
-
@Input() fieldDescriptions: FieldDescription[] = [];
|
|
119
|
-
@Input() properties?: Properties;
|
|
120
|
-
@Output() onChange = new EventEmitter<Properties>();
|
|
121
|
-
@Output() validityChange = new EventEmitter<ValidationResult>();
|
|
122
|
-
@Input() layout: LayoutConfig = 'column';
|
|
123
|
-
// Top-level form data, threaded down through repeatable groups so a nested
|
|
124
|
-
// field's appearCondition/computeValue can read the root form. Omitted at
|
|
125
|
-
// the top level, where the form's own data is the root.
|
|
126
|
-
@Input() rootData?: Properties;
|
|
127
|
-
|
|
128
|
-
data: Properties = {};
|
|
129
|
-
visibleFields: FieldDescription[] = [];
|
|
130
|
-
private isMobile = false;
|
|
131
|
-
|
|
132
|
-
constructor(private cdr: ChangeDetectorRef) {}
|
|
133
|
-
|
|
134
|
-
@HostListener('window:resize')
|
|
135
|
-
onWindowResize(): void {
|
|
136
|
-
const wasMobile = this.isMobile;
|
|
137
|
-
this.updateIsMobile();
|
|
138
|
-
if (wasMobile !== this.isMobile) {
|
|
139
|
-
this.cdr.markForCheck();
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
get resolvedLayout(): BaseLayoutConfig {
|
|
144
|
-
if (typeof this.layout === 'object' && this.layout.type === 'responsive') {
|
|
145
|
-
return this.isMobile ? this.layout.mobile : this.layout.desktop;
|
|
146
|
-
}
|
|
147
|
-
return this.layout as BaseLayoutConfig;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
get resolvedLayoutType(): string {
|
|
151
|
-
const resolved = this.resolvedLayout;
|
|
152
|
-
return typeof resolved === 'string' ? resolved : resolved.type;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
get gap(): number {
|
|
156
|
-
const resolved = this.resolvedLayout;
|
|
157
|
-
if (typeof resolved === 'object' && resolved.gap !== undefined) {
|
|
158
|
-
return resolved.gap;
|
|
159
|
-
}
|
|
160
|
-
return 12;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
get columns(): number {
|
|
164
|
-
const resolved = this.resolvedLayout;
|
|
165
|
-
if (typeof resolved === 'object' && resolved.type === 'grid') {
|
|
166
|
-
return resolved.columns ?? 2;
|
|
167
|
-
}
|
|
168
|
-
return 2;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
trackByFn(index: number, field: FieldDescription): string | number {
|
|
172
|
-
return field.name || index;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
trackByIndex(index: number): number {
|
|
176
|
-
return index;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
private groupTrackByCache = new WeakMap<
|
|
180
|
-
FieldDescription,
|
|
181
|
-
(index: number, item: Properties) => unknown
|
|
182
|
-
>();
|
|
183
|
-
|
|
184
|
-
// Returns a stable trackBy per group field: item[keyField] when configured,
|
|
185
|
-
// else the index. Cached by field reference so the template hands Angular the
|
|
186
|
-
// same function each change-detection pass.
|
|
187
|
-
groupTrackBy(
|
|
188
|
-
field: FieldDescription
|
|
189
|
-
): (index: number, item: Properties) => unknown {
|
|
190
|
-
let fn = this.groupTrackByCache.get(field);
|
|
191
|
-
if (!fn) {
|
|
192
|
-
fn = (index: number, item: Properties) =>
|
|
193
|
-
field.keyField ? item[field.keyField] ?? index : index;
|
|
194
|
-
this.groupTrackByCache.set(field, fn);
|
|
195
|
-
}
|
|
196
|
-
return fn;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
ngOnInit() {
|
|
200
|
-
this.updateIsMobile();
|
|
201
|
-
this.init();
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
ngOnChanges(_changes: SimpleChanges) {
|
|
205
|
-
this.init();
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
private updateIsMobile(): void {
|
|
209
|
-
const breakpoint =
|
|
210
|
-
typeof this.layout === 'object' && this.layout.type === 'responsive'
|
|
211
|
-
? this.layout.breakpoint ?? DEFAULT_BREAKPOINT
|
|
212
|
-
: DEFAULT_BREAKPOINT;
|
|
213
|
-
if (
|
|
214
|
-
typeof window !== 'undefined' &&
|
|
215
|
-
typeof window.matchMedia === 'function'
|
|
216
|
-
) {
|
|
217
|
-
this.isMobile = window.matchMedia(
|
|
218
|
-
`(max-width: ${breakpoint - 1}px)`
|
|
219
|
-
).matches;
|
|
220
|
-
} else {
|
|
221
|
-
this.isMobile =
|
|
222
|
-
typeof window !== 'undefined' && window.innerWidth < breakpoint;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
private init() {
|
|
227
|
-
if (this.properties) {
|
|
228
|
-
this.data = applyComputedValues(
|
|
229
|
-
this.fieldDescriptions,
|
|
230
|
-
{ ...this.properties },
|
|
231
|
-
this.rootData
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
this.updateVisibleFields();
|
|
235
|
-
this.validityChange.emit(
|
|
236
|
-
validateFields(this.fieldDescriptions, this.data, this.rootData)
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
private updateVisibleFields() {
|
|
241
|
-
const root = this.rootData ?? this.data;
|
|
242
|
-
this.visibleFields = this.fieldDescriptions.filter(
|
|
243
|
-
(f) => !f.appearCondition || f.appearCondition(this.data, root)
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
onFieldChange(event: { value: unknown; key: string }): void {
|
|
248
|
-
this.commitData({ ...this.data, [event.key]: event.value });
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
getItems(field: FieldDescription): Properties[] {
|
|
252
|
-
const value = this.data[field.name];
|
|
253
|
-
return Array.isArray(value) ? (value as Properties[]) : [];
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
getResolvedOptions(field: FieldDescription): Properties[] | undefined {
|
|
257
|
-
return resolveOptions(field, this.data, this.rootData);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
getDisabled(field: FieldDescription): boolean {
|
|
261
|
-
return resolveDisabled(field, this.data, this.rootData);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
getReadOnly(field: FieldDescription): boolean {
|
|
265
|
-
return resolveReadOnly(field, this.data, this.rootData);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
getError(field: FieldDescription): string[] | undefined {
|
|
269
|
-
if (this.getDisabled(field)) {
|
|
270
|
-
return undefined;
|
|
271
|
-
}
|
|
272
|
-
const errors = validateField(
|
|
273
|
-
field,
|
|
274
|
-
this.data[field.name],
|
|
275
|
-
this.data,
|
|
276
|
-
this.rootData
|
|
277
|
-
);
|
|
278
|
-
return errors.length > 0 ? errors : undefined;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
canAddItem(field: FieldDescription): boolean {
|
|
282
|
-
return canAddGroupItem(field, this.getItems(field));
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
canRemoveItem(field: FieldDescription): boolean {
|
|
286
|
-
return canRemoveGroupItem(field, this.getItems(field));
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
onGroupItemAdd(field: FieldDescription): void {
|
|
290
|
-
if (!this.canAddItem(field)) {
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
const items = this.getItems(field);
|
|
294
|
-
this.commitData({
|
|
295
|
-
...this.data,
|
|
296
|
-
[field.name]: [...items, createGroupItem(field)],
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
onGroupItemRemove(field: FieldDescription, index: number): void {
|
|
301
|
-
if (!this.canRemoveItem(field)) {
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
const items = this.getItems(field).filter((_, i) => i !== index);
|
|
305
|
-
this.commitData({ ...this.data, [field.name]: items });
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
onGroupItemChange(
|
|
309
|
-
field: FieldDescription,
|
|
310
|
-
index: number,
|
|
311
|
-
next: Properties
|
|
312
|
-
): void {
|
|
313
|
-
const items = this.getItems(field).slice();
|
|
314
|
-
items[index] = next;
|
|
315
|
-
this.commitData({ ...this.data, [field.name]: items });
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
private commitData(nextData: Properties): void {
|
|
319
|
-
this.data = applyComputedValues(
|
|
320
|
-
this.fieldDescriptions,
|
|
321
|
-
nextData,
|
|
322
|
-
this.rootData
|
|
323
|
-
);
|
|
324
|
-
this.updateVisibleFields();
|
|
325
|
-
this.onChange.emit(this.data);
|
|
326
|
-
this.validityChange.emit(
|
|
327
|
-
validateFields(this.fieldDescriptions, this.data, this.rootData)
|
|
328
|
-
);
|
|
329
|
-
this.cdr.markForCheck();
|
|
330
|
-
}
|
|
331
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { InjectionToken } from '@angular/core';
|
|
2
|
-
import { FieldRegistry, fieldRegistry } from '@dynamic-field-kit/core';
|
|
3
|
-
|
|
4
|
-
// Injecting this token yields the process-wide singleton by default, so code
|
|
5
|
-
// that never provides it keeps working. Provide it on a component/route to give
|
|
6
|
-
// that subtree an isolated set of renderers:
|
|
7
|
-
//
|
|
8
|
-
// providers: [{ provide: FIELD_REGISTRY, useValue: myScopedRegistry }]
|
|
9
|
-
export const FIELD_REGISTRY = new InjectionToken<FieldRegistry>(
|
|
10
|
-
'dfk.FieldRegistry',
|
|
11
|
-
{
|
|
12
|
-
providedIn: 'root',
|
|
13
|
-
factory: () => fieldRegistry,
|
|
14
|
-
}
|
|
15
|
-
);
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { CommonModule } from '@angular/common';
|
|
2
|
-
import { Component, Input, TemplateRef } from '@angular/core';
|
|
3
|
-
import { layoutRegistry } from './layoutRegistry';
|
|
4
|
-
|
|
5
|
-
@Component({
|
|
6
|
-
selector: 'dfk-column-layout',
|
|
7
|
-
standalone: true,
|
|
8
|
-
imports: [CommonModule],
|
|
9
|
-
template: `<div
|
|
10
|
-
[style.display]="'flex'"
|
|
11
|
-
[style.flexDirection]="'column'"
|
|
12
|
-
[style.gap]="gap + 'px'"
|
|
13
|
-
>
|
|
14
|
-
<ng-container *ngTemplateOutlet="template"></ng-container>
|
|
15
|
-
</div>`,
|
|
16
|
-
})
|
|
17
|
-
export class ColumnLayout {
|
|
18
|
-
@Input() config?: { gap?: number };
|
|
19
|
-
@Input() template!: TemplateRef<unknown>;
|
|
20
|
-
get gap() {
|
|
21
|
-
return this.config?.gap ?? 12;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
@Component({
|
|
26
|
-
selector: 'dfk-row-layout',
|
|
27
|
-
standalone: true,
|
|
28
|
-
imports: [CommonModule],
|
|
29
|
-
template: `<div
|
|
30
|
-
[style.display]="'flex'"
|
|
31
|
-
[style.flexDirection]="'row'"
|
|
32
|
-
[style.gap]="gap + 'px'"
|
|
33
|
-
>
|
|
34
|
-
<ng-container *ngTemplateOutlet="template"></ng-container>
|
|
35
|
-
</div>`,
|
|
36
|
-
})
|
|
37
|
-
export class RowLayout {
|
|
38
|
-
@Input() config?: { gap?: number };
|
|
39
|
-
@Input() template!: TemplateRef<unknown>;
|
|
40
|
-
get gap() {
|
|
41
|
-
return this.config?.gap ?? 12;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
@Component({
|
|
46
|
-
selector: 'dfk-grid-layout',
|
|
47
|
-
standalone: true,
|
|
48
|
-
imports: [CommonModule],
|
|
49
|
-
template: `<div
|
|
50
|
-
[style.display]="'grid'"
|
|
51
|
-
[style.gridTemplateColumns]="'repeat(' + columns + ', 1fr)'"
|
|
52
|
-
[style.gap]="gap + 'px'"
|
|
53
|
-
>
|
|
54
|
-
<ng-container *ngTemplateOutlet="template"></ng-container>
|
|
55
|
-
</div>`,
|
|
56
|
-
})
|
|
57
|
-
export class GridLayout {
|
|
58
|
-
@Input() config?: { columns?: number; gap?: number };
|
|
59
|
-
@Input() template!: TemplateRef<unknown>;
|
|
60
|
-
get columns() {
|
|
61
|
-
return this.config?.columns ?? 2;
|
|
62
|
-
}
|
|
63
|
-
get gap() {
|
|
64
|
-
return this.config?.gap ?? 12;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
layoutRegistry.register('column', ColumnLayout);
|
|
69
|
-
layoutRegistry.register('row', RowLayout);
|
|
70
|
-
layoutRegistry.register('grid', GridLayout);
|
package/src/layout/index.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { Type } from '@angular/core';
|
|
2
|
-
|
|
3
|
-
export type LayoutRenderer<C = unknown> = (props: {
|
|
4
|
-
children: unknown[];
|
|
5
|
-
config?: C;
|
|
6
|
-
}) => unknown;
|
|
7
|
-
|
|
8
|
-
export type AngularLayoutComponent = Type<unknown>;
|
|
9
|
-
|
|
10
|
-
export class LayoutRegistry {
|
|
11
|
-
private layouts = new Map<string, AngularLayoutComponent>();
|
|
12
|
-
|
|
13
|
-
register(type: string, component: AngularLayoutComponent): void {
|
|
14
|
-
if (this.layouts.has(type)) {
|
|
15
|
-
console.warn(`[dynamic-field-kit] Layout "${type}" already exists`);
|
|
16
|
-
}
|
|
17
|
-
this.layouts.set(type, component);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
get(type: string): AngularLayoutComponent | undefined {
|
|
21
|
-
return this.layouts.get(type);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const layoutRegistry = new LayoutRegistry();
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { CommonModule } from '@angular/common';
|
|
2
|
-
import { NgModule } from '@angular/core';
|
|
3
|
-
|
|
4
|
-
import { DynamicInput } from '../components/DynamicInput';
|
|
5
|
-
import { FieldInput } from '../components/FieldInput';
|
|
6
|
-
import { MultiFieldInput } from '../components/MultiFieldInput';
|
|
7
|
-
|
|
8
|
-
import { ColumnLayout, RowLayout, GridLayout } from '../layout/defaultLayouts';
|
|
9
|
-
|
|
10
|
-
@NgModule({
|
|
11
|
-
imports: [
|
|
12
|
-
CommonModule,
|
|
13
|
-
DynamicInput,
|
|
14
|
-
FieldInput,
|
|
15
|
-
MultiFieldInput,
|
|
16
|
-
ColumnLayout,
|
|
17
|
-
RowLayout,
|
|
18
|
-
GridLayout,
|
|
19
|
-
],
|
|
20
|
-
exports: [
|
|
21
|
-
DynamicInput,
|
|
22
|
-
FieldInput,
|
|
23
|
-
MultiFieldInput,
|
|
24
|
-
ColumnLayout,
|
|
25
|
-
RowLayout,
|
|
26
|
-
GridLayout,
|
|
27
|
-
],
|
|
28
|
-
})
|
|
29
|
-
export class DynamicFieldKitModule {}
|
package/src/public-api.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
// public-api.ts
|
|
2
|
-
|
|
3
|
-
// Components
|
|
4
|
-
export * from './components/BaseInput';
|
|
5
|
-
export * from './components/DynamicInput';
|
|
6
|
-
export * from './components/FieldInput';
|
|
7
|
-
export * from './components/MultiFieldInput';
|
|
8
|
-
|
|
9
|
-
// Layout
|
|
10
|
-
export * from './layout';
|
|
11
|
-
export * from './types/layout';
|
|
12
|
-
|
|
13
|
-
// Module
|
|
14
|
-
export * from './lib/dynamic-field-kit.module';
|
|
15
|
-
|
|
16
|
-
// Re-export types from core (only type, not export * )
|
|
17
|
-
export type {
|
|
18
|
-
FieldDescription,
|
|
19
|
-
FieldRendererProps,
|
|
20
|
-
FieldTypeKey,
|
|
21
|
-
} from '@dynamic-field-kit/core';
|
|
22
|
-
|
|
23
|
-
// Optional: expose registry for advanced use cases, but not required for basic usage
|
|
24
|
-
export { fieldRegistry, FieldRegistry } from '@dynamic-field-kit/core';
|
|
25
|
-
|
|
26
|
-
export {
|
|
27
|
-
validateField,
|
|
28
|
-
validateFieldAsync,
|
|
29
|
-
validateFields,
|
|
30
|
-
validateFieldsAsync,
|
|
31
|
-
resolveDisabled,
|
|
32
|
-
resolveReadOnly,
|
|
33
|
-
resolveOptions,
|
|
34
|
-
validators,
|
|
35
|
-
} from '@dynamic-field-kit/core';
|
|
36
|
-
export type { ValidationResult } from '@dynamic-field-kit/core';
|
|
37
|
-
|
|
38
|
-
// Scoped registry: provide FIELD_REGISTRY on a component/route to give that
|
|
39
|
-
// subtree an isolated set of renderers.
|
|
40
|
-
export { FIELD_REGISTRY } from './fieldRegistryToken';
|
package/src/types/layout.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
// Layout config types live in @dynamic-field-kit/core so the contract stays
|
|
2
|
-
// identical across every framework adapter. Re-exported here (with the
|
|
3
|
-
// historical Angular ...Config aliases) to preserve existing import paths.
|
|
4
|
-
import type { BaseLayout, ResponsiveLayout } from '@dynamic-field-kit/core';
|
|
5
|
-
|
|
6
|
-
export type {
|
|
7
|
-
ColumnLayoutConfig,
|
|
8
|
-
RowLayoutConfig,
|
|
9
|
-
GridLayoutConfig,
|
|
10
|
-
LayoutConfig,
|
|
11
|
-
} from '@dynamic-field-kit/core';
|
|
12
|
-
|
|
13
|
-
export type BaseLayoutConfig = BaseLayout;
|
|
14
|
-
export type ResponsiveLayoutConfig = ResponsiveLayout;
|