@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
@@ -0,0 +1,1908 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, Injectable, computed, input, signal, viewChild, Component, effect, output } from '@angular/core';
3
+ import * as i4 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+ import * as i1 from '@angular/forms';
6
+ import { FormControl, ReactiveFormsModule, FormGroup, FormsModule } from '@angular/forms';
7
+ import * as i1$1 from 'primeng/tabs';
8
+ import { TabsModule } from 'primeng/tabs';
9
+ import * as i2 from 'primeng/skeleton';
10
+ import { SkeletonModule } from 'primeng/skeleton';
11
+ import { Button } from '@masterteam/components/button';
12
+ import { Card } from '@masterteam/components/card';
13
+ import { ModalService } from '@masterteam/components/modal';
14
+ import { ConfirmationService } from '@masterteam/components/confirmation';
15
+ import { TranslocoDirective, TranslocoService } from '@jsverse/transloco';
16
+ import * as i3 from '@angular/cdk/drag-drop';
17
+ import { CdkDrag, CdkDropList, CdkDragPlaceholder, DragDropModule } from '@angular/cdk/drag-drop';
18
+ import { DynamicField } from '@masterteam/forms/dynamic-field';
19
+ import { Icon } from '@masterteam/icons';
20
+ import { Action, Selector, State, Store, select } from '@ngxs/store';
21
+ import { HttpClient } from '@angular/common/http';
22
+ import { CrudStateBase, handleApiRequest, RadioCardsFieldConfig } from '@masterteam/components';
23
+ import { DynamicForm } from '@masterteam/forms/dynamic-form';
24
+ import { ToggleField } from '@masterteam/components/toggle-field';
25
+ import { ModalRef } from '@masterteam/components/dialog';
26
+ import { FormulaToolbar, FormulaEditor } from '@masterteam/components/formula';
27
+ import { Tabs } from '@masterteam/components/tabs';
28
+
29
+ // ============================================================================
30
+ // Module Configuration Actions
31
+ // ============================================================================
32
+ class SetModuleInfo {
33
+ moduleType;
34
+ moduleId;
35
+ parentModuleType;
36
+ parentModuleId;
37
+ parentPath;
38
+ static type = '[FormBuilder] Set Module Info';
39
+ constructor(moduleType, moduleId, parentModuleType, parentModuleId, parentPath) {
40
+ this.moduleType = moduleType;
41
+ this.moduleId = moduleId;
42
+ this.parentModuleType = parentModuleType;
43
+ this.parentModuleId = parentModuleId;
44
+ this.parentPath = parentPath;
45
+ }
46
+ }
47
+ class ResetFormBuilderState {
48
+ static type = '[FormBuilder] Reset State';
49
+ }
50
+ class SetProperties {
51
+ properties;
52
+ static type = '[FormBuilder] Set Properties';
53
+ constructor(properties) {
54
+ this.properties = properties;
55
+ }
56
+ }
57
+ // ============================================================================
58
+ // Form Configuration Actions
59
+ // ============================================================================
60
+ class GetFormConfiguration {
61
+ static type = '[FormBuilder] Get Form Configuration';
62
+ }
63
+ class ResetFormConfiguration {
64
+ static type = '[FormBuilder] Reset Form Configuration';
65
+ }
66
+ // ============================================================================
67
+ // Section Actions
68
+ // ============================================================================
69
+ class AddSection {
70
+ payload;
71
+ static type = '[FormBuilder] Add Section';
72
+ constructor(payload) {
73
+ this.payload = payload;
74
+ }
75
+ }
76
+ class UpdateSection {
77
+ sectionId;
78
+ payload;
79
+ static type = '[FormBuilder] Update Section';
80
+ constructor(sectionId, payload) {
81
+ this.sectionId = sectionId;
82
+ this.payload = payload;
83
+ }
84
+ }
85
+ class DeleteSection {
86
+ sectionId;
87
+ static type = '[FormBuilder] Delete Section';
88
+ constructor(sectionId) {
89
+ this.sectionId = sectionId;
90
+ }
91
+ }
92
+ // ============================================================================
93
+ // Field Actions
94
+ // ============================================================================
95
+ class AddField {
96
+ sectionId;
97
+ payload;
98
+ static type = '[FormBuilder] Add Field';
99
+ constructor(sectionId, payload) {
100
+ this.sectionId = sectionId;
101
+ this.payload = payload;
102
+ }
103
+ }
104
+ class UpdateField {
105
+ sectionId;
106
+ fieldId;
107
+ payload;
108
+ static type = '[FormBuilder] Update Field';
109
+ constructor(sectionId, fieldId, payload) {
110
+ this.sectionId = sectionId;
111
+ this.fieldId = fieldId;
112
+ this.payload = payload;
113
+ }
114
+ }
115
+ class DeleteField {
116
+ sectionId;
117
+ fieldId;
118
+ static type = '[FormBuilder] Delete Field';
119
+ constructor(sectionId, fieldId) {
120
+ this.sectionId = sectionId;
121
+ this.fieldId = fieldId;
122
+ }
123
+ }
124
+ class ReorderFields {
125
+ sectionId;
126
+ payload;
127
+ static type = '[FormBuilder] Reorder Fields';
128
+ constructor(sectionId, payload) {
129
+ this.sectionId = sectionId;
130
+ this.payload = payload;
131
+ }
132
+ }
133
+ class MoveField {
134
+ sectionId;
135
+ fieldId;
136
+ payload;
137
+ static type = '[FormBuilder] Move Field';
138
+ constructor(sectionId, fieldId, payload) {
139
+ this.sectionId = sectionId;
140
+ this.fieldId = fieldId;
141
+ this.payload = payload;
142
+ }
143
+ }
144
+
145
+ // ============================================================================
146
+ // Action Keys Enum
147
+ // ============================================================================
148
+ var FormBuilderActionKey;
149
+ (function (FormBuilderActionKey) {
150
+ // Form Configuration
151
+ FormBuilderActionKey["GetFormConfiguration"] = "getFormConfiguration";
152
+ FormBuilderActionKey["ResetFormConfiguration"] = "resetFormConfiguration";
153
+ // Section CRUD
154
+ FormBuilderActionKey["AddSection"] = "addSection";
155
+ FormBuilderActionKey["UpdateSection"] = "updateSection";
156
+ FormBuilderActionKey["DeleteSection"] = "deleteSection";
157
+ // Field CRUD
158
+ FormBuilderActionKey["AddField"] = "addField";
159
+ FormBuilderActionKey["UpdateField"] = "updateField";
160
+ FormBuilderActionKey["DeleteField"] = "deleteField";
161
+ FormBuilderActionKey["MoveField"] = "moveField";
162
+ FormBuilderActionKey["ReorderFields"] = "reorderFields";
163
+ })(FormBuilderActionKey || (FormBuilderActionKey = {}));
164
+
165
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
166
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
167
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
168
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
169
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
170
+ };
171
+ // Default State
172
+ const DEFAULT_STATE = {
173
+ // Module configuration
174
+ moduleType: null,
175
+ moduleId: null,
176
+ parentModuleType: null,
177
+ parentModuleId: null,
178
+ parentPath: '',
179
+ // Form data
180
+ formConfiguration: null,
181
+ // Properties for field enrichment
182
+ properties: [],
183
+ // Loading state (from LoadingStateShape)
184
+ loadingActive: [],
185
+ errors: {},
186
+ };
187
+ let FormBuilderState = class FormBuilderState extends CrudStateBase {
188
+ http = inject(HttpClient);
189
+ baseUrl = 'formConfigurations';
190
+ // ============================================================================
191
+ // Helpers
192
+ // ============================================================================
193
+ getApiPath(state) {
194
+ const { parentPath, moduleType, moduleId } = state;
195
+ return `${this.baseUrl}${parentPath}/${moduleType}/${moduleId}`;
196
+ }
197
+ // ============================================================================
198
+ // Selectors
199
+ // ============================================================================
200
+ static getState(state) {
201
+ return state ?? DEFAULT_STATE;
202
+ }
203
+ static getFormConfiguration(state) {
204
+ return state?.formConfiguration ?? null;
205
+ }
206
+ static getSections(state) {
207
+ return state?.formConfiguration?.sections ?? [];
208
+ }
209
+ static getModuleType(state) {
210
+ return state?.moduleType ?? null;
211
+ }
212
+ static getModuleId(state) {
213
+ return state?.moduleId ?? null;
214
+ }
215
+ static getProperties(state) {
216
+ return state?.properties ?? [];
217
+ }
218
+ // ============================================================================
219
+ // Module Configuration Actions
220
+ // ============================================================================
221
+ setModuleInfo(ctx, action) {
222
+ let parentPath = '';
223
+ if (action.parentModuleType && action.parentModuleId) {
224
+ parentPath = `/${action.parentModuleType}/${action.parentModuleId}`;
225
+ }
226
+ else if (action.parentPath) {
227
+ parentPath = action.parentPath;
228
+ }
229
+ ctx.patchState({
230
+ moduleType: action.moduleType,
231
+ moduleId: action.moduleId,
232
+ parentModuleType: action.parentModuleType ?? null,
233
+ parentModuleId: action.parentModuleId ?? null,
234
+ parentPath: parentPath ?? '',
235
+ });
236
+ }
237
+ resetState(ctx) {
238
+ ctx.setState(DEFAULT_STATE);
239
+ }
240
+ setProperties(ctx, action) {
241
+ ctx.patchState({
242
+ properties: action.properties,
243
+ });
244
+ }
245
+ // ============================================================================
246
+ // Form Configuration Actions
247
+ // ============================================================================
248
+ getFormConfiguration(ctx) {
249
+ const state = ctx.getState();
250
+ const apiPath = this.getApiPath(state);
251
+ const req$ = this.http.get(apiPath);
252
+ return this.load(ctx, {
253
+ key: FormBuilderActionKey.GetFormConfiguration,
254
+ request$: req$,
255
+ updateState: (_state, data) => ({
256
+ formConfiguration: data ?? null,
257
+ }),
258
+ });
259
+ }
260
+ resetFormConfiguration(ctx) {
261
+ const state = ctx.getState();
262
+ const apiPath = `${this.getApiPath(state)}/reset`;
263
+ const req$ = this.http.delete(apiPath);
264
+ return handleApiRequest({
265
+ ctx,
266
+ key: FormBuilderActionKey.ResetFormConfiguration,
267
+ request$: req$,
268
+ onSuccess: (res, _currentState) => ({
269
+ formConfiguration: res.data ?? null,
270
+ }),
271
+ });
272
+ }
273
+ // ============================================================================
274
+ // Section Actions
275
+ // ============================================================================
276
+ addSection(ctx, action) {
277
+ const state = ctx.getState();
278
+ const apiPath = `${this.getApiPath(state)}/sections`;
279
+ const req$ = this.http.post(apiPath, action.payload);
280
+ return handleApiRequest({
281
+ ctx,
282
+ key: FormBuilderActionKey.AddSection,
283
+ request$: req$,
284
+ onSuccess: (res, currentState) => {
285
+ const sections = this.adapter.addOne(currentState.formConfiguration?.sections ?? [], res.data);
286
+ return {
287
+ formConfiguration: {
288
+ ...currentState.formConfiguration,
289
+ sections,
290
+ },
291
+ };
292
+ },
293
+ });
294
+ }
295
+ updateSection(ctx, action) {
296
+ const state = ctx.getState();
297
+ const apiPath = `${this.getApiPath(state)}/sections/${action.sectionId}`;
298
+ const req$ = this.http.put(apiPath, action.payload);
299
+ return handleApiRequest({
300
+ ctx,
301
+ key: FormBuilderActionKey.UpdateSection,
302
+ request$: req$,
303
+ onSuccess: (res, currentState) => {
304
+ const sections = this.adapter.upsertOne(currentState.formConfiguration?.sections ?? [], res.data, 'id');
305
+ return {
306
+ formConfiguration: {
307
+ ...currentState.formConfiguration,
308
+ sections,
309
+ },
310
+ };
311
+ },
312
+ });
313
+ }
314
+ deleteSection(ctx, action) {
315
+ const state = ctx.getState();
316
+ const apiPath = `${this.getApiPath(state)}/sections/${action.sectionId}`;
317
+ const req$ = this.http.delete(apiPath);
318
+ return handleApiRequest({
319
+ ctx,
320
+ key: FormBuilderActionKey.DeleteSection,
321
+ request$: req$,
322
+ onSuccess: (res, currentState) => {
323
+ const sections = this.adapter.removeOne(currentState.formConfiguration?.sections ?? [], res.data.id, 'id');
324
+ return {
325
+ formConfiguration: {
326
+ ...currentState.formConfiguration,
327
+ sections,
328
+ },
329
+ };
330
+ },
331
+ });
332
+ }
333
+ // ============================================================================
334
+ // Field Actions
335
+ // ============================================================================
336
+ addField(ctx, action) {
337
+ const state = ctx.getState();
338
+ const apiPath = `${this.getApiPath(state)}/sections/${action.sectionId}/fields`;
339
+ const req$ = this.http.post(apiPath, action.payload);
340
+ // Generate temp ID for optimistic update
341
+ const tempId = `temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
342
+ const tempField = {
343
+ id: tempId,
344
+ sectionId: action.sectionId,
345
+ propertyId: action.payload.propertyId,
346
+ width: action.payload.width,
347
+ order: action.payload.order ?? 0,
348
+ hiddenInCreation: action.payload.hiddenInCreation,
349
+ _pending: true,
350
+ };
351
+ // Optimistically add the temp field
352
+ const sectionsWithTemp = (state.formConfiguration?.sections ?? []).map((section) => {
353
+ if (section.id === action.sectionId) {
354
+ const fields = [...section.fields];
355
+ const insertIndex = action.payload.order ?? fields.length;
356
+ fields.splice(insertIndex, 0, tempField);
357
+ return { ...section, fields };
358
+ }
359
+ return section;
360
+ });
361
+ ctx.patchState({
362
+ formConfiguration: {
363
+ ...state.formConfiguration,
364
+ sections: sectionsWithTemp,
365
+ },
366
+ });
367
+ return handleApiRequest({
368
+ ctx,
369
+ key: FormBuilderActionKey.AddField,
370
+ request$: req$,
371
+ onSuccess: (res, currentState) => {
372
+ const newField = res.data;
373
+ // Replace temp field with real field
374
+ const sections = (currentState.formConfiguration?.sections ?? []).map((section) => {
375
+ if (section.id === action.sectionId) {
376
+ return {
377
+ ...section,
378
+ fields: section.fields.map((f) => f.id === tempId ? newField : f),
379
+ };
380
+ }
381
+ return section;
382
+ });
383
+ return {
384
+ formConfiguration: {
385
+ ...currentState.formConfiguration,
386
+ sections,
387
+ },
388
+ };
389
+ },
390
+ onError: (_error, currentState) => {
391
+ // Remove temp field on error
392
+ const sections = (currentState.formConfiguration?.sections ?? []).map((section) => {
393
+ if (section.id === action.sectionId) {
394
+ return {
395
+ ...section,
396
+ fields: section.fields.filter((f) => f.id !== tempId),
397
+ };
398
+ }
399
+ return section;
400
+ });
401
+ return {
402
+ formConfiguration: {
403
+ ...currentState.formConfiguration,
404
+ sections,
405
+ },
406
+ };
407
+ },
408
+ });
409
+ }
410
+ updateField(ctx, action) {
411
+ const state = ctx.getState();
412
+ const apiPath = `${this.getApiPath(state)}/sections/${action.sectionId}/fields/${action.fieldId}`;
413
+ const req$ = this.http.put(apiPath, action.payload);
414
+ // Apply optimistic update for order changes
415
+ if (action.payload.order !== undefined) {
416
+ const newOrder = action.payload.order;
417
+ const sections = (state.formConfiguration?.sections ?? []).map((section) => {
418
+ if (section.id === action.sectionId) {
419
+ const fields = [...section.fields];
420
+ const currentIndex = fields.findIndex((f) => f.id === action.fieldId);
421
+ if (currentIndex !== -1) {
422
+ const [movedField] = fields.splice(currentIndex, 1);
423
+ fields.splice(newOrder, 0, movedField);
424
+ }
425
+ return { ...section, fields };
426
+ }
427
+ return section;
428
+ });
429
+ ctx.patchState({
430
+ formConfiguration: {
431
+ ...state.formConfiguration,
432
+ sections,
433
+ },
434
+ });
435
+ }
436
+ return handleApiRequest({
437
+ ctx,
438
+ key: FormBuilderActionKey.UpdateField,
439
+ request$: req$,
440
+ onSuccess: (res, currentState) => {
441
+ const updatedField = res.data;
442
+ const sections = (currentState.formConfiguration?.sections ?? []).map((section) => {
443
+ if (section.id === action.sectionId) {
444
+ // Update the field but preserve current array order (from optimistic update)
445
+ // Don't re-sort by order property as other fields may have stale order values
446
+ const updatedFields = section.fields.map((f, index) => f.id === updatedField.id
447
+ ? { ...updatedField, order: index }
448
+ : { ...f, order: index });
449
+ return {
450
+ ...section,
451
+ fields: updatedFields,
452
+ };
453
+ }
454
+ return section;
455
+ });
456
+ return {
457
+ formConfiguration: {
458
+ ...currentState.formConfiguration,
459
+ sections,
460
+ },
461
+ };
462
+ },
463
+ onError: (_error, _currentState) => {
464
+ // Revert optimistic update on error - reload original state
465
+ if (action.payload.order !== undefined) {
466
+ return {
467
+ formConfiguration: state.formConfiguration,
468
+ };
469
+ }
470
+ return {};
471
+ },
472
+ });
473
+ }
474
+ deleteField(ctx, action) {
475
+ const state = ctx.getState();
476
+ const apiPath = `${this.getApiPath(state)}/sections/${action.sectionId}/fields/${action.fieldId}`;
477
+ const req$ = this.http.delete(apiPath);
478
+ // Optimistically mark field as deleting
479
+ const sectionsWithDeleting = (state.formConfiguration?.sections ?? []).map((section) => {
480
+ if (section.id === action.sectionId) {
481
+ return {
482
+ ...section,
483
+ fields: section.fields.map((f) => f.id === action.fieldId ? { ...f, _deleting: true } : f),
484
+ };
485
+ }
486
+ return section;
487
+ });
488
+ ctx.patchState({
489
+ formConfiguration: {
490
+ ...state.formConfiguration,
491
+ sections: sectionsWithDeleting,
492
+ },
493
+ });
494
+ return handleApiRequest({
495
+ ctx,
496
+ key: FormBuilderActionKey.DeleteField,
497
+ request$: req$,
498
+ onSuccess: (res, currentState) => {
499
+ const { id: deletedId, sectionId } = res.data;
500
+ const sections = (currentState.formConfiguration?.sections ?? []).map((section) => {
501
+ if (section.id === sectionId) {
502
+ return {
503
+ ...section,
504
+ fields: section.fields.filter((f) => f.id !== deletedId),
505
+ };
506
+ }
507
+ return section;
508
+ });
509
+ return {
510
+ formConfiguration: {
511
+ ...currentState.formConfiguration,
512
+ sections,
513
+ },
514
+ };
515
+ },
516
+ onError: (_error, currentState) => {
517
+ // Remove _deleting flag on error
518
+ const sections = (currentState.formConfiguration?.sections ?? []).map((section) => {
519
+ if (section.id === action.sectionId) {
520
+ return {
521
+ ...section,
522
+ fields: section.fields.map((f) => f.id === action.fieldId ? { ...f, _deleting: false } : f),
523
+ };
524
+ }
525
+ return section;
526
+ });
527
+ return {
528
+ formConfiguration: {
529
+ ...currentState.formConfiguration,
530
+ sections,
531
+ },
532
+ };
533
+ },
534
+ });
535
+ }
536
+ reorderFields(ctx, action) {
537
+ const state = ctx.getState();
538
+ const apiPath = `${this.getApiPath(state)}/sections/${action.sectionId}/fields/reorder`;
539
+ const req$ = this.http.put(apiPath, action.payload);
540
+ // Create order map for optimistic update
541
+ const orderMap = new Map(action.payload.map((p) => [p.id, p.order]));
542
+ // Apply optimistic update
543
+ const optimisticSections = (state.formConfiguration?.sections ?? []).map((section) => {
544
+ if (section.id === action.sectionId) {
545
+ const reorderedFields = section.fields
546
+ .map((f) => ({
547
+ ...f,
548
+ order: orderMap.get(f.id) ?? f.order,
549
+ }))
550
+ .sort((a, b) => a.order - b.order);
551
+ return { ...section, fields: reorderedFields };
552
+ }
553
+ return section;
554
+ });
555
+ ctx.patchState({
556
+ formConfiguration: {
557
+ ...state.formConfiguration,
558
+ sections: optimisticSections,
559
+ },
560
+ });
561
+ return handleApiRequest({
562
+ ctx,
563
+ key: FormBuilderActionKey.ReorderFields,
564
+ request$: req$,
565
+ onSuccess: (res, currentState) => {
566
+ const updatedFields = res.data.fields;
567
+ const fieldMap = new Map(updatedFields.map((f) => [f.id, f]));
568
+ const sections = (currentState.formConfiguration?.sections ?? []).map((section) => {
569
+ if (section.id === action.sectionId) {
570
+ const mergedFields = section.fields
571
+ .map((f) => fieldMap.get(f.id) ?? f)
572
+ .sort((a, b) => a.order - b.order);
573
+ return { ...section, fields: mergedFields };
574
+ }
575
+ return section;
576
+ });
577
+ return {
578
+ formConfiguration: {
579
+ ...currentState.formConfiguration,
580
+ sections,
581
+ },
582
+ };
583
+ },
584
+ onError: (_error, _currentState) => {
585
+ // Revert to original state on error
586
+ return {
587
+ formConfiguration: state.formConfiguration,
588
+ };
589
+ },
590
+ });
591
+ }
592
+ moveField(ctx, action) {
593
+ const state = ctx.getState();
594
+ const apiPath = `${this.getApiPath(state)}/sections/${action.sectionId}/fields/${action.fieldId}/move`;
595
+ const req$ = this.http.put(apiPath, action.payload);
596
+ // Apply optimistic update - move field immediately
597
+ const sourceSectionId = action.sectionId;
598
+ const targetSectionId = action.payload.targetSectionId;
599
+ const targetOrder = action.payload.order ?? 0;
600
+ let movedFieldData = null;
601
+ const optimisticSections = (state.formConfiguration?.sections ?? []).map((section) => {
602
+ if (section.id === sourceSectionId) {
603
+ const fieldToMove = section.fields.find((f) => f.id === action.fieldId);
604
+ if (fieldToMove) {
605
+ movedFieldData = { ...fieldToMove, order: targetOrder };
606
+ }
607
+ return {
608
+ ...section,
609
+ fields: section.fields.filter((f) => f.id !== action.fieldId),
610
+ };
611
+ }
612
+ return section;
613
+ });
614
+ // Add to target section
615
+ const sectionsWithMoved = optimisticSections.map((section) => {
616
+ if (section.id === targetSectionId && movedFieldData) {
617
+ const fields = [...section.fields];
618
+ fields.splice(targetOrder, 0, movedFieldData);
619
+ return { ...section, fields };
620
+ }
621
+ return section;
622
+ });
623
+ ctx.patchState({
624
+ formConfiguration: {
625
+ ...state.formConfiguration,
626
+ sections: sectionsWithMoved,
627
+ },
628
+ });
629
+ return handleApiRequest({
630
+ ctx,
631
+ key: FormBuilderActionKey.MoveField,
632
+ request$: req$,
633
+ onSuccess: (res, currentState) => {
634
+ const movedField = res.data;
635
+ const sections = (currentState.formConfiguration?.sections ?? []).map((section) => {
636
+ // Update the moved field with server response
637
+ if (section.id === targetSectionId) {
638
+ return {
639
+ ...section,
640
+ fields: section.fields
641
+ .map((f) => (f.id === movedField.id ? movedField : f))
642
+ .sort((a, b) => a.order - b.order),
643
+ };
644
+ }
645
+ return section;
646
+ });
647
+ return {
648
+ formConfiguration: {
649
+ ...currentState.formConfiguration,
650
+ sections,
651
+ },
652
+ };
653
+ },
654
+ onError: (_error, _currentState) => {
655
+ // Revert optimistic update on error
656
+ return {
657
+ formConfiguration: state.formConfiguration,
658
+ };
659
+ },
660
+ });
661
+ }
662
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilderState, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
663
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilderState });
664
+ };
665
+ __decorate([
666
+ Action(SetModuleInfo)
667
+ ], FormBuilderState.prototype, "setModuleInfo", null);
668
+ __decorate([
669
+ Action(ResetFormBuilderState)
670
+ ], FormBuilderState.prototype, "resetState", null);
671
+ __decorate([
672
+ Action(SetProperties)
673
+ ], FormBuilderState.prototype, "setProperties", null);
674
+ __decorate([
675
+ Action(GetFormConfiguration)
676
+ ], FormBuilderState.prototype, "getFormConfiguration", null);
677
+ __decorate([
678
+ Action(ResetFormConfiguration)
679
+ ], FormBuilderState.prototype, "resetFormConfiguration", null);
680
+ __decorate([
681
+ Action(AddSection)
682
+ ], FormBuilderState.prototype, "addSection", null);
683
+ __decorate([
684
+ Action(UpdateSection)
685
+ ], FormBuilderState.prototype, "updateSection", null);
686
+ __decorate([
687
+ Action(DeleteSection)
688
+ ], FormBuilderState.prototype, "deleteSection", null);
689
+ __decorate([
690
+ Action(AddField)
691
+ ], FormBuilderState.prototype, "addField", null);
692
+ __decorate([
693
+ Action(UpdateField)
694
+ ], FormBuilderState.prototype, "updateField", null);
695
+ __decorate([
696
+ Action(DeleteField)
697
+ ], FormBuilderState.prototype, "deleteField", null);
698
+ __decorate([
699
+ Action(ReorderFields)
700
+ ], FormBuilderState.prototype, "reorderFields", null);
701
+ __decorate([
702
+ Action(MoveField)
703
+ ], FormBuilderState.prototype, "moveField", null);
704
+ __decorate([
705
+ Selector()
706
+ ], FormBuilderState, "getState", null);
707
+ __decorate([
708
+ Selector()
709
+ ], FormBuilderState, "getFormConfiguration", null);
710
+ __decorate([
711
+ Selector()
712
+ ], FormBuilderState, "getSections", null);
713
+ __decorate([
714
+ Selector()
715
+ ], FormBuilderState, "getModuleType", null);
716
+ __decorate([
717
+ Selector()
718
+ ], FormBuilderState, "getModuleId", null);
719
+ __decorate([
720
+ Selector()
721
+ ], FormBuilderState, "getProperties", null);
722
+ FormBuilderState = __decorate([
723
+ State({
724
+ name: 'formBuilder',
725
+ defaults: DEFAULT_STATE,
726
+ })
727
+ ], FormBuilderState);
728
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilderState, decorators: [{
729
+ type: Injectable
730
+ }], propDecorators: { setModuleInfo: [], resetState: [], setProperties: [], getFormConfiguration: [], resetFormConfiguration: [], addSection: [], updateSection: [], deleteSection: [], addField: [], updateField: [], deleteField: [], reorderFields: [], moveField: [] } });
731
+
732
+ class FormBuilderFacade {
733
+ store = inject(Store);
734
+ // ============================================================================
735
+ // State Selectors
736
+ // ============================================================================
737
+ stateSignal = select(FormBuilderState.getState);
738
+ formConfiguration = select(FormBuilderState.getFormConfiguration);
739
+ sections = select(FormBuilderState.getSections);
740
+ properties = select(FormBuilderState.getProperties);
741
+ moduleType = select(FormBuilderState.getModuleType);
742
+ moduleId = select(FormBuilderState.getModuleId);
743
+ // ============================================================================
744
+ // Loading Signals
745
+ // ============================================================================
746
+ isLoadingFormConfiguration = computed(() => this.stateSignal().loadingActive.includes(FormBuilderActionKey.GetFormConfiguration), ...(ngDevMode ? [{ debugName: "isLoadingFormConfiguration" }] : []));
747
+ isResettingFormConfiguration = computed(() => this.stateSignal().loadingActive.includes(FormBuilderActionKey.ResetFormConfiguration), ...(ngDevMode ? [{ debugName: "isResettingFormConfiguration" }] : []));
748
+ isAddingSection = computed(() => this.stateSignal().loadingActive.includes(FormBuilderActionKey.AddSection), ...(ngDevMode ? [{ debugName: "isAddingSection" }] : []));
749
+ isUpdatingSection = computed(() => this.stateSignal().loadingActive.includes(FormBuilderActionKey.UpdateSection), ...(ngDevMode ? [{ debugName: "isUpdatingSection" }] : []));
750
+ isDeletingSection = computed(() => this.stateSignal().loadingActive.includes(FormBuilderActionKey.DeleteSection), ...(ngDevMode ? [{ debugName: "isDeletingSection" }] : []));
751
+ isAddingField = computed(() => this.stateSignal().loadingActive.includes(FormBuilderActionKey.AddField), ...(ngDevMode ? [{ debugName: "isAddingField" }] : []));
752
+ isUpdatingField = computed(() => this.stateSignal().loadingActive.includes(FormBuilderActionKey.UpdateField), ...(ngDevMode ? [{ debugName: "isUpdatingField" }] : []));
753
+ isDeletingField = computed(() => this.stateSignal().loadingActive.includes(FormBuilderActionKey.DeleteField), ...(ngDevMode ? [{ debugName: "isDeletingField" }] : []));
754
+ isMovingField = computed(() => this.stateSignal().loadingActive.includes(FormBuilderActionKey.MoveField), ...(ngDevMode ? [{ debugName: "isMovingField" }] : []));
755
+ // ============================================================================
756
+ // Error Signals
757
+ // ============================================================================
758
+ formConfigurationError = computed(() => this.stateSignal().errors[FormBuilderActionKey.GetFormConfiguration] ??
759
+ null, ...(ngDevMode ? [{ debugName: "formConfigurationError" }] : []));
760
+ sectionError = computed(() => {
761
+ const errors = this.stateSignal().errors;
762
+ return (errors[FormBuilderActionKey.AddSection] ??
763
+ errors[FormBuilderActionKey.UpdateSection] ??
764
+ errors[FormBuilderActionKey.DeleteSection] ??
765
+ null);
766
+ }, ...(ngDevMode ? [{ debugName: "sectionError" }] : []));
767
+ fieldError = computed(() => {
768
+ const errors = this.stateSignal().errors;
769
+ return (errors[FormBuilderActionKey.AddField] ??
770
+ errors[FormBuilderActionKey.UpdateField] ??
771
+ errors[FormBuilderActionKey.DeleteField] ??
772
+ errors[FormBuilderActionKey.MoveField] ??
773
+ null);
774
+ }, ...(ngDevMode ? [{ debugName: "fieldError" }] : []));
775
+ // ============================================================================
776
+ // Module Configuration Dispatchers
777
+ // ============================================================================
778
+ setModuleInfo(moduleType, moduleId, parentModuleType, parentModuleId, parentPath) {
779
+ return this.store.dispatch(new SetModuleInfo(moduleType, moduleId, parentModuleType, parentModuleId, parentPath));
780
+ }
781
+ resetState() {
782
+ return this.store.dispatch(new ResetFormBuilderState());
783
+ }
784
+ setProperties(properties) {
785
+ return this.store.dispatch(new SetProperties(properties));
786
+ }
787
+ // ============================================================================
788
+ // Form Configuration Dispatchers
789
+ // ============================================================================
790
+ getFormConfiguration() {
791
+ return this.store.dispatch(new GetFormConfiguration());
792
+ }
793
+ resetFormConfiguration() {
794
+ return this.store.dispatch(new ResetFormConfiguration());
795
+ }
796
+ // ============================================================================
797
+ // Section Dispatchers
798
+ // ============================================================================
799
+ addSection(payload) {
800
+ return this.store.dispatch(new AddSection(payload));
801
+ }
802
+ updateSection(sectionId, payload) {
803
+ return this.store.dispatch(new UpdateSection(sectionId, payload));
804
+ }
805
+ deleteSection(sectionId) {
806
+ return this.store.dispatch(new DeleteSection(sectionId));
807
+ }
808
+ // ============================================================================
809
+ // Field Dispatchers
810
+ // ============================================================================
811
+ addField(sectionId, payload) {
812
+ return this.store.dispatch(new AddField(sectionId, payload));
813
+ }
814
+ updateField(sectionId, fieldId, payload) {
815
+ return this.store.dispatch(new UpdateField(sectionId, fieldId, payload));
816
+ }
817
+ deleteField(sectionId, fieldId) {
818
+ return this.store.dispatch(new DeleteField(sectionId, fieldId));
819
+ }
820
+ reorderFields(sectionId, payload) {
821
+ return this.store.dispatch(new ReorderFields(sectionId, payload));
822
+ }
823
+ moveField(sectionId, fieldId, payload) {
824
+ return this.store.dispatch(new MoveField(sectionId, fieldId, payload));
825
+ }
826
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilderFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
827
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilderFacade, providedIn: 'root' });
828
+ }
829
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilderFacade, decorators: [{
830
+ type: Injectable,
831
+ args: [{ providedIn: 'root' }]
832
+ }] });
833
+
834
+ /**
835
+ * Condition Formula Constants
836
+ * Static functions and operators for condition formulas (Show/Hide/Enable/Disable)
837
+ */
838
+ /**
839
+ * Functions for field conditions
840
+ */
841
+ const CONDITION_FUNCTION_CATEGORIES = [
842
+ {
843
+ name: 'Logic',
844
+ displayName: 'Field Visibility',
845
+ functions: [
846
+ {
847
+ name: 'SHOW_IF',
848
+ category: 'Logic',
849
+ description: 'Show this field when condition is true',
850
+ signature: 'SHOW_IF(condition)',
851
+ parameters: [
852
+ {
853
+ name: 'condition',
854
+ type: 'boolean',
855
+ description: 'When true, field is visible',
856
+ required: true,
857
+ },
858
+ ],
859
+ returnType: 'boolean',
860
+ examples: [
861
+ 'SHOW_IF(@Status == "Active")',
862
+ 'SHOW_IF(@Type == "Custom")',
863
+ ],
864
+ },
865
+ {
866
+ name: 'HIDE_IF',
867
+ category: 'Logic',
868
+ description: 'Hide this field when condition is true',
869
+ signature: 'HIDE_IF(condition)',
870
+ parameters: [
871
+ {
872
+ name: 'condition',
873
+ type: 'boolean',
874
+ description: 'When true, field is hidden',
875
+ required: true,
876
+ },
877
+ ],
878
+ returnType: 'boolean',
879
+ examples: ['HIDE_IF(@Status == "Closed")', 'HIDE_IF(ISNULL(@Parent))'],
880
+ },
881
+ {
882
+ name: 'DISABLE_IF',
883
+ category: 'Logic',
884
+ description: 'Disable this field when condition is true',
885
+ signature: 'DISABLE_IF(condition)',
886
+ parameters: [
887
+ {
888
+ name: 'condition',
889
+ type: 'boolean',
890
+ description: 'When true, field is disabled',
891
+ required: true,
892
+ },
893
+ ],
894
+ returnType: 'boolean',
895
+ examples: [
896
+ 'DISABLE_IF(@IsLocked == true)',
897
+ 'DISABLE_IF(@Status == "Approved")',
898
+ ],
899
+ },
900
+ {
901
+ name: 'ENABLE_IF',
902
+ category: 'Logic',
903
+ description: 'Enable this field when condition is true',
904
+ signature: 'ENABLE_IF(condition)',
905
+ parameters: [
906
+ {
907
+ name: 'condition',
908
+ type: 'boolean',
909
+ description: 'When true, field is enabled',
910
+ required: true,
911
+ },
912
+ ],
913
+ returnType: 'boolean',
914
+ examples: [
915
+ 'ENABLE_IF(@Status == "Draft")',
916
+ 'ENABLE_IF(@CanEdit == true)',
917
+ ],
918
+ },
919
+ ],
920
+ },
921
+ {
922
+ name: 'Aggregation',
923
+ displayName: 'Logic Helpers',
924
+ functions: [
925
+ {
926
+ name: 'AND',
927
+ category: 'Aggregation',
928
+ description: 'Returns true if all conditions are true',
929
+ signature: 'AND(condition1, condition2)',
930
+ parameters: [
931
+ {
932
+ name: 'condition1',
933
+ type: 'boolean',
934
+ description: 'First condition',
935
+ required: true,
936
+ },
937
+ {
938
+ name: 'condition2',
939
+ type: 'boolean',
940
+ description: 'Second condition',
941
+ required: true,
942
+ },
943
+ ],
944
+ returnType: 'boolean',
945
+ examples: ['AND(@Status == "Active", @Priority > 0)'],
946
+ },
947
+ {
948
+ name: 'OR',
949
+ category: 'Aggregation',
950
+ description: 'Returns true if any condition is true',
951
+ signature: 'OR(condition1, condition2)',
952
+ parameters: [
953
+ {
954
+ name: 'condition1',
955
+ type: 'boolean',
956
+ description: 'First condition',
957
+ required: true,
958
+ },
959
+ {
960
+ name: 'condition2',
961
+ type: 'boolean',
962
+ description: 'Second condition',
963
+ required: true,
964
+ },
965
+ ],
966
+ returnType: 'boolean',
967
+ examples: ['OR(@Status == "Active", @Status == "Pending")'],
968
+ },
969
+ {
970
+ name: 'NOT',
971
+ category: 'Aggregation',
972
+ description: 'Returns the opposite of the condition',
973
+ signature: 'NOT(condition)',
974
+ parameters: [
975
+ {
976
+ name: 'condition',
977
+ type: 'boolean',
978
+ description: 'The condition to negate',
979
+ required: true,
980
+ },
981
+ ],
982
+ returnType: 'boolean',
983
+ examples: ['NOT(@IsCompleted)'],
984
+ },
985
+ {
986
+ name: 'ISNULL',
987
+ category: 'Aggregation',
988
+ description: 'Returns true if value is null or empty',
989
+ signature: 'ISNULL(value)',
990
+ parameters: [
991
+ {
992
+ name: 'value',
993
+ type: 'any',
994
+ description: 'The value to check',
995
+ required: true,
996
+ },
997
+ ],
998
+ returnType: 'boolean',
999
+ examples: ['ISNULL(@Description)'],
1000
+ },
1001
+ {
1002
+ name: 'CONTAINS',
1003
+ category: 'Aggregation',
1004
+ description: 'Returns true if text contains the search string',
1005
+ signature: 'CONTAINS(text, search)',
1006
+ parameters: [
1007
+ {
1008
+ name: 'text',
1009
+ type: 'string',
1010
+ description: 'The text to search in',
1011
+ required: true,
1012
+ },
1013
+ {
1014
+ name: 'search',
1015
+ type: 'string',
1016
+ description: 'The string to search for',
1017
+ required: true,
1018
+ },
1019
+ ],
1020
+ returnType: 'boolean',
1021
+ examples: ['CONTAINS(@Name, "Project")'],
1022
+ },
1023
+ ],
1024
+ },
1025
+ ];
1026
+ /**
1027
+ * Operators for conditions
1028
+ */
1029
+ const CONDITION_OPERATORS = [
1030
+ // Comparison
1031
+ {
1032
+ symbol: '==',
1033
+ name: 'Equal',
1034
+ type: 'comparison',
1035
+ description: 'Equal to',
1036
+ precedence: 3,
1037
+ },
1038
+ {
1039
+ symbol: '!=',
1040
+ name: 'Not Equal',
1041
+ type: 'comparison',
1042
+ description: 'Not equal to',
1043
+ precedence: 3,
1044
+ },
1045
+ {
1046
+ symbol: '>',
1047
+ name: 'Greater Than',
1048
+ type: 'comparison',
1049
+ description: 'Greater than',
1050
+ precedence: 4,
1051
+ },
1052
+ {
1053
+ symbol: '<',
1054
+ name: 'Less Than',
1055
+ type: 'comparison',
1056
+ description: 'Less than',
1057
+ precedence: 4,
1058
+ },
1059
+ {
1060
+ symbol: '>=',
1061
+ name: 'Greater or Equal',
1062
+ type: 'comparison',
1063
+ description: 'Greater than or equal',
1064
+ precedence: 4,
1065
+ },
1066
+ {
1067
+ symbol: '<=',
1068
+ name: 'Less or Equal',
1069
+ type: 'comparison',
1070
+ description: 'Less than or equal',
1071
+ precedence: 4,
1072
+ },
1073
+ // Logical
1074
+ {
1075
+ symbol: '&&',
1076
+ name: 'And',
1077
+ type: 'logical',
1078
+ description: 'Logical AND',
1079
+ precedence: 2,
1080
+ },
1081
+ {
1082
+ symbol: '||',
1083
+ name: 'Or',
1084
+ type: 'logical',
1085
+ description: 'Logical OR',
1086
+ precedence: 1,
1087
+ },
1088
+ ];
1089
+
1090
+ /**
1091
+ * Field Conditions Dialog
1092
+ *
1093
+ * Allows users to define conditional display formulas for form fields.
1094
+ * Uses FormulaToolbar + FormulaEditor directly for a visual formula editing experience.
1095
+ */
1096
+ class FBFieldConditions {
1097
+ modalService = inject(ModalService);
1098
+ ref = inject(ModalRef);
1099
+ // Inputs
1100
+ /** Initial formula (JSON string from backend) */
1101
+ initialFormula = input('', ...(ngDevMode ? [{ debugName: "initialFormula" }] : []));
1102
+ /** Available fields from the form builder (other fields that can be referenced) */
1103
+ availableFields = input([], ...(ngDevMode ? [{ debugName: "availableFields" }] : []));
1104
+ // UI State
1105
+ submitting = signal(false, ...(ngDevMode ? [{ debugName: "submitting" }] : []));
1106
+ // Form control for the formula editor (tokens array)
1107
+ formulaControl = new FormControl([]);
1108
+ // View child for the editor
1109
+ editorRef = viewChild('editor', ...(ngDevMode ? [{ debugName: "editorRef" }] : []));
1110
+ // Condition-specific functions and operators
1111
+ functionCategories = CONDITION_FUNCTION_CATEGORIES;
1112
+ operators = CONDITION_OPERATORS;
1113
+ /** Extract property keys for toolbar */
1114
+ propertyKeys = computed(() => this.availableFields().map((f) => f.key), ...(ngDevMode ? [{ debugName: "propertyKeys" }] : []));
1115
+ /** Toolbar labels */
1116
+ toolbarLabels = {
1117
+ functions: 'Functions',
1118
+ properties: 'Fields',
1119
+ operators: 'Operators',
1120
+ noPropertiesAvailable: 'No fields available',
1121
+ };
1122
+ ngOnInit() {
1123
+ // Parse JSON string to tokens
1124
+ const formula = this.initialFormula();
1125
+ if (formula) {
1126
+ try {
1127
+ const tokens = JSON.parse(formula);
1128
+ this.formulaControl.patchValue(tokens);
1129
+ }
1130
+ catch {
1131
+ // Invalid JSON, start with empty
1132
+ }
1133
+ }
1134
+ }
1135
+ /** Handle block insert from toolbar */
1136
+ onBlockInsert(block) {
1137
+ const editor = this.editorRef();
1138
+ if (editor) {
1139
+ editor.addBlock(block);
1140
+ }
1141
+ }
1142
+ onSave() {
1143
+ const tokens = this.formulaControl.value ?? [];
1144
+ const formula = tokens.length > 0 ? JSON.stringify(tokens) : '';
1145
+ this.ref.close({ saved: true, conditionalDisplayFormula: formula });
1146
+ }
1147
+ onCancel() {
1148
+ this.ref.close({ saved: false });
1149
+ }
1150
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBFieldConditions, deps: [], target: i0.ɵɵFactoryTarget.Component });
1151
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.0.3", type: FBFieldConditions, isStandalone: true, selector: "mt-fb-field-conditions", inputs: { initialFormula: { classPropertyName: "initialFormula", publicName: "initialFormula", isSignal: true, isRequired: false, transformFunction: null }, availableFields: { classPropertyName: "availableFields", publicName: "availableFields", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["editor"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-4 h-full overflow-y-auto pb-10 flex flex-col gap-3\">\r\n <!-- Formula Toolbar -->\r\n <mt-formula-toolbar\r\n [knownProperties]=\"propertyKeys()\"\r\n [functionCategories]=\"functionCategories\"\r\n [operators]=\"operators\"\r\n [labels]=\"toolbarLabels\"\r\n (onBlockInsert)=\"onBlockInsert($event)\"\r\n />\r\n <!-- Formula Editor -->\r\n <mt-formula-editor\r\n #editor\r\n [placeholder]=\"t('build-condition-formula')\"\r\n [formControl]=\"formulaControl\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modalService.footerClass\">\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n severity=\"secondary\"\r\n [disabled]=\"submitting()\"\r\n (onClick)=\"onCancel()\"\r\n />\r\n <mt-button\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n (onClick)=\"onSave()\"\r\n />\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: FormulaToolbar, selector: "mt-formula-toolbar", inputs: ["knownProperties", "functionCategories", "operators", "initialTab", "searchPlaceholder", "labels"], outputs: ["onBlockInsert", "onTabChange"] }, { kind: "component", type: FormulaEditor, selector: "mt-formula-editor", inputs: ["placeholder", "initialTokens", "disabled"], outputs: ["formulaChange", "tokensChange", "onBlur", "onFocus"] }] });
1152
+ }
1153
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBFieldConditions, decorators: [{
1154
+ type: Component,
1155
+ args: [{ selector: 'mt-fb-field-conditions', standalone: true, imports: [
1156
+ TranslocoDirective,
1157
+ ReactiveFormsModule,
1158
+ Button,
1159
+ FormulaToolbar,
1160
+ FormulaEditor,
1161
+ ], template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-4 h-full overflow-y-auto pb-10 flex flex-col gap-3\">\r\n <!-- Formula Toolbar -->\r\n <mt-formula-toolbar\r\n [knownProperties]=\"propertyKeys()\"\r\n [functionCategories]=\"functionCategories\"\r\n [operators]=\"operators\"\r\n [labels]=\"toolbarLabels\"\r\n (onBlockInsert)=\"onBlockInsert($event)\"\r\n />\r\n <!-- Formula Editor -->\r\n <mt-formula-editor\r\n #editor\r\n [placeholder]=\"t('build-condition-formula')\"\r\n [formControl]=\"formulaControl\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modalService.footerClass\">\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n severity=\"secondary\"\r\n [disabled]=\"submitting()\"\r\n (onClick)=\"onCancel()\"\r\n />\r\n <mt-button\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n (onClick)=\"onSave()\"\r\n />\r\n </div>\r\n</ng-container>\r\n" }]
1162
+ }], propDecorators: { initialFormula: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialFormula", required: false }] }], availableFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableFields", required: false }] }], editorRef: [{ type: i0.ViewChild, args: ['editor', { isSignal: true }] }] } });
1163
+
1164
+ class FBFieldForm {
1165
+ transloco = inject(TranslocoService);
1166
+ modalService = inject(ModalService);
1167
+ ref = inject(ModalRef);
1168
+ confirmationService = inject(ConfirmationService);
1169
+ facade = inject(FormBuilderFacade);
1170
+ // Inputs
1171
+ sectionId = input('', ...(ngDevMode ? [{ debugName: "sectionId" }] : []));
1172
+ initialData = input(null, ...(ngDevMode ? [{ debugName: "initialData" }] : []));
1173
+ /** All sections with enriched fields (to get available fields for conditions) */
1174
+ allSections = input([], ...(ngDevMode ? [{ debugName: "allSections" }] : []));
1175
+ // UI State
1176
+ submitting = signal(false, ...(ngDevMode ? [{ debugName: "submitting" }] : []));
1177
+ deleting = signal(false, ...(ngDevMode ? [{ debugName: "deleting" }] : []));
1178
+ conditionalDisplayFormula = signal('', ...(ngDevMode ? [{ debugName: "conditionalDisplayFormula" }] : []));
1179
+ conditionsDialogRef;
1180
+ /**
1181
+ * Compute available fields for condition formula
1182
+ * Excludes the current field being edited
1183
+ */
1184
+ availableFields = computed(() => {
1185
+ const sections = this.allSections();
1186
+ const currentField = this.initialData();
1187
+ const fields = [];
1188
+ for (const section of sections) {
1189
+ for (const field of section.fields) {
1190
+ // Exclude the current field being edited
1191
+ if (currentField && field.id === currentField.id)
1192
+ continue;
1193
+ fields.push({
1194
+ key: field.name,
1195
+ name: field.name,
1196
+ type: field.type,
1197
+ });
1198
+ }
1199
+ }
1200
+ return fields;
1201
+ }, ...(ngDevMode ? [{ debugName: "availableFields" }] : []));
1202
+ // Form
1203
+ formControl = new FormControl();
1204
+ showHideControl = new FormControl(false);
1205
+ formConfig = {
1206
+ sections: [
1207
+ {
1208
+ key: 'section-form',
1209
+ type: 'none',
1210
+ columns: 12,
1211
+ order: 1,
1212
+ fields: [
1213
+ {
1214
+ key: 'isRequired',
1215
+ label: this.transloco.translate('formBuilder.is-required'),
1216
+ type: 'toggle',
1217
+ toggleShape: 'card',
1218
+ colSpan: 12,
1219
+ order: 1,
1220
+ },
1221
+ {
1222
+ key: 'hiddenInCreation',
1223
+ label: this.transloco.translate('formBuilder.hidden-in-creation'),
1224
+ type: 'toggle',
1225
+ toggleShape: 'card',
1226
+ colSpan: 12,
1227
+ order: 2,
1228
+ },
1229
+ {
1230
+ key: 'hiddenInEditForm',
1231
+ label: this.transloco.translate('formBuilder.hidden-in-edit-form'),
1232
+ type: 'toggle',
1233
+ toggleShape: 'card',
1234
+ colSpan: 12,
1235
+ order: 3,
1236
+ },
1237
+ new RadioCardsFieldConfig({
1238
+ key: 'size',
1239
+ label: this.transloco.translate('formBuilder.size'),
1240
+ placeholder: this.transloco.translate('formBuilder.size'),
1241
+ options: [
1242
+ { id: 's', name: 'S' },
1243
+ { id: 'm', name: 'M' },
1244
+ { id: 'l', name: 'L' },
1245
+ ],
1246
+ order: 4,
1247
+ size: 'small',
1248
+ }),
1249
+ ],
1250
+ },
1251
+ ],
1252
+ };
1253
+ constructor() {
1254
+ effect(() => {
1255
+ const data = this.initialData();
1256
+ if (data) {
1257
+ const widthToSize = {
1258
+ '25': 's',
1259
+ '50': 'm',
1260
+ '100': 'l',
1261
+ };
1262
+ this.formControl.patchValue({
1263
+ isRequired: data.isRequired ?? false,
1264
+ hiddenInCreation: data.hiddenInCreation ?? false,
1265
+ hiddenInEditForm: data.hiddenInEditForm ?? false,
1266
+ size: widthToSize[data.width] ?? 'l',
1267
+ });
1268
+ // Set show/hide toggle based on existing value
1269
+ this.showHideControl.patchValue(data.showConditionalDisplayFormula ?? false);
1270
+ this.conditionalDisplayFormula.set(data.conditionalDisplayFormula ?? '');
1271
+ }
1272
+ });
1273
+ }
1274
+ onSave() {
1275
+ if (this.formControl.invalid)
1276
+ return;
1277
+ const formValue = this.formControl.value;
1278
+ const field = this.initialData();
1279
+ const sectionId = this.sectionId();
1280
+ if (!field || !sectionId)
1281
+ return;
1282
+ const widthMap = {
1283
+ s: '25',
1284
+ m: '50',
1285
+ l: '100',
1286
+ };
1287
+ const payload = {
1288
+ width: widthMap[formValue.size] ?? '100',
1289
+ hiddenInCreation: formValue.hiddenInCreation ?? false,
1290
+ hiddenInEditForm: formValue.hiddenInEditForm ?? false,
1291
+ isRequired: formValue.isRequired ?? false,
1292
+ showConditionalDisplayFormula: this.showHideControl.value ?? false,
1293
+ conditionalDisplayFormula: this.showHideControl.value
1294
+ ? this.conditionalDisplayFormula()
1295
+ : null,
1296
+ };
1297
+ this.submitting.set(true);
1298
+ this.facade.updateField(sectionId, field.id, payload).subscribe({
1299
+ next: () => this.ref.close(true),
1300
+ error: () => this.submitting.set(false),
1301
+ });
1302
+ }
1303
+ onCancel() {
1304
+ this.ref.close(false);
1305
+ }
1306
+ onSetConditions() {
1307
+ this.conditionsDialogRef = this.modalService.openModal(FBFieldConditions, 'drawer', {
1308
+ header: this.transloco.translate('formBuilder.set-conditions'),
1309
+ styleClass: '!w-[calc(100%-25rem)] !absolute ',
1310
+ position: 'start',
1311
+ modal: true,
1312
+ dismissible: true,
1313
+ appendTo: '#page-content',
1314
+ inputValues: {
1315
+ initialFormula: this.conditionalDisplayFormula(),
1316
+ availableFields: this.availableFields(),
1317
+ },
1318
+ });
1319
+ this.conditionsDialogRef.onClose.subscribe((result) => {
1320
+ if (result?.saved) {
1321
+ this.conditionalDisplayFormula.set(result.conditionalDisplayFormula ?? '');
1322
+ }
1323
+ });
1324
+ }
1325
+ onDelete(event) {
1326
+ const field = this.initialData();
1327
+ const sectionId = this.sectionId();
1328
+ if (!field || !sectionId)
1329
+ return;
1330
+ this.confirmationService.confirmDelete({
1331
+ event,
1332
+ type: 'popup',
1333
+ accept: () => {
1334
+ this.deleting.set(true);
1335
+ this.facade.deleteField(sectionId, field.id).subscribe({
1336
+ next: () => this.ref.close(true),
1337
+ error: () => this.deleting.set(false),
1338
+ });
1339
+ },
1340
+ });
1341
+ }
1342
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBFieldForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
1343
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: FBFieldForm, isStandalone: true, selector: "mt-fb-field-form", inputs: { sectionId: { classPropertyName: "sectionId", publicName: "sectionId", isSignal: true, isRequired: false, transformFunction: null }, initialData: { classPropertyName: "initialData", publicName: "initialData", isSignal: true, isRequired: false, transformFunction: null }, allSections: { classPropertyName: "allSections", publicName: "allSections", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-4 h-full overflow-y-auto pb-10 flex flex-col gap-4\">\r\n <mt-dynamic-form [formConfig]=\"formConfig\" [formControl]=\"formControl\">\r\n </mt-dynamic-form>\r\n\r\n <!-- Show/Hide Toggle with Set Conditions -->\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-hide')\"\r\n [descriptionCard]=\"t('show-hide-description')\"\r\n icon=\"general.eye\"\r\n [formControl]=\"showHideControl\"\r\n >\r\n <ng-template #toggleCardBottom>\r\n @if (showHideControl.value) {\r\n <div class=\"mt-3\">\r\n <mt-button\r\n [label]=\"t('set-conditions')\"\r\n size=\"small\"\r\n (onClick)=\"onSetConditions()\"\r\n ></mt-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </mt-toggle-field>\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modalService.footerClass\">\r\n @if (initialData()) {\r\n <mt-button\r\n [tooltip]=\"t('delete')\"\r\n severity=\"danger\"\r\n outlined\r\n icon=\"general.trash-01\"\r\n [loading]=\"deleting()\"\r\n [disabled]=\"submitting()\"\r\n (onClick)=\"onDelete($event)\"\r\n class=\"me-auto\"\r\n ></mt-button>\r\n }\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n severity=\"secondary\"\r\n [disabled]=\"submitting() || deleting()\"\r\n (onClick)=\"onCancel()\"\r\n >\r\n </mt-button>\r\n <mt-button\r\n [disabled]=\"!formControl.valid || deleting()\"\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n (onClick)=\"onSave()\"\r\n >\r\n </mt-button>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }] });
1344
+ }
1345
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBFieldForm, decorators: [{
1346
+ type: Component,
1347
+ args: [{ selector: 'mt-fb-field-form', standalone: true, imports: [
1348
+ TranslocoDirective,
1349
+ ReactiveFormsModule,
1350
+ DynamicForm,
1351
+ Button,
1352
+ ToggleField,
1353
+ ], template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-4 h-full overflow-y-auto pb-10 flex flex-col gap-4\">\r\n <mt-dynamic-form [formConfig]=\"formConfig\" [formControl]=\"formControl\">\r\n </mt-dynamic-form>\r\n\r\n <!-- Show/Hide Toggle with Set Conditions -->\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-hide')\"\r\n [descriptionCard]=\"t('show-hide-description')\"\r\n icon=\"general.eye\"\r\n [formControl]=\"showHideControl\"\r\n >\r\n <ng-template #toggleCardBottom>\r\n @if (showHideControl.value) {\r\n <div class=\"mt-3\">\r\n <mt-button\r\n [label]=\"t('set-conditions')\"\r\n size=\"small\"\r\n (onClick)=\"onSetConditions()\"\r\n ></mt-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </mt-toggle-field>\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modalService.footerClass\">\r\n @if (initialData()) {\r\n <mt-button\r\n [tooltip]=\"t('delete')\"\r\n severity=\"danger\"\r\n outlined\r\n icon=\"general.trash-01\"\r\n [loading]=\"deleting()\"\r\n [disabled]=\"submitting()\"\r\n (onClick)=\"onDelete($event)\"\r\n class=\"me-auto\"\r\n ></mt-button>\r\n }\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n severity=\"secondary\"\r\n [disabled]=\"submitting() || deleting()\"\r\n (onClick)=\"onCancel()\"\r\n >\r\n </mt-button>\r\n <mt-button\r\n [disabled]=\"!formControl.valid || deleting()\"\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n (onClick)=\"onSave()\"\r\n >\r\n </mt-button>\r\n </div>\r\n</ng-container>\r\n" }]
1354
+ }], ctorParameters: () => [], propDecorators: { sectionId: [{ type: i0.Input, args: [{ isSignal: true, alias: "sectionId", required: false }] }], initialData: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialData", required: false }] }], allSections: [{ type: i0.Input, args: [{ isSignal: true, alias: "allSections", required: false }] }] } });
1355
+
1356
+ class FBSectionForm {
1357
+ transloco = inject(TranslocoService);
1358
+ modalService = inject(ModalService);
1359
+ ref = inject(ModalRef);
1360
+ confirmationService = inject(ConfirmationService);
1361
+ facade = inject(FormBuilderFacade);
1362
+ // Inputs
1363
+ sectionId = input(null, ...(ngDevMode ? [{ debugName: "sectionId" }] : []));
1364
+ initialData = input(null, ...(ngDevMode ? [{ debugName: "initialData" }] : []));
1365
+ sectionsCount = input(0, ...(ngDevMode ? [{ debugName: "sectionsCount" }] : []));
1366
+ // UI State
1367
+ submitting = signal(false, ...(ngDevMode ? [{ debugName: "submitting" }] : []));
1368
+ deleting = signal(false, ...(ngDevMode ? [{ debugName: "deleting" }] : []));
1369
+ // Form
1370
+ formControl = new FormControl();
1371
+ formConfig = {
1372
+ sections: [
1373
+ {
1374
+ key: 'section-form',
1375
+ type: 'none',
1376
+ columns: 12,
1377
+ order: 1,
1378
+ fields: [
1379
+ {
1380
+ key: 'name-ar',
1381
+ label: this.transloco.translate('formBuilder.name-ar'),
1382
+ type: 'text',
1383
+ placeholder: this.transloco.translate('formBuilder.name-ar'),
1384
+ colSpan: 12,
1385
+ order: 1,
1386
+ },
1387
+ {
1388
+ key: 'name-en',
1389
+ label: this.transloco.translate('formBuilder.name-en'),
1390
+ type: 'text',
1391
+ placeholder: this.transloco.translate('formBuilder.name-en'),
1392
+ colSpan: 12,
1393
+ order: 2,
1394
+ },
1395
+ ],
1396
+ },
1397
+ ],
1398
+ };
1399
+ constructor() {
1400
+ effect(() => {
1401
+ const data = this.initialData();
1402
+ if (data) {
1403
+ this.formControl.patchValue({
1404
+ 'name-ar': data.ar,
1405
+ 'name-en': data.en,
1406
+ });
1407
+ }
1408
+ });
1409
+ }
1410
+ onSave() {
1411
+ if (this.formControl.invalid)
1412
+ return;
1413
+ const formValue = this.formControl.value;
1414
+ const payload = {
1415
+ name: {
1416
+ ar: formValue['name-ar'],
1417
+ en: formValue['name-en'],
1418
+ },
1419
+ };
1420
+ this.submitting.set(true);
1421
+ const sectionId = this.sectionId();
1422
+ if (sectionId) {
1423
+ // Update existing section
1424
+ this.facade.updateSection(sectionId, payload).subscribe({
1425
+ next: () => this.ref.close(true),
1426
+ error: () => this.submitting.set(false),
1427
+ });
1428
+ }
1429
+ else {
1430
+ // Create new section
1431
+ this.facade
1432
+ .addSection({ ...payload, order: this.sectionsCount() })
1433
+ .subscribe({
1434
+ next: () => this.ref.close(true),
1435
+ error: () => this.submitting.set(false),
1436
+ });
1437
+ }
1438
+ }
1439
+ onCancel() {
1440
+ this.ref.close(false);
1441
+ }
1442
+ onDelete(event) {
1443
+ const sectionId = this.sectionId();
1444
+ if (!sectionId)
1445
+ return;
1446
+ this.confirmationService.confirmDelete({
1447
+ event,
1448
+ type: 'popup',
1449
+ accept: () => {
1450
+ this.deleting.set(true);
1451
+ this.facade.deleteSection(sectionId).subscribe({
1452
+ next: () => this.ref.close(true),
1453
+ error: () => this.deleting.set(false),
1454
+ });
1455
+ },
1456
+ });
1457
+ }
1458
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBSectionForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
1459
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: FBSectionForm, isStandalone: true, selector: "mt-fb-section-form", inputs: { sectionId: { classPropertyName: "sectionId", publicName: "sectionId", isSignal: true, isRequired: false, transformFunction: null }, initialData: { classPropertyName: "initialData", publicName: "initialData", isSignal: true, isRequired: false, transformFunction: null }, sectionsCount: { classPropertyName: "sectionsCount", publicName: "sectionsCount", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-4 h-full overflow-y-auto pb-10\">\r\n <mt-dynamic-form [formConfig]=\"formConfig\" [formControl]=\"formControl\">\r\n </mt-dynamic-form>\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modalService.footerClass\">\r\n @if (sectionId()) {\r\n <mt-button\r\n [tooltip]=\"t('delete')\"\r\n severity=\"danger\"\r\n outlined\r\n icon=\"general.trash-01\"\r\n [loading]=\"deleting()\"\r\n [disabled]=\"submitting()\"\r\n (onClick)=\"onDelete($event)\"\r\n class=\"me-auto\"\r\n ></mt-button>\r\n }\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n severity=\"secondary\"\r\n [disabled]=\"submitting() || deleting()\"\r\n (onClick)=\"onCancel()\"\r\n >\r\n </mt-button>\r\n <mt-button\r\n [disabled]=\"!formControl.valid || deleting()\"\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n (onClick)=\"onSave()\"\r\n >\r\n </mt-button>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }] });
1460
+ }
1461
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBSectionForm, decorators: [{
1462
+ type: Component,
1463
+ args: [{ selector: 'mt-fb-section-form', standalone: true, imports: [TranslocoDirective, ReactiveFormsModule, DynamicForm, Button], template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-4 h-full overflow-y-auto pb-10\">\r\n <mt-dynamic-form [formConfig]=\"formConfig\" [formControl]=\"formControl\">\r\n </mt-dynamic-form>\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modalService.footerClass\">\r\n @if (sectionId()) {\r\n <mt-button\r\n [tooltip]=\"t('delete')\"\r\n severity=\"danger\"\r\n outlined\r\n icon=\"general.trash-01\"\r\n [loading]=\"deleting()\"\r\n [disabled]=\"submitting()\"\r\n (onClick)=\"onDelete($event)\"\r\n class=\"me-auto\"\r\n ></mt-button>\r\n }\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n severity=\"secondary\"\r\n [disabled]=\"submitting() || deleting()\"\r\n (onClick)=\"onCancel()\"\r\n >\r\n </mt-button>\r\n <mt-button\r\n [disabled]=\"!formControl.valid || deleting()\"\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n (onClick)=\"onSave()\"\r\n >\r\n </mt-button>\r\n </div>\r\n</ng-container>\r\n" }]
1464
+ }], ctorParameters: () => [], propDecorators: { sectionId: [{ type: i0.Input, args: [{ isSignal: true, alias: "sectionId", required: false }] }], initialData: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialData", required: false }] }], sectionsCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "sectionsCount", required: false }] }] } });
1465
+
1466
+ class FBSection {
1467
+ confirmationService = inject(ConfirmationService);
1468
+ modalService = inject(ModalService);
1469
+ translocoService = inject(TranslocoService);
1470
+ facade = inject(FormBuilderFacade);
1471
+ // Inputs
1472
+ section = input.required(...(ngDevMode ? [{ debugName: "section" }] : []));
1473
+ sectionsCount = input(0, ...(ngDevMode ? [{ debugName: "sectionsCount" }] : []));
1474
+ /** All sections - used to get available fields for condition formulas */
1475
+ allSections = input([], ...(ngDevMode ? [{ debugName: "allSections" }] : []));
1476
+ // Outputs - only keep drag/drop since it needs coordination with parent drop list group
1477
+ onFieldDrop = output();
1478
+ // Computed
1479
+ sectionName = computed(() => {
1480
+ const lang = document.documentElement.lang;
1481
+ const section = this.section();
1482
+ return section.name[lang] ?? section.name['en'];
1483
+ }, ...(ngDevMode ? [{ debugName: "sectionName" }] : []));
1484
+ fields = computed(() => this.section().fields, ...(ngDevMode ? [{ debugName: "fields" }] : []));
1485
+ // UI State
1486
+ expanded = signal(true, ...(ngDevMode ? [{ debugName: "expanded" }] : []));
1487
+ // Form groups cache for dynamic fields
1488
+ formGroupsCache = new Map();
1489
+ constructor() {
1490
+ // Cleanup stale form groups when fields change
1491
+ effect(() => {
1492
+ const currentIds = new Set(this.fields().map((f) => f.id));
1493
+ for (const id of this.formGroupsCache.keys()) {
1494
+ if (!currentIds.has(id)) {
1495
+ this.formGroupsCache.delete(id);
1496
+ }
1497
+ }
1498
+ });
1499
+ }
1500
+ getFormGroup(field) {
1501
+ let fg = this.formGroupsCache.get(field.id);
1502
+ if (fg) {
1503
+ // Check if field name changed
1504
+ if (!fg.contains(field.name)) {
1505
+ const oldKey = Object.keys(fg.controls)[0];
1506
+ const value = oldKey ? fg.get(oldKey)?.value : field.data?.data;
1507
+ fg = new FormGroup({
1508
+ [field.name]: new FormControl(value),
1509
+ });
1510
+ this.formGroupsCache.set(field.id, fg);
1511
+ }
1512
+ }
1513
+ else {
1514
+ fg = new FormGroup({
1515
+ [field.name]: new FormControl(field.data?.data),
1516
+ });
1517
+ this.formGroupsCache.set(field.id, fg);
1518
+ }
1519
+ return fg;
1520
+ }
1521
+ toggleExpanded() {
1522
+ this.expanded.update((v) => !v);
1523
+ }
1524
+ onDrop(event) {
1525
+ this.onFieldDrop.emit(event);
1526
+ }
1527
+ editSection(event) {
1528
+ event.stopPropagation();
1529
+ const section = this.section();
1530
+ this.modalService.openModal(FBSectionForm, 'drawer', {
1531
+ header: this.translocoService.translate('formBuilder.edit-section'),
1532
+ height: '20vw',
1533
+ styleClass: '!w-100 !absolute ',
1534
+ position: 'end',
1535
+ appendTo: '#page-content',
1536
+ modal: true,
1537
+ dismissible: true,
1538
+ inputValues: {
1539
+ sectionId: section.id,
1540
+ initialData: section.name,
1541
+ sectionsCount: this.sectionsCount(),
1542
+ },
1543
+ });
1544
+ }
1545
+ removeField(event, field) {
1546
+ this.confirmationService.confirmDelete({
1547
+ event,
1548
+ type: 'popup',
1549
+ accept: () => {
1550
+ this.facade.deleteField(this.section().id, field.id);
1551
+ },
1552
+ });
1553
+ }
1554
+ editField(field) {
1555
+ this.modalService.openModal(FBFieldForm, 'drawer', {
1556
+ header: this.translocoService.translate('formBuilder.field-settings'),
1557
+ height: '20vw',
1558
+ styleClass: '!w-100 !absolute !shadow-none',
1559
+ position: 'end',
1560
+ modal: true,
1561
+ dismissible: true,
1562
+ appendTo: '#page-content',
1563
+ inputValues: {
1564
+ initialData: field,
1565
+ sectionId: this.section().id,
1566
+ allSections: this.allSections(),
1567
+ },
1568
+ });
1569
+ }
1570
+ getFieldColSpan(field) {
1571
+ switch (field.width) {
1572
+ case '100':
1573
+ return 'col-span-12';
1574
+ case '50':
1575
+ return 'col-span-6';
1576
+ case '25':
1577
+ return 'col-span-3';
1578
+ default:
1579
+ return 'col-span-12';
1580
+ }
1581
+ }
1582
+ getFieldType(field) {
1583
+ const typeMap = {
1584
+ User: 'select',
1585
+ Text: 'text',
1586
+ LongText: 'editor-field',
1587
+ Percentage: 'slider',
1588
+ Date: 'date',
1589
+ Currency: 'text',
1590
+ Number: 'number',
1591
+ Lookup: 'select',
1592
+ LookupMultiSelect: 'select',
1593
+ Checkbox: 'toggle',
1594
+ InternalModule: 'select',
1595
+ DynamicList: 'select',
1596
+ API: 'select',
1597
+ Time: 'date',
1598
+ Status: 'select',
1599
+ Attachment: 'attachment',
1600
+ EditableListView: 'actionableTable',
1601
+ LookupLog: 'actionableTable',
1602
+ LookupMatrix: 'select',
1603
+ Location: 'select',
1604
+ };
1605
+ return typeMap[field.type] ?? 'text';
1606
+ }
1607
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBSection, deps: [], target: i0.ɵɵFactoryTarget.Component });
1608
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: FBSection, isStandalone: true, selector: "mt-fb-section", inputs: { section: { classPropertyName: "section", publicName: "section", isSignal: true, isRequired: true, transformFunction: null }, sectionsCount: { classPropertyName: "sectionsCount", publicName: "sectionsCount", isSignal: true, isRequired: false, transformFunction: null }, allSections: { classPropertyName: "allSections", publicName: "allSections", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onFieldDrop: "onFieldDrop" }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div class=\"flex flex-col\">\r\n <div\r\n class=\"flex justify-between items-center bg-primary text-primary-contrast p-2 rounded-xl\"\r\n >\r\n <mt-button\r\n size=\"small\"\r\n icon=\"arrow.chevron-down\"\r\n class=\"transition-[rotate]\"\r\n [class.rotate-180]=\"expanded()\"\r\n (onClick)=\"toggleExpanded()\"\r\n ></mt-button>\r\n <span class=\"font-bold\">\r\n {{ sectionName() }}\r\n </span>\r\n <div class=\"flex gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n icon=\"general.edit-02\"\r\n [tooltip]=\"t('edit-section')\"\r\n (onClick)=\"editSection($event)\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n @if (expanded()) {\r\n <div\r\n cdkDropList\r\n [id]=\"section().id\"\r\n cdkDropListOrientation=\"mixed\"\r\n [cdkDropListData]=\"fields()\"\r\n (cdkDropListDropped)=\"onDrop($event)\"\r\n class=\"grid grid-cols-12 gap-4 relative py-4\"\r\n [class.min-h-27]=\"fields().length === 0\"\r\n >\r\n @for (field of fields(); track field.id) {\r\n <div\r\n cdkDrag\r\n [cdkDragData]=\"field\"\r\n [cdkDragDisabled]=\"field._pending || field._deleting\"\r\n [class]=\"\r\n getFieldColSpan(field) + ' cursor-grab active:cursor-grabbing'\r\n \"\r\n >\r\n <div\r\n *cdkDragPlaceholder\r\n [class]=\"\r\n 'h-full min-h-27 bg-black/10 rounded-2xl ' +\r\n getFieldColSpan(field)\r\n \"\r\n ></div>\r\n @if (field._pending) {\r\n <!-- Skeleton for pending field -->\r\n <mt-card class=\"h-full animate-pulse\">\r\n <div class=\"flex gap-2\">\r\n <div class=\"flex-1 space-y-3\">\r\n <div class=\"h-4 bg-surface-200 rounded w-1/3\"></div>\r\n <div class=\"h-10 bg-surface-200 rounded\"></div>\r\n </div>\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"h-8 w-8 bg-surface-200 rounded\"></div>\r\n <div class=\"h-8 w-8 bg-surface-200 rounded\"></div>\r\n </div>\r\n </div>\r\n </mt-card>\r\n } @else {\r\n <mt-card\r\n class=\"h-full relative\"\r\n [class.opacity-50]=\"field._deleting\"\r\n >\r\n @if (field._deleting) {\r\n <div\r\n class=\"absolute inset-0 flex items-center justify-center bg-white/50 rounded-xl z-10\"\r\n >\r\n <div\r\n class=\"animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full\"\r\n ></div>\r\n </div>\r\n }\r\n <div class=\"flex gap-2\">\r\n <div class=\"flex-1\">\r\n <form [formGroup]=\"getFormGroup(field)\">\r\n <mt-dynamic-field\r\n [fieldName]=\"field.name\"\r\n [fieldConfig]=\"{\r\n label: field.name,\r\n readonly: true,\r\n type: getFieldType(field),\r\n }\"\r\n />\r\n </form>\r\n </div>\r\n <div class=\"flex flex-col gap-1\">\r\n <mt-button\r\n size=\"small\"\r\n icon=\"general.settings-01\"\r\n [tooltip]=\"t('edit-field')\"\r\n outlined\r\n [disabled]=\"field._deleting\"\r\n (onClick)=\"editField(field)\"\r\n ></mt-button>\r\n <mt-button\r\n size=\"small\"\r\n icon=\"general.trash-01\"\r\n severity=\"danger\"\r\n outlined\r\n [tooltip]=\"t('remove-field')\"\r\n [disabled]=\"field._deleting\"\r\n (onClick)=\"removeField($event, field)\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n </mt-card>\r\n }\r\n </div>\r\n } @empty {\r\n <mt-card class=\"absolute inset-0 top-4 h-full\" paddingless>\r\n <div class=\"size-full p-4\">\r\n <div\r\n class=\"flex justify-center items-center gap-4 h-full border-2 border-primary rounded-xl bg-primary-200 text-primary\"\r\n >\r\n <mt-icon icon=\"editor.move\" class=\"text-3xl\" />\r\n <span>{{ t(\"drag-from-the-side\") }}</span>\r\n </div>\r\n </div>\r\n </mt-card>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DynamicField, selector: "mt-dynamic-field", inputs: ["fieldConfig", "fieldName"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
1609
+ }
1610
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBSection, decorators: [{
1611
+ type: Component,
1612
+ args: [{ selector: 'mt-fb-section', standalone: true, imports: [
1613
+ Button,
1614
+ Card,
1615
+ Icon,
1616
+ CdkDrag,
1617
+ CdkDropList,
1618
+ CdkDragPlaceholder,
1619
+ TranslocoDirective,
1620
+ DynamicField,
1621
+ ReactiveFormsModule,
1622
+ ], template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div class=\"flex flex-col\">\r\n <div\r\n class=\"flex justify-between items-center bg-primary text-primary-contrast p-2 rounded-xl\"\r\n >\r\n <mt-button\r\n size=\"small\"\r\n icon=\"arrow.chevron-down\"\r\n class=\"transition-[rotate]\"\r\n [class.rotate-180]=\"expanded()\"\r\n (onClick)=\"toggleExpanded()\"\r\n ></mt-button>\r\n <span class=\"font-bold\">\r\n {{ sectionName() }}\r\n </span>\r\n <div class=\"flex gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n icon=\"general.edit-02\"\r\n [tooltip]=\"t('edit-section')\"\r\n (onClick)=\"editSection($event)\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n @if (expanded()) {\r\n <div\r\n cdkDropList\r\n [id]=\"section().id\"\r\n cdkDropListOrientation=\"mixed\"\r\n [cdkDropListData]=\"fields()\"\r\n (cdkDropListDropped)=\"onDrop($event)\"\r\n class=\"grid grid-cols-12 gap-4 relative py-4\"\r\n [class.min-h-27]=\"fields().length === 0\"\r\n >\r\n @for (field of fields(); track field.id) {\r\n <div\r\n cdkDrag\r\n [cdkDragData]=\"field\"\r\n [cdkDragDisabled]=\"field._pending || field._deleting\"\r\n [class]=\"\r\n getFieldColSpan(field) + ' cursor-grab active:cursor-grabbing'\r\n \"\r\n >\r\n <div\r\n *cdkDragPlaceholder\r\n [class]=\"\r\n 'h-full min-h-27 bg-black/10 rounded-2xl ' +\r\n getFieldColSpan(field)\r\n \"\r\n ></div>\r\n @if (field._pending) {\r\n <!-- Skeleton for pending field -->\r\n <mt-card class=\"h-full animate-pulse\">\r\n <div class=\"flex gap-2\">\r\n <div class=\"flex-1 space-y-3\">\r\n <div class=\"h-4 bg-surface-200 rounded w-1/3\"></div>\r\n <div class=\"h-10 bg-surface-200 rounded\"></div>\r\n </div>\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"h-8 w-8 bg-surface-200 rounded\"></div>\r\n <div class=\"h-8 w-8 bg-surface-200 rounded\"></div>\r\n </div>\r\n </div>\r\n </mt-card>\r\n } @else {\r\n <mt-card\r\n class=\"h-full relative\"\r\n [class.opacity-50]=\"field._deleting\"\r\n >\r\n @if (field._deleting) {\r\n <div\r\n class=\"absolute inset-0 flex items-center justify-center bg-white/50 rounded-xl z-10\"\r\n >\r\n <div\r\n class=\"animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full\"\r\n ></div>\r\n </div>\r\n }\r\n <div class=\"flex gap-2\">\r\n <div class=\"flex-1\">\r\n <form [formGroup]=\"getFormGroup(field)\">\r\n <mt-dynamic-field\r\n [fieldName]=\"field.name\"\r\n [fieldConfig]=\"{\r\n label: field.name,\r\n readonly: true,\r\n type: getFieldType(field),\r\n }\"\r\n />\r\n </form>\r\n </div>\r\n <div class=\"flex flex-col gap-1\">\r\n <mt-button\r\n size=\"small\"\r\n icon=\"general.settings-01\"\r\n [tooltip]=\"t('edit-field')\"\r\n outlined\r\n [disabled]=\"field._deleting\"\r\n (onClick)=\"editField(field)\"\r\n ></mt-button>\r\n <mt-button\r\n size=\"small\"\r\n icon=\"general.trash-01\"\r\n severity=\"danger\"\r\n outlined\r\n [tooltip]=\"t('remove-field')\"\r\n [disabled]=\"field._deleting\"\r\n (onClick)=\"removeField($event, field)\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n </mt-card>\r\n }\r\n </div>\r\n } @empty {\r\n <mt-card class=\"absolute inset-0 top-4 h-full\" paddingless>\r\n <div class=\"size-full p-4\">\r\n <div\r\n class=\"flex justify-center items-center gap-4 h-full border-2 border-primary rounded-xl bg-primary-200 text-primary\"\r\n >\r\n <mt-icon icon=\"editor.move\" class=\"text-3xl\" />\r\n <span>{{ t(\"drag-from-the-side\") }}</span>\r\n </div>\r\n </div>\r\n </mt-card>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-container>\r\n" }]
1623
+ }], ctorParameters: () => [], propDecorators: { section: [{ type: i0.Input, args: [{ isSignal: true, alias: "section", required: true }] }], sectionsCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "sectionsCount", required: false }] }], allSections: [{ type: i0.Input, args: [{ isSignal: true, alias: "allSections", required: false }] }], onFieldDrop: [{ type: i0.Output, args: ["onFieldDrop"] }] } });
1624
+
1625
+ class FBPreviewForm {
1626
+ modalService = inject(ModalService);
1627
+ ref = inject(ModalRef);
1628
+ transloco = inject(TranslocoService);
1629
+ // Inputs
1630
+ sections = input([], ...(ngDevMode ? [{ debugName: "sections" }] : []));
1631
+ // Tab state
1632
+ activeTab = signal('create', ...(ngDevMode ? [{ debugName: "activeTab" }] : []));
1633
+ isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
1634
+ tabOptions = [
1635
+ {
1636
+ label: this.transloco.translate('formBuilder.create-form'),
1637
+ value: 'create',
1638
+ },
1639
+ { label: this.transloco.translate('formBuilder.edit-form'), value: 'edit' },
1640
+ ];
1641
+ // Form control for dynamic form
1642
+ formControl = new FormControl();
1643
+ constructor() {
1644
+ // Show skeleton briefly when tab changes to force rebuild
1645
+ effect(() => {
1646
+ this.activeTab(); // Track tab changes
1647
+ this.isLoading.set(true);
1648
+ setTimeout(() => this.isLoading.set(false), 50);
1649
+ });
1650
+ }
1651
+ // Convert enriched sections to DynamicFormConfig based on active tab
1652
+ formConfig = computed(() => {
1653
+ const sections = this.sections();
1654
+ const mode = this.activeTab();
1655
+ return {
1656
+ sections: sections
1657
+ .map((section, sectionIndex) => {
1658
+ const lang = document.documentElement.lang;
1659
+ const sectionName = section.name[lang] ?? section.name['en'] ?? '';
1660
+ // Filter fields based on mode
1661
+ const visibleFields = section.fields.filter((field) => {
1662
+ if (mode === 'create') {
1663
+ return !field.hiddenInCreation;
1664
+ }
1665
+ else {
1666
+ return !field.hiddenInEditForm;
1667
+ }
1668
+ });
1669
+ return {
1670
+ key: section.id,
1671
+ label: sectionName,
1672
+ type: 'header',
1673
+ columns: 12,
1674
+ order: section.order ?? sectionIndex,
1675
+ fields: visibleFields.map((field, fieldIndex) => {
1676
+ const colSpan = this.getColSpan(field.width);
1677
+ return {
1678
+ key: `field_${field.propertyId}`,
1679
+ label: field.name,
1680
+ type: this.mapFieldType(field.type),
1681
+ colSpan,
1682
+ order: field.order ?? fieldIndex,
1683
+ placeholder: field.name,
1684
+ };
1685
+ }),
1686
+ };
1687
+ })
1688
+ .filter((section) => section.fields.length > 0), // Hide empty sections
1689
+ };
1690
+ }, ...(ngDevMode ? [{ debugName: "formConfig" }] : []));
1691
+ /**
1692
+ * Map property view type to dynamic form field type
1693
+ */
1694
+ mapFieldType(viewType) {
1695
+ const typeMap = {
1696
+ User: 'select',
1697
+ Text: 'text',
1698
+ LongText: 'editor-field',
1699
+ Percentage: 'slider',
1700
+ Date: 'date',
1701
+ Currency: 'text',
1702
+ Number: 'number',
1703
+ Lookup: 'select',
1704
+ LookupMultiSelect: 'select',
1705
+ Checkbox: 'toggle',
1706
+ InternalModule: 'select',
1707
+ DynamicList: 'select',
1708
+ API: 'select',
1709
+ Time: 'date',
1710
+ Status: 'select',
1711
+ Attachment: 'attachment',
1712
+ EditableListView: 'actionableTable',
1713
+ LookupLog: 'actionableTable',
1714
+ LookupMatrix: 'select',
1715
+ Location: 'select',
1716
+ };
1717
+ return typeMap[viewType] ?? 'text';
1718
+ }
1719
+ /**
1720
+ * Convert field width to colSpan
1721
+ */
1722
+ getColSpan(width) {
1723
+ const widthMap = {
1724
+ '25': 3,
1725
+ '50': 6,
1726
+ '100': 12,
1727
+ };
1728
+ return widthMap[width] ?? 12;
1729
+ }
1730
+ onClose() {
1731
+ this.ref.close();
1732
+ }
1733
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBPreviewForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
1734
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: FBPreviewForm, isStandalone: true, selector: "mt-fb-preview-form", inputs: { sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <!-- Tabs for Create/Edit mode -->\r\n <div class=\"flex justify-center mb-4\">\r\n <mt-tabs [options]=\"tabOptions\" [(active)]=\"activeTab\"></mt-tabs>\r\n </div>\r\n\r\n <div class=\"h-full overflow-y-auto pb-10\">\r\n @if (isLoading()) {\r\n <div class=\"space-y-4 animate-pulse\">\r\n <div class=\"h-6 bg-surface-200 rounded w-1/3\"></div>\r\n <div class=\"h-10 bg-surface-200 rounded\"></div>\r\n <div class=\"h-10 bg-surface-200 rounded\"></div>\r\n <div class=\"h-10 bg-surface-200 rounded w-2/3\"></div>\r\n </div>\r\n } @else if (formConfig().sections.length > 0) {\r\n <mt-dynamic-form\r\n [formConfig]=\"formConfig()\"\r\n [formControl]=\"formControl\"\r\n >\r\n </mt-dynamic-form>\r\n } @else {\r\n <div\r\n class=\"flex justify-center items-center h-64 text-muted-color text-lg\"\r\n >\r\n {{ t(\"no-fields-visible\") }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "size", "fluid", "disabled"], outputs: ["activeChange", "onChange"] }] });
1735
+ }
1736
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBPreviewForm, decorators: [{
1737
+ type: Component,
1738
+ args: [{ selector: 'mt-fb-preview-form', standalone: true, imports: [TranslocoDirective, ReactiveFormsModule, DynamicForm, Tabs], template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <!-- Tabs for Create/Edit mode -->\r\n <div class=\"flex justify-center mb-4\">\r\n <mt-tabs [options]=\"tabOptions\" [(active)]=\"activeTab\"></mt-tabs>\r\n </div>\r\n\r\n <div class=\"h-full overflow-y-auto pb-10\">\r\n @if (isLoading()) {\r\n <div class=\"space-y-4 animate-pulse\">\r\n <div class=\"h-6 bg-surface-200 rounded w-1/3\"></div>\r\n <div class=\"h-10 bg-surface-200 rounded\"></div>\r\n <div class=\"h-10 bg-surface-200 rounded\"></div>\r\n <div class=\"h-10 bg-surface-200 rounded w-2/3\"></div>\r\n </div>\r\n } @else if (formConfig().sections.length > 0) {\r\n <mt-dynamic-form\r\n [formConfig]=\"formConfig()\"\r\n [formControl]=\"formControl\"\r\n >\r\n </mt-dynamic-form>\r\n } @else {\r\n <div\r\n class=\"flex justify-center items-center h-64 text-muted-color text-lg\"\r\n >\r\n {{ t(\"no-fields-visible\") }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n</ng-container>\r\n" }]
1739
+ }], ctorParameters: () => [], propDecorators: { sections: [{ type: i0.Input, args: [{ isSignal: true, alias: "sections", required: false }] }] } });
1740
+
1741
+ class FormBuilder {
1742
+ modalService = inject(ModalService);
1743
+ confirmationService = inject(ConfirmationService);
1744
+ translocoService = inject(TranslocoService);
1745
+ facade = inject(FormBuilderFacade);
1746
+ dialogRef;
1747
+ // Local UI state
1748
+ activeTab = signal('system', ...(ngDevMode ? [{ debugName: "activeTab" }] : []));
1749
+ // State from facade
1750
+ sections = this.facade.sections;
1751
+ properties = this.facade.properties;
1752
+ isLoading = this.facade.isLoadingFormConfiguration;
1753
+ error = this.facade.formConfigurationError;
1754
+ // Properties map for enrichment
1755
+ propertiesMap = computed(() => {
1756
+ const map = new Map();
1757
+ for (const prop of this.properties()) {
1758
+ map.set(prop.id, prop);
1759
+ }
1760
+ return map;
1761
+ }, ...(ngDevMode ? [{ debugName: "propertiesMap" }] : []));
1762
+ // Enrich sections with property data for UI display
1763
+ enrichedSections = computed(() => {
1764
+ const sections = this.sections();
1765
+ const propsMap = this.propertiesMap();
1766
+ const lang = document.documentElement.lang;
1767
+ return sections.map((section) => ({
1768
+ ...section,
1769
+ fields: section.fields.map((field) => {
1770
+ const prop = propsMap.get(field.propertyId);
1771
+ const propName = prop?.name;
1772
+ const name = typeof propName === 'string'
1773
+ ? propName
1774
+ : (propName?.[lang] ??
1775
+ propName?.['en'] ??
1776
+ `Property ${field.propertyId}`);
1777
+ return {
1778
+ ...field,
1779
+ name,
1780
+ type: prop?.viewType || 'text',
1781
+ data: prop,
1782
+ };
1783
+ }),
1784
+ }));
1785
+ }, ...(ngDevMode ? [{ debugName: "enrichedSections" }] : []));
1786
+ // Available properties for toolbox tabs
1787
+ availableTabs = computed(() => {
1788
+ const tabs = [];
1789
+ const usedPropertyIds = new Set(this.sections().flatMap((s) => s.fields.map((f) => f.propertyId)));
1790
+ const availableProps = this.properties().filter((p) => !usedPropertyIds.has(p.id));
1791
+ const systemProps = availableProps.filter((p) => p['isSystem']);
1792
+ const customProps = availableProps.filter((p) => !p['isSystem']);
1793
+ if (systemProps.length > 0) {
1794
+ tabs.push({ id: 'system', title: 'System', properties: systemProps });
1795
+ }
1796
+ if (customProps.length > 0) {
1797
+ tabs.push({ id: 'custom', title: 'Custom', properties: customProps });
1798
+ }
1799
+ return tabs;
1800
+ }, ...(ngDevMode ? [{ debugName: "availableTabs" }] : []));
1801
+ constructor() {
1802
+ // Update active tab when tabs change
1803
+ effect(() => {
1804
+ const tabs = this.availableTabs();
1805
+ const currentTab = this.activeTab();
1806
+ if (tabs.length > 0 && !tabs.some((t) => t.id === currentTab)) {
1807
+ this.activeTab.set(tabs[0].id);
1808
+ }
1809
+ });
1810
+ }
1811
+ drop(event) {
1812
+ const targetSectionId = event.container.id;
1813
+ if (event.previousContainer === event.container) {
1814
+ // Reordering within the same section
1815
+ const fields = [...event.container.data];
1816
+ const [movedField] = fields.splice(event.previousIndex, 1);
1817
+ fields.splice(event.currentIndex, 0, movedField);
1818
+ // Build bulk reorder payload with new order values
1819
+ const reorderPayload = fields.map((field, index) => ({
1820
+ id: field.id,
1821
+ order: index,
1822
+ }));
1823
+ this.facade.reorderFields(targetSectionId, reorderPayload);
1824
+ }
1825
+ else if (event.previousContainer.id.startsWith('toolbox-')) {
1826
+ // Adding from toolbox
1827
+ const propertyItem = event.item.data;
1828
+ this.facade.addField(targetSectionId, {
1829
+ propertyId: propertyItem.id,
1830
+ width: '100',
1831
+ order: event.currentIndex,
1832
+ hiddenInCreation: false,
1833
+ });
1834
+ }
1835
+ else {
1836
+ // Moving between sections
1837
+ const sourceSectionId = event.previousContainer.id;
1838
+ const field = event.item.data;
1839
+ this.facade.moveField(sourceSectionId, field.id, {
1840
+ targetSectionId,
1841
+ order: event.currentIndex,
1842
+ });
1843
+ }
1844
+ }
1845
+ addSection() {
1846
+ this.dialogRef = this.modalService.openModal(FBSectionForm, 'drawer', {
1847
+ header: this.translocoService.translate('formBuilder.add-section'),
1848
+ height: '20vw',
1849
+ styleClass: '!w-100 !absolute ',
1850
+ position: 'end',
1851
+ appendTo: '#page-content',
1852
+ modal: true,
1853
+ dismissible: true,
1854
+ inputValues: {
1855
+ sectionsCount: this.sections().length,
1856
+ },
1857
+ });
1858
+ }
1859
+ openPreview() {
1860
+ this.dialogRef = this.modalService.openModal(FBPreviewForm, 'drawer', {
1861
+ header: this.translocoService.translate('formBuilder.preview'),
1862
+ styleClass: '!w-[80vw] ',
1863
+ position: 'end',
1864
+ modal: true,
1865
+ dismissible: true,
1866
+ inputValues: {
1867
+ sections: this.enrichedSections(),
1868
+ },
1869
+ });
1870
+ }
1871
+ resetFormConfiguration() {
1872
+ this.confirmationService.confirm({
1873
+ type: 'dialog',
1874
+ acceptButtonStyleClass: 'p-button-danger',
1875
+ accept: () => {
1876
+ this.facade.resetFormConfiguration();
1877
+ },
1878
+ });
1879
+ }
1880
+ noReturnPredicate = () => false;
1881
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
1882
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: FormBuilder, isStandalone: true, selector: "mt-form-builder", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div class=\"flex gap-4 h-full w-full overflow-hidden\" cdkDropListGroup>\r\n <!-- Properties Sidebar -->\r\n <mt-card\r\n class=\"z-1 w-1/5 min-w-xs shrink-0 h-full flex flex-col overflow-hidden\"\r\n >\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <div class=\"flex items-center justify-between px-4 pt-5\">\r\n <h3 class=\"text-xl font-semibold\">{{ t(\"form-elements\") }}</h3>\r\n </div>\r\n\r\n @if (properties().length === 0) {\r\n @if (isLoading()) {\r\n <!-- Properties Loading Skeleton -->\r\n <div class=\"p-4 space-y-3\">\r\n <p-skeleton height=\"2rem\" styleClass=\"mb-4\" />\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <p-skeleton height=\"3rem\" styleClass=\"rounded-lg\" />\r\n }\r\n </div>\r\n } @else {\r\n <!-- No Properties State -->\r\n <div class=\"flex-1 flex items-center justify-center p-4\">\r\n <div class=\"text-center text-muted-color\">\r\n <p class=\"text-sm\">{{ t(\"no-properties\") }}</p>\r\n </div>\r\n </div>\r\n }\r\n } @else {\r\n <!-- Tabs using PrimeNG -->\r\n <p-tabs\r\n [(value)]=\"activeTab\"\r\n styleClass=\"structure-tabs\"\r\n class=\"flex flex-1 flex-col min-h-0\"\r\n >\r\n <p-tablist class=\"shrink-0\">\r\n @for (tab of availableTabs(); track tab.id) {\r\n <p-tab [value]=\"tab.id\">{{ tab.title | titlecase }}</p-tab>\r\n }\r\n </p-tablist>\r\n <p-tabpanels class=\"!bg-transparent !p-0 flex-1 overflow-hidden\">\r\n @for (tab of availableTabs(); track tab.id) {\r\n <p-tabpanel [value]=\"tab.id\" class=\"h-full\">\r\n <!-- Node List -->\r\n <div\r\n class=\"space-y-1 p-4 [&_.cdk-drag-placeholder]:hidden h-full overflow-y-auto\"\r\n [id]=\"'toolbox-' + tab.id\"\r\n cdkDropList\r\n cdkDropListSortingDisabled\r\n [cdkDropListData]=\"tab.properties\"\r\n [cdkDropListEnterPredicate]=\"noReturnPredicate\"\r\n >\r\n @for (node of tab.properties; track $index) {\r\n <div\r\n cdkDrag\r\n [cdkDragData]=\"node\"\r\n class=\"group cursor-move select-none relative flex items-center gap-3 py-3 px-4 rounded-lg border border-dashed border-surface-300 hover:border-solid hover:bg-emphasis dark:border-surface-500 transition-colors\"\r\n >\r\n <div\r\n *cdkDragPlaceholder\r\n class=\"col-span-12 min-h-27 w-full rounded-2xl bg-black/10 z-1\"\r\n ></div>\r\n <span class=\"flex-1 text-base font-medium\">{{\r\n node.name\r\n }}</span>\r\n </div>\r\n }\r\n\r\n @if (tab.properties.length === 0) {\r\n <div class=\"py-8 text-center text-muted-color\">\r\n <p class=\"text-sm\">\r\n All {{ tab.title }} items are in use\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n </p-tabpanel>\r\n }\r\n </p-tabpanels>\r\n </p-tabs>\r\n }\r\n </ng-template>\r\n </mt-card>\r\n\r\n <!-- Main Canvas Area -->\r\n <div class=\"flex flex-col gap-4 flex-1 w-full h-full overflow-y-auto\">\r\n <mt-card>\r\n <ng-template #headless>\r\n <div class=\"p-4 flex items-center gap-2\">\r\n <mt-button\r\n icon=\"layout.layout-top\"\r\n [label]=\"t('add-section')\"\r\n (onClick)=\"addSection()\"\r\n [disabled]=\"isLoading()\"\r\n ></mt-button>\r\n <mt-button\r\n icon=\"general.eye\"\r\n [label]=\"t('preview')\"\r\n (onClick)=\"openPreview()\"\r\n [disabled]=\"isLoading()\"\r\n ></mt-button>\r\n <mt-button\r\n icon=\"finance.credit-card-plus\"\r\n [label]=\"t('reset')\"\r\n (onClick)=\"resetFormConfiguration()\"\r\n [disabled]=\"isLoading()\"\r\n ></mt-button>\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n\r\n @if (isLoading()) {\r\n <!-- Form Loading Skeleton -->\r\n @for (i of [1, 2]; track i) {\r\n <mt-card>\r\n <ng-template #headless>\r\n <div class=\"p-4 space-y-4\">\r\n <!-- Section header skeleton -->\r\n <div class=\"flex items-center justify-between\">\r\n <p-skeleton width=\"10rem\" height=\"1.5rem\" />\r\n <div class=\"flex gap-2\">\r\n <p-skeleton width=\"2rem\" height=\"2rem\" shape=\"circle\" />\r\n <p-skeleton width=\"2rem\" height=\"2rem\" shape=\"circle\" />\r\n </div>\r\n </div>\r\n <!-- Fields skeleton -->\r\n <div class=\"grid grid-cols-12 gap-4\">\r\n <div class=\"col-span-6\">\r\n <p-skeleton height=\"4rem\" styleClass=\"rounded-lg\" />\r\n </div>\r\n <div class=\"col-span-6\">\r\n <p-skeleton height=\"4rem\" styleClass=\"rounded-lg\" />\r\n </div>\r\n <div class=\"col-span-12\">\r\n <p-skeleton height=\"4rem\" styleClass=\"rounded-lg\" />\r\n </div>\r\n </div>\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n }\r\n } @else {\r\n @for (section of enrichedSections(); track section.id) {\r\n <mt-fb-section\r\n [section]=\"section\"\r\n [sectionsCount]=\"enrichedSections().length\"\r\n [allSections]=\"enrichedSections()\"\r\n (onFieldDrop)=\"drop($event)\"\r\n >\r\n </mt-fb-section>\r\n } @empty {\r\n <mt-card>\r\n <div class=\"h-27 p-4\">\r\n <div\r\n class=\"flex justify-center items-center gap-4 h-full border-2 border-primary rounded-xl bg-primary-200 text-primary\"\r\n >\r\n <span>{{ t(\"no-section\") }}</span>\r\n </div>\r\n </div>\r\n </mt-card>\r\n }\r\n }\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [".cdk-drag{cursor:grab}.cdk-drag:active,.cdk-drag-preview{cursor:grabbing}.cdk-drag-placeholder{opacity:.5}.cdk-drop-list-dragging .cdk-drag{cursor:grabbing}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i1$1.Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i1$1.TabPanels, selector: "p-tabpanels" }, { kind: "component", type: i1$1.TabPanel, selector: "p-tabpanel", inputs: ["lazy", "value"], outputs: ["valueChange"] }, { kind: "component", type: i1$1.TabList, selector: "p-tablist" }, { kind: "component", type: i1$1.Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: FBSection, selector: "mt-fb-section", inputs: ["section", "sectionsCount", "allSections"], outputs: ["onFieldDrop"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i3.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i3.CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "directive", type: i3.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i3.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "pipe", type: i4.TitleCasePipe, name: "titlecase" }] });
1883
+ }
1884
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilder, decorators: [{
1885
+ type: Component,
1886
+ args: [{ selector: 'mt-form-builder', standalone: true, imports: [
1887
+ CommonModule,
1888
+ FormsModule,
1889
+ TabsModule,
1890
+ SkeletonModule,
1891
+ Button,
1892
+ Card,
1893
+ TranslocoDirective,
1894
+ FBSection,
1895
+ DragDropModule,
1896
+ ], template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div class=\"flex gap-4 h-full w-full overflow-hidden\" cdkDropListGroup>\r\n <!-- Properties Sidebar -->\r\n <mt-card\r\n class=\"z-1 w-1/5 min-w-xs shrink-0 h-full flex flex-col overflow-hidden\"\r\n >\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <div class=\"flex items-center justify-between px-4 pt-5\">\r\n <h3 class=\"text-xl font-semibold\">{{ t(\"form-elements\") }}</h3>\r\n </div>\r\n\r\n @if (properties().length === 0) {\r\n @if (isLoading()) {\r\n <!-- Properties Loading Skeleton -->\r\n <div class=\"p-4 space-y-3\">\r\n <p-skeleton height=\"2rem\" styleClass=\"mb-4\" />\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <p-skeleton height=\"3rem\" styleClass=\"rounded-lg\" />\r\n }\r\n </div>\r\n } @else {\r\n <!-- No Properties State -->\r\n <div class=\"flex-1 flex items-center justify-center p-4\">\r\n <div class=\"text-center text-muted-color\">\r\n <p class=\"text-sm\">{{ t(\"no-properties\") }}</p>\r\n </div>\r\n </div>\r\n }\r\n } @else {\r\n <!-- Tabs using PrimeNG -->\r\n <p-tabs\r\n [(value)]=\"activeTab\"\r\n styleClass=\"structure-tabs\"\r\n class=\"flex flex-1 flex-col min-h-0\"\r\n >\r\n <p-tablist class=\"shrink-0\">\r\n @for (tab of availableTabs(); track tab.id) {\r\n <p-tab [value]=\"tab.id\">{{ tab.title | titlecase }}</p-tab>\r\n }\r\n </p-tablist>\r\n <p-tabpanels class=\"!bg-transparent !p-0 flex-1 overflow-hidden\">\r\n @for (tab of availableTabs(); track tab.id) {\r\n <p-tabpanel [value]=\"tab.id\" class=\"h-full\">\r\n <!-- Node List -->\r\n <div\r\n class=\"space-y-1 p-4 [&_.cdk-drag-placeholder]:hidden h-full overflow-y-auto\"\r\n [id]=\"'toolbox-' + tab.id\"\r\n cdkDropList\r\n cdkDropListSortingDisabled\r\n [cdkDropListData]=\"tab.properties\"\r\n [cdkDropListEnterPredicate]=\"noReturnPredicate\"\r\n >\r\n @for (node of tab.properties; track $index) {\r\n <div\r\n cdkDrag\r\n [cdkDragData]=\"node\"\r\n class=\"group cursor-move select-none relative flex items-center gap-3 py-3 px-4 rounded-lg border border-dashed border-surface-300 hover:border-solid hover:bg-emphasis dark:border-surface-500 transition-colors\"\r\n >\r\n <div\r\n *cdkDragPlaceholder\r\n class=\"col-span-12 min-h-27 w-full rounded-2xl bg-black/10 z-1\"\r\n ></div>\r\n <span class=\"flex-1 text-base font-medium\">{{\r\n node.name\r\n }}</span>\r\n </div>\r\n }\r\n\r\n @if (tab.properties.length === 0) {\r\n <div class=\"py-8 text-center text-muted-color\">\r\n <p class=\"text-sm\">\r\n All {{ tab.title }} items are in use\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n </p-tabpanel>\r\n }\r\n </p-tabpanels>\r\n </p-tabs>\r\n }\r\n </ng-template>\r\n </mt-card>\r\n\r\n <!-- Main Canvas Area -->\r\n <div class=\"flex flex-col gap-4 flex-1 w-full h-full overflow-y-auto\">\r\n <mt-card>\r\n <ng-template #headless>\r\n <div class=\"p-4 flex items-center gap-2\">\r\n <mt-button\r\n icon=\"layout.layout-top\"\r\n [label]=\"t('add-section')\"\r\n (onClick)=\"addSection()\"\r\n [disabled]=\"isLoading()\"\r\n ></mt-button>\r\n <mt-button\r\n icon=\"general.eye\"\r\n [label]=\"t('preview')\"\r\n (onClick)=\"openPreview()\"\r\n [disabled]=\"isLoading()\"\r\n ></mt-button>\r\n <mt-button\r\n icon=\"finance.credit-card-plus\"\r\n [label]=\"t('reset')\"\r\n (onClick)=\"resetFormConfiguration()\"\r\n [disabled]=\"isLoading()\"\r\n ></mt-button>\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n\r\n @if (isLoading()) {\r\n <!-- Form Loading Skeleton -->\r\n @for (i of [1, 2]; track i) {\r\n <mt-card>\r\n <ng-template #headless>\r\n <div class=\"p-4 space-y-4\">\r\n <!-- Section header skeleton -->\r\n <div class=\"flex items-center justify-between\">\r\n <p-skeleton width=\"10rem\" height=\"1.5rem\" />\r\n <div class=\"flex gap-2\">\r\n <p-skeleton width=\"2rem\" height=\"2rem\" shape=\"circle\" />\r\n <p-skeleton width=\"2rem\" height=\"2rem\" shape=\"circle\" />\r\n </div>\r\n </div>\r\n <!-- Fields skeleton -->\r\n <div class=\"grid grid-cols-12 gap-4\">\r\n <div class=\"col-span-6\">\r\n <p-skeleton height=\"4rem\" styleClass=\"rounded-lg\" />\r\n </div>\r\n <div class=\"col-span-6\">\r\n <p-skeleton height=\"4rem\" styleClass=\"rounded-lg\" />\r\n </div>\r\n <div class=\"col-span-12\">\r\n <p-skeleton height=\"4rem\" styleClass=\"rounded-lg\" />\r\n </div>\r\n </div>\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n }\r\n } @else {\r\n @for (section of enrichedSections(); track section.id) {\r\n <mt-fb-section\r\n [section]=\"section\"\r\n [sectionsCount]=\"enrichedSections().length\"\r\n [allSections]=\"enrichedSections()\"\r\n (onFieldDrop)=\"drop($event)\"\r\n >\r\n </mt-fb-section>\r\n } @empty {\r\n <mt-card>\r\n <div class=\"h-27 p-4\">\r\n <div\r\n class=\"flex justify-center items-center gap-4 h-full border-2 border-primary rounded-xl bg-primary-200 text-primary\"\r\n >\r\n <span>{{ t(\"no-section\") }}</span>\r\n </div>\r\n </div>\r\n </mt-card>\r\n }\r\n }\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [".cdk-drag{cursor:grab}.cdk-drag:active,.cdk-drag-preview{cursor:grabbing}.cdk-drag-placeholder{opacity:.5}.cdk-drop-list-dragging .cdk-drag{cursor:grabbing}\n"] }]
1897
+ }], ctorParameters: () => [] });
1898
+
1899
+ /*
1900
+ * Public API Surface of form-builder
1901
+ */
1902
+
1903
+ /**
1904
+ * Generated bundle index. Do not edit.
1905
+ */
1906
+
1907
+ export { AddField, AddSection, DeleteField, DeleteSection, FormBuilder, FormBuilderActionKey, FormBuilderFacade, FormBuilderState, GetFormConfiguration, MoveField, ReorderFields, ResetFormBuilderState, ResetFormConfiguration, SetModuleInfo, SetProperties, UpdateField, UpdateSection };
1908
+ //# sourceMappingURL=masterteam-form-builder.mjs.map