@dynamic-field-kit/angular 1.3.3 → 1.5.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 +185 -0
- package/LICENSE +21 -0
- package/README.md +193 -7
- package/dist/components/BaseInput.d.ts +5 -11
- package/dist/components/DynamicFormDevTools.d.ts +16 -0
- package/dist/components/DynamicInput.d.ts +8 -3
- package/dist/components/FieldInput.d.ts +12 -2
- package/dist/components/MultiFieldInput.d.ts +22 -2
- package/dist/fesm2022/dynamic-field-kit-angular.mjs +778 -87
- package/dist/fieldRegistryToken.d.ts +3 -0
- 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 +13 -8
- package/dist/types/layout.d.ts +4 -25
- package/package.json +46 -29
- package/angular.json +0 -27
- package/dist/README.md +0 -188
- package/dist/esm2022/components/BaseInput.mjs +0 -68
- package/dist/esm2022/components/DynamicInput.mjs +0 -191
- package/dist/esm2022/components/FieldInput.mjs +0 -80
- package/dist/esm2022/components/MultiFieldInput.mjs +0 -264
- package/dist/esm2022/dynamic-field-kit-angular.mjs +0 -5
- 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 -14
- package/dist/esm2022/types/layout.mjs +0 -2
- package/dist/fesm2022/dynamic-field-kit-angular.mjs.map +0 -1
- package/karma.conf.js +0 -37
- package/ng-package.json +0 -7
- package/src/components/BaseInput.ts +0 -60
- package/src/components/DynamicInput.ts +0 -224
- package/src/components/FieldInput.ts +0 -67
- package/src/components/MultiFieldInput.ts +0 -238
- 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 -24
- package/src/types/layout.ts +0 -36
- package/test/DynamicInput.spec.ts +0 -30
- package/test/FieldInput.spec.ts +0 -44
- package/test/angular.spec.ts +0 -102
- package/test/integration.spec.ts +0 -57
- package/test/layouts.spec.ts +0 -26
- package/test.ts +0 -12
- package/tsconfig.json +0 -13
- package/tsconfig.spec.json +0 -21
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { NgIf } from '@angular/common';
|
|
2
|
-
import {
|
|
3
|
-
ChangeDetectionStrategy,
|
|
4
|
-
ChangeDetectorRef,
|
|
5
|
-
Component,
|
|
6
|
-
EventEmitter,
|
|
7
|
-
Input,
|
|
8
|
-
OnChanges,
|
|
9
|
-
Output,
|
|
10
|
-
SimpleChanges,
|
|
11
|
-
} from '@angular/core';
|
|
12
|
-
import { FieldDescription } from '@dynamic-field-kit/core';
|
|
13
|
-
import { DynamicInput } from './DynamicInput';
|
|
14
|
-
|
|
15
|
-
@Component({
|
|
16
|
-
selector: 'dfk-field-input',
|
|
17
|
-
standalone: true,
|
|
18
|
-
imports: [NgIf, DynamicInput],
|
|
19
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
20
|
-
template: `
|
|
21
|
-
<dfk-dynamic-input
|
|
22
|
-
*ngIf="shouldRender"
|
|
23
|
-
[type]="fieldDescription!.type"
|
|
24
|
-
[value]="getFieldValue()"
|
|
25
|
-
[label]="fieldDescription!.label"
|
|
26
|
-
[placeholder]="fieldDescription!.placeholder"
|
|
27
|
-
[required]="fieldDescription!.required"
|
|
28
|
-
[description]="$any(fieldDescription!.description)"
|
|
29
|
-
[options]="fieldDescription!.options"
|
|
30
|
-
[className]="fieldDescription!.className"
|
|
31
|
-
(valueChange)="
|
|
32
|
-
onValueChangeField.emit({ value: $event, key: fieldDescription!.name })
|
|
33
|
-
"
|
|
34
|
-
[disabled]="false"
|
|
35
|
-
[errorMessage]="''"
|
|
36
|
-
></dfk-dynamic-input>
|
|
37
|
-
`,
|
|
38
|
-
})
|
|
39
|
-
export class FieldInput implements OnChanges {
|
|
40
|
-
@Input() fieldDescription?: FieldDescription;
|
|
41
|
-
@Input() value?: unknown;
|
|
42
|
-
@Output() onValueChangeField = new EventEmitter<{
|
|
43
|
-
value: unknown;
|
|
44
|
-
key: string;
|
|
45
|
-
}>();
|
|
46
|
-
|
|
47
|
-
shouldRender = false;
|
|
48
|
-
|
|
49
|
-
constructor(private cdr: ChangeDetectorRef) {}
|
|
50
|
-
|
|
51
|
-
ngOnChanges(_changes: SimpleChanges): void {
|
|
52
|
-
this.shouldRender = !!this.fieldDescription;
|
|
53
|
-
this.cdr.markForCheck();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
getFieldValue(): unknown {
|
|
57
|
-
if (!this.fieldDescription) {
|
|
58
|
-
return undefined;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (this.value === undefined && this.fieldDescription.type === 'text') {
|
|
62
|
-
return '';
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return this.value;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
@@ -1,238 +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
|
-
FieldDescription,
|
|
20
|
-
Properties,
|
|
21
|
-
} from '@dynamic-field-kit/core';
|
|
22
|
-
import { BaseLayoutConfig, LayoutConfig } from '../types/layout';
|
|
23
|
-
import { FieldInput } from './FieldInput';
|
|
24
|
-
|
|
25
|
-
const DEFAULT_BREAKPOINT = 768;
|
|
26
|
-
|
|
27
|
-
@Component({
|
|
28
|
-
selector: 'dfk-multi-field-input',
|
|
29
|
-
standalone: true,
|
|
30
|
-
// Repeatable field groups render a nested <dfk-multi-field-input> per item
|
|
31
|
-
// (see the #groupTpl below), so this component imports itself. That's the
|
|
32
|
-
// supported way to give a standalone component recursive template usage
|
|
33
|
-
// without a cross-file circular import between FieldInput and a separate
|
|
34
|
-
// group component (whose `imports` arrays are evaluated eagerly at module
|
|
35
|
-
// load time and would deadlock on an actual circular module reference).
|
|
36
|
-
imports: [NgClass, NgFor, NgIf, FieldInput, MultiFieldInput],
|
|
37
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
38
|
-
template: `
|
|
39
|
-
<div
|
|
40
|
-
class="p-4 border rounded-lg bg-gray-50"
|
|
41
|
-
[ngClass]="{
|
|
42
|
-
'flex flex-col': resolvedLayoutType === 'column',
|
|
43
|
-
'flex flex-row': resolvedLayoutType === 'row',
|
|
44
|
-
grid: resolvedLayoutType === 'grid'
|
|
45
|
-
}"
|
|
46
|
-
[style.gap.px]="gap"
|
|
47
|
-
[style.gridTemplateColumns]="
|
|
48
|
-
resolvedLayoutType === 'grid' ? 'repeat(' + columns + ', 1fr)' : null
|
|
49
|
-
"
|
|
50
|
-
>
|
|
51
|
-
<ng-container *ngFor="let field of visibleFields; trackBy: trackByFn">
|
|
52
|
-
<dfk-field-input
|
|
53
|
-
*ngIf="!field.fields"
|
|
54
|
-
[fieldDescription]="field"
|
|
55
|
-
[value]="data[field.name]"
|
|
56
|
-
(onValueChangeField)="onFieldChange($event)"
|
|
57
|
-
></dfk-field-input>
|
|
58
|
-
|
|
59
|
-
<div *ngIf="field.fields" [class]="field.className">
|
|
60
|
-
<div *ngIf="field.label">{{ field.label }}</div>
|
|
61
|
-
<div
|
|
62
|
-
*ngFor="
|
|
63
|
-
let item of getItems(field);
|
|
64
|
-
let i = index;
|
|
65
|
-
trackBy: trackByIndex
|
|
66
|
-
"
|
|
67
|
-
style="display: flex; align-items: flex-start; gap: 8px;"
|
|
68
|
-
>
|
|
69
|
-
<div style="flex: 1">
|
|
70
|
-
<dfk-multi-field-input
|
|
71
|
-
[fieldDescriptions]="field.fields"
|
|
72
|
-
[properties]="item"
|
|
73
|
-
(onChange)="onGroupItemChange(field, i, $event)"
|
|
74
|
-
></dfk-multi-field-input>
|
|
75
|
-
</div>
|
|
76
|
-
<button
|
|
77
|
-
type="button"
|
|
78
|
-
(click)="onGroupItemRemove(field, i)"
|
|
79
|
-
[disabled]="!canRemoveItem(field)"
|
|
80
|
-
>
|
|
81
|
-
{{ field.removeLabel || 'Remove' }}
|
|
82
|
-
</button>
|
|
83
|
-
</div>
|
|
84
|
-
<button
|
|
85
|
-
type="button"
|
|
86
|
-
(click)="onGroupItemAdd(field)"
|
|
87
|
-
[disabled]="!canAddItem(field)"
|
|
88
|
-
>
|
|
89
|
-
{{ field.addLabel || 'Add' }}
|
|
90
|
-
</button>
|
|
91
|
-
</div>
|
|
92
|
-
</ng-container>
|
|
93
|
-
</div>
|
|
94
|
-
`,
|
|
95
|
-
})
|
|
96
|
-
export class MultiFieldInput implements OnInit, OnChanges {
|
|
97
|
-
@Input() fieldDescriptions: FieldDescription[] = [];
|
|
98
|
-
@Input() properties?: Properties;
|
|
99
|
-
@Output() onChange = new EventEmitter<Properties>();
|
|
100
|
-
@Input() layout: LayoutConfig = 'column';
|
|
101
|
-
|
|
102
|
-
data: Properties = {};
|
|
103
|
-
visibleFields: FieldDescription[] = [];
|
|
104
|
-
private isMobile = false;
|
|
105
|
-
|
|
106
|
-
constructor(private cdr: ChangeDetectorRef) {}
|
|
107
|
-
|
|
108
|
-
@HostListener('window:resize')
|
|
109
|
-
onWindowResize(): void {
|
|
110
|
-
const wasMobile = this.isMobile;
|
|
111
|
-
this.updateIsMobile();
|
|
112
|
-
if (wasMobile !== this.isMobile) {
|
|
113
|
-
this.cdr.markForCheck();
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
get resolvedLayout(): BaseLayoutConfig {
|
|
118
|
-
if (typeof this.layout === 'object' && this.layout.type === 'responsive') {
|
|
119
|
-
return this.isMobile ? this.layout.mobile : this.layout.desktop;
|
|
120
|
-
}
|
|
121
|
-
return this.layout as BaseLayoutConfig;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
get resolvedLayoutType(): string {
|
|
125
|
-
const resolved = this.resolvedLayout;
|
|
126
|
-
return typeof resolved === 'string' ? resolved : resolved.type;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
get gap(): number {
|
|
130
|
-
const resolved = this.resolvedLayout;
|
|
131
|
-
if (typeof resolved === 'object' && resolved.gap !== undefined) {
|
|
132
|
-
return resolved.gap;
|
|
133
|
-
}
|
|
134
|
-
return 12;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
get columns(): number {
|
|
138
|
-
const resolved = this.resolvedLayout;
|
|
139
|
-
if (typeof resolved === 'object' && resolved.type === 'grid') {
|
|
140
|
-
return resolved.columns ?? 2;
|
|
141
|
-
}
|
|
142
|
-
return 2;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
trackByFn(index: number, field: FieldDescription): string | number {
|
|
146
|
-
return field.name || index;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
trackByIndex(index: number): number {
|
|
150
|
-
return index;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
ngOnInit() {
|
|
154
|
-
this.updateIsMobile();
|
|
155
|
-
this.init();
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
ngOnChanges(_changes: SimpleChanges) {
|
|
159
|
-
this.init();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
private updateIsMobile(): void {
|
|
163
|
-
const breakpoint =
|
|
164
|
-
typeof this.layout === 'object' && this.layout.type === 'responsive'
|
|
165
|
-
? this.layout.breakpoint ?? DEFAULT_BREAKPOINT
|
|
166
|
-
: DEFAULT_BREAKPOINT;
|
|
167
|
-
this.isMobile =
|
|
168
|
-
typeof window !== 'undefined' && window.innerWidth < breakpoint;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
private init() {
|
|
172
|
-
if (this.properties) {
|
|
173
|
-
this.data = applyComputedValues(this.fieldDescriptions, {
|
|
174
|
-
...this.properties,
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
this.updateVisibleFields();
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
private updateVisibleFields() {
|
|
181
|
-
this.visibleFields = this.fieldDescriptions.filter(
|
|
182
|
-
(f) => !f.appearCondition || f.appearCondition(this.data)
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
onFieldChange(event: { value: unknown; key: string }): void {
|
|
187
|
-
this.commitData({ ...this.data, [event.key]: event.value });
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
getItems(field: FieldDescription): Properties[] {
|
|
191
|
-
const value = this.data[field.name];
|
|
192
|
-
return Array.isArray(value) ? (value as Properties[]) : [];
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
canAddItem(field: FieldDescription): boolean {
|
|
196
|
-
return canAddGroupItem(field, this.getItems(field));
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
canRemoveItem(field: FieldDescription): boolean {
|
|
200
|
-
return canRemoveGroupItem(field, this.getItems(field));
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
onGroupItemAdd(field: FieldDescription): void {
|
|
204
|
-
if (!this.canAddItem(field)) {
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
const items = this.getItems(field);
|
|
208
|
-
this.commitData({
|
|
209
|
-
...this.data,
|
|
210
|
-
[field.name]: [...items, createGroupItem(field)],
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
onGroupItemRemove(field: FieldDescription, index: number): void {
|
|
215
|
-
if (!this.canRemoveItem(field)) {
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
const items = this.getItems(field).filter((_, i) => i !== index);
|
|
219
|
-
this.commitData({ ...this.data, [field.name]: items });
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
onGroupItemChange(
|
|
223
|
-
field: FieldDescription,
|
|
224
|
-
index: number,
|
|
225
|
-
next: Properties
|
|
226
|
-
): void {
|
|
227
|
-
const items = this.getItems(field).slice();
|
|
228
|
-
items[index] = next;
|
|
229
|
-
this.commitData({ ...this.data, [field.name]: items });
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
private commitData(nextData: Properties): void {
|
|
233
|
-
this.data = applyComputedValues(this.fieldDescriptions, nextData);
|
|
234
|
-
this.updateVisibleFields();
|
|
235
|
-
this.onChange.emit(this.data);
|
|
236
|
-
this.cdr.markForCheck();
|
|
237
|
-
}
|
|
238
|
-
}
|
|
@@ -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,24 +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 } from '@dynamic-field-kit/core';
|
package/src/types/layout.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export interface GridLayoutConfig {
|
|
2
|
-
type: 'grid';
|
|
3
|
-
columns?: number;
|
|
4
|
-
gap?: number;
|
|
5
|
-
width?: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface RowLayoutConfig {
|
|
9
|
-
type: 'row';
|
|
10
|
-
gap?: number;
|
|
11
|
-
width?: string;
|
|
12
|
-
flex?: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface ColumnLayoutConfig {
|
|
16
|
-
type: 'column';
|
|
17
|
-
gap?: number;
|
|
18
|
-
width?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export type BaseLayoutConfig =
|
|
22
|
-
| 'column'
|
|
23
|
-
| 'row'
|
|
24
|
-
| 'grid'
|
|
25
|
-
| GridLayoutConfig
|
|
26
|
-
| RowLayoutConfig
|
|
27
|
-
| ColumnLayoutConfig;
|
|
28
|
-
|
|
29
|
-
export interface ResponsiveLayoutConfig {
|
|
30
|
-
type: 'responsive';
|
|
31
|
-
mobile: BaseLayoutConfig;
|
|
32
|
-
desktop: BaseLayoutConfig;
|
|
33
|
-
breakpoint?: number;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type LayoutConfig = BaseLayoutConfig | ResponsiveLayoutConfig;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
describe('DynamicInput', () => {
|
|
2
|
-
it('should create DynamicInput', () => {
|
|
3
|
-
expect(true).toBe(true);
|
|
4
|
-
});
|
|
5
|
-
|
|
6
|
-
it('should accept type input', () => {
|
|
7
|
-
const type = 'text';
|
|
8
|
-
expect(type).toBe('text');
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('should have valueChange output', () => {
|
|
12
|
-
const hasOutput = true;
|
|
13
|
-
expect(hasOutput).toBe(true);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('should have onChange output for backward compatibility', () => {
|
|
17
|
-
const hasOutput = true;
|
|
18
|
-
expect(hasOutput).toBe(true);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('should handle unknown field type', () => {
|
|
22
|
-
const unknownType = 'unknownType';
|
|
23
|
-
expect(typeof unknownType).toBe('string');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('should emit value on change', () => {
|
|
27
|
-
const value = 'test-value';
|
|
28
|
-
expect(value).toBe('test-value');
|
|
29
|
-
});
|
|
30
|
-
});
|
package/test/FieldInput.spec.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
describe('FieldInput', () => {
|
|
2
|
-
it('should create FieldInput', () => {
|
|
3
|
-
expect(true).toBe(true);
|
|
4
|
-
});
|
|
5
|
-
|
|
6
|
-
it('should accept fieldDescription prop', () => {
|
|
7
|
-
const fieldDesc: any = {
|
|
8
|
-
name: 'username',
|
|
9
|
-
type: 'text',
|
|
10
|
-
label: 'Username',
|
|
11
|
-
placeholder: 'Enter name',
|
|
12
|
-
};
|
|
13
|
-
expect(fieldDesc.name).toBe('username');
|
|
14
|
-
expect(fieldDesc.type).toBe('text');
|
|
15
|
-
expect(fieldDesc.label).toBe('Username');
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('should pass options from fieldDescription', () => {
|
|
19
|
-
const fieldDesc: any = {
|
|
20
|
-
name: 'country',
|
|
21
|
-
type: 'text',
|
|
22
|
-
options: [{ label: 'USA' }, { label: 'VN' }],
|
|
23
|
-
};
|
|
24
|
-
expect(fieldDesc.options?.length).toBe(2);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('should pass className from fieldDescription', () => {
|
|
28
|
-
const fieldDesc: any = {
|
|
29
|
-
name: 'test',
|
|
30
|
-
type: 'text',
|
|
31
|
-
className: 'my-custom-class',
|
|
32
|
-
};
|
|
33
|
-
expect(fieldDesc.className).toBe('my-custom-class');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should pass description from fieldDescription', () => {
|
|
37
|
-
const fieldDesc: any = {
|
|
38
|
-
name: 'test',
|
|
39
|
-
type: 'text',
|
|
40
|
-
description: 'This is a help text',
|
|
41
|
-
};
|
|
42
|
-
expect(fieldDesc.description).toBe('This is a help text');
|
|
43
|
-
});
|
|
44
|
-
});
|
package/test/angular.spec.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
describe('Angular Package', () => {
|
|
2
|
-
describe('Components', () => {
|
|
3
|
-
it('should have DynamicInput component', () => {
|
|
4
|
-
expect(true).toBe(true);
|
|
5
|
-
});
|
|
6
|
-
|
|
7
|
-
it('should have FieldInput component', () => {
|
|
8
|
-
expect(true).toBe(true);
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('should have MultiFieldInput component', () => {
|
|
12
|
-
expect(true).toBe(true);
|
|
13
|
-
});
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
describe('Field Description', () => {
|
|
17
|
-
it('should create field with name and type', () => {
|
|
18
|
-
const field = { name: 'username', type: 'text' };
|
|
19
|
-
expect(field.name).toBe('username');
|
|
20
|
-
expect(field.type).toBe('text');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('should support optional label', () => {
|
|
24
|
-
const field = { name: 'email', type: 'text', label: 'Email' };
|
|
25
|
-
expect(field.label).toBe('Email');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('should support placeholder', () => {
|
|
29
|
-
const field = { name: 'name', type: 'text', placeholder: 'Enter name' };
|
|
30
|
-
expect(field.placeholder).toBe('Enter name');
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('should support required flag', () => {
|
|
34
|
-
const field = { name: 'email', type: 'text', required: true };
|
|
35
|
-
expect(field.required).toBe(true);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should support options for select fields', () => {
|
|
39
|
-
const field = {
|
|
40
|
-
name: 'country',
|
|
41
|
-
type: 'text',
|
|
42
|
-
options: [{ label: 'Vietnam', value: 'vn' }],
|
|
43
|
-
};
|
|
44
|
-
expect(field.options?.length).toBe(1);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should support className', () => {
|
|
48
|
-
const field = { name: 'test', type: 'text', className: 'custom-class' };
|
|
49
|
-
expect(field.className).toBe('custom-class');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('should support description', () => {
|
|
53
|
-
const field = { name: 'test', type: 'text', description: 'Help text' };
|
|
54
|
-
expect(field.description).toBe('Help text');
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
describe('Layouts', () => {
|
|
59
|
-
it('should support row layout', () => {
|
|
60
|
-
expect('row').toBe('row');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should support grid layout', () => {
|
|
64
|
-
expect('grid').toBe('grid');
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should support column layout', () => {
|
|
68
|
-
expect('column').toBe('column');
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
describe('appearCondition', () => {
|
|
73
|
-
it('should evaluate appearCondition', () => {
|
|
74
|
-
const condition = (data: any) => data.show === true;
|
|
75
|
-
expect(condition({ show: true })).toBe(true);
|
|
76
|
-
expect(condition({ show: false })).toBe(false);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should filter fields based on appearCondition', () => {
|
|
80
|
-
const fields = [
|
|
81
|
-
{ name: 'alwaysShow', type: 'text' },
|
|
82
|
-
{
|
|
83
|
-
name: 'conditional',
|
|
84
|
-
type: 'text',
|
|
85
|
-
appearCondition: (data: any) => data.accountType === 'business',
|
|
86
|
-
},
|
|
87
|
-
];
|
|
88
|
-
|
|
89
|
-
const visibleForPersonal = fields.filter(
|
|
90
|
-
(f) =>
|
|
91
|
-
!f.appearCondition || f.appearCondition({ accountType: 'personal' })
|
|
92
|
-
);
|
|
93
|
-
expect(visibleForPersonal.length).toBe(1);
|
|
94
|
-
|
|
95
|
-
const visibleForBusiness = fields.filter(
|
|
96
|
-
(f) =>
|
|
97
|
-
!f.appearCondition || f.appearCondition({ accountType: 'business' })
|
|
98
|
-
);
|
|
99
|
-
expect(visibleForBusiness.length).toBe(2);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
});
|