@masterteam/form-builder 0.0.2 → 0.0.4

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 (33) hide show
  1. package/assets/form-builder.css +2 -4
  2. package/fesm2022/masterteam-form-builder.mjs +1908 -0
  3. package/fesm2022/masterteam-form-builder.mjs.map +1 -0
  4. package/package.json +16 -16
  5. package/types/masterteam-form-builder.d.ts +299 -0
  6. package/.angular/cache/21.0.2/ng-packagr/db70d8f07b5a2d2d1c3124ca92e8d56d14fb894dce4d4867ba7c0db29ba913a3 +0 -1
  7. package/.angular/cache/21.0.2/ng-packagr/tsbuildinfo/masterteam-form-builder.tsbuildinfo +0 -1
  8. package/BACKEND_API_SPEC.md +0 -338
  9. package/angular.json +0 -26
  10. package/ng-package.json +0 -13
  11. package/src/lib/fb-field-conditions/condition-constants.ts +0 -262
  12. package/src/lib/fb-field-conditions/fb-field-conditions.html +0 -35
  13. package/src/lib/fb-field-conditions/fb-field-conditions.ts +0 -123
  14. package/src/lib/fb-field-form/fb-field-form.html +0 -59
  15. package/src/lib/fb-field-form/fb-field-form.ts +0 -250
  16. package/src/lib/fb-preview-form/fb-preview-form.html +0 -31
  17. package/src/lib/fb-preview-form/fb-preview-form.ts +0 -147
  18. package/src/lib/fb-section/fb-section.html +0 -130
  19. package/src/lib/fb-section/fb-section.ts +0 -211
  20. package/src/lib/fb-section-form/fb-section-form.html +0 -38
  21. package/src/lib/fb-section-form/fb-section-form.ts +0 -128
  22. package/src/lib/form-builder.html +0 -166
  23. package/src/lib/form-builder.model.ts +0 -27
  24. package/src/lib/form-builder.scss +0 -20
  25. package/src/lib/form-builder.ts +0 -208
  26. package/src/public-api.ts +0 -6
  27. package/src/store/form-builder/api.model.ts +0 -13
  28. package/src/store/form-builder/form-builder.actions.ts +0 -113
  29. package/src/store/form-builder/form-builder.facade.ts +0 -207
  30. package/src/store/form-builder/form-builder.model.ts +0 -147
  31. package/src/store/form-builder/form-builder.state.ts +0 -668
  32. package/src/store/form-builder/index.ts +0 -5
  33. package/tsconfig.json +0 -31
@@ -1,123 +0,0 @@
1
- import {
2
- Component,
3
- computed,
4
- inject,
5
- input,
6
- OnInit,
7
- signal,
8
- viewChild,
9
- } from '@angular/core';
10
- import { TranslocoDirective } from '@jsverse/transloco';
11
- import { ReactiveFormsModule, FormControl } from '@angular/forms';
12
- import { Button } from '@masterteam/components/button';
13
- import { ModalService } from '@masterteam/components/modal';
14
- import { ModalRef } from '@masterteam/components/dialog';
15
- import {
16
- FormulaEditor,
17
- FormulaToolbar,
18
- SmartBlock,
19
- FormulaToken,
20
- } from '@masterteam/components/formula';
21
- import {
22
- CONDITION_FUNCTION_CATEGORIES,
23
- CONDITION_OPERATORS,
24
- } from './condition-constants';
25
-
26
- /**
27
- * Field property for condition formula
28
- */
29
- export interface ConditionFieldProperty {
30
- /** Unique key (used in formula as @key) */
31
- key: string;
32
- /** Display name */
33
- name: string;
34
- /** Field type for hint */
35
- type?: string;
36
- }
37
-
38
- /**
39
- * Field Conditions Dialog
40
- *
41
- * Allows users to define conditional display formulas for form fields.
42
- * Uses FormulaToolbar + FormulaEditor directly for a visual formula editing experience.
43
- */
44
- @Component({
45
- selector: 'mt-fb-field-conditions',
46
- standalone: true,
47
- imports: [
48
- TranslocoDirective,
49
- ReactiveFormsModule,
50
- Button,
51
- FormulaToolbar,
52
- FormulaEditor,
53
- ],
54
- templateUrl: './fb-field-conditions.html',
55
- })
56
- export class FBFieldConditions implements OnInit {
57
- protected readonly modalService = inject(ModalService);
58
- private readonly ref = inject(ModalRef);
59
-
60
- // Inputs
61
- /** Initial formula (JSON string from backend) */
62
- readonly initialFormula = input<string>('');
63
-
64
- /** Available fields from the form builder (other fields that can be referenced) */
65
- readonly availableFields = input<ConditionFieldProperty[]>([]);
66
-
67
- // UI State
68
- readonly submitting = signal(false);
69
-
70
- // Form control for the formula editor (tokens array)
71
- readonly formulaControl = new FormControl<FormulaToken[]>([]);
72
-
73
- // View child for the editor
74
- private readonly editorRef = viewChild<FormulaEditor>('editor');
75
-
76
- // Condition-specific functions and operators
77
- readonly functionCategories = CONDITION_FUNCTION_CATEGORIES;
78
- readonly operators = CONDITION_OPERATORS;
79
-
80
- /** Extract property keys for toolbar */
81
- readonly propertyKeys = computed(() =>
82
- this.availableFields().map((f) => f.key),
83
- );
84
-
85
- /** Toolbar labels */
86
- readonly toolbarLabels = {
87
- functions: 'Functions',
88
- properties: 'Fields',
89
- operators: 'Operators',
90
- noPropertiesAvailable: 'No fields available',
91
- };
92
-
93
- ngOnInit(): void {
94
- // Parse JSON string to tokens
95
- const formula = this.initialFormula();
96
- if (formula) {
97
- try {
98
- const tokens = JSON.parse(formula) as FormulaToken[];
99
- this.formulaControl.patchValue(tokens);
100
- } catch {
101
- // Invalid JSON, start with empty
102
- }
103
- }
104
- }
105
-
106
- /** Handle block insert from toolbar */
107
- onBlockInsert(block: SmartBlock): void {
108
- const editor = this.editorRef();
109
- if (editor) {
110
- editor.addBlock(block);
111
- }
112
- }
113
-
114
- onSave(): void {
115
- const tokens = this.formulaControl.value ?? [];
116
- const formula = tokens.length > 0 ? JSON.stringify(tokens) : '';
117
- this.ref.close({ saved: true, conditionalDisplayFormula: formula });
118
- }
119
-
120
- onCancel(): void {
121
- this.ref.close({ saved: false });
122
- }
123
- }
@@ -1,59 +0,0 @@
1
- <ng-container *transloco="let t; prefix: 'formBuilder'">
2
- <div [class]="[modalService.contentClass, 'p-4', 'overflow-y-hidden!']">
3
- <div class="mt-4 h-full overflow-y-auto pb-10 flex flex-col gap-4">
4
- <mt-dynamic-form [formConfig]="formConfig" [formControl]="formControl">
5
- </mt-dynamic-form>
6
-
7
- <!-- Show/Hide Toggle with Set Conditions -->
8
- <mt-toggle-field
9
- toggleShape="card"
10
- [label]="t('show-hide')"
11
- [descriptionCard]="t('show-hide-description')"
12
- icon="general.eye"
13
- [formControl]="showHideControl"
14
- >
15
- <ng-template #toggleCardBottom>
16
- @if (showHideControl.value) {
17
- <div class="mt-3">
18
- <mt-button
19
- [label]="t('set-conditions')"
20
- size="small"
21
- (onClick)="onSetConditions()"
22
- ></mt-button>
23
- </div>
24
- }
25
- </ng-template>
26
- </mt-toggle-field>
27
- </div>
28
- </div>
29
-
30
- <div [class]="modalService.footerClass">
31
- @if (initialData()) {
32
- <mt-button
33
- [tooltip]="t('delete')"
34
- severity="danger"
35
- outlined
36
- icon="general.trash-01"
37
- [loading]="deleting()"
38
- [disabled]="submitting()"
39
- (onClick)="onDelete($event)"
40
- class="me-auto"
41
- ></mt-button>
42
- }
43
- <mt-button
44
- [label]="t('cancel')"
45
- severity="secondary"
46
- [disabled]="submitting() || deleting()"
47
- (onClick)="onCancel()"
48
- >
49
- </mt-button>
50
- <mt-button
51
- [disabled]="!formControl.valid || deleting()"
52
- [label]="t('save')"
53
- severity="primary"
54
- [loading]="submitting()"
55
- (onClick)="onSave()"
56
- >
57
- </mt-button>
58
- </div>
59
- </ng-container>
@@ -1,250 +0,0 @@
1
- import {
2
- Component,
3
- computed,
4
- effect,
5
- inject,
6
- input,
7
- signal,
8
- } from '@angular/core';
9
- import { TranslocoDirective, TranslocoService } from '@jsverse/transloco';
10
- import { ReactiveFormsModule, FormControl } from '@angular/forms';
11
- import { DynamicForm } from '@masterteam/forms/dynamic-form';
12
- import {
13
- DynamicFormConfig,
14
- RadioCardsFieldConfig,
15
- } from '@masterteam/components';
16
- import { Button } from '@masterteam/components/button';
17
- import { ToggleField } from '@masterteam/components/toggle-field';
18
- import { ModalService } from '@masterteam/components/modal';
19
- import { ModalRef } from '@masterteam/components/dialog';
20
- import { ConfirmationService } from '@masterteam/components/confirmation';
21
- import type {
22
- EnrichedFormField,
23
- EnrichedFormSection,
24
- FieldWidth,
25
- } from '../../store/form-builder/form-builder.model';
26
- import { FormBuilderFacade } from '../../store/form-builder';
27
- import {
28
- FBFieldConditions,
29
- ConditionFieldProperty,
30
- } from '../fb-field-conditions/fb-field-conditions';
31
-
32
- @Component({
33
- selector: 'mt-fb-field-form',
34
- standalone: true,
35
- imports: [
36
- TranslocoDirective,
37
- ReactiveFormsModule,
38
- DynamicForm,
39
- Button,
40
- ToggleField,
41
- ],
42
- templateUrl: './fb-field-form.html',
43
- })
44
- export class FBFieldForm {
45
- private readonly transloco = inject(TranslocoService);
46
- protected readonly modalService = inject(ModalService);
47
- private readonly ref = inject(ModalRef);
48
- private readonly confirmationService = inject(ConfirmationService);
49
- private readonly facade = inject(FormBuilderFacade);
50
-
51
- // Inputs
52
- readonly sectionId = input<string>('');
53
- readonly initialData = input<EnrichedFormField | null>(null);
54
- /** All sections with enriched fields (to get available fields for conditions) */
55
- readonly allSections = input<EnrichedFormSection[]>([]);
56
-
57
- // UI State
58
- readonly submitting = signal(false);
59
- readonly deleting = signal(false);
60
- readonly conditionalDisplayFormula = signal<string>('');
61
- private conditionsDialogRef: any;
62
-
63
- /**
64
- * Compute available fields for condition formula
65
- * Excludes the current field being edited
66
- */
67
- readonly availableFields = computed<ConditionFieldProperty[]>(() => {
68
- const sections = this.allSections();
69
- const currentField = this.initialData();
70
- const fields: ConditionFieldProperty[] = [];
71
-
72
- for (const section of sections) {
73
- for (const field of section.fields) {
74
- // Exclude the current field being edited
75
- if (currentField && field.id === currentField.id) continue;
76
-
77
- fields.push({
78
- key: field.name,
79
- name: field.name,
80
- type: field.type,
81
- });
82
- }
83
- }
84
-
85
- return fields;
86
- });
87
-
88
- // Form
89
- readonly formControl = new FormControl();
90
- readonly showHideControl = new FormControl(false);
91
- readonly formConfig: DynamicFormConfig = {
92
- sections: [
93
- {
94
- key: 'section-form',
95
- type: 'none',
96
- columns: 12,
97
- order: 1,
98
- fields: [
99
- {
100
- key: 'isRequired',
101
- label: this.transloco.translate('formBuilder.is-required'),
102
- type: 'toggle',
103
- toggleShape: 'card',
104
- colSpan: 12,
105
- order: 1,
106
- },
107
- {
108
- key: 'hiddenInCreation',
109
- label: this.transloco.translate('formBuilder.hidden-in-creation'),
110
- type: 'toggle',
111
- toggleShape: 'card',
112
- colSpan: 12,
113
- order: 2,
114
- },
115
- {
116
- key: 'hiddenInEditForm',
117
- label: this.transloco.translate('formBuilder.hidden-in-edit-form'),
118
- type: 'toggle',
119
- toggleShape: 'card',
120
- colSpan: 12,
121
- order: 3,
122
- },
123
- new RadioCardsFieldConfig({
124
- key: 'size',
125
- label: this.transloco.translate('formBuilder.size'),
126
- placeholder: this.transloco.translate('formBuilder.size'),
127
- options: [
128
- { id: 's', name: 'S' },
129
- { id: 'm', name: 'M' },
130
- { id: 'l', name: 'L' },
131
- ],
132
- order: 4,
133
- size: 'small',
134
- }),
135
- ],
136
- },
137
- ],
138
- };
139
-
140
- constructor() {
141
- effect(() => {
142
- const data = this.initialData();
143
- if (data) {
144
- const widthToSize: Record<string, string> = {
145
- '25': 's',
146
- '50': 'm',
147
- '100': 'l',
148
- };
149
- this.formControl.patchValue({
150
- isRequired: data.isRequired ?? false,
151
- hiddenInCreation: data.hiddenInCreation ?? false,
152
- hiddenInEditForm: data.hiddenInEditForm ?? false,
153
- size: widthToSize[data.width] ?? 'l',
154
- });
155
- // Set show/hide toggle based on existing value
156
- this.showHideControl.patchValue(
157
- data.showConditionalDisplayFormula ?? false,
158
- );
159
- this.conditionalDisplayFormula.set(
160
- data.conditionalDisplayFormula ?? '',
161
- );
162
- }
163
- });
164
- }
165
-
166
- onSave(): void {
167
- if (this.formControl.invalid) return;
168
-
169
- const formValue = this.formControl.value;
170
- const field = this.initialData();
171
- const sectionId = this.sectionId();
172
-
173
- if (!field || !sectionId) return;
174
-
175
- const widthMap: Record<string, FieldWidth> = {
176
- s: '25',
177
- m: '50',
178
- l: '100',
179
- };
180
-
181
- const payload = {
182
- width: widthMap[formValue.size] ?? '100',
183
- hiddenInCreation: formValue.hiddenInCreation ?? false,
184
- hiddenInEditForm: formValue.hiddenInEditForm ?? false,
185
- isRequired: formValue.isRequired ?? false,
186
- showConditionalDisplayFormula: this.showHideControl.value ?? false,
187
- conditionalDisplayFormula: this.showHideControl.value
188
- ? this.conditionalDisplayFormula()
189
- : null,
190
- };
191
-
192
- this.submitting.set(true);
193
- this.facade.updateField(sectionId, field.id, payload).subscribe({
194
- next: () => this.ref.close(true),
195
- error: () => this.submitting.set(false),
196
- });
197
- }
198
-
199
- onCancel(): void {
200
- this.ref.close(false);
201
- }
202
-
203
- onSetConditions(): void {
204
- this.conditionsDialogRef = this.modalService.openModal(
205
- FBFieldConditions,
206
- 'drawer',
207
- {
208
- header: this.transloco.translate('formBuilder.set-conditions'),
209
- styleClass: '!w-[calc(100%-25rem)] !absolute ',
210
- position: 'start',
211
- modal: true,
212
- dismissible: true,
213
- appendTo: '#page-content',
214
- inputValues: {
215
- initialFormula: this.conditionalDisplayFormula(),
216
- availableFields: this.availableFields(),
217
- },
218
- },
219
- );
220
-
221
- this.conditionsDialogRef.onClose.subscribe(
222
- (result: { saved: boolean; conditionalDisplayFormula?: string }) => {
223
- if (result?.saved) {
224
- this.conditionalDisplayFormula.set(
225
- result.conditionalDisplayFormula ?? '',
226
- );
227
- }
228
- },
229
- );
230
- }
231
-
232
- onDelete(event: Event): void {
233
- const field = this.initialData();
234
- const sectionId = this.sectionId();
235
-
236
- if (!field || !sectionId) return;
237
-
238
- this.confirmationService.confirmDelete({
239
- event,
240
- type: 'popup',
241
- accept: () => {
242
- this.deleting.set(true);
243
- this.facade.deleteField(sectionId, field.id).subscribe({
244
- next: () => this.ref.close(true),
245
- error: () => this.deleting.set(false),
246
- });
247
- },
248
- });
249
- }
250
- }
@@ -1,31 +0,0 @@
1
- <ng-container *transloco="let t; prefix: 'formBuilder'">
2
- <div [class]="[modalService.contentClass, 'p-4', 'overflow-y-hidden!']">
3
- <!-- Tabs for Create/Edit mode -->
4
- <div class="flex justify-center mb-4">
5
- <mt-tabs [options]="tabOptions" [(active)]="activeTab"></mt-tabs>
6
- </div>
7
-
8
- <div class="h-full overflow-y-auto pb-10">
9
- @if (isLoading()) {
10
- <div class="space-y-4 animate-pulse">
11
- <div class="h-6 bg-surface-200 rounded w-1/3"></div>
12
- <div class="h-10 bg-surface-200 rounded"></div>
13
- <div class="h-10 bg-surface-200 rounded"></div>
14
- <div class="h-10 bg-surface-200 rounded w-2/3"></div>
15
- </div>
16
- } @else if (formConfig().sections.length > 0) {
17
- <mt-dynamic-form
18
- [formConfig]="formConfig()"
19
- [formControl]="formControl"
20
- >
21
- </mt-dynamic-form>
22
- } @else {
23
- <div
24
- class="flex justify-center items-center h-64 text-muted-color text-lg"
25
- >
26
- {{ t("no-fields-visible") }}
27
- </div>
28
- }
29
- </div>
30
- </div>
31
- </ng-container>
@@ -1,147 +0,0 @@
1
- import {
2
- Component,
3
- computed,
4
- effect,
5
- inject,
6
- input,
7
- signal,
8
- } from '@angular/core';
9
- import { TranslocoDirective, TranslocoService } from '@jsverse/transloco';
10
- import { ReactiveFormsModule, FormControl } from '@angular/forms';
11
- import { DynamicForm } from '@masterteam/forms/dynamic-form';
12
- import { DynamicFormConfig, DynamicFieldConfig } from '@masterteam/components';
13
- import { Tabs } from '@masterteam/components/tabs';
14
- import { ModalService } from '@masterteam/components/modal';
15
- import { ModalRef } from '@masterteam/components/dialog';
16
- import type { EnrichedFormSection } from '../../store/form-builder/form-builder.model';
17
-
18
- type PreviewMode = 'create' | 'edit';
19
-
20
- @Component({
21
- selector: 'mt-fb-preview-form',
22
- standalone: true,
23
- imports: [TranslocoDirective, ReactiveFormsModule, DynamicForm, Tabs],
24
- templateUrl: './fb-preview-form.html',
25
- })
26
- export class FBPreviewForm {
27
- protected readonly modalService = inject(ModalService);
28
- private readonly ref = inject(ModalRef);
29
- private readonly transloco = inject(TranslocoService);
30
-
31
- // Inputs
32
- readonly sections = input<EnrichedFormSection[]>([]);
33
-
34
- // Tab state
35
- readonly activeTab = signal<PreviewMode>('create');
36
- readonly isLoading = signal(false);
37
- readonly tabOptions = [
38
- {
39
- label: this.transloco.translate('formBuilder.create-form'),
40
- value: 'create',
41
- },
42
- { label: this.transloco.translate('formBuilder.edit-form'), value: 'edit' },
43
- ];
44
-
45
- // Form control for dynamic form
46
- readonly formControl = new FormControl();
47
-
48
- constructor() {
49
- // Show skeleton briefly when tab changes to force rebuild
50
- effect(() => {
51
- this.activeTab(); // Track tab changes
52
- this.isLoading.set(true);
53
- setTimeout(() => this.isLoading.set(false), 50);
54
- });
55
- }
56
-
57
- // Convert enriched sections to DynamicFormConfig based on active tab
58
- readonly formConfig = computed<DynamicFormConfig>(() => {
59
- const sections = this.sections();
60
- const mode = this.activeTab();
61
-
62
- return {
63
- sections: sections
64
- .map((section, sectionIndex) => {
65
- const lang = document.documentElement.lang as 'en' | 'ar';
66
- const sectionName = section.name[lang] ?? section.name['en'] ?? '';
67
-
68
- // Filter fields based on mode
69
- const visibleFields = section.fields.filter((field) => {
70
- if (mode === 'create') {
71
- return !field.hiddenInCreation;
72
- } else {
73
- return !field.hiddenInEditForm;
74
- }
75
- });
76
-
77
- return {
78
- key: section.id,
79
- label: sectionName,
80
- type: 'header' as const,
81
- columns: 12,
82
- order: section.order ?? sectionIndex,
83
- fields: visibleFields.map((field, fieldIndex) => {
84
- const colSpan = this.getColSpan(field.width);
85
-
86
- return {
87
- key: `field_${field.propertyId}`,
88
- label: field.name,
89
- type: this.mapFieldType(field.type),
90
- colSpan,
91
- order: field.order ?? fieldIndex,
92
- placeholder: field.name,
93
- } as DynamicFieldConfig;
94
- }),
95
- };
96
- })
97
- .filter((section) => section.fields.length > 0), // Hide empty sections
98
- };
99
- });
100
-
101
- /**
102
- * Map property view type to dynamic form field type
103
- */
104
- private mapFieldType(viewType: string): string {
105
- const typeMap: Record<string, string> = {
106
- User: 'select',
107
- Text: 'text',
108
- LongText: 'editor-field',
109
- Percentage: 'slider',
110
- Date: 'date',
111
- Currency: 'text',
112
- Number: 'number',
113
- Lookup: 'select',
114
- LookupMultiSelect: 'select',
115
- Checkbox: 'toggle',
116
- InternalModule: 'select',
117
- DynamicList: 'select',
118
- API: 'select',
119
- Time: 'date',
120
- Status: 'select',
121
- Attachment: 'attachment',
122
- EditableListView: 'actionableTable',
123
- LookupLog: 'actionableTable',
124
- LookupMatrix: 'select',
125
- Location: 'select',
126
- };
127
-
128
- return typeMap[viewType] ?? 'text';
129
- }
130
-
131
- /**
132
- * Convert field width to colSpan
133
- */
134
- private getColSpan(width: string): number {
135
- const widthMap: Record<string, number> = {
136
- '25': 3,
137
- '50': 6,
138
- '100': 12,
139
- };
140
-
141
- return widthMap[width] ?? 12;
142
- }
143
-
144
- onClose(): void {
145
- this.ref.close();
146
- }
147
- }