@masterteam/form-builder 0.0.7 → 0.0.8

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.
@@ -1,6 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, Injectable, computed, input, signal, viewChild, Component, effect, output, DestroyRef } from '@angular/core';
3
- import * as i5 from '@angular/common';
2
+ import { inject, Injectable, computed, input, signal, viewChild, Component, effect, output, DestroyRef, viewChildren } from '@angular/core';
4
3
  import { CommonModule } from '@angular/common';
5
4
  import * as i1 from '@angular/forms';
6
5
  import { FormControl, ReactiveFormsModule, FormGroup, FormsModule } from '@angular/forms';
@@ -8,13 +7,16 @@ import * as i2 from 'primeng/tabs';
8
7
  import { TabsModule } from 'primeng/tabs';
9
8
  import * as i3 from 'primeng/skeleton';
10
9
  import { SkeletonModule } from 'primeng/skeleton';
10
+ import * as i4 from 'primeng/popover';
11
+ import { Popover, PopoverModule } from 'primeng/popover';
11
12
  import { Button } from '@masterteam/components/button';
12
13
  import { Card } from '@masterteam/components/card';
13
14
  import { TextField } from '@masterteam/components/text-field';
15
+ import { SelectField } from '@masterteam/components/select-field';
14
16
  import { ModalService } from '@masterteam/components/modal';
15
17
  import { ConfirmationService } from '@masterteam/components/confirmation';
16
18
  import { TranslocoDirective, TranslocoService } from '@jsverse/transloco';
17
- import * as i4 from '@angular/cdk/drag-drop';
19
+ import * as i5 from '@angular/cdk/drag-drop';
18
20
  import { CdkDrag, CdkDropList, CdkDragPlaceholder, DragDropModule } from '@angular/cdk/drag-drop';
19
21
  import { DynamicField } from '@masterteam/forms/dynamic-field';
20
22
  import { Icon } from '@masterteam/icons';
@@ -30,6 +32,7 @@ import { Tabs } from '@masterteam/components/tabs';
30
32
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
31
33
  import { Table } from '@masterteam/components/table';
32
34
  import { RadioCards } from '@masterteam/components/radio-cards';
35
+ import { of, map, catchError } from 'rxjs';
33
36
 
34
37
  // ============================================================================
35
38
  // Module Configuration Actions
@@ -257,6 +260,15 @@ let FormBuilderState = class FormBuilderState extends CrudStateBase {
257
260
  static getModuleId(state) {
258
261
  return state?.moduleId ?? null;
259
262
  }
263
+ static getParentModuleType(state) {
264
+ return state?.parentModuleType ?? null;
265
+ }
266
+ static getParentModuleId(state) {
267
+ return state?.parentModuleId ?? null;
268
+ }
269
+ static getParentPath(state) {
270
+ return state?.parentPath ?? '';
271
+ }
260
272
  static getProperties(state) {
261
273
  return state?.properties ?? [];
262
274
  }
@@ -394,7 +406,7 @@ let FormBuilderState = class FormBuilderState extends CrudStateBase {
394
406
  const tempField = {
395
407
  id: tempId,
396
408
  sectionId: action.sectionId,
397
- propertyId: action.payload.propertyId,
409
+ propertyKey: action.payload.propertyKey,
398
410
  width: action.payload.width,
399
411
  order: action.payload.order ?? 0,
400
412
  hiddenInCreation: action.payload.hiddenInCreation,
@@ -862,6 +874,15 @@ __decorate([
862
874
  __decorate([
863
875
  Selector()
864
876
  ], FormBuilderState, "getModuleId", null);
877
+ __decorate([
878
+ Selector()
879
+ ], FormBuilderState, "getParentModuleType", null);
880
+ __decorate([
881
+ Selector()
882
+ ], FormBuilderState, "getParentModuleId", null);
883
+ __decorate([
884
+ Selector()
885
+ ], FormBuilderState, "getParentPath", null);
865
886
  __decorate([
866
887
  Selector()
867
888
  ], FormBuilderState, "getProperties", null);
@@ -890,6 +911,9 @@ class FormBuilderFacade {
890
911
  validations = select(FormBuilderState.getValidations);
891
912
  moduleType = select(FormBuilderState.getModuleType);
892
913
  moduleId = select(FormBuilderState.getModuleId);
914
+ parentModuleType = select(FormBuilderState.getParentModuleType);
915
+ parentModuleId = select(FormBuilderState.getParentModuleId);
916
+ parentPath = select(FormBuilderState.getParentPath);
893
917
  // ============================================================================
894
918
  // Loading Signals
895
919
  // ============================================================================
@@ -1325,7 +1349,7 @@ class FBFieldConditions {
1325
1349
  this.ref.close({ saved: false });
1326
1350
  }
1327
1351
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBFieldConditions, deps: [], target: i0.ɵɵFactoryTarget.Component });
1328
- 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", "propertiesTemplate", "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"] }] });
1352
+ 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", "propertiesTemplate", "functionCategories", "operators", "initialTab", "searchPlaceholder", "labels"], outputs: ["onBlockInsert", "onTabChange"] }, { kind: "component", type: FormulaEditor, selector: "mt-formula-editor", inputs: ["placeholder", "initialTokens", "disabled", "borderless"], outputs: ["formulaChange", "tokensChange", "onBlur", "onFocus"] }] });
1329
1353
  }
1330
1354
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBFieldConditions, decorators: [{
1331
1355
  type: Component,
@@ -1368,7 +1392,7 @@ class FBFieldForm {
1368
1392
  if (currentField && field.id === currentField.id)
1369
1393
  continue;
1370
1394
  fields.push({
1371
- key: field.name,
1395
+ key: field.propertyKey,
1372
1396
  name: field.name,
1373
1397
  type: field.type,
1374
1398
  });
@@ -1853,7 +1877,7 @@ class FBPreviewForm {
1853
1877
  fields: visibleFields.map((field, fieldIndex) => {
1854
1878
  const colSpan = this.getColSpan(field.width);
1855
1879
  return {
1856
- key: `field_${field.propertyId}`,
1880
+ key: `field_${field.propertyKey}`,
1857
1881
  label: field.name,
1858
1882
  type: this.mapFieldType(field.type),
1859
1883
  colSpan,
@@ -2337,7 +2361,7 @@ class FBValidationRuleForm {
2337
2361
  this.ref.close(false);
2338
2362
  }
2339
2363
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBValidationRuleForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
2340
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.0.3", type: FBValidationRuleForm, isStandalone: true, selector: "mt-fb-validation-rule-form", inputs: { initialData: { classPropertyName: "initialData", publicName: "initialData", 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-4\">\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 <mt-formula-editor\r\n #editor\r\n [placeholder]=\"t('validation-formula-placeholder')\"\r\n [formControl]=\"formulaControl\"\r\n />\r\n <div class=\"grid grid-cols-2 gap-4\">\r\n <mt-text-field\r\n [label]=\"t('message-en')\"\r\n [placeholder]=\"t('message-en')\"\r\n [formControl]=\"messageEnControl\"\r\n />\r\n <mt-text-field\r\n [label]=\"t('message-ar')\"\r\n [placeholder]=\"t('message-ar')\"\r\n [formControl]=\"messageArControl\"\r\n />\r\n </div>\r\n <div class=\"grid gap-2\">\r\n <label class=\"text-sm font-medium text-gray-700 dark:text-gray-100\">\r\n {{ t(\"severity\") }}\r\n </label>\r\n <mt-radio-cards\r\n [options]=\"severityOptions()\"\r\n [activeId]=\"severityControl.value\"\r\n (selectionChange)=\"onSeverityChange($event)\"\r\n ></mt-radio-cards>\r\n </div>\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: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "icon", "iconPosition"] }, { kind: "component", type: RadioCards, selector: "mt-radio-cards", inputs: ["circle", "color", "size", "columns", "options", "activeId", "itemTemplate"], outputs: ["optionsChange", "activeIdChange", "selectionChange"] }, { kind: "component", type: FormulaToolbar, selector: "mt-formula-toolbar", inputs: ["knownProperties", "propertiesTemplate", "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"] }] });
2364
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.0.3", type: FBValidationRuleForm, isStandalone: true, selector: "mt-fb-validation-rule-form", inputs: { initialData: { classPropertyName: "initialData", publicName: "initialData", 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-4\">\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 <mt-formula-editor\r\n #editor\r\n [placeholder]=\"t('validation-formula-placeholder')\"\r\n [formControl]=\"formulaControl\"\r\n />\r\n <div class=\"grid grid-cols-2 gap-4\">\r\n <mt-text-field\r\n [label]=\"t('message-en')\"\r\n [placeholder]=\"t('message-en')\"\r\n [formControl]=\"messageEnControl\"\r\n />\r\n <mt-text-field\r\n [label]=\"t('message-ar')\"\r\n [placeholder]=\"t('message-ar')\"\r\n [formControl]=\"messageArControl\"\r\n />\r\n </div>\r\n <div class=\"grid gap-2\">\r\n <label class=\"text-sm font-medium text-gray-700 dark:text-gray-100\">\r\n {{ t(\"severity\") }}\r\n </label>\r\n <mt-radio-cards\r\n [options]=\"severityOptions()\"\r\n [activeId]=\"severityControl.value\"\r\n (selectionChange)=\"onSeverityChange($event)\"\r\n ></mt-radio-cards>\r\n </div>\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: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "icon", "iconPosition"] }, { kind: "component", type: RadioCards, selector: "mt-radio-cards", inputs: ["circle", "color", "size", "columns", "options", "activeId", "itemTemplate"], outputs: ["optionsChange", "activeIdChange", "selectionChange"] }, { kind: "component", type: FormulaToolbar, selector: "mt-formula-toolbar", inputs: ["knownProperties", "propertiesTemplate", "functionCategories", "operators", "initialTab", "searchPlaceholder", "labels"], outputs: ["onBlockInsert", "onTabChange"] }, { kind: "component", type: FormulaEditor, selector: "mt-formula-editor", inputs: ["placeholder", "initialTokens", "disabled", "borderless"], outputs: ["formulaChange", "tokensChange", "onBlur", "onFocus"] }] });
2341
2365
  }
2342
2366
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBValidationRuleForm, decorators: [{
2343
2367
  type: Component,
@@ -2364,7 +2388,7 @@ class FBValidationRules {
2364
2388
  propertiesMap = computed(() => {
2365
2389
  const map = new Map();
2366
2390
  for (const prop of this.properties()) {
2367
- map.set(prop.id, prop);
2391
+ map.set(prop.key, prop);
2368
2392
  }
2369
2393
  return map;
2370
2394
  }, ...(ngDevMode ? [{ debugName: "propertiesMap" }] : []));
@@ -2375,15 +2399,10 @@ class FBValidationRules {
2375
2399
  const fields = [];
2376
2400
  for (const section of sections) {
2377
2401
  for (const field of section.fields) {
2378
- const prop = propsMap.get(field.propertyId);
2379
- const propName = prop?.name;
2380
- const name = typeof propName === 'string'
2381
- ? propName
2382
- : (propName?.[lang] ??
2383
- propName?.['en'] ??
2384
- `Property ${field.propertyId}`);
2402
+ const prop = field.property ?? propsMap.get(field.propertyKey);
2403
+ const name = this.resolvePropertyName(prop, field.propertyKey, lang);
2385
2404
  fields.push({
2386
- key: name,
2405
+ key: field.propertyKey,
2387
2406
  name,
2388
2407
  type: prop?.viewType || 'text',
2389
2408
  });
@@ -2438,6 +2457,24 @@ class FBValidationRules {
2438
2457
  this.activeLang.set(lang ?? 'en');
2439
2458
  });
2440
2459
  }
2460
+ resolvePropertyName(property, fallbackKey, lang) {
2461
+ if (!property) {
2462
+ return `Property ${fallbackKey}`;
2463
+ }
2464
+ const name = property?.name;
2465
+ if (typeof name === 'string') {
2466
+ return name;
2467
+ }
2468
+ const display = name?.['display'];
2469
+ if (typeof display === 'string' && display.trim().length > 0) {
2470
+ return display;
2471
+ }
2472
+ const localized = name?.[lang] ?? name?.['en'] ?? name?.['ar'];
2473
+ if (typeof localized === 'string' && localized.trim().length > 0) {
2474
+ return localized;
2475
+ }
2476
+ return property?.key ?? `Property ${fallbackKey}`;
2477
+ }
2441
2478
  openCreate() {
2442
2479
  this.modalService.openModal(FBValidationRuleForm, 'drawer', {
2443
2480
  header: this.transloco.translate('formBuilder.add-validation-rule'),
@@ -2483,29 +2520,199 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
2483
2520
  args: [{ selector: 'mt-fb-validation-rules', standalone: true, imports: [TranslocoDirective, Table, Button], template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-auto']\">\r\n <div class=\"flex justify-end pb-4\">\r\n <mt-button\r\n [label]=\"t('add-validation-rule')\"\r\n severity=\"primary\"\r\n variant=\"outlined\"\r\n (onClick)=\"openCreate()\"\r\n ></mt-button>\r\n </div>\r\n <mt-table\r\n [data]=\"tableRows()\"\r\n [columns]=\"tableColumns()\"\r\n [rowActions]=\"rowActions()\"\r\n (cellChange)=\"onCellChange($event)\"\r\n ></mt-table>\r\n </div>\r\n</ng-container>\r\n" }]
2484
2521
  }], ctorParameters: () => [] });
2485
2522
 
2523
+ class FormBuilderContextService {
2524
+ http = inject(HttpClient);
2525
+ baseUrl = 'context';
2526
+ /**
2527
+ * Resolves context data including navigation paths and properties.
2528
+ * No caching - always fetches fresh data.
2529
+ */
2530
+ resolveContext(contextKey, options = {}) {
2531
+ if (!contextKey)
2532
+ return of(null);
2533
+ return this.http
2534
+ .post(`${this.baseUrl}/resolve`, {
2535
+ contextKey,
2536
+ displayTags: options.displayTags,
2537
+ propertyFlags: options.propertyFlags,
2538
+ matchAllTags: options.matchAllTags,
2539
+ includeVirtual: options.includeVirtual,
2540
+ includeNavigationPaths: options.includeNavigationPaths,
2541
+ includeConnectionMetadata: options.includeConnectionMetadata,
2542
+ includeRelatedContextValues: options.includeRelatedContextValues,
2543
+ relatedContextsToInclude: options.relatedContextsToInclude,
2544
+ })
2545
+ .pipe(map((response) => this.normalizeResponse(response)), catchError((error) => {
2546
+ console.warn('Failed to resolve context:', error);
2547
+ return of(null);
2548
+ }));
2549
+ }
2550
+ /**
2551
+ * Gets properties for a context.
2552
+ * No caching - always fetches fresh data.
2553
+ */
2554
+ getProperties(contextKey, options = {}) {
2555
+ if (!contextKey)
2556
+ return of([]);
2557
+ return this.resolveContext(contextKey, options).pipe(map((resolved) => resolved?.properties ?? []));
2558
+ }
2559
+ normalizeResponse(response) {
2560
+ const payload = response?.data ?? response;
2561
+ if (!payload)
2562
+ return null;
2563
+ const properties = this.normalizeProperties(payload.properties ?? []);
2564
+ return {
2565
+ ...payload,
2566
+ properties,
2567
+ };
2568
+ }
2569
+ normalizeProperties(raw) {
2570
+ return (raw ?? [])
2571
+ .map((prop) => {
2572
+ const key = prop?.key ?? prop?.propertyKey;
2573
+ if (!key)
2574
+ return null;
2575
+ const name = prop?.name ?? prop?.displayName ?? key;
2576
+ return {
2577
+ ...prop,
2578
+ key,
2579
+ name,
2580
+ };
2581
+ })
2582
+ .filter((prop) => Boolean(prop));
2583
+ }
2584
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilderContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2585
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilderContextService, providedIn: 'root' });
2586
+ }
2587
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilderContextService, decorators: [{
2588
+ type: Injectable,
2589
+ args: [{ providedIn: 'root' }]
2590
+ }] });
2591
+
2592
+ const SCOPE_ORDER = [
2593
+ 'Current',
2594
+ 'Host',
2595
+ 'Parent',
2596
+ 'Ancestors',
2597
+ 'Children',
2598
+ 'Descendants',
2599
+ 'Related',
2600
+ 'Siblings',
2601
+ ];
2602
+ const PATH_TOKENS = [
2603
+ 'Parent',
2604
+ 'Ancestors',
2605
+ 'Children',
2606
+ 'Descendants',
2607
+ 'Related',
2608
+ 'Siblings',
2609
+ 'Host',
2610
+ ];
2611
+ const SCOPE_LABELS = {
2612
+ Current: 'Current',
2613
+ Host: 'Host',
2614
+ Parent: 'Parent',
2615
+ Ancestors: 'Ancestors',
2616
+ Children: 'Children',
2617
+ Descendants: 'Descendants',
2618
+ Related: 'Related',
2619
+ Siblings: 'Siblings',
2620
+ };
2621
+ // ============================================================================
2622
+ // Component
2623
+ // ============================================================================
2486
2624
  class FormBuilder {
2487
2625
  modalService = inject(ModalService);
2488
2626
  confirmationService = inject(ConfirmationService);
2489
2627
  translocoService = inject(TranslocoService);
2490
2628
  facade = inject(FormBuilderFacade);
2629
+ contextService = inject(FormBuilderContextService);
2630
+ popovers = viewChildren(Popover, ...(ngDevMode ? [{ debugName: "popovers" }] : []));
2491
2631
  dialogRef;
2492
- // Local UI state
2493
- activeTab = signal('system', ...(ngDevMode ? [{ debugName: "activeTab" }] : []));
2494
- searchQuery = signal('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : []));
2495
- // State from facade
2632
+ // ============================================================================
2633
+ // Facade State
2634
+ // ============================================================================
2496
2635
  sections = this.facade.sections;
2497
2636
  properties = this.facade.properties;
2498
2637
  isLoading = this.facade.isLoadingFormConfiguration;
2499
2638
  error = this.facade.formConfigurationError;
2500
- // Properties map for enrichment
2639
+ moduleType = this.facade.moduleType;
2640
+ moduleId = this.facade.moduleId;
2641
+ parentPath = this.facade.parentPath;
2642
+ // ============================================================================
2643
+ // UI State (Simple Signals)
2644
+ // ============================================================================
2645
+ activeScope = signal('Current', ...(ngDevMode ? [{ debugName: "activeScope" }] : []));
2646
+ searchQuery = signal('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : []));
2647
+ isContextLoading = signal(false, ...(ngDevMode ? [{ debugName: "isContextLoading" }] : []));
2648
+ // Initial context data (from first load)
2649
+ initialContext = signal(null, ...(ngDevMode ? [{ debugName: "initialContext" }] : []));
2650
+ currentProperties = signal([], ...(ngDevMode ? [{ debugName: "currentProperties" }] : []));
2651
+ // Non-current scope state (reset on tab change)
2652
+ scopeLoading = signal(false, ...(ngDevMode ? [{ debugName: "scopeLoading" }] : []));
2653
+ scopeProperties = signal([], ...(ngDevMode ? [{ debugName: "scopeProperties" }] : []));
2654
+ scopePath = signal([], ...(ngDevMode ? [{ debugName: "scopePath" }] : []));
2655
+ scopeSelectedKey = signal('', ...(ngDevMode ? [{ debugName: "scopeSelectedKey" }] : []));
2656
+ scopeNavigation = signal(null, ...(ngDevMode ? [{ debugName: "scopeNavigation" }] : []));
2657
+ // Request tracking
2658
+ initialRequestId = 0;
2659
+ scopeRequestId = 0;
2660
+ lastContextKey = '';
2661
+ // ============================================================================
2662
+ // Computed Values
2663
+ // ============================================================================
2664
+ contextKey = computed(() => {
2665
+ const type = this.moduleType();
2666
+ const id = this.moduleId();
2667
+ const parent = this.parentPath();
2668
+ if (!type || id === null || id === undefined)
2669
+ return '';
2670
+ const segments = [];
2671
+ const raw = parent.split('/').filter(Boolean);
2672
+ for (let i = 0; i < raw.length; i += 2) {
2673
+ if (raw[i] && raw[i + 1] !== undefined) {
2674
+ segments.push(`${raw[i]}:${raw[i + 1]}`);
2675
+ }
2676
+ }
2677
+ segments.push(`${type}:${id}`);
2678
+ return segments.join('/');
2679
+ }, ...(ngDevMode ? [{ debugName: "contextKey" }] : []));
2680
+ usedPropertyKeys = computed(() => {
2681
+ return new Set(this.sections().flatMap((s) => s.fields.map((f) => f.propertyKey)));
2682
+ }, ...(ngDevMode ? [{ debugName: "usedPropertyKeys" }] : []));
2501
2683
  propertiesMap = computed(() => {
2502
2684
  const map = new Map();
2503
2685
  for (const prop of this.properties()) {
2504
- map.set(prop.id, prop);
2686
+ map.set(prop.key, prop);
2505
2687
  }
2506
2688
  return map;
2507
2689
  }, ...(ngDevMode ? [{ debugName: "propertiesMap" }] : []));
2508
- // Enrich sections with property data for UI display
2690
+ /** Navigation options from initial context */
2691
+ baseScopeContexts = computed(() => {
2692
+ const ctx = this.initialContext();
2693
+ const nav = ctx?.navigationPaths;
2694
+ const currentKey = ctx?.currentState?.contextPath ?? this.contextKey();
2695
+ return {
2696
+ Current: currentKey
2697
+ ? [this.createContextOption(currentKey, ctx?.currentState)]
2698
+ : [],
2699
+ Host: nav?.hostContext
2700
+ ? [this.createContextOption(nav.hostContext)]
2701
+ : [],
2702
+ Parent: this.mergeContextOptions(nav?.parent),
2703
+ Ancestors: this.mergeContextOptions(nav?.ancestors),
2704
+ Children: this.mergeContextOptions(nav?.children),
2705
+ Descendants: this.mergeContextOptions(nav?.descendants),
2706
+ Related: this.mergeContextOptions(nav?.related),
2707
+ Siblings: this.mergeContextOptions(nav?.siblingContexts),
2708
+ };
2709
+ }, ...(ngDevMode ? [{ debugName: "baseScopeContexts" }] : []));
2710
+ /** Available scope tabs */
2711
+ scopeOptions = computed(() => {
2712
+ const contexts = this.baseScopeContexts();
2713
+ return SCOPE_ORDER.filter((s) => (contexts[s] ?? []).length > 0).map((s) => ({ key: s, label: SCOPE_LABELS[s] }));
2714
+ }, ...(ngDevMode ? [{ debugName: "scopeOptions" }] : []));
2715
+ /** Enriched sections for display */
2509
2716
  enrichedSections = computed(() => {
2510
2717
  const sections = this.sections();
2511
2718
  const propsMap = this.propertiesMap();
@@ -2513,119 +2720,427 @@ class FormBuilder {
2513
2720
  return sections.map((section) => ({
2514
2721
  ...section,
2515
2722
  fields: section.fields.map((field) => {
2516
- const prop = propsMap.get(field.propertyId);
2517
- const propName = prop?.name;
2518
- const name = typeof propName === 'string'
2519
- ? propName
2520
- : (propName?.[lang] ??
2521
- propName?.['en'] ??
2522
- `Property ${field.propertyId}`);
2723
+ const prop = field.property ?? propsMap.get(field.propertyKey);
2523
2724
  return {
2524
2725
  ...field,
2525
- name,
2726
+ name: this.resolvePropertyName(prop, field.propertyKey, lang),
2526
2727
  type: prop?.viewType || 'text',
2527
2728
  data: prop,
2528
2729
  };
2529
2730
  }),
2530
2731
  }));
2531
2732
  }, ...(ngDevMode ? [{ debugName: "enrichedSections" }] : []));
2532
- // Available properties for toolbox tabs
2533
- availableTabs = computed(() => {
2534
- const tabs = [];
2535
- const usedPropertyIds = new Set(this.sections().flatMap((s) => s.fields.map((f) => f.propertyId)));
2536
- const availableProps = this.properties().filter((p) => !usedPropertyIds.has(p.id));
2537
- const systemProps = availableProps.filter((p) => p['isSystem']);
2538
- const customProps = availableProps.filter((p) => !p['isSystem']);
2539
- if (systemProps.length > 0) {
2540
- tabs.push({ id: 'system', title: 'System', properties: systemProps });
2541
- }
2542
- if (customProps.length > 0) {
2543
- tabs.push({ id: 'custom', title: 'Custom', properties: customProps });
2544
- }
2545
- return tabs;
2546
- }, ...(ngDevMode ? [{ debugName: "availableTabs" }] : []));
2547
- // Filtered properties based on search query
2548
- filteredPropertiesByTab = computed(() => {
2549
- const query = this.searchQuery().toLowerCase().trim();
2550
- const tabs = this.availableTabs();
2551
- if (!query) {
2552
- return tabs;
2553
- }
2554
- return tabs.map((tab) => ({
2555
- ...tab,
2556
- properties: tab.properties.filter((prop) => {
2557
- const name = prop.name;
2558
- return name.toLowerCase().includes(query);
2559
- }),
2560
- }));
2561
- }, ...(ngDevMode ? [{ debugName: "filteredPropertiesByTab" }] : []));
2733
+ // ============================================================================
2734
+ // Lifecycle
2735
+ // ============================================================================
2562
2736
  constructor() {
2563
- // Update active tab when tabs change
2737
+ // Effect: watch for context key changes and load initial data
2564
2738
  effect(() => {
2565
- const tabs = this.availableTabs();
2566
- const currentTab = this.activeTab();
2567
- if (tabs.length > 0 && !tabs.some((t) => t.id === currentTab)) {
2568
- this.activeTab.set(tabs[0].id);
2739
+ const key = this.contextKey();
2740
+ if (key && key !== this.lastContextKey) {
2741
+ this.lastContextKey = key;
2742
+ this.loadInitialContext(key);
2743
+ }
2744
+ }, { allowSignalWrites: true });
2745
+ }
2746
+ ngOnInit() {
2747
+ // Trigger initial load if context key is already available
2748
+ const key = this.contextKey();
2749
+ if (key && key !== this.lastContextKey) {
2750
+ this.lastContextKey = key;
2751
+ this.loadInitialContext(key);
2752
+ }
2753
+ }
2754
+ // ============================================================================
2755
+ // Initial Context Loading
2756
+ // ============================================================================
2757
+ loadInitialContext(contextKey) {
2758
+ const requestId = ++this.initialRequestId;
2759
+ this.isContextLoading.set(true);
2760
+ this.initialContext.set(null);
2761
+ this.currentProperties.set([]);
2762
+ this.resetScopeState();
2763
+ this.activeScope.set('Current');
2764
+ this.contextService
2765
+ .resolveContext(contextKey, {
2766
+ propertyFlags: 'None',
2767
+ includeVirtual: true,
2768
+ includeNavigationPaths: true,
2769
+ includeConnectionMetadata: true,
2770
+ matchAllTags: true,
2771
+ includeRelatedContextValues: true,
2772
+ })
2773
+ .subscribe((response) => {
2774
+ if (requestId !== this.initialRequestId)
2775
+ return;
2776
+ this.isContextLoading.set(false);
2777
+ this.initialContext.set(response);
2778
+ this.currentProperties.set(response?.properties ?? []);
2779
+ this.facade.setProperties(response?.properties ?? []);
2780
+ });
2781
+ }
2782
+ // ============================================================================
2783
+ // Tab Change Handler (Called from template)
2784
+ // ============================================================================
2785
+ onScopeChange(scope) {
2786
+ if (scope === this.activeScope())
2787
+ return;
2788
+ this.activeScope.set(scope);
2789
+ this.resetScopeState();
2790
+ if (scope === 'Current') {
2791
+ return;
2792
+ }
2793
+ // Get first context option for this scope
2794
+ const options = this.baseScopeContexts()[scope] ?? [];
2795
+ if (options.length === 0)
2796
+ return;
2797
+ const firstOption = options[0];
2798
+ this.scopePath.set([
2799
+ {
2800
+ token: scope,
2801
+ value: firstOption.contextKey,
2802
+ label: firstOption.label,
2803
+ },
2804
+ ]);
2805
+ this.loadScopeContext(firstOption.contextKey);
2806
+ }
2807
+ // ============================================================================
2808
+ // Scope Context Loading
2809
+ // ============================================================================
2810
+ loadScopeContext(contextKey) {
2811
+ const requestId = ++this.scopeRequestId;
2812
+ this.scopeLoading.set(true);
2813
+ this.contextService
2814
+ .resolveContext(contextKey, {
2815
+ propertyFlags: 'None',
2816
+ includeVirtual: true,
2817
+ includeNavigationPaths: true,
2818
+ includeConnectionMetadata: true,
2819
+ matchAllTags: true,
2820
+ includeRelatedContextValues: true,
2821
+ })
2822
+ .subscribe((response) => {
2823
+ if (requestId !== this.scopeRequestId)
2824
+ return;
2825
+ this.scopeLoading.set(false);
2826
+ this.scopeNavigation.set(response);
2827
+ this.scopeProperties.set(response?.properties ?? []);
2828
+ // Auto-select first available property
2829
+ const used = this.usedPropertyKeys();
2830
+ const available = (response?.properties ?? []).filter((p) => !used.has(p.key));
2831
+ if (available.length > 0) {
2832
+ this.scopeSelectedKey.set(available[0].key);
2569
2833
  }
2570
2834
  });
2571
2835
  }
2836
+ resetScopeState() {
2837
+ this.scopeLoading.set(false);
2838
+ this.scopeProperties.set([]);
2839
+ this.scopePath.set([]);
2840
+ this.scopeSelectedKey.set('');
2841
+ this.scopeNavigation.set(null);
2842
+ this.hideAllPopovers();
2843
+ }
2844
+ // ============================================================================
2845
+ // Path Segment Methods (accept scope parameter for template compatibility)
2846
+ // ============================================================================
2847
+ getScopeBaseContexts(scope) {
2848
+ return this.baseScopeContexts()[scope] ?? [];
2849
+ }
2850
+ /** Template calls this with scope.key */
2851
+ getScopePath(_scope) {
2852
+ return this.scopePath();
2853
+ }
2854
+ getTokenLabel(token) {
2855
+ return SCOPE_LABELS[token] ?? token;
2856
+ }
2857
+ getScopeSegmentLabel(_scope, index) {
2858
+ const path = this.scopePath();
2859
+ const segment = path[index];
2860
+ if (!segment?.value)
2861
+ return `Select ${this.getTokenLabel(segment?.token)}`;
2862
+ // Use stored label - don't look up from options (which change on resolve)
2863
+ return segment.label || this.formatContextKey(segment.value);
2864
+ }
2865
+ getScopeSegmentOptions(_scope, index) {
2866
+ const path = this.scopePath();
2867
+ const segment = path[index];
2868
+ if (!segment)
2869
+ return [];
2870
+ if (index === 0) {
2871
+ return this.getScopeBaseContexts(this.activeScope());
2872
+ }
2873
+ const nav = this.scopeNavigation();
2874
+ return this.getNavigationOptionsByToken(nav, segment.token);
2875
+ }
2876
+ setScopeSegmentValue(_scope, index, contextKey, label) {
2877
+ const path = this.scopePath();
2878
+ const newPath = path.slice(0, index + 1);
2879
+ if (newPath[index]) {
2880
+ // Store label at selection time so it doesn't change on resolve
2881
+ const segmentLabel = label || this.findOptionLabel(_scope, index, contextKey);
2882
+ newPath[index] = {
2883
+ ...newPath[index],
2884
+ value: contextKey,
2885
+ label: segmentLabel,
2886
+ };
2887
+ }
2888
+ this.scopePath.set(newPath);
2889
+ this.loadScopeContext(contextKey);
2890
+ }
2891
+ findOptionLabel(_scope, index, contextKey) {
2892
+ const options = this.getScopeSegmentOptions(_scope, index);
2893
+ const option = options.find((o) => o.contextKey === contextKey);
2894
+ return option?.label ?? this.formatContextKey(contextKey);
2895
+ }
2896
+ /** Template handler: hide popover first, then set value (like formula-builder) */
2897
+ onSetScopeSegmentValue(scope, index, contextKey, popover) {
2898
+ popover.hide();
2899
+ this.setScopeSegmentValue(scope, index, contextKey);
2900
+ }
2901
+ addScopeSegment(_scope, token) {
2902
+ const path = this.scopePath();
2903
+ this.scopePath.set([...path, { token, value: '', label: '' }]);
2904
+ }
2905
+ /** Template handler: hide popover first, then add segment (like formula-builder) */
2906
+ onAddScopeSegment(scope, token, popover) {
2907
+ popover.hide();
2908
+ this.addScopeSegment(scope, token);
2909
+ }
2910
+ getScopeNextTokens(_scope) {
2911
+ const path = this.scopePath();
2912
+ const last = path[path.length - 1];
2913
+ if (!last?.value)
2914
+ return [];
2915
+ const nav = this.scopeNavigation();
2916
+ if (!nav)
2917
+ return [];
2918
+ return PATH_TOKENS.filter((t) => this.getNavigationOptionsByToken(nav, t).length > 0);
2919
+ }
2920
+ canAddNextScopeSegment(_scope) {
2921
+ const path = this.scopePath();
2922
+ if (path.length === 0)
2923
+ return false;
2924
+ if (!path.every((s) => Boolean(s.value)))
2925
+ return false;
2926
+ return this.getScopeNextTokens().length > 0;
2927
+ }
2928
+ canRemoveScopePath(_scope) {
2929
+ return this.scopePath().length > 1;
2930
+ }
2931
+ removeScopePath(_scope) {
2932
+ const path = this.scopePath();
2933
+ if (path.length <= 1)
2934
+ return;
2935
+ const newPath = path.slice(0, -1);
2936
+ this.scopePath.set(newPath);
2937
+ const last = newPath[newPath.length - 1];
2938
+ if (last?.value) {
2939
+ this.loadScopeContext(last.value);
2940
+ }
2941
+ }
2942
+ /** Template-compatible version (returns properties array for the scope) */
2943
+ getScopeProperties(_scope) {
2944
+ const scope = _scope ?? this.activeScope();
2945
+ return scope === 'Current'
2946
+ ? this.currentProperties()
2947
+ : this.scopeProperties();
2948
+ }
2949
+ // ============================================================================
2950
+ // Property Methods (accept scope parameter for template compatibility)
2951
+ // ============================================================================
2952
+ getScopePropertyOptions(_scope) {
2953
+ const scope = _scope ?? this.activeScope();
2954
+ const props = scope === 'Current' ? this.currentProperties() : this.scopeProperties();
2955
+ const used = this.usedPropertyKeys();
2956
+ return props
2957
+ .filter((p) => !used.has(p.key))
2958
+ .map((p) => ({ key: p.key, name: this.getPropertyLabel(p) }));
2959
+ }
2960
+ getScopeSelectedPropertyKey(_scope) {
2961
+ return this.scopeSelectedKey();
2962
+ }
2963
+ setScopeSelectedProperty(_scope, key) {
2964
+ this.scopeSelectedKey.set(key);
2965
+ }
2966
+ getScopeSelectedProperty(_scope) {
2967
+ const key = this.scopeSelectedKey();
2968
+ if (!key)
2969
+ return null;
2970
+ return this.scopeProperties().find((p) => p.key === key) ?? null;
2971
+ }
2972
+ getFilteredProperties(_scope) {
2973
+ const scope = _scope ?? this.activeScope();
2974
+ const used = this.usedPropertyKeys();
2975
+ const query = this.searchQuery().toLowerCase().trim();
2976
+ if (scope !== 'Current') {
2977
+ const selected = this.getScopeSelectedProperty();
2978
+ if (!selected || used.has(selected.key))
2979
+ return [];
2980
+ return [selected];
2981
+ }
2982
+ let available = this.currentProperties().filter((p) => !used.has(p.key));
2983
+ if (query) {
2984
+ available = available.filter((p) => this.getPropertyLabel(p).toLowerCase().includes(query));
2985
+ }
2986
+ return available;
2987
+ }
2988
+ isScopePropertiesLoading(_scope) {
2989
+ const scope = _scope ?? this.activeScope();
2990
+ return scope !== 'Current' && this.scopeLoading();
2991
+ }
2992
+ getPropertyLabel(property) {
2993
+ const lang = document.documentElement.lang;
2994
+ return this.resolvePropertyName(property, property.key, lang);
2995
+ }
2996
+ // ============================================================================
2997
+ // Helper Methods
2998
+ // ============================================================================
2999
+ hideAllPopovers() {
3000
+ for (const popover of this.popovers()) {
3001
+ popover.hide();
3002
+ }
3003
+ }
3004
+ getNavigationOptionsByToken(response, token) {
3005
+ const nav = response?.navigationPaths;
3006
+ if (!nav)
3007
+ return [];
3008
+ switch (token) {
3009
+ case 'Parent':
3010
+ return this.mergeContextOptions(nav.parent);
3011
+ case 'Ancestors':
3012
+ return this.mergeContextOptions(nav.ancestors);
3013
+ case 'Children':
3014
+ return this.mergeContextOptions(nav.children);
3015
+ case 'Descendants':
3016
+ return this.mergeContextOptions(nav.descendants);
3017
+ case 'Related':
3018
+ return this.mergeContextOptions(nav.related);
3019
+ case 'Siblings':
3020
+ return this.mergeContextOptions(nav.siblingContexts);
3021
+ case 'Host':
3022
+ return nav.hostContext
3023
+ ? [this.createContextOption(nav.hostContext)]
3024
+ : [];
3025
+ default:
3026
+ return [];
3027
+ }
3028
+ }
3029
+ resolvePropertyName(property, fallbackKey, lang) {
3030
+ if (!property)
3031
+ return `Property ${fallbackKey}`;
3032
+ const name = property.name;
3033
+ if (typeof name === 'string')
3034
+ return name;
3035
+ const display = name?.['display'];
3036
+ if (typeof display === 'string' && display.trim())
3037
+ return display;
3038
+ const localized = name?.[lang] ?? name?.['en'] ?? name?.['ar'];
3039
+ if (typeof localized === 'string' && localized.trim())
3040
+ return localized;
3041
+ return property.key ?? `Property ${fallbackKey}`;
3042
+ }
3043
+ createContextOption(pathOrKey, currentState) {
3044
+ if (typeof pathOrKey === 'string') {
3045
+ return {
3046
+ contextKey: pathOrKey,
3047
+ label: this.formatContextLabel({
3048
+ contextKey: pathOrKey,
3049
+ contextType: currentState?.contextType,
3050
+ contextId: currentState?.contextId,
3051
+ }),
3052
+ };
3053
+ }
3054
+ return {
3055
+ contextKey: pathOrKey.contextKey,
3056
+ label: this.formatContextLabel(pathOrKey),
3057
+ };
3058
+ }
3059
+ formatContextLabel(path) {
3060
+ // Just return the name - no path formatting needed
3061
+ if (path.name)
3062
+ return path.name;
3063
+ if (path.key)
3064
+ return path.key;
3065
+ if (path.contextType && path.contextId !== undefined)
3066
+ return `${path.contextType} ${path.contextId}`;
3067
+ return path.contextKey || 'Context';
3068
+ }
3069
+ formatContextKey(contextKey) {
3070
+ return contextKey
3071
+ .split('/')
3072
+ .filter(Boolean)
3073
+ .map((seg) => {
3074
+ const [type, id] = seg.split(':');
3075
+ return id !== undefined ? `${type} ${id}` : type;
3076
+ })
3077
+ .join(' / ');
3078
+ }
3079
+ mergeContextOptions(items) {
3080
+ if (!items?.length)
3081
+ return [];
3082
+ const seen = new Set();
3083
+ return items
3084
+ .slice()
3085
+ .sort((a, b) => (a.distance ?? 0) - (b.distance ?? 0))
3086
+ .filter((item) => {
3087
+ if (!item?.contextKey || seen.has(item.contextKey))
3088
+ return false;
3089
+ seen.add(item.contextKey);
3090
+ return true;
3091
+ })
3092
+ .map((item) => this.createContextOption(item));
3093
+ }
3094
+ // ============================================================================
3095
+ // Drag & Drop
3096
+ // ============================================================================
2572
3097
  drop(event) {
2573
3098
  const targetSectionId = event.container.id;
2574
3099
  if (event.previousContainer === event.container) {
2575
- // Reordering within the same section
2576
3100
  const fields = [...event.container.data];
2577
3101
  const [movedField] = fields.splice(event.previousIndex, 1);
2578
3102
  fields.splice(event.currentIndex, 0, movedField);
2579
- // Build bulk reorder payload with new order values
2580
- const reorderPayload = fields.map((field, index) => ({
2581
- id: field.id,
2582
- order: index,
2583
- }));
2584
- this.facade.reorderFields(targetSectionId, reorderPayload);
3103
+ this.facade.reorderFields(targetSectionId, fields.map((f, i) => ({ id: f.id, order: i })));
2585
3104
  }
2586
3105
  else if (event.previousContainer.id.startsWith('toolbox-')) {
2587
- // Adding from toolbox
2588
- const propertyItem = event.item.data;
3106
+ const prop = event.item.data;
2589
3107
  this.facade.addField(targetSectionId, {
2590
- propertyId: propertyItem.id,
3108
+ propertyKey: prop.key,
2591
3109
  width: '100',
2592
3110
  order: event.currentIndex,
2593
3111
  hiddenInCreation: false,
2594
3112
  });
2595
3113
  }
2596
3114
  else {
2597
- // Moving between sections
2598
- const sourceSectionId = event.previousContainer.id;
2599
3115
  const field = event.item.data;
2600
- this.facade.moveField(sourceSectionId, field.id, {
3116
+ this.facade.moveField(event.previousContainer.id, field.id, {
2601
3117
  targetSectionId,
2602
3118
  order: event.currentIndex,
2603
3119
  });
2604
3120
  }
2605
3121
  }
3122
+ // ============================================================================
3123
+ // Modal Actions
3124
+ // ============================================================================
2606
3125
  addSection() {
2607
3126
  this.dialogRef = this.modalService.openModal(FBSectionForm, 'drawer', {
2608
3127
  header: this.translocoService.translate('formBuilder.add-section'),
2609
3128
  height: '20vw',
2610
- styleClass: '!w-[27%] !absolute !shadow-none ',
3129
+ styleClass: '!w-[27%] !absolute !shadow-none',
2611
3130
  position: 'end',
2612
3131
  appendTo: '#page-content',
2613
3132
  dismissible: true,
2614
- inputValues: {
2615
- sectionsCount: this.sections().length,
2616
- },
3133
+ inputValues: { sectionsCount: this.sections().length },
2617
3134
  });
2618
3135
  }
2619
3136
  openPreview() {
2620
3137
  this.dialogRef = this.modalService.openModal(FBPreviewForm, 'drawer', {
2621
3138
  header: this.translocoService.translate('formBuilder.preview'),
2622
- styleClass: '!w-[79%] !absolute !shadow-none ',
3139
+ styleClass: '!w-[79%] !absolute !shadow-none',
2623
3140
  position: 'end',
2624
3141
  appendTo: 'page-content',
2625
3142
  dismissible: true,
2626
- inputValues: {
2627
- sections: this.enrichedSections(),
2628
- },
3143
+ inputValues: { sections: this.enrichedSections() },
2629
3144
  });
2630
3145
  }
2631
3146
  openValidationRules() {
@@ -2641,14 +3156,12 @@ class FormBuilder {
2641
3156
  this.confirmationService.confirm({
2642
3157
  type: 'dialog',
2643
3158
  acceptButtonStyleClass: 'p-button-danger',
2644
- accept: () => {
2645
- this.facade.resetFormConfiguration();
2646
- },
3159
+ accept: () => this.facade.resetFormConfiguration(),
2647
3160
  });
2648
3161
  }
2649
3162
  noReturnPredicate = () => false;
2650
3163
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
2651
- 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-7 h-full w-full overflow-hidden\" cdkDropListGroup>\r\n <!-- Properties Sidebar -->\r\n <mt-card class=\"w-1/5 h-full flex flex-col overflow-hidden\">\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <h3 class=\"text-xl font-semibold px-4 pt-5\">\r\n {{ t(\"form-elements\") }}\r\n </h3>\r\n\r\n @if (properties().length === 0) {\r\n @if (isLoading()) {\r\n <!-- Properties Loading Skeleton -->\r\n <div class=\"flex gap-4 py-3 px-5 mt-4\">\r\n <p-skeleton height=\"2rem\" styleClass=\"rounded-lg\" />\r\n <p-skeleton height=\"2rem\" styleClass=\"rounded-lg\" />\r\n </div>\r\n <div class=\"py-4 px-5 space-y-5\">\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 <p class=\"text-sm font-semibold text-gray-500\">\r\n {{ t(\"no-data-found\") }}\r\n </p>\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\r\n class=\"!bg-transparent !p-0 !pb-3 flex-1 overflow-hidden\"\r\n >\r\n @for (tab of filteredPropertiesByTab(); track tab.id) {\r\n <p-tabpanel [value]=\"tab.id\" class=\"h-full flex flex-col\">\r\n <!-- Node List -->\r\n <div\r\n class=\"space-y-4 px-4 pb-4 [&_.cdk-drag-placeholder]:hidden flex-1 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 <!-- Search Field (Sticky) -->\r\n <div class=\"sticky top-0 bg-surface-0 mb-0 py-3 pb-2 mb-1\">\r\n <mt-text-field\r\n [placeholder]=\"t('search-properties')\"\r\n [(ngModel)]=\"searchQuery\"\r\n icon=\"general.search-lg\"\r\n />\r\n </div>\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 flex items-center gap-3 py-3 px-3 rounded-lg border border-dashed border-surface-300 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\r\n <mt-icon\r\n class=\"text-lg\"\r\n icon=\"general.menu-05\"\r\n ></mt-icon>\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 @if (searchQuery()) {\r\n {{ t(\"no-data-found\") }}\r\n } @else {\r\n All {{ tab.title }} items are in use\r\n }\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-1 gap-4 h-full overflow-y-auto\">\r\n <div class=\"flex flex-col w-2/3 gap-4 h-full\">\r\n <mt-card>\r\n <ng-template #headless>\r\n <div class=\"p-4 flex items-center gap-3\">\r\n <mt-button\r\n icon=\"layout.layout-top\"\r\n [label]=\"t('add-section')\"\r\n variant=\"outlined\"\r\n severity=\"primary\"\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 variant=\"outlined\"\r\n severity=\"primary\"\r\n (onClick)=\"openPreview()\"\r\n [disabled]=\"isLoading()\"\r\n ></mt-button>\r\n <mt-button\r\n [label]=\"t('validation-rules')\"\r\n variant=\"outlined\"\r\n severity=\"primary\"\r\n (onClick)=\"openValidationRules()\"\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 variant=\"outlined\"\r\n severity=\"primary\"\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-1 border-primary rounded-xl bg-primary-50 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 </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: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i2.Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i2.TabPanels, selector: "p-tabpanels" }, { kind: "component", type: i2.TabPanel, selector: "p-tabpanel", inputs: ["lazy", "value"], outputs: ["valueChange"] }, { kind: "component", type: i2.TabList, selector: "p-tablist" }, { kind: "component", type: i2.Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i3.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: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "icon", "iconPosition"] }, { 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: i4.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: i4.CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "directive", type: i4.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: i4.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "pipe", type: i5.TitleCasePipe, name: "titlecase" }] });
3164
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: FormBuilder, isStandalone: true, selector: "mt-form-builder", viewQueries: [{ propertyName: "popovers", predicate: Popover, descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div class=\"flex gap-7 h-full w-full overflow-hidden\" cdkDropListGroup>\r\n <!-- Properties Sidebar -->\r\n <mt-card class=\"w-1/5 h-full flex flex-col overflow-hidden\">\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <h3 class=\"text-xl font-semibold px-4 pt-5\">\r\n {{ t(\"form-elements\") }}\r\n </h3>\r\n\r\n @if (isContextLoading()) {\r\n <!-- Properties Loading Skeleton -->\r\n <div class=\"flex gap-4 py-3 px-5 mt-4\">\r\n <p-skeleton height=\"2rem\" styleClass=\"rounded-lg\" />\r\n <p-skeleton height=\"2rem\" styleClass=\"rounded-lg\" />\r\n </div>\r\n <div class=\"py-4 px-5 space-y-5\">\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 if (scopeOptions().length === 0) {\r\n <!-- No Properties State -->\r\n <div class=\"flex-1 flex items-center justify-center p-4\">\r\n <p class=\"text-sm font-semibold text-gray-500\">\r\n {{ t(\"no-data-found\") }}\r\n </p>\r\n </div>\r\n } @else {\r\n <!-- Scope Tabs using PrimeNG -->\r\n <p-tabs\r\n [value]=\"activeScope()\"\r\n (valueChange)=\"onScopeChange($event)\"\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 (scope of scopeOptions(); track scope.key) {\r\n <p-tab [value]=\"scope.key\">{{ scope.label }}</p-tab>\r\n }\r\n </p-tablist>\r\n <p-tabpanels\r\n class=\"!bg-transparent !p-0 !pb-3 flex-1 overflow-hidden\"\r\n >\r\n @for (scope of scopeOptions(); track scope.key) {\r\n <p-tabpanel [value]=\"scope.key\" class=\"h-full flex flex-col\">\r\n @if (scope.key !== \"Current\") {\r\n <div class=\"px-4 pt-3 pb-2\">\r\n <div\r\n class=\"flex flex-col gap-3 rounded-lg bg-slate-50 px-3 py-2 dark:bg-slate-800/50\"\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <span\r\n class=\"shrink-0 text-xs font-semibold uppercase tracking-wider text-slate-400\"\r\n >\r\n Path\r\n </span>\r\n <div\r\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\r\n ></div>\r\n <span class=\"text-xs font-semibold text-slate-600\">\r\n {{ scope.label }}\r\n </span>\r\n </div>\r\n @if (getScopeBaseContexts(scope.key).length === 0) {\r\n <span class=\"text-xs italic text-slate-400\">\r\n No context available\r\n </span>\r\n } @else {\r\n <div class=\"flex flex-col gap-1.5\">\r\n @for (\r\n segment of getScopePath(scope.key);\r\n track $index;\r\n let segmentIndex = $index;\r\n let isLast = $last\r\n ) {\r\n <div\r\n class=\"flex items-center gap-1 py-0.5 px-2 border border-gray-200 rounded bg-white dark:bg-slate-800 dark:border-slate-600\"\r\n >\r\n <div\r\n class=\"flex-1 min-w-0 text-xs font-medium text-slate-600 dark:text-slate-300 truncate cursor-pointer\"\r\n (click)=\"segmentPopover.toggle($event)\"\r\n >\r\n {{\r\n getScopeSegmentLabel(\r\n scope.key,\r\n segmentIndex\r\n )\r\n }}\r\n </div>\r\n <mt-button\r\n type=\"button\"\r\n icon=\"arrow.chevron-down\"\r\n [outlined]=\"true\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n styleClass=\"!p-0.5 !min-w-0\"\r\n (onClick)=\"segmentPopover.toggle($event)\"\r\n ></mt-button>\r\n @if (isLast && canRemoveScopePath(scope.key)) {\r\n <mt-button\r\n type=\"button\"\r\n icon=\"general.trash-01\"\r\n [outlined]=\"true\"\r\n size=\"small\"\r\n severity=\"danger\"\r\n styleClass=\"!p-0.5 !min-w-0\"\r\n (onClick)=\"removeScopePath(scope.key)\"\r\n ></mt-button>\r\n }\r\n </div>\r\n <p-popover\r\n #segmentPopover\r\n [style]=\"{ width: 'max-content' }\"\r\n [dismissable]=\"true\"\r\n >\r\n <div class=\"p-2\">\r\n <div\r\n class=\"mb-2 text-xs font-semibold uppercase text-slate-400\"\r\n >\r\n Select {{ getTokenLabel(segment.token) }}\r\n </div>\r\n @if (\r\n getScopeSegmentOptions(\r\n scope.key,\r\n segmentIndex\r\n ).length === 0\r\n ) {\r\n <div\r\n class=\"text-xs font-medium text-slate-400\"\r\n >\r\n No options\r\n </div>\r\n } @else {\r\n <div class=\"flex flex-col gap-1\">\r\n @for (\r\n option of getScopeSegmentOptions(\r\n scope.key,\r\n segmentIndex\r\n );\r\n track option.contextKey\r\n ) {\r\n <mt-button\r\n type=\"button\"\r\n [label]=\"option.label\"\r\n [icon]=\"\r\n option.contextKey === segment.value\r\n ? 'general.check'\r\n : 'general.minus'\r\n \"\r\n [iconPos]=\"'end'\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n styleClass=\"w-full justify-start rounded-md px-2.5 py-1.5 text-left text-xs font-medium transition-colors text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700\"\r\n (onClick)=\"\r\n onSetScopeSegmentValue(\r\n scope.key,\r\n segmentIndex,\r\n option.contextKey,\r\n segmentPopover\r\n )\r\n \"\r\n ></mt-button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </p-popover>\r\n }\r\n @if (canAddNextScopeSegment(scope.key)) {\r\n <div class=\"flex items-center gap-2\">\r\n <mt-button\r\n type=\"button\"\r\n icon=\"general.plus\"\r\n size=\"small\"\r\n severity=\"primary\"\r\n styleClass=\"flex size-7 shrink-0 items-center justify-center rounded-md bg-primary text-xs font-bold text-white hover:opacity-90\"\r\n (onClick)=\"nextContextPopover.toggle($event)\"\r\n ></mt-button>\r\n <span class=\"text-xs text-slate-400\">\r\n Add segment\r\n </span>\r\n </div>\r\n }\r\n <!-- Popover OUTSIDE the @if block like formula-builder -->\r\n <p-popover\r\n #nextContextPopover\r\n [style]=\"{ width: 'max-content' }\"\r\n [dismissable]=\"true\"\r\n appendTo=\"body\"\r\n >\r\n <div class=\"p-2\">\r\n <div\r\n class=\"mb-2 text-xs font-semibold uppercase text-slate-400\"\r\n >\r\n Add Segment\r\n </div>\r\n <div class=\"flex flex-col gap-1\">\r\n @for (\r\n token of getScopeNextTokens(scope.key);\r\n track token\r\n ) {\r\n <mt-button\r\n type=\"button\"\r\n [label]=\"getTokenLabel(token)\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n styleClass=\"w-full justify-start rounded-md px-2.5 py-1.5 text-left text-xs font-medium transition-colors text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700\"\r\n (onClick)=\"\r\n onAddScopeSegment(\r\n scope.key,\r\n token,\r\n nextContextPopover\r\n )\r\n \"\r\n ></mt-button>\r\n }\r\n </div>\r\n </div>\r\n </p-popover>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Node List -->\r\n <div\r\n class=\"space-y-4 px-4 pb-4 [&_.cdk-drag-placeholder]:hidden flex-1 overflow-y-auto\"\r\n [id]=\"'toolbox-' + scope.key\"\r\n cdkDropList\r\n cdkDropListSortingDisabled\r\n [cdkDropListData]=\"getFilteredProperties(scope.key)\"\r\n [cdkDropListEnterPredicate]=\"noReturnPredicate\"\r\n >\r\n @if (scope.key === \"Current\") {\r\n <!-- Search Field (Sticky) -->\r\n <div\r\n class=\"sticky top-0 bg-surface-0 mb-0 py-3 pb-2 mb-1\"\r\n >\r\n <mt-text-field\r\n [placeholder]=\"t('search-properties')\"\r\n [(ngModel)]=\"searchQuery\"\r\n icon=\"general.search-lg\"\r\n />\r\n </div>\r\n } @else {\r\n <div class=\"sticky top-0 bg-surface-0 py-3\">\r\n @if (isScopePropertiesLoading(scope.key)) {\r\n <p-skeleton height=\"2.5rem\" styleClass=\"rounded-lg\" />\r\n } @else {\r\n <mt-select-field\r\n class=\"w-full\"\r\n [label]=\"''\"\r\n [filter]=\"true\"\r\n [hasPlaceholderPrefix]=\"false\"\r\n placeholder=\"Select...\"\r\n [ngModel]=\"getScopeSelectedPropertyKey(scope.key)\"\r\n (ngModelChange)=\"\r\n setScopeSelectedProperty(scope.key, $event)\r\n \"\r\n [options]=\"getScopePropertyOptions(scope.key)\"\r\n optionLabel=\"name\"\r\n optionValue=\"key\"\r\n [size]=\"'small'\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n @if (isScopePropertiesLoading(scope.key)) {\r\n <div class=\"space-y-4\">\r\n @for (i of [1, 2, 3, 4]; track i) {\r\n <p-skeleton height=\"3rem\" styleClass=\"rounded-lg\" />\r\n }\r\n </div>\r\n } @else {\r\n @for (\r\n node of getFilteredProperties(scope.key);\r\n track node.key\r\n ) {\r\n <div\r\n cdkDrag\r\n [cdkDragData]=\"node\"\r\n class=\"group cursor-move select-none flex items-center gap-3 py-3 px-3 rounded-lg border border-dashed border-surface-300 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\r\n class=\"flex-1 text-start text-base font-medium\"\r\n >{{ getPropertyLabel(node) }}</span\r\n >\r\n\r\n <mt-icon\r\n class=\"text-lg\"\r\n icon=\"general.menu-05\"\r\n ></mt-icon>\r\n </div>\r\n }\r\n\r\n @if (getFilteredProperties(scope.key).length === 0) {\r\n <div class=\"py-8 text-center text-muted-color\">\r\n <p class=\"text-sm\">\r\n @if (scope.key === \"Current\" && searchQuery()) {\r\n {{ t(\"no-data-found\") }}\r\n } @else if (scope.key !== \"Current\") {\r\n Select a property\r\n } @else if (\r\n getScopeProperties(scope.key).length === 0\r\n ) {\r\n No properties available\r\n } @else {\r\n All items are in use\r\n }\r\n </p>\r\n </div>\r\n }\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-1 gap-4 h-full overflow-y-auto\">\r\n <div class=\"flex flex-col w-2/3 gap-4 h-full\">\r\n <mt-card>\r\n <ng-template #headless>\r\n <div class=\"p-4 flex items-center gap-3\">\r\n <mt-button\r\n icon=\"layout.layout-top\"\r\n [label]=\"t('add-section')\"\r\n variant=\"outlined\"\r\n severity=\"primary\"\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 variant=\"outlined\"\r\n severity=\"primary\"\r\n (onClick)=\"openPreview()\"\r\n [disabled]=\"isLoading()\"\r\n ></mt-button>\r\n <mt-button\r\n [label]=\"t('validation-rules')\"\r\n variant=\"outlined\"\r\n severity=\"primary\"\r\n (onClick)=\"openValidationRules()\"\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 variant=\"outlined\"\r\n severity=\"primary\"\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-1 border-primary rounded-xl bg-primary-50 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 </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: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i2.Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i2.TabPanels, selector: "p-tabpanels" }, { kind: "component", type: i2.TabPanel, selector: "p-tabpanel", inputs: ["lazy", "value"], outputs: ["valueChange"] }, { kind: "component", type: i2.TabList, selector: "p-tablist" }, { kind: "component", type: i2.Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i3.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i4.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { 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: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "icon", "iconPosition"] }, { kind: "component", type: SelectField, selector: "mt-select-field", inputs: ["field", "label", "placeholder", "hasPlaceholderPrefix", "class", "readonly", "pInputs", "options", "optionValue", "optionLabel", "filter", "filterBy", "dataKey", "showClear", "clearAfterSelect", "required", "group", "size", "optionGroupLabel", "optionGroupChildren", "loading"], outputs: ["onChange"] }, { 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: i5.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: i5.CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "directive", type: i5.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: i5.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }] });
2652
3165
  }
2653
3166
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilder, decorators: [{
2654
3167
  type: Component,
@@ -2657,15 +3170,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
2657
3170
  FormsModule,
2658
3171
  TabsModule,
2659
3172
  SkeletonModule,
3173
+ PopoverModule,
2660
3174
  Button,
2661
3175
  Card,
2662
3176
  TextField,
3177
+ SelectField,
2663
3178
  TranslocoDirective,
2664
3179
  FBSection,
2665
3180
  DragDropModule,
2666
3181
  Icon,
2667
- ], template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div class=\"flex gap-7 h-full w-full overflow-hidden\" cdkDropListGroup>\r\n <!-- Properties Sidebar -->\r\n <mt-card class=\"w-1/5 h-full flex flex-col overflow-hidden\">\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <h3 class=\"text-xl font-semibold px-4 pt-5\">\r\n {{ t(\"form-elements\") }}\r\n </h3>\r\n\r\n @if (properties().length === 0) {\r\n @if (isLoading()) {\r\n <!-- Properties Loading Skeleton -->\r\n <div class=\"flex gap-4 py-3 px-5 mt-4\">\r\n <p-skeleton height=\"2rem\" styleClass=\"rounded-lg\" />\r\n <p-skeleton height=\"2rem\" styleClass=\"rounded-lg\" />\r\n </div>\r\n <div class=\"py-4 px-5 space-y-5\">\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 <p class=\"text-sm font-semibold text-gray-500\">\r\n {{ t(\"no-data-found\") }}\r\n </p>\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\r\n class=\"!bg-transparent !p-0 !pb-3 flex-1 overflow-hidden\"\r\n >\r\n @for (tab of filteredPropertiesByTab(); track tab.id) {\r\n <p-tabpanel [value]=\"tab.id\" class=\"h-full flex flex-col\">\r\n <!-- Node List -->\r\n <div\r\n class=\"space-y-4 px-4 pb-4 [&_.cdk-drag-placeholder]:hidden flex-1 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 <!-- Search Field (Sticky) -->\r\n <div class=\"sticky top-0 bg-surface-0 mb-0 py-3 pb-2 mb-1\">\r\n <mt-text-field\r\n [placeholder]=\"t('search-properties')\"\r\n [(ngModel)]=\"searchQuery\"\r\n icon=\"general.search-lg\"\r\n />\r\n </div>\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 flex items-center gap-3 py-3 px-3 rounded-lg border border-dashed border-surface-300 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\r\n <mt-icon\r\n class=\"text-lg\"\r\n icon=\"general.menu-05\"\r\n ></mt-icon>\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 @if (searchQuery()) {\r\n {{ t(\"no-data-found\") }}\r\n } @else {\r\n All {{ tab.title }} items are in use\r\n }\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-1 gap-4 h-full overflow-y-auto\">\r\n <div class=\"flex flex-col w-2/3 gap-4 h-full\">\r\n <mt-card>\r\n <ng-template #headless>\r\n <div class=\"p-4 flex items-center gap-3\">\r\n <mt-button\r\n icon=\"layout.layout-top\"\r\n [label]=\"t('add-section')\"\r\n variant=\"outlined\"\r\n severity=\"primary\"\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 variant=\"outlined\"\r\n severity=\"primary\"\r\n (onClick)=\"openPreview()\"\r\n [disabled]=\"isLoading()\"\r\n ></mt-button>\r\n <mt-button\r\n [label]=\"t('validation-rules')\"\r\n variant=\"outlined\"\r\n severity=\"primary\"\r\n (onClick)=\"openValidationRules()\"\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 variant=\"outlined\"\r\n severity=\"primary\"\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-1 border-primary rounded-xl bg-primary-50 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 </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"] }]
2668
- }], ctorParameters: () => [] });
3182
+ ], template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div class=\"flex gap-7 h-full w-full overflow-hidden\" cdkDropListGroup>\r\n <!-- Properties Sidebar -->\r\n <mt-card class=\"w-1/5 h-full flex flex-col overflow-hidden\">\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <h3 class=\"text-xl font-semibold px-4 pt-5\">\r\n {{ t(\"form-elements\") }}\r\n </h3>\r\n\r\n @if (isContextLoading()) {\r\n <!-- Properties Loading Skeleton -->\r\n <div class=\"flex gap-4 py-3 px-5 mt-4\">\r\n <p-skeleton height=\"2rem\" styleClass=\"rounded-lg\" />\r\n <p-skeleton height=\"2rem\" styleClass=\"rounded-lg\" />\r\n </div>\r\n <div class=\"py-4 px-5 space-y-5\">\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 if (scopeOptions().length === 0) {\r\n <!-- No Properties State -->\r\n <div class=\"flex-1 flex items-center justify-center p-4\">\r\n <p class=\"text-sm font-semibold text-gray-500\">\r\n {{ t(\"no-data-found\") }}\r\n </p>\r\n </div>\r\n } @else {\r\n <!-- Scope Tabs using PrimeNG -->\r\n <p-tabs\r\n [value]=\"activeScope()\"\r\n (valueChange)=\"onScopeChange($event)\"\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 (scope of scopeOptions(); track scope.key) {\r\n <p-tab [value]=\"scope.key\">{{ scope.label }}</p-tab>\r\n }\r\n </p-tablist>\r\n <p-tabpanels\r\n class=\"!bg-transparent !p-0 !pb-3 flex-1 overflow-hidden\"\r\n >\r\n @for (scope of scopeOptions(); track scope.key) {\r\n <p-tabpanel [value]=\"scope.key\" class=\"h-full flex flex-col\">\r\n @if (scope.key !== \"Current\") {\r\n <div class=\"px-4 pt-3 pb-2\">\r\n <div\r\n class=\"flex flex-col gap-3 rounded-lg bg-slate-50 px-3 py-2 dark:bg-slate-800/50\"\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <span\r\n class=\"shrink-0 text-xs font-semibold uppercase tracking-wider text-slate-400\"\r\n >\r\n Path\r\n </span>\r\n <div\r\n class=\"h-4 w-px shrink-0 bg-slate-200 dark:bg-slate-700\"\r\n ></div>\r\n <span class=\"text-xs font-semibold text-slate-600\">\r\n {{ scope.label }}\r\n </span>\r\n </div>\r\n @if (getScopeBaseContexts(scope.key).length === 0) {\r\n <span class=\"text-xs italic text-slate-400\">\r\n No context available\r\n </span>\r\n } @else {\r\n <div class=\"flex flex-col gap-1.5\">\r\n @for (\r\n segment of getScopePath(scope.key);\r\n track $index;\r\n let segmentIndex = $index;\r\n let isLast = $last\r\n ) {\r\n <div\r\n class=\"flex items-center gap-1 py-0.5 px-2 border border-gray-200 rounded bg-white dark:bg-slate-800 dark:border-slate-600\"\r\n >\r\n <div\r\n class=\"flex-1 min-w-0 text-xs font-medium text-slate-600 dark:text-slate-300 truncate cursor-pointer\"\r\n (click)=\"segmentPopover.toggle($event)\"\r\n >\r\n {{\r\n getScopeSegmentLabel(\r\n scope.key,\r\n segmentIndex\r\n )\r\n }}\r\n </div>\r\n <mt-button\r\n type=\"button\"\r\n icon=\"arrow.chevron-down\"\r\n [outlined]=\"true\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n styleClass=\"!p-0.5 !min-w-0\"\r\n (onClick)=\"segmentPopover.toggle($event)\"\r\n ></mt-button>\r\n @if (isLast && canRemoveScopePath(scope.key)) {\r\n <mt-button\r\n type=\"button\"\r\n icon=\"general.trash-01\"\r\n [outlined]=\"true\"\r\n size=\"small\"\r\n severity=\"danger\"\r\n styleClass=\"!p-0.5 !min-w-0\"\r\n (onClick)=\"removeScopePath(scope.key)\"\r\n ></mt-button>\r\n }\r\n </div>\r\n <p-popover\r\n #segmentPopover\r\n [style]=\"{ width: 'max-content' }\"\r\n [dismissable]=\"true\"\r\n >\r\n <div class=\"p-2\">\r\n <div\r\n class=\"mb-2 text-xs font-semibold uppercase text-slate-400\"\r\n >\r\n Select {{ getTokenLabel(segment.token) }}\r\n </div>\r\n @if (\r\n getScopeSegmentOptions(\r\n scope.key,\r\n segmentIndex\r\n ).length === 0\r\n ) {\r\n <div\r\n class=\"text-xs font-medium text-slate-400\"\r\n >\r\n No options\r\n </div>\r\n } @else {\r\n <div class=\"flex flex-col gap-1\">\r\n @for (\r\n option of getScopeSegmentOptions(\r\n scope.key,\r\n segmentIndex\r\n );\r\n track option.contextKey\r\n ) {\r\n <mt-button\r\n type=\"button\"\r\n [label]=\"option.label\"\r\n [icon]=\"\r\n option.contextKey === segment.value\r\n ? 'general.check'\r\n : 'general.minus'\r\n \"\r\n [iconPos]=\"'end'\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n styleClass=\"w-full justify-start rounded-md px-2.5 py-1.5 text-left text-xs font-medium transition-colors text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700\"\r\n (onClick)=\"\r\n onSetScopeSegmentValue(\r\n scope.key,\r\n segmentIndex,\r\n option.contextKey,\r\n segmentPopover\r\n )\r\n \"\r\n ></mt-button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </p-popover>\r\n }\r\n @if (canAddNextScopeSegment(scope.key)) {\r\n <div class=\"flex items-center gap-2\">\r\n <mt-button\r\n type=\"button\"\r\n icon=\"general.plus\"\r\n size=\"small\"\r\n severity=\"primary\"\r\n styleClass=\"flex size-7 shrink-0 items-center justify-center rounded-md bg-primary text-xs font-bold text-white hover:opacity-90\"\r\n (onClick)=\"nextContextPopover.toggle($event)\"\r\n ></mt-button>\r\n <span class=\"text-xs text-slate-400\">\r\n Add segment\r\n </span>\r\n </div>\r\n }\r\n <!-- Popover OUTSIDE the @if block like formula-builder -->\r\n <p-popover\r\n #nextContextPopover\r\n [style]=\"{ width: 'max-content' }\"\r\n [dismissable]=\"true\"\r\n appendTo=\"body\"\r\n >\r\n <div class=\"p-2\">\r\n <div\r\n class=\"mb-2 text-xs font-semibold uppercase text-slate-400\"\r\n >\r\n Add Segment\r\n </div>\r\n <div class=\"flex flex-col gap-1\">\r\n @for (\r\n token of getScopeNextTokens(scope.key);\r\n track token\r\n ) {\r\n <mt-button\r\n type=\"button\"\r\n [label]=\"getTokenLabel(token)\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n styleClass=\"w-full justify-start rounded-md px-2.5 py-1.5 text-left text-xs font-medium transition-colors text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700\"\r\n (onClick)=\"\r\n onAddScopeSegment(\r\n scope.key,\r\n token,\r\n nextContextPopover\r\n )\r\n \"\r\n ></mt-button>\r\n }\r\n </div>\r\n </div>\r\n </p-popover>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Node List -->\r\n <div\r\n class=\"space-y-4 px-4 pb-4 [&_.cdk-drag-placeholder]:hidden flex-1 overflow-y-auto\"\r\n [id]=\"'toolbox-' + scope.key\"\r\n cdkDropList\r\n cdkDropListSortingDisabled\r\n [cdkDropListData]=\"getFilteredProperties(scope.key)\"\r\n [cdkDropListEnterPredicate]=\"noReturnPredicate\"\r\n >\r\n @if (scope.key === \"Current\") {\r\n <!-- Search Field (Sticky) -->\r\n <div\r\n class=\"sticky top-0 bg-surface-0 mb-0 py-3 pb-2 mb-1\"\r\n >\r\n <mt-text-field\r\n [placeholder]=\"t('search-properties')\"\r\n [(ngModel)]=\"searchQuery\"\r\n icon=\"general.search-lg\"\r\n />\r\n </div>\r\n } @else {\r\n <div class=\"sticky top-0 bg-surface-0 py-3\">\r\n @if (isScopePropertiesLoading(scope.key)) {\r\n <p-skeleton height=\"2.5rem\" styleClass=\"rounded-lg\" />\r\n } @else {\r\n <mt-select-field\r\n class=\"w-full\"\r\n [label]=\"''\"\r\n [filter]=\"true\"\r\n [hasPlaceholderPrefix]=\"false\"\r\n placeholder=\"Select...\"\r\n [ngModel]=\"getScopeSelectedPropertyKey(scope.key)\"\r\n (ngModelChange)=\"\r\n setScopeSelectedProperty(scope.key, $event)\r\n \"\r\n [options]=\"getScopePropertyOptions(scope.key)\"\r\n optionLabel=\"name\"\r\n optionValue=\"key\"\r\n [size]=\"'small'\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n @if (isScopePropertiesLoading(scope.key)) {\r\n <div class=\"space-y-4\">\r\n @for (i of [1, 2, 3, 4]; track i) {\r\n <p-skeleton height=\"3rem\" styleClass=\"rounded-lg\" />\r\n }\r\n </div>\r\n } @else {\r\n @for (\r\n node of getFilteredProperties(scope.key);\r\n track node.key\r\n ) {\r\n <div\r\n cdkDrag\r\n [cdkDragData]=\"node\"\r\n class=\"group cursor-move select-none flex items-center gap-3 py-3 px-3 rounded-lg border border-dashed border-surface-300 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\r\n class=\"flex-1 text-start text-base font-medium\"\r\n >{{ getPropertyLabel(node) }}</span\r\n >\r\n\r\n <mt-icon\r\n class=\"text-lg\"\r\n icon=\"general.menu-05\"\r\n ></mt-icon>\r\n </div>\r\n }\r\n\r\n @if (getFilteredProperties(scope.key).length === 0) {\r\n <div class=\"py-8 text-center text-muted-color\">\r\n <p class=\"text-sm\">\r\n @if (scope.key === \"Current\" && searchQuery()) {\r\n {{ t(\"no-data-found\") }}\r\n } @else if (scope.key !== \"Current\") {\r\n Select a property\r\n } @else if (\r\n getScopeProperties(scope.key).length === 0\r\n ) {\r\n No properties available\r\n } @else {\r\n All items are in use\r\n }\r\n </p>\r\n </div>\r\n }\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-1 gap-4 h-full overflow-y-auto\">\r\n <div class=\"flex flex-col w-2/3 gap-4 h-full\">\r\n <mt-card>\r\n <ng-template #headless>\r\n <div class=\"p-4 flex items-center gap-3\">\r\n <mt-button\r\n icon=\"layout.layout-top\"\r\n [label]=\"t('add-section')\"\r\n variant=\"outlined\"\r\n severity=\"primary\"\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 variant=\"outlined\"\r\n severity=\"primary\"\r\n (onClick)=\"openPreview()\"\r\n [disabled]=\"isLoading()\"\r\n ></mt-button>\r\n <mt-button\r\n [label]=\"t('validation-rules')\"\r\n variant=\"outlined\"\r\n severity=\"primary\"\r\n (onClick)=\"openValidationRules()\"\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 variant=\"outlined\"\r\n severity=\"primary\"\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-1 border-primary rounded-xl bg-primary-50 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 </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"] }]
3183
+ }], ctorParameters: () => [], propDecorators: { popovers: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => Popover), { isSignal: true }] }] } });
2669
3184
 
2670
3185
  /*
2671
3186
  * Public API Surface of form-builder