commons-shared-web-ui 0.0.6 → 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.
@@ -15,6 +15,7 @@ import * as i2 from '@angular/material/icon';
15
15
  import { MatIconModule } from '@angular/material/icon';
16
16
  import * as i10 from '@angular/material/button';
17
17
  import { MatButtonModule } from '@angular/material/button';
18
+ import * as i7$1 from '@angular/material/menu';
18
19
  import { MatMenuModule } from '@angular/material/menu';
19
20
  import * as i7 from '@angular/material/datepicker';
20
21
  import { MatDatepickerModule } from '@angular/material/datepicker';
@@ -39,14 +40,14 @@ import { MatAutocompleteModule } from '@angular/material/autocomplete';
39
40
  import { MatSlideToggleModule } from '@angular/material/slide-toggle';
40
41
  import { MatButtonToggleModule } from '@angular/material/button-toggle';
41
42
  import * as i1$2 from '@angular/forms';
42
- import { FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule, Validators } from '@angular/forms';
43
+ import { FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule, Validators, FormControl, FormArray, FormGroup } from '@angular/forms';
43
44
  import * as i1$1 from '@angular/router';
44
45
  import * as i2$1 from '@angular/cdk/scrolling';
45
46
  import { CdkVirtualScrollViewport, ScrollingModule } from '@angular/cdk/scrolling';
46
47
  import { Subject, BehaviorSubject, combineLatest, forkJoin, of } from 'rxjs';
47
48
  import { debounceTime, distinctUntilChanged, takeUntil, map, finalize, catchError } from 'rxjs/operators';
48
49
  import * as i3 from '@angular/common/http';
49
- import { HttpParams, HttpHeaders } from '@angular/common/http';
50
+ import { HttpHeaders, HttpParams } from '@angular/common/http';
50
51
 
51
52
  class MaterialModule {
52
53
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MaterialModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
@@ -2836,6 +2837,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
2836
2837
  class SummaryCardComponent {
2837
2838
  config;
2838
2839
  theme;
2840
+ labels;
2839
2841
  cardClick = new EventEmitter();
2840
2842
  constructor() { }
2841
2843
  onCardClick() {
@@ -2895,15 +2897,17 @@ class SummaryCardComponent {
2895
2897
  return styles;
2896
2898
  }
2897
2899
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SummaryCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2898
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SummaryCardComponent, isStandalone: false, selector: "lib-summary-card", inputs: { config: "config", theme: "theme" }, outputs: { cardClick: "cardClick" }, ngImport: i0, template: "<div class=\"cc-summary-card\" (click)=\"onCardClick()\">\r\n <!-- Icon Section (Left Side) -->\r\n <div class=\"icon-section\" *ngIf=\"config.icon || config.iconImage\" [ngClass]=\"config.iconClass\"\r\n [ngStyle]=\"iconStyles\">\r\n <mat-icon *ngIf=\"config.icon\">{{ config.icon }}</mat-icon>\r\n <img *ngIf=\"!config.icon && config.iconImage\" [src]=\"config.iconImage\" alt=\"icon\">\r\n </div>\r\n\r\n <!-- Right Section (Header + Value/Meta) -->\r\n <div class=\"right-section\">\r\n <!-- Header (Full Width on Right) -->\r\n <div class=\"header\" [ngClass]=\"config.headerClass\" [ngStyle]=\"headerStyles\">\r\n {{ config.header }}\r\n </div>\r\n\r\n <!-- Value and Meta Row -->\r\n <div class=\"value-meta-row\">\r\n <!-- Content Section -->\r\n <div class=\"content-section\">\r\n <!-- Value Row (with optional inline description) -->\r\n <div class=\"value-row\" [class.inline-layout]=\"isDescriptionInline\">\r\n <div class=\"value-container\">\r\n <div class=\"value\" [ngClass]=\"config.valueClass\" [ngStyle]=\"valueStyles\">\r\n {{ config.value }}\r\n <span *ngIf=\"config.valueSubtext\" class=\"value-subtext\">{{ config.valueSubtext }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- Description (inline or bottom based on config) -->\r\n <div class=\"description\" *ngIf=\"config.description\" [ngClass]=\"config.descriptionClass\"\r\n [ngStyle]=\"descriptionStyles\">\r\n {{ config.description }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Meta/Status Section (Aligned with Value) -->\r\n <div class=\"meta-section\" *ngIf=\"config.metaData && config.metaData.length > 0\">\r\n <div *ngFor=\"let meta of config.metaData\" class=\"meta-item\"\r\n [ngClass]=\"[meta.type === 'pill' ? 'meta-pill' : 'meta-text', meta.cssClass || '']\"\r\n [ngStyle]=\"getMetaStyles(meta)\">\r\n {{ meta.text }}\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>", styles: [":host{display:block;width:100%}.cc-summary-card{display:flex;flex-direction:row;align-items:stretch;box-sizing:border-box;font-family:var(--cc-sc-font-family, \"Inter\", sans-serif);background-color:var(--cc-sc-bg-color, #ffffff);border-radius:var(--cc-sc-border-radius, 8px);border:var(--cc-sc-border, 1px solid #e0e0e0);padding:var(--cc-sc-padding, 16px);box-shadow:var(--cc-sc-shadow, 0 2px 4px rgba(0, 0, 0, .05));transition:all var(--cc-sc-transition-duration, .2s) ease;height:100%;width:100%;overflow:hidden;gap:var(--cc-sc-icon-margin, 1rem)}.cc-summary-card.clickable{cursor:pointer}.cc-summary-card.clickable:hover{transform:var(--cc-sc-hover-transform);box-shadow:var(--cc-sc-hover-shadow)}.cc-summary-card.disabled{opacity:var(--cc-sc-disabled-opacity);cursor:not-allowed;pointer-events:none}.cc-summary-card .icon-section{width:var(--cc-sc-icon-box-size);height:var(--cc-sc-icon-box-size);min-width:var(--cc-sc-icon-box-size);display:flex;align-items:center;justify-content:center;border-radius:var(--cc-sc-icon-radius);background-color:var(--cc-sc-icon-bg);color:var(--cc-sc-icon-color);flex-shrink:0;align-self:center}.cc-summary-card .icon-section mat-icon{width:var(--cc-sc-icon-size);height:var(--cc-sc-icon-size);font-size:var(--cc-sc-icon-size);line-height:var(--cc-sc-icon-size)}.cc-summary-card .icon-section img{width:var(--cc-sc-icon-size);height:var(--cc-sc-icon-size);object-fit:contain}.cc-summary-card .right-section{display:flex;flex-direction:column;flex:1;min-width:0;gap:var(--cc-sc-content-gap, .5rem)}.cc-summary-card .right-section .header{font-size:var(--cc-sc-header-size);font-weight:var(--cc-sc-header-weight);text-transform:var(--cc-sc-header-transform);color:var(--cc-sc-header-color);letter-spacing:var(--cc-sc-header-letter-spacing);line-height:var(--cc-sc-header-line-height);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:100%}.cc-summary-card .right-section .value-meta-row{display:flex;flex-direction:row;align-items:flex-start;justify-content:space-between;gap:1rem;flex:1}.cc-summary-card .right-section .value-meta-row .content-section{display:flex;flex-direction:column;justify-content:flex-start;flex:1;min-width:0}.cc-summary-card .right-section .value-meta-row .content-section .value-row{display:flex;flex-direction:column;gap:var(--cc-sc-value-desc-gap)}.cc-summary-card .right-section .value-meta-row .content-section .value-row.inline-layout{flex-direction:row;align-items:baseline;flex-wrap:wrap}.cc-summary-card .right-section .value-meta-row .content-section .value-row .value{font-size:var(--cc-sc-value-size);font-weight:var(--cc-sc-value-weight);color:var(--cc-sc-value-color);line-height:var(--cc-sc-value-line-height);white-space:nowrap}.cc-summary-card .right-section .value-meta-row .content-section .value-row .value .value-subtext{font-size:var(--cc-sc-desc-size);font-weight:var(--cc-sc-desc-weight);color:var(--cc-sc-desc-color);margin-left:4px}.cc-summary-card .right-section .value-meta-row .content-section .value-row .description{font-size:var(--cc-sc-desc-size);font-weight:var(--cc-sc-desc-weight);color:var(--cc-sc-desc-color);line-height:var(--cc-sc-desc-line-height)}.cc-summary-card .right-section .value-meta-row .meta-section{display:flex;flex-direction:column;align-items:flex-end;justify-content:flex-start;flex-shrink:0;gap:6px}.cc-summary-card .right-section .value-meta-row .meta-section .meta-item{font-size:.8125rem;line-height:1.4;white-space:nowrap;text-align:right;flex-shrink:0}.cc-summary-card .right-section .value-meta-row .meta-section .meta-item.meta-text{color:var(--cc-sc-desc-color, #64748b);font-weight:500}.cc-summary-card .right-section .value-meta-row .meta-section .meta-item.meta-pill{display:inline-flex;align-items:center;justify-content:center;padding:var(--cc-sc-meta-pill-padding, 4px 12px);border-radius:var(--cc-sc-meta-pill-radius, 20px);font-size:var(--cc-sc-meta-pill-font-size, .75rem);font-weight:var(--cc-sc-meta-pill-font-weight, 600);line-height:1.2;background-color:var(--cc-sc-meta-pill-bg, #f1f5f9);color:var(--cc-sc-meta-pill-color, #475569)}@media(max-width:600px){.cc-summary-card .icon-section{margin-right:var(--cc-sc-icon-margin-mobile, .75rem)}.cc-summary-card .content-section .header{font-size:var(--cc-sc-header-size-mobile, .7rem)}.cc-summary-card .content-section .value-row .value{font-size:var(--cc-sc-value-size-mobile, 1.25rem)}.cc-summary-card .content-section .value-row .description{font-size:var(--cc-sc-desc-size-mobile, .7rem)}}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }] });
2900
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SummaryCardComponent, isStandalone: false, selector: "lib-summary-card", inputs: { config: "config", theme: "theme", labels: "labels" }, outputs: { cardClick: "cardClick" }, ngImport: i0, template: "<div class=\"cc-summary-card\" (click)=\"onCardClick()\">\r\n <!-- Icon Section (Left Side) -->\r\n <div class=\"icon-section\" *ngIf=\"config.icon || config.iconImage\" [ngClass]=\"config.iconClass\"\r\n [ngStyle]=\"iconStyles\">\r\n <mat-icon *ngIf=\"config.icon\">{{ config.icon }}</mat-icon>\r\n <img *ngIf=\"!config.icon && config.iconImage\" [src]=\"config.iconImage\" [alt]=\"labels.iconAlt\">\r\n </div>\r\n\r\n <!-- Right Section (Header + Value/Meta) -->\r\n <div class=\"right-section\">\r\n <!-- Header (Full Width on Right) -->\r\n <div class=\"header\" [ngClass]=\"config.headerClass\" [ngStyle]=\"headerStyles\">\r\n {{ config.header }}\r\n </div>\r\n\r\n <!-- Value and Meta Row -->\r\n <div class=\"value-meta-row\">\r\n <!-- Content Section -->\r\n <div class=\"content-section\">\r\n <!-- Value Row (with optional inline description) -->\r\n <div class=\"value-row\" [class.inline-layout]=\"isDescriptionInline\">\r\n <div class=\"value-container\">\r\n <div class=\"value\" [ngClass]=\"config.valueClass\" [ngStyle]=\"valueStyles\">\r\n {{ config.value }}\r\n <span *ngIf=\"config.valueSubtext\" class=\"value-subtext\">{{ config.valueSubtext }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- Description (inline or bottom based on config) -->\r\n <div class=\"description\" *ngIf=\"config.description\" [ngClass]=\"config.descriptionClass\"\r\n [ngStyle]=\"descriptionStyles\">\r\n {{ config.description }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Meta/Status Section (Aligned with Value) -->\r\n <div class=\"meta-section\" *ngIf=\"config.metaData && config.metaData.length > 0\">\r\n <div *ngFor=\"let meta of config.metaData\" class=\"meta-item\"\r\n [ngClass]=\"[meta.type === 'pill' ? 'meta-pill' : 'meta-text', meta.cssClass || '']\"\r\n [ngStyle]=\"getMetaStyles(meta)\">\r\n {{ meta.text }}\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>", styles: [":host{display:block;width:100%}.cc-summary-card{display:flex;flex-direction:row;align-items:stretch;box-sizing:border-box;font-family:var(--cc-sc-font-family, \"Inter\", sans-serif);background-color:var(--cc-sc-bg-color, #ffffff);border-radius:var(--cc-sc-border-radius, 8px);border:var(--cc-sc-border, 1px solid #e0e0e0);padding:var(--cc-sc-padding, 16px);box-shadow:var(--cc-sc-shadow, 0 2px 4px rgba(0, 0, 0, .05));transition:all var(--cc-sc-transition-duration, .2s) ease;height:100%;width:100%;overflow:hidden;gap:var(--cc-sc-icon-margin, 1rem)}.cc-summary-card.clickable{cursor:pointer}.cc-summary-card.clickable:hover{transform:var(--cc-sc-hover-transform);box-shadow:var(--cc-sc-hover-shadow)}.cc-summary-card.disabled{opacity:var(--cc-sc-disabled-opacity);cursor:not-allowed;pointer-events:none}.cc-summary-card .icon-section{width:var(--cc-sc-icon-box-size);height:var(--cc-sc-icon-box-size);min-width:var(--cc-sc-icon-box-size);display:flex;align-items:center;justify-content:center;border-radius:var(--cc-sc-icon-radius);background-color:var(--cc-sc-icon-bg);color:var(--cc-sc-icon-color);flex-shrink:0;align-self:center}.cc-summary-card .icon-section mat-icon{width:var(--cc-sc-icon-size);height:var(--cc-sc-icon-size);font-size:var(--cc-sc-icon-size);line-height:var(--cc-sc-icon-size)}.cc-summary-card .icon-section img{width:var(--cc-sc-icon-size);height:var(--cc-sc-icon-size);object-fit:contain}.cc-summary-card .right-section{display:flex;flex-direction:column;flex:1;min-width:0;gap:var(--cc-sc-content-gap, .5rem)}.cc-summary-card .right-section .header{font-size:var(--cc-sc-header-size);font-weight:var(--cc-sc-header-weight);text-transform:var(--cc-sc-header-transform);color:var(--cc-sc-header-color);letter-spacing:var(--cc-sc-header-letter-spacing);line-height:var(--cc-sc-header-line-height);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:100%}.cc-summary-card .right-section .value-meta-row{display:flex;flex-direction:row;align-items:flex-start;justify-content:space-between;gap:1rem;flex:1}.cc-summary-card .right-section .value-meta-row .content-section{display:flex;flex-direction:column;justify-content:flex-start;flex:1;min-width:0}.cc-summary-card .right-section .value-meta-row .content-section .value-row{display:flex;flex-direction:column;gap:var(--cc-sc-value-desc-gap)}.cc-summary-card .right-section .value-meta-row .content-section .value-row.inline-layout{flex-direction:row;align-items:baseline;flex-wrap:wrap}.cc-summary-card .right-section .value-meta-row .content-section .value-row .value{font-size:var(--cc-sc-value-size);font-weight:var(--cc-sc-value-weight);color:var(--cc-sc-value-color);line-height:var(--cc-sc-value-line-height);white-space:nowrap}.cc-summary-card .right-section .value-meta-row .content-section .value-row .value .value-subtext{font-size:var(--cc-sc-desc-size);font-weight:var(--cc-sc-desc-weight);color:var(--cc-sc-desc-color);margin-left:4px}.cc-summary-card .right-section .value-meta-row .content-section .value-row .description{font-size:var(--cc-sc-desc-size);font-weight:var(--cc-sc-desc-weight);color:var(--cc-sc-desc-color);line-height:var(--cc-sc-desc-line-height)}.cc-summary-card .right-section .value-meta-row .meta-section{display:flex;flex-direction:column;align-items:flex-end;justify-content:flex-start;flex-shrink:0;gap:6px}.cc-summary-card .right-section .value-meta-row .meta-section .meta-item{font-size:.8125rem;line-height:1.4;white-space:nowrap;text-align:right;flex-shrink:0}.cc-summary-card .right-section .value-meta-row .meta-section .meta-item.meta-text{color:var(--cc-sc-desc-color, #64748b);font-weight:500}.cc-summary-card .right-section .value-meta-row .meta-section .meta-item.meta-pill{display:inline-flex;align-items:center;justify-content:center;padding:var(--cc-sc-meta-pill-padding, 4px 12px);border-radius:var(--cc-sc-meta-pill-radius, 20px);font-size:var(--cc-sc-meta-pill-font-size, .75rem);font-weight:var(--cc-sc-meta-pill-font-weight, 600);line-height:1.2;background-color:var(--cc-sc-meta-pill-bg, #f1f5f9);color:var(--cc-sc-meta-pill-color, #475569)}@media(max-width:600px){.cc-summary-card .icon-section{margin-right:var(--cc-sc-icon-margin-mobile, .75rem)}.cc-summary-card .content-section .header{font-size:var(--cc-sc-header-size-mobile, .7rem)}.cc-summary-card .content-section .value-row .value{font-size:var(--cc-sc-value-size-mobile, 1.25rem)}.cc-summary-card .content-section .value-row .description{font-size:var(--cc-sc-desc-size-mobile, .7rem)}}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }] });
2899
2901
  }
2900
2902
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SummaryCardComponent, decorators: [{
2901
2903
  type: Component,
2902
- args: [{ selector: 'lib-summary-card', standalone: false, template: "<div class=\"cc-summary-card\" (click)=\"onCardClick()\">\r\n <!-- Icon Section (Left Side) -->\r\n <div class=\"icon-section\" *ngIf=\"config.icon || config.iconImage\" [ngClass]=\"config.iconClass\"\r\n [ngStyle]=\"iconStyles\">\r\n <mat-icon *ngIf=\"config.icon\">{{ config.icon }}</mat-icon>\r\n <img *ngIf=\"!config.icon && config.iconImage\" [src]=\"config.iconImage\" alt=\"icon\">\r\n </div>\r\n\r\n <!-- Right Section (Header + Value/Meta) -->\r\n <div class=\"right-section\">\r\n <!-- Header (Full Width on Right) -->\r\n <div class=\"header\" [ngClass]=\"config.headerClass\" [ngStyle]=\"headerStyles\">\r\n {{ config.header }}\r\n </div>\r\n\r\n <!-- Value and Meta Row -->\r\n <div class=\"value-meta-row\">\r\n <!-- Content Section -->\r\n <div class=\"content-section\">\r\n <!-- Value Row (with optional inline description) -->\r\n <div class=\"value-row\" [class.inline-layout]=\"isDescriptionInline\">\r\n <div class=\"value-container\">\r\n <div class=\"value\" [ngClass]=\"config.valueClass\" [ngStyle]=\"valueStyles\">\r\n {{ config.value }}\r\n <span *ngIf=\"config.valueSubtext\" class=\"value-subtext\">{{ config.valueSubtext }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- Description (inline or bottom based on config) -->\r\n <div class=\"description\" *ngIf=\"config.description\" [ngClass]=\"config.descriptionClass\"\r\n [ngStyle]=\"descriptionStyles\">\r\n {{ config.description }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Meta/Status Section (Aligned with Value) -->\r\n <div class=\"meta-section\" *ngIf=\"config.metaData && config.metaData.length > 0\">\r\n <div *ngFor=\"let meta of config.metaData\" class=\"meta-item\"\r\n [ngClass]=\"[meta.type === 'pill' ? 'meta-pill' : 'meta-text', meta.cssClass || '']\"\r\n [ngStyle]=\"getMetaStyles(meta)\">\r\n {{ meta.text }}\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>", styles: [":host{display:block;width:100%}.cc-summary-card{display:flex;flex-direction:row;align-items:stretch;box-sizing:border-box;font-family:var(--cc-sc-font-family, \"Inter\", sans-serif);background-color:var(--cc-sc-bg-color, #ffffff);border-radius:var(--cc-sc-border-radius, 8px);border:var(--cc-sc-border, 1px solid #e0e0e0);padding:var(--cc-sc-padding, 16px);box-shadow:var(--cc-sc-shadow, 0 2px 4px rgba(0, 0, 0, .05));transition:all var(--cc-sc-transition-duration, .2s) ease;height:100%;width:100%;overflow:hidden;gap:var(--cc-sc-icon-margin, 1rem)}.cc-summary-card.clickable{cursor:pointer}.cc-summary-card.clickable:hover{transform:var(--cc-sc-hover-transform);box-shadow:var(--cc-sc-hover-shadow)}.cc-summary-card.disabled{opacity:var(--cc-sc-disabled-opacity);cursor:not-allowed;pointer-events:none}.cc-summary-card .icon-section{width:var(--cc-sc-icon-box-size);height:var(--cc-sc-icon-box-size);min-width:var(--cc-sc-icon-box-size);display:flex;align-items:center;justify-content:center;border-radius:var(--cc-sc-icon-radius);background-color:var(--cc-sc-icon-bg);color:var(--cc-sc-icon-color);flex-shrink:0;align-self:center}.cc-summary-card .icon-section mat-icon{width:var(--cc-sc-icon-size);height:var(--cc-sc-icon-size);font-size:var(--cc-sc-icon-size);line-height:var(--cc-sc-icon-size)}.cc-summary-card .icon-section img{width:var(--cc-sc-icon-size);height:var(--cc-sc-icon-size);object-fit:contain}.cc-summary-card .right-section{display:flex;flex-direction:column;flex:1;min-width:0;gap:var(--cc-sc-content-gap, .5rem)}.cc-summary-card .right-section .header{font-size:var(--cc-sc-header-size);font-weight:var(--cc-sc-header-weight);text-transform:var(--cc-sc-header-transform);color:var(--cc-sc-header-color);letter-spacing:var(--cc-sc-header-letter-spacing);line-height:var(--cc-sc-header-line-height);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:100%}.cc-summary-card .right-section .value-meta-row{display:flex;flex-direction:row;align-items:flex-start;justify-content:space-between;gap:1rem;flex:1}.cc-summary-card .right-section .value-meta-row .content-section{display:flex;flex-direction:column;justify-content:flex-start;flex:1;min-width:0}.cc-summary-card .right-section .value-meta-row .content-section .value-row{display:flex;flex-direction:column;gap:var(--cc-sc-value-desc-gap)}.cc-summary-card .right-section .value-meta-row .content-section .value-row.inline-layout{flex-direction:row;align-items:baseline;flex-wrap:wrap}.cc-summary-card .right-section .value-meta-row .content-section .value-row .value{font-size:var(--cc-sc-value-size);font-weight:var(--cc-sc-value-weight);color:var(--cc-sc-value-color);line-height:var(--cc-sc-value-line-height);white-space:nowrap}.cc-summary-card .right-section .value-meta-row .content-section .value-row .value .value-subtext{font-size:var(--cc-sc-desc-size);font-weight:var(--cc-sc-desc-weight);color:var(--cc-sc-desc-color);margin-left:4px}.cc-summary-card .right-section .value-meta-row .content-section .value-row .description{font-size:var(--cc-sc-desc-size);font-weight:var(--cc-sc-desc-weight);color:var(--cc-sc-desc-color);line-height:var(--cc-sc-desc-line-height)}.cc-summary-card .right-section .value-meta-row .meta-section{display:flex;flex-direction:column;align-items:flex-end;justify-content:flex-start;flex-shrink:0;gap:6px}.cc-summary-card .right-section .value-meta-row .meta-section .meta-item{font-size:.8125rem;line-height:1.4;white-space:nowrap;text-align:right;flex-shrink:0}.cc-summary-card .right-section .value-meta-row .meta-section .meta-item.meta-text{color:var(--cc-sc-desc-color, #64748b);font-weight:500}.cc-summary-card .right-section .value-meta-row .meta-section .meta-item.meta-pill{display:inline-flex;align-items:center;justify-content:center;padding:var(--cc-sc-meta-pill-padding, 4px 12px);border-radius:var(--cc-sc-meta-pill-radius, 20px);font-size:var(--cc-sc-meta-pill-font-size, .75rem);font-weight:var(--cc-sc-meta-pill-font-weight, 600);line-height:1.2;background-color:var(--cc-sc-meta-pill-bg, #f1f5f9);color:var(--cc-sc-meta-pill-color, #475569)}@media(max-width:600px){.cc-summary-card .icon-section{margin-right:var(--cc-sc-icon-margin-mobile, .75rem)}.cc-summary-card .content-section .header{font-size:var(--cc-sc-header-size-mobile, .7rem)}.cc-summary-card .content-section .value-row .value{font-size:var(--cc-sc-value-size-mobile, 1.25rem)}.cc-summary-card .content-section .value-row .description{font-size:var(--cc-sc-desc-size-mobile, .7rem)}}\n"] }]
2904
+ args: [{ selector: 'lib-summary-card', standalone: false, template: "<div class=\"cc-summary-card\" (click)=\"onCardClick()\">\r\n <!-- Icon Section (Left Side) -->\r\n <div class=\"icon-section\" *ngIf=\"config.icon || config.iconImage\" [ngClass]=\"config.iconClass\"\r\n [ngStyle]=\"iconStyles\">\r\n <mat-icon *ngIf=\"config.icon\">{{ config.icon }}</mat-icon>\r\n <img *ngIf=\"!config.icon && config.iconImage\" [src]=\"config.iconImage\" [alt]=\"labels.iconAlt\">\r\n </div>\r\n\r\n <!-- Right Section (Header + Value/Meta) -->\r\n <div class=\"right-section\">\r\n <!-- Header (Full Width on Right) -->\r\n <div class=\"header\" [ngClass]=\"config.headerClass\" [ngStyle]=\"headerStyles\">\r\n {{ config.header }}\r\n </div>\r\n\r\n <!-- Value and Meta Row -->\r\n <div class=\"value-meta-row\">\r\n <!-- Content Section -->\r\n <div class=\"content-section\">\r\n <!-- Value Row (with optional inline description) -->\r\n <div class=\"value-row\" [class.inline-layout]=\"isDescriptionInline\">\r\n <div class=\"value-container\">\r\n <div class=\"value\" [ngClass]=\"config.valueClass\" [ngStyle]=\"valueStyles\">\r\n {{ config.value }}\r\n <span *ngIf=\"config.valueSubtext\" class=\"value-subtext\">{{ config.valueSubtext }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- Description (inline or bottom based on config) -->\r\n <div class=\"description\" *ngIf=\"config.description\" [ngClass]=\"config.descriptionClass\"\r\n [ngStyle]=\"descriptionStyles\">\r\n {{ config.description }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Meta/Status Section (Aligned with Value) -->\r\n <div class=\"meta-section\" *ngIf=\"config.metaData && config.metaData.length > 0\">\r\n <div *ngFor=\"let meta of config.metaData\" class=\"meta-item\"\r\n [ngClass]=\"[meta.type === 'pill' ? 'meta-pill' : 'meta-text', meta.cssClass || '']\"\r\n [ngStyle]=\"getMetaStyles(meta)\">\r\n {{ meta.text }}\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>", styles: [":host{display:block;width:100%}.cc-summary-card{display:flex;flex-direction:row;align-items:stretch;box-sizing:border-box;font-family:var(--cc-sc-font-family, \"Inter\", sans-serif);background-color:var(--cc-sc-bg-color, #ffffff);border-radius:var(--cc-sc-border-radius, 8px);border:var(--cc-sc-border, 1px solid #e0e0e0);padding:var(--cc-sc-padding, 16px);box-shadow:var(--cc-sc-shadow, 0 2px 4px rgba(0, 0, 0, .05));transition:all var(--cc-sc-transition-duration, .2s) ease;height:100%;width:100%;overflow:hidden;gap:var(--cc-sc-icon-margin, 1rem)}.cc-summary-card.clickable{cursor:pointer}.cc-summary-card.clickable:hover{transform:var(--cc-sc-hover-transform);box-shadow:var(--cc-sc-hover-shadow)}.cc-summary-card.disabled{opacity:var(--cc-sc-disabled-opacity);cursor:not-allowed;pointer-events:none}.cc-summary-card .icon-section{width:var(--cc-sc-icon-box-size);height:var(--cc-sc-icon-box-size);min-width:var(--cc-sc-icon-box-size);display:flex;align-items:center;justify-content:center;border-radius:var(--cc-sc-icon-radius);background-color:var(--cc-sc-icon-bg);color:var(--cc-sc-icon-color);flex-shrink:0;align-self:center}.cc-summary-card .icon-section mat-icon{width:var(--cc-sc-icon-size);height:var(--cc-sc-icon-size);font-size:var(--cc-sc-icon-size);line-height:var(--cc-sc-icon-size)}.cc-summary-card .icon-section img{width:var(--cc-sc-icon-size);height:var(--cc-sc-icon-size);object-fit:contain}.cc-summary-card .right-section{display:flex;flex-direction:column;flex:1;min-width:0;gap:var(--cc-sc-content-gap, .5rem)}.cc-summary-card .right-section .header{font-size:var(--cc-sc-header-size);font-weight:var(--cc-sc-header-weight);text-transform:var(--cc-sc-header-transform);color:var(--cc-sc-header-color);letter-spacing:var(--cc-sc-header-letter-spacing);line-height:var(--cc-sc-header-line-height);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:100%}.cc-summary-card .right-section .value-meta-row{display:flex;flex-direction:row;align-items:flex-start;justify-content:space-between;gap:1rem;flex:1}.cc-summary-card .right-section .value-meta-row .content-section{display:flex;flex-direction:column;justify-content:flex-start;flex:1;min-width:0}.cc-summary-card .right-section .value-meta-row .content-section .value-row{display:flex;flex-direction:column;gap:var(--cc-sc-value-desc-gap)}.cc-summary-card .right-section .value-meta-row .content-section .value-row.inline-layout{flex-direction:row;align-items:baseline;flex-wrap:wrap}.cc-summary-card .right-section .value-meta-row .content-section .value-row .value{font-size:var(--cc-sc-value-size);font-weight:var(--cc-sc-value-weight);color:var(--cc-sc-value-color);line-height:var(--cc-sc-value-line-height);white-space:nowrap}.cc-summary-card .right-section .value-meta-row .content-section .value-row .value .value-subtext{font-size:var(--cc-sc-desc-size);font-weight:var(--cc-sc-desc-weight);color:var(--cc-sc-desc-color);margin-left:4px}.cc-summary-card .right-section .value-meta-row .content-section .value-row .description{font-size:var(--cc-sc-desc-size);font-weight:var(--cc-sc-desc-weight);color:var(--cc-sc-desc-color);line-height:var(--cc-sc-desc-line-height)}.cc-summary-card .right-section .value-meta-row .meta-section{display:flex;flex-direction:column;align-items:flex-end;justify-content:flex-start;flex-shrink:0;gap:6px}.cc-summary-card .right-section .value-meta-row .meta-section .meta-item{font-size:.8125rem;line-height:1.4;white-space:nowrap;text-align:right;flex-shrink:0}.cc-summary-card .right-section .value-meta-row .meta-section .meta-item.meta-text{color:var(--cc-sc-desc-color, #64748b);font-weight:500}.cc-summary-card .right-section .value-meta-row .meta-section .meta-item.meta-pill{display:inline-flex;align-items:center;justify-content:center;padding:var(--cc-sc-meta-pill-padding, 4px 12px);border-radius:var(--cc-sc-meta-pill-radius, 20px);font-size:var(--cc-sc-meta-pill-font-size, .75rem);font-weight:var(--cc-sc-meta-pill-font-weight, 600);line-height:1.2;background-color:var(--cc-sc-meta-pill-bg, #f1f5f9);color:var(--cc-sc-meta-pill-color, #475569)}@media(max-width:600px){.cc-summary-card .icon-section{margin-right:var(--cc-sc-icon-margin-mobile, .75rem)}.cc-summary-card .content-section .header{font-size:var(--cc-sc-header-size-mobile, .7rem)}.cc-summary-card .content-section .value-row .value{font-size:var(--cc-sc-value-size-mobile, 1.25rem)}.cc-summary-card .content-section .value-row .description{font-size:var(--cc-sc-desc-size-mobile, .7rem)}}\n"] }]
2903
2905
  }], ctorParameters: () => [], propDecorators: { config: [{
2904
2906
  type: Input
2905
2907
  }], theme: [{
2906
2908
  type: Input
2909
+ }], labels: [{
2910
+ type: Input
2907
2911
  }], cardClick: [{
2908
2912
  type: Output
2909
2913
  }] } });
@@ -2931,6 +2935,85 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
2931
2935
  }]
2932
2936
  }] });
2933
2937
 
2938
+ /**
2939
+ * Recursively translates the Configuration object using the provided labels map.
2940
+ * This function processes:
2941
+ * - Section Titles
2942
+ * - Field Labels
2943
+ * - Placeholders
2944
+ * - Help Texts
2945
+ * - Option Labels (for static options)
2946
+ * - Repeater Labels (addLabel, repeaterItemLabel)
2947
+ *
2948
+ * @param config The FormConfig object to translate
2949
+ * @param labelsMap A map of key-value pairs for translation (Flattened JSON)
2950
+ * @returns A new FormConfig object with translated strings
2951
+ */
2952
+ function translateConfig(config, labelsMap) {
2953
+ if (!config || !labelsMap) {
2954
+ return config;
2955
+ }
2956
+ // Deep clone to avoid mutating original config, if not already cloned
2957
+ // Note: Assuming config passed here is already a clone to be modified or we clone it.
2958
+ // To be safe, let's clone it if it's the root call, but this function is helper.
2959
+ // In component we do: processedConfig = JSON.parse(JSON.stringify(config)); -> translateConfig(processedConfig, map)
2960
+ // So distinct reference is passed.
2961
+ // Helper to translate a single key
2962
+ const t = (key) => {
2963
+ return labelsMap[key] || key;
2964
+ };
2965
+ if (config.sections) {
2966
+ config.sections.forEach((section) => {
2967
+ if (section.sectionTitle) {
2968
+ section.sectionTitle = t(section.sectionTitle);
2969
+ }
2970
+ if (section.repeaterItemLabel) {
2971
+ section.repeaterItemLabel = t(section.repeaterItemLabel);
2972
+ }
2973
+ if (section.addLabel) {
2974
+ section.addLabel = t(section.addLabel);
2975
+ }
2976
+ if (section.fields) {
2977
+ section.fields.forEach((field) => {
2978
+ translateField(field, t);
2979
+ });
2980
+ }
2981
+ });
2982
+ }
2983
+ return config;
2984
+ }
2985
+ function translateField(field, t) {
2986
+ if (field.label) {
2987
+ field.label = t(field.label);
2988
+ }
2989
+ if (field.placeholder) {
2990
+ field.placeholder = t(field.placeholder);
2991
+ }
2992
+ if (field.helpText) {
2993
+ field.helpText = t(field.helpText);
2994
+ }
2995
+ if (field.suffixText) {
2996
+ field.suffixText = t(field.suffixText);
2997
+ }
2998
+ if (field.prefixText) { // Assuming prefixText might exist or be added
2999
+ field.prefixText = t(field.prefixText);
3000
+ }
3001
+ // Translate options if they exist
3002
+ if (field.options) {
3003
+ field.options.forEach((opt) => {
3004
+ if (opt.label) {
3005
+ opt.label = t(opt.label);
3006
+ }
3007
+ });
3008
+ }
3009
+ // Handle subFields for composite type
3010
+ if (field.subFields) {
3011
+ field.subFields.forEach((sub) => {
3012
+ translateField(sub, t);
3013
+ });
3014
+ }
3015
+ }
3016
+
2934
3017
  class ConfigurableFormComponent {
2935
3018
  fb;
2936
3019
  snackBar;
@@ -2939,6 +3022,7 @@ class ConfigurableFormComponent {
2939
3022
  jsonConfig;
2940
3023
  data = {};
2941
3024
  baseApiUrl = '';
3025
+ labels;
2942
3026
  optionsLoad = new EventEmitter();
2943
3027
  form;
2944
3028
  processedConfig;
@@ -2971,6 +3055,10 @@ class ConfigurableFormComponent {
2971
3055
  else {
2972
3056
  return;
2973
3057
  }
3058
+ // Apply translations if labels and labelsObject are provided
3059
+ if (this.labels && this.labels.labelsObject) {
3060
+ translateConfig(this.processedConfig, this.labels.labelsObject);
3061
+ }
2974
3062
  this.normalizeFields();
2975
3063
  this.initializeFieldVisibility();
2976
3064
  this.buildForm();
@@ -3197,7 +3285,7 @@ class ConfigurableFormComponent {
3197
3285
  const formArray = this.getFormArray(arrayName);
3198
3286
  // Check max items
3199
3287
  if (section.maxItems && formArray.length >= section.maxItems) {
3200
- this.snackBar.open(`Maximum ${section.maxItems} items allowed`, 'Close', { duration: 3000 });
3288
+ this.snackBar.open(this.labels.errorMaxItemsAllowed.replace('{0}', String(section.maxItems)), this.labels.closeSnackBar, { duration: 3000 });
3201
3289
  return;
3202
3290
  }
3203
3291
  formArray.push(this.createGroup(section.fields));
@@ -3223,14 +3311,6 @@ class ConfigurableFormComponent {
3223
3311
  setupDependencies() {
3224
3312
  this.processedConfig.sections.forEach(section => {
3225
3313
  section.fields.forEach(field => {
3226
- if (field.dependsOn) {
3227
- const control = this.form.get(field.dependsOn);
3228
- if (control) {
3229
- control.valueChanges.subscribe(value => {
3230
- this.onFieldValueChange(field, value);
3231
- });
3232
- }
3233
- }
3234
3314
  // Also setup for fields with dependent array
3235
3315
  if (field.dependent && field.dependent.length > 0) {
3236
3316
  const control = this.form.get(field.name);
@@ -3383,7 +3463,7 @@ class ConfigurableFormComponent {
3383
3463
  const file = files[i];
3384
3464
  // Check file size (5MB limit)
3385
3465
  if (file.size > 5 * 1024 * 1024) {
3386
- this.snackBar.open(`File ${file.name} exceeds 5MB limit`, 'Close', { duration: 3000 });
3466
+ this.snackBar.open(this.labels.errorFileLimitExceeded.replace('{0}', file.name), this.labels.closeSnackBar, { duration: 3000 });
3387
3467
  continue;
3388
3468
  }
3389
3469
  uploadedFiles.push({
@@ -3453,11 +3533,11 @@ class ConfigurableFormComponent {
3453
3533
  return this.passwordFieldState.get(fieldName) || false;
3454
3534
  }
3455
3535
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ConfigurableFormComponent, deps: [{ token: i1$2.FormBuilder }, { token: i2$2.MatSnackBar }, { token: i3.HttpClient }], target: i0.ɵɵFactoryTarget.Component });
3456
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: ConfigurableFormComponent, isStandalone: true, selector: "lib-configurable-form", inputs: { config: "config", jsonConfig: "jsonConfig", data: "data", baseApiUrl: "baseApiUrl" }, outputs: { optionsLoad: "optionsLoad" }, usesOnChanges: true, ngImport: i0, template: "<form [formGroup]=\"form\" class=\"configurable-form-container\" *ngIf=\"form\">\r\n\r\n <ng-container *ngFor=\"let section of sections\">\r\n\r\n <!-- Repeater Section -->\r\n <div *ngIf=\"section.isRepeater; else normalSection\"\r\n [ngClass]=\"section.noCardLayout ? 'section-no-card' : 'section-card'\">\r\n <div class=\"section-header\" *ngIf=\"section.sectionTitle\">\r\n <h3 class=\"section-title\">{{ section.sectionTitle }}</h3>\r\n <button *ngIf=\"section.collapsible\" mat-icon-button type=\"button\" (click)=\"toggleSection(section)\">\r\n <mat-icon>{{ section.collapsed ? 'expand_more' : 'expand_less' }}</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <div [formArrayName]=\"section.formArrayName || 'items'\" *ngIf=\"!section.collapsed\">\r\n <div *ngFor=\"let item of getFormArray(section.formArrayName!).controls; let i = index\"\r\n [formGroupName]=\"i\" class=\"repeater-item\">\r\n <div class=\"repeater-item-header\" *ngIf=\"getFormArray(section.formArrayName!).length > 1\">\r\n <span class=\"text-small\">{{ section.repeaterItemLabel || 'Item' }} {{i + 1}}</span>\r\n <button mat-icon-button color=\"warn\" type=\"button\"\r\n (click)=\"removeRepeaterItem(section.formArrayName!, i)\"\r\n [disabled]=\"section.minItems && getFormArray(section.formArrayName!).length <= section.minItems\">\r\n <mat-icon>delete</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Fields Grid -->\r\n <div class=\"form-grid\">\r\n <div *ngFor=\"let field of section.fields\" [ngClass]=\"['form-col', field.class || 'col-12']\"\r\n [hidden]=\"!isFieldVisible(field)\">\r\n <ng-container\r\n *ngTemplateOutlet=\"fieldTemplate; context: {field: field, group: item}\"></ng-container>\r\n </div>\r\n </div>\r\n\r\n <div class=\"repeater-separator\" *ngIf=\"i < getFormArray(section.formArrayName!).length - 1\"></div>\r\n </div>\r\n </div>\r\n\r\n <button mat-button color=\"primary\" type=\"button\" (click)=\"addRepeaterItem(section)\" class=\"add-btn\"\r\n *ngIf=\"!section.collapsed\"\r\n [disabled]=\"section.maxItems && getFormArray(section.formArrayName!).length >= section.maxItems\">\r\n <mat-icon>add_circle_outline</mat-icon> {{ section.addLabel || 'Add More' }}\r\n </button>\r\n </div>\r\n\r\n <!-- Normal Section -->\r\n <ng-template #normalSection>\r\n <div [ngClass]=\"section.noCardLayout ? 'section-no-card' : 'section-card'\">\r\n <div class=\"section-header\" *ngIf=\"section.sectionTitle\">\r\n <h3 class=\"section-title\">{{ section.sectionTitle }}</h3>\r\n <button *ngIf=\"section.collapsible\" mat-icon-button type=\"button\" (click)=\"toggleSection(section)\">\r\n <mat-icon>{{ section.collapsed ? 'expand_more' : 'expand_less' }}</mat-icon>\r\n </button>\r\n </div>\r\n <div class=\"form-grid\" *ngIf=\"!section.collapsed\">\r\n <div *ngFor=\"let field of section.fields\" [ngClass]=\"['form-col', field.class || 'col-12']\"\r\n [hidden]=\"!isFieldVisible(field)\">\r\n <!-- Pass the root form group to the template -->\r\n <ng-container\r\n *ngTemplateOutlet=\"fieldTemplate; context: {field: field, group: form}\"></ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n </ng-container>\r\n\r\n</form>\r\n\r\n<!-- Reusable Field Template -->\r\n<ng-template #fieldTemplate let-field=\"field\" let-group=\"group\">\r\n <div [formGroup]=\"group\">\r\n\r\n <!-- Text / Email / Number / Tel / URL (Password removed) -->\r\n <div *ngIf=\"['text', 'email', 'number', 'tel', 'url'].includes(field.type || '')\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <div class=\"input-wrapper\">\r\n <input [type]=\"field.type\" [formControlName]=\"field.name\" [placeholder]=\"field.placeholder || ''\"\r\n [readonly]=\"field.readonly\" class=\"form-input\" [name]=\"field.name\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\"\r\n [maxlength]=\"field.validationRules?.maxLength || field.uiConfig?.maxCharacters\">\r\n <div class=\"input-suffix\">\r\n <span class=\"suffix-text\" *ngIf=\"field.suffixText\">{{ field.suffixText }}</span>\r\n <mat-icon *ngIf=\"field.suffixIcon\">{{ field.suffixIcon }}</mat-icon>\r\n <mat-icon *ngIf=\"field.icon\">{{ field.icon }}</mat-icon>\r\n <mat-icon *ngIf=\"field.readonly\" class=\"lock-icon\">lock_outline</mat-icon>\r\n </div>\r\n </div>\r\n <!-- Character count -->\r\n <div class=\"char-count-row\" *ngIf=\"field.uiConfig?.maxCharacters || field.validationRules?.maxLength\">\r\n <div class=\"hint-text\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n <div class=\"char-count\">\r\n {{ getCharacterCount(field.name) }} / {{ field.uiConfig?.maxCharacters ||\r\n field.validationRules?.maxLength }} Characters\r\n </div>\r\n </div>\r\n <div class=\"hint-text mt-2\"\r\n *ngIf=\"(field.hint || field.helpText) && !field.uiConfig?.maxCharacters && !field.validationRules?.maxLength\">\r\n {{ field.hint || field.helpText }}\r\n </div>\r\n </div>\r\n\r\n <!-- Password Field (Separated) -->\r\n <div *ngIf=\"field.type === 'password'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <div class=\"input-wrapper\">\r\n <input [type]=\"isPasswordVisible(field.name) ? 'text' : 'password'\" [formControlName]=\"field.name\"\r\n [placeholder]=\"field.placeholder || ''\" [readonly]=\"field.readonly\" class=\"form-input\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\"\r\n [maxlength]=\"field.validationRules?.maxLength || field.uiConfig?.maxCharacters\">\r\n <div class=\"input-suffix\">\r\n <span class=\"suffix-text\" *ngIf=\"field.suffixText\">{{ field.suffixText }}</span>\r\n <mat-icon *ngIf=\"field.suffixIcon\">{{ field.suffixIcon }}</mat-icon>\r\n <mat-icon *ngIf=\"field.icon\">{{ field.icon }}</mat-icon>\r\n <mat-icon *ngIf=\"field.readonly\" class=\"lock-icon\">lock_outline</mat-icon>\r\n <mat-icon class=\"password-toggle-icon\" (click)=\"togglePassword(field.name)\">\r\n {{ isPasswordVisible(field.name) ? 'visibility' : 'visibility_off' }}\r\n </mat-icon>\r\n </div>\r\n </div>\r\n <!-- Character count -->\r\n <div class=\"char-count-row\" *ngIf=\"field.uiConfig?.maxCharacters || field.validationRules?.maxLength\">\r\n <div class=\"hint-text\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n <div class=\"char-count\">\r\n {{ getCharacterCount(field.name) }} / {{ field.uiConfig?.maxCharacters ||\r\n field.validationRules?.maxLength }} Characters\r\n </div>\r\n </div>\r\n <div class=\"hint-text mt-2\"\r\n *ngIf=\"(field.hint || field.helpText) && !field.uiConfig?.maxCharacters && !field.validationRules?.maxLength\">\r\n {{ field.hint || field.helpText }}\r\n </div>\r\n </div>\r\n\r\n <!-- Textarea -->\r\n <div *ngIf=\"field.type === 'textarea'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <textarea [formControlName]=\"field.name\" [placeholder]=\"field.placeholder || ''\" [readonly]=\"field.readonly\"\r\n class=\"form-input form-textarea\" rows=\"4\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\"\r\n [maxlength]=\"field.validationRules?.maxLength || field.uiConfig?.maxCharacters\"></textarea>\r\n <!-- Character count -->\r\n <div class=\"char-count-row\" *ngIf=\"field.uiConfig?.maxCharacters || field.validationRules?.maxLength\">\r\n <div class=\"hint-text\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n <div class=\"char-count\">\r\n {{ getCharacterCount(field.name) }} / {{ field.uiConfig?.maxCharacters ||\r\n field.validationRules?.maxLength }} Characters\r\n </div>\r\n </div>\r\n <div class=\"hint-text mt-2\"\r\n *ngIf=\"(field.hint || field.helpText) && !field.uiConfig?.maxCharacters && !field.validationRules?.maxLength\">\r\n {{ field.hint || field.helpText }}\r\n </div>\r\n </div>\r\n\r\n <!-- Select / Dropdown -->\r\n <div *ngIf=\"field.type === 'select' || field.type === 'dropdown'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <select [formControlName]=\"field.name\" class=\"form-input\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\">\r\n <option value=\"\" disabled selected *ngIf=\"field.placeholder\">{{ field.placeholder }}</option>\r\n <option value=\"\" disabled selected *ngIf=\"!field.placeholder\">Select</option>\r\n <option *ngFor=\"let opt of getFieldOptions(field)\" [value]=\"opt.value\">{{ opt.label }}</option>\r\n </select>\r\n <div class=\"hint-text mt-2\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n </div>\r\n\r\n <!-- Date -->\r\n <div *ngIf=\"field.type === 'date'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <div class=\"relative\">\r\n <input matInput [matDatepicker]=\"picker\" [formControlName]=\"field.name\"\r\n [placeholder]=\"field.placeholder || ''\" class=\"form-input\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\">\r\n <mat-datepicker-toggle matSuffix [for]=\"picker\" class=\"date-toggle\"></mat-datepicker-toggle>\r\n <mat-datepicker #picker></mat-datepicker>\r\n </div>\r\n <div class=\"hint-text mt-2\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n </div>\r\n\r\n <!-- Radio -->\r\n <div *ngIf=\"field.type === 'radio'\" class=\"radio-group-container\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\">\r\n <label class=\"field-label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </label>\r\n <mat-radio-group [formControlName]=\"field.name\" class=\"radio-group\">\r\n <mat-radio-button *ngFor=\"let opt of getFieldOptions(field)\" [value]=\"opt.value\" class=\"radio-button\">\r\n {{ opt.label }}\r\n </mat-radio-button>\r\n </mat-radio-group>\r\n <div class=\"hint-text mt-2\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n </div>\r\n\r\n <!-- Composite Field (Sub-groups) -->\r\n <div *ngIf=\"field.type === 'composite'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <div class=\"composite-container\">\r\n <ng-container *ngFor=\"let sub of field.subFields; let last = last\">\r\n <div class=\"composite-sub-field\" style=\"flex: 1;\">\r\n <div class=\"field-label\" *ngIf=\"sub.label\"\r\n style=\"font-size: 0.75rem; font-weight: normal; margin-bottom: 0.25rem;\">\r\n {{ sub.label }}\r\n </div>\r\n <div class=\"input-wrapper\">\r\n <input [type]=\"sub.type\" [formControlName]=\"sub.name\" [placeholder]=\"sub.placeholder || ''\"\r\n [readonly]=\"sub.readonly\" class=\"form-input\"\r\n [class.is-invalid]=\"group.get(sub.name)?.touched && group.get(sub.name)?.invalid\">\r\n <div class=\"input-suffix\" [class.color-suffix]=\"sub.suffixText === '%'\">\r\n <span class=\"suffix-text\" *ngIf=\"sub.suffixText\">{{ sub.suffixText }}</span>\r\n <mat-icon *ngIf=\"sub.readonly\" class=\"lock-icon\">lock_outline</mat-icon>\r\n </div>\r\n </div>\r\n </div>\r\n <!-- Adjust separator alignment if labels are present -->\r\n <span class=\"separator\" *ngIf=\"!last && field.separator\"\r\n [style.padding-top]=\"sub.label ? '1.25rem' : '0'\">{{ field.separator }}</span>\r\n </ng-container>\r\n </div>\r\n <div class=\"hint-text mt-2\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n <div class=\"error-text text-danger text-small mt-1\" *ngIf=\"group.errors?.['invalid_minMax_' + field.name]\">\r\n Min value cannot be greater than Max value.\r\n </div>\r\n <div class=\"error-text text-danger text-small mt-1\"\r\n *ngIf=\"group.errors?.['invalid_percentageTotal_' + field.name]\">\r\n Total percentage must be 100%.\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- File Upload -->\r\n <div *ngIf=\"field.type === 'file'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n\r\n <!-- Upload Box -->\r\n <div class=\"upload-box\" (click)=\"fileInput.click()\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\"\r\n *ngIf=\"!field.uploadedFiles || field.uploadedFiles.length === 0 || field.multiple\">\r\n <mat-icon class=\"upload-icon\">cloud_upload</mat-icon>\r\n <div class=\"upload-text\">Click to upload File</div>\r\n <div class=\"upload-hint\">PDF, DOCX, JPG, PNG (Max 5MB)</div>\r\n <input #fileInput type=\"file\" [attr.multiple]=\"field.multiple ? true : null\" [attr.accept]=\"field.accept\"\r\n (change)=\"onFileChange($event, field)\" style=\"display: none;\">\r\n </div>\r\n\r\n <!-- Uploaded Files List -->\r\n <div class=\"uploaded-files-list\" *ngIf=\"field.uploadedFiles && field.uploadedFiles.length > 0\">\r\n <div class=\"uploaded-file-item\" *ngFor=\"let file of field.uploadedFiles; let i = index\">\r\n <div class=\"file-icon\">\r\n <mat-icon class=\"pdf-icon\" *ngIf=\"file.type === 'application/pdf'\">picture_as_pdf</mat-icon>\r\n <mat-icon class=\"doc-icon\"\r\n *ngIf=\"file.type.includes('word') || file.type.includes('document')\">description</mat-icon>\r\n <mat-icon class=\"img-icon\" *ngIf=\"file.type.includes('image')\">image</mat-icon>\r\n <mat-icon class=\"file-icon-default\"\r\n *ngIf=\"!file.type.includes('pdf') && !file.type.includes('word') && !file.type.includes('document') && !file.type.includes('image')\">insert_drive_file</mat-icon>\r\n </div>\r\n <div class=\"file-info\">\r\n <div class=\"file-name\">{{ file.name }}</div>\r\n <div class=\"file-size\">{{ (file.size / 1024).toFixed(2) }} KB</div>\r\n </div>\r\n <button mat-icon-button color=\"warn\" type=\"button\" (click)=\"removeFile(field, i)\"\r\n class=\"remove-file-btn\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n</ng-template>", styles: [":host{display:block;width:100%}.configurable-form-container{padding:0;font-family:var(--cf-font-family, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif)}.configurable-form-container .section-card{background:var(--cf-surface-background, #ffffff);border:1px solid var(--cf-border-color, #D1D5DB);border-radius:var(--cf-section-radius, 12px);padding:var(--cf-section-padding, 2rem);margin-bottom:var(--cf-section-spacing, 1.5rem);box-shadow:var(--cf-section-shadow, 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px 0 rgba(0, 0, 0, .06))}.configurable-form-container .section-card:last-of-type{margin-bottom:0}.configurable-form-container .section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--cf-section-spacing, 1.5rem);padding-bottom:1rem;border-bottom:2px solid var(--cf-border-color, #D1D5DB)}.configurable-form-container .section-title{font-size:var(--cf-section-title-size, 1.25rem);font-weight:var(--cf-section-title-weight, 600);color:var(--cf-text-primary, #111827);margin:0;letter-spacing:-.025em}.configurable-form-container .form-grid{display:flex;flex-wrap:wrap;margin-left:-.625rem;margin-right:-.625rem}.configurable-form-container .form-col{position:relative;width:100%;padding-left:.625rem;padding-right:.625rem;margin-bottom:1.25rem}.configurable-form-container .col-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.configurable-form-container .col-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.configurable-form-container .col-3{flex:0 0 25%;max-width:25%}.configurable-form-container .col-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.configurable-form-container .col-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.configurable-form-container .col-6{flex:0 0 50%;max-width:50%}.configurable-form-container .col-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.configurable-form-container .col-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.configurable-form-container .col-9{flex:0 0 75%;max-width:75%}.configurable-form-container .col-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.configurable-form-container .col-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.configurable-form-container .col-12{flex:0 0 100%;max-width:100%}@media(min-width:769px){.configurable-form-container .col-md-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.configurable-form-container .col-md-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.configurable-form-container .col-md-3{flex:0 0 25%;max-width:25%}.configurable-form-container .col-md-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.configurable-form-container .col-md-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.configurable-form-container .col-md-6{flex:0 0 50%;max-width:50%}.configurable-form-container .col-md-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.configurable-form-container .col-md-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.configurable-form-container .col-md-9{flex:0 0 75%;max-width:75%}.configurable-form-container .col-md-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.configurable-form-container .col-md-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.configurable-form-container .col-md-12{flex:0 0 100%;max-width:100%}}.configurable-form-container .field-container{margin-bottom:0}.configurable-form-container .field-label{display:block;font-size:var(--cf-label-size, .875rem);font-weight:var(--cf-label-weight, 600);color:var(--cf-text-primary, #111827);margin-bottom:.5rem;line-height:1.25rem}.configurable-form-container .field-label .text-danger{color:var(--cf-error-color, #DC2626);margin-left:.125rem}.configurable-form-container .input-wrapper{position:relative;display:flex;align-items:center}.configurable-form-container .form-input{width:100%;padding:var(--cf-input-padding-y, .625rem) var(--cf-input-padding-x, .875rem);font-size:var(--cf-input-font-size, .875rem);line-height:1.5;color:var(--cf-text-primary, #111827);background-color:var(--cf-input-bg, #ffffff);border:1.5px solid var(--cf-border-color, #D1D5DB);border-radius:var(--cf-input-radius, 8px);transition:all .2s ease;font-family:inherit}.configurable-form-container .form-input:hover:not(:disabled):not([readonly]){border-color:var(--cf-input-hover-border-color, #9CA3AF)}.configurable-form-container .form-input:focus{outline:none;border-color:var(--cf-primary-color, #3B82F6);box-shadow:0 0 0 3px #3b82f61a}.configurable-form-container .form-input::placeholder{color:#9ca3af}.configurable-form-container .form-input:disabled,.configurable-form-container .form-input[readonly]{background-color:var(--cf-disabled-background, #F3F4F6);color:var(--cf-text-secondary, #6B7280);cursor:not-allowed;border-color:#e5e7eb}.configurable-form-container .form-input.is-invalid{border-color:var(--cf-error-color, #DC2626);background-color:#fef2f2}.configurable-form-container .form-input.is-invalid:focus{box-shadow:0 0 0 3px #dc26261a}.configurable-form-container select.form-input{appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236B7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3E%3C/svg%3E\");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;cursor:pointer}.configurable-form-container select.form-input:disabled{cursor:not-allowed}.configurable-form-container .form-textarea{resize:vertical;min-height:100px;font-family:inherit}.configurable-form-container .input-suffix{position:absolute;right:.75rem;display:flex;align-items:center;gap:.5rem;pointer-events:none}.configurable-form-container .input-suffix .password-toggle-icon{pointer-events:auto;cursor:pointer}.configurable-form-container .input-suffix .password-toggle-icon:hover{color:var(--cf-text-primary, #111827)}.configurable-form-container .input-suffix .suffix-text{font-size:.875rem;color:var(--cf-text-secondary, #6B7280);font-weight:500}.configurable-form-container .input-suffix mat-icon{font-size:1.25rem;width:1.25rem;height:1.25rem;color:var(--cf-text-secondary, #6B7280)}.configurable-form-container .input-suffix .lock-icon{color:#9ca3af}.configurable-form-container .input-suffix.color-suffix .suffix-text{color:var(--cf-primary-color, #3B82F6);font-weight:600}.configurable-form-container .char-count-row{display:flex;justify-content:space-between;align-items:center;margin-top:.5rem}.configurable-form-container .char-count{font-size:.75rem;color:var(--cf-text-secondary, #6B7280);font-weight:500}.configurable-form-container .hint-text{font-size:var(--cf-hint-size, .75rem);color:var(--cf-text-secondary, #6B7280);margin-top:.375rem;line-height:1.25rem}.configurable-form-container .mt-2{margin-top:.5rem}.configurable-form-container .radio-group-container.is-invalid .field-label{color:var(--cf-error-color, #DC2626)}.configurable-form-container .radio-group{display:flex;gap:2rem;flex-wrap:wrap;margin-top:.5rem}.configurable-form-container .radio-button{margin:0}.configurable-form-container .composite-container{display:flex;align-items:center;gap:.75rem}.configurable-form-container .composite-container .separator{font-size:.875rem;color:var(--cf-text-secondary, #6B7280);font-weight:500}.configurable-form-container .composite-container .input-wrapper{flex:1}.configurable-form-container .upload-box{border:2px dashed var(--cf-border-color, #D1D5DB);border-radius:var(--cf-section-radius, 12px);padding:2.5rem;text-align:center;cursor:pointer;transition:all .2s ease;background-color:var(--cf-hover-background, #F9FAFB)}.configurable-form-container .upload-box:hover{border-color:var(--cf-primary-color, #3B82F6);background-color:#3b82f608}.configurable-form-container .upload-box.is-invalid{border-color:var(--cf-error-color, #DC2626);background-color:#fef2f2}.configurable-form-container .upload-box .upload-icon{font-size:3rem;width:3rem;height:3rem;color:var(--cf-primary-color, #3B82F6);margin-bottom:.75rem}.configurable-form-container .upload-box .upload-text{font-size:.875rem;color:var(--cf-text-primary, #111827);font-weight:500;margin-bottom:.375rem}.configurable-form-container .upload-box .upload-hint{font-size:.75rem;color:var(--cf-text-secondary, #6B7280)}.configurable-form-container .uploaded-files-list{margin-top:1rem}.configurable-form-container .uploaded-file-item{display:flex;align-items:center;gap:.75rem;padding:.875rem;border:1.5px solid var(--cf-border-color, #D1D5DB);border-radius:8px;margin-bottom:.5rem;background-color:var(--cf-hover-background, #F9FAFB);transition:all .2s ease}.configurable-form-container .uploaded-file-item:hover{border-color:#9ca3af;box-shadow:0 1px 3px #0000001a}.configurable-form-container .uploaded-file-item .file-icon mat-icon{font-size:2rem;width:2rem;height:2rem}.configurable-form-container .uploaded-file-item .file-icon mat-icon.pdf-icon{color:#dc2626}.configurable-form-container .uploaded-file-item .file-icon mat-icon.doc-icon{color:#2563eb}.configurable-form-container .uploaded-file-item .file-icon mat-icon.img-icon{color:#059669}.configurable-form-container .uploaded-file-item .file-icon mat-icon.file-icon-default{color:var(--cf-text-secondary, #6B7280)}.configurable-form-container .uploaded-file-item .file-info{flex:1}.configurable-form-container .uploaded-file-item .file-info .file-name{font-size:.875rem;color:var(--cf-text-primary, #111827);font-weight:500;margin-bottom:.125rem}.configurable-form-container .uploaded-file-item .file-info .file-size{font-size:.75rem;color:var(--cf-text-secondary, #6B7280)}.configurable-form-container .uploaded-file-item .remove-file-btn mat-icon{font-size:1.25rem;width:1.25rem;height:1.25rem}.configurable-form-container .relative{position:relative}.configurable-form-container .date-toggle{position:absolute;right:0;top:50%;transform:translateY(-50%)}.configurable-form-container .repeater-item{padding:1.5rem;border:1.5px solid var(--cf-border-color, #D1D5DB);border-radius:10px;margin-bottom:1rem;background-color:var(--cf-hover-background, #F9FAFB);transition:all .2s ease}.configurable-form-container .repeater-item:hover{border-color:#9ca3af;box-shadow:0 2px 4px #0000000d}.configurable-form-container .repeater-item-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1.25rem;padding-bottom:.75rem;border-bottom:1.5px solid var(--cf-border-color, #D1D5DB)}.configurable-form-container .repeater-item-header .text-small{font-size:.875rem;font-weight:600;color:var(--cf-text-primary, #111827);text-transform:uppercase;letter-spacing:.025em}.configurable-form-container .repeater-separator{height:1px;background-color:var(--cf-border-color, #D1D5DB);margin:1.5rem 0}.configurable-form-container .add-btn{margin-top:1rem;display:inline-flex;align-items:center;gap:.5rem;padding:.625rem 1.25rem;background-color:transparent;color:var(--cf-primary-color, #3B82F6);border:1.5px solid var(--cf-primary-color, #3B82F6);border-radius:8px;font-size:.875rem;font-weight:600;cursor:pointer;transition:all .2s ease}.configurable-form-container .add-btn:hover:not(:disabled){background-color:var(--cf-primary-color, #3B82F6);color:#fff}.configurable-form-container .add-btn:disabled{opacity:.5;cursor:not-allowed;border-color:var(--cf-disabled-background, #E5E7EB)}.configurable-form-container .add-btn mat-icon{font-size:1.25rem;width:1.25rem;height:1.25rem}@media(max-width:768px){.configurable-form-container .section-card{padding:1.25rem;border-radius:8px}.configurable-form-container .form-grid{margin-left:-.5rem;margin-right:-.5rem}.configurable-form-container .form-col{padding-left:.5rem;padding-right:.5rem;margin-bottom:1rem}.configurable-form-container .radio-group{flex-direction:column;gap:.75rem}.configurable-form-container .composite-container{flex-direction:column;align-items:stretch}.configurable-form-container .composite-container .separator{text-align:center}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$2.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i1$2.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "ngmodule", type: MaterialModule }, { kind: "directive", type: i5.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i6.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i6.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "component", type: i7.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i7.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i7.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "directive", type: i8.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i10.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i10.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }] });
3536
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: ConfigurableFormComponent, isStandalone: true, selector: "lib-configurable-form", inputs: { config: "config", jsonConfig: "jsonConfig", data: "data", baseApiUrl: "baseApiUrl", labels: "labels" }, outputs: { optionsLoad: "optionsLoad" }, usesOnChanges: true, ngImport: i0, template: "<form [formGroup]=\"form\" class=\"configurable-form-container\" *ngIf=\"form\">\r\n\r\n <ng-container *ngFor=\"let section of sections\">\r\n\r\n <!-- Repeater Section -->\r\n <div *ngIf=\"section.isRepeater; else normalSection\"\r\n [ngClass]=\"section.noCardLayout ? 'section-no-card' : 'section-card'\">\r\n <div class=\"section-header\" *ngIf=\"section.sectionTitle\">\r\n <h3 class=\"section-title\">{{ section.sectionTitle }}</h3>\r\n <button *ngIf=\"section.collapsible\" mat-icon-button type=\"button\" (click)=\"toggleSection(section)\">\r\n <mat-icon>{{ section.collapsed ? 'expand_more' : 'expand_less' }}</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <div [formArrayName]=\"section.formArrayName || 'items'\" *ngIf=\"!section.collapsed\">\r\n <div *ngFor=\"let item of getFormArray(section.formArrayName!).controls; let i = index\"\r\n [formGroupName]=\"i\" class=\"repeater-item\">\r\n <div class=\"repeater-item-header\" *ngIf=\"getFormArray(section.formArrayName!).length > 1\">\r\n <span class=\"text-small\">{{ section.repeaterItemLabel || labels.repeaterItemDefaultLabel }} {{i\r\n + 1}}</span>\r\n <button mat-icon-button color=\"warn\" type=\"button\"\r\n (click)=\"removeRepeaterItem(section.formArrayName!, i)\"\r\n [disabled]=\"section.minItems && getFormArray(section.formArrayName!).length <= section.minItems\">\r\n <mat-icon>delete</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Fields Grid -->\r\n <div class=\"form-grid\">\r\n <div *ngFor=\"let field of section.fields\" [ngClass]=\"['form-col', field.class || 'col-12']\"\r\n [hidden]=\"!isFieldVisible(field)\">\r\n <ng-container\r\n *ngTemplateOutlet=\"fieldTemplate; context: {field: field, group: item}\"></ng-container>\r\n </div>\r\n </div>\r\n\r\n <div class=\"repeater-separator\" *ngIf=\"i < getFormArray(section.formArrayName!).length - 1\"></div>\r\n </div>\r\n </div>\r\n\r\n <button mat-button color=\"primary\" type=\"button\" (click)=\"addRepeaterItem(section)\" class=\"add-btn\"\r\n *ngIf=\"!section.collapsed\"\r\n [disabled]=\"section.maxItems && getFormArray(section.formArrayName!).length >= section.maxItems\">\r\n <mat-icon>add_circle_outline</mat-icon> {{ section.addLabel || labels.addMoreDefaultLabel }}\r\n </button>\r\n </div>\r\n\r\n <!-- Normal Section -->\r\n <ng-template #normalSection>\r\n <div [ngClass]=\"section.noCardLayout ? 'section-no-card' : 'section-card'\">\r\n <div class=\"section-header\" *ngIf=\"section.sectionTitle\">\r\n <h3 class=\"section-title\">{{ section.sectionTitle }}</h3>\r\n <button *ngIf=\"section.collapsible\" mat-icon-button type=\"button\" (click)=\"toggleSection(section)\">\r\n <mat-icon>{{ section.collapsed ? 'expand_more' : 'expand_less' }}</mat-icon>\r\n </button>\r\n </div>\r\n <div class=\"form-grid\" *ngIf=\"!section.collapsed\">\r\n <div *ngFor=\"let field of section.fields\" [ngClass]=\"['form-col', field.class || 'col-12']\"\r\n [hidden]=\"!isFieldVisible(field)\">\r\n <!-- Pass the root form group to the template -->\r\n <ng-container\r\n *ngTemplateOutlet=\"fieldTemplate; context: {field: field, group: form}\"></ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n </ng-container>\r\n\r\n</form>\r\n\r\n<!-- Reusable Field Template -->\r\n<ng-template #fieldTemplate let-field=\"field\" let-group=\"group\">\r\n <div [formGroup]=\"group\">\r\n\r\n <!-- Text / Email / Number / Tel / URL (Password removed) -->\r\n <div *ngIf=\"['text', 'email', 'number', 'tel', 'url'].includes(field.type || '')\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <div class=\"input-wrapper\">\r\n <input [type]=\"field.type\" [formControlName]=\"field.name\" [placeholder]=\"field.placeholder || ''\"\r\n [readonly]=\"field.readonly\" class=\"form-input\" [name]=\"field.name\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\"\r\n [maxlength]=\"field.validationRules?.maxLength || field.uiConfig?.maxCharacters\">\r\n <div class=\"input-suffix\">\r\n <span class=\"suffix-text\" *ngIf=\"field.suffixText\">{{ field.suffixText }}</span>\r\n <mat-icon *ngIf=\"field.suffixIcon\">{{ field.suffixIcon }}</mat-icon>\r\n <mat-icon *ngIf=\"field.icon\">{{ field.icon }}</mat-icon>\r\n <mat-icon *ngIf=\"field.readonly\" class=\"lock-icon\">lock_outline</mat-icon>\r\n </div>\r\n </div>\r\n <!-- Character count -->\r\n <div class=\"char-count-row\" *ngIf=\"field.uiConfig?.maxCharacters || field.validationRules?.maxLength\">\r\n <div class=\"hint-text\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n <div class=\"char-count\">\r\n {{ getCharacterCount(field.name) }} / {{ field.uiConfig?.maxCharacters ||\r\n field.validationRules?.maxLength }} {{ labels.charactersLabel }}\r\n </div>\r\n </div>\r\n <div class=\"hint-text mt-2\"\r\n *ngIf=\"(field.hint || field.helpText) && !field.uiConfig?.maxCharacters && !field.validationRules?.maxLength\">\r\n {{ field.hint || field.helpText }}\r\n </div>\r\n </div>\r\n\r\n <!-- Password Field (Separated) -->\r\n <div *ngIf=\"field.type === 'password'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <div class=\"input-wrapper\">\r\n <input [type]=\"isPasswordVisible(field.name) ? 'text' : 'password'\" [formControlName]=\"field.name\"\r\n [placeholder]=\"field.placeholder || ''\" [readonly]=\"field.readonly\" class=\"form-input\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\"\r\n [maxlength]=\"field.validationRules?.maxLength || field.uiConfig?.maxCharacters\">\r\n <div class=\"input-suffix\">\r\n <span class=\"suffix-text\" *ngIf=\"field.suffixText\">{{ field.suffixText }}</span>\r\n <mat-icon *ngIf=\"field.suffixIcon\">{{ field.suffixIcon }}</mat-icon>\r\n <mat-icon *ngIf=\"field.icon\">{{ field.icon }}</mat-icon>\r\n <mat-icon *ngIf=\"field.readonly\" class=\"lock-icon\">lock_outline</mat-icon>\r\n <mat-icon class=\"password-toggle-icon\" (click)=\"togglePassword(field.name)\">\r\n {{ isPasswordVisible(field.name) ? 'visibility' : 'visibility_off' }}\r\n </mat-icon>\r\n </div>\r\n </div>\r\n <!-- Character count -->\r\n <div class=\"char-count-row\" *ngIf=\"field.uiConfig?.maxCharacters || field.validationRules?.maxLength\">\r\n <div class=\"hint-text\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n <div class=\"char-count\">\r\n {{ getCharacterCount(field.name) }} / {{ field.uiConfig?.maxCharacters ||\r\n field.validationRules?.maxLength }} {{ labels.charactersLabel }}\r\n </div>\r\n </div>\r\n <div class=\"hint-text mt-2\"\r\n *ngIf=\"(field.hint || field.helpText) && !field.uiConfig?.maxCharacters && !field.validationRules?.maxLength\">\r\n {{ field.hint || field.helpText }}\r\n </div>\r\n </div>\r\n\r\n <!-- Textarea -->\r\n <div *ngIf=\"field.type === 'textarea'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <textarea [formControlName]=\"field.name\" [placeholder]=\"field.placeholder || ''\" [readonly]=\"field.readonly\"\r\n class=\"form-input form-textarea\" rows=\"4\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\"\r\n [maxlength]=\"field.validationRules?.maxLength || field.uiConfig?.maxCharacters\"></textarea>\r\n <!-- Character count -->\r\n <div class=\"char-count-row\" *ngIf=\"field.uiConfig?.maxCharacters || field.validationRules?.maxLength\">\r\n <div class=\"hint-text\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n <div class=\"char-count\">\r\n {{ getCharacterCount(field.name) }} / {{ field.uiConfig?.maxCharacters ||\r\n field.validationRules?.maxLength }} {{ labels.charactersLabel }}\r\n </div>\r\n </div>\r\n <div class=\"hint-text mt-2\"\r\n *ngIf=\"(field.hint || field.helpText) && !field.uiConfig?.maxCharacters && !field.validationRules?.maxLength\">\r\n {{ field.hint || field.helpText }}\r\n </div>\r\n </div>\r\n\r\n <!-- Select / Dropdown -->\r\n <div *ngIf=\"field.type === 'select' || field.type === 'dropdown'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <select [formControlName]=\"field.name\" class=\"form-input\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\">\r\n <option value=\"\" disabled selected *ngIf=\"field.placeholder\">{{ field.placeholder }}</option>\r\n <option value=\"\" disabled selected *ngIf=\"!field.placeholder\">{{ labels.selectDefaultPlaceholder }}\r\n </option>\r\n <option *ngFor=\"let opt of getFieldOptions(field)\" [value]=\"opt.value\">{{ opt.label }}</option>\r\n </select>\r\n <div class=\"hint-text mt-2\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n </div>\r\n\r\n <!-- Date -->\r\n <div *ngIf=\"field.type === 'date'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <div class=\"relative\">\r\n <input matInput [matDatepicker]=\"picker\" [formControlName]=\"field.name\"\r\n [placeholder]=\"field.placeholder || ''\" class=\"form-input\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\">\r\n <mat-datepicker-toggle matSuffix [for]=\"picker\" class=\"date-toggle\"></mat-datepicker-toggle>\r\n <mat-datepicker #picker></mat-datepicker>\r\n </div>\r\n <div class=\"hint-text mt-2\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n </div>\r\n\r\n <!-- Radio -->\r\n <div *ngIf=\"field.type === 'radio'\" class=\"radio-group-container\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\">\r\n <label class=\"field-label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </label>\r\n <mat-radio-group [formControlName]=\"field.name\" class=\"radio-group\">\r\n <mat-radio-button *ngFor=\"let opt of getFieldOptions(field)\" [value]=\"opt.value\" class=\"radio-button\">\r\n {{ opt.label }}\r\n </mat-radio-button>\r\n </mat-radio-group>\r\n <div class=\"hint-text mt-2\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n </div>\r\n\r\n <!-- Composite Field (Sub-groups) -->\r\n <div *ngIf=\"field.type === 'composite'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <div class=\"composite-container\">\r\n <ng-container *ngFor=\"let sub of field.subFields; let last = last\">\r\n <div class=\"composite-sub-field\" style=\"flex: 1;\">\r\n <div class=\"field-label\" *ngIf=\"sub.label\"\r\n style=\"font-size: 0.75rem; font-weight: normal; margin-bottom: 0.25rem;\">\r\n {{ sub.label }}\r\n </div>\r\n <div class=\"input-wrapper\">\r\n <input [type]=\"sub.type\" [formControlName]=\"sub.name\" [placeholder]=\"sub.placeholder || ''\"\r\n [readonly]=\"sub.readonly\" class=\"form-input\"\r\n [class.is-invalid]=\"group.get(sub.name)?.touched && group.get(sub.name)?.invalid\">\r\n <div class=\"input-suffix\" [class.color-suffix]=\"sub.suffixText === '%'\">\r\n <span class=\"suffix-text\" *ngIf=\"sub.suffixText\">{{ sub.suffixText }}</span>\r\n <mat-icon *ngIf=\"sub.readonly\" class=\"lock-icon\">lock_outline</mat-icon>\r\n </div>\r\n </div>\r\n </div>\r\n <!-- Adjust separator alignment if labels are present -->\r\n <span class=\"separator\" *ngIf=\"!last && field.separator\"\r\n [style.padding-top]=\"sub.label ? '1.25rem' : '0'\">{{ field.separator }}</span>\r\n </ng-container>\r\n </div>\r\n <div class=\"hint-text mt-2\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n <div class=\"error-text text-danger text-small mt-1\" *ngIf=\"group.errors?.['invalid_minMax_' + field.name]\">\r\n {{ labels.errorMinValue }}\r\n </div>\r\n <div class=\"error-text text-danger text-small mt-1\"\r\n *ngIf=\"group.errors?.['invalid_percentageTotal_' + field.name]\">\r\n {{ labels.errorPercentageTotal }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- File Upload -->\r\n <div *ngIf=\"field.type === 'file'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n\r\n <!-- Upload Box -->\r\n <div class=\"upload-box\" (click)=\"fileInput.click()\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\"\r\n *ngIf=\"!field.uploadedFiles || field.uploadedFiles.length === 0 || field.multiple\">\r\n <mat-icon class=\"upload-icon\">cloud_upload</mat-icon>\r\n <div class=\"upload-text\">{{ labels.uploadDragDropText }}</div>\r\n <div class=\"upload-hint\">{{ labels.uploadHint }}</div>\r\n <input #fileInput type=\"file\" [attr.multiple]=\"field.multiple ? true : null\" [attr.accept]=\"field.accept\"\r\n (change)=\"onFileChange($event, field)\" style=\"display: none;\">\r\n </div>\r\n\r\n <!-- Uploaded Files List -->\r\n <div class=\"uploaded-files-list\" *ngIf=\"field.uploadedFiles && field.uploadedFiles.length > 0\">\r\n <div class=\"uploaded-file-item\" *ngFor=\"let file of field.uploadedFiles; let i = index\">\r\n <div class=\"file-icon\">\r\n <mat-icon class=\"pdf-icon\" *ngIf=\"file.type === 'application/pdf'\">picture_as_pdf</mat-icon>\r\n <mat-icon class=\"doc-icon\"\r\n *ngIf=\"file.type.includes('word') || file.type.includes('document')\">description</mat-icon>\r\n <mat-icon class=\"img-icon\" *ngIf=\"file.type.includes('image')\">image</mat-icon>\r\n <mat-icon class=\"file-icon-default\"\r\n *ngIf=\"!file.type.includes('pdf') && !file.type.includes('word') && !file.type.includes('document') && !file.type.includes('image')\">insert_drive_file</mat-icon>\r\n </div>\r\n <div class=\"file-info\">\r\n <div class=\"file-name\">{{ file.name }}</div>\r\n <div class=\"file-size\">{{ (file.size / 1024).toFixed(2) }} KB</div>\r\n </div>\r\n <button mat-icon-button color=\"warn\" type=\"button\" (click)=\"removeFile(field, i)\"\r\n class=\"remove-file-btn\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n</ng-template>", styles: [":host{display:block;width:100%}.configurable-form-container{padding:0;font-family:var(--cf-font-family, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif)}.configurable-form-container .section-card{background:var(--cf-surface-background, #ffffff);border:1px solid var(--cf-border-color, #D1D5DB);border-radius:var(--cf-section-radius, 12px);padding:var(--cf-section-padding, 2rem);margin-bottom:var(--cf-section-spacing, 1.5rem);box-shadow:var(--cf-section-shadow, 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px 0 rgba(0, 0, 0, .06))}.configurable-form-container .section-card:last-of-type{margin-bottom:0}.configurable-form-container .section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--cf-section-spacing, 1.5rem);padding-bottom:1rem;border-bottom:2px solid var(--cf-border-color, #D1D5DB)}.configurable-form-container .section-title{font-size:var(--cf-section-title-size, 1.25rem);font-weight:var(--cf-section-title-weight, 600);color:var(--cf-text-primary, #111827);margin:0;letter-spacing:-.025em}.configurable-form-container .form-grid{display:flex;flex-wrap:wrap;margin-left:-.625rem;margin-right:-.625rem}.configurable-form-container .form-col{position:relative;width:100%;padding-left:.625rem;padding-right:.625rem;margin-bottom:1.25rem}.configurable-form-container .col-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.configurable-form-container .col-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.configurable-form-container .col-3{flex:0 0 25%;max-width:25%}.configurable-form-container .col-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.configurable-form-container .col-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.configurable-form-container .col-6{flex:0 0 50%;max-width:50%}.configurable-form-container .col-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.configurable-form-container .col-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.configurable-form-container .col-9{flex:0 0 75%;max-width:75%}.configurable-form-container .col-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.configurable-form-container .col-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.configurable-form-container .col-12{flex:0 0 100%;max-width:100%}@media(min-width:769px){.configurable-form-container .col-md-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.configurable-form-container .col-md-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.configurable-form-container .col-md-3{flex:0 0 25%;max-width:25%}.configurable-form-container .col-md-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.configurable-form-container .col-md-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.configurable-form-container .col-md-6{flex:0 0 50%;max-width:50%}.configurable-form-container .col-md-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.configurable-form-container .col-md-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.configurable-form-container .col-md-9{flex:0 0 75%;max-width:75%}.configurable-form-container .col-md-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.configurable-form-container .col-md-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.configurable-form-container .col-md-12{flex:0 0 100%;max-width:100%}}.configurable-form-container .field-container{margin-bottom:0}.configurable-form-container .field-label{display:block;font-size:var(--cf-label-size, .875rem);font-weight:var(--cf-label-weight, 600);color:var(--cf-text-primary, #111827);margin-bottom:.5rem;line-height:1.25rem}.configurable-form-container .field-label .text-danger{color:var(--cf-error-color, #DC2626);margin-left:.125rem}.configurable-form-container .input-wrapper{position:relative;display:flex;align-items:center}.configurable-form-container .form-input{width:100%;padding:var(--cf-input-padding-y, .625rem) var(--cf-input-padding-x, .875rem);font-size:var(--cf-input-font-size, .875rem);line-height:1.5;color:var(--cf-text-primary, #111827);background-color:var(--cf-input-bg, #ffffff);border:1.5px solid var(--cf-border-color, #D1D5DB);border-radius:var(--cf-input-radius, 8px);transition:all .2s ease;font-family:inherit}.configurable-form-container .form-input:hover:not(:disabled):not([readonly]){border-color:var(--cf-input-hover-border-color, #9CA3AF)}.configurable-form-container .form-input:focus{outline:none;border-color:var(--cf-primary-color, #3B82F6);box-shadow:0 0 0 3px #3b82f61a}.configurable-form-container .form-input::placeholder{color:#9ca3af}.configurable-form-container .form-input:disabled,.configurable-form-container .form-input[readonly]{background-color:var(--cf-disabled-background, #F3F4F6);color:var(--cf-text-secondary, #6B7280);cursor:not-allowed;border-color:#e5e7eb}.configurable-form-container .form-input.is-invalid{border-color:var(--cf-error-color, #DC2626);background-color:#fef2f2}.configurable-form-container .form-input.is-invalid:focus{box-shadow:0 0 0 3px #dc26261a}.configurable-form-container select.form-input{appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236B7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3E%3C/svg%3E\");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;cursor:pointer}.configurable-form-container select.form-input:disabled{cursor:not-allowed}.configurable-form-container .form-textarea{resize:vertical;min-height:100px;font-family:inherit}.configurable-form-container .input-suffix{position:absolute;right:.75rem;display:flex;align-items:center;gap:.5rem;pointer-events:none}.configurable-form-container .input-suffix .password-toggle-icon{pointer-events:auto;cursor:pointer}.configurable-form-container .input-suffix .password-toggle-icon:hover{color:var(--cf-text-primary, #111827)}.configurable-form-container .input-suffix .suffix-text{font-size:.875rem;color:var(--cf-text-secondary, #6B7280);font-weight:500}.configurable-form-container .input-suffix mat-icon{font-size:1.25rem;width:1.25rem;height:1.25rem;color:var(--cf-text-secondary, #6B7280)}.configurable-form-container .input-suffix .lock-icon{color:#9ca3af}.configurable-form-container .input-suffix.color-suffix .suffix-text{color:var(--cf-primary-color, #3B82F6);font-weight:600}.configurable-form-container .char-count-row{display:flex;justify-content:space-between;align-items:center;margin-top:.5rem}.configurable-form-container .char-count{font-size:.75rem;color:var(--cf-text-secondary, #6B7280);font-weight:500}.configurable-form-container .hint-text{font-size:var(--cf-hint-size, .75rem);color:var(--cf-text-secondary, #6B7280);margin-top:.375rem;line-height:1.25rem}.configurable-form-container .mt-2{margin-top:.5rem}.configurable-form-container .radio-group-container.is-invalid .field-label{color:var(--cf-error-color, #DC2626)}.configurable-form-container .radio-group{display:flex;gap:2rem;flex-wrap:wrap;margin-top:.5rem}.configurable-form-container .radio-button{margin:0}.configurable-form-container .composite-container{display:flex;align-items:center;gap:.75rem}.configurable-form-container .composite-container .separator{font-size:.875rem;color:var(--cf-text-secondary, #6B7280);font-weight:500}.configurable-form-container .composite-container .input-wrapper{flex:1}.configurable-form-container .upload-box{border:2px dashed var(--cf-border-color, #D1D5DB);border-radius:var(--cf-section-radius, 12px);padding:2.5rem;text-align:center;cursor:pointer;transition:all .2s ease;background-color:var(--cf-hover-background, #F9FAFB)}.configurable-form-container .upload-box:hover{border-color:var(--cf-primary-color, #3B82F6);background-color:#3b82f608}.configurable-form-container .upload-box.is-invalid{border-color:var(--cf-error-color, #DC2626);background-color:#fef2f2}.configurable-form-container .upload-box .upload-icon{font-size:3rem;width:3rem;height:3rem;color:var(--cf-primary-color, #3B82F6);margin-bottom:.75rem}.configurable-form-container .upload-box .upload-text{font-size:.875rem;color:var(--cf-text-primary, #111827);font-weight:500;margin-bottom:.375rem}.configurable-form-container .upload-box .upload-hint{font-size:.75rem;color:var(--cf-text-secondary, #6B7280)}.configurable-form-container .uploaded-files-list{margin-top:1rem}.configurable-form-container .uploaded-file-item{display:flex;align-items:center;gap:.75rem;padding:.875rem;border:1.5px solid var(--cf-border-color, #D1D5DB);border-radius:8px;margin-bottom:.5rem;background-color:var(--cf-hover-background, #F9FAFB);transition:all .2s ease}.configurable-form-container .uploaded-file-item:hover{border-color:#9ca3af;box-shadow:0 1px 3px #0000001a}.configurable-form-container .uploaded-file-item .file-icon mat-icon{font-size:2rem;width:2rem;height:2rem}.configurable-form-container .uploaded-file-item .file-icon mat-icon.pdf-icon{color:#dc2626}.configurable-form-container .uploaded-file-item .file-icon mat-icon.doc-icon{color:#2563eb}.configurable-form-container .uploaded-file-item .file-icon mat-icon.img-icon{color:#059669}.configurable-form-container .uploaded-file-item .file-icon mat-icon.file-icon-default{color:var(--cf-text-secondary, #6B7280)}.configurable-form-container .uploaded-file-item .file-info{flex:1}.configurable-form-container .uploaded-file-item .file-info .file-name{font-size:.875rem;color:var(--cf-text-primary, #111827);font-weight:500;margin-bottom:.125rem}.configurable-form-container .uploaded-file-item .file-info .file-size{font-size:.75rem;color:var(--cf-text-secondary, #6B7280)}.configurable-form-container .uploaded-file-item .remove-file-btn mat-icon{font-size:1.25rem;width:1.25rem;height:1.25rem}.configurable-form-container .relative{position:relative}.configurable-form-container .date-toggle{position:absolute;right:0;top:50%;transform:translateY(-50%)}.configurable-form-container .repeater-item{padding:1.5rem;border:1.5px solid var(--cf-border-color, #D1D5DB);border-radius:10px;margin-bottom:1rem;background-color:var(--cf-hover-background, #F9FAFB);transition:all .2s ease}.configurable-form-container .repeater-item:hover{border-color:#9ca3af;box-shadow:0 2px 4px #0000000d}.configurable-form-container .repeater-item-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1.25rem;padding-bottom:.75rem;border-bottom:1.5px solid var(--cf-border-color, #D1D5DB)}.configurable-form-container .repeater-item-header .text-small{font-size:.875rem;font-weight:600;color:var(--cf-text-primary, #111827);text-transform:uppercase;letter-spacing:.025em}.configurable-form-container .repeater-separator{height:1px;background-color:var(--cf-border-color, #D1D5DB);margin:1.5rem 0}.configurable-form-container .add-btn{margin-top:1rem;display:inline-flex;align-items:center;gap:.5rem;padding:.625rem 1.25rem;background-color:transparent;color:var(--cf-primary-color, #3B82F6);border:1.5px solid var(--cf-primary-color, #3B82F6);border-radius:8px;font-size:.875rem;font-weight:600;cursor:pointer;transition:all .2s ease}.configurable-form-container .add-btn:hover:not(:disabled){background-color:var(--cf-primary-color, #3B82F6);color:#fff}.configurable-form-container .add-btn:disabled{opacity:.5;cursor:not-allowed;border-color:var(--cf-disabled-background, #E5E7EB)}.configurable-form-container .add-btn mat-icon{font-size:1.25rem;width:1.25rem;height:1.25rem}@media(max-width:768px){.configurable-form-container .section-card{padding:1.25rem;border-radius:8px}.configurable-form-container .form-grid{margin-left:-.5rem;margin-right:-.5rem}.configurable-form-container .form-col{padding-left:.5rem;padding-right:.5rem;margin-bottom:1rem}.configurable-form-container .radio-group{flex-direction:column;gap:.75rem}.configurable-form-container .composite-container{flex-direction:column;align-items:stretch}.configurable-form-container .composite-container .separator{text-align:center}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$2.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i1$2.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "ngmodule", type: MaterialModule }, { kind: "directive", type: i5.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i6.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i6.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "component", type: i7.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i7.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i7.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "directive", type: i8.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i10.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i10.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }] });
3457
3537
  }
3458
3538
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ConfigurableFormComponent, decorators: [{
3459
3539
  type: Component,
3460
- args: [{ selector: 'lib-configurable-form', standalone: true, imports: [CommonModule, ReactiveFormsModule, MaterialModule], template: "<form [formGroup]=\"form\" class=\"configurable-form-container\" *ngIf=\"form\">\r\n\r\n <ng-container *ngFor=\"let section of sections\">\r\n\r\n <!-- Repeater Section -->\r\n <div *ngIf=\"section.isRepeater; else normalSection\"\r\n [ngClass]=\"section.noCardLayout ? 'section-no-card' : 'section-card'\">\r\n <div class=\"section-header\" *ngIf=\"section.sectionTitle\">\r\n <h3 class=\"section-title\">{{ section.sectionTitle }}</h3>\r\n <button *ngIf=\"section.collapsible\" mat-icon-button type=\"button\" (click)=\"toggleSection(section)\">\r\n <mat-icon>{{ section.collapsed ? 'expand_more' : 'expand_less' }}</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <div [formArrayName]=\"section.formArrayName || 'items'\" *ngIf=\"!section.collapsed\">\r\n <div *ngFor=\"let item of getFormArray(section.formArrayName!).controls; let i = index\"\r\n [formGroupName]=\"i\" class=\"repeater-item\">\r\n <div class=\"repeater-item-header\" *ngIf=\"getFormArray(section.formArrayName!).length > 1\">\r\n <span class=\"text-small\">{{ section.repeaterItemLabel || 'Item' }} {{i + 1}}</span>\r\n <button mat-icon-button color=\"warn\" type=\"button\"\r\n (click)=\"removeRepeaterItem(section.formArrayName!, i)\"\r\n [disabled]=\"section.minItems && getFormArray(section.formArrayName!).length <= section.minItems\">\r\n <mat-icon>delete</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Fields Grid -->\r\n <div class=\"form-grid\">\r\n <div *ngFor=\"let field of section.fields\" [ngClass]=\"['form-col', field.class || 'col-12']\"\r\n [hidden]=\"!isFieldVisible(field)\">\r\n <ng-container\r\n *ngTemplateOutlet=\"fieldTemplate; context: {field: field, group: item}\"></ng-container>\r\n </div>\r\n </div>\r\n\r\n <div class=\"repeater-separator\" *ngIf=\"i < getFormArray(section.formArrayName!).length - 1\"></div>\r\n </div>\r\n </div>\r\n\r\n <button mat-button color=\"primary\" type=\"button\" (click)=\"addRepeaterItem(section)\" class=\"add-btn\"\r\n *ngIf=\"!section.collapsed\"\r\n [disabled]=\"section.maxItems && getFormArray(section.formArrayName!).length >= section.maxItems\">\r\n <mat-icon>add_circle_outline</mat-icon> {{ section.addLabel || 'Add More' }}\r\n </button>\r\n </div>\r\n\r\n <!-- Normal Section -->\r\n <ng-template #normalSection>\r\n <div [ngClass]=\"section.noCardLayout ? 'section-no-card' : 'section-card'\">\r\n <div class=\"section-header\" *ngIf=\"section.sectionTitle\">\r\n <h3 class=\"section-title\">{{ section.sectionTitle }}</h3>\r\n <button *ngIf=\"section.collapsible\" mat-icon-button type=\"button\" (click)=\"toggleSection(section)\">\r\n <mat-icon>{{ section.collapsed ? 'expand_more' : 'expand_less' }}</mat-icon>\r\n </button>\r\n </div>\r\n <div class=\"form-grid\" *ngIf=\"!section.collapsed\">\r\n <div *ngFor=\"let field of section.fields\" [ngClass]=\"['form-col', field.class || 'col-12']\"\r\n [hidden]=\"!isFieldVisible(field)\">\r\n <!-- Pass the root form group to the template -->\r\n <ng-container\r\n *ngTemplateOutlet=\"fieldTemplate; context: {field: field, group: form}\"></ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n </ng-container>\r\n\r\n</form>\r\n\r\n<!-- Reusable Field Template -->\r\n<ng-template #fieldTemplate let-field=\"field\" let-group=\"group\">\r\n <div [formGroup]=\"group\">\r\n\r\n <!-- Text / Email / Number / Tel / URL (Password removed) -->\r\n <div *ngIf=\"['text', 'email', 'number', 'tel', 'url'].includes(field.type || '')\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <div class=\"input-wrapper\">\r\n <input [type]=\"field.type\" [formControlName]=\"field.name\" [placeholder]=\"field.placeholder || ''\"\r\n [readonly]=\"field.readonly\" class=\"form-input\" [name]=\"field.name\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\"\r\n [maxlength]=\"field.validationRules?.maxLength || field.uiConfig?.maxCharacters\">\r\n <div class=\"input-suffix\">\r\n <span class=\"suffix-text\" *ngIf=\"field.suffixText\">{{ field.suffixText }}</span>\r\n <mat-icon *ngIf=\"field.suffixIcon\">{{ field.suffixIcon }}</mat-icon>\r\n <mat-icon *ngIf=\"field.icon\">{{ field.icon }}</mat-icon>\r\n <mat-icon *ngIf=\"field.readonly\" class=\"lock-icon\">lock_outline</mat-icon>\r\n </div>\r\n </div>\r\n <!-- Character count -->\r\n <div class=\"char-count-row\" *ngIf=\"field.uiConfig?.maxCharacters || field.validationRules?.maxLength\">\r\n <div class=\"hint-text\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n <div class=\"char-count\">\r\n {{ getCharacterCount(field.name) }} / {{ field.uiConfig?.maxCharacters ||\r\n field.validationRules?.maxLength }} Characters\r\n </div>\r\n </div>\r\n <div class=\"hint-text mt-2\"\r\n *ngIf=\"(field.hint || field.helpText) && !field.uiConfig?.maxCharacters && !field.validationRules?.maxLength\">\r\n {{ field.hint || field.helpText }}\r\n </div>\r\n </div>\r\n\r\n <!-- Password Field (Separated) -->\r\n <div *ngIf=\"field.type === 'password'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <div class=\"input-wrapper\">\r\n <input [type]=\"isPasswordVisible(field.name) ? 'text' : 'password'\" [formControlName]=\"field.name\"\r\n [placeholder]=\"field.placeholder || ''\" [readonly]=\"field.readonly\" class=\"form-input\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\"\r\n [maxlength]=\"field.validationRules?.maxLength || field.uiConfig?.maxCharacters\">\r\n <div class=\"input-suffix\">\r\n <span class=\"suffix-text\" *ngIf=\"field.suffixText\">{{ field.suffixText }}</span>\r\n <mat-icon *ngIf=\"field.suffixIcon\">{{ field.suffixIcon }}</mat-icon>\r\n <mat-icon *ngIf=\"field.icon\">{{ field.icon }}</mat-icon>\r\n <mat-icon *ngIf=\"field.readonly\" class=\"lock-icon\">lock_outline</mat-icon>\r\n <mat-icon class=\"password-toggle-icon\" (click)=\"togglePassword(field.name)\">\r\n {{ isPasswordVisible(field.name) ? 'visibility' : 'visibility_off' }}\r\n </mat-icon>\r\n </div>\r\n </div>\r\n <!-- Character count -->\r\n <div class=\"char-count-row\" *ngIf=\"field.uiConfig?.maxCharacters || field.validationRules?.maxLength\">\r\n <div class=\"hint-text\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n <div class=\"char-count\">\r\n {{ getCharacterCount(field.name) }} / {{ field.uiConfig?.maxCharacters ||\r\n field.validationRules?.maxLength }} Characters\r\n </div>\r\n </div>\r\n <div class=\"hint-text mt-2\"\r\n *ngIf=\"(field.hint || field.helpText) && !field.uiConfig?.maxCharacters && !field.validationRules?.maxLength\">\r\n {{ field.hint || field.helpText }}\r\n </div>\r\n </div>\r\n\r\n <!-- Textarea -->\r\n <div *ngIf=\"field.type === 'textarea'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <textarea [formControlName]=\"field.name\" [placeholder]=\"field.placeholder || ''\" [readonly]=\"field.readonly\"\r\n class=\"form-input form-textarea\" rows=\"4\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\"\r\n [maxlength]=\"field.validationRules?.maxLength || field.uiConfig?.maxCharacters\"></textarea>\r\n <!-- Character count -->\r\n <div class=\"char-count-row\" *ngIf=\"field.uiConfig?.maxCharacters || field.validationRules?.maxLength\">\r\n <div class=\"hint-text\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n <div class=\"char-count\">\r\n {{ getCharacterCount(field.name) }} / {{ field.uiConfig?.maxCharacters ||\r\n field.validationRules?.maxLength }} Characters\r\n </div>\r\n </div>\r\n <div class=\"hint-text mt-2\"\r\n *ngIf=\"(field.hint || field.helpText) && !field.uiConfig?.maxCharacters && !field.validationRules?.maxLength\">\r\n {{ field.hint || field.helpText }}\r\n </div>\r\n </div>\r\n\r\n <!-- Select / Dropdown -->\r\n <div *ngIf=\"field.type === 'select' || field.type === 'dropdown'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <select [formControlName]=\"field.name\" class=\"form-input\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\">\r\n <option value=\"\" disabled selected *ngIf=\"field.placeholder\">{{ field.placeholder }}</option>\r\n <option value=\"\" disabled selected *ngIf=\"!field.placeholder\">Select</option>\r\n <option *ngFor=\"let opt of getFieldOptions(field)\" [value]=\"opt.value\">{{ opt.label }}</option>\r\n </select>\r\n <div class=\"hint-text mt-2\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n </div>\r\n\r\n <!-- Date -->\r\n <div *ngIf=\"field.type === 'date'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <div class=\"relative\">\r\n <input matInput [matDatepicker]=\"picker\" [formControlName]=\"field.name\"\r\n [placeholder]=\"field.placeholder || ''\" class=\"form-input\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\">\r\n <mat-datepicker-toggle matSuffix [for]=\"picker\" class=\"date-toggle\"></mat-datepicker-toggle>\r\n <mat-datepicker #picker></mat-datepicker>\r\n </div>\r\n <div class=\"hint-text mt-2\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n </div>\r\n\r\n <!-- Radio -->\r\n <div *ngIf=\"field.type === 'radio'\" class=\"radio-group-container\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\">\r\n <label class=\"field-label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </label>\r\n <mat-radio-group [formControlName]=\"field.name\" class=\"radio-group\">\r\n <mat-radio-button *ngFor=\"let opt of getFieldOptions(field)\" [value]=\"opt.value\" class=\"radio-button\">\r\n {{ opt.label }}\r\n </mat-radio-button>\r\n </mat-radio-group>\r\n <div class=\"hint-text mt-2\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n </div>\r\n\r\n <!-- Composite Field (Sub-groups) -->\r\n <div *ngIf=\"field.type === 'composite'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <div class=\"composite-container\">\r\n <ng-container *ngFor=\"let sub of field.subFields; let last = last\">\r\n <div class=\"composite-sub-field\" style=\"flex: 1;\">\r\n <div class=\"field-label\" *ngIf=\"sub.label\"\r\n style=\"font-size: 0.75rem; font-weight: normal; margin-bottom: 0.25rem;\">\r\n {{ sub.label }}\r\n </div>\r\n <div class=\"input-wrapper\">\r\n <input [type]=\"sub.type\" [formControlName]=\"sub.name\" [placeholder]=\"sub.placeholder || ''\"\r\n [readonly]=\"sub.readonly\" class=\"form-input\"\r\n [class.is-invalid]=\"group.get(sub.name)?.touched && group.get(sub.name)?.invalid\">\r\n <div class=\"input-suffix\" [class.color-suffix]=\"sub.suffixText === '%'\">\r\n <span class=\"suffix-text\" *ngIf=\"sub.suffixText\">{{ sub.suffixText }}</span>\r\n <mat-icon *ngIf=\"sub.readonly\" class=\"lock-icon\">lock_outline</mat-icon>\r\n </div>\r\n </div>\r\n </div>\r\n <!-- Adjust separator alignment if labels are present -->\r\n <span class=\"separator\" *ngIf=\"!last && field.separator\"\r\n [style.padding-top]=\"sub.label ? '1.25rem' : '0'\">{{ field.separator }}</span>\r\n </ng-container>\r\n </div>\r\n <div class=\"hint-text mt-2\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n <div class=\"error-text text-danger text-small mt-1\" *ngIf=\"group.errors?.['invalid_minMax_' + field.name]\">\r\n Min value cannot be greater than Max value.\r\n </div>\r\n <div class=\"error-text text-danger text-small mt-1\"\r\n *ngIf=\"group.errors?.['invalid_percentageTotal_' + field.name]\">\r\n Total percentage must be 100%.\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- File Upload -->\r\n <div *ngIf=\"field.type === 'file'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n\r\n <!-- Upload Box -->\r\n <div class=\"upload-box\" (click)=\"fileInput.click()\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\"\r\n *ngIf=\"!field.uploadedFiles || field.uploadedFiles.length === 0 || field.multiple\">\r\n <mat-icon class=\"upload-icon\">cloud_upload</mat-icon>\r\n <div class=\"upload-text\">Click to upload File</div>\r\n <div class=\"upload-hint\">PDF, DOCX, JPG, PNG (Max 5MB)</div>\r\n <input #fileInput type=\"file\" [attr.multiple]=\"field.multiple ? true : null\" [attr.accept]=\"field.accept\"\r\n (change)=\"onFileChange($event, field)\" style=\"display: none;\">\r\n </div>\r\n\r\n <!-- Uploaded Files List -->\r\n <div class=\"uploaded-files-list\" *ngIf=\"field.uploadedFiles && field.uploadedFiles.length > 0\">\r\n <div class=\"uploaded-file-item\" *ngFor=\"let file of field.uploadedFiles; let i = index\">\r\n <div class=\"file-icon\">\r\n <mat-icon class=\"pdf-icon\" *ngIf=\"file.type === 'application/pdf'\">picture_as_pdf</mat-icon>\r\n <mat-icon class=\"doc-icon\"\r\n *ngIf=\"file.type.includes('word') || file.type.includes('document')\">description</mat-icon>\r\n <mat-icon class=\"img-icon\" *ngIf=\"file.type.includes('image')\">image</mat-icon>\r\n <mat-icon class=\"file-icon-default\"\r\n *ngIf=\"!file.type.includes('pdf') && !file.type.includes('word') && !file.type.includes('document') && !file.type.includes('image')\">insert_drive_file</mat-icon>\r\n </div>\r\n <div class=\"file-info\">\r\n <div class=\"file-name\">{{ file.name }}</div>\r\n <div class=\"file-size\">{{ (file.size / 1024).toFixed(2) }} KB</div>\r\n </div>\r\n <button mat-icon-button color=\"warn\" type=\"button\" (click)=\"removeFile(field, i)\"\r\n class=\"remove-file-btn\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n</ng-template>", styles: [":host{display:block;width:100%}.configurable-form-container{padding:0;font-family:var(--cf-font-family, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif)}.configurable-form-container .section-card{background:var(--cf-surface-background, #ffffff);border:1px solid var(--cf-border-color, #D1D5DB);border-radius:var(--cf-section-radius, 12px);padding:var(--cf-section-padding, 2rem);margin-bottom:var(--cf-section-spacing, 1.5rem);box-shadow:var(--cf-section-shadow, 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px 0 rgba(0, 0, 0, .06))}.configurable-form-container .section-card:last-of-type{margin-bottom:0}.configurable-form-container .section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--cf-section-spacing, 1.5rem);padding-bottom:1rem;border-bottom:2px solid var(--cf-border-color, #D1D5DB)}.configurable-form-container .section-title{font-size:var(--cf-section-title-size, 1.25rem);font-weight:var(--cf-section-title-weight, 600);color:var(--cf-text-primary, #111827);margin:0;letter-spacing:-.025em}.configurable-form-container .form-grid{display:flex;flex-wrap:wrap;margin-left:-.625rem;margin-right:-.625rem}.configurable-form-container .form-col{position:relative;width:100%;padding-left:.625rem;padding-right:.625rem;margin-bottom:1.25rem}.configurable-form-container .col-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.configurable-form-container .col-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.configurable-form-container .col-3{flex:0 0 25%;max-width:25%}.configurable-form-container .col-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.configurable-form-container .col-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.configurable-form-container .col-6{flex:0 0 50%;max-width:50%}.configurable-form-container .col-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.configurable-form-container .col-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.configurable-form-container .col-9{flex:0 0 75%;max-width:75%}.configurable-form-container .col-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.configurable-form-container .col-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.configurable-form-container .col-12{flex:0 0 100%;max-width:100%}@media(min-width:769px){.configurable-form-container .col-md-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.configurable-form-container .col-md-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.configurable-form-container .col-md-3{flex:0 0 25%;max-width:25%}.configurable-form-container .col-md-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.configurable-form-container .col-md-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.configurable-form-container .col-md-6{flex:0 0 50%;max-width:50%}.configurable-form-container .col-md-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.configurable-form-container .col-md-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.configurable-form-container .col-md-9{flex:0 0 75%;max-width:75%}.configurable-form-container .col-md-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.configurable-form-container .col-md-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.configurable-form-container .col-md-12{flex:0 0 100%;max-width:100%}}.configurable-form-container .field-container{margin-bottom:0}.configurable-form-container .field-label{display:block;font-size:var(--cf-label-size, .875rem);font-weight:var(--cf-label-weight, 600);color:var(--cf-text-primary, #111827);margin-bottom:.5rem;line-height:1.25rem}.configurable-form-container .field-label .text-danger{color:var(--cf-error-color, #DC2626);margin-left:.125rem}.configurable-form-container .input-wrapper{position:relative;display:flex;align-items:center}.configurable-form-container .form-input{width:100%;padding:var(--cf-input-padding-y, .625rem) var(--cf-input-padding-x, .875rem);font-size:var(--cf-input-font-size, .875rem);line-height:1.5;color:var(--cf-text-primary, #111827);background-color:var(--cf-input-bg, #ffffff);border:1.5px solid var(--cf-border-color, #D1D5DB);border-radius:var(--cf-input-radius, 8px);transition:all .2s ease;font-family:inherit}.configurable-form-container .form-input:hover:not(:disabled):not([readonly]){border-color:var(--cf-input-hover-border-color, #9CA3AF)}.configurable-form-container .form-input:focus{outline:none;border-color:var(--cf-primary-color, #3B82F6);box-shadow:0 0 0 3px #3b82f61a}.configurable-form-container .form-input::placeholder{color:#9ca3af}.configurable-form-container .form-input:disabled,.configurable-form-container .form-input[readonly]{background-color:var(--cf-disabled-background, #F3F4F6);color:var(--cf-text-secondary, #6B7280);cursor:not-allowed;border-color:#e5e7eb}.configurable-form-container .form-input.is-invalid{border-color:var(--cf-error-color, #DC2626);background-color:#fef2f2}.configurable-form-container .form-input.is-invalid:focus{box-shadow:0 0 0 3px #dc26261a}.configurable-form-container select.form-input{appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236B7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3E%3C/svg%3E\");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;cursor:pointer}.configurable-form-container select.form-input:disabled{cursor:not-allowed}.configurable-form-container .form-textarea{resize:vertical;min-height:100px;font-family:inherit}.configurable-form-container .input-suffix{position:absolute;right:.75rem;display:flex;align-items:center;gap:.5rem;pointer-events:none}.configurable-form-container .input-suffix .password-toggle-icon{pointer-events:auto;cursor:pointer}.configurable-form-container .input-suffix .password-toggle-icon:hover{color:var(--cf-text-primary, #111827)}.configurable-form-container .input-suffix .suffix-text{font-size:.875rem;color:var(--cf-text-secondary, #6B7280);font-weight:500}.configurable-form-container .input-suffix mat-icon{font-size:1.25rem;width:1.25rem;height:1.25rem;color:var(--cf-text-secondary, #6B7280)}.configurable-form-container .input-suffix .lock-icon{color:#9ca3af}.configurable-form-container .input-suffix.color-suffix .suffix-text{color:var(--cf-primary-color, #3B82F6);font-weight:600}.configurable-form-container .char-count-row{display:flex;justify-content:space-between;align-items:center;margin-top:.5rem}.configurable-form-container .char-count{font-size:.75rem;color:var(--cf-text-secondary, #6B7280);font-weight:500}.configurable-form-container .hint-text{font-size:var(--cf-hint-size, .75rem);color:var(--cf-text-secondary, #6B7280);margin-top:.375rem;line-height:1.25rem}.configurable-form-container .mt-2{margin-top:.5rem}.configurable-form-container .radio-group-container.is-invalid .field-label{color:var(--cf-error-color, #DC2626)}.configurable-form-container .radio-group{display:flex;gap:2rem;flex-wrap:wrap;margin-top:.5rem}.configurable-form-container .radio-button{margin:0}.configurable-form-container .composite-container{display:flex;align-items:center;gap:.75rem}.configurable-form-container .composite-container .separator{font-size:.875rem;color:var(--cf-text-secondary, #6B7280);font-weight:500}.configurable-form-container .composite-container .input-wrapper{flex:1}.configurable-form-container .upload-box{border:2px dashed var(--cf-border-color, #D1D5DB);border-radius:var(--cf-section-radius, 12px);padding:2.5rem;text-align:center;cursor:pointer;transition:all .2s ease;background-color:var(--cf-hover-background, #F9FAFB)}.configurable-form-container .upload-box:hover{border-color:var(--cf-primary-color, #3B82F6);background-color:#3b82f608}.configurable-form-container .upload-box.is-invalid{border-color:var(--cf-error-color, #DC2626);background-color:#fef2f2}.configurable-form-container .upload-box .upload-icon{font-size:3rem;width:3rem;height:3rem;color:var(--cf-primary-color, #3B82F6);margin-bottom:.75rem}.configurable-form-container .upload-box .upload-text{font-size:.875rem;color:var(--cf-text-primary, #111827);font-weight:500;margin-bottom:.375rem}.configurable-form-container .upload-box .upload-hint{font-size:.75rem;color:var(--cf-text-secondary, #6B7280)}.configurable-form-container .uploaded-files-list{margin-top:1rem}.configurable-form-container .uploaded-file-item{display:flex;align-items:center;gap:.75rem;padding:.875rem;border:1.5px solid var(--cf-border-color, #D1D5DB);border-radius:8px;margin-bottom:.5rem;background-color:var(--cf-hover-background, #F9FAFB);transition:all .2s ease}.configurable-form-container .uploaded-file-item:hover{border-color:#9ca3af;box-shadow:0 1px 3px #0000001a}.configurable-form-container .uploaded-file-item .file-icon mat-icon{font-size:2rem;width:2rem;height:2rem}.configurable-form-container .uploaded-file-item .file-icon mat-icon.pdf-icon{color:#dc2626}.configurable-form-container .uploaded-file-item .file-icon mat-icon.doc-icon{color:#2563eb}.configurable-form-container .uploaded-file-item .file-icon mat-icon.img-icon{color:#059669}.configurable-form-container .uploaded-file-item .file-icon mat-icon.file-icon-default{color:var(--cf-text-secondary, #6B7280)}.configurable-form-container .uploaded-file-item .file-info{flex:1}.configurable-form-container .uploaded-file-item .file-info .file-name{font-size:.875rem;color:var(--cf-text-primary, #111827);font-weight:500;margin-bottom:.125rem}.configurable-form-container .uploaded-file-item .file-info .file-size{font-size:.75rem;color:var(--cf-text-secondary, #6B7280)}.configurable-form-container .uploaded-file-item .remove-file-btn mat-icon{font-size:1.25rem;width:1.25rem;height:1.25rem}.configurable-form-container .relative{position:relative}.configurable-form-container .date-toggle{position:absolute;right:0;top:50%;transform:translateY(-50%)}.configurable-form-container .repeater-item{padding:1.5rem;border:1.5px solid var(--cf-border-color, #D1D5DB);border-radius:10px;margin-bottom:1rem;background-color:var(--cf-hover-background, #F9FAFB);transition:all .2s ease}.configurable-form-container .repeater-item:hover{border-color:#9ca3af;box-shadow:0 2px 4px #0000000d}.configurable-form-container .repeater-item-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1.25rem;padding-bottom:.75rem;border-bottom:1.5px solid var(--cf-border-color, #D1D5DB)}.configurable-form-container .repeater-item-header .text-small{font-size:.875rem;font-weight:600;color:var(--cf-text-primary, #111827);text-transform:uppercase;letter-spacing:.025em}.configurable-form-container .repeater-separator{height:1px;background-color:var(--cf-border-color, #D1D5DB);margin:1.5rem 0}.configurable-form-container .add-btn{margin-top:1rem;display:inline-flex;align-items:center;gap:.5rem;padding:.625rem 1.25rem;background-color:transparent;color:var(--cf-primary-color, #3B82F6);border:1.5px solid var(--cf-primary-color, #3B82F6);border-radius:8px;font-size:.875rem;font-weight:600;cursor:pointer;transition:all .2s ease}.configurable-form-container .add-btn:hover:not(:disabled){background-color:var(--cf-primary-color, #3B82F6);color:#fff}.configurable-form-container .add-btn:disabled{opacity:.5;cursor:not-allowed;border-color:var(--cf-disabled-background, #E5E7EB)}.configurable-form-container .add-btn mat-icon{font-size:1.25rem;width:1.25rem;height:1.25rem}@media(max-width:768px){.configurable-form-container .section-card{padding:1.25rem;border-radius:8px}.configurable-form-container .form-grid{margin-left:-.5rem;margin-right:-.5rem}.configurable-form-container .form-col{padding-left:.5rem;padding-right:.5rem;margin-bottom:1rem}.configurable-form-container .radio-group{flex-direction:column;gap:.75rem}.configurable-form-container .composite-container{flex-direction:column;align-items:stretch}.configurable-form-container .composite-container .separator{text-align:center}}\n"] }]
3540
+ args: [{ selector: 'lib-configurable-form', standalone: true, imports: [CommonModule, ReactiveFormsModule, MaterialModule], template: "<form [formGroup]=\"form\" class=\"configurable-form-container\" *ngIf=\"form\">\r\n\r\n <ng-container *ngFor=\"let section of sections\">\r\n\r\n <!-- Repeater Section -->\r\n <div *ngIf=\"section.isRepeater; else normalSection\"\r\n [ngClass]=\"section.noCardLayout ? 'section-no-card' : 'section-card'\">\r\n <div class=\"section-header\" *ngIf=\"section.sectionTitle\">\r\n <h3 class=\"section-title\">{{ section.sectionTitle }}</h3>\r\n <button *ngIf=\"section.collapsible\" mat-icon-button type=\"button\" (click)=\"toggleSection(section)\">\r\n <mat-icon>{{ section.collapsed ? 'expand_more' : 'expand_less' }}</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <div [formArrayName]=\"section.formArrayName || 'items'\" *ngIf=\"!section.collapsed\">\r\n <div *ngFor=\"let item of getFormArray(section.formArrayName!).controls; let i = index\"\r\n [formGroupName]=\"i\" class=\"repeater-item\">\r\n <div class=\"repeater-item-header\" *ngIf=\"getFormArray(section.formArrayName!).length > 1\">\r\n <span class=\"text-small\">{{ section.repeaterItemLabel || labels.repeaterItemDefaultLabel }} {{i\r\n + 1}}</span>\r\n <button mat-icon-button color=\"warn\" type=\"button\"\r\n (click)=\"removeRepeaterItem(section.formArrayName!, i)\"\r\n [disabled]=\"section.minItems && getFormArray(section.formArrayName!).length <= section.minItems\">\r\n <mat-icon>delete</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Fields Grid -->\r\n <div class=\"form-grid\">\r\n <div *ngFor=\"let field of section.fields\" [ngClass]=\"['form-col', field.class || 'col-12']\"\r\n [hidden]=\"!isFieldVisible(field)\">\r\n <ng-container\r\n *ngTemplateOutlet=\"fieldTemplate; context: {field: field, group: item}\"></ng-container>\r\n </div>\r\n </div>\r\n\r\n <div class=\"repeater-separator\" *ngIf=\"i < getFormArray(section.formArrayName!).length - 1\"></div>\r\n </div>\r\n </div>\r\n\r\n <button mat-button color=\"primary\" type=\"button\" (click)=\"addRepeaterItem(section)\" class=\"add-btn\"\r\n *ngIf=\"!section.collapsed\"\r\n [disabled]=\"section.maxItems && getFormArray(section.formArrayName!).length >= section.maxItems\">\r\n <mat-icon>add_circle_outline</mat-icon> {{ section.addLabel || labels.addMoreDefaultLabel }}\r\n </button>\r\n </div>\r\n\r\n <!-- Normal Section -->\r\n <ng-template #normalSection>\r\n <div [ngClass]=\"section.noCardLayout ? 'section-no-card' : 'section-card'\">\r\n <div class=\"section-header\" *ngIf=\"section.sectionTitle\">\r\n <h3 class=\"section-title\">{{ section.sectionTitle }}</h3>\r\n <button *ngIf=\"section.collapsible\" mat-icon-button type=\"button\" (click)=\"toggleSection(section)\">\r\n <mat-icon>{{ section.collapsed ? 'expand_more' : 'expand_less' }}</mat-icon>\r\n </button>\r\n </div>\r\n <div class=\"form-grid\" *ngIf=\"!section.collapsed\">\r\n <div *ngFor=\"let field of section.fields\" [ngClass]=\"['form-col', field.class || 'col-12']\"\r\n [hidden]=\"!isFieldVisible(field)\">\r\n <!-- Pass the root form group to the template -->\r\n <ng-container\r\n *ngTemplateOutlet=\"fieldTemplate; context: {field: field, group: form}\"></ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n </ng-container>\r\n\r\n</form>\r\n\r\n<!-- Reusable Field Template -->\r\n<ng-template #fieldTemplate let-field=\"field\" let-group=\"group\">\r\n <div [formGroup]=\"group\">\r\n\r\n <!-- Text / Email / Number / Tel / URL (Password removed) -->\r\n <div *ngIf=\"['text', 'email', 'number', 'tel', 'url'].includes(field.type || '')\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <div class=\"input-wrapper\">\r\n <input [type]=\"field.type\" [formControlName]=\"field.name\" [placeholder]=\"field.placeholder || ''\"\r\n [readonly]=\"field.readonly\" class=\"form-input\" [name]=\"field.name\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\"\r\n [maxlength]=\"field.validationRules?.maxLength || field.uiConfig?.maxCharacters\">\r\n <div class=\"input-suffix\">\r\n <span class=\"suffix-text\" *ngIf=\"field.suffixText\">{{ field.suffixText }}</span>\r\n <mat-icon *ngIf=\"field.suffixIcon\">{{ field.suffixIcon }}</mat-icon>\r\n <mat-icon *ngIf=\"field.icon\">{{ field.icon }}</mat-icon>\r\n <mat-icon *ngIf=\"field.readonly\" class=\"lock-icon\">lock_outline</mat-icon>\r\n </div>\r\n </div>\r\n <!-- Character count -->\r\n <div class=\"char-count-row\" *ngIf=\"field.uiConfig?.maxCharacters || field.validationRules?.maxLength\">\r\n <div class=\"hint-text\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n <div class=\"char-count\">\r\n {{ getCharacterCount(field.name) }} / {{ field.uiConfig?.maxCharacters ||\r\n field.validationRules?.maxLength }} {{ labels.charactersLabel }}\r\n </div>\r\n </div>\r\n <div class=\"hint-text mt-2\"\r\n *ngIf=\"(field.hint || field.helpText) && !field.uiConfig?.maxCharacters && !field.validationRules?.maxLength\">\r\n {{ field.hint || field.helpText }}\r\n </div>\r\n </div>\r\n\r\n <!-- Password Field (Separated) -->\r\n <div *ngIf=\"field.type === 'password'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <div class=\"input-wrapper\">\r\n <input [type]=\"isPasswordVisible(field.name) ? 'text' : 'password'\" [formControlName]=\"field.name\"\r\n [placeholder]=\"field.placeholder || ''\" [readonly]=\"field.readonly\" class=\"form-input\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\"\r\n [maxlength]=\"field.validationRules?.maxLength || field.uiConfig?.maxCharacters\">\r\n <div class=\"input-suffix\">\r\n <span class=\"suffix-text\" *ngIf=\"field.suffixText\">{{ field.suffixText }}</span>\r\n <mat-icon *ngIf=\"field.suffixIcon\">{{ field.suffixIcon }}</mat-icon>\r\n <mat-icon *ngIf=\"field.icon\">{{ field.icon }}</mat-icon>\r\n <mat-icon *ngIf=\"field.readonly\" class=\"lock-icon\">lock_outline</mat-icon>\r\n <mat-icon class=\"password-toggle-icon\" (click)=\"togglePassword(field.name)\">\r\n {{ isPasswordVisible(field.name) ? 'visibility' : 'visibility_off' }}\r\n </mat-icon>\r\n </div>\r\n </div>\r\n <!-- Character count -->\r\n <div class=\"char-count-row\" *ngIf=\"field.uiConfig?.maxCharacters || field.validationRules?.maxLength\">\r\n <div class=\"hint-text\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n <div class=\"char-count\">\r\n {{ getCharacterCount(field.name) }} / {{ field.uiConfig?.maxCharacters ||\r\n field.validationRules?.maxLength }} {{ labels.charactersLabel }}\r\n </div>\r\n </div>\r\n <div class=\"hint-text mt-2\"\r\n *ngIf=\"(field.hint || field.helpText) && !field.uiConfig?.maxCharacters && !field.validationRules?.maxLength\">\r\n {{ field.hint || field.helpText }}\r\n </div>\r\n </div>\r\n\r\n <!-- Textarea -->\r\n <div *ngIf=\"field.type === 'textarea'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <textarea [formControlName]=\"field.name\" [placeholder]=\"field.placeholder || ''\" [readonly]=\"field.readonly\"\r\n class=\"form-input form-textarea\" rows=\"4\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\"\r\n [maxlength]=\"field.validationRules?.maxLength || field.uiConfig?.maxCharacters\"></textarea>\r\n <!-- Character count -->\r\n <div class=\"char-count-row\" *ngIf=\"field.uiConfig?.maxCharacters || field.validationRules?.maxLength\">\r\n <div class=\"hint-text\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n <div class=\"char-count\">\r\n {{ getCharacterCount(field.name) }} / {{ field.uiConfig?.maxCharacters ||\r\n field.validationRules?.maxLength }} {{ labels.charactersLabel }}\r\n </div>\r\n </div>\r\n <div class=\"hint-text mt-2\"\r\n *ngIf=\"(field.hint || field.helpText) && !field.uiConfig?.maxCharacters && !field.validationRules?.maxLength\">\r\n {{ field.hint || field.helpText }}\r\n </div>\r\n </div>\r\n\r\n <!-- Select / Dropdown -->\r\n <div *ngIf=\"field.type === 'select' || field.type === 'dropdown'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <select [formControlName]=\"field.name\" class=\"form-input\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\">\r\n <option value=\"\" disabled selected *ngIf=\"field.placeholder\">{{ field.placeholder }}</option>\r\n <option value=\"\" disabled selected *ngIf=\"!field.placeholder\">{{ labels.selectDefaultPlaceholder }}\r\n </option>\r\n <option *ngFor=\"let opt of getFieldOptions(field)\" [value]=\"opt.value\">{{ opt.label }}</option>\r\n </select>\r\n <div class=\"hint-text mt-2\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n </div>\r\n\r\n <!-- Date -->\r\n <div *ngIf=\"field.type === 'date'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <div class=\"relative\">\r\n <input matInput [matDatepicker]=\"picker\" [formControlName]=\"field.name\"\r\n [placeholder]=\"field.placeholder || ''\" class=\"form-input\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\">\r\n <mat-datepicker-toggle matSuffix [for]=\"picker\" class=\"date-toggle\"></mat-datepicker-toggle>\r\n <mat-datepicker #picker></mat-datepicker>\r\n </div>\r\n <div class=\"hint-text mt-2\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n </div>\r\n\r\n <!-- Radio -->\r\n <div *ngIf=\"field.type === 'radio'\" class=\"radio-group-container\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\">\r\n <label class=\"field-label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </label>\r\n <mat-radio-group [formControlName]=\"field.name\" class=\"radio-group\">\r\n <mat-radio-button *ngFor=\"let opt of getFieldOptions(field)\" [value]=\"opt.value\" class=\"radio-button\">\r\n {{ opt.label }}\r\n </mat-radio-button>\r\n </mat-radio-group>\r\n <div class=\"hint-text mt-2\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n </div>\r\n\r\n <!-- Composite Field (Sub-groups) -->\r\n <div *ngIf=\"field.type === 'composite'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n <div class=\"composite-container\">\r\n <ng-container *ngFor=\"let sub of field.subFields; let last = last\">\r\n <div class=\"composite-sub-field\" style=\"flex: 1;\">\r\n <div class=\"field-label\" *ngIf=\"sub.label\"\r\n style=\"font-size: 0.75rem; font-weight: normal; margin-bottom: 0.25rem;\">\r\n {{ sub.label }}\r\n </div>\r\n <div class=\"input-wrapper\">\r\n <input [type]=\"sub.type\" [formControlName]=\"sub.name\" [placeholder]=\"sub.placeholder || ''\"\r\n [readonly]=\"sub.readonly\" class=\"form-input\"\r\n [class.is-invalid]=\"group.get(sub.name)?.touched && group.get(sub.name)?.invalid\">\r\n <div class=\"input-suffix\" [class.color-suffix]=\"sub.suffixText === '%'\">\r\n <span class=\"suffix-text\" *ngIf=\"sub.suffixText\">{{ sub.suffixText }}</span>\r\n <mat-icon *ngIf=\"sub.readonly\" class=\"lock-icon\">lock_outline</mat-icon>\r\n </div>\r\n </div>\r\n </div>\r\n <!-- Adjust separator alignment if labels are present -->\r\n <span class=\"separator\" *ngIf=\"!last && field.separator\"\r\n [style.padding-top]=\"sub.label ? '1.25rem' : '0'\">{{ field.separator }}</span>\r\n </ng-container>\r\n </div>\r\n <div class=\"hint-text mt-2\" *ngIf=\"field.hint || field.helpText\">{{ field.hint || field.helpText }}</div>\r\n <div class=\"error-text text-danger text-small mt-1\" *ngIf=\"group.errors?.['invalid_minMax_' + field.name]\">\r\n {{ labels.errorMinValue }}\r\n </div>\r\n <div class=\"error-text text-danger text-small mt-1\"\r\n *ngIf=\"group.errors?.['invalid_percentageTotal_' + field.name]\">\r\n {{ labels.errorPercentageTotal }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- File Upload -->\r\n <div *ngIf=\"field.type === 'file'\" class=\"field-container\">\r\n <div class=\"field-label\" *ngIf=\"field.label\">\r\n {{ field.label }}\r\n <span class=\"text-danger\" *ngIf=\"field.required || field.mandatory\">*</span>\r\n </div>\r\n\r\n <!-- Upload Box -->\r\n <div class=\"upload-box\" (click)=\"fileInput.click()\"\r\n [class.is-invalid]=\"group.get(field.name)?.touched && group.get(field.name)?.invalid\"\r\n *ngIf=\"!field.uploadedFiles || field.uploadedFiles.length === 0 || field.multiple\">\r\n <mat-icon class=\"upload-icon\">cloud_upload</mat-icon>\r\n <div class=\"upload-text\">{{ labels.uploadDragDropText }}</div>\r\n <div class=\"upload-hint\">{{ labels.uploadHint }}</div>\r\n <input #fileInput type=\"file\" [attr.multiple]=\"field.multiple ? true : null\" [attr.accept]=\"field.accept\"\r\n (change)=\"onFileChange($event, field)\" style=\"display: none;\">\r\n </div>\r\n\r\n <!-- Uploaded Files List -->\r\n <div class=\"uploaded-files-list\" *ngIf=\"field.uploadedFiles && field.uploadedFiles.length > 0\">\r\n <div class=\"uploaded-file-item\" *ngFor=\"let file of field.uploadedFiles; let i = index\">\r\n <div class=\"file-icon\">\r\n <mat-icon class=\"pdf-icon\" *ngIf=\"file.type === 'application/pdf'\">picture_as_pdf</mat-icon>\r\n <mat-icon class=\"doc-icon\"\r\n *ngIf=\"file.type.includes('word') || file.type.includes('document')\">description</mat-icon>\r\n <mat-icon class=\"img-icon\" *ngIf=\"file.type.includes('image')\">image</mat-icon>\r\n <mat-icon class=\"file-icon-default\"\r\n *ngIf=\"!file.type.includes('pdf') && !file.type.includes('word') && !file.type.includes('document') && !file.type.includes('image')\">insert_drive_file</mat-icon>\r\n </div>\r\n <div class=\"file-info\">\r\n <div class=\"file-name\">{{ file.name }}</div>\r\n <div class=\"file-size\">{{ (file.size / 1024).toFixed(2) }} KB</div>\r\n </div>\r\n <button mat-icon-button color=\"warn\" type=\"button\" (click)=\"removeFile(field, i)\"\r\n class=\"remove-file-btn\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n</ng-template>", styles: [":host{display:block;width:100%}.configurable-form-container{padding:0;font-family:var(--cf-font-family, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif)}.configurable-form-container .section-card{background:var(--cf-surface-background, #ffffff);border:1px solid var(--cf-border-color, #D1D5DB);border-radius:var(--cf-section-radius, 12px);padding:var(--cf-section-padding, 2rem);margin-bottom:var(--cf-section-spacing, 1.5rem);box-shadow:var(--cf-section-shadow, 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px 0 rgba(0, 0, 0, .06))}.configurable-form-container .section-card:last-of-type{margin-bottom:0}.configurable-form-container .section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--cf-section-spacing, 1.5rem);padding-bottom:1rem;border-bottom:2px solid var(--cf-border-color, #D1D5DB)}.configurable-form-container .section-title{font-size:var(--cf-section-title-size, 1.25rem);font-weight:var(--cf-section-title-weight, 600);color:var(--cf-text-primary, #111827);margin:0;letter-spacing:-.025em}.configurable-form-container .form-grid{display:flex;flex-wrap:wrap;margin-left:-.625rem;margin-right:-.625rem}.configurable-form-container .form-col{position:relative;width:100%;padding-left:.625rem;padding-right:.625rem;margin-bottom:1.25rem}.configurable-form-container .col-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.configurable-form-container .col-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.configurable-form-container .col-3{flex:0 0 25%;max-width:25%}.configurable-form-container .col-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.configurable-form-container .col-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.configurable-form-container .col-6{flex:0 0 50%;max-width:50%}.configurable-form-container .col-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.configurable-form-container .col-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.configurable-form-container .col-9{flex:0 0 75%;max-width:75%}.configurable-form-container .col-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.configurable-form-container .col-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.configurable-form-container .col-12{flex:0 0 100%;max-width:100%}@media(min-width:769px){.configurable-form-container .col-md-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.configurable-form-container .col-md-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.configurable-form-container .col-md-3{flex:0 0 25%;max-width:25%}.configurable-form-container .col-md-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.configurable-form-container .col-md-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.configurable-form-container .col-md-6{flex:0 0 50%;max-width:50%}.configurable-form-container .col-md-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.configurable-form-container .col-md-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.configurable-form-container .col-md-9{flex:0 0 75%;max-width:75%}.configurable-form-container .col-md-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.configurable-form-container .col-md-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.configurable-form-container .col-md-12{flex:0 0 100%;max-width:100%}}.configurable-form-container .field-container{margin-bottom:0}.configurable-form-container .field-label{display:block;font-size:var(--cf-label-size, .875rem);font-weight:var(--cf-label-weight, 600);color:var(--cf-text-primary, #111827);margin-bottom:.5rem;line-height:1.25rem}.configurable-form-container .field-label .text-danger{color:var(--cf-error-color, #DC2626);margin-left:.125rem}.configurable-form-container .input-wrapper{position:relative;display:flex;align-items:center}.configurable-form-container .form-input{width:100%;padding:var(--cf-input-padding-y, .625rem) var(--cf-input-padding-x, .875rem);font-size:var(--cf-input-font-size, .875rem);line-height:1.5;color:var(--cf-text-primary, #111827);background-color:var(--cf-input-bg, #ffffff);border:1.5px solid var(--cf-border-color, #D1D5DB);border-radius:var(--cf-input-radius, 8px);transition:all .2s ease;font-family:inherit}.configurable-form-container .form-input:hover:not(:disabled):not([readonly]){border-color:var(--cf-input-hover-border-color, #9CA3AF)}.configurable-form-container .form-input:focus{outline:none;border-color:var(--cf-primary-color, #3B82F6);box-shadow:0 0 0 3px #3b82f61a}.configurable-form-container .form-input::placeholder{color:#9ca3af}.configurable-form-container .form-input:disabled,.configurable-form-container .form-input[readonly]{background-color:var(--cf-disabled-background, #F3F4F6);color:var(--cf-text-secondary, #6B7280);cursor:not-allowed;border-color:#e5e7eb}.configurable-form-container .form-input.is-invalid{border-color:var(--cf-error-color, #DC2626);background-color:#fef2f2}.configurable-form-container .form-input.is-invalid:focus{box-shadow:0 0 0 3px #dc26261a}.configurable-form-container select.form-input{appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236B7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3E%3C/svg%3E\");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;cursor:pointer}.configurable-form-container select.form-input:disabled{cursor:not-allowed}.configurable-form-container .form-textarea{resize:vertical;min-height:100px;font-family:inherit}.configurable-form-container .input-suffix{position:absolute;right:.75rem;display:flex;align-items:center;gap:.5rem;pointer-events:none}.configurable-form-container .input-suffix .password-toggle-icon{pointer-events:auto;cursor:pointer}.configurable-form-container .input-suffix .password-toggle-icon:hover{color:var(--cf-text-primary, #111827)}.configurable-form-container .input-suffix .suffix-text{font-size:.875rem;color:var(--cf-text-secondary, #6B7280);font-weight:500}.configurable-form-container .input-suffix mat-icon{font-size:1.25rem;width:1.25rem;height:1.25rem;color:var(--cf-text-secondary, #6B7280)}.configurable-form-container .input-suffix .lock-icon{color:#9ca3af}.configurable-form-container .input-suffix.color-suffix .suffix-text{color:var(--cf-primary-color, #3B82F6);font-weight:600}.configurable-form-container .char-count-row{display:flex;justify-content:space-between;align-items:center;margin-top:.5rem}.configurable-form-container .char-count{font-size:.75rem;color:var(--cf-text-secondary, #6B7280);font-weight:500}.configurable-form-container .hint-text{font-size:var(--cf-hint-size, .75rem);color:var(--cf-text-secondary, #6B7280);margin-top:.375rem;line-height:1.25rem}.configurable-form-container .mt-2{margin-top:.5rem}.configurable-form-container .radio-group-container.is-invalid .field-label{color:var(--cf-error-color, #DC2626)}.configurable-form-container .radio-group{display:flex;gap:2rem;flex-wrap:wrap;margin-top:.5rem}.configurable-form-container .radio-button{margin:0}.configurable-form-container .composite-container{display:flex;align-items:center;gap:.75rem}.configurable-form-container .composite-container .separator{font-size:.875rem;color:var(--cf-text-secondary, #6B7280);font-weight:500}.configurable-form-container .composite-container .input-wrapper{flex:1}.configurable-form-container .upload-box{border:2px dashed var(--cf-border-color, #D1D5DB);border-radius:var(--cf-section-radius, 12px);padding:2.5rem;text-align:center;cursor:pointer;transition:all .2s ease;background-color:var(--cf-hover-background, #F9FAFB)}.configurable-form-container .upload-box:hover{border-color:var(--cf-primary-color, #3B82F6);background-color:#3b82f608}.configurable-form-container .upload-box.is-invalid{border-color:var(--cf-error-color, #DC2626);background-color:#fef2f2}.configurable-form-container .upload-box .upload-icon{font-size:3rem;width:3rem;height:3rem;color:var(--cf-primary-color, #3B82F6);margin-bottom:.75rem}.configurable-form-container .upload-box .upload-text{font-size:.875rem;color:var(--cf-text-primary, #111827);font-weight:500;margin-bottom:.375rem}.configurable-form-container .upload-box .upload-hint{font-size:.75rem;color:var(--cf-text-secondary, #6B7280)}.configurable-form-container .uploaded-files-list{margin-top:1rem}.configurable-form-container .uploaded-file-item{display:flex;align-items:center;gap:.75rem;padding:.875rem;border:1.5px solid var(--cf-border-color, #D1D5DB);border-radius:8px;margin-bottom:.5rem;background-color:var(--cf-hover-background, #F9FAFB);transition:all .2s ease}.configurable-form-container .uploaded-file-item:hover{border-color:#9ca3af;box-shadow:0 1px 3px #0000001a}.configurable-form-container .uploaded-file-item .file-icon mat-icon{font-size:2rem;width:2rem;height:2rem}.configurable-form-container .uploaded-file-item .file-icon mat-icon.pdf-icon{color:#dc2626}.configurable-form-container .uploaded-file-item .file-icon mat-icon.doc-icon{color:#2563eb}.configurable-form-container .uploaded-file-item .file-icon mat-icon.img-icon{color:#059669}.configurable-form-container .uploaded-file-item .file-icon mat-icon.file-icon-default{color:var(--cf-text-secondary, #6B7280)}.configurable-form-container .uploaded-file-item .file-info{flex:1}.configurable-form-container .uploaded-file-item .file-info .file-name{font-size:.875rem;color:var(--cf-text-primary, #111827);font-weight:500;margin-bottom:.125rem}.configurable-form-container .uploaded-file-item .file-info .file-size{font-size:.75rem;color:var(--cf-text-secondary, #6B7280)}.configurable-form-container .uploaded-file-item .remove-file-btn mat-icon{font-size:1.25rem;width:1.25rem;height:1.25rem}.configurable-form-container .relative{position:relative}.configurable-form-container .date-toggle{position:absolute;right:0;top:50%;transform:translateY(-50%)}.configurable-form-container .repeater-item{padding:1.5rem;border:1.5px solid var(--cf-border-color, #D1D5DB);border-radius:10px;margin-bottom:1rem;background-color:var(--cf-hover-background, #F9FAFB);transition:all .2s ease}.configurable-form-container .repeater-item:hover{border-color:#9ca3af;box-shadow:0 2px 4px #0000000d}.configurable-form-container .repeater-item-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1.25rem;padding-bottom:.75rem;border-bottom:1.5px solid var(--cf-border-color, #D1D5DB)}.configurable-form-container .repeater-item-header .text-small{font-size:.875rem;font-weight:600;color:var(--cf-text-primary, #111827);text-transform:uppercase;letter-spacing:.025em}.configurable-form-container .repeater-separator{height:1px;background-color:var(--cf-border-color, #D1D5DB);margin:1.5rem 0}.configurable-form-container .add-btn{margin-top:1rem;display:inline-flex;align-items:center;gap:.5rem;padding:.625rem 1.25rem;background-color:transparent;color:var(--cf-primary-color, #3B82F6);border:1.5px solid var(--cf-primary-color, #3B82F6);border-radius:8px;font-size:.875rem;font-weight:600;cursor:pointer;transition:all .2s ease}.configurable-form-container .add-btn:hover:not(:disabled){background-color:var(--cf-primary-color, #3B82F6);color:#fff}.configurable-form-container .add-btn:disabled{opacity:.5;cursor:not-allowed;border-color:var(--cf-disabled-background, #E5E7EB)}.configurable-form-container .add-btn mat-icon{font-size:1.25rem;width:1.25rem;height:1.25rem}@media(max-width:768px){.configurable-form-container .section-card{padding:1.25rem;border-radius:8px}.configurable-form-container .form-grid{margin-left:-.5rem;margin-right:-.5rem}.configurable-form-container .form-col{padding-left:.5rem;padding-right:.5rem;margin-bottom:1rem}.configurable-form-container .radio-group{flex-direction:column;gap:.75rem}.configurable-form-container .composite-container{flex-direction:column;align-items:stretch}.configurable-form-container .composite-container .separator{text-align:center}}\n"] }]
3461
3541
  }], ctorParameters: () => [{ type: i1$2.FormBuilder }, { type: i2$2.MatSnackBar }, { type: i3.HttpClient }], propDecorators: { config: [{
3462
3542
  type: Input
3463
3543
  }], jsonConfig: [{
@@ -3466,6 +3546,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
3466
3546
  type: Input
3467
3547
  }], baseApiUrl: [{
3468
3548
  type: Input
3549
+ }], labels: [{
3550
+ type: Input
3469
3551
  }], optionsLoad: [{
3470
3552
  type: Output
3471
3553
  }] } });
@@ -3499,6 +3581,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
3499
3581
 
3500
3582
  class SmartFormController {
3501
3583
  formData = {};
3584
+ /** Auth token sourced from the FormSchema configJSON (e.g. "Bearer eyJ…") */
3585
+ token;
3586
+ /** HTTP header name for the token (default: "Authorization") */
3587
+ tokenHeader;
3588
+ /** Flat map of translated i18n labels passed from SmartFormComponent */
3589
+ labels = {};
3590
+ /** Custom label keys for form actions (Next, Submit, Add, etc.) */
3591
+ actionLabels;
3502
3592
  fieldSubjects = new Map();
3503
3593
  initialize(initialData) {
3504
3594
  this.formData = { ...initialData };
@@ -3542,38 +3632,112 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
3542
3632
  type: Injectable
3543
3633
  }] });
3544
3634
 
3635
+ /**
3636
+ * Utility class for translating Smart Form schemas.
3637
+ */
3638
+ class SmartFormTranslationUtils {
3639
+ /**
3640
+ * Recursively walks the schema and replaces i18n keys with values from labels map.
3641
+ * @param schema The FormSchema to translate.
3642
+ * @param labels The labels map (can be flat or contain labelsObject property).
3643
+ */
3644
+ static translateSchema(schema, labels) {
3645
+ if (!schema || !labels)
3646
+ return;
3647
+ // Support both flat labels map and the pattern used in ConfigurableForm which passes { labelsObject: ... }
3648
+ const labelsMap = labels.labelsObject || labels;
3649
+ const translate = (key) => labelsMap[key] || key;
3650
+ // Root properties
3651
+ if (schema.label)
3652
+ schema.label = translate(schema.label);
3653
+ if (schema.description)
3654
+ schema.description = translate(schema.description);
3655
+ // Submit config
3656
+ if (schema.submitConfig) {
3657
+ if (schema.submitConfig.successMessage)
3658
+ schema.submitConfig.successMessage = translate(schema.submitConfig.successMessage);
3659
+ if (schema.submitConfig.errorMessage)
3660
+ schema.submitConfig.errorMessage = translate(schema.submitConfig.errorMessage);
3661
+ }
3662
+ // Section config
3663
+ if (schema.sectionConfig) {
3664
+ this.translateSection(schema.sectionConfig, translate);
3665
+ }
3666
+ // Stepper config
3667
+ if (schema.stepperConfig?.children) {
3668
+ schema.stepperConfig.children.forEach(field => this.translateField(field, translate));
3669
+ }
3670
+ }
3671
+ static translateSection(section, translate) {
3672
+ if (section.label)
3673
+ section.label = translate(section.label);
3674
+ if (section.children) {
3675
+ section.children.forEach((field) => this.translateField(field, translate));
3676
+ }
3677
+ }
3678
+ static translateField(field, translate) {
3679
+ if (field.label)
3680
+ field.label = translate(field.label);
3681
+ if (field.placeholder)
3682
+ field.placeholder = translate(field.placeholder);
3683
+ if (field.hint)
3684
+ field.hint = translate(field.hint);
3685
+ if (field.textConfig?.patternMessage) {
3686
+ field.textConfig.patternMessage = translate(field.textConfig.patternMessage);
3687
+ }
3688
+ if (field.attachmentConfig?.acceptLabel) {
3689
+ field.attachmentConfig.acceptLabel = translate(field.attachmentConfig.acceptLabel);
3690
+ }
3691
+ if (field.optionConfig?.optionList) {
3692
+ field.optionConfig.optionList.forEach(opt => {
3693
+ if (opt.label)
3694
+ opt.label = translate(opt.label);
3695
+ });
3696
+ }
3697
+ // Recurse into children (ROW or GROUP)
3698
+ if (field.children) {
3699
+ field.children.forEach(child => this.translateField(child, translate));
3700
+ }
3701
+ // Recurse into nested section config
3702
+ if (field.sectionConfig) {
3703
+ this.translateSection(field.sectionConfig, translate);
3704
+ }
3705
+ }
3706
+ }
3707
+
3545
3708
  class ExpressionService {
3546
3709
  loadedFunctions = new Map();
3547
3710
  evaluate(expression, context, variables) {
3548
3711
  try {
3549
- const filteredContext = variables
3550
- ? Object.fromEntries(Object.entries(context).filter(([key]) => variables.includes(key)))
3551
- : context;
3552
- const func = new Function(...Object.keys(filteredContext), `return ${expression};`);
3553
- return func(...Object.values(filteredContext));
3712
+ const vars = variables || this.extractVariables(expression);
3713
+ const args = vars.map(v => context[v]);
3714
+ const func = new Function(...vars, `return ${expression};`);
3715
+ return func(...args);
3554
3716
  }
3555
3717
  catch (e) {
3556
- console.error('Expression evaluation error:', e);
3718
+ console.error('Expression evaluation error:', e, { expression, vars: variables || this.extractVariables(expression), context });
3557
3719
  return null;
3558
3720
  }
3559
3721
  }
3560
3722
  evaluateCondition(expression, context) {
3723
+ const vars = this.extractVariables(expression);
3561
3724
  try {
3562
- const func = new Function(...Object.keys(context), `return ${expression};`);
3563
- const result = func(...Object.values(context));
3725
+ const args = vars.map(v => context[v]);
3726
+ const func = new Function(...vars, `return ${expression};`);
3727
+ const result = func(...args);
3564
3728
  return !!result;
3565
3729
  }
3566
3730
  catch (e) {
3567
- console.error('Condition evaluation error:', e);
3731
+ console.error('Condition evaluation error:', e, { expression, variables: vars, context });
3568
3732
  return false;
3569
3733
  }
3570
3734
  }
3571
3735
  evaluateFormula(formula, functionName, context, variables) {
3572
3736
  try {
3573
3737
  if (!this.loadedFunctions.has(functionName)) {
3574
- const func = new Function('context', `
3575
- ${formula}
3576
- return ${functionName};
3738
+ const func = new Function('context', `
3739
+ ${formula}
3740
+ return ${functionName};
3577
3741
  `)(context);
3578
3742
  this.loadedFunctions.set(functionName, func);
3579
3743
  }
@@ -3589,9 +3753,21 @@ class ExpressionService {
3589
3753
  }
3590
3754
  }
3591
3755
  extractVariables(expression) {
3756
+ if (!expression)
3757
+ return [];
3758
+ // Remove strings from expression to avoid picking up variables inside quotes
3759
+ const cleanExpr = expression
3760
+ .replace(/'[^']*'/g, '')
3761
+ .replace(/"[^"]*"/g, '')
3762
+ .replace(/`[^`]*`/g, '')
3763
+ // Remove properties (e.g., .max) to only pick up top-level identifiers
3764
+ .replace(/\.\w+/g, '');
3592
3765
  const regex = /\b[a-zA-Z_]\w*\b/g;
3593
- const matches = expression.match(regex) || [];
3594
- const keywords = ['true', 'false', 'null', 'undefined', 'return', 'if', 'else', 'for', 'while', 'function', 'var', 'let', 'const'];
3766
+ const matches = cleanExpr.match(regex) || [];
3767
+ const keywords = [
3768
+ 'true', 'false', 'null', 'undefined', 'return', 'if', 'else', 'for', 'while', 'function', 'var', 'let', 'const', 'this',
3769
+ 'Math', 'JSON', 'console', 'window', 'document', 'Date', 'Object', 'Array', 'Number', 'String', 'Boolean', 'Symbol'
3770
+ ];
3595
3771
  return [...new Set(matches.filter(m => !keywords.includes(m)))];
3596
3772
  }
3597
3773
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ExpressionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
@@ -3604,174 +3780,303 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
3604
3780
  }]
3605
3781
  }] });
3606
3782
 
3607
- class ValidationUtils {
3608
- static email() {
3609
- return (control) => {
3610
- if (!control.value)
3611
- return null;
3612
- const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
3613
- return emailRegex.test(control.value) ? null : { email: true };
3614
- };
3615
- }
3616
- static phone() {
3617
- return (control) => {
3618
- if (!control.value)
3619
- return null;
3620
- const phoneRegex = /^\+?[\d\s\-\(\)]{10,}$/;
3621
- return phoneRegex.test(control.value) ? null : { phone: true };
3622
- };
3623
- }
3624
- static url() {
3625
- return (control) => {
3626
- if (!control.value)
3627
- return null;
3628
- const urlRegex = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/;
3629
- return urlRegex.test(control.value) ? null : { url: true };
3630
- };
3631
- }
3632
- static minLength(min) {
3633
- return (control) => {
3634
- if (!control.value)
3635
- return null;
3636
- return control.value.length >= min ? null : { minLength: { min, actual: control.value.length } };
3637
- };
3638
- }
3639
- static maxLength(max) {
3640
- return (control) => {
3641
- if (!control.value)
3642
- return null;
3643
- return control.value.length <= max ? null : { maxLength: { max, actual: control.value.length } };
3644
- };
3645
- }
3646
- static pattern(pattern, message) {
3647
- return (control) => {
3648
- if (!control.value)
3649
- return null;
3650
- const regex = new RegExp(pattern);
3651
- return regex.test(control.value) ? null : { pattern: { message: message || 'Invalid format' } };
3652
- };
3653
- }
3654
- static numberRange(min, max) {
3655
- return (control) => {
3656
- if (!control.value && control.value !== 0)
3657
- return null;
3658
- const value = Number(control.value);
3659
- if (min !== undefined && value < min) {
3660
- return { min: { min, actual: value } };
3661
- }
3662
- if (max !== undefined && value > max) {
3663
- return { max: { max, actual: value } };
3664
- }
3665
- return null;
3666
- };
3667
- }
3668
- static dateRange(minDate, maxDate) {
3669
- return (control) => {
3670
- if (!control.value)
3671
- return null;
3672
- const date = new Date(control.value);
3673
- if (minDate && date < new Date(minDate)) {
3674
- return { minDate: { minDate } };
3675
- }
3676
- if (maxDate && date > new Date(maxDate)) {
3677
- return { maxDate: { maxDate } };
3678
- }
3679
- return null;
3680
- };
3681
- }
3682
- static getErrorMessage(errors) {
3683
- if (!errors)
3783
+ /**
3784
+ * Utility functions for string manipulation
3785
+ */
3786
+ class StringUtils {
3787
+ /**
3788
+ * Converts a string to camelCase.
3789
+ * Example: "First Name" -> "firstName", "Profile Picture" -> "profilePicture"
3790
+ */
3791
+ static toCamelCase(str) {
3792
+ if (!str)
3684
3793
  return '';
3685
- if (errors['required'])
3686
- return 'This field is required';
3687
- if (errors['email'])
3688
- return 'Please enter a valid email address';
3689
- if (errors['phone'])
3690
- return 'Please enter a valid phone number';
3691
- if (errors['url'])
3692
- return 'Please enter a valid URL';
3693
- if (errors['minLength'])
3694
- return `Minimum length is ${errors['minLength'].min} characters`;
3695
- if (errors['maxLength'])
3696
- return `Maximum length is ${errors['maxLength'].max} characters`;
3697
- if (errors['min'])
3698
- return `Minimum value is ${errors['min'].min}`;
3699
- if (errors['max'])
3700
- return `Maximum value is ${errors['max'].max}`;
3701
- if (errors['minDate'])
3702
- return `Date must be after ${errors['minDate'].minDate}`;
3703
- if (errors['maxDate'])
3704
- return `Date must be before ${errors['maxDate'].maxDate}`;
3705
- if (errors['pattern'])
3706
- return errors['pattern'].message || 'Invalid format';
3707
- return 'Invalid value';
3794
+ return str
3795
+ .replace(/[^a-zA-Z0-9 ]/g, '')
3796
+ .split(' ')
3797
+ .filter(Boolean)
3798
+ .map((word, i) => i === 0
3799
+ ? word.charAt(0).toLowerCase() + word.slice(1)
3800
+ : word.charAt(0).toUpperCase() + word.slice(1))
3801
+ .join('');
3708
3802
  }
3709
3803
  }
3710
3804
 
3711
3805
  class FormFieldComponent {
3806
+ fb;
3712
3807
  expressionService;
3713
3808
  http;
3714
3809
  config;
3715
3810
  controller;
3716
- sectionIndex;
3811
+ /**
3812
+ * The FormGroup that THIS field's control should be registered in.
3813
+ * For repeater instances this is the instance's own isolated FormGroup.
3814
+ * For flat (non-repeater) fields this is the root formGroup.
3815
+ */
3816
+ formGroup;
3817
+ /**
3818
+ * Set to TRUE when this field is part of a repeatable group (config.sectionConfig.allowMulti = true).
3819
+ * When true, the field does NOT sync with the global controller to prevent data collision
3820
+ * between different instances of the same repeater row.
3821
+ */
3822
+ allowMulti = false;
3717
3823
  value;
3718
3824
  isVisible = true;
3719
- errorMessage = '';
3825
+ showPassword = false; // password show/hide toggle
3826
+ isDragOver = false; // file upload drag-over state
3827
+ fileUploadError = ''; // per-field file validation error
3720
3828
  destroy$ = new Subject();
3721
- constructor(expressionService, http) {
3829
+ // ── GROUP / allowMulti support ──────────────────────────────────────────
3830
+ /** For GROUP fields with allowMulti = true */
3831
+ groupFormArray;
3832
+ /** For GROUP fields with allowMulti = false — single nested FormGroup */
3833
+ groupFormGroup;
3834
+ /**
3835
+ * Tracked list of repeater instances.
3836
+ * Using a separate array (not FormArray.controls) + trackBy(id) ensures
3837
+ * Angular creates FRESH child components for every new row, preventing
3838
+ * cached values from bleeding into new instances.
3839
+ */
3840
+ instanceList = [];
3841
+ _nextInstanceId = 0;
3842
+ /**
3843
+ * Key used to register the GROUP control on the parent formGroup.
3844
+ * Priority: sectionConfig.name > field.name > camelCase(label) > '__group__'
3845
+ */
3846
+ get groupKey() {
3847
+ return (this.config.sectionConfig?.name ||
3848
+ this.config.name ||
3849
+ StringUtils.toCamelCase(this.config.sectionConfig?.label) ||
3850
+ '__group__');
3851
+ }
3852
+ constructor(fb, expressionService, http) {
3853
+ this.fb = fb;
3722
3854
  this.expressionService = expressionService;
3723
3855
  this.http = http;
3724
3856
  }
3725
3857
  ngOnInit() {
3726
- this.setupField();
3858
+ if (this.isGroup) {
3859
+ this.initGroupField();
3860
+ return;
3861
+ }
3862
+ this.registerControl();
3727
3863
  this.setupVisibility();
3728
3864
  this.setupGeneratedField();
3865
+ this.setupFormulaValidation(); // Generic formula-based validation
3729
3866
  this.setupDependencies();
3730
- // Initial load if no dependencies or if dependencies have initial values
3867
+ this.setupMatchValidation(); // cross-field match (e.g. confirmPassword)
3731
3868
  if (!this.config.optionConfig?.dependencies) {
3732
3869
  this.loadDropdownOptions();
3733
3870
  }
3734
3871
  }
3872
+ // ── GROUP initialisation ──────────────────────────────────────────────────
3873
+ initGroupField() {
3874
+ if (this.config.sectionConfig?.allowMulti) {
3875
+ this.groupFormArray = this.fb.array([]);
3876
+ this.formGroup.addControl(this.groupKey, this.groupFormArray);
3877
+ this.addGroupInstance();
3878
+ }
3879
+ else {
3880
+ this.groupFormGroup = this.fb.group({});
3881
+ this.formGroup.addControl(this.groupKey, this.groupFormGroup);
3882
+ }
3883
+ }
3884
+ /**
3885
+ * Sets up cross-field validation based on the `onValidate` formula.
3886
+ * Watches all variables mentioned in the formula and updates the field's
3887
+ * validity whenever any of them change.
3888
+ */
3889
+ setupFormulaValidation() {
3890
+ if (!this.config.onValidate)
3891
+ return;
3892
+ const expression = this.config.onValidate;
3893
+ const variables = this.expressionService.extractVariables(expression);
3894
+ // Subscribe to all fields mentioned in the formula
3895
+ const observables = variables.map(v => this.controller.getFieldObservable(v));
3896
+ combineLatest(observables).pipe(takeUntil(this.destroy$)).subscribe(() => {
3897
+ const context = this.controller.getAllData();
3898
+ const isValid = this.expressionService.evaluateCondition(expression, context);
3899
+ const control = this.formGroup.get(this.config.name);
3900
+ if (control) {
3901
+ if (!isValid) {
3902
+ control.setErrors({ ...control.errors, formulaError: true });
3903
+ }
3904
+ else {
3905
+ // Clear only the formulaError
3906
+ if (control.hasError('formulaError')) {
3907
+ const errors = { ...control.errors };
3908
+ delete errors['formulaError'];
3909
+ control.setErrors(Object.keys(errors).length ? errors : null);
3910
+ }
3911
+ }
3912
+ }
3913
+ });
3914
+ }
3915
+ addGroupInstance() {
3916
+ const fg = this.fb.group({});
3917
+ this.groupFormArray.push(fg);
3918
+ // Spread to a new array reference so *ngFor always detects the change
3919
+ this.instanceList = [...this.instanceList, { id: this._nextInstanceId++, fg }];
3920
+ }
3921
+ removeGroupInstance(index) {
3922
+ if (this.instanceList.length > 1) {
3923
+ this.groupFormArray.removeAt(index);
3924
+ this.instanceList = this.instanceList.filter((_, i) => i !== index);
3925
+ }
3926
+ }
3927
+ trackByInstanceId(_, item) {
3928
+ return item.id;
3929
+ }
3930
+ // ── Leaf control ─────────────────────────────────────────────────────────
3735
3931
  ngOnDestroy() {
3932
+ // Always complete so any subscriptions (e.g. subfields valueChanges) are cleaned up
3736
3933
  this.destroy$.next();
3737
3934
  this.destroy$.complete();
3935
+ if (this.isGroup) {
3936
+ if (this.formGroup?.contains(this.groupKey)) {
3937
+ this.formGroup.removeControl(this.groupKey);
3938
+ }
3939
+ return;
3940
+ }
3941
+ const fieldName = this.config.name;
3942
+ if (fieldName && this.formGroup?.contains(fieldName)) {
3943
+ this.formGroup.removeControl(fieldName);
3944
+ }
3738
3945
  }
3739
- setupField() {
3740
- if (this.config.name) {
3741
- const fieldName = this.getFieldName();
3742
- this.controller.getFieldObservable(fieldName)
3743
- .pipe(takeUntil(this.destroy$))
3744
- .subscribe(val => {
3946
+ registerControl() {
3947
+ if (!this.config.name || !this.formGroup)
3948
+ return;
3949
+ const fieldName = this.config.name;
3950
+ const validators = this.getValidators();
3951
+ // When inside a repeater instance, ALWAYS start with defaultValue (never
3952
+ // read from the shared controller — prevents cross-instance value copying).
3953
+ const initialValue = this.allowMulti
3954
+ ? (this.config.defaultValue ?? null)
3955
+ : (this.controller.getFieldValue(fieldName) ?? this.config.defaultValue ?? null);
3956
+ let control = this.formGroup.get(fieldName);
3957
+ if (!control) {
3958
+ control = new FormControl({ value: initialValue, disabled: !!this.config.disabled }, validators);
3959
+ this.formGroup.addControl(fieldName, control);
3960
+ }
3961
+ this.value = control.value;
3962
+ if (!this.allowMulti) {
3963
+ // ── Flat field: keep in sync with shared controller ──────────────────
3964
+ control.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(val => {
3745
3965
  this.value = val;
3966
+ this.controller.updateField(fieldName, val);
3967
+ });
3968
+ this.controller.getFieldObservable(fieldName).pipe(takeUntil(this.destroy$)).subscribe(val => {
3969
+ if (val !== control.value) {
3970
+ control.setValue(val, { emitEvent: false });
3971
+ this.value = val;
3972
+ }
3746
3973
  });
3747
3974
  }
3748
- }
3749
- setupVisibility() {
3750
- if (this.config.visibilityExpression) {
3751
- const variables = this.expressionService.extractVariables(this.config.visibilityExpression);
3752
- const observables = variables.map(v => this.controller.getFieldObservable(v));
3753
- combineLatest(observables)
3754
- .pipe(takeUntil(this.destroy$))
3755
- .subscribe(() => {
3756
- const context = this.controller.getAllData();
3757
- this.isVisible = this.expressionService.evaluateCondition(this.config.visibilityExpression, context);
3975
+ else {
3976
+ // ── Repeater field: local value tracking only ─────────────────────────
3977
+ control.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(val => {
3978
+ this.value = val;
3758
3979
  });
3759
3980
  }
3760
3981
  }
3761
- setupGeneratedField() {
3762
- if (this.config.type === 'GENERATED' && this.config.generatedConfig) {
3763
- const variables = this.config.generatedConfig.variables || [];
3764
- const observables = variables.map(v => this.controller.getFieldObservable(v));
3765
- combineLatest(observables)
3766
- .pipe(takeUntil(this.destroy$))
3767
- .subscribe(() => {
3768
- const context = this.controller.getAllData();
3769
- const result = this.evaluateFormula(context);
3770
- if (result !== null && this.config.name) {
3771
- this.controller.updateField(this.getFieldName(), result);
3772
- }
3773
- });
3982
+ getValidators() {
3983
+ const validators = [];
3984
+ if (this.config.required)
3985
+ validators.push(Validators.required);
3986
+ if (this.config.subType === 'EMAIL')
3987
+ validators.push(Validators.email);
3988
+ if (this.config.subType === 'PASSWORD') {
3989
+ // Minimum 8 chars by default; honour explicit textConfig.length overrides
3990
+ const minLen = this.config.textConfig?.length?.min ?? 8;
3991
+ validators.push(Validators.minLength(minLen));
3774
3992
  }
3993
+ if (this.config.textConfig?.length) {
3994
+ const { min, max } = this.config.textConfig.length;
3995
+ if (min && this.config.subType !== 'PASSWORD')
3996
+ validators.push(Validators.minLength(min));
3997
+ if (max)
3998
+ validators.push(Validators.maxLength(max));
3999
+ }
4000
+ if (this.config.textConfig?.pattern)
4001
+ validators.push(Validators.pattern(this.config.textConfig.pattern));
4002
+ if (this.config.numberConfig) {
4003
+ const { min, max } = this.config.numberConfig;
4004
+ if (min !== undefined)
4005
+ validators.push(Validators.min(min));
4006
+ if (max !== undefined)
4007
+ validators.push(Validators.max(max));
4008
+ }
4009
+ return validators;
4010
+ }
4011
+ // ── Cross-field match validation (password === confirmPassword) ───────────
4012
+ /**
4013
+ * When `textConfig.matchField` is configured, subscribes to value changes on
4014
+ * BOTH this control and the referenced control so the mismatch error updates
4015
+ * instantly whichever field the user edits last.
4016
+ *
4017
+ * - Values differ → sets `{ passwordMismatch: true }` on THIS control.
4018
+ * - Values match → clears `passwordMismatch` from THIS control.
4019
+ */
4020
+ setupMatchValidation() {
4021
+ const matchFieldName = this.config.textConfig?.matchField;
4022
+ if (!matchFieldName || !this.config.name || !this.formGroup)
4023
+ return;
4024
+ const thisControl = this.formGroup.get(this.config.name);
4025
+ const otherControl = this.formGroup.get(matchFieldName);
4026
+ if (!thisControl || !otherControl)
4027
+ return;
4028
+ const runCheck = () => {
4029
+ const thisVal = thisControl.value;
4030
+ const otherVal = otherControl.value;
4031
+ if (thisVal && otherVal && thisVal !== otherVal) {
4032
+ // Both have a value but they differ — flag the mismatch
4033
+ thisControl.setErrors({ ...thisControl.errors, passwordMismatch: true }, { emitEvent: false });
4034
+ }
4035
+ else {
4036
+ // Either one is empty OR they now match — clear the mismatch error
4037
+ if (thisControl.hasError('passwordMismatch')) {
4038
+ const errors = { ...thisControl.errors };
4039
+ delete errors['passwordMismatch'];
4040
+ thisControl.setErrors(Object.keys(errors).length ? errors : null, { emitEvent: false });
4041
+ }
4042
+ }
4043
+ };
4044
+ // Fire when THIS (confirmPassword) field changes
4045
+ thisControl.valueChanges
4046
+ .pipe(takeUntil(this.destroy$))
4047
+ .subscribe(() => runCheck());
4048
+ // Also fire when the OTHER (password) field changes so the error clears
4049
+ // as soon as the user corrects the source value
4050
+ otherControl.valueChanges
4051
+ .pipe(takeUntil(this.destroy$))
4052
+ .subscribe(() => runCheck());
4053
+ }
4054
+ setupVisibility() {
4055
+ if (!this.config.visibilityExpression)
4056
+ return;
4057
+ const variables = this.expressionService.extractVariables(this.config.visibilityExpression);
4058
+ const observables = variables.map(v => this.controller.getFieldObservable(v));
4059
+ combineLatest(observables).pipe(takeUntil(this.destroy$)).subscribe(() => {
4060
+ const context = this.controller.getAllData();
4061
+ this.isVisible = this.expressionService.evaluateCondition(this.config.visibilityExpression, context);
4062
+ const control = this.formGroup?.get(this.config.name);
4063
+ if (control) {
4064
+ this.isVisible ? control.enable({ emitEvent: false }) : control.disable({ emitEvent: false });
4065
+ }
4066
+ });
4067
+ }
4068
+ setupGeneratedField() {
4069
+ if (this.config.type !== 'GENERATED' || !this.config.generatedConfig)
4070
+ return;
4071
+ const variables = this.config.generatedConfig.variables || [];
4072
+ const observables = variables.map(v => this.controller.getFieldObservable(v));
4073
+ combineLatest(observables).pipe(takeUntil(this.destroy$)).subscribe(() => {
4074
+ const context = this.controller.getAllData();
4075
+ const result = this.evaluateFormula(context);
4076
+ if (result !== null && this.config.name) {
4077
+ this.controller.updateField(this.config.name, result);
4078
+ }
4079
+ });
3775
4080
  }
3776
4081
  evaluateFormula(context) {
3777
4082
  if (!this.config.generatedConfig)
@@ -3792,74 +4097,59 @@ class FormFieldComponent {
3792
4097
  return;
3793
4098
  const dependencies = this.config.optionConfig.dependencies;
3794
4099
  const observables = Object.values(dependencies).map(fieldName => this.controller.getFieldObservable(fieldName));
3795
- combineLatest(observables)
3796
- .pipe(takeUntil(this.destroy$))
3797
- .subscribe((values) => {
3798
- // Create a map of dependency values
4100
+ combineLatest(observables).pipe(takeUntil(this.destroy$)).subscribe(values => {
3799
4101
  const dependencyValues = {};
3800
4102
  Object.keys(dependencies).forEach((paramKey, index) => {
3801
4103
  dependencyValues[paramKey] = values[index];
3802
4104
  });
3803
- // Check if all required dependencies have values (optional: could skip this if partial dependencies are allowed)
3804
4105
  const allPresent = Object.values(dependencyValues).every(v => v !== null && v !== undefined && v !== '');
3805
4106
  if (allPresent) {
3806
4107
  this.loadDropdownOptions(dependencyValues);
3807
4108
  }
3808
- else {
3809
- // Clear options if dependencies are missing
3810
- if (this.config.optionConfig) {
3811
- this.config.optionConfig.optionList = [];
3812
- }
4109
+ else if (this.config.optionConfig) {
4110
+ this.config.optionConfig.optionList = [];
3813
4111
  }
3814
4112
  });
3815
4113
  }
3816
4114
  loadDropdownOptions(queryParams = {}) {
3817
4115
  const optionConfig = this.config.optionConfig;
3818
- const urls = optionConfig?.apiUrls || (optionConfig?.apiUrl ? [optionConfig.apiUrl] : optionConfig?.optionUrl ? [optionConfig.optionUrl] : []);
4116
+ const urls = optionConfig?.apiUrls ||
4117
+ (optionConfig?.apiUrl ? [optionConfig.apiUrl] :
4118
+ optionConfig?.optionUrl ? [optionConfig.optionUrl] : []);
3819
4119
  if (!urls || urls.length === 0)
3820
4120
  return;
3821
- // Create observables for each URL with query params
3822
4121
  const observables = urls.map(url => {
3823
4122
  let fullUrl = url;
3824
4123
  const params = new URLSearchParams();
3825
4124
  Object.entries(queryParams).forEach(([key, value]) => {
3826
- if (value !== null && value !== undefined) {
4125
+ if (value !== null && value !== undefined)
3827
4126
  params.append(key, String(value));
3828
- }
3829
4127
  });
3830
4128
  const queryString = params.toString();
3831
- if (queryString) {
4129
+ if (queryString)
3832
4130
  fullUrl += (fullUrl.includes('?') ? '&' : '?') + queryString;
3833
- }
3834
- return this.http.get(fullUrl);
4131
+ return this.http.get(fullUrl, { headers: this.getHeaders() });
3835
4132
  });
3836
- forkJoin(observables)
3837
- .pipe(takeUntil(this.destroy$))
3838
- .subscribe({
3839
- next: (responses) => {
4133
+ forkJoin(observables).pipe(takeUntil(this.destroy$)).subscribe({
4134
+ next: responses => {
3840
4135
  let mergedData = [];
3841
4136
  responses.forEach(response => {
3842
- // Identify array source for each response
3843
4137
  const data = optionConfig?.dataPath
3844
4138
  ? this.getValueByPath(response, optionConfig.dataPath)
3845
4139
  : (Array.isArray(response) ? response : response.data || response.items || []);
3846
- if (Array.isArray(data)) {
4140
+ if (Array.isArray(data))
3847
4141
  mergedData = [...mergedData, ...data];
3848
- }
3849
4142
  });
3850
- // Sort merged data BEFORE mapping
3851
4143
  if (optionConfig?.sortBy) {
3852
4144
  const sortKey = optionConfig.sortBy;
3853
4145
  const direction = optionConfig.sortDirection === 'DESC' ? -1 : 1;
3854
4146
  mergedData.sort((a, b) => {
3855
4147
  const valA = this.getValueByPath(a, sortKey);
3856
4148
  const valB = this.getValueByPath(b, sortKey);
3857
- if (typeof valA === 'string' && typeof valB === 'string') {
4149
+ if (typeof valA === 'string' && typeof valB === 'string')
3858
4150
  return direction * valA.localeCompare(valB);
3859
- }
3860
- if (typeof valA === 'number' && typeof valB === 'number') {
4151
+ if (typeof valA === 'number' && typeof valB === 'number')
3861
4152
  return direction * (valA - valB);
3862
- }
3863
4153
  return 0;
3864
4154
  });
3865
4155
  }
@@ -3870,14 +4160,10 @@ class FormFieldComponent {
3870
4160
  const code = optionConfig?.valuePath
3871
4161
  ? this.getValueByPath(item, optionConfig.valuePath)
3872
4162
  : (item.code || item.id || item.value);
3873
- return {
3874
- label: String(label),
3875
- code: code,
3876
- value: item
3877
- };
4163
+ return { label: String(label), code, value: item };
3878
4164
  });
3879
4165
  },
3880
- error: (err) => console.error('Failed to load dropdown options:', err)
4166
+ error: err => console.error('Failed to load dropdown options:', err)
3881
4167
  });
3882
4168
  }
3883
4169
  getValueByPath(obj, path) {
@@ -3885,138 +4171,97 @@ class FormFieldComponent {
3885
4171
  return obj;
3886
4172
  return path.split('.').reduce((acc, part) => {
3887
4173
  const match = part.match(/(\w+)\[(\d+)\]/);
3888
- if (match) {
4174
+ if (match)
3889
4175
  return acc?.[match[1]]?.[parseInt(match[2])];
3890
- }
3891
4176
  return acc?.[part];
3892
4177
  }, obj);
3893
4178
  }
3894
- onValueChange(event) {
4179
+ /** Builds HttpHeaders using the token stored in the SmartFormController (sourced from configJSON). */
4180
+ getHeaders() {
4181
+ let headers = new HttpHeaders();
4182
+ if (this.controller.token) {
4183
+ const headerName = this.controller.tokenHeader || 'Authorization';
4184
+ headers = headers.set(headerName, this.controller.token);
4185
+ }
4186
+ return headers;
4187
+ }
4188
+ updateValue(newValue) {
3895
4189
  if (!this.config.name)
3896
4190
  return;
3897
- const fieldName = this.getFieldName();
3898
- let newValue;
3899
- if (event?.target) {
3900
- const target = event.target;
3901
- if (target.type === 'checkbox') {
3902
- newValue = target.checked;
3903
- }
3904
- else if (target.type === 'number') {
3905
- newValue = target.value ? Number(target.value) : null;
3906
- }
3907
- else {
3908
- newValue = target.value;
3909
- }
3910
- }
3911
- else {
3912
- newValue = event;
4191
+ const control = this.formGroup.get(this.config.name);
4192
+ if (control) {
4193
+ control.setValue(newValue);
4194
+ control.markAsDirty();
4195
+ control.markAsTouched();
3913
4196
  }
3914
- this.controller.updateField(fieldName, newValue);
3915
- this.validate(newValue);
3916
4197
  }
3917
4198
  onCheckboxListChange(code, checked) {
3918
4199
  if (!this.config.name)
3919
4200
  return;
3920
- const fieldName = this.getFieldName();
3921
- const currentValue = this.controller.getFieldValue(fieldName) || [];
3922
- let newValue;
3923
- if (checked) {
3924
- newValue = [...currentValue, code];
3925
- }
3926
- else {
3927
- newValue = currentValue.filter((c) => c !== code);
3928
- }
3929
- this.controller.updateField(fieldName, newValue);
3930
- this.validate(newValue);
4201
+ const currentValue = (this.allowMulti
4202
+ ? (this.formGroup.get(this.config.name)?.value)
4203
+ : this.controller.getFieldValue(this.config.name)) || [];
4204
+ const newValue = checked
4205
+ ? [...currentValue, code]
4206
+ : currentValue.filter((c) => c !== code);
4207
+ this.updateValue(newValue);
3931
4208
  }
3932
4209
  isChecked(code) {
3933
4210
  const value = this.value || [];
3934
4211
  return Array.isArray(value) && value.includes(code);
3935
4212
  }
3936
- validate(value) {
3937
- this.errorMessage = '';
3938
- if (this.config.required && !value) {
3939
- this.errorMessage = 'This field is required';
3940
- return;
3941
- }
3942
- if (!value)
3943
- return;
3944
- if (this.config.subType === 'EMAIL' && !this.config.textConfig?.pattern) {
3945
- const validator = ValidationUtils.email();
3946
- const errors = validator({ value });
3947
- if (errors)
3948
- this.errorMessage = ValidationUtils.getErrorMessage(errors);
3949
- }
3950
- if (this.config.subType === 'PHONE' && !this.config.textConfig?.pattern) {
3951
- const validator = ValidationUtils.phone();
3952
- const errors = validator({ value });
3953
- if (errors)
3954
- this.errorMessage = ValidationUtils.getErrorMessage(errors);
3955
- }
3956
- if (this.config.textConfig?.length) {
3957
- const { min, max } = this.config.textConfig.length;
3958
- if (min && value.length < min) {
3959
- this.errorMessage = `Minimum length is ${min} characters`;
3960
- }
3961
- if (max && value.length > max) {
3962
- this.errorMessage = `Maximum length is ${max} characters`;
3963
- }
3964
- }
3965
- if (this.config.textConfig?.pattern) {
3966
- try {
3967
- const regex = new RegExp(this.config.textConfig.pattern);
3968
- if (!regex.test(value)) {
3969
- this.errorMessage = this.config.textConfig.patternMessage || 'Invalid format';
3970
- }
3971
- }
3972
- catch (e) {
3973
- console.error('Invalid regex pattern:', this.config.textConfig.pattern);
3974
- }
3975
- }
3976
- if (this.config.numberConfig) {
3977
- const { min, max } = this.config.numberConfig;
3978
- const numValue = Number(value);
3979
- if (min !== undefined && numValue < min) {
3980
- this.errorMessage = `Minimum value is ${min}`;
3981
- }
3982
- if (max !== undefined && numValue > max) {
3983
- this.errorMessage = `Maximum value is ${max}`;
3984
- }
3985
- }
3986
- }
3987
- getFieldName() {
3988
- if (this.sectionIndex !== undefined && this.config.name) {
3989
- return `${this.config.name}_${this.sectionIndex}`;
3990
- }
3991
- return this.config.name || '';
3992
- }
3993
- get isTextField() {
3994
- return this.config.type === 'TEXT_INPUT';
3995
- }
3996
- get isNumberField() {
3997
- return this.config.type === 'NUMBER_INPUT';
3998
- }
3999
- get isDateField() {
4000
- return this.config.type === 'DATE';
4001
- }
4002
- get isDropdown() {
4003
- return this.config.type === 'DROPDOWN';
4004
- }
4005
- get isRadio() {
4006
- return this.config.type === 'RADIO';
4007
- }
4008
- get isCheckbox() {
4009
- return this.config.type === 'CHECKBOX';
4010
- }
4011
- get isChip() {
4012
- return this.config.type === 'CHIP';
4013
- }
4014
- get isSwitch() {
4015
- return this.config.type === 'SWITCH';
4016
- }
4017
- get isRating() {
4018
- return this.config.type === 'RATING';
4019
- }
4213
+ get errorMessage() {
4214
+ if (!this.config.name || !this.formGroup)
4215
+ return '';
4216
+ const control = this.formGroup.get(this.config.name);
4217
+ if (control && control.invalid && (control.touched || control.dirty)) {
4218
+ if (control.hasError('required'))
4219
+ return 'This field is required';
4220
+ if (control.hasError('email'))
4221
+ return 'Invalid email format';
4222
+ if (control.hasError('passwordMismatch'))
4223
+ return 'Passwords do not match';
4224
+ if (control.hasError('formulaError'))
4225
+ return this.config.errorMessage || 'Invalid value';
4226
+ if (control.hasError('minlength'))
4227
+ return `Minimum length is ${control.errors?.['minlength'].requiredLength} characters`;
4228
+ if (control.hasError('maxlength'))
4229
+ return `Maximum length is ${control.errors?.['maxlength'].requiredLength} characters`;
4230
+ if (control.hasError('min'))
4231
+ return `Minimum value is ${control.errors?.['min'].min}`;
4232
+ if (control.hasError('max'))
4233
+ return `Maximum value is ${control.errors?.['max'].max}`;
4234
+ if (control.hasError('pattern'))
4235
+ return this.config.textConfig?.patternMessage || 'Invalid format';
4236
+ }
4237
+ return '';
4238
+ }
4239
+ // ── Type guards ──────────────────────────────────────────────────────────
4240
+ get isTextField() { return this.config.type === 'TEXT_INPUT'; }
4241
+ get isNumberField() { return this.config.type === 'NUMBER_INPUT'; }
4242
+ get isDateField() { return this.config.type === 'DATE'; }
4243
+ get isDropdown() { return this.config.type === 'DROPDOWN'; }
4244
+ get isFileUpload() { return this.config.type === 'FILE_UPLOAD'; }
4245
+ get isRadio() { return this.config.type === 'RADIO'; }
4246
+ get isCheckbox() { return this.config.type === 'CHECKBOX'; }
4247
+ get isChip() { return this.config.type === 'CHIP'; }
4248
+ get isSwitch() { return this.config.type === 'SWITCH'; }
4249
+ get isRating() { return this.config.type === 'RATING'; }
4250
+ get isGenerated() { return this.config.type === 'GENERATED'; }
4251
+ get isRow() { return this.config.type === 'ROW'; }
4252
+ get isGroup() { return this.config.type === 'GROUP'; }
4253
+ /**
4254
+ * Returns the effective grid column span for a child inside a ROW.
4255
+ * If the child declares an explicit colSpan, use it.
4256
+ * Otherwise divide 12 equally among all children (floor, min 1).
4257
+ */
4258
+ getChildColSpan(child) {
4259
+ if (child.colSpan)
4260
+ return child.colSpan;
4261
+ const count = this.config.children?.length || 1;
4262
+ return Math.max(1, Math.floor(12 / count));
4263
+ }
4264
+ // ── Rating helpers ───────────────────────────────────────────────────────
4020
4265
  onRatingChange(star, event) {
4021
4266
  if (!this.config.name || this.config.disabled)
4022
4267
  return;
@@ -4024,78 +4269,214 @@ class FormFieldComponent {
4024
4269
  if (this.config.ratingConfig?.allowHalf && event) {
4025
4270
  const target = event.target;
4026
4271
  const rect = target.getBoundingClientRect();
4027
- const x = event.clientX - rect.left;
4028
- // If click is in the first 50% of the star, it's a half star
4029
- if (x < rect.width / 2) {
4272
+ if (event.clientX - rect.left < rect.width / 2)
4030
4273
  newValue = star - 0.5;
4031
- }
4032
4274
  }
4033
- // Toggle off if clicking same value
4034
- if (this.value === newValue) {
4275
+ if (this.value === newValue)
4035
4276
  newValue = 0;
4036
- }
4037
- this.controller.updateField(this.getFieldName(), newValue);
4038
- this.validate(newValue);
4277
+ this.updateValue(newValue);
4039
4278
  }
4040
4279
  getStarArray() {
4041
4280
  const max = this.config.ratingConfig?.maxRating || 5;
4042
4281
  return Array.from({ length: max }, (_, i) => i + 1);
4043
4282
  }
4044
- isStarHalf(star) {
4045
- const value = this.value || 0;
4046
- return value === star - 0.5;
4047
- }
4048
- isStarFilled(star) {
4049
- const value = this.value || 0;
4050
- return value >= star;
4051
- }
4052
- get isGenerated() {
4053
- return this.config.type === 'GENERATED';
4054
- }
4055
- get isRow() {
4056
- return this.config.type === 'ROW';
4057
- }
4058
- get isGroup() {
4059
- return this.config.type === 'GROUP';
4060
- }
4061
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FormFieldComponent, deps: [{ token: ExpressionService }, { token: i3.HttpClient }], target: i0.ɵɵFactoryTarget.Component });
4062
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: FormFieldComponent, isStandalone: false, selector: "lib-form-field", inputs: { config: "config", controller: "controller", sectionIndex: "sectionIndex" }, ngImport: i0, template: "<div class=\"form-field\" *ngIf=\"isVisible\" [class.has-error]=\"errorMessage\">\n <!-- ROW Layout -->\n <div *ngIf=\"isRow\" class=\"form-row\" [class.horizontal]=\"config.subType === 'HORIZONTAL'\">\n <ng-container *ngFor=\"let child of config.children\">\n <div class=\"row-field\">\n <lib-form-field\n [config]=\"child\"\n [controller]=\"controller\"\n [sectionIndex]=\"sectionIndex\">\n </lib-form-field>\n </div>\n </ng-container>\n </div>\n\n <!-- GROUP (Section) -->\n <div *ngIf=\"isGroup && config.sectionConfig\" class=\"form-group\">\n <ng-container *ngFor=\"let field of config.sectionConfig.children\">\n <lib-form-field\n [config]=\"field\"\n [controller]=\"controller\"\n [sectionIndex]=\"sectionIndex\">\n </lib-form-field>\n </ng-container>\n </div>\n\n <!-- Text Input -->\n <div *ngIf=\"isTextField\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <textarea\n *ngIf=\"config.subType === 'LONG'\"\n class=\"field-input textarea\"\n [placeholder]=\"config.hint || ''\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n (input)=\"onValueChange($event)\"\n rows=\"4\">\n </textarea>\n \n <input\n *ngIf=\"config.subType !== 'LONG'\"\n [type]=\"config.subType === 'EMAIL' ? 'email' : config.subType === 'PHONE' ? 'tel' : 'text'\"\n class=\"field-input\"\n [placeholder]=\"config.hint || ''\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n (input)=\"onValueChange($event)\">\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Number Input -->\n <div *ngIf=\"isNumberField\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <input\n type=\"number\"\n class=\"field-input\"\n [placeholder]=\"config.hint || ''\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n [min]=\"config.numberConfig?.min\"\n [max]=\"config.numberConfig?.max\"\n [step]=\"config.numberConfig?.step || 1\"\n (input)=\"onValueChange($event)\">\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Date Input -->\n <div *ngIf=\"isDateField\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <input\n type=\"date\"\n class=\"field-input\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n [min]=\"config.dateConfig?.minDate\"\n [max]=\"config.dateConfig?.maxDate\"\n (input)=\"onValueChange($event)\">\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Dropdown -->\n <div *ngIf=\"isDropdown\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <select\n *ngIf=\"config.subType === 'SINGLE'\"\n class=\"field-input\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange($event)\">\n <option value=\"\">Select...</option>\n <option *ngFor=\"let option of config.optionConfig?.optionList\" [value]=\"option.code\">\n {{ option.label }}\n </option>\n </select>\n \n <select\n *ngIf=\"config.subType === 'MULTIPLE'\"\n class=\"field-input\"\n multiple\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange($event)\">\n <option *ngFor=\"let option of config.optionConfig?.optionList\" [value]=\"option.code\">\n {{ option.label }}\n </option>\n </select>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Radio -->\n <div *ngIf=\"isRadio\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <div class=\"radio-group\">\n <label *ngFor=\"let option of config.optionConfig?.optionList\" class=\"radio-label\">\n <input\n type=\"radio\"\n [name]=\"getFieldName()\"\n [value]=\"option.code\"\n [checked]=\"value === option.code\"\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange(option.code)\">\n <span>{{ option.label }}</span>\n </label>\n </div>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Checkbox -->\n <div *ngIf=\"isCheckbox\" class=\"field-wrapper\">\n <label *ngIf=\"config.label && config.subType === 'LIST'\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <div *ngIf=\"config.subType === 'BOOL'\" class=\"checkbox-single\">\n <label class=\"checkbox-label\">\n <input\n type=\"checkbox\"\n [checked]=\"value === true\"\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange($event)\">\n <span>{{ config.label }}</span>\n </label>\n </div>\n \n <div *ngIf=\"config.subType === 'LIST'\" class=\"checkbox-group\">\n <label *ngFor=\"let option of config.optionConfig?.optionList\" class=\"checkbox-label\">\n <input\n type=\"checkbox\"\n [checked]=\"isChecked(option.code)\"\n [disabled]=\"config.disabled\"\n (change)=\"onCheckboxListChange(option.code, $any($event.target).checked)\">\n <span>{{ option.label }}</span>\n </label>\n </div>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Chip -->\n <div *ngIf=\"isChip\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <div class=\"chip-group\">\n <label *ngFor=\"let option of config.optionConfig?.optionList\" \n class=\"chip-label\"\n [class.selected]=\"isChecked(option.code)\">\n <input\n type=\"checkbox\"\n [checked]=\"isChecked(option.code)\"\n [disabled]=\"config.disabled\"\n (change)=\"onCheckboxListChange(option.code, $any($event.target).checked)\"\n style=\"display: none;\">\n <span>{{ option.label }}</span>\n </label>\n </div>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Switch -->\n <div *ngIf=\"isSwitch\" class=\"field-wrapper\">\n <label class=\"switch-container\">\n <span class=\"field-label\">{{ config.label }}</span>\n <div class=\"switch\">\n <input\n type=\"checkbox\"\n [checked]=\"value === true\"\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange($event)\">\n <span class=\"slider\"></span>\n </div>\n </label>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Rating -->\n <div *ngIf=\"isRating\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <div class=\"rating-group\">\n <span *ngFor=\"let star of getStarArray()\" \n class=\"star\"\n [class.filled]=\"isStarFilled(star)\"\n [class.half]=\"isStarHalf(star)\"\n (click)=\"onRatingChange(star, $event)\">\n \u2605\n </span>\n </div>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Generated Field (Read-only) -->\n <div *ngIf=\"isGenerated\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">{{ config.label }}</label>\n <div class=\"generated-value\">{{ value || '-' }}</div>\n <span class=\"field-hint\" *ngIf=\"config.hint\">{{ config.hint }}</span>\n </div>\n</div>\n", styles: [".form-field{margin-bottom:16px}.form-field.has-error .field-input{border-color:#f44336}.form-row{display:flex;gap:16px}.form-row.horizontal{flex-direction:row}.form-row.horizontal>*{flex:1}.form-row:not(.horizontal){flex-direction:column}.field-wrapper{display:flex;flex-direction:column;gap:6px}.field-label{font-size:14px;font-weight:500;color:#333}.field-label .required{color:#f44336;margin-left:4px}.field-input{padding:10px 12px;border:1px solid #ddd;border-radius:4px;font-size:14px;transition:border-color .3s}.field-input:focus{outline:none;border-color:#2196f3}.field-input:disabled{background:#f5f5f5;cursor:not-allowed}.field-input.textarea{resize:vertical;font-family:inherit}.field-hint{font-size:12px;color:#666}.field-error{font-size:12px;color:#f44336}.radio-group,.checkbox-group{display:flex;flex-direction:column;gap:8px}.radio-label,.checkbox-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px}.radio-label input,.checkbox-label input{cursor:pointer}.checkbox-single .checkbox-label{font-weight:500}.chip-group{display:flex;flex-wrap:wrap;gap:8px}.chip-label{padding:8px 16px;border:1px solid #ddd;border-radius:20px;cursor:pointer;font-size:14px;transition:all .3s}.chip-label:hover{background:#f5f5f5}.chip-label.selected{background:#2196f3;color:#fff;border-color:#2196f3}.switch-container{display:flex;justify-content:space-between;align-items:center;cursor:pointer}.switch{position:relative;width:50px;height:24px}.switch input{opacity:0;width:0;height:0}.switch input:checked+.slider{background-color:#2196f3}.switch input:checked+.slider:before{transform:translate(26px)}.switch .slider{position:absolute;cursor:pointer;inset:0;background-color:#ccc;transition:.4s;border-radius:24px}.switch .slider:before{position:absolute;content:\"\";height:18px;width:18px;left:3px;bottom:3px;background-color:#fff;transition:.4s;border-radius:50%}.rating-group{display:flex;gap:4px}.rating-group .star{font-size:28px;display:inline-block;background:#ddd;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.rating-group .star.filled{background:#ffc107;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.rating-group .star.half{background:linear-gradient(90deg,#ffc107 50%,#ddd 50%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.rating-group .star:hover{background:#ffc107;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.generated-value{padding:10px 12px;background:#f5f5f5;border:1px solid #ddd;border-radius:4px;font-size:14px;color:#666}select[multiple]{min-height:120px}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "component", type: FormFieldComponent, selector: "lib-form-field", inputs: ["config", "controller", "sectionIndex"] }] });
4283
+ isStarHalf(star) { return (this.value || 0) === star - 0.5; }
4284
+ isStarFilled(star) { return (this.value || 0) >= star; }
4285
+ // ── File Upload helpers ──────────────────────────────────────────────────
4286
+ onDragOver(event) {
4287
+ event.preventDefault();
4288
+ event.stopPropagation();
4289
+ this.isDragOver = true;
4290
+ }
4291
+ onDragLeave(event) {
4292
+ event.preventDefault();
4293
+ event.stopPropagation();
4294
+ this.isDragOver = false;
4295
+ }
4296
+ onFileDrop(event) {
4297
+ event.preventDefault();
4298
+ event.stopPropagation();
4299
+ this.isDragOver = false;
4300
+ const files = event.dataTransfer?.files;
4301
+ if (files && files.length > 0) {
4302
+ this.processFiles(files);
4303
+ }
4304
+ }
4305
+ onFileSelected(event) {
4306
+ const input = event.target;
4307
+ if (input.files && input.files.length > 0) {
4308
+ this.processFiles(input.files);
4309
+ }
4310
+ // Reset input so the same file can be re-selected if removed
4311
+ input.value = '';
4312
+ }
4313
+ processFiles(files) {
4314
+ this.fileUploadError = '';
4315
+ const cfg = this.config.attachmentConfig;
4316
+ const maxSizeBytes = (cfg?.maxSizeMB ?? 10) * 1024 * 1024;
4317
+ const currentFiles = this.value || [];
4318
+ const maxFiles = cfg?.maxFiles ?? (cfg?.multiple ? 10 : 1);
4319
+ const isMultiple = cfg?.multiple ?? false;
4320
+ const incoming = Array.from(files);
4321
+ for (const file of incoming) {
4322
+ // Size validation
4323
+ if (file.size > maxSizeBytes) {
4324
+ this.fileUploadError = `"${file.name}" exceeds the maximum allowed size of ${cfg?.maxSizeMB ?? 10} MB.`;
4325
+ continue;
4326
+ }
4327
+ // Max-files validation
4328
+ if (isMultiple && currentFiles.length >= maxFiles) {
4329
+ this.fileUploadError = `Maximum ${maxFiles} file${maxFiles !== 1 ? 's' : ''} allowed.`;
4330
+ break;
4331
+ }
4332
+ const reader = new FileReader();
4333
+ reader.onload = (e) => {
4334
+ const entry = {
4335
+ name: file.name,
4336
+ size: file.size,
4337
+ type: file.type,
4338
+ dataUrl: e.target?.result,
4339
+ file
4340
+ };
4341
+ const updated = isMultiple ? [...currentFiles, entry] : [entry];
4342
+ this.updateValue(updated);
4343
+ };
4344
+ reader.readAsDataURL(file);
4345
+ }
4346
+ }
4347
+ removeUploadedFile(index) {
4348
+ const current = this.value || [];
4349
+ const updated = current.filter((_, i) => i !== index);
4350
+ this.updateValue(updated);
4351
+ }
4352
+ getFileIcon(mimeType) {
4353
+ if (mimeType.includes('pdf'))
4354
+ return 'picture_as_pdf';
4355
+ if (mimeType.includes('image'))
4356
+ return 'image';
4357
+ if (mimeType.includes('word') || mimeType.includes('document'))
4358
+ return 'description';
4359
+ if (mimeType.includes('sheet') || mimeType.includes('excel') || mimeType.includes('csv'))
4360
+ return 'table_chart';
4361
+ if (mimeType.includes('zip') || mimeType.includes('compressed'))
4362
+ return 'folder_zip';
4363
+ return 'attach_file';
4364
+ }
4365
+ formatFileSize(bytes) {
4366
+ if (bytes < 1024)
4367
+ return `${bytes} B`;
4368
+ if (bytes < 1024 * 1024)
4369
+ return `${(bytes / 1024).toFixed(1)} KB`;
4370
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
4371
+ }
4372
+ // ── Action Labels ──────────────────────────────────────────────────────────
4373
+ get addLabel() {
4374
+ const key = this.controller.actionLabels?.addLabel || 'Add';
4375
+ return this.controller.labels[key] || key;
4376
+ }
4377
+ get removeLabel() {
4378
+ const key = this.controller.actionLabels?.removeLabel || 'Remove';
4379
+ return this.controller.labels[key] || key;
4380
+ }
4381
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FormFieldComponent, deps: [{ token: i1$2.FormBuilder }, { token: ExpressionService }, { token: i3.HttpClient }], target: i0.ɵɵFactoryTarget.Component });
4382
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: FormFieldComponent, isStandalone: false, selector: "lib-form-field", inputs: { config: "config", controller: "controller", formGroup: "formGroup", allowMulti: "allowMulti" }, ngImport: i0, template: "<div class=\"form-field\" *ngIf=\"isVisible\" [class.has-error]=\"errorMessage\">\r\n\r\n <!-- \u2550\u2550 ROW Layout \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isRow\" class=\"form-row grid-row\">\r\n <ng-container *ngFor=\"let child of config.children\">\r\n <div class=\"row-field\" [style.gridColumn]=\"'span ' + getChildColSpan(child)\">\r\n <lib-form-field [config]=\"child\" [controller]=\"controller\" [formGroup]=\"formGroup\" [allowMulti]=\"allowMulti\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- \u2550\u2550 GROUP \u2014 allowMulti (repeater) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isGroup && config.sectionConfig?.allowMulti\" class=\"group-section-wrapper\">\r\n <h3 class=\"group-label\" *ngIf=\"config.sectionConfig?.label\">{{ config.sectionConfig!.label }}</h3>\r\n\r\n <div *ngFor=\"let instance of instanceList; trackBy: trackByInstanceId; let i = index\" class=\"group-instance\">\r\n <!-- Instance header \u2014 show remove only when more than 1 instance -->\r\n <div class=\"group-header\" *ngIf=\"instanceList.length > 1\">\r\n <span class=\"group-number\">{{ config.sectionConfig!.label }} #{{ i + 1 }}</span>\r\n <lib-button [variant]=\"'danger-outline'\" [icon]=\"{type: 'material', value: 'delete_outline'}\"\r\n (click)=\"removeGroupInstance(i)\">\r\n {{ removeLabel }}\r\n </lib-button>\r\n </div>\r\n\r\n <!-- Fields \u2014 each child receives the *instance* FormGroup and the group's allowMulti config -->\r\n <div class=\"group-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.sectionConfig!.children\">\r\n <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"instance.fg\" [allowMulti]=\"true\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n <lib-button [variant]=\"'outline'\" [icon]=\"{type: 'material', value: 'add'}\" (click)=\"addGroupInstance()\"\r\n class=\"btn-add-group-wrapper\">\r\n {{ addLabel }} {{ config.sectionConfig!.label }}\r\n </lib-button>\r\n </div>\r\n\r\n <!-- \u2550\u2550 GROUP \u2014 single (non-repeater) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isGroup && config.sectionConfig && !config.sectionConfig.allowMulti\" class=\"group-section-wrapper\">\r\n <h3 class=\"group-label\" *ngIf=\"config.sectionConfig.label\">{{ config.sectionConfig.label }}</h3>\r\n <div class=\"group-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.sectionConfig.children\">\r\n <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"groupFormGroup\" [allowMulti]=\"false\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n\r\n <!-- \u2550\u2550 Text Input \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isTextField\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <textarea *ngIf=\"config.subType === 'LONG'\" class=\"field-input textarea\" [placeholder]=\"config.placeholder || ''\"\r\n [formControlName]=\"config.name!\" [class.is-invalid]=\"errorMessage\" rows=\"4\">\r\n </textarea>\r\n\r\n <!-- Password input with show/hide toggle -->\r\n <div *ngIf=\"config.subType === 'PASSWORD'\" class=\"password-wrapper\">\r\n <input [type]=\"showPassword ? 'text' : 'password'\" class=\"field-input password-input\"\r\n [placeholder]=\"config.placeholder || ''\" [formControlName]=\"config.name!\" [class.is-invalid]=\"errorMessage\">\r\n <button type=\"button\" class=\"password-toggle\" (click)=\"showPassword = !showPassword\" tabindex=\"-1\"\r\n [attr.aria-label]=\"showPassword ? 'Hide password' : 'Show password'\">\r\n <mat-icon class=\"eye-icon\">{{ showPassword ? 'visibility' : 'visibility_off' }}</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <div class=\"input-group\" [class.readonly]=\"config.readonly\">\r\n <span class=\"input-prefix\" *ngIf=\"config.prefix\">{{ config.prefix }}</span>\r\n\r\n <input *ngIf=\"config.subType !== 'LONG' && config.subType !== 'PASSWORD'\"\r\n [type]=\"config.subType === 'EMAIL' ? 'email' : config.subType === 'PHONE' ? 'tel' : 'text'\" class=\"field-input\"\r\n [placeholder]=\"config.placeholder || ''\" [formControlName]=\"config.name!\" [class.is-invalid]=\"errorMessage\"\r\n [readonly]=\"config.readonly\">\r\n\r\n <span class=\"input-suffix\" *ngIf=\"config.suffix\">{{ config.suffix }}</span>\r\n\r\n <div class=\"readonly-icons\" *ngIf=\"config.readonly\">\r\n <mat-icon class=\"lock-icon\">lock</mat-icon>\r\n </div>\r\n </div>\r\n\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Number Input \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isNumberField\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"input-group\" [class.readonly]=\"config.readonly\">\r\n <span class=\"input-prefix\" *ngIf=\"config.prefix\">{{ config.prefix }}</span>\r\n\r\n <input type=\"number\" class=\"field-input\" [placeholder]=\"config.placeholder || ''\" [formControlName]=\"config.name!\"\r\n [min]=\"config.numberConfig?.min\" [max]=\"config.numberConfig?.max\" [step]=\"config.numberConfig?.step || 1\"\r\n [class.is-invalid]=\"errorMessage\" [readonly]=\"config.readonly\">\r\n\r\n <span class=\"input-suffix\" *ngIf=\"config.suffix\">{{ config.suffix }}</span>\r\n\r\n <div class=\"readonly-icons\" *ngIf=\"config.readonly\">\r\n <mat-icon class=\"lock-icon\">lock</mat-icon>\r\n </div>\r\n </div>\r\n\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Date Input \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isDateField\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <input type=\"date\" class=\"field-input\" [formControlName]=\"config.name!\" [min]=\"config.dateConfig?.minDate\"\r\n [max]=\"config.dateConfig?.maxDate\" [class.is-invalid]=\"errorMessage\">\r\n\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Dropdown \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isDropdown\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <select *ngIf=\"config.subType === 'SINGLE'\" class=\"field-input\" [formControlName]=\"config.name!\"\r\n [class.is-invalid]=\"errorMessage\">\r\n <option [ngValue]=\"null\" disabled selected>{{ config.placeholder || 'Select' }}</option>\r\n <option *ngFor=\"let option of config.optionConfig?.optionList\" [value]=\"option.code\">\r\n {{ option.label }}\r\n </option>\r\n </select>\r\n\r\n <select *ngIf=\"config.subType === 'MULTIPLE'\" class=\"field-input\" multiple [formControlName]=\"config.name!\"\r\n [class.is-invalid]=\"errorMessage\">\r\n <option *ngFor=\"let option of config.optionConfig?.optionList\" [value]=\"option.code\">\r\n {{ option.label }}\r\n </option>\r\n </select>\r\n\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Radio \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isRadio\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"radio-group\" [class.is-invalid]=\"errorMessage\">\r\n <label *ngFor=\"let option of config.optionConfig?.optionList\" class=\"radio-label\">\r\n <input type=\"radio\" [formControlName]=\"config.name!\" [value]=\"option.code\">\r\n <span>{{ option.label }}</span>\r\n </label>\r\n </div>\r\n\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Checkbox \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isCheckbox\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label && config.subType === 'LIST'\" class=\"field-label\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div *ngIf=\"config.subType === 'BOOL'\" class=\"checkbox-single\">\r\n <label class=\"checkbox-label\">\r\n <input type=\"checkbox\" [formControlName]=\"config.name!\" [class.is-invalid]=\"errorMessage\">\r\n <span>{{ config.label }}</span>\r\n </label>\r\n </div>\r\n\r\n <div *ngIf=\"config.subType === 'LIST'\" class=\"checkbox-group\" [class.is-invalid]=\"errorMessage\">\r\n <label *ngFor=\"let option of config.optionConfig?.optionList\" class=\"checkbox-label\">\r\n <input type=\"checkbox\" [checked]=\"isChecked(option.code)\" [disabled]=\"!!config.disabled\"\r\n (change)=\"onCheckboxListChange(option.code, $any($event.target).checked)\">\r\n <span>{{ option.label }}</span>\r\n </label>\r\n </div>\r\n\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Chip \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isChip\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"chip-group\" [class.is-invalid]=\"errorMessage\">\r\n <label *ngFor=\"let option of config.optionConfig?.optionList\" class=\"chip-label\"\r\n [class.selected]=\"isChecked(option.code)\">\r\n <input type=\"checkbox\" [checked]=\"isChecked(option.code)\" [disabled]=\"!!config.disabled\"\r\n (change)=\"onCheckboxListChange(option.code, $any($event.target).checked)\" style=\"display: none;\">\r\n <span>{{ option.label }}</span>\r\n </label>\r\n </div>\r\n\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Switch \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isSwitch\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label class=\"switch-container\">\r\n <span class=\"field-label\">{{ config.label }}</span>\r\n <div class=\"switch\">\r\n <input type=\"checkbox\" [formControlName]=\"config.name!\" [class.is-invalid]=\"errorMessage\">\r\n <span class=\"slider\"></span>\r\n </div>\r\n </label>\r\n\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Rating \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isRating\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"rating-group\" [class.is-invalid]=\"errorMessage\">\r\n <span *ngFor=\"let star of getStarArray()\" class=\"star\" [class.filled]=\"isStarFilled(star)\"\r\n [class.half]=\"isStarHalf(star)\" (click)=\"onRatingChange(star, $event)\">\r\n <mat-icon>{{ isStarFilled(star) || isStarHalf(star) ? 'star' : 'star_border' }}</mat-icon>\r\n </span>\r\n </div>\r\n\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Generated Field (read-only) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isGenerated\" class=\"field-wrapper\">\r\n <label *ngIf=\"config.label\" class=\"field-label\">{{ config.label }}</label>\r\n <div class=\"generated-value\">{{ value || '-' }}</div>\r\n <span class=\"field-hint\" *ngIf=\"config.hint\">{{ config.hint }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 File Upload \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isFileUpload\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <!-- Drop Zone -->\r\n <div class=\"upload-drop-zone\" [class.drag-over]=\"isDragOver\" [class.has-files]=\"value?.length\"\r\n [class.is-invalid]=\"errorMessage\" (dragover)=\"onDragOver($event)\" (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onFileDrop($event)\" (click)=\"fileInput.click()\">\r\n\r\n <div class=\"upload-icon-wrap\">\r\n <mat-icon class=\"upload-cloud-icon\">cloud_upload</mat-icon>\r\n </div>\r\n\r\n <p class=\"upload-main-text\">Drag and drop files here or <span class=\"upload-link\">click to upload</span></p>\r\n <p class=\"upload-hint-text\" *ngIf=\"config.attachmentConfig?.acceptLabel\">\r\n Supported formats:\r\n <span class=\"upload-formats\">{{ config.attachmentConfig!.acceptLabel }}</span>\r\n </p>\r\n <p class=\"upload-hint-text\" *ngIf=\"!config.attachmentConfig?.acceptLabel && config.hint\">\r\n {{ config.hint }}\r\n </p>\r\n\r\n <!-- Hidden native file input -->\r\n <input #fileInput type=\"file\" style=\"display:none\"\r\n [attr.multiple]=\"config.attachmentConfig?.multiple ? true : null\"\r\n [attr.accept]=\"config.attachmentConfig?.accept || null\" (change)=\"onFileSelected($event)\">\r\n </div>\r\n\r\n <!-- Uploaded file list -->\r\n <div class=\"uploaded-list\" *ngIf=\"value?.length\">\r\n <div *ngFor=\"let f of value; let i = index\" class=\"uploaded-item\">\r\n <!-- File type icon -->\r\n <mat-icon class=\"file-type-icon\">{{ getFileIcon(f.type) }}</mat-icon>\r\n\r\n <!-- Image thumbnail (only for images) -->\r\n <img *ngIf=\"f.type?.startsWith('image') && f.dataUrl\" [src]=\"f.dataUrl\" class=\"file-thumb\" alt=\"preview\">\r\n\r\n <!-- Name & size -->\r\n <div class=\"file-info\">\r\n <span class=\"file-name\" [title]=\"f.name\">{{ f.name }}</span>\r\n <span class=\"file-size\">{{ formatFileSize(f.size) }}</span>\r\n </div>\r\n <lib-button [variant]=\"'danger-outline'\" (click)=\"removeUploadedFile(i)\">\r\n <mat-icon>close</mat-icon>\r\n </lib-button>\r\n </div>\r\n </div>\r\n\r\n <!-- Validation / file errors -->\r\n <span class=\"field-error\" *ngIf=\"fileUploadError\">{{ fileUploadError }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage && !fileUploadError\">{{ errorMessage }}</span>\r\n <span class=\"field-hint\"\r\n *ngIf=\"config.hint && !errorMessage && !fileUploadError && !config.attachmentConfig?.acceptLabel\">\r\n {{ config.hint }}\r\n </span>\r\n </div>\r\n\r\n</div>", styles: [".form-field{margin-bottom:var(--cc-sf-grid-gap, 16px)}.form-field.has-error .field-input{border-color:var(--cc-sf-error-border, #DC2626)}.form-row{display:flex;gap:var(--cc-sf-grid-gap, 16px)}.form-row.horizontal{flex-direction:row}.form-row.horizontal>*{flex:1}.form-row:not(.horizontal){flex-direction:column}.form-row.grid-row{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--cc-sf-grid-gap, 16px);align-items:start}@media(max-width:640px){.form-row.grid-row{grid-template-columns:1fr}.form-row.grid-row .row-field{grid-column:span 12!important}}.field-wrapper{display:flex;flex-direction:column;gap:6px}.field-label{display:block;font-size:var(--cc-sf-label-size, .875rem);font-weight:var(--cc-sf-label-weight, 500);color:var(--cc-sf-label-color, #111827);margin-bottom:.5rem;line-height:1.25rem}.field-label .required{color:var(--cc-sf-label-required-color, #DC2626);margin-left:.125rem}.field-input{width:100%;padding:var(--cc-sf-input-padding, .625rem .875rem);font-size:var(--cc-sf-input-font-size, .875rem);line-height:1.5;color:var(--cc-sf-input-color, #111827);background-color:var(--cc-sf-input-bg, #ffffff);border:var(--cc-sf-input-border, 1.5px solid #D1D5DB);border-radius:var(--cc-sf-input-radius, 8px);box-shadow:var(--cc-sf-input-shadow, none);transition:var(--cc-sf-input-transition, all .2s ease);font-family:var(--cc-sf-font-family, inherit)}.field-input::placeholder{color:var(--cc-sf-input-placeholder, #9CA3AF)}.field-input:hover:not(:disabled):not([readonly]){border-color:var(--cc-sf-input-hover-border, #9CA3AF)}.field-input:focus{outline:none;border-color:var(--cc-sf-input-focus-border, #3B82F6);box-shadow:var(--cc-sf-input-focus-shadow, 0 0 0 3px rgba(59, 130, 246, .12))}.field-input:disabled,.field-input[readonly]{background-color:var(--cc-sf-input-disabled-bg, #F3F4F6);color:var(--cc-sf-input-disabled-color, #6B7280);cursor:not-allowed;border-color:var(--cc-sf-input-disabled-border, #E5E7EB)}.field-input.is-invalid{border-color:var(--cc-sf-error-border, #DC2626);background-color:var(--cc-sf-error-bg, #FEF2F2)}.field-input.is-invalid:focus{box-shadow:var(--cc-sf-error-focus-shadow, 0 0 0 3px rgba(220, 38, 38, .1))}.field-input.textarea{resize:vertical;min-height:100px;font-family:var(--cc-sf-font-family, inherit)}select.field-input{appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236B7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3E%3C/svg%3E\");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;cursor:pointer}select.field-input:disabled{cursor:not-allowed}.field-hint{font-size:var(--cc-sf-hint-size, .75rem);color:var(--cc-sf-hint-color, #6B7280)}.field-error{font-size:var(--cc-sf-error-text-size, .75rem);color:var(--cc-sf-error-text-color, #DC2626)}.radio-group,.checkbox-group{display:flex;flex-direction:column;gap:8px}.radio-label,.checkbox-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:var(--cc-sf-label-size, .875rem);color:var(--cc-sf-label-color, #111827)}.radio-label input,.checkbox-label input{cursor:pointer;accent-color:var(--cc-sf-chip-selected-bg, #3B82F6)}.checkbox-single .checkbox-label{font-weight:var(--cc-sf-label-weight, 500)}.chip-group{display:flex;flex-wrap:wrap;gap:8px}.chip-label{padding:var(--cc-sf-chip-padding, 6px 14px);background:var(--cc-sf-chip-bg, #ffffff);color:var(--cc-sf-chip-color, #374151);border:var(--cc-sf-chip-border, 1px solid #D1D5DB);border-radius:var(--cc-sf-chip-radius, 20px);cursor:pointer;font-size:var(--cc-sf-font-size-base, .875rem);transition:var(--cc-sf-input-transition, all .2s ease)}.chip-label:hover{background:var(--cc-sf-chip-hover-bg, #F3F4F6)}.chip-label.selected{background:var(--cc-sf-chip-selected-bg, #3B82F6);color:var(--cc-sf-chip-selected-color, #ffffff);border-color:var(--cc-sf-chip-selected-border, #3B82F6)}.switch-container{display:flex;justify-content:space-between;align-items:center;cursor:pointer}.switch{position:relative;width:50px;height:24px}.switch input{opacity:0;width:0;height:0}.switch input:checked+.slider{background-color:var(--cc-sf-switch-track-on, #3B82F6)}.switch input:checked+.slider:before{transform:translate(26px)}.switch .slider{position:absolute;cursor:pointer;inset:0;background-color:var(--cc-sf-switch-track-off, #D1D5DB);transition:.4s;border-radius:24px}.switch .slider:before{position:absolute;content:\"\";height:18px;width:18px;left:3px;bottom:3px;background-color:var(--cc-sf-switch-thumb, #ffffff);transition:.4s;border-radius:50%}.rating-group{display:flex;gap:4px}.rating-group .star{display:inline-flex;align-items:center;cursor:pointer;transition:var(--cc-sf-input-transition, all .2s ease)}.rating-group .star mat-icon{font-size:var(--cc-sf-star-size, 28px);width:var(--cc-sf-star-size, 28px);height:var(--cc-sf-star-size, 28px);line-height:var(--cc-sf-star-size, 28px);color:var(--cc-sf-star-empty, #D1D5DB);transition:var(--cc-sf-input-transition, all .2s ease)}.rating-group .star.filled mat-icon,.rating-group .star.half mat-icon{color:var(--cc-sf-star-filled, #F59E0B)}.rating-group .star:hover mat-icon{color:var(--cc-sf-star-filled, #F59E0B)}.password-wrapper{position:relative;display:flex;align-items:center}.password-wrapper .password-input{padding-right:2.75rem;width:100%}.password-wrapper .password-toggle{position:absolute;right:.625rem;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;padding:.25rem;line-height:1;color:var(--cc-sf-hint-color, #6B7280);display:flex;align-items:center;justify-content:center;transition:color var(--cc-sf-input-transition, .2s ease)}.password-wrapper .password-toggle mat-icon.eye-icon{font-size:1.125rem;width:1.125rem;height:1.125rem;line-height:1.125rem}.password-wrapper .password-toggle:hover{color:var(--cc-sf-label-color, #374151)}.password-wrapper .password-toggle:focus{outline:none}.generated-value{padding:var(--cc-sf-generated-padding, .625rem .875rem);background:var(--cc-sf-generated-bg, #F3F4F6);border:var(--cc-sf-generated-border, 1px solid #E5E7EB);border-radius:var(--cc-sf-generated-radius, 8px);font-size:var(--cc-sf-input-font-size, .875rem);color:var(--cc-sf-generated-color, #6B7280);font-family:var(--cc-sf-font-family, inherit)}.group-section-wrapper{margin-bottom:var(--cc-sf-section-gap, 20px);border:var(--cc-sf-section-border, 1px solid #E5E7EB);border-radius:var(--cc-sf-section-radius, 10px);padding:var(--cc-sf-section-padding, 20px);background-color:var(--cc-sf-section-bg, #ffffff);box-shadow:var(--cc-sf-section-shadow, 0 1px 4px rgba(0, 0, 0, .05))}.group-section-wrapper .group-label{font-size:var(--cc-sf-section-label-size, 1rem);font-weight:var(--cc-sf-section-label-weight, 600);color:var(--cc-sf-section-label-color, #1F2937);margin:0 0 16px;padding-bottom:10px;border-bottom:var(--cc-sf-section-label-border, 2px solid #E5E7EB)}.group-section-wrapper .group-fields.sf-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--cc-sf-grid-gap, 16px);align-items:start}@media(max-width:640px){.group-section-wrapper .group-fields.sf-grid{grid-template-columns:1fr}.group-section-wrapper .group-fields.sf-grid .sf-col{grid-column:span 12!important}}.group-section-wrapper .group-instance{position:relative;margin-bottom:16px;padding:var(--cc-sf-instance-padding, 16px);background:var(--cc-sf-instance-bg, #F9FAFB);border:var(--cc-sf-instance-border, 1px solid #E5E7EB);border-radius:var(--cc-sf-instance-radius, 8px)}.group-section-wrapper .group-instance:last-child{margin-bottom:0}.group-section-wrapper .group-instance .group-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;padding-bottom:10px;border-bottom:var(--cc-sf-instance-divider, 1px dashed #D1D5DB)}.group-section-wrapper .group-instance .group-header .group-number{font-weight:500;color:var(--cc-sf-instance-num-color, #4B5563);font-size:var(--cc-sf-instance-num-size, .8125rem)}.btn-remove{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;background:var(--cc-sf-btn-remove-bg, #FFF5F5);color:var(--cc-sf-btn-remove-color, #E53E3E);border:var(--cc-sf-btn-remove-border, 1px solid #FED7D7);border-radius:var(--cc-sf-btn-remove-radius, 4px);cursor:pointer;font-size:var(--cc-sf-error-text-size, .75rem);transition:var(--cc-sf-btn-transition, all .2s ease)}.btn-remove mat-icon{font-size:1rem;width:1rem;height:1rem;line-height:1rem}.btn-remove:hover{background:var(--cc-sf-btn-remove-hover-bg, #FED7D7)}.btn-add-group{display:flex;align-items:center;justify-content:center;gap:4px;width:100%;padding:8px 16px;margin-top:12px;background:var(--cc-sf-btn-add-bg, transparent);color:var(--cc-sf-btn-add-color, #3B82F6);border:var(--cc-sf-btn-add-border, 1px dashed #CBD5E1);border-radius:var(--cc-sf-btn-add-radius, 6px);cursor:pointer;font-size:var(--cc-sf-btn-font-size, .875rem);font-weight:var(--cc-sf-btn-font-weight, 600);transition:var(--cc-sf-btn-transition, all .2s ease);font-family:var(--cc-sf-font-family, inherit)}.btn-add-group mat-icon{font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem}.btn-add-group:hover{background:var(--cc-sf-btn-add-hover-bg, #EFF6FF);border-color:var(--cc-sf-btn-add-hover-border, #BFDBFE)}.upload-drop-zone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:32px 24px;border:var(--cc-sf-dropzone-border, 1.5px dashed #CBD5E1);border-radius:var(--cc-sf-dropzone-radius, 12px);background-color:var(--cc-sf-dropzone-bg, #F8FAFC);cursor:pointer;transition:background-color .2s ease,border-color .2s ease;text-align:center;-webkit-user-select:none;user-select:none}.upload-drop-zone:hover{background-color:var(--cc-sf-dropzone-hover-bg, #EFF6FF);border-color:var(--cc-sf-dropzone-hover-border, #93C5FD)}.upload-drop-zone.drag-over{background-color:var(--cc-sf-dropzone-hover-bg, #EFF6FF);border-color:var(--cc-sf-dropzone-over-border, #3B82F6);box-shadow:var(--cc-sf-dropzone-over-shadow, 0 0 0 4px rgba(59, 130, 246, .12))}.upload-drop-zone.is-invalid{border-color:var(--cc-sf-error-border, #DC2626);background-color:var(--cc-sf-error-bg, #FEF2F2)}.upload-icon-wrap{margin-bottom:4px;color:var(--cc-sf-dropzone-icon-color, #94A3B8)}.upload-icon-wrap mat-icon.upload-cloud-icon{font-size:56px;width:56px;height:56px;line-height:56px;color:var(--cc-sf-dropzone-icon-color, #94A3B8)}.upload-main-text{font-size:.9rem;font-weight:600;color:var(--cc-sf-label-color, #1E293B);margin:0}.upload-main-text .upload-link{color:var(--cc-sf-dropzone-link-color, #3B82F6)}.upload-hint-text{font-size:.78rem;color:var(--cc-sf-dropzone-hint-color, #64748B);margin:0}.upload-hint-text .upload-formats{color:var(--cc-sf-dropzone-link-color, #3B82F6);font-weight:500}.uploaded-list{display:flex;flex-direction:column;gap:8px;margin-top:10px}.uploaded-item{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--cc-sf-uploaded-item-bg, #ffffff);border:var(--cc-sf-uploaded-item-border, 1px solid #E2E8F0);border-radius:var(--cc-sf-uploaded-item-radius, 8px);transition:box-shadow .15s ease}.uploaded-item:hover{box-shadow:0 2px 6px #0000000f}.uploaded-item mat-icon.file-type-icon{font-size:20px;width:20px;height:20px;line-height:20px;flex-shrink:0;color:var(--cc-sf-hint-color, #64748B)}.uploaded-item .file-thumb{width:36px;height:36px;object-fit:cover;border-radius:4px;flex-shrink:0}.uploaded-item .file-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.uploaded-item .file-info .file-name{font-size:.85rem;font-weight:500;color:var(--cc-sf-label-color, #1E293B);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.uploaded-item .file-info .file-size{font-size:.72rem;color:var(--cc-sf-hint-color, #94A3B8)}.uploaded-item .file-remove-btn{display:flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;color:var(--cc-sf-uploaded-remove-color, #94A3B8);padding:4px;border-radius:4px;flex-shrink:0;transition:color .15s ease,background .15s ease}.uploaded-item .file-remove-btn mat-icon{font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem}.uploaded-item .file-remove-btn:hover{color:var(--cc-sf-uploaded-remove-hover-color, #DC2626);background:var(--cc-sf-uploaded-remove-hover-bg, #FEF2F2)}.input-group{position:relative;display:flex;align-items:stretch;width:100%}.input-group .field-input{flex:1;border-radius:0}.input-group .field-input:first-child{border-top-left-radius:var(--cc-sf-input-radius, 8px);border-bottom-left-radius:var(--cc-sf-input-radius, 8px)}.input-group .field-input:last-child{border-top-right-radius:var(--cc-sf-input-radius, 8px);border-bottom-right-radius:var(--cc-sf-input-radius, 8px)}.input-group.readonly .field-input{background-color:var(--cc-sf-input-disabled-bg, #F3F4F6);cursor:default;padding-right:3.5rem}.input-group.readonly .input-prefix,.input-group.readonly .input-suffix{background-color:var(--cc-sf-input-disabled-bg, #F3F4F6)}.input-prefix,.input-suffix{display:flex;align-items:center;padding:0 .875rem;background-color:var(--cc-sf-input-bg, #FFFFFF);border:var(--cc-sf-input-border, 1.5px solid #D1D5DB);color:var(--cc-sf-hint-color, #6B7280);font-size:var(--cc-sf-input-font-size, .875rem);white-space:nowrap;-webkit-user-select:none;user-select:none}.input-prefix{border-right:none;border-top-left-radius:var(--cc-sf-input-radius, 8px);border-bottom-left-radius:var(--cc-sf-input-radius, 8px)}.input-suffix{border-left:none;border-top-right-radius:var(--cc-sf-input-radius, 8px);border-bottom-right-radius:var(--cc-sf-input-radius, 8px);color:var(--cc-sf-chip-selected-bg, #3B82F6);font-weight:500}.readonly-icons{position:absolute;right:.875rem;top:50%;transform:translateY(-50%);display:flex;gap:8px;pointer-events:none}.readonly-icons mat-icon.lock-icon{font-size:1rem;width:1rem;height:1rem;line-height:1rem;opacity:.5;color:var(--cc-sf-hint-color, #6B7280)}.subfields-group-wrapper{margin-bottom:var(--cc-sf-grid-gap, 16px)}.subfields-group-wrapper .group-label{display:block;font-size:var(--cc-sf-label-size, .875rem);font-weight:600;color:var(--cc-sf-label-color, #111827);margin-bottom:.75rem}.subfields-group-wrapper .group-label .required{color:var(--cc-sf-label-required-color, #DC2626);margin-left:.125rem}.subfields-group-wrapper .subfields-row{display:flex;align-items:flex-end;gap:12px;border-radius:var(--cc-sf-input-radius, 8px);transition:all .2s ease}.subfields-group-wrapper .subfields-row.is-invalid .subfield-item ::ng-deep .field-input{border-color:var(--cc-sf-error-border, #DC2626);background-color:var(--cc-sf-error-bg, #FEF2F2)}.subfields-group-wrapper .subfields-row .subfield-item{flex:1;min-width:0}.subfields-group-wrapper .subfields-row .subfield-item ::ng-deep .field-label{font-size:.75rem!important;margin-bottom:4px!important;font-weight:500!important;color:var(--cc-sf-hint-color, #6B7280)!important}.subfields-group-wrapper .subfields-row .subfield-separator{margin-bottom:24px;font-weight:700;color:#94a3b8}.subfields-group-wrapper .subfields-group-error{display:block;margin-top:6px;font-size:var(--cc-sf-error-text-size, .75rem);color:var(--cc-sf-error-text-color, #DC2626)}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$2.SelectMultipleControlValueAccessor, selector: "select[multiple][formControlName],select[multiple][formControl],select[multiple][ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$2.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$2.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: ButtonComponent, selector: "lib-button", inputs: ["variant", "type", "disabled", "width", "height", "borderRadius", "fontSize", "fontWeight", "backgroundColor", "color", "border", "icon", "labels"] }, { kind: "component", type: FormFieldComponent, selector: "lib-form-field", inputs: ["config", "controller", "formGroup", "allowMulti"] }] });
4063
4383
  }
4064
4384
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FormFieldComponent, decorators: [{
4065
4385
  type: Component,
4066
- args: [{ selector: 'lib-form-field', standalone: false, template: "<div class=\"form-field\" *ngIf=\"isVisible\" [class.has-error]=\"errorMessage\">\n <!-- ROW Layout -->\n <div *ngIf=\"isRow\" class=\"form-row\" [class.horizontal]=\"config.subType === 'HORIZONTAL'\">\n <ng-container *ngFor=\"let child of config.children\">\n <div class=\"row-field\">\n <lib-form-field\n [config]=\"child\"\n [controller]=\"controller\"\n [sectionIndex]=\"sectionIndex\">\n </lib-form-field>\n </div>\n </ng-container>\n </div>\n\n <!-- GROUP (Section) -->\n <div *ngIf=\"isGroup && config.sectionConfig\" class=\"form-group\">\n <ng-container *ngFor=\"let field of config.sectionConfig.children\">\n <lib-form-field\n [config]=\"field\"\n [controller]=\"controller\"\n [sectionIndex]=\"sectionIndex\">\n </lib-form-field>\n </ng-container>\n </div>\n\n <!-- Text Input -->\n <div *ngIf=\"isTextField\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <textarea\n *ngIf=\"config.subType === 'LONG'\"\n class=\"field-input textarea\"\n [placeholder]=\"config.hint || ''\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n (input)=\"onValueChange($event)\"\n rows=\"4\">\n </textarea>\n \n <input\n *ngIf=\"config.subType !== 'LONG'\"\n [type]=\"config.subType === 'EMAIL' ? 'email' : config.subType === 'PHONE' ? 'tel' : 'text'\"\n class=\"field-input\"\n [placeholder]=\"config.hint || ''\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n (input)=\"onValueChange($event)\">\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Number Input -->\n <div *ngIf=\"isNumberField\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <input\n type=\"number\"\n class=\"field-input\"\n [placeholder]=\"config.hint || ''\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n [min]=\"config.numberConfig?.min\"\n [max]=\"config.numberConfig?.max\"\n [step]=\"config.numberConfig?.step || 1\"\n (input)=\"onValueChange($event)\">\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Date Input -->\n <div *ngIf=\"isDateField\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <input\n type=\"date\"\n class=\"field-input\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n [min]=\"config.dateConfig?.minDate\"\n [max]=\"config.dateConfig?.maxDate\"\n (input)=\"onValueChange($event)\">\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Dropdown -->\n <div *ngIf=\"isDropdown\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <select\n *ngIf=\"config.subType === 'SINGLE'\"\n class=\"field-input\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange($event)\">\n <option value=\"\">Select...</option>\n <option *ngFor=\"let option of config.optionConfig?.optionList\" [value]=\"option.code\">\n {{ option.label }}\n </option>\n </select>\n \n <select\n *ngIf=\"config.subType === 'MULTIPLE'\"\n class=\"field-input\"\n multiple\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange($event)\">\n <option *ngFor=\"let option of config.optionConfig?.optionList\" [value]=\"option.code\">\n {{ option.label }}\n </option>\n </select>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Radio -->\n <div *ngIf=\"isRadio\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <div class=\"radio-group\">\n <label *ngFor=\"let option of config.optionConfig?.optionList\" class=\"radio-label\">\n <input\n type=\"radio\"\n [name]=\"getFieldName()\"\n [value]=\"option.code\"\n [checked]=\"value === option.code\"\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange(option.code)\">\n <span>{{ option.label }}</span>\n </label>\n </div>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Checkbox -->\n <div *ngIf=\"isCheckbox\" class=\"field-wrapper\">\n <label *ngIf=\"config.label && config.subType === 'LIST'\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <div *ngIf=\"config.subType === 'BOOL'\" class=\"checkbox-single\">\n <label class=\"checkbox-label\">\n <input\n type=\"checkbox\"\n [checked]=\"value === true\"\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange($event)\">\n <span>{{ config.label }}</span>\n </label>\n </div>\n \n <div *ngIf=\"config.subType === 'LIST'\" class=\"checkbox-group\">\n <label *ngFor=\"let option of config.optionConfig?.optionList\" class=\"checkbox-label\">\n <input\n type=\"checkbox\"\n [checked]=\"isChecked(option.code)\"\n [disabled]=\"config.disabled\"\n (change)=\"onCheckboxListChange(option.code, $any($event.target).checked)\">\n <span>{{ option.label }}</span>\n </label>\n </div>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Chip -->\n <div *ngIf=\"isChip\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <div class=\"chip-group\">\n <label *ngFor=\"let option of config.optionConfig?.optionList\" \n class=\"chip-label\"\n [class.selected]=\"isChecked(option.code)\">\n <input\n type=\"checkbox\"\n [checked]=\"isChecked(option.code)\"\n [disabled]=\"config.disabled\"\n (change)=\"onCheckboxListChange(option.code, $any($event.target).checked)\"\n style=\"display: none;\">\n <span>{{ option.label }}</span>\n </label>\n </div>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Switch -->\n <div *ngIf=\"isSwitch\" class=\"field-wrapper\">\n <label class=\"switch-container\">\n <span class=\"field-label\">{{ config.label }}</span>\n <div class=\"switch\">\n <input\n type=\"checkbox\"\n [checked]=\"value === true\"\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange($event)\">\n <span class=\"slider\"></span>\n </div>\n </label>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Rating -->\n <div *ngIf=\"isRating\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <div class=\"rating-group\">\n <span *ngFor=\"let star of getStarArray()\" \n class=\"star\"\n [class.filled]=\"isStarFilled(star)\"\n [class.half]=\"isStarHalf(star)\"\n (click)=\"onRatingChange(star, $event)\">\n \u2605\n </span>\n </div>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Generated Field (Read-only) -->\n <div *ngIf=\"isGenerated\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">{{ config.label }}</label>\n <div class=\"generated-value\">{{ value || '-' }}</div>\n <span class=\"field-hint\" *ngIf=\"config.hint\">{{ config.hint }}</span>\n </div>\n</div>\n", styles: [".form-field{margin-bottom:16px}.form-field.has-error .field-input{border-color:#f44336}.form-row{display:flex;gap:16px}.form-row.horizontal{flex-direction:row}.form-row.horizontal>*{flex:1}.form-row:not(.horizontal){flex-direction:column}.field-wrapper{display:flex;flex-direction:column;gap:6px}.field-label{font-size:14px;font-weight:500;color:#333}.field-label .required{color:#f44336;margin-left:4px}.field-input{padding:10px 12px;border:1px solid #ddd;border-radius:4px;font-size:14px;transition:border-color .3s}.field-input:focus{outline:none;border-color:#2196f3}.field-input:disabled{background:#f5f5f5;cursor:not-allowed}.field-input.textarea{resize:vertical;font-family:inherit}.field-hint{font-size:12px;color:#666}.field-error{font-size:12px;color:#f44336}.radio-group,.checkbox-group{display:flex;flex-direction:column;gap:8px}.radio-label,.checkbox-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px}.radio-label input,.checkbox-label input{cursor:pointer}.checkbox-single .checkbox-label{font-weight:500}.chip-group{display:flex;flex-wrap:wrap;gap:8px}.chip-label{padding:8px 16px;border:1px solid #ddd;border-radius:20px;cursor:pointer;font-size:14px;transition:all .3s}.chip-label:hover{background:#f5f5f5}.chip-label.selected{background:#2196f3;color:#fff;border-color:#2196f3}.switch-container{display:flex;justify-content:space-between;align-items:center;cursor:pointer}.switch{position:relative;width:50px;height:24px}.switch input{opacity:0;width:0;height:0}.switch input:checked+.slider{background-color:#2196f3}.switch input:checked+.slider:before{transform:translate(26px)}.switch .slider{position:absolute;cursor:pointer;inset:0;background-color:#ccc;transition:.4s;border-radius:24px}.switch .slider:before{position:absolute;content:\"\";height:18px;width:18px;left:3px;bottom:3px;background-color:#fff;transition:.4s;border-radius:50%}.rating-group{display:flex;gap:4px}.rating-group .star{font-size:28px;display:inline-block;background:#ddd;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.rating-group .star.filled{background:#ffc107;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.rating-group .star.half{background:linear-gradient(90deg,#ffc107 50%,#ddd 50%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.rating-group .star:hover{background:#ffc107;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.generated-value{padding:10px 12px;background:#f5f5f5;border:1px solid #ddd;border-radius:4px;font-size:14px;color:#666}select[multiple]{min-height:120px}\n"] }]
4067
- }], ctorParameters: () => [{ type: ExpressionService }, { type: i3.HttpClient }], propDecorators: { config: [{
4386
+ args: [{ selector: 'lib-form-field', standalone: false, template: "<div class=\"form-field\" *ngIf=\"isVisible\" [class.has-error]=\"errorMessage\">\r\n\r\n <!-- \u2550\u2550 ROW Layout \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isRow\" class=\"form-row grid-row\">\r\n <ng-container *ngFor=\"let child of config.children\">\r\n <div class=\"row-field\" [style.gridColumn]=\"'span ' + getChildColSpan(child)\">\r\n <lib-form-field [config]=\"child\" [controller]=\"controller\" [formGroup]=\"formGroup\" [allowMulti]=\"allowMulti\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- \u2550\u2550 GROUP \u2014 allowMulti (repeater) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isGroup && config.sectionConfig?.allowMulti\" class=\"group-section-wrapper\">\r\n <h3 class=\"group-label\" *ngIf=\"config.sectionConfig?.label\">{{ config.sectionConfig!.label }}</h3>\r\n\r\n <div *ngFor=\"let instance of instanceList; trackBy: trackByInstanceId; let i = index\" class=\"group-instance\">\r\n <!-- Instance header \u2014 show remove only when more than 1 instance -->\r\n <div class=\"group-header\" *ngIf=\"instanceList.length > 1\">\r\n <span class=\"group-number\">{{ config.sectionConfig!.label }} #{{ i + 1 }}</span>\r\n <lib-button [variant]=\"'danger-outline'\" [icon]=\"{type: 'material', value: 'delete_outline'}\"\r\n (click)=\"removeGroupInstance(i)\">\r\n {{ removeLabel }}\r\n </lib-button>\r\n </div>\r\n\r\n <!-- Fields \u2014 each child receives the *instance* FormGroup and the group's allowMulti config -->\r\n <div class=\"group-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.sectionConfig!.children\">\r\n <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"instance.fg\" [allowMulti]=\"true\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n <lib-button [variant]=\"'outline'\" [icon]=\"{type: 'material', value: 'add'}\" (click)=\"addGroupInstance()\"\r\n class=\"btn-add-group-wrapper\">\r\n {{ addLabel }} {{ config.sectionConfig!.label }}\r\n </lib-button>\r\n </div>\r\n\r\n <!-- \u2550\u2550 GROUP \u2014 single (non-repeater) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isGroup && config.sectionConfig && !config.sectionConfig.allowMulti\" class=\"group-section-wrapper\">\r\n <h3 class=\"group-label\" *ngIf=\"config.sectionConfig.label\">{{ config.sectionConfig.label }}</h3>\r\n <div class=\"group-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.sectionConfig.children\">\r\n <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"groupFormGroup\" [allowMulti]=\"false\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n\r\n <!-- \u2550\u2550 Text Input \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isTextField\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <textarea *ngIf=\"config.subType === 'LONG'\" class=\"field-input textarea\" [placeholder]=\"config.placeholder || ''\"\r\n [formControlName]=\"config.name!\" [class.is-invalid]=\"errorMessage\" rows=\"4\">\r\n </textarea>\r\n\r\n <!-- Password input with show/hide toggle -->\r\n <div *ngIf=\"config.subType === 'PASSWORD'\" class=\"password-wrapper\">\r\n <input [type]=\"showPassword ? 'text' : 'password'\" class=\"field-input password-input\"\r\n [placeholder]=\"config.placeholder || ''\" [formControlName]=\"config.name!\" [class.is-invalid]=\"errorMessage\">\r\n <button type=\"button\" class=\"password-toggle\" (click)=\"showPassword = !showPassword\" tabindex=\"-1\"\r\n [attr.aria-label]=\"showPassword ? 'Hide password' : 'Show password'\">\r\n <mat-icon class=\"eye-icon\">{{ showPassword ? 'visibility' : 'visibility_off' }}</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <div class=\"input-group\" [class.readonly]=\"config.readonly\">\r\n <span class=\"input-prefix\" *ngIf=\"config.prefix\">{{ config.prefix }}</span>\r\n\r\n <input *ngIf=\"config.subType !== 'LONG' && config.subType !== 'PASSWORD'\"\r\n [type]=\"config.subType === 'EMAIL' ? 'email' : config.subType === 'PHONE' ? 'tel' : 'text'\" class=\"field-input\"\r\n [placeholder]=\"config.placeholder || ''\" [formControlName]=\"config.name!\" [class.is-invalid]=\"errorMessage\"\r\n [readonly]=\"config.readonly\">\r\n\r\n <span class=\"input-suffix\" *ngIf=\"config.suffix\">{{ config.suffix }}</span>\r\n\r\n <div class=\"readonly-icons\" *ngIf=\"config.readonly\">\r\n <mat-icon class=\"lock-icon\">lock</mat-icon>\r\n </div>\r\n </div>\r\n\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Number Input \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isNumberField\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"input-group\" [class.readonly]=\"config.readonly\">\r\n <span class=\"input-prefix\" *ngIf=\"config.prefix\">{{ config.prefix }}</span>\r\n\r\n <input type=\"number\" class=\"field-input\" [placeholder]=\"config.placeholder || ''\" [formControlName]=\"config.name!\"\r\n [min]=\"config.numberConfig?.min\" [max]=\"config.numberConfig?.max\" [step]=\"config.numberConfig?.step || 1\"\r\n [class.is-invalid]=\"errorMessage\" [readonly]=\"config.readonly\">\r\n\r\n <span class=\"input-suffix\" *ngIf=\"config.suffix\">{{ config.suffix }}</span>\r\n\r\n <div class=\"readonly-icons\" *ngIf=\"config.readonly\">\r\n <mat-icon class=\"lock-icon\">lock</mat-icon>\r\n </div>\r\n </div>\r\n\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Date Input \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isDateField\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <input type=\"date\" class=\"field-input\" [formControlName]=\"config.name!\" [min]=\"config.dateConfig?.minDate\"\r\n [max]=\"config.dateConfig?.maxDate\" [class.is-invalid]=\"errorMessage\">\r\n\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Dropdown \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isDropdown\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <select *ngIf=\"config.subType === 'SINGLE'\" class=\"field-input\" [formControlName]=\"config.name!\"\r\n [class.is-invalid]=\"errorMessage\">\r\n <option [ngValue]=\"null\" disabled selected>{{ config.placeholder || 'Select' }}</option>\r\n <option *ngFor=\"let option of config.optionConfig?.optionList\" [value]=\"option.code\">\r\n {{ option.label }}\r\n </option>\r\n </select>\r\n\r\n <select *ngIf=\"config.subType === 'MULTIPLE'\" class=\"field-input\" multiple [formControlName]=\"config.name!\"\r\n [class.is-invalid]=\"errorMessage\">\r\n <option *ngFor=\"let option of config.optionConfig?.optionList\" [value]=\"option.code\">\r\n {{ option.label }}\r\n </option>\r\n </select>\r\n\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Radio \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isRadio\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"radio-group\" [class.is-invalid]=\"errorMessage\">\r\n <label *ngFor=\"let option of config.optionConfig?.optionList\" class=\"radio-label\">\r\n <input type=\"radio\" [formControlName]=\"config.name!\" [value]=\"option.code\">\r\n <span>{{ option.label }}</span>\r\n </label>\r\n </div>\r\n\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Checkbox \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isCheckbox\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label && config.subType === 'LIST'\" class=\"field-label\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div *ngIf=\"config.subType === 'BOOL'\" class=\"checkbox-single\">\r\n <label class=\"checkbox-label\">\r\n <input type=\"checkbox\" [formControlName]=\"config.name!\" [class.is-invalid]=\"errorMessage\">\r\n <span>{{ config.label }}</span>\r\n </label>\r\n </div>\r\n\r\n <div *ngIf=\"config.subType === 'LIST'\" class=\"checkbox-group\" [class.is-invalid]=\"errorMessage\">\r\n <label *ngFor=\"let option of config.optionConfig?.optionList\" class=\"checkbox-label\">\r\n <input type=\"checkbox\" [checked]=\"isChecked(option.code)\" [disabled]=\"!!config.disabled\"\r\n (change)=\"onCheckboxListChange(option.code, $any($event.target).checked)\">\r\n <span>{{ option.label }}</span>\r\n </label>\r\n </div>\r\n\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Chip \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isChip\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"chip-group\" [class.is-invalid]=\"errorMessage\">\r\n <label *ngFor=\"let option of config.optionConfig?.optionList\" class=\"chip-label\"\r\n [class.selected]=\"isChecked(option.code)\">\r\n <input type=\"checkbox\" [checked]=\"isChecked(option.code)\" [disabled]=\"!!config.disabled\"\r\n (change)=\"onCheckboxListChange(option.code, $any($event.target).checked)\" style=\"display: none;\">\r\n <span>{{ option.label }}</span>\r\n </label>\r\n </div>\r\n\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Switch \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isSwitch\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label class=\"switch-container\">\r\n <span class=\"field-label\">{{ config.label }}</span>\r\n <div class=\"switch\">\r\n <input type=\"checkbox\" [formControlName]=\"config.name!\" [class.is-invalid]=\"errorMessage\">\r\n <span class=\"slider\"></span>\r\n </div>\r\n </label>\r\n\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Rating \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isRating\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"rating-group\" [class.is-invalid]=\"errorMessage\">\r\n <span *ngFor=\"let star of getStarArray()\" class=\"star\" [class.filled]=\"isStarFilled(star)\"\r\n [class.half]=\"isStarHalf(star)\" (click)=\"onRatingChange(star, $event)\">\r\n <mat-icon>{{ isStarFilled(star) || isStarHalf(star) ? 'star' : 'star_border' }}</mat-icon>\r\n </span>\r\n </div>\r\n\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Generated Field (read-only) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isGenerated\" class=\"field-wrapper\">\r\n <label *ngIf=\"config.label\" class=\"field-label\">{{ config.label }}</label>\r\n <div class=\"generated-value\">{{ value || '-' }}</div>\r\n <span class=\"field-hint\" *ngIf=\"config.hint\">{{ config.hint }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 File Upload \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isFileUpload\" class=\"field-wrapper\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <!-- Drop Zone -->\r\n <div class=\"upload-drop-zone\" [class.drag-over]=\"isDragOver\" [class.has-files]=\"value?.length\"\r\n [class.is-invalid]=\"errorMessage\" (dragover)=\"onDragOver($event)\" (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onFileDrop($event)\" (click)=\"fileInput.click()\">\r\n\r\n <div class=\"upload-icon-wrap\">\r\n <mat-icon class=\"upload-cloud-icon\">cloud_upload</mat-icon>\r\n </div>\r\n\r\n <p class=\"upload-main-text\">Drag and drop files here or <span class=\"upload-link\">click to upload</span></p>\r\n <p class=\"upload-hint-text\" *ngIf=\"config.attachmentConfig?.acceptLabel\">\r\n Supported formats:\r\n <span class=\"upload-formats\">{{ config.attachmentConfig!.acceptLabel }}</span>\r\n </p>\r\n <p class=\"upload-hint-text\" *ngIf=\"!config.attachmentConfig?.acceptLabel && config.hint\">\r\n {{ config.hint }}\r\n </p>\r\n\r\n <!-- Hidden native file input -->\r\n <input #fileInput type=\"file\" style=\"display:none\"\r\n [attr.multiple]=\"config.attachmentConfig?.multiple ? true : null\"\r\n [attr.accept]=\"config.attachmentConfig?.accept || null\" (change)=\"onFileSelected($event)\">\r\n </div>\r\n\r\n <!-- Uploaded file list -->\r\n <div class=\"uploaded-list\" *ngIf=\"value?.length\">\r\n <div *ngFor=\"let f of value; let i = index\" class=\"uploaded-item\">\r\n <!-- File type icon -->\r\n <mat-icon class=\"file-type-icon\">{{ getFileIcon(f.type) }}</mat-icon>\r\n\r\n <!-- Image thumbnail (only for images) -->\r\n <img *ngIf=\"f.type?.startsWith('image') && f.dataUrl\" [src]=\"f.dataUrl\" class=\"file-thumb\" alt=\"preview\">\r\n\r\n <!-- Name & size -->\r\n <div class=\"file-info\">\r\n <span class=\"file-name\" [title]=\"f.name\">{{ f.name }}</span>\r\n <span class=\"file-size\">{{ formatFileSize(f.size) }}</span>\r\n </div>\r\n <lib-button [variant]=\"'danger-outline'\" (click)=\"removeUploadedFile(i)\">\r\n <mat-icon>close</mat-icon>\r\n </lib-button>\r\n </div>\r\n </div>\r\n\r\n <!-- Validation / file errors -->\r\n <span class=\"field-error\" *ngIf=\"fileUploadError\">{{ fileUploadError }}</span>\r\n <span class=\"field-error\" *ngIf=\"errorMessage && !fileUploadError\">{{ errorMessage }}</span>\r\n <span class=\"field-hint\"\r\n *ngIf=\"config.hint && !errorMessage && !fileUploadError && !config.attachmentConfig?.acceptLabel\">\r\n {{ config.hint }}\r\n </span>\r\n </div>\r\n\r\n</div>", styles: [".form-field{margin-bottom:var(--cc-sf-grid-gap, 16px)}.form-field.has-error .field-input{border-color:var(--cc-sf-error-border, #DC2626)}.form-row{display:flex;gap:var(--cc-sf-grid-gap, 16px)}.form-row.horizontal{flex-direction:row}.form-row.horizontal>*{flex:1}.form-row:not(.horizontal){flex-direction:column}.form-row.grid-row{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--cc-sf-grid-gap, 16px);align-items:start}@media(max-width:640px){.form-row.grid-row{grid-template-columns:1fr}.form-row.grid-row .row-field{grid-column:span 12!important}}.field-wrapper{display:flex;flex-direction:column;gap:6px}.field-label{display:block;font-size:var(--cc-sf-label-size, .875rem);font-weight:var(--cc-sf-label-weight, 500);color:var(--cc-sf-label-color, #111827);margin-bottom:.5rem;line-height:1.25rem}.field-label .required{color:var(--cc-sf-label-required-color, #DC2626);margin-left:.125rem}.field-input{width:100%;padding:var(--cc-sf-input-padding, .625rem .875rem);font-size:var(--cc-sf-input-font-size, .875rem);line-height:1.5;color:var(--cc-sf-input-color, #111827);background-color:var(--cc-sf-input-bg, #ffffff);border:var(--cc-sf-input-border, 1.5px solid #D1D5DB);border-radius:var(--cc-sf-input-radius, 8px);box-shadow:var(--cc-sf-input-shadow, none);transition:var(--cc-sf-input-transition, all .2s ease);font-family:var(--cc-sf-font-family, inherit)}.field-input::placeholder{color:var(--cc-sf-input-placeholder, #9CA3AF)}.field-input:hover:not(:disabled):not([readonly]){border-color:var(--cc-sf-input-hover-border, #9CA3AF)}.field-input:focus{outline:none;border-color:var(--cc-sf-input-focus-border, #3B82F6);box-shadow:var(--cc-sf-input-focus-shadow, 0 0 0 3px rgba(59, 130, 246, .12))}.field-input:disabled,.field-input[readonly]{background-color:var(--cc-sf-input-disabled-bg, #F3F4F6);color:var(--cc-sf-input-disabled-color, #6B7280);cursor:not-allowed;border-color:var(--cc-sf-input-disabled-border, #E5E7EB)}.field-input.is-invalid{border-color:var(--cc-sf-error-border, #DC2626);background-color:var(--cc-sf-error-bg, #FEF2F2)}.field-input.is-invalid:focus{box-shadow:var(--cc-sf-error-focus-shadow, 0 0 0 3px rgba(220, 38, 38, .1))}.field-input.textarea{resize:vertical;min-height:100px;font-family:var(--cc-sf-font-family, inherit)}select.field-input{appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236B7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3E%3C/svg%3E\");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;cursor:pointer}select.field-input:disabled{cursor:not-allowed}.field-hint{font-size:var(--cc-sf-hint-size, .75rem);color:var(--cc-sf-hint-color, #6B7280)}.field-error{font-size:var(--cc-sf-error-text-size, .75rem);color:var(--cc-sf-error-text-color, #DC2626)}.radio-group,.checkbox-group{display:flex;flex-direction:column;gap:8px}.radio-label,.checkbox-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:var(--cc-sf-label-size, .875rem);color:var(--cc-sf-label-color, #111827)}.radio-label input,.checkbox-label input{cursor:pointer;accent-color:var(--cc-sf-chip-selected-bg, #3B82F6)}.checkbox-single .checkbox-label{font-weight:var(--cc-sf-label-weight, 500)}.chip-group{display:flex;flex-wrap:wrap;gap:8px}.chip-label{padding:var(--cc-sf-chip-padding, 6px 14px);background:var(--cc-sf-chip-bg, #ffffff);color:var(--cc-sf-chip-color, #374151);border:var(--cc-sf-chip-border, 1px solid #D1D5DB);border-radius:var(--cc-sf-chip-radius, 20px);cursor:pointer;font-size:var(--cc-sf-font-size-base, .875rem);transition:var(--cc-sf-input-transition, all .2s ease)}.chip-label:hover{background:var(--cc-sf-chip-hover-bg, #F3F4F6)}.chip-label.selected{background:var(--cc-sf-chip-selected-bg, #3B82F6);color:var(--cc-sf-chip-selected-color, #ffffff);border-color:var(--cc-sf-chip-selected-border, #3B82F6)}.switch-container{display:flex;justify-content:space-between;align-items:center;cursor:pointer}.switch{position:relative;width:50px;height:24px}.switch input{opacity:0;width:0;height:0}.switch input:checked+.slider{background-color:var(--cc-sf-switch-track-on, #3B82F6)}.switch input:checked+.slider:before{transform:translate(26px)}.switch .slider{position:absolute;cursor:pointer;inset:0;background-color:var(--cc-sf-switch-track-off, #D1D5DB);transition:.4s;border-radius:24px}.switch .slider:before{position:absolute;content:\"\";height:18px;width:18px;left:3px;bottom:3px;background-color:var(--cc-sf-switch-thumb, #ffffff);transition:.4s;border-radius:50%}.rating-group{display:flex;gap:4px}.rating-group .star{display:inline-flex;align-items:center;cursor:pointer;transition:var(--cc-sf-input-transition, all .2s ease)}.rating-group .star mat-icon{font-size:var(--cc-sf-star-size, 28px);width:var(--cc-sf-star-size, 28px);height:var(--cc-sf-star-size, 28px);line-height:var(--cc-sf-star-size, 28px);color:var(--cc-sf-star-empty, #D1D5DB);transition:var(--cc-sf-input-transition, all .2s ease)}.rating-group .star.filled mat-icon,.rating-group .star.half mat-icon{color:var(--cc-sf-star-filled, #F59E0B)}.rating-group .star:hover mat-icon{color:var(--cc-sf-star-filled, #F59E0B)}.password-wrapper{position:relative;display:flex;align-items:center}.password-wrapper .password-input{padding-right:2.75rem;width:100%}.password-wrapper .password-toggle{position:absolute;right:.625rem;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;padding:.25rem;line-height:1;color:var(--cc-sf-hint-color, #6B7280);display:flex;align-items:center;justify-content:center;transition:color var(--cc-sf-input-transition, .2s ease)}.password-wrapper .password-toggle mat-icon.eye-icon{font-size:1.125rem;width:1.125rem;height:1.125rem;line-height:1.125rem}.password-wrapper .password-toggle:hover{color:var(--cc-sf-label-color, #374151)}.password-wrapper .password-toggle:focus{outline:none}.generated-value{padding:var(--cc-sf-generated-padding, .625rem .875rem);background:var(--cc-sf-generated-bg, #F3F4F6);border:var(--cc-sf-generated-border, 1px solid #E5E7EB);border-radius:var(--cc-sf-generated-radius, 8px);font-size:var(--cc-sf-input-font-size, .875rem);color:var(--cc-sf-generated-color, #6B7280);font-family:var(--cc-sf-font-family, inherit)}.group-section-wrapper{margin-bottom:var(--cc-sf-section-gap, 20px);border:var(--cc-sf-section-border, 1px solid #E5E7EB);border-radius:var(--cc-sf-section-radius, 10px);padding:var(--cc-sf-section-padding, 20px);background-color:var(--cc-sf-section-bg, #ffffff);box-shadow:var(--cc-sf-section-shadow, 0 1px 4px rgba(0, 0, 0, .05))}.group-section-wrapper .group-label{font-size:var(--cc-sf-section-label-size, 1rem);font-weight:var(--cc-sf-section-label-weight, 600);color:var(--cc-sf-section-label-color, #1F2937);margin:0 0 16px;padding-bottom:10px;border-bottom:var(--cc-sf-section-label-border, 2px solid #E5E7EB)}.group-section-wrapper .group-fields.sf-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--cc-sf-grid-gap, 16px);align-items:start}@media(max-width:640px){.group-section-wrapper .group-fields.sf-grid{grid-template-columns:1fr}.group-section-wrapper .group-fields.sf-grid .sf-col{grid-column:span 12!important}}.group-section-wrapper .group-instance{position:relative;margin-bottom:16px;padding:var(--cc-sf-instance-padding, 16px);background:var(--cc-sf-instance-bg, #F9FAFB);border:var(--cc-sf-instance-border, 1px solid #E5E7EB);border-radius:var(--cc-sf-instance-radius, 8px)}.group-section-wrapper .group-instance:last-child{margin-bottom:0}.group-section-wrapper .group-instance .group-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;padding-bottom:10px;border-bottom:var(--cc-sf-instance-divider, 1px dashed #D1D5DB)}.group-section-wrapper .group-instance .group-header .group-number{font-weight:500;color:var(--cc-sf-instance-num-color, #4B5563);font-size:var(--cc-sf-instance-num-size, .8125rem)}.btn-remove{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;background:var(--cc-sf-btn-remove-bg, #FFF5F5);color:var(--cc-sf-btn-remove-color, #E53E3E);border:var(--cc-sf-btn-remove-border, 1px solid #FED7D7);border-radius:var(--cc-sf-btn-remove-radius, 4px);cursor:pointer;font-size:var(--cc-sf-error-text-size, .75rem);transition:var(--cc-sf-btn-transition, all .2s ease)}.btn-remove mat-icon{font-size:1rem;width:1rem;height:1rem;line-height:1rem}.btn-remove:hover{background:var(--cc-sf-btn-remove-hover-bg, #FED7D7)}.btn-add-group{display:flex;align-items:center;justify-content:center;gap:4px;width:100%;padding:8px 16px;margin-top:12px;background:var(--cc-sf-btn-add-bg, transparent);color:var(--cc-sf-btn-add-color, #3B82F6);border:var(--cc-sf-btn-add-border, 1px dashed #CBD5E1);border-radius:var(--cc-sf-btn-add-radius, 6px);cursor:pointer;font-size:var(--cc-sf-btn-font-size, .875rem);font-weight:var(--cc-sf-btn-font-weight, 600);transition:var(--cc-sf-btn-transition, all .2s ease);font-family:var(--cc-sf-font-family, inherit)}.btn-add-group mat-icon{font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem}.btn-add-group:hover{background:var(--cc-sf-btn-add-hover-bg, #EFF6FF);border-color:var(--cc-sf-btn-add-hover-border, #BFDBFE)}.upload-drop-zone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:32px 24px;border:var(--cc-sf-dropzone-border, 1.5px dashed #CBD5E1);border-radius:var(--cc-sf-dropzone-radius, 12px);background-color:var(--cc-sf-dropzone-bg, #F8FAFC);cursor:pointer;transition:background-color .2s ease,border-color .2s ease;text-align:center;-webkit-user-select:none;user-select:none}.upload-drop-zone:hover{background-color:var(--cc-sf-dropzone-hover-bg, #EFF6FF);border-color:var(--cc-sf-dropzone-hover-border, #93C5FD)}.upload-drop-zone.drag-over{background-color:var(--cc-sf-dropzone-hover-bg, #EFF6FF);border-color:var(--cc-sf-dropzone-over-border, #3B82F6);box-shadow:var(--cc-sf-dropzone-over-shadow, 0 0 0 4px rgba(59, 130, 246, .12))}.upload-drop-zone.is-invalid{border-color:var(--cc-sf-error-border, #DC2626);background-color:var(--cc-sf-error-bg, #FEF2F2)}.upload-icon-wrap{margin-bottom:4px;color:var(--cc-sf-dropzone-icon-color, #94A3B8)}.upload-icon-wrap mat-icon.upload-cloud-icon{font-size:56px;width:56px;height:56px;line-height:56px;color:var(--cc-sf-dropzone-icon-color, #94A3B8)}.upload-main-text{font-size:.9rem;font-weight:600;color:var(--cc-sf-label-color, #1E293B);margin:0}.upload-main-text .upload-link{color:var(--cc-sf-dropzone-link-color, #3B82F6)}.upload-hint-text{font-size:.78rem;color:var(--cc-sf-dropzone-hint-color, #64748B);margin:0}.upload-hint-text .upload-formats{color:var(--cc-sf-dropzone-link-color, #3B82F6);font-weight:500}.uploaded-list{display:flex;flex-direction:column;gap:8px;margin-top:10px}.uploaded-item{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--cc-sf-uploaded-item-bg, #ffffff);border:var(--cc-sf-uploaded-item-border, 1px solid #E2E8F0);border-radius:var(--cc-sf-uploaded-item-radius, 8px);transition:box-shadow .15s ease}.uploaded-item:hover{box-shadow:0 2px 6px #0000000f}.uploaded-item mat-icon.file-type-icon{font-size:20px;width:20px;height:20px;line-height:20px;flex-shrink:0;color:var(--cc-sf-hint-color, #64748B)}.uploaded-item .file-thumb{width:36px;height:36px;object-fit:cover;border-radius:4px;flex-shrink:0}.uploaded-item .file-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.uploaded-item .file-info .file-name{font-size:.85rem;font-weight:500;color:var(--cc-sf-label-color, #1E293B);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.uploaded-item .file-info .file-size{font-size:.72rem;color:var(--cc-sf-hint-color, #94A3B8)}.uploaded-item .file-remove-btn{display:flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;color:var(--cc-sf-uploaded-remove-color, #94A3B8);padding:4px;border-radius:4px;flex-shrink:0;transition:color .15s ease,background .15s ease}.uploaded-item .file-remove-btn mat-icon{font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem}.uploaded-item .file-remove-btn:hover{color:var(--cc-sf-uploaded-remove-hover-color, #DC2626);background:var(--cc-sf-uploaded-remove-hover-bg, #FEF2F2)}.input-group{position:relative;display:flex;align-items:stretch;width:100%}.input-group .field-input{flex:1;border-radius:0}.input-group .field-input:first-child{border-top-left-radius:var(--cc-sf-input-radius, 8px);border-bottom-left-radius:var(--cc-sf-input-radius, 8px)}.input-group .field-input:last-child{border-top-right-radius:var(--cc-sf-input-radius, 8px);border-bottom-right-radius:var(--cc-sf-input-radius, 8px)}.input-group.readonly .field-input{background-color:var(--cc-sf-input-disabled-bg, #F3F4F6);cursor:default;padding-right:3.5rem}.input-group.readonly .input-prefix,.input-group.readonly .input-suffix{background-color:var(--cc-sf-input-disabled-bg, #F3F4F6)}.input-prefix,.input-suffix{display:flex;align-items:center;padding:0 .875rem;background-color:var(--cc-sf-input-bg, #FFFFFF);border:var(--cc-sf-input-border, 1.5px solid #D1D5DB);color:var(--cc-sf-hint-color, #6B7280);font-size:var(--cc-sf-input-font-size, .875rem);white-space:nowrap;-webkit-user-select:none;user-select:none}.input-prefix{border-right:none;border-top-left-radius:var(--cc-sf-input-radius, 8px);border-bottom-left-radius:var(--cc-sf-input-radius, 8px)}.input-suffix{border-left:none;border-top-right-radius:var(--cc-sf-input-radius, 8px);border-bottom-right-radius:var(--cc-sf-input-radius, 8px);color:var(--cc-sf-chip-selected-bg, #3B82F6);font-weight:500}.readonly-icons{position:absolute;right:.875rem;top:50%;transform:translateY(-50%);display:flex;gap:8px;pointer-events:none}.readonly-icons mat-icon.lock-icon{font-size:1rem;width:1rem;height:1rem;line-height:1rem;opacity:.5;color:var(--cc-sf-hint-color, #6B7280)}.subfields-group-wrapper{margin-bottom:var(--cc-sf-grid-gap, 16px)}.subfields-group-wrapper .group-label{display:block;font-size:var(--cc-sf-label-size, .875rem);font-weight:600;color:var(--cc-sf-label-color, #111827);margin-bottom:.75rem}.subfields-group-wrapper .group-label .required{color:var(--cc-sf-label-required-color, #DC2626);margin-left:.125rem}.subfields-group-wrapper .subfields-row{display:flex;align-items:flex-end;gap:12px;border-radius:var(--cc-sf-input-radius, 8px);transition:all .2s ease}.subfields-group-wrapper .subfields-row.is-invalid .subfield-item ::ng-deep .field-input{border-color:var(--cc-sf-error-border, #DC2626);background-color:var(--cc-sf-error-bg, #FEF2F2)}.subfields-group-wrapper .subfields-row .subfield-item{flex:1;min-width:0}.subfields-group-wrapper .subfields-row .subfield-item ::ng-deep .field-label{font-size:.75rem!important;margin-bottom:4px!important;font-weight:500!important;color:var(--cc-sf-hint-color, #6B7280)!important}.subfields-group-wrapper .subfields-row .subfield-separator{margin-bottom:24px;font-weight:700;color:#94a3b8}.subfields-group-wrapper .subfields-group-error{display:block;margin-top:6px;font-size:var(--cc-sf-error-text-size, .75rem);color:var(--cc-sf-error-text-color, #DC2626)}\n"] }]
4387
+ }], ctorParameters: () => [{ type: i1$2.FormBuilder }, { type: ExpressionService }, { type: i3.HttpClient }], propDecorators: { config: [{
4068
4388
  type: Input
4069
4389
  }], controller: [{
4070
4390
  type: Input
4071
- }], sectionIndex: [{
4391
+ }], formGroup: [{
4392
+ type: Input
4393
+ }], allowMulti: [{
4072
4394
  type: Input
4073
4395
  }] } });
4074
4396
 
4075
4397
  class FormSectionComponent {
4398
+ fb;
4076
4399
  config;
4077
4400
  controller;
4078
- sections = [{}];
4079
- addSection() {
4401
+ formGroup;
4402
+ /**
4403
+ * For allowMulti sections: the FormArray registered on the root formGroup.
4404
+ * Each element is a FormGroup representing one repeater instance.
4405
+ */
4406
+ repeaterFormArray;
4407
+ /**
4408
+ * The key under which the FormArray is registered in the root formGroup.
4409
+ * Falls back to config.name or a generated key.
4410
+ */
4411
+ get arrayKey() {
4412
+ return this.config.name || '__repeater__';
4413
+ }
4414
+ constructor(fb) {
4415
+ this.fb = fb;
4416
+ }
4417
+ ngOnInit() {
4080
4418
  if (this.config.allowMulti) {
4081
- this.sections.push({});
4419
+ this.repeaterFormArray = this.fb.array([]);
4420
+ this.formGroup.addControl(this.arrayKey, this.repeaterFormArray);
4421
+ // Start with one empty instance
4422
+ this.addInstance();
4423
+ }
4424
+ }
4425
+ ngOnDestroy() {
4426
+ if (this.config.allowMulti && this.formGroup.contains(this.arrayKey)) {
4427
+ this.formGroup.removeControl(this.arrayKey);
4082
4428
  }
4083
4429
  }
4084
- removeSection(index) {
4085
- if (this.sections.length > 1) {
4086
- this.sections.splice(index, 1);
4430
+ // ── Repeater helpers ──────────────────────────────────────────────────────
4431
+ /** Creates a fresh FormGroup for one repeater instance */
4432
+ createInstanceGroup() {
4433
+ return this.fb.group({});
4434
+ }
4435
+ addInstance() {
4436
+ this.repeaterFormArray.push(this.createInstanceGroup());
4437
+ }
4438
+ removeInstance(index) {
4439
+ if (this.repeaterFormArray.length > 1) {
4440
+ this.repeaterFormArray.removeAt(index);
4087
4441
  }
4088
4442
  }
4089
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FormSectionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4090
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: FormSectionComponent, isStandalone: false, selector: "lib-form-section", inputs: { config: "config", controller: "controller" }, ngImport: i0, template: "<div class=\"form-section-container\">\n <h3 class=\"section-label\" *ngIf=\"config.label\">{{ config.label }}</h3>\n \n <div *ngFor=\"let section of sections; let sectionIndex = index\" class=\"section-instance\">\n <div class=\"section-header\" *ngIf=\"config.allowMulti && sections.length > 1\">\n <span class=\"section-number\">{{ config.label }} #{{ sectionIndex + 1 }}</span>\n <button \n type=\"button\" \n class=\"btn-remove\"\n (click)=\"removeSection(sectionIndex)\"\n *ngIf=\"sectionIndex > 0\">\n Remove\n </button>\n </div>\n \n <div class=\"section-fields\">\n <ng-container *ngFor=\"let field of config.children\">\n <lib-form-field\n [config]=\"field\"\n [controller]=\"controller\"\n [sectionIndex]=\"config.allowMulti ? sectionIndex : undefined\">\n </lib-form-field>\n </ng-container>\n </div>\n </div>\n \n <button \n type=\"button\" \n class=\"btn-add-section\"\n *ngIf=\"config.allowMulti\"\n (click)=\"addSection()\">\n + Add {{ config.label }}\n </button>\n</div>\n", styles: [".form-section-container{margin-bottom:24px}.form-section-container .section-label{font-size:18px;font-weight:600;color:#333;margin:0 0 16px}.form-section-container .section-instance{margin-bottom:16px;padding:16px;border:1px solid #e0e0e0;border-radius:4px}.form-section-container .section-instance:last-of-type{margin-bottom:0}.form-section-container .section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid #e0e0e0}.form-section-container .section-header .section-number{font-weight:600;color:#333}.form-section-container .section-header .btn-remove{padding:6px 12px;background:#f44336;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px}.form-section-container .section-header .btn-remove:hover{background:#d32f2f}.form-section-container .section-fields{display:flex;flex-direction:column;gap:16px}.form-section-container .btn-add-section{padding:10px 20px;background:#2196f3;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:14px;margin-top:16px}.form-section-container .btn-add-section:hover{background:#1976d2}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: FormFieldComponent, selector: "lib-form-field", inputs: ["config", "controller", "sectionIndex"] }] });
4443
+ getInstanceGroup(index) {
4444
+ return this.repeaterFormArray.at(index);
4445
+ }
4446
+ get instanceGroups() {
4447
+ return this.repeaterFormArray.controls;
4448
+ }
4449
+ // ── Non-repeater: flat single FormGroup (root formGroup passed through) ──
4450
+ /** For non-allowMulti sections we simply pass the root formGroup down */
4451
+ get flatFormGroup() {
4452
+ return this.formGroup;
4453
+ }
4454
+ // ── Collect nested fields for a given child ───────────────────────────────
4455
+ /** Flatten a field tree to get all leaf fields (for ROW children etc.) */
4456
+ getFlatFields(fields) {
4457
+ const result = [];
4458
+ fields.forEach(f => {
4459
+ if (f.type === 'ROW' && f.children) {
4460
+ result.push(...this.getFlatFields(f.children));
4461
+ }
4462
+ else {
4463
+ result.push(f);
4464
+ }
4465
+ });
4466
+ return result;
4467
+ }
4468
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FormSectionComponent, deps: [{ token: i1$2.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
4469
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: FormSectionComponent, isStandalone: false, selector: "lib-form-section", inputs: { config: "config", controller: "controller", formGroup: "formGroup" }, ngImport: i0, template: "<div class=\"form-section-container\">\r\n <h3 class=\"section-label\" *ngIf=\"config.label && !config.allowMulti\">{{ config.label }}</h3>\r\n\r\n <!-- \u2550\u2550 Repeater (allowMulti) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <ng-container *ngIf=\"config.allowMulti\">\r\n <h3 class=\"section-label\">{{ config.label }}</h3>\r\n\r\n <div *ngFor=\"let instanceGroup of instanceGroups; let i = index\" class=\"section-instance\">\r\n <!-- Instance header (remove button) -->\r\n <div class=\"section-header\" *ngIf=\"instanceGroups.length > 1\">\r\n <span class=\"section-number\">{{ config.label }} #{{ i + 1 }}</span>\r\n <lib-button [variant]=\"'danger-outline'\" (click)=\"removeInstance(i)\">\r\n <mat-icon>delete_outline</mat-icon> Remove\r\n </lib-button>\r\n </div>\r\n\r\n <!-- Fields \u2013 each child rendered with the *instance* FormGroup -->\r\n <div class=\"section-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.children\">\r\n <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"instanceGroup\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n <lib-button [variant]=\"'primary'\" (click)=\"addInstance()\">\r\n <mat-icon>add</mat-icon> Add {{ config.label }}\r\n </lib-button>\r\n </ng-container>\r\n\r\n <!-- \u2550\u2550 Non-repeater (single instance) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <ng-container *ngIf=\"!config.allowMulti\">\r\n <div class=\"section-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.children\">\r\n <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"flatFormGroup\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </ng-container>\r\n\r\n</div>", styles: [".form-section-container{margin-bottom:var(--cc-sf-section-gap, 20px)}.form-section-container .section-label{font-size:var(--cc-sf-section-label-size, 1rem);font-weight:var(--cc-sf-section-label-weight, 600);color:var(--cc-sf-section-label-color, #1F2937);margin:0 0 16px;padding-bottom:10px;border-bottom:var(--cc-sf-section-label-border, 2px solid #E5E7EB)}.form-section-container .section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;padding-bottom:12px;border-bottom:var(--cc-sf-instance-divider, 1px dashed #D1D5DB)}.form-section-container .section-header .section-number{font-weight:600;font-size:var(--cc-sf-instance-num-size, .8125rem);color:var(--cc-sf-instance-num-color, #4B5563)}.form-section-container .section-header .btn-remove{padding:6px 12px;background:var(--cc-sf-btn-remove-bg, #FFF5F5);color:var(--cc-sf-btn-remove-color, #E53E3E);border:var(--cc-sf-btn-remove-border, 1px solid #FED7D7);border-radius:var(--cc-sf-btn-remove-radius, 4px);cursor:pointer;font-size:12px;transition:var(--cc-sf-btn-transition, all .2s ease)}.form-section-container .section-header .btn-remove:hover{background:var(--cc-sf-btn-remove-hover-bg, #FED7D7)}.form-section-container .section-fields.sf-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--cc-sf-grid-gap, 16px);align-items:start}@media(max-width:640px){.form-section-container .section-fields.sf-grid{grid-template-columns:1fr}.form-section-container .section-fields.sf-grid .sf-col{grid-column:span 12!important}}.form-section-container .section-fields .section-instance{margin-bottom:12px;padding:var(--cc-sf-instance-padding, 16px);background:var(--cc-sf-instance-bg, #F9FAFB);border:var(--cc-sf-instance-border, 1px solid #E5E7EB);border-radius:var(--cc-sf-instance-radius, 8px);transition:var(--cc-sf-btn-transition, all .2s ease)}.form-section-container .section-fields .section-instance:last-of-type{margin-bottom:0}.form-section-container .btn-add-section{display:flex;align-items:center;justify-content:center;width:100%;padding:10px 20px;background:var(--cc-sf-btn-add-bg, transparent);color:var(--cc-sf-btn-add-color, #3B82F6);border:var(--cc-sf-btn-add-border, 1px dashed #CBD5E1);border-radius:var(--cc-sf-btn-add-radius, 6px);cursor:pointer;font-size:var(--cc-sf-btn-font-size, .875rem);font-weight:var(--cc-sf-btn-font-weight, 600);margin-top:16px;transition:var(--cc-sf-btn-transition, all .2s ease)}.form-section-container .btn-add-section:hover{background:var(--cc-sf-btn-add-hover-bg, #EFF6FF);border-color:var(--cc-sf-btn-add-hover-border, #BFDBFE)}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: ButtonComponent, selector: "lib-button", inputs: ["variant", "type", "disabled", "width", "height", "borderRadius", "fontSize", "fontWeight", "backgroundColor", "color", "border", "icon", "labels"] }, { kind: "component", type: FormFieldComponent, selector: "lib-form-field", inputs: ["config", "controller", "formGroup", "allowMulti"] }] });
4091
4470
  }
4092
4471
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FormSectionComponent, decorators: [{
4093
4472
  type: Component,
4094
- args: [{ selector: 'lib-form-section', standalone: false, template: "<div class=\"form-section-container\">\n <h3 class=\"section-label\" *ngIf=\"config.label\">{{ config.label }}</h3>\n \n <div *ngFor=\"let section of sections; let sectionIndex = index\" class=\"section-instance\">\n <div class=\"section-header\" *ngIf=\"config.allowMulti && sections.length > 1\">\n <span class=\"section-number\">{{ config.label }} #{{ sectionIndex + 1 }}</span>\n <button \n type=\"button\" \n class=\"btn-remove\"\n (click)=\"removeSection(sectionIndex)\"\n *ngIf=\"sectionIndex > 0\">\n Remove\n </button>\n </div>\n \n <div class=\"section-fields\">\n <ng-container *ngFor=\"let field of config.children\">\n <lib-form-field\n [config]=\"field\"\n [controller]=\"controller\"\n [sectionIndex]=\"config.allowMulti ? sectionIndex : undefined\">\n </lib-form-field>\n </ng-container>\n </div>\n </div>\n \n <button \n type=\"button\" \n class=\"btn-add-section\"\n *ngIf=\"config.allowMulti\"\n (click)=\"addSection()\">\n + Add {{ config.label }}\n </button>\n</div>\n", styles: [".form-section-container{margin-bottom:24px}.form-section-container .section-label{font-size:18px;font-weight:600;color:#333;margin:0 0 16px}.form-section-container .section-instance{margin-bottom:16px;padding:16px;border:1px solid #e0e0e0;border-radius:4px}.form-section-container .section-instance:last-of-type{margin-bottom:0}.form-section-container .section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid #e0e0e0}.form-section-container .section-header .section-number{font-weight:600;color:#333}.form-section-container .section-header .btn-remove{padding:6px 12px;background:#f44336;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px}.form-section-container .section-header .btn-remove:hover{background:#d32f2f}.form-section-container .section-fields{display:flex;flex-direction:column;gap:16px}.form-section-container .btn-add-section{padding:10px 20px;background:#2196f3;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:14px;margin-top:16px}.form-section-container .btn-add-section:hover{background:#1976d2}\n"] }]
4095
- }], propDecorators: { config: [{
4473
+ args: [{ selector: 'lib-form-section', standalone: false, template: "<div class=\"form-section-container\">\r\n <h3 class=\"section-label\" *ngIf=\"config.label && !config.allowMulti\">{{ config.label }}</h3>\r\n\r\n <!-- \u2550\u2550 Repeater (allowMulti) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <ng-container *ngIf=\"config.allowMulti\">\r\n <h3 class=\"section-label\">{{ config.label }}</h3>\r\n\r\n <div *ngFor=\"let instanceGroup of instanceGroups; let i = index\" class=\"section-instance\">\r\n <!-- Instance header (remove button) -->\r\n <div class=\"section-header\" *ngIf=\"instanceGroups.length > 1\">\r\n <span class=\"section-number\">{{ config.label }} #{{ i + 1 }}</span>\r\n <lib-button [variant]=\"'danger-outline'\" (click)=\"removeInstance(i)\">\r\n <mat-icon>delete_outline</mat-icon> Remove\r\n </lib-button>\r\n </div>\r\n\r\n <!-- Fields \u2013 each child rendered with the *instance* FormGroup -->\r\n <div class=\"section-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.children\">\r\n <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"instanceGroup\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n <lib-button [variant]=\"'primary'\" (click)=\"addInstance()\">\r\n <mat-icon>add</mat-icon> Add {{ config.label }}\r\n </lib-button>\r\n </ng-container>\r\n\r\n <!-- \u2550\u2550 Non-repeater (single instance) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <ng-container *ngIf=\"!config.allowMulti\">\r\n <div class=\"section-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.children\">\r\n <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"flatFormGroup\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </ng-container>\r\n\r\n</div>", styles: [".form-section-container{margin-bottom:var(--cc-sf-section-gap, 20px)}.form-section-container .section-label{font-size:var(--cc-sf-section-label-size, 1rem);font-weight:var(--cc-sf-section-label-weight, 600);color:var(--cc-sf-section-label-color, #1F2937);margin:0 0 16px;padding-bottom:10px;border-bottom:var(--cc-sf-section-label-border, 2px solid #E5E7EB)}.form-section-container .section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;padding-bottom:12px;border-bottom:var(--cc-sf-instance-divider, 1px dashed #D1D5DB)}.form-section-container .section-header .section-number{font-weight:600;font-size:var(--cc-sf-instance-num-size, .8125rem);color:var(--cc-sf-instance-num-color, #4B5563)}.form-section-container .section-header .btn-remove{padding:6px 12px;background:var(--cc-sf-btn-remove-bg, #FFF5F5);color:var(--cc-sf-btn-remove-color, #E53E3E);border:var(--cc-sf-btn-remove-border, 1px solid #FED7D7);border-radius:var(--cc-sf-btn-remove-radius, 4px);cursor:pointer;font-size:12px;transition:var(--cc-sf-btn-transition, all .2s ease)}.form-section-container .section-header .btn-remove:hover{background:var(--cc-sf-btn-remove-hover-bg, #FED7D7)}.form-section-container .section-fields.sf-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--cc-sf-grid-gap, 16px);align-items:start}@media(max-width:640px){.form-section-container .section-fields.sf-grid{grid-template-columns:1fr}.form-section-container .section-fields.sf-grid .sf-col{grid-column:span 12!important}}.form-section-container .section-fields .section-instance{margin-bottom:12px;padding:var(--cc-sf-instance-padding, 16px);background:var(--cc-sf-instance-bg, #F9FAFB);border:var(--cc-sf-instance-border, 1px solid #E5E7EB);border-radius:var(--cc-sf-instance-radius, 8px);transition:var(--cc-sf-btn-transition, all .2s ease)}.form-section-container .section-fields .section-instance:last-of-type{margin-bottom:0}.form-section-container .btn-add-section{display:flex;align-items:center;justify-content:center;width:100%;padding:10px 20px;background:var(--cc-sf-btn-add-bg, transparent);color:var(--cc-sf-btn-add-color, #3B82F6);border:var(--cc-sf-btn-add-border, 1px dashed #CBD5E1);border-radius:var(--cc-sf-btn-add-radius, 6px);cursor:pointer;font-size:var(--cc-sf-btn-font-size, .875rem);font-weight:var(--cc-sf-btn-font-weight, 600);margin-top:16px;transition:var(--cc-sf-btn-transition, all .2s ease)}.form-section-container .btn-add-section:hover{background:var(--cc-sf-btn-add-hover-bg, #EFF6FF);border-color:var(--cc-sf-btn-add-hover-border, #BFDBFE)}\n"] }]
4474
+ }], ctorParameters: () => [{ type: i1$2.FormBuilder }], propDecorators: { config: [{
4096
4475
  type: Input
4097
4476
  }], controller: [{
4098
4477
  type: Input
4478
+ }], formGroup: [{
4479
+ type: Input
4099
4480
  }] } });
4100
4481
 
4101
4482
  class SmartFormComponent {
@@ -4106,6 +4487,12 @@ class SmartFormComponent {
4106
4487
  formJson;
4107
4488
  initialValues;
4108
4489
  enableDraftAutoSave = false;
4490
+ /** Flat i18n labels map passed by the consuming app.
4491
+ * After JSON parse the schema is walked and every string value that
4492
+ * matches a key in this map is replaced with the translated value.
4493
+ * Mirrors the pattern used by ConfigurableFormComponent + translateConfig.
4494
+ */
4495
+ labels = {};
4109
4496
  submit = new EventEmitter();
4110
4497
  draftSave = new EventEmitter();
4111
4498
  formSchema;
@@ -4121,23 +4508,41 @@ class SmartFormComponent {
4121
4508
  this.http = http;
4122
4509
  }
4123
4510
  ngOnInit() {
4511
+ this.parseFormJson();
4124
4512
  if (this.initialValues) {
4125
4513
  this.controller.initialize(this.initialValues);
4126
4514
  }
4127
- this.parseFormJson();
4128
4515
  }
4129
4516
  ngOnChanges(changes) {
4130
4517
  if (changes['formJson'] && !changes['formJson'].isFirstChange()) {
4131
4518
  this.parseFormJson();
4132
4519
  }
4520
+ // Re-translate if labels arrive after the JSON was already parsed
4521
+ if (changes['labels'] && !changes['labels'].isFirstChange() && this.formSchema) {
4522
+ this.parseFormJson();
4523
+ }
4133
4524
  }
4134
4525
  ngOnDestroy() {
4135
4526
  this.controller.destroy();
4136
4527
  }
4137
4528
  parseFormJson() {
4529
+ if (!this.formJson) {
4530
+ return;
4531
+ } // guard: labels may arrive before formJson
4138
4532
  try {
4139
4533
  const jsonData = JSON.parse(this.formJson);
4140
4534
  this.formSchema = jsonData;
4535
+ // Push token from configJSON into the shared controller so all child
4536
+ // components (form-field, etc.) can read it without @Input prop-drilling.
4537
+ this.controller.token = this.formSchema.token;
4538
+ this.controller.tokenHeader = this.formSchema.tokenHeader;
4539
+ // Translate i18n keys before rendering
4540
+ if (this.labels && Object.keys(this.labels).length) {
4541
+ SmartFormTranslationUtils.translateSchema(this.formSchema, this.labels);
4542
+ }
4543
+ // Share translated labels with child components via controller
4544
+ this.controller.labels = this.labels;
4545
+ this.controller.actionLabels = this.formSchema.labels;
4141
4546
  this.isStepper = this.formSchema.formType === 'STEPPER';
4142
4547
  this.fieldList = this.isStepper
4143
4548
  ? this.formSchema.stepperConfig?.children || []
@@ -4154,21 +4559,34 @@ class SmartFormComponent {
4154
4559
  }
4155
4560
  collectFields(fields) {
4156
4561
  fields.forEach(field => {
4157
- if (field.name) {
4158
- const value = field.defaultValue !== undefined ? field.defaultValue : null;
4562
+ // Flat leaf fields: seed controller with default values
4563
+ if (field.name && field.type !== 'GROUP' && field.type !== 'ROW') {
4564
+ // Check if initialValues already has a value for this field
4565
+ const existingValue = this.initialValues?.[field.name];
4566
+ const value = existingValue !== undefined
4567
+ ? existingValue
4568
+ : (field.defaultValue !== undefined ? field.defaultValue : null);
4159
4569
  this.controller.updateField(field.name, value);
4160
4570
  }
4161
- if (field.children && field.children.length > 0) {
4571
+ // Recurse into ROW children
4572
+ if (field.type === 'ROW' && field.children?.length) {
4162
4573
  this.collectFields(field.children);
4163
4574
  }
4575
+ // GROUP children will be handled dynamically by FormFieldComponent
4164
4576
  });
4165
4577
  }
4578
+ // ───────────────────────────────────────────────────────────────────────────
4579
+ // Submit
4580
+ // ───────────────────────────────────────────────────────────────────────────
4166
4581
  handleSubmit() {
4167
4582
  if (this.isStepper && this.currentStep < this.fieldList.length - 1) {
4168
- this.nextStep();
4583
+ if (this.validate())
4584
+ this.nextStep();
4169
4585
  return;
4170
4586
  }
4171
- const formData = this.controller.getAllData();
4587
+ if (!this.validate())
4588
+ return;
4589
+ const formData = this.collectFormData();
4172
4590
  this.isLoading = true;
4173
4591
  if (this.formSchema.submitConfig?.apiUrl) {
4174
4592
  this.submitToApi(formData);
@@ -4178,54 +4596,116 @@ class SmartFormComponent {
4178
4596
  this.isLoading = false;
4179
4597
  }
4180
4598
  }
4599
+ /**
4600
+ * Recursively extracts values from the formGroup, converting FormArrays to
4601
+ * arrays of objects so repeater groups come out as expected.
4602
+ */
4603
+ collectFormData() {
4604
+ return this.extractGroupValue(this.formGroup);
4605
+ }
4606
+ extractGroupValue(group) {
4607
+ const result = {};
4608
+ Object.keys(group.controls).forEach(key => {
4609
+ const ctrl = group.get(key);
4610
+ if (ctrl instanceof FormArray) {
4611
+ // Repeater → array of objects
4612
+ result[key] = ctrl.controls.map(fg => this.extractGroupValue(fg));
4613
+ }
4614
+ else if (ctrl instanceof FormGroup) {
4615
+ // Nested single group → nested object
4616
+ result[key] = this.extractGroupValue(ctrl);
4617
+ }
4618
+ else {
4619
+ result[key] = ctrl?.value ?? null;
4620
+ }
4621
+ });
4622
+ return result;
4623
+ }
4624
+ validate() {
4625
+ if (this.formGroup.invalid) {
4626
+ this.formGroup.markAllAsTouched();
4627
+ this.scrollToFirstInvalidControl();
4628
+ return false;
4629
+ }
4630
+ return true;
4631
+ }
4632
+ scrollToFirstInvalidControl() {
4633
+ setTimeout(() => {
4634
+ const firstInvalidControl = document.querySelector('.is-invalid, .ng-invalid.ng-touched');
4635
+ if (firstInvalidControl) {
4636
+ firstInvalidControl.scrollIntoView({ behavior: 'smooth', block: 'center' });
4637
+ }
4638
+ }, 100);
4639
+ }
4181
4640
  submitToApi(formData) {
4182
4641
  const config = this.formSchema.submitConfig;
4183
4642
  const method = config.method || 'POST';
4184
- this.http.request(method, config.apiUrl, { body: formData })
4185
- .subscribe({
4186
- next: (response) => {
4643
+ const headers = this.getHeaders();
4644
+ this.http.request(method, config.apiUrl, { body: formData, headers }).subscribe({
4645
+ next: response => {
4187
4646
  alert(config.successMessage || 'Form submitted successfully');
4188
4647
  this.submit.emit(response);
4189
4648
  this.isLoading = false;
4190
4649
  },
4191
- error: (err) => {
4650
+ error: err => {
4192
4651
  alert(config.errorMessage || 'Failed to submit form');
4193
4652
  console.error('Submit error:', err);
4194
4653
  this.isLoading = false;
4195
4654
  }
4196
4655
  });
4197
4656
  }
4657
+ /** Builds HttpHeaders from the token stored in the controller (sourced from configJSON). */
4658
+ getHeaders() {
4659
+ let headers = new HttpHeaders();
4660
+ if (this.controller.token) {
4661
+ const headerName = this.controller.tokenHeader || 'Authorization';
4662
+ headers = headers.set(headerName, this.controller.token);
4663
+ }
4664
+ return headers;
4665
+ }
4666
+ // ───────────────────────────────────────────────────────────────────────────
4667
+ // Stepper
4668
+ // ───────────────────────────────────────────────────────────────────────────
4198
4669
  nextStep() {
4199
- if (this.currentStep < this.fieldList.length - 1) {
4670
+ if (this.currentStep < this.fieldList.length - 1)
4200
4671
  this.currentStep++;
4201
- }
4202
4672
  }
4203
4673
  previousStep() {
4204
- if (this.currentStep > 0) {
4674
+ if (this.currentStep > 0)
4205
4675
  this.currentStep--;
4206
- }
4207
- }
4208
- get canGoNext() {
4209
- return this.currentStep < this.fieldList.length - 1;
4210
- }
4211
- get canGoPrevious() {
4212
- return this.currentStep > 0;
4213
4676
  }
4677
+ get canGoNext() { return this.currentStep < this.fieldList.length - 1; }
4678
+ get canGoPrevious() { return this.currentStep > 0; }
4214
4679
  get currentStepConfig() {
4215
4680
  return this.isStepper ? this.fieldList[this.currentStep] : undefined;
4216
4681
  }
4682
+ // ── Action Labels ──────────────────────────────────────────────────────────
4683
+ get nextLabel() {
4684
+ const key = this.formSchema?.labels?.nextLabel || 'Next';
4685
+ return this.labels[key] || key;
4686
+ }
4687
+ get submitLabel() {
4688
+ const key = this.formSchema?.labels?.submitLabel || 'Submit';
4689
+ return this.labels[key] || key;
4690
+ }
4691
+ get previousLabel() {
4692
+ const key = this.formSchema?.labels?.previousLabel || 'Previous';
4693
+ return this.labels[key] || key;
4694
+ }
4217
4695
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormComponent, deps: [{ token: i1$2.FormBuilder }, { token: SmartFormController }, { token: ExpressionService }, { token: i3.HttpClient }], target: i0.ɵɵFactoryTarget.Component });
4218
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SmartFormComponent, isStandalone: false, selector: "lib-smart-form", inputs: { formJson: "formJson", initialValues: "initialValues", enableDraftAutoSave: "enableDraftAutoSave" }, outputs: { submit: "submit", draftSave: "draftSave" }, providers: [SmartFormController], usesOnChanges: true, ngImport: i0, template: "<div class=\"smart-form-container\">\n <div class=\"smart-form-wrapper\" *ngIf=\"formSchema\">\n <!-- Form Header -->\n <div class=\"form-header\" *ngIf=\"formSchema.showTitle !== false\">\n <h2 class=\"form-title\">{{ formSchema.label }}</h2>\n <p class=\"form-description\" *ngIf=\"formSchema.description\">{{ formSchema.description }}</p>\n </div>\n\n <!-- Stepper Navigation -->\n <div class=\"stepper-nav\" *ngIf=\"isStepper && formSchema.stepperConfig?.showStep !== false\">\n <div class=\"stepper-steps\" [class.horizontal]=\"formSchema.stepperConfig?.isHorizontal !== false\">\n <div \n *ngFor=\"let step of fieldList; let i = index\"\n class=\"stepper-step\"\n [class.active]=\"i === currentStep\"\n [class.completed]=\"i < currentStep\">\n <div class=\"step-number\">{{ i + 1 }}</div>\n <div class=\"step-label\">{{ step.sectionConfig?.label || 'Step ' + (i + 1) }}</div>\n </div>\n </div>\n </div>\n\n <!-- Form Content -->\n <form [formGroup]=\"formGroup\" class=\"smart-form\">\n <!-- Section Form -->\n <div *ngIf=\"!isStepper && formSchema.sectionConfig\" class=\"form-section\">\n <lib-form-section \n [config]=\"formSchema.sectionConfig\"\n [controller]=\"controller\">\n </lib-form-section>\n </div>\n\n <!-- Stepper Form -->\n <div *ngIf=\"isStepper && currentStepConfig\" class=\"form-stepper\">\n <lib-form-section \n [config]=\"currentStepConfig.sectionConfig!\"\n [controller]=\"controller\">\n </lib-form-section>\n </div>\n </form>\n\n <!-- Form Actions -->\n <div class=\"form-actions\">\n <button \n *ngIf=\"isStepper && canGoPrevious\"\n type=\"button\" \n class=\"btn btn-secondary\"\n (click)=\"previousStep()\">\n Previous\n </button>\n \n <button \n type=\"button\" \n class=\"btn btn-primary\"\n [disabled]=\"isLoading\"\n (click)=\"handleSubmit()\">\n {{ isStepper && canGoNext ? 'Next' : 'Submit' }}\n </button>\n </div>\n </div>\n</div>\n", styles: [".smart-form-container{width:100%;max-width:1200px;margin:0 auto;padding:20px}.smart-form-wrapper{background:#fff;border-radius:8px;box-shadow:0 2px 8px #0000001a;padding:24px}.form-header{margin-bottom:24px}.form-header .form-title{font-size:24px;font-weight:600;color:#333;margin:0 0 8px}.form-header .form-description{font-size:14px;color:#666;margin:0}.stepper-nav{margin-bottom:32px}.stepper-nav .stepper-steps{display:flex;gap:16px}.stepper-nav .stepper-steps.horizontal{flex-direction:row;justify-content:space-between}.stepper-nav .stepper-steps:not(.horizontal){flex-direction:column}.stepper-nav .stepper-step{display:flex;align-items:center;gap:12px;flex:1;position:relative}.stepper-nav .stepper-step:not(:last-child):after{content:\"\";position:absolute;top:20px;left:calc(100% + 8px);width:calc(100% - 40px);height:2px;background:#e0e0e0}.stepper-nav .stepper-step.completed:after{background:#4caf50}.stepper-nav .stepper-step .step-number{width:40px;height:40px;border-radius:50%;background:#e0e0e0;color:#666;display:flex;align-items:center;justify-content:center;font-weight:600;transition:all .3s}.stepper-nav .stepper-step .step-label{font-size:14px;color:#666;font-weight:500}.stepper-nav .stepper-step.active .step-number{background:#2196f3;color:#fff}.stepper-nav .stepper-step.active .step-label{color:#2196f3;font-weight:600}.stepper-nav .stepper-step.completed .step-number{background:#4caf50;color:#fff}.smart-form{margin-bottom:24px}.form-actions{display:flex;justify-content:flex-end;gap:12px;padding-top:24px;border-top:1px solid #e0e0e0}.form-actions .btn{padding:10px 24px;border-radius:4px;font-size:14px;font-weight:500;border:none;cursor:pointer;transition:all .3s}.form-actions .btn.btn-primary{background:#2196f3;color:#fff}.form-actions .btn.btn-primary:hover:not(:disabled){background:#1976d2}.form-actions .btn.btn-primary:disabled{opacity:.6;cursor:not-allowed}.form-actions .btn.btn-secondary{background:#f5f5f5;color:#333}.form-actions .btn.btn-secondary:hover{background:#e0e0e0}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: FormSectionComponent, selector: "lib-form-section", inputs: ["config", "controller"] }] });
4696
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SmartFormComponent, isStandalone: false, selector: "lib-smart-form", inputs: { formJson: "formJson", initialValues: "initialValues", enableDraftAutoSave: "enableDraftAutoSave", labels: "labels" }, outputs: { submit: "submit", draftSave: "draftSave" }, providers: [SmartFormController], usesOnChanges: true, ngImport: i0, template: "<div class=\"smart-form-container\">\r\n <div class=\"smart-form-wrapper\" *ngIf=\"formSchema\">\r\n <!-- Form Header -->\r\n <div class=\"form-header\" *ngIf=\"formSchema.showTitle !== false\">\r\n <h2 class=\"form-title\">{{ formSchema.label }}</h2>\r\n <p class=\"form-description\" *ngIf=\"formSchema.description\">{{ formSchema.description }}</p>\r\n </div>\r\n\r\n <!-- Stepper Navigation -->\r\n <div class=\"stepper-nav\" *ngIf=\"isStepper && formSchema.stepperConfig?.showStep !== false\">\r\n <div class=\"stepper-steps\" [class.horizontal]=\"formSchema.stepperConfig?.isHorizontal !== false\">\r\n <div *ngFor=\"let step of fieldList; let i = index\" class=\"stepper-step\" [class.active]=\"i === currentStep\"\r\n [class.completed]=\"i < currentStep\">\r\n <div class=\"step-number\">{{ i + 1 }}</div>\r\n <div class=\"step-label\">{{ step.sectionConfig?.label || 'Step ' + (i + 1) }}</div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Form Content -->\r\n <form [formGroup]=\"formGroup\" class=\"smart-form\">\r\n <!-- Section Form -->\r\n <div *ngIf=\"!isStepper && formSchema.sectionConfig\" class=\"form-section\">\r\n <lib-form-section [config]=\"formSchema.sectionConfig\" [controller]=\"controller\" [formGroup]=\"formGroup\">\r\n </lib-form-section>\r\n </div>\r\n\r\n <!-- Stepper Form -->\r\n <div *ngIf=\"isStepper && currentStepConfig\" class=\"form-stepper\">\r\n <lib-form-section [config]=\"currentStepConfig.sectionConfig!\" [controller]=\"controller\" [formGroup]=\"formGroup\">\r\n </lib-form-section>\r\n </div>\r\n </form>\r\n\r\n <!-- Form Actions -->\r\n <div class=\"form-actions\" *ngIf=\"formSchema.showActions !== false\">\r\n <lib-button *ngIf=\"isStepper && canGoPrevious\" [variant]=\"'outline'\" (click)=\"previousStep()\">\r\n {{ previousLabel }}\r\n </lib-button>\r\n\r\n <lib-button [variant]=\"'warning'\" [disabled]=\"isLoading\" (click)=\"handleSubmit()\">\r\n {{ isStepper && canGoNext ? nextLabel : submitLabel }}\r\n </lib-button>\r\n </div>\r\n </div>\r\n</div>", styles: [".smart-form-container{width:100%;max-width:var(--cc-sf-form-max-width, 1200px);margin:0 auto;padding:var(--cc-sf-form-padding, 24px);font-family:var(--cc-sf-font-family, \"Inter\", sans-serif)}.smart-form-wrapper{background:var(--cc-sf-form-bg, #ffffff);border-radius:var(--cc-sf-form-border-radius, 12px);border:var(--cc-sf-form-border, none);box-shadow:var(--cc-sf-form-shadow, 0 1px 3px rgba(0, 0, 0, .06))}.form-header{margin-bottom:24px}.form-header .form-title{font-size:var(--cc-sf-form-title-size, 1.5rem);font-weight:var(--cc-sf-form-title-weight, 700);color:var(--cc-sf-form-title-color, #111827);margin:0 0 8px;line-height:1.25}.form-header .form-description{font-size:var(--cc-sf-form-desc-size, .875rem);color:var(--cc-sf-form-desc-color, #6B7280);margin:0}.stepper-nav{margin-bottom:32px}.stepper-nav .stepper-steps{display:flex;gap:16px}.stepper-nav .stepper-steps.horizontal{flex-direction:row;justify-content:space-between}.stepper-nav .stepper-steps:not(.horizontal){flex-direction:column}.stepper-nav .stepper-step{display:flex;align-items:center;gap:12px;flex:1;position:relative}.stepper-nav .stepper-step:not(:last-child):after{content:\"\";position:absolute;top:calc(var(--cc-sf-step-number-size, 40px) / 2);left:calc(100% + 8px);width:calc(100% - 40px);height:2px;background:var(--cc-sf-step-connector-color, #E5E7EB);transition:background var(--cc-sf-btn-transition, .2s ease)}.stepper-nav .stepper-step.completed:after{background:var(--cc-sf-step-connector-done, #22C55E)}.stepper-nav .stepper-step .step-number{width:var(--cc-sf-step-number-size, 40px);height:var(--cc-sf-step-number-size, 40px);min-width:var(--cc-sf-step-number-size, 40px);border-radius:50%;background:var(--cc-sf-step-number-bg, #E5E7EB);color:var(--cc-sf-step-number-color, #6B7280);display:flex;align-items:center;justify-content:center;font-size:var(--cc-sf-step-number-font-size, .875rem);font-weight:var(--cc-sf-step-number-weight, 600);transition:var(--cc-sf-btn-transition, all .2s ease)}.stepper-nav .stepper-step .step-label{font-size:var(--cc-sf-step-label-size, .875rem);color:var(--cc-sf-step-label-color, #6B7280);font-weight:var(--cc-sf-step-label-weight, 500);transition:var(--cc-sf-btn-transition, all .2s ease)}.stepper-nav .stepper-step.active .step-number{background:var(--cc-sf-step-active-bg, #3B82F6);color:var(--cc-sf-step-active-color, #ffffff)}.stepper-nav .stepper-step.active .step-label{color:var(--cc-sf-step-active-label, #1D4ED8);font-weight:var(--cc-sf-step-active-label-weight, 700)}.stepper-nav .stepper-step.completed .step-number{background:var(--cc-sf-step-done-bg, #22C55E);color:var(--cc-sf-step-done-color, #ffffff)}.smart-form{margin-bottom:24px}.form-actions{display:flex;justify-content:flex-end;gap:var(--cc-sf-actions-gap, 12px);padding:var(--cc-sf-actions-padding, 20px 0 0);border-top:var(--cc-sf-actions-border, 1px solid #E5E7EB)}.form-actions .btn{font-size:var(--cc-sf-btn-font-size, .875rem);font-weight:var(--cc-sf-btn-font-weight, 600);border:none;cursor:pointer;transition:var(--cc-sf-btn-transition, all .2s ease);font-family:var(--cc-sf-font-family, inherit);line-height:1.5}.form-actions .btn.btn-primary{background:var(--cc-sf-btn-primary-bg, #3B82F6);color:var(--cc-sf-btn-primary-color, #ffffff);border-radius:var(--cc-sf-btn-primary-radius, 8px);padding:var(--cc-sf-btn-primary-padding, .625rem 1.5rem)}.form-actions .btn.btn-primary:hover:not(:disabled){background:var(--cc-sf-btn-primary-hover-bg, #2563EB)}.form-actions .btn.btn-primary:disabled{opacity:var(--cc-sf-btn-disabled-opacity, .55);cursor:not-allowed}.form-actions .btn.btn-secondary{background:var(--cc-sf-btn-secondary-bg, #F3F4F6);color:var(--cc-sf-btn-secondary-color, #374151);border-radius:var(--cc-sf-btn-secondary-radius, 8px);padding:var(--cc-sf-btn-secondary-padding, .625rem 1.5rem)}.form-actions .btn.btn-secondary:hover{background:var(--cc-sf-btn-secondary-hover-bg, #E5E7EB)}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: ButtonComponent, selector: "lib-button", inputs: ["variant", "type", "disabled", "width", "height", "borderRadius", "fontSize", "fontWeight", "backgroundColor", "color", "border", "icon", "labels"] }, { kind: "component", type: FormSectionComponent, selector: "lib-form-section", inputs: ["config", "controller", "formGroup"] }] });
4219
4697
  }
4220
4698
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormComponent, decorators: [{
4221
4699
  type: Component,
4222
- args: [{ selector: 'lib-smart-form', providers: [SmartFormController], standalone: false, template: "<div class=\"smart-form-container\">\n <div class=\"smart-form-wrapper\" *ngIf=\"formSchema\">\n <!-- Form Header -->\n <div class=\"form-header\" *ngIf=\"formSchema.showTitle !== false\">\n <h2 class=\"form-title\">{{ formSchema.label }}</h2>\n <p class=\"form-description\" *ngIf=\"formSchema.description\">{{ formSchema.description }}</p>\n </div>\n\n <!-- Stepper Navigation -->\n <div class=\"stepper-nav\" *ngIf=\"isStepper && formSchema.stepperConfig?.showStep !== false\">\n <div class=\"stepper-steps\" [class.horizontal]=\"formSchema.stepperConfig?.isHorizontal !== false\">\n <div \n *ngFor=\"let step of fieldList; let i = index\"\n class=\"stepper-step\"\n [class.active]=\"i === currentStep\"\n [class.completed]=\"i < currentStep\">\n <div class=\"step-number\">{{ i + 1 }}</div>\n <div class=\"step-label\">{{ step.sectionConfig?.label || 'Step ' + (i + 1) }}</div>\n </div>\n </div>\n </div>\n\n <!-- Form Content -->\n <form [formGroup]=\"formGroup\" class=\"smart-form\">\n <!-- Section Form -->\n <div *ngIf=\"!isStepper && formSchema.sectionConfig\" class=\"form-section\">\n <lib-form-section \n [config]=\"formSchema.sectionConfig\"\n [controller]=\"controller\">\n </lib-form-section>\n </div>\n\n <!-- Stepper Form -->\n <div *ngIf=\"isStepper && currentStepConfig\" class=\"form-stepper\">\n <lib-form-section \n [config]=\"currentStepConfig.sectionConfig!\"\n [controller]=\"controller\">\n </lib-form-section>\n </div>\n </form>\n\n <!-- Form Actions -->\n <div class=\"form-actions\">\n <button \n *ngIf=\"isStepper && canGoPrevious\"\n type=\"button\" \n class=\"btn btn-secondary\"\n (click)=\"previousStep()\">\n Previous\n </button>\n \n <button \n type=\"button\" \n class=\"btn btn-primary\"\n [disabled]=\"isLoading\"\n (click)=\"handleSubmit()\">\n {{ isStepper && canGoNext ? 'Next' : 'Submit' }}\n </button>\n </div>\n </div>\n</div>\n", styles: [".smart-form-container{width:100%;max-width:1200px;margin:0 auto;padding:20px}.smart-form-wrapper{background:#fff;border-radius:8px;box-shadow:0 2px 8px #0000001a;padding:24px}.form-header{margin-bottom:24px}.form-header .form-title{font-size:24px;font-weight:600;color:#333;margin:0 0 8px}.form-header .form-description{font-size:14px;color:#666;margin:0}.stepper-nav{margin-bottom:32px}.stepper-nav .stepper-steps{display:flex;gap:16px}.stepper-nav .stepper-steps.horizontal{flex-direction:row;justify-content:space-between}.stepper-nav .stepper-steps:not(.horizontal){flex-direction:column}.stepper-nav .stepper-step{display:flex;align-items:center;gap:12px;flex:1;position:relative}.stepper-nav .stepper-step:not(:last-child):after{content:\"\";position:absolute;top:20px;left:calc(100% + 8px);width:calc(100% - 40px);height:2px;background:#e0e0e0}.stepper-nav .stepper-step.completed:after{background:#4caf50}.stepper-nav .stepper-step .step-number{width:40px;height:40px;border-radius:50%;background:#e0e0e0;color:#666;display:flex;align-items:center;justify-content:center;font-weight:600;transition:all .3s}.stepper-nav .stepper-step .step-label{font-size:14px;color:#666;font-weight:500}.stepper-nav .stepper-step.active .step-number{background:#2196f3;color:#fff}.stepper-nav .stepper-step.active .step-label{color:#2196f3;font-weight:600}.stepper-nav .stepper-step.completed .step-number{background:#4caf50;color:#fff}.smart-form{margin-bottom:24px}.form-actions{display:flex;justify-content:flex-end;gap:12px;padding-top:24px;border-top:1px solid #e0e0e0}.form-actions .btn{padding:10px 24px;border-radius:4px;font-size:14px;font-weight:500;border:none;cursor:pointer;transition:all .3s}.form-actions .btn.btn-primary{background:#2196f3;color:#fff}.form-actions .btn.btn-primary:hover:not(:disabled){background:#1976d2}.form-actions .btn.btn-primary:disabled{opacity:.6;cursor:not-allowed}.form-actions .btn.btn-secondary{background:#f5f5f5;color:#333}.form-actions .btn.btn-secondary:hover{background:#e0e0e0}\n"] }]
4700
+ args: [{ selector: 'lib-smart-form', providers: [SmartFormController], standalone: false, template: "<div class=\"smart-form-container\">\r\n <div class=\"smart-form-wrapper\" *ngIf=\"formSchema\">\r\n <!-- Form Header -->\r\n <div class=\"form-header\" *ngIf=\"formSchema.showTitle !== false\">\r\n <h2 class=\"form-title\">{{ formSchema.label }}</h2>\r\n <p class=\"form-description\" *ngIf=\"formSchema.description\">{{ formSchema.description }}</p>\r\n </div>\r\n\r\n <!-- Stepper Navigation -->\r\n <div class=\"stepper-nav\" *ngIf=\"isStepper && formSchema.stepperConfig?.showStep !== false\">\r\n <div class=\"stepper-steps\" [class.horizontal]=\"formSchema.stepperConfig?.isHorizontal !== false\">\r\n <div *ngFor=\"let step of fieldList; let i = index\" class=\"stepper-step\" [class.active]=\"i === currentStep\"\r\n [class.completed]=\"i < currentStep\">\r\n <div class=\"step-number\">{{ i + 1 }}</div>\r\n <div class=\"step-label\">{{ step.sectionConfig?.label || 'Step ' + (i + 1) }}</div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Form Content -->\r\n <form [formGroup]=\"formGroup\" class=\"smart-form\">\r\n <!-- Section Form -->\r\n <div *ngIf=\"!isStepper && formSchema.sectionConfig\" class=\"form-section\">\r\n <lib-form-section [config]=\"formSchema.sectionConfig\" [controller]=\"controller\" [formGroup]=\"formGroup\">\r\n </lib-form-section>\r\n </div>\r\n\r\n <!-- Stepper Form -->\r\n <div *ngIf=\"isStepper && currentStepConfig\" class=\"form-stepper\">\r\n <lib-form-section [config]=\"currentStepConfig.sectionConfig!\" [controller]=\"controller\" [formGroup]=\"formGroup\">\r\n </lib-form-section>\r\n </div>\r\n </form>\r\n\r\n <!-- Form Actions -->\r\n <div class=\"form-actions\" *ngIf=\"formSchema.showActions !== false\">\r\n <lib-button *ngIf=\"isStepper && canGoPrevious\" [variant]=\"'outline'\" (click)=\"previousStep()\">\r\n {{ previousLabel }}\r\n </lib-button>\r\n\r\n <lib-button [variant]=\"'warning'\" [disabled]=\"isLoading\" (click)=\"handleSubmit()\">\r\n {{ isStepper && canGoNext ? nextLabel : submitLabel }}\r\n </lib-button>\r\n </div>\r\n </div>\r\n</div>", styles: [".smart-form-container{width:100%;max-width:var(--cc-sf-form-max-width, 1200px);margin:0 auto;padding:var(--cc-sf-form-padding, 24px);font-family:var(--cc-sf-font-family, \"Inter\", sans-serif)}.smart-form-wrapper{background:var(--cc-sf-form-bg, #ffffff);border-radius:var(--cc-sf-form-border-radius, 12px);border:var(--cc-sf-form-border, none);box-shadow:var(--cc-sf-form-shadow, 0 1px 3px rgba(0, 0, 0, .06))}.form-header{margin-bottom:24px}.form-header .form-title{font-size:var(--cc-sf-form-title-size, 1.5rem);font-weight:var(--cc-sf-form-title-weight, 700);color:var(--cc-sf-form-title-color, #111827);margin:0 0 8px;line-height:1.25}.form-header .form-description{font-size:var(--cc-sf-form-desc-size, .875rem);color:var(--cc-sf-form-desc-color, #6B7280);margin:0}.stepper-nav{margin-bottom:32px}.stepper-nav .stepper-steps{display:flex;gap:16px}.stepper-nav .stepper-steps.horizontal{flex-direction:row;justify-content:space-between}.stepper-nav .stepper-steps:not(.horizontal){flex-direction:column}.stepper-nav .stepper-step{display:flex;align-items:center;gap:12px;flex:1;position:relative}.stepper-nav .stepper-step:not(:last-child):after{content:\"\";position:absolute;top:calc(var(--cc-sf-step-number-size, 40px) / 2);left:calc(100% + 8px);width:calc(100% - 40px);height:2px;background:var(--cc-sf-step-connector-color, #E5E7EB);transition:background var(--cc-sf-btn-transition, .2s ease)}.stepper-nav .stepper-step.completed:after{background:var(--cc-sf-step-connector-done, #22C55E)}.stepper-nav .stepper-step .step-number{width:var(--cc-sf-step-number-size, 40px);height:var(--cc-sf-step-number-size, 40px);min-width:var(--cc-sf-step-number-size, 40px);border-radius:50%;background:var(--cc-sf-step-number-bg, #E5E7EB);color:var(--cc-sf-step-number-color, #6B7280);display:flex;align-items:center;justify-content:center;font-size:var(--cc-sf-step-number-font-size, .875rem);font-weight:var(--cc-sf-step-number-weight, 600);transition:var(--cc-sf-btn-transition, all .2s ease)}.stepper-nav .stepper-step .step-label{font-size:var(--cc-sf-step-label-size, .875rem);color:var(--cc-sf-step-label-color, #6B7280);font-weight:var(--cc-sf-step-label-weight, 500);transition:var(--cc-sf-btn-transition, all .2s ease)}.stepper-nav .stepper-step.active .step-number{background:var(--cc-sf-step-active-bg, #3B82F6);color:var(--cc-sf-step-active-color, #ffffff)}.stepper-nav .stepper-step.active .step-label{color:var(--cc-sf-step-active-label, #1D4ED8);font-weight:var(--cc-sf-step-active-label-weight, 700)}.stepper-nav .stepper-step.completed .step-number{background:var(--cc-sf-step-done-bg, #22C55E);color:var(--cc-sf-step-done-color, #ffffff)}.smart-form{margin-bottom:24px}.form-actions{display:flex;justify-content:flex-end;gap:var(--cc-sf-actions-gap, 12px);padding:var(--cc-sf-actions-padding, 20px 0 0);border-top:var(--cc-sf-actions-border, 1px solid #E5E7EB)}.form-actions .btn{font-size:var(--cc-sf-btn-font-size, .875rem);font-weight:var(--cc-sf-btn-font-weight, 600);border:none;cursor:pointer;transition:var(--cc-sf-btn-transition, all .2s ease);font-family:var(--cc-sf-font-family, inherit);line-height:1.5}.form-actions .btn.btn-primary{background:var(--cc-sf-btn-primary-bg, #3B82F6);color:var(--cc-sf-btn-primary-color, #ffffff);border-radius:var(--cc-sf-btn-primary-radius, 8px);padding:var(--cc-sf-btn-primary-padding, .625rem 1.5rem)}.form-actions .btn.btn-primary:hover:not(:disabled){background:var(--cc-sf-btn-primary-hover-bg, #2563EB)}.form-actions .btn.btn-primary:disabled{opacity:var(--cc-sf-btn-disabled-opacity, .55);cursor:not-allowed}.form-actions .btn.btn-secondary{background:var(--cc-sf-btn-secondary-bg, #F3F4F6);color:var(--cc-sf-btn-secondary-color, #374151);border-radius:var(--cc-sf-btn-secondary-radius, 8px);padding:var(--cc-sf-btn-secondary-padding, .625rem 1.5rem)}.form-actions .btn.btn-secondary:hover{background:var(--cc-sf-btn-secondary-hover-bg, #E5E7EB)}\n"] }]
4223
4701
  }], ctorParameters: () => [{ type: i1$2.FormBuilder }, { type: SmartFormController }, { type: ExpressionService }, { type: i3.HttpClient }], propDecorators: { formJson: [{
4224
4702
  type: Input
4225
4703
  }], initialValues: [{
4226
4704
  type: Input
4227
4705
  }], enableDraftAutoSave: [{
4228
4706
  type: Input
4707
+ }], labels: [{
4708
+ type: Input
4229
4709
  }], submit: [{
4230
4710
  type: Output
4231
4711
  }], draftSave: [{
@@ -4238,12 +4718,16 @@ class SmartFormModule {
4238
4718
  FormSectionComponent,
4239
4719
  FormFieldComponent], imports: [CommonModule,
4240
4720
  ReactiveFormsModule,
4241
- FormsModule], exports: [SmartFormComponent] });
4721
+ FormsModule,
4722
+ MaterialModule,
4723
+ ButtonModule], exports: [SmartFormComponent] });
4242
4724
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormModule, providers: [
4243
4725
  ExpressionService
4244
4726
  ], imports: [CommonModule,
4245
4727
  ReactiveFormsModule,
4246
- FormsModule] });
4728
+ FormsModule,
4729
+ MaterialModule,
4730
+ ButtonModule] });
4247
4731
  }
4248
4732
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormModule, decorators: [{
4249
4733
  type: NgModule,
@@ -4256,7 +4740,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
4256
4740
  imports: [
4257
4741
  CommonModule,
4258
4742
  ReactiveFormsModule,
4259
- FormsModule
4743
+ FormsModule,
4744
+ MaterialModule,
4745
+ ButtonModule
4260
4746
  ],
4261
4747
  exports: [
4262
4748
  SmartFormComponent
@@ -5264,17 +5750,93 @@ class SmartTableComponent {
5264
5750
  if (!confirm(message))
5265
5751
  return;
5266
5752
  }
5267
- this.action.emit({ action, row });
5753
+ if (action.apiUrl) {
5754
+ this.executeApiAction(action, row);
5755
+ }
5756
+ else {
5757
+ this.action.emit({ action, row });
5758
+ }
5268
5759
  }
5269
5760
  else {
5270
5761
  this.action.emit({ action, row });
5271
5762
  }
5272
5763
  }
5764
+ onActionItemClick(item, row, event) {
5765
+ event.stopPropagation();
5766
+ if (item.type === 'callback' && item.callback) {
5767
+ item.callback(row);
5768
+ }
5769
+ if (item.type === 'route' && item.route) {
5770
+ const url = this.replaceParams(item.route, row);
5771
+ this.router.navigateByUrl(url);
5772
+ return;
5773
+ }
5774
+ if (item.type === 'api') {
5775
+ if (item.confirmationNeeded) {
5776
+ const message = item.confirmationMessage || this.config.labels?.defaultConfirmationMessage || 'Are you sure?';
5777
+ if (!confirm(message))
5778
+ return;
5779
+ }
5780
+ if (item.apiUrl) {
5781
+ this.executeApiAction(item, row);
5782
+ }
5783
+ else {
5784
+ this.action.emit({ action: item, row });
5785
+ }
5786
+ }
5787
+ else {
5788
+ this.action.emit({ action: item, row });
5789
+ }
5790
+ }
5273
5791
  onTopAction(action) {
5274
5792
  if (action.type === 'callback' && action.callback) {
5275
5793
  action.callback(null); // No row for top action
5276
5794
  }
5277
- this.topAction.emit(action);
5795
+ if (action.type === 'route' && action.route) {
5796
+ // Since it's a top action, replaceParams with an empty object or handle statically
5797
+ const url = this.replaceParams(action.route, {});
5798
+ this.router.navigateByUrl(url);
5799
+ return;
5800
+ }
5801
+ if (action.type === 'api') {
5802
+ if (action.confirmationNeeded) {
5803
+ const message = action.confirmationMessage || this.config.labels?.defaultConfirmationMessage || 'Are you sure?';
5804
+ if (!confirm(message))
5805
+ return;
5806
+ }
5807
+ if (action.apiUrl) {
5808
+ this.executeApiAction(action, null);
5809
+ }
5810
+ else {
5811
+ this.topAction.emit(action);
5812
+ }
5813
+ }
5814
+ else {
5815
+ this.topAction.emit(action);
5816
+ }
5817
+ }
5818
+ executeApiAction(action, row) {
5819
+ if (!action.apiUrl)
5820
+ return;
5821
+ const url = row ? this.replaceParams(action.apiUrl, row) : action.apiUrl;
5822
+ const method = action.apiMethod || 'POST';
5823
+ const headers = this.getHeaders();
5824
+ const body = row ? row : {};
5825
+ this.loading = true;
5826
+ this.http.request(method, url, { body, headers }).pipe(finalize(() => this.loading = false)).subscribe({
5827
+ next: () => {
5828
+ this.loadData(); // reload on success
5829
+ if (row) {
5830
+ this.action.emit({ action, row });
5831
+ }
5832
+ else {
5833
+ this.topAction.emit(action);
5834
+ }
5835
+ },
5836
+ error: (err) => {
5837
+ console.error('API Action Error', err);
5838
+ }
5839
+ });
5278
5840
  }
5279
5841
  // --- Selection ---
5280
5842
  onSelectAll(event) {
@@ -5450,11 +6012,11 @@ class SmartTableComponent {
5450
6012
  return headers;
5451
6013
  }
5452
6014
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartTableComponent, deps: [{ token: i3.HttpClient }, { token: i1$1.Router }, { token: i0.ChangeDetectorRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
5453
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SmartTableComponent, isStandalone: false, selector: "lib-smart-table", inputs: { config: "config" }, outputs: { action: "action", topAction: "topAction", filterChange: "filterChange", rowSelect: "rowSelect", columnClick: "columnClick" }, viewQueries: [{ propertyName: "stickyHeaders", predicate: ["stickyHeader"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"smart-table-wrapper\">\n <!-- Top Toolbar -->\n <div class=\"st-toolbar\" *ngIf=\"config.searchConfig?.enabled || (config.filters && config.filters.length > 0) || (config.topBarButtons && config.topBarButtons.length > 0)\">\n \n <!-- Search -->\n <div class=\"st-search\" *ngIf=\"config.searchConfig?.enabled\">\n <i class=\"fa fa-search\"></i>\n <input type=\"text\" [placeholder]=\"config.labels?.searchPlaceholder || 'Search'\" (input)=\"onSearch($event)\">\n </div>\n\n <!-- Filters -->\n <div class=\"st-filters\" *ngIf=\"config.filters\">\n <div class=\"st-filter-item\" *ngFor=\"let filter of config.filters\">\n <select (change)=\"onFilterChange(filter.key, $event)\">\n <option value=\"\">{{ filter.label }}</option>\n <option *ngFor=\"let opt of filter.options\" [value]=\"opt.value\">{{ opt.label }}</option>\n </select>\n </div>\n </div>\n\n <!-- Top Bar Buttons -->\n <div class=\"st-actions\" *ngIf=\"config.topBarButtons\">\n <lib-button *ngFor=\"let btn of config.topBarButtons\" \n [variant]=\"btn.btnVariant || 'primary'\"\n [icon]=\"btn.icon || ''\"\n (click)=\"onTopAction(btn)\">\n {{ btn.label }}\n </lib-button>\n </div>\n </div>\n\n <!-- Table Container -->\n <div class=\"st-table-container\">\n <div class=\"st-check-loader\" *ngIf=\"loading\">\n <div class=\"spinner\"></div>\n </div>\n <table class=\"st-table\" [class.loading-data]=\"loading\">\n <thead>\n <tr>\n <th *ngIf=\"config.selectable\" class=\"st-checkbox-col\">\n <input type=\"checkbox\" (change)=\"onSelectAll($event)\">\n </th>\n <th *ngFor=\"let col of config.columns\" \n #stickyHeader\n [class.sortable]=\"col.sortable\"\n [class.sticky-col]=\"col.sticky\"\n [ngStyle]=\"stickyColumnStyles[col.key]\"\n (click)=\"onSort(col)\">\n {{ col.label }}\n <span *ngIf=\"col.sortable\" class=\"sort-icon\">\n <i class=\"fa\" [ngClass]=\"getSortIcon(col.key)\"></i>\n </span>\n </th>\n <th *ngIf=\"config.actions && config.actions.length > 0\">{{ config.labels?.actionColumnHeader || 'Actions' }}</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let row of data\">\n <td *ngIf=\"config.selectable\" class=\"st-checkbox-col\">\n <input type=\"checkbox\" [(ngModel)]=\"row.selected\" (change)=\"onRowSelect(row)\">\n </td>\n <td *ngFor=\"let col of config.columns\" \n [class.sticky-col]=\"col.sticky\" \n [ngStyle]=\"stickyColumnStyles[col.key]\"\n [class.clickable-cell]=\"col.clickAction\"\n (click)=\"onColumnClick(row, col)\">\n <!-- Text/Number/Date -->\n <span *ngIf=\"col.type !== 'custom' && col.type !== 'html' && col.type !== 'badge'\">\n {{ getCellValue(row, col) }}\n </span>\n <!-- HTML -->\n <div *ngIf=\"col.type === 'html'\" [innerHTML]=\"getCellValue(row, col)\"></div>\n <!-- Badge -->\n <span *ngIf=\"col.type === 'badge'\" class=\"st-badge\" [ngClass]=\"getBadgeClass(row, col)\">\n {{ getCellValue(row, col) }}\n </span>\n </td>\n \n <!-- Row Actions -->\n <td *ngIf=\"config.actions && config.actions.length > 0\" class=\"st-row-actions\">\n <div class=\"action-buttons\">\n <ng-container *ngFor=\"let action of config.actions\">\n <lib-button \n [variant]=\"action.btnVariant || 'secondary'\"\n [icon]=\"action.icon || ''\"\n (click)=\"onAction(action, row)\">\n {{ action.label }}\n </lib-button>\n </ng-container>\n </div>\n <!-- Alternatively use specific icons if needed, but button component is requested -->\n </td>\n </tr>\n <tr *ngIf=\"data.length === 0 && !loading\">\n <td [attr.colspan]=\"columnCount + (config.selectable ? 1 : 0) + (config.actions ? 1 : 0)\" class=\"no-data\">\n {{ config.labels?.noDataMessage || 'No data available' }}\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n <div class=\"st-pagination\" *ngIf=\"config.pagination && config.pagination.enabled\">\n <lib-pagination\n [totalItems]=\"totalItems\"\n [itemsPerPage]=\"config.pagination.pageSize\"\n [currentPage]=\"currentPage\"\n [pageSizeOptions]=\"config.pagination.pageSizeOptions\"\n (pageChange)=\"onPageChange($event)\"\n (itemsPerPageChange)=\"onPageSizeChange($event)\">\n </lib-pagination>\n </div>\n</div>\n", styles: [".smart-table-wrapper{font-family:var(--st-font-family, \"Roboto\", sans-serif);background:var(--st-table-bg, #fff);border-radius:var(--st-border-radius, 8px);box-shadow:var(--st-box-shadow, 0 2px 4px rgba(0, 0, 0, .05));display:flex;flex-direction:column;gap:0;padding:0;border:var(--st-table-border, 1px solid #e0e0e0);overflow:hidden}.st-toolbar{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;padding:var(--st-toolbar-padding, 1rem);background:var(--st-toolbar-bg, #fff);border-bottom:var(--st-toolbar-border-bottom, 1px solid #eee);gap:var(--st-toolbar-gap, 1rem)}.st-toolbar .st-search{position:relative;width:var(--st-search-width, auto)}.st-toolbar .st-search input{padding:var(--st-search-padding, .5rem .5rem .5rem 2rem);border:var(--st-search-border, 1px solid #ccc);border-radius:var(--st-search-radius, 4px);background:var(--st-search-bg, #fff);font-size:var(--st-font-size, 14px);width:100%;color:var(--st-text-color, #333)}.st-toolbar .st-search i{position:absolute;left:.75rem;top:50%;transform:translateY(-50%);color:var(--st-search-icon-color, #999)}.st-toolbar .st-filters{display:flex;gap:1rem}.st-toolbar .st-filters select{padding:var(--st-filter-padding, .5rem);border:var(--st-filter-border, 1px solid #ccc);border-radius:var(--st-filter-radius, 4px);font-size:var(--st-filter-font-size, 14px);background:var(--st-filter-bg, #fff);color:var(--st-filter-color, #333)}.st-toolbar .st-actions{display:flex;gap:.5rem}.st-table-container{overflow-x:auto;overflow-y:auto;padding:var(--st-table-padding, 1rem)}.st-table-container::-webkit-scrollbar{width:var(--st-scrollbar-width, 8px);height:var(--st-scrollbar-height, 8px)}.st-table-container::-webkit-scrollbar-track{background:var(--st-scrollbar-track-bg, #f1f1f1);border-radius:var(--st-scrollbar-track-radius, 4px)}.st-table-container::-webkit-scrollbar-thumb{background:var(--st-scrollbar-thumb-bg, #c1c1c1);border-radius:var(--st-scrollbar-thumb-radius, 4px)}.st-table-container::-webkit-scrollbar-thumb:hover{background:var(--st-scrollbar-thumb-hover-bg, #a8a8a8)}.st-table-container.has-sticky-header .st-table thead th{position:sticky;top:0;z-index:10;background:var(--st-header-bg, #f9f9f9);box-shadow:0 1px 2px -1px #0000001a}.st-table-container table{width:100%;border-collapse:separate;border-spacing:0;font-size:var(--st-font-size, 14px)}.st-table-container table thead{background:var(--st-header-bg, #f9f9f9)}.st-table-container table thead th{padding:.75rem 1rem;text-align:left;color:var(--st-header-color, #333);font-weight:var(--st-header-weight, 500);font-size:var(--st-header-size, 14px);text-transform:var(--st-header-transform, none);border-bottom:var(--st-header-border, 1px solid #eee);white-space:nowrap}.st-table-container table thead th.sortable{cursor:pointer}.st-table-container table thead th.sortable:hover{opacity:.8}.st-table-container table thead th .sort-icon{margin-left:.5rem}.st-table-container table thead th .sort-icon .sort-icon{margin-left:.5rem;font-size:var(--st-sort-icon-size, .8em)}.st-table-container table thead th.st-checkbox-col{width:40px}.st-table-container table thead th.sticky-col{position:sticky;z-index:3;background:var(--st-header-bg, #f9f9f9);box-shadow:var(--st-sticky-shadow, 2px 0 5px -2px rgba(0, 0, 0, .1));border-right:var(--st-sticky-border-right, 1px solid rgba(0, 0, 0, .05))}.st-table-container table thead th.sticky-col:first-child{left:0}.st-table-container table tbody tr{background:var(--st-row-bg, #fff)}.st-table-container table tbody tr td{padding:var(--st-cell-padding, 1rem);color:var(--st-text-color, #333);vertical-align:middle;border-bottom:var(--st-row-border, 1px solid #eee)}.st-table-container table tbody tr td.sticky-col{position:sticky;z-index:2;background:var(--st-row-bg, #fff);box-shadow:var(--st-sticky-shadow, 2px 0 5px -2px rgba(0, 0, 0, .1));border-right:var(--st-sticky-border-right, 1px solid rgba(0, 0, 0, .05))}.st-table-container table tbody tr td.sticky-col:first-child{left:0}.st-table-container table tbody tr:hover td,.st-table-container table tbody tr:hover td.sticky-col{background:var(--st-row-hover-bg, #f9f9f9)}.st-table-container table tbody tr.selected td,.st-table-container table tbody tr.selected td.sticky-col{background:var(--st-row-selected-bg, #f3e5f5)}.st-table-container table tbody tr .clickable-cell{cursor:pointer;transition:background .2s}.st-table-container table tbody tr .clickable-cell:hover{background:var(--st-cell-hover-bg, #f0f0f0)!important}input[type=checkbox]{accent-color:var(--st-checkbox-color, #6200EE);width:var(--st-checkbox-size, 16px);height:var(--st-checkbox-size, 16px);cursor:pointer}.st-badge{display:inline-block;padding:var(--st-badge-padding, 4px 12px);border-radius:var(--st-badge-radius, 12px);font-size:var(--st-badge-font-size, 12px);font-weight:var(--st-badge-font-weight, 500);text-align:center;white-space:nowrap}.st-badge.badge-success{background:var(--st-badge-success-bg, #e8f5e9);color:var(--st-badge-success-color, #2e7d32)}.st-badge.badge-warning{background:var(--st-badge-warning-bg, #fff3e0);color:var(--st-badge-warning-color, #ef6c00)}.st-badge.badge-danger{background:var(--st-badge-danger-bg, #ffebee);color:var(--st-badge-danger-color, #c62828)}.st-badge.badge-info{background:var(--st-badge-info-bg, #e3f2fd);color:var(--st-badge-info-color, #1565c0)}.st-badge.badge-neutral{background:var(--st-badge-neutral-bg, #f5f5f5);color:var(--st-badge-neutral-color, #616161)}.st-row-actions .action-buttons{display:flex;gap:.5rem;align-items:center}.no-data{text-align:center;padding:2rem;color:var(--st-no-data-color, #888)}.st-pagination{padding:var(--st-pagination-padding, 1rem);border-top:var(--st-pagination-border-top, none)}@media(max-width:768px){.st-toolbar{flex-direction:column;align-items:stretch}.st-toolbar .st-search,.st-toolbar .st-filters,.st-toolbar .st-actions,.st-toolbar .st-search input{width:100%}}.st-table-container{position:relative;min-height:200px}.st-table-container .st-table.loading-data{opacity:.5;pointer-events:none}.st-table-container .st-check-loader{position:absolute;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center;z-index:10}.st-table-container .st-check-loader .spinner{width:40px;height:40px;border:4px solid var(--st-spinner-border-color, rgba(0, 0, 0, .1));border-left-color:var(--st-loader-color);border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: PaginationComponent, selector: "lib-pagination", inputs: ["totalItems", "itemsPerPage", "currentPage", "pageSizeOptions", "theme", "labels"], outputs: ["pageChange", "itemsPerPageChange"] }, { kind: "component", type: ButtonComponent, selector: "lib-button", inputs: ["variant", "type", "disabled", "width", "height", "borderRadius", "fontSize", "fontWeight", "backgroundColor", "color", "border", "icon", "labels"] }] });
6015
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SmartTableComponent, isStandalone: false, selector: "lib-smart-table", inputs: { config: "config" }, outputs: { action: "action", topAction: "topAction", filterChange: "filterChange", rowSelect: "rowSelect", columnClick: "columnClick" }, viewQueries: [{ propertyName: "stickyHeaders", predicate: ["stickyHeader"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"smart-table-wrapper\">\n <!-- Top Toolbar -->\n <div class=\"st-toolbar\" *ngIf=\"config.searchConfig?.enabled || (config.filters && config.filters.length > 0) || (config.topBarButtons && config.topBarButtons.length > 0)\">\n \n <!-- Search -->\n <div class=\"st-search\" *ngIf=\"config.searchConfig?.enabled\">\n <i class=\"fa fa-search\"></i>\n <input type=\"text\" [placeholder]=\"config.labels?.searchPlaceholder || 'Search'\" (input)=\"onSearch($event)\">\n </div>\n\n <!-- Filters -->\n <div class=\"st-filters\" *ngIf=\"config.filters\">\n <div class=\"st-filter-item\" *ngFor=\"let filter of config.filters\">\n <select (change)=\"onFilterChange(filter.key, $event)\">\n <option value=\"\">{{ filter.label }}</option>\n <option *ngFor=\"let opt of filter.options\" [value]=\"opt.value\">{{ opt.label }}</option>\n </select>\n </div>\n </div>\n\n <!-- Top Bar Buttons -->\n <div class=\"st-actions\" *ngIf=\"config.topBarButtons\">\n <lib-button *ngFor=\"let btn of config.topBarButtons\" \n [variant]=\"btn.btnVariant || 'primary'\"\n [icon]=\"btn.icon || ''\"\n (click)=\"onTopAction(btn)\">\n {{ btn.label }}\n </lib-button>\n </div>\n </div>\n\n <!-- Table Container -->\n <div class=\"st-table-container\">\n <div class=\"st-check-loader\" *ngIf=\"loading\">\n <div class=\"spinner\"></div>\n </div>\n <table class=\"st-table\" [class.loading-data]=\"loading\">\n <thead>\n <tr>\n <th *ngIf=\"config.selectable\" class=\"st-checkbox-col\">\n <input type=\"checkbox\" (change)=\"onSelectAll($event)\">\n </th>\n <th *ngFor=\"let col of config.columns\" \n #stickyHeader\n [class.sortable]=\"col.sortable\"\n [class.sticky-col]=\"col.sticky\"\n [ngStyle]=\"stickyColumnStyles[col.key]\"\n (click)=\"onSort(col)\">\n {{ col.label }}\n <span *ngIf=\"col.sortable\" class=\"sort-icon\">\n <i class=\"fa\" [ngClass]=\"getSortIcon(col.key)\"></i>\n </span>\n </th>\n <th *ngIf=\"config.actions && config.actions.length > 0\">{{ config.labels?.actionColumnHeader || 'Actions' }}</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let row of data\">\n <td *ngIf=\"config.selectable\" class=\"st-checkbox-col\">\n <input type=\"checkbox\" [(ngModel)]=\"row.selected\" (change)=\"onRowSelect(row)\">\n </td>\n <td *ngFor=\"let col of config.columns\" \n [class.sticky-col]=\"col.sticky\" \n [ngStyle]=\"stickyColumnStyles[col.key]\"\n [class.clickable-cell]=\"col.clickAction\"\n (click)=\"onColumnClick(row, col)\">\n <!-- Text/Number/Date -->\n <span *ngIf=\"col.type !== 'custom' && col.type !== 'html' && col.type !== 'badge'\">\n {{ getCellValue(row, col) }}\n </span>\n <!-- HTML -->\n <div *ngIf=\"col.type === 'html'\" [innerHTML]=\"getCellValue(row, col)\"></div>\n <!-- Badge -->\n <span *ngIf=\"col.type === 'badge'\" class=\"st-badge\" [ngClass]=\"getBadgeClass(row, col)\">\n {{ getCellValue(row, col) }}\n </span>\n </td>\n \n <!-- Row Actions -->\n <td *ngIf=\"config.actions && config.actions.length > 0\" class=\"st-row-actions\">\n <div class=\"action-buttons\">\n <ng-container *ngFor=\"let action of config.actions\">\n <ng-container *ngIf=\"action.type === 'dropdown'\">\n <button class=\"st-dropdown-btn\" [matMenuTriggerFor]=\"menu\" (click)=\"$event.stopPropagation()\">\n <i [class]=\"action.icon || 'fa fa-ellipsis-h'\"></i>\n </button>\n <mat-menu #menu=\"matMenu\" xPosition=\"before\" class=\"st-action-menu\">\n <button mat-menu-item *ngFor=\"let item of action.items\" (click)=\"onActionItemClick(item, row, $event)\">\n <i *ngIf=\"item.icon\" [class]=\"item.icon\" style=\"margin-right: 8px;\"></i>\n <span>{{ item.label }}</span>\n </button>\n </mat-menu>\n </ng-container>\n <ng-container *ngIf=\"action.type !== 'dropdown'\">\n <lib-button \n [variant]=\"action.btnVariant || 'secondary'\"\n [icon]=\"action.icon || ''\"\n (click)=\"onAction(action, row)\">\n {{ action.label }}\n </lib-button>\n </ng-container>\n </ng-container>\n </div>\n </td>\n </tr>\n <tr *ngIf=\"data.length === 0 && !loading\">\n <td [attr.colspan]=\"columnCount + (config.selectable ? 1 : 0) + (config.actions ? 1 : 0)\" class=\"no-data\">\n {{ config.labels?.noDataMessage || 'No data available' }}\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n <div class=\"st-pagination\" *ngIf=\"config.pagination && config.pagination.enabled\">\n <lib-pagination\n [totalItems]=\"totalItems\"\n [itemsPerPage]=\"config.pagination.pageSize\"\n [currentPage]=\"currentPage\"\n [pageSizeOptions]=\"config.pagination.pageSizeOptions\"\n (pageChange)=\"onPageChange($event)\"\n (itemsPerPageChange)=\"onPageSizeChange($event)\">\n </lib-pagination>\n </div>\n</div>\n", styles: [".smart-table-wrapper{font-family:var(--st-font-family, \"Roboto\", sans-serif);background:var(--st-table-bg, #fff);border-radius:var(--st-border-radius, 8px);box-shadow:var(--st-box-shadow, 0 2px 4px rgba(0, 0, 0, .05));display:flex;flex-direction:column;gap:0;padding:0;border:var(--st-table-border, 1px solid #e0e0e0);overflow:hidden}.st-toolbar{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;padding:var(--st-toolbar-padding, 1rem);background:var(--st-toolbar-bg, #fff);border-bottom:var(--st-toolbar-border-bottom, 1px solid #eee);gap:var(--st-toolbar-gap, 1rem)}.st-toolbar .st-search{position:relative;width:var(--st-search-width, auto)}.st-toolbar .st-search input{padding:var(--st-search-padding, .5rem .5rem .5rem 2rem);border:var(--st-search-border, 1px solid #ccc);border-radius:var(--st-search-radius, 4px);background:var(--st-search-bg, #fff);font-size:var(--st-font-size, 14px);width:100%;color:var(--st-text-color, #333)}.st-toolbar .st-search i{position:absolute;left:.75rem;top:50%;transform:translateY(-50%);color:var(--st-search-icon-color, #999)}.st-toolbar .st-filters{display:flex;gap:1rem}.st-toolbar .st-filters select{padding:var(--st-filter-padding, .5rem);border:var(--st-filter-border, 1px solid #ccc);border-radius:var(--st-filter-radius, 4px);font-size:var(--st-filter-font-size, 14px);background:var(--st-filter-bg, #fff);color:var(--st-filter-color, #333)}.st-toolbar .st-actions{display:flex;gap:.5rem}.st-table-container{overflow-x:auto;overflow-y:auto;padding:var(--st-table-padding, 1rem)}.st-table-container::-webkit-scrollbar{width:var(--st-scrollbar-width, 8px);height:var(--st-scrollbar-height, 8px)}.st-table-container::-webkit-scrollbar-track{background:var(--st-scrollbar-track-bg, #f1f1f1);border-radius:var(--st-scrollbar-track-radius, 4px)}.st-table-container::-webkit-scrollbar-thumb{background:var(--st-scrollbar-thumb-bg, #c1c1c1);border-radius:var(--st-scrollbar-thumb-radius, 4px)}.st-table-container::-webkit-scrollbar-thumb:hover{background:var(--st-scrollbar-thumb-hover-bg, #a8a8a8)}.st-table-container.has-sticky-header .st-table thead th{position:sticky;top:0;z-index:10;background:var(--st-header-bg, #f9f9f9);box-shadow:0 1px 2px -1px #0000001a}.st-table-container table{width:100%;border-collapse:separate;border-spacing:0;font-size:var(--st-font-size, 14px)}.st-table-container table thead{background:var(--st-header-bg, #f9f9f9)}.st-table-container table thead th{padding:.75rem 1rem;text-align:left;color:var(--st-header-color, #333);font-weight:var(--st-header-weight, 500);font-size:var(--st-header-size, 14px);text-transform:var(--st-header-transform, none);border-bottom:var(--st-header-border, 1px solid #eee);white-space:nowrap}.st-table-container table thead th.sortable{cursor:pointer}.st-table-container table thead th.sortable:hover{opacity:.8}.st-table-container table thead th .sort-icon{margin-left:.5rem}.st-table-container table thead th .sort-icon .sort-icon{margin-left:.5rem;font-size:var(--st-sort-icon-size, .8em)}.st-table-container table thead th.st-checkbox-col{width:40px}.st-table-container table thead th.sticky-col{position:sticky;z-index:3;background:var(--st-header-bg, #f9f9f9);box-shadow:var(--st-sticky-shadow, 2px 0 5px -2px rgba(0, 0, 0, .1));border-right:var(--st-sticky-border-right, 1px solid rgba(0, 0, 0, .05))}.st-table-container table thead th.sticky-col:first-child{left:0}.st-table-container table tbody tr{background:var(--st-row-bg, #fff)}.st-table-container table tbody tr td{padding:var(--st-cell-padding, 1rem);color:var(--st-text-color, #333);vertical-align:middle;border-bottom:var(--st-row-border, 1px solid #eee)}.st-table-container table tbody tr td.sticky-col{position:sticky;z-index:2;background:var(--st-row-bg, #fff);box-shadow:var(--st-sticky-shadow, 2px 0 5px -2px rgba(0, 0, 0, .1));border-right:var(--st-sticky-border-right, 1px solid rgba(0, 0, 0, .05))}.st-table-container table tbody tr td.sticky-col:first-child{left:0}.st-table-container table tbody tr:hover td,.st-table-container table tbody tr:hover td.sticky-col{background:var(--st-row-hover-bg, #f9f9f9)}.st-table-container table tbody tr.selected td,.st-table-container table tbody tr.selected td.sticky-col{background:var(--st-row-selected-bg, #f3e5f5)}.st-table-container table tbody tr .clickable-cell{cursor:pointer;transition:background .2s}.st-table-container table tbody tr .clickable-cell:hover{background:var(--st-cell-hover-bg, #f0f0f0)!important}input[type=checkbox]{accent-color:var(--st-checkbox-color, #6200EE);width:var(--st-checkbox-size, 16px);height:var(--st-checkbox-size, 16px);cursor:pointer}.st-badge{display:inline-block;padding:var(--st-badge-padding, 4px 12px);border-radius:var(--st-badge-radius, 12px);font-size:var(--st-badge-font-size, 12px);font-weight:var(--st-badge-font-weight, 500);text-align:center;white-space:nowrap}.st-badge.badge-success{background:var(--st-badge-success-bg, #e8f5e9);color:var(--st-badge-success-color, #2e7d32)}.st-badge.badge-warning{background:var(--st-badge-warning-bg, #fff3e0);color:var(--st-badge-warning-color, #ef6c00)}.st-badge.badge-danger{background:var(--st-badge-danger-bg, #ffebee);color:var(--st-badge-danger-color, #c62828)}.st-badge.badge-info{background:var(--st-badge-info-bg, #e3f2fd);color:var(--st-badge-info-color, #1565c0)}.st-badge.badge-neutral{background:var(--st-badge-neutral-bg, #f5f5f5);color:var(--st-badge-neutral-color, #616161)}.st-row-actions .action-buttons{display:flex;gap:.5rem;align-items:center}.no-data{text-align:center;padding:2rem;color:var(--st-no-data-color, #888)}.st-pagination{padding:var(--st-pagination-padding, 1rem);border-top:var(--st-pagination-border-top, none)}@media(max-width:768px){.st-toolbar{flex-direction:column;align-items:stretch}.st-toolbar .st-search,.st-toolbar .st-filters,.st-toolbar .st-actions,.st-toolbar .st-search input{width:100%}}.st-table-container{position:relative;min-height:200px}.st-table-container .st-table.loading-data{opacity:.5;pointer-events:none}.st-table-container .st-check-loader{position:absolute;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center;z-index:10}.st-table-container .st-check-loader .spinner{width:40px;height:40px;border:4px solid var(--st-spinner-border-color, rgba(0, 0, 0, .1));border-left-color:var(--st-loader-color);border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: PaginationComponent, selector: "lib-pagination", inputs: ["totalItems", "itemsPerPage", "currentPage", "pageSizeOptions", "theme", "labels"], outputs: ["pageChange", "itemsPerPageChange"] }, { kind: "component", type: ButtonComponent, selector: "lib-button", inputs: ["variant", "type", "disabled", "width", "height", "borderRadius", "fontSize", "fontWeight", "backgroundColor", "color", "border", "icon", "labels"] }, { kind: "component", type: i7$1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }] });
5454
6016
  }
5455
6017
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartTableComponent, decorators: [{
5456
6018
  type: Component,
5457
- args: [{ selector: 'lib-smart-table', standalone: false, template: "<div class=\"smart-table-wrapper\">\n <!-- Top Toolbar -->\n <div class=\"st-toolbar\" *ngIf=\"config.searchConfig?.enabled || (config.filters && config.filters.length > 0) || (config.topBarButtons && config.topBarButtons.length > 0)\">\n \n <!-- Search -->\n <div class=\"st-search\" *ngIf=\"config.searchConfig?.enabled\">\n <i class=\"fa fa-search\"></i>\n <input type=\"text\" [placeholder]=\"config.labels?.searchPlaceholder || 'Search'\" (input)=\"onSearch($event)\">\n </div>\n\n <!-- Filters -->\n <div class=\"st-filters\" *ngIf=\"config.filters\">\n <div class=\"st-filter-item\" *ngFor=\"let filter of config.filters\">\n <select (change)=\"onFilterChange(filter.key, $event)\">\n <option value=\"\">{{ filter.label }}</option>\n <option *ngFor=\"let opt of filter.options\" [value]=\"opt.value\">{{ opt.label }}</option>\n </select>\n </div>\n </div>\n\n <!-- Top Bar Buttons -->\n <div class=\"st-actions\" *ngIf=\"config.topBarButtons\">\n <lib-button *ngFor=\"let btn of config.topBarButtons\" \n [variant]=\"btn.btnVariant || 'primary'\"\n [icon]=\"btn.icon || ''\"\n (click)=\"onTopAction(btn)\">\n {{ btn.label }}\n </lib-button>\n </div>\n </div>\n\n <!-- Table Container -->\n <div class=\"st-table-container\">\n <div class=\"st-check-loader\" *ngIf=\"loading\">\n <div class=\"spinner\"></div>\n </div>\n <table class=\"st-table\" [class.loading-data]=\"loading\">\n <thead>\n <tr>\n <th *ngIf=\"config.selectable\" class=\"st-checkbox-col\">\n <input type=\"checkbox\" (change)=\"onSelectAll($event)\">\n </th>\n <th *ngFor=\"let col of config.columns\" \n #stickyHeader\n [class.sortable]=\"col.sortable\"\n [class.sticky-col]=\"col.sticky\"\n [ngStyle]=\"stickyColumnStyles[col.key]\"\n (click)=\"onSort(col)\">\n {{ col.label }}\n <span *ngIf=\"col.sortable\" class=\"sort-icon\">\n <i class=\"fa\" [ngClass]=\"getSortIcon(col.key)\"></i>\n </span>\n </th>\n <th *ngIf=\"config.actions && config.actions.length > 0\">{{ config.labels?.actionColumnHeader || 'Actions' }}</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let row of data\">\n <td *ngIf=\"config.selectable\" class=\"st-checkbox-col\">\n <input type=\"checkbox\" [(ngModel)]=\"row.selected\" (change)=\"onRowSelect(row)\">\n </td>\n <td *ngFor=\"let col of config.columns\" \n [class.sticky-col]=\"col.sticky\" \n [ngStyle]=\"stickyColumnStyles[col.key]\"\n [class.clickable-cell]=\"col.clickAction\"\n (click)=\"onColumnClick(row, col)\">\n <!-- Text/Number/Date -->\n <span *ngIf=\"col.type !== 'custom' && col.type !== 'html' && col.type !== 'badge'\">\n {{ getCellValue(row, col) }}\n </span>\n <!-- HTML -->\n <div *ngIf=\"col.type === 'html'\" [innerHTML]=\"getCellValue(row, col)\"></div>\n <!-- Badge -->\n <span *ngIf=\"col.type === 'badge'\" class=\"st-badge\" [ngClass]=\"getBadgeClass(row, col)\">\n {{ getCellValue(row, col) }}\n </span>\n </td>\n \n <!-- Row Actions -->\n <td *ngIf=\"config.actions && config.actions.length > 0\" class=\"st-row-actions\">\n <div class=\"action-buttons\">\n <ng-container *ngFor=\"let action of config.actions\">\n <lib-button \n [variant]=\"action.btnVariant || 'secondary'\"\n [icon]=\"action.icon || ''\"\n (click)=\"onAction(action, row)\">\n {{ action.label }}\n </lib-button>\n </ng-container>\n </div>\n <!-- Alternatively use specific icons if needed, but button component is requested -->\n </td>\n </tr>\n <tr *ngIf=\"data.length === 0 && !loading\">\n <td [attr.colspan]=\"columnCount + (config.selectable ? 1 : 0) + (config.actions ? 1 : 0)\" class=\"no-data\">\n {{ config.labels?.noDataMessage || 'No data available' }}\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n <div class=\"st-pagination\" *ngIf=\"config.pagination && config.pagination.enabled\">\n <lib-pagination\n [totalItems]=\"totalItems\"\n [itemsPerPage]=\"config.pagination.pageSize\"\n [currentPage]=\"currentPage\"\n [pageSizeOptions]=\"config.pagination.pageSizeOptions\"\n (pageChange)=\"onPageChange($event)\"\n (itemsPerPageChange)=\"onPageSizeChange($event)\">\n </lib-pagination>\n </div>\n</div>\n", styles: [".smart-table-wrapper{font-family:var(--st-font-family, \"Roboto\", sans-serif);background:var(--st-table-bg, #fff);border-radius:var(--st-border-radius, 8px);box-shadow:var(--st-box-shadow, 0 2px 4px rgba(0, 0, 0, .05));display:flex;flex-direction:column;gap:0;padding:0;border:var(--st-table-border, 1px solid #e0e0e0);overflow:hidden}.st-toolbar{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;padding:var(--st-toolbar-padding, 1rem);background:var(--st-toolbar-bg, #fff);border-bottom:var(--st-toolbar-border-bottom, 1px solid #eee);gap:var(--st-toolbar-gap, 1rem)}.st-toolbar .st-search{position:relative;width:var(--st-search-width, auto)}.st-toolbar .st-search input{padding:var(--st-search-padding, .5rem .5rem .5rem 2rem);border:var(--st-search-border, 1px solid #ccc);border-radius:var(--st-search-radius, 4px);background:var(--st-search-bg, #fff);font-size:var(--st-font-size, 14px);width:100%;color:var(--st-text-color, #333)}.st-toolbar .st-search i{position:absolute;left:.75rem;top:50%;transform:translateY(-50%);color:var(--st-search-icon-color, #999)}.st-toolbar .st-filters{display:flex;gap:1rem}.st-toolbar .st-filters select{padding:var(--st-filter-padding, .5rem);border:var(--st-filter-border, 1px solid #ccc);border-radius:var(--st-filter-radius, 4px);font-size:var(--st-filter-font-size, 14px);background:var(--st-filter-bg, #fff);color:var(--st-filter-color, #333)}.st-toolbar .st-actions{display:flex;gap:.5rem}.st-table-container{overflow-x:auto;overflow-y:auto;padding:var(--st-table-padding, 1rem)}.st-table-container::-webkit-scrollbar{width:var(--st-scrollbar-width, 8px);height:var(--st-scrollbar-height, 8px)}.st-table-container::-webkit-scrollbar-track{background:var(--st-scrollbar-track-bg, #f1f1f1);border-radius:var(--st-scrollbar-track-radius, 4px)}.st-table-container::-webkit-scrollbar-thumb{background:var(--st-scrollbar-thumb-bg, #c1c1c1);border-radius:var(--st-scrollbar-thumb-radius, 4px)}.st-table-container::-webkit-scrollbar-thumb:hover{background:var(--st-scrollbar-thumb-hover-bg, #a8a8a8)}.st-table-container.has-sticky-header .st-table thead th{position:sticky;top:0;z-index:10;background:var(--st-header-bg, #f9f9f9);box-shadow:0 1px 2px -1px #0000001a}.st-table-container table{width:100%;border-collapse:separate;border-spacing:0;font-size:var(--st-font-size, 14px)}.st-table-container table thead{background:var(--st-header-bg, #f9f9f9)}.st-table-container table thead th{padding:.75rem 1rem;text-align:left;color:var(--st-header-color, #333);font-weight:var(--st-header-weight, 500);font-size:var(--st-header-size, 14px);text-transform:var(--st-header-transform, none);border-bottom:var(--st-header-border, 1px solid #eee);white-space:nowrap}.st-table-container table thead th.sortable{cursor:pointer}.st-table-container table thead th.sortable:hover{opacity:.8}.st-table-container table thead th .sort-icon{margin-left:.5rem}.st-table-container table thead th .sort-icon .sort-icon{margin-left:.5rem;font-size:var(--st-sort-icon-size, .8em)}.st-table-container table thead th.st-checkbox-col{width:40px}.st-table-container table thead th.sticky-col{position:sticky;z-index:3;background:var(--st-header-bg, #f9f9f9);box-shadow:var(--st-sticky-shadow, 2px 0 5px -2px rgba(0, 0, 0, .1));border-right:var(--st-sticky-border-right, 1px solid rgba(0, 0, 0, .05))}.st-table-container table thead th.sticky-col:first-child{left:0}.st-table-container table tbody tr{background:var(--st-row-bg, #fff)}.st-table-container table tbody tr td{padding:var(--st-cell-padding, 1rem);color:var(--st-text-color, #333);vertical-align:middle;border-bottom:var(--st-row-border, 1px solid #eee)}.st-table-container table tbody tr td.sticky-col{position:sticky;z-index:2;background:var(--st-row-bg, #fff);box-shadow:var(--st-sticky-shadow, 2px 0 5px -2px rgba(0, 0, 0, .1));border-right:var(--st-sticky-border-right, 1px solid rgba(0, 0, 0, .05))}.st-table-container table tbody tr td.sticky-col:first-child{left:0}.st-table-container table tbody tr:hover td,.st-table-container table tbody tr:hover td.sticky-col{background:var(--st-row-hover-bg, #f9f9f9)}.st-table-container table tbody tr.selected td,.st-table-container table tbody tr.selected td.sticky-col{background:var(--st-row-selected-bg, #f3e5f5)}.st-table-container table tbody tr .clickable-cell{cursor:pointer;transition:background .2s}.st-table-container table tbody tr .clickable-cell:hover{background:var(--st-cell-hover-bg, #f0f0f0)!important}input[type=checkbox]{accent-color:var(--st-checkbox-color, #6200EE);width:var(--st-checkbox-size, 16px);height:var(--st-checkbox-size, 16px);cursor:pointer}.st-badge{display:inline-block;padding:var(--st-badge-padding, 4px 12px);border-radius:var(--st-badge-radius, 12px);font-size:var(--st-badge-font-size, 12px);font-weight:var(--st-badge-font-weight, 500);text-align:center;white-space:nowrap}.st-badge.badge-success{background:var(--st-badge-success-bg, #e8f5e9);color:var(--st-badge-success-color, #2e7d32)}.st-badge.badge-warning{background:var(--st-badge-warning-bg, #fff3e0);color:var(--st-badge-warning-color, #ef6c00)}.st-badge.badge-danger{background:var(--st-badge-danger-bg, #ffebee);color:var(--st-badge-danger-color, #c62828)}.st-badge.badge-info{background:var(--st-badge-info-bg, #e3f2fd);color:var(--st-badge-info-color, #1565c0)}.st-badge.badge-neutral{background:var(--st-badge-neutral-bg, #f5f5f5);color:var(--st-badge-neutral-color, #616161)}.st-row-actions .action-buttons{display:flex;gap:.5rem;align-items:center}.no-data{text-align:center;padding:2rem;color:var(--st-no-data-color, #888)}.st-pagination{padding:var(--st-pagination-padding, 1rem);border-top:var(--st-pagination-border-top, none)}@media(max-width:768px){.st-toolbar{flex-direction:column;align-items:stretch}.st-toolbar .st-search,.st-toolbar .st-filters,.st-toolbar .st-actions,.st-toolbar .st-search input{width:100%}}.st-table-container{position:relative;min-height:200px}.st-table-container .st-table.loading-data{opacity:.5;pointer-events:none}.st-table-container .st-check-loader{position:absolute;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center;z-index:10}.st-table-container .st-check-loader .spinner{width:40px;height:40px;border:4px solid var(--st-spinner-border-color, rgba(0, 0, 0, .1));border-left-color:var(--st-loader-color);border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
6019
+ args: [{ selector: 'lib-smart-table', standalone: false, template: "<div class=\"smart-table-wrapper\">\n <!-- Top Toolbar -->\n <div class=\"st-toolbar\" *ngIf=\"config.searchConfig?.enabled || (config.filters && config.filters.length > 0) || (config.topBarButtons && config.topBarButtons.length > 0)\">\n \n <!-- Search -->\n <div class=\"st-search\" *ngIf=\"config.searchConfig?.enabled\">\n <i class=\"fa fa-search\"></i>\n <input type=\"text\" [placeholder]=\"config.labels?.searchPlaceholder || 'Search'\" (input)=\"onSearch($event)\">\n </div>\n\n <!-- Filters -->\n <div class=\"st-filters\" *ngIf=\"config.filters\">\n <div class=\"st-filter-item\" *ngFor=\"let filter of config.filters\">\n <select (change)=\"onFilterChange(filter.key, $event)\">\n <option value=\"\">{{ filter.label }}</option>\n <option *ngFor=\"let opt of filter.options\" [value]=\"opt.value\">{{ opt.label }}</option>\n </select>\n </div>\n </div>\n\n <!-- Top Bar Buttons -->\n <div class=\"st-actions\" *ngIf=\"config.topBarButtons\">\n <lib-button *ngFor=\"let btn of config.topBarButtons\" \n [variant]=\"btn.btnVariant || 'primary'\"\n [icon]=\"btn.icon || ''\"\n (click)=\"onTopAction(btn)\">\n {{ btn.label }}\n </lib-button>\n </div>\n </div>\n\n <!-- Table Container -->\n <div class=\"st-table-container\">\n <div class=\"st-check-loader\" *ngIf=\"loading\">\n <div class=\"spinner\"></div>\n </div>\n <table class=\"st-table\" [class.loading-data]=\"loading\">\n <thead>\n <tr>\n <th *ngIf=\"config.selectable\" class=\"st-checkbox-col\">\n <input type=\"checkbox\" (change)=\"onSelectAll($event)\">\n </th>\n <th *ngFor=\"let col of config.columns\" \n #stickyHeader\n [class.sortable]=\"col.sortable\"\n [class.sticky-col]=\"col.sticky\"\n [ngStyle]=\"stickyColumnStyles[col.key]\"\n (click)=\"onSort(col)\">\n {{ col.label }}\n <span *ngIf=\"col.sortable\" class=\"sort-icon\">\n <i class=\"fa\" [ngClass]=\"getSortIcon(col.key)\"></i>\n </span>\n </th>\n <th *ngIf=\"config.actions && config.actions.length > 0\">{{ config.labels?.actionColumnHeader || 'Actions' }}</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let row of data\">\n <td *ngIf=\"config.selectable\" class=\"st-checkbox-col\">\n <input type=\"checkbox\" [(ngModel)]=\"row.selected\" (change)=\"onRowSelect(row)\">\n </td>\n <td *ngFor=\"let col of config.columns\" \n [class.sticky-col]=\"col.sticky\" \n [ngStyle]=\"stickyColumnStyles[col.key]\"\n [class.clickable-cell]=\"col.clickAction\"\n (click)=\"onColumnClick(row, col)\">\n <!-- Text/Number/Date -->\n <span *ngIf=\"col.type !== 'custom' && col.type !== 'html' && col.type !== 'badge'\">\n {{ getCellValue(row, col) }}\n </span>\n <!-- HTML -->\n <div *ngIf=\"col.type === 'html'\" [innerHTML]=\"getCellValue(row, col)\"></div>\n <!-- Badge -->\n <span *ngIf=\"col.type === 'badge'\" class=\"st-badge\" [ngClass]=\"getBadgeClass(row, col)\">\n {{ getCellValue(row, col) }}\n </span>\n </td>\n \n <!-- Row Actions -->\n <td *ngIf=\"config.actions && config.actions.length > 0\" class=\"st-row-actions\">\n <div class=\"action-buttons\">\n <ng-container *ngFor=\"let action of config.actions\">\n <ng-container *ngIf=\"action.type === 'dropdown'\">\n <button class=\"st-dropdown-btn\" [matMenuTriggerFor]=\"menu\" (click)=\"$event.stopPropagation()\">\n <i [class]=\"action.icon || 'fa fa-ellipsis-h'\"></i>\n </button>\n <mat-menu #menu=\"matMenu\" xPosition=\"before\" class=\"st-action-menu\">\n <button mat-menu-item *ngFor=\"let item of action.items\" (click)=\"onActionItemClick(item, row, $event)\">\n <i *ngIf=\"item.icon\" [class]=\"item.icon\" style=\"margin-right: 8px;\"></i>\n <span>{{ item.label }}</span>\n </button>\n </mat-menu>\n </ng-container>\n <ng-container *ngIf=\"action.type !== 'dropdown'\">\n <lib-button \n [variant]=\"action.btnVariant || 'secondary'\"\n [icon]=\"action.icon || ''\"\n (click)=\"onAction(action, row)\">\n {{ action.label }}\n </lib-button>\n </ng-container>\n </ng-container>\n </div>\n </td>\n </tr>\n <tr *ngIf=\"data.length === 0 && !loading\">\n <td [attr.colspan]=\"columnCount + (config.selectable ? 1 : 0) + (config.actions ? 1 : 0)\" class=\"no-data\">\n {{ config.labels?.noDataMessage || 'No data available' }}\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n <div class=\"st-pagination\" *ngIf=\"config.pagination && config.pagination.enabled\">\n <lib-pagination\n [totalItems]=\"totalItems\"\n [itemsPerPage]=\"config.pagination.pageSize\"\n [currentPage]=\"currentPage\"\n [pageSizeOptions]=\"config.pagination.pageSizeOptions\"\n (pageChange)=\"onPageChange($event)\"\n (itemsPerPageChange)=\"onPageSizeChange($event)\">\n </lib-pagination>\n </div>\n</div>\n", styles: [".smart-table-wrapper{font-family:var(--st-font-family, \"Roboto\", sans-serif);background:var(--st-table-bg, #fff);border-radius:var(--st-border-radius, 8px);box-shadow:var(--st-box-shadow, 0 2px 4px rgba(0, 0, 0, .05));display:flex;flex-direction:column;gap:0;padding:0;border:var(--st-table-border, 1px solid #e0e0e0);overflow:hidden}.st-toolbar{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;padding:var(--st-toolbar-padding, 1rem);background:var(--st-toolbar-bg, #fff);border-bottom:var(--st-toolbar-border-bottom, 1px solid #eee);gap:var(--st-toolbar-gap, 1rem)}.st-toolbar .st-search{position:relative;width:var(--st-search-width, auto)}.st-toolbar .st-search input{padding:var(--st-search-padding, .5rem .5rem .5rem 2rem);border:var(--st-search-border, 1px solid #ccc);border-radius:var(--st-search-radius, 4px);background:var(--st-search-bg, #fff);font-size:var(--st-font-size, 14px);width:100%;color:var(--st-text-color, #333)}.st-toolbar .st-search i{position:absolute;left:.75rem;top:50%;transform:translateY(-50%);color:var(--st-search-icon-color, #999)}.st-toolbar .st-filters{display:flex;gap:1rem}.st-toolbar .st-filters select{padding:var(--st-filter-padding, .5rem);border:var(--st-filter-border, 1px solid #ccc);border-radius:var(--st-filter-radius, 4px);font-size:var(--st-filter-font-size, 14px);background:var(--st-filter-bg, #fff);color:var(--st-filter-color, #333)}.st-toolbar .st-actions{display:flex;gap:.5rem}.st-table-container{overflow-x:auto;overflow-y:auto;padding:var(--st-table-padding, 1rem)}.st-table-container::-webkit-scrollbar{width:var(--st-scrollbar-width, 8px);height:var(--st-scrollbar-height, 8px)}.st-table-container::-webkit-scrollbar-track{background:var(--st-scrollbar-track-bg, #f1f1f1);border-radius:var(--st-scrollbar-track-radius, 4px)}.st-table-container::-webkit-scrollbar-thumb{background:var(--st-scrollbar-thumb-bg, #c1c1c1);border-radius:var(--st-scrollbar-thumb-radius, 4px)}.st-table-container::-webkit-scrollbar-thumb:hover{background:var(--st-scrollbar-thumb-hover-bg, #a8a8a8)}.st-table-container.has-sticky-header .st-table thead th{position:sticky;top:0;z-index:10;background:var(--st-header-bg, #f9f9f9);box-shadow:0 1px 2px -1px #0000001a}.st-table-container table{width:100%;border-collapse:separate;border-spacing:0;font-size:var(--st-font-size, 14px)}.st-table-container table thead{background:var(--st-header-bg, #f9f9f9)}.st-table-container table thead th{padding:.75rem 1rem;text-align:left;color:var(--st-header-color, #333);font-weight:var(--st-header-weight, 500);font-size:var(--st-header-size, 14px);text-transform:var(--st-header-transform, none);border-bottom:var(--st-header-border, 1px solid #eee);white-space:nowrap}.st-table-container table thead th.sortable{cursor:pointer}.st-table-container table thead th.sortable:hover{opacity:.8}.st-table-container table thead th .sort-icon{margin-left:.5rem}.st-table-container table thead th .sort-icon .sort-icon{margin-left:.5rem;font-size:var(--st-sort-icon-size, .8em)}.st-table-container table thead th.st-checkbox-col{width:40px}.st-table-container table thead th.sticky-col{position:sticky;z-index:3;background:var(--st-header-bg, #f9f9f9);box-shadow:var(--st-sticky-shadow, 2px 0 5px -2px rgba(0, 0, 0, .1));border-right:var(--st-sticky-border-right, 1px solid rgba(0, 0, 0, .05))}.st-table-container table thead th.sticky-col:first-child{left:0}.st-table-container table tbody tr{background:var(--st-row-bg, #fff)}.st-table-container table tbody tr td{padding:var(--st-cell-padding, 1rem);color:var(--st-text-color, #333);vertical-align:middle;border-bottom:var(--st-row-border, 1px solid #eee)}.st-table-container table tbody tr td.sticky-col{position:sticky;z-index:2;background:var(--st-row-bg, #fff);box-shadow:var(--st-sticky-shadow, 2px 0 5px -2px rgba(0, 0, 0, .1));border-right:var(--st-sticky-border-right, 1px solid rgba(0, 0, 0, .05))}.st-table-container table tbody tr td.sticky-col:first-child{left:0}.st-table-container table tbody tr:hover td,.st-table-container table tbody tr:hover td.sticky-col{background:var(--st-row-hover-bg, #f9f9f9)}.st-table-container table tbody tr.selected td,.st-table-container table tbody tr.selected td.sticky-col{background:var(--st-row-selected-bg, #f3e5f5)}.st-table-container table tbody tr .clickable-cell{cursor:pointer;transition:background .2s}.st-table-container table tbody tr .clickable-cell:hover{background:var(--st-cell-hover-bg, #f0f0f0)!important}input[type=checkbox]{accent-color:var(--st-checkbox-color, #6200EE);width:var(--st-checkbox-size, 16px);height:var(--st-checkbox-size, 16px);cursor:pointer}.st-badge{display:inline-block;padding:var(--st-badge-padding, 4px 12px);border-radius:var(--st-badge-radius, 12px);font-size:var(--st-badge-font-size, 12px);font-weight:var(--st-badge-font-weight, 500);text-align:center;white-space:nowrap}.st-badge.badge-success{background:var(--st-badge-success-bg, #e8f5e9);color:var(--st-badge-success-color, #2e7d32)}.st-badge.badge-warning{background:var(--st-badge-warning-bg, #fff3e0);color:var(--st-badge-warning-color, #ef6c00)}.st-badge.badge-danger{background:var(--st-badge-danger-bg, #ffebee);color:var(--st-badge-danger-color, #c62828)}.st-badge.badge-info{background:var(--st-badge-info-bg, #e3f2fd);color:var(--st-badge-info-color, #1565c0)}.st-badge.badge-neutral{background:var(--st-badge-neutral-bg, #f5f5f5);color:var(--st-badge-neutral-color, #616161)}.st-row-actions .action-buttons{display:flex;gap:.5rem;align-items:center}.no-data{text-align:center;padding:2rem;color:var(--st-no-data-color, #888)}.st-pagination{padding:var(--st-pagination-padding, 1rem);border-top:var(--st-pagination-border-top, none)}@media(max-width:768px){.st-toolbar{flex-direction:column;align-items:stretch}.st-toolbar .st-search,.st-toolbar .st-filters,.st-toolbar .st-actions,.st-toolbar .st-search input{width:100%}}.st-table-container{position:relative;min-height:200px}.st-table-container .st-table.loading-data{opacity:.5;pointer-events:none}.st-table-container .st-check-loader{position:absolute;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center;z-index:10}.st-table-container .st-check-loader .spinner{width:40px;height:40px;border:4px solid var(--st-spinner-border-color, rgba(0, 0, 0, .1));border-left-color:var(--st-loader-color);border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
5458
6020
  }], ctorParameters: () => [{ type: i3.HttpClient }, { type: i1$1.Router }, { type: i0.ChangeDetectorRef }, { type: i0.NgZone }], propDecorators: { config: [{
5459
6021
  type: Input
5460
6022
  }], action: [{
@@ -5477,11 +6039,15 @@ class SmartTableModule {
5477
6039
  static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: SmartTableModule, declarations: [SmartTableComponent], imports: [CommonModule,
5478
6040
  FormsModule,
5479
6041
  PaginationModule,
5480
- ButtonModule], exports: [SmartTableComponent] });
6042
+ ButtonModule,
6043
+ MatMenuModule,
6044
+ MatIconModule], exports: [SmartTableComponent] });
5481
6045
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartTableModule, imports: [CommonModule,
5482
6046
  FormsModule,
5483
6047
  PaginationModule,
5484
- ButtonModule] });
6048
+ ButtonModule,
6049
+ MatMenuModule,
6050
+ MatIconModule] });
5485
6051
  }
5486
6052
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartTableModule, decorators: [{
5487
6053
  type: NgModule,
@@ -5493,7 +6059,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
5493
6059
  CommonModule,
5494
6060
  FormsModule,
5495
6061
  PaginationModule,
5496
- ButtonModule
6062
+ ButtonModule,
6063
+ MatMenuModule,
6064
+ MatIconModule
5497
6065
  ],
5498
6066
  exports: [
5499
6067
  SmartTableComponent
@@ -5501,382 +6069,1324 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
5501
6069
  }]
5502
6070
  }] });
5503
6071
 
5504
- const SAMPLE_FORMS = {
5505
- // Simple Contact Form
5506
- contactForm: `{
5507
- "entityType": "CONTACT",
5508
- "label": "Contact Us",
5509
- "description": "Send us a message",
5510
- "formType": "SECTION",
5511
- "sectionConfig": {
5512
- "children": [
5513
- {
5514
- "name": "name",
5515
- "label": "Full Name",
5516
- "type": "TEXT_INPUT",
5517
- "subType": "SHORT",
5518
- "required": true,
5519
- "hint": "Enter your full name"
5520
- },
5521
- {
5522
- "name": "email",
5523
- "label": "Email Address",
5524
- "type": "TEXT_INPUT",
5525
- "subType": "EMAIL",
5526
- "required": true,
5527
- "hint": "your.email@example.com"
5528
- },
5529
- {
5530
- "name": "message",
5531
- "label": "Message",
5532
- "type": "TEXT_INPUT",
5533
- "subType": "LONG",
5534
- "required": true,
5535
- "textConfig": {
5536
- "length": {
5537
- "min": 10,
5538
- "max": 500
6072
+ class ValidationUtils {
6073
+ static email() {
6074
+ return (control) => {
6075
+ if (!control.value)
6076
+ return null;
6077
+ const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
6078
+ return emailRegex.test(control.value) ? null : { email: true };
6079
+ };
6080
+ }
6081
+ static phone() {
6082
+ return (control) => {
6083
+ if (!control.value)
6084
+ return null;
6085
+ const phoneRegex = /^\+?[\d\s\-\(\)]{10,}$/;
6086
+ return phoneRegex.test(control.value) ? null : { phone: true };
6087
+ };
6088
+ }
6089
+ static url() {
6090
+ return (control) => {
6091
+ if (!control.value)
6092
+ return null;
6093
+ const urlRegex = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/;
6094
+ return urlRegex.test(control.value) ? null : { url: true };
6095
+ };
6096
+ }
6097
+ static minLength(min) {
6098
+ return (control) => {
6099
+ if (!control.value)
6100
+ return null;
6101
+ return control.value.length >= min ? null : { minLength: { min, actual: control.value.length } };
6102
+ };
6103
+ }
6104
+ static maxLength(max) {
6105
+ return (control) => {
6106
+ if (!control.value)
6107
+ return null;
6108
+ return control.value.length <= max ? null : { maxLength: { max, actual: control.value.length } };
6109
+ };
6110
+ }
6111
+ static pattern(pattern, message) {
6112
+ return (control) => {
6113
+ if (!control.value)
6114
+ return null;
6115
+ const regex = new RegExp(pattern);
6116
+ return regex.test(control.value) ? null : { pattern: { message: message || 'Invalid format' } };
6117
+ };
6118
+ }
6119
+ static numberRange(min, max) {
6120
+ return (control) => {
6121
+ if (!control.value && control.value !== 0)
6122
+ return null;
6123
+ const value = Number(control.value);
6124
+ if (min !== undefined && value < min) {
6125
+ return { min: { min, actual: value } };
5539
6126
  }
5540
- }
5541
- }
5542
- ]
6127
+ if (max !== undefined && value > max) {
6128
+ return { max: { max, actual: value } };
6129
+ }
6130
+ return null;
6131
+ };
6132
+ }
6133
+ static dateRange(minDate, maxDate) {
6134
+ return (control) => {
6135
+ if (!control.value)
6136
+ return null;
6137
+ const date = new Date(control.value);
6138
+ if (minDate && date < new Date(minDate)) {
6139
+ return { minDate: { minDate } };
6140
+ }
6141
+ if (maxDate && date > new Date(maxDate)) {
6142
+ return { maxDate: { maxDate } };
6143
+ }
6144
+ return null;
6145
+ };
6146
+ }
6147
+ static getErrorMessage(errors) {
6148
+ if (!errors)
6149
+ return '';
6150
+ if (errors['required'])
6151
+ return 'This field is required';
6152
+ if (errors['email'])
6153
+ return 'Please enter a valid email address';
6154
+ if (errors['phone'])
6155
+ return 'Please enter a valid phone number';
6156
+ if (errors['url'])
6157
+ return 'Please enter a valid URL';
6158
+ if (errors['minLength'])
6159
+ return `Minimum length is ${errors['minLength'].min} characters`;
6160
+ if (errors['maxLength'])
6161
+ return `Maximum length is ${errors['maxLength'].max} characters`;
6162
+ if (errors['min'])
6163
+ return `Minimum value is ${errors['min'].min}`;
6164
+ if (errors['max'])
6165
+ return `Maximum value is ${errors['max'].max}`;
6166
+ if (errors['minDate'])
6167
+ return `Date must be after ${errors['minDate'].minDate}`;
6168
+ if (errors['maxDate'])
6169
+ return `Date must be before ${errors['maxDate'].maxDate}`;
6170
+ if (errors['pattern'])
6171
+ return errors['pattern'].message || 'Invalid format';
6172
+ return 'Invalid value';
5543
6173
  }
6174
+ }
6175
+
6176
+ const SAMPLE_FORMS = {
6177
+ // Simple Contact Form
6178
+ contactForm: `{
6179
+ "entityType": "CONTACT",
6180
+ "label": "Contact Us",
6181
+ "description": "Send us a message",
6182
+ "formType": "SECTION",
6183
+ "sectionConfig": {
6184
+ "children": [
6185
+ {
6186
+ "name": "name",
6187
+ "label": "Full Name",
6188
+ "type": "TEXT_INPUT",
6189
+ "subType": "SHORT",
6190
+ "required": true,
6191
+ "hint": "Enter your full name"
6192
+ },
6193
+ {
6194
+ "name": "email",
6195
+ "label": "Email Address",
6196
+ "type": "TEXT_INPUT",
6197
+ "subType": "EMAIL",
6198
+ "required": true,
6199
+ "hint": "your.email@example.com"
6200
+ },
6201
+ {
6202
+ "name": "message",
6203
+ "label": "Message",
6204
+ "type": "TEXT_INPUT",
6205
+ "subType": "LONG",
6206
+ "required": true,
6207
+ "textConfig": {
6208
+ "length": {
6209
+ "min": 10,
6210
+ "max": 500
6211
+ }
6212
+ }
6213
+ }
6214
+ ]
6215
+ }
5544
6216
  }`,
5545
6217
  // User Registration with Stepper
5546
- registrationForm: `{
5547
- "entityType": "USER",
5548
- "label": "User Registration",
5549
- "description": "Create your account",
5550
- "formType": "STEPPER",
5551
- "stepperConfig": {
5552
- "children": [
5553
- {
5554
- "type": "GROUP",
5555
- "subType": "SECTION",
5556
- "sectionConfig": {
5557
- "label": "Personal Information",
5558
- "children": [
5559
- {
5560
- "type": "ROW",
5561
- "subType": "HORIZONTAL",
5562
- "children": [
5563
- {
5564
- "name": "firstName",
5565
- "label": "First Name",
5566
- "type": "TEXT_INPUT",
5567
- "subType": "SHORT",
5568
- "required": true
5569
- },
5570
- {
5571
- "name": "lastName",
5572
- "label": "Last Name",
5573
- "type": "TEXT_INPUT",
5574
- "subType": "SHORT",
5575
- "required": true
5576
- }
5577
- ]
5578
- },
5579
- {
5580
- "name": "fullName",
5581
- "label": "Full Name",
5582
- "type": "GENERATED",
5583
- "subType": "FORMULA",
5584
- "generatedConfig": {
5585
- "formula": "function fullName(first, last) { return (first || '') + ' ' + (last || ''); }",
5586
- "variables": ["firstName", "lastName"]
5587
- }
5588
- },
5589
- {
5590
- "name": "dateOfBirth",
5591
- "label": "Date of Birth",
5592
- "type": "DATE",
5593
- "subType": "SINGLE",
5594
- "required": true,
5595
- "dateConfig": {
5596
- "allowFuture": false
5597
- }
5598
- }
5599
- ]
5600
- }
5601
- },
5602
- {
5603
- "type": "GROUP",
5604
- "subType": "SECTION",
5605
- "sectionConfig": {
5606
- "label": "Contact Information",
5607
- "children": [
5608
- {
5609
- "name": "email",
5610
- "label": "Email",
5611
- "type": "TEXT_INPUT",
5612
- "subType": "EMAIL",
5613
- "required": true
5614
- },
5615
- {
5616
- "name": "phone",
5617
- "label": "Phone Number",
5618
- "type": "TEXT_INPUT",
5619
- "subType": "PHONE",
5620
- "required": true
5621
- }
5622
- ]
5623
- }
5624
- },
5625
- {
5626
- "type": "GROUP",
5627
- "subType": "SECTION",
5628
- "sectionConfig": {
5629
- "label": "Preferences",
5630
- "children": [
5631
- {
5632
- "name": "notifications",
5633
- "label": "Enable Email Notifications",
5634
- "type": "SWITCH",
5635
- "subType": "BOOL",
5636
- "defaultValue": true
5637
- },
5638
- {
5639
- "name": "interests",
5640
- "label": "Interests",
5641
- "type": "CHIP",
5642
- "subType": "MULTIPLE",
5643
- "optionConfig": {
5644
- "optionList": [
5645
- { "label": "Technology", "code": "TECH" },
5646
- { "label": "Sports", "code": "SPORTS" },
5647
- { "label": "Music", "code": "MUSIC" },
5648
- { "label": "Travel", "code": "TRAVEL" }
5649
- ]
5650
- }
5651
- }
5652
- ]
5653
- }
5654
- }
5655
- ],
5656
- "showStep": true,
5657
- "isHorizontal": true
5658
- }
6218
+ registrationForm: `{
6219
+ "entityType": "USER",
6220
+ "label": "User Registration",
6221
+ "description": "Create your account",
6222
+ "formType": "STEPPER",
6223
+ "stepperConfig": {
6224
+ "children": [
6225
+ {
6226
+ "type": "GROUP",
6227
+ "subType": "SECTION",
6228
+ "sectionConfig": {
6229
+ "label": "Personal Information",
6230
+ "children": [
6231
+ {
6232
+ "type": "ROW",
6233
+ "subType": "HORIZONTAL",
6234
+ "children": [
6235
+ {
6236
+ "name": "firstName",
6237
+ "label": "First Name",
6238
+ "type": "TEXT_INPUT",
6239
+ "subType": "SHORT",
6240
+ "required": true
6241
+ },
6242
+ {
6243
+ "name": "lastName",
6244
+ "label": "Last Name",
6245
+ "type": "TEXT_INPUT",
6246
+ "subType": "SHORT",
6247
+ "required": true
6248
+ }
6249
+ ]
6250
+ },
6251
+ {
6252
+ "name": "fullName",
6253
+ "label": "Full Name",
6254
+ "type": "GENERATED",
6255
+ "subType": "FORMULA",
6256
+ "generatedConfig": {
6257
+ "formula": "function fullName(first, last) { return (first || '') + ' ' + (last || ''); }",
6258
+ "variables": ["firstName", "lastName"]
6259
+ }
6260
+ },
6261
+ {
6262
+ "name": "dateOfBirth",
6263
+ "label": "Date of Birth",
6264
+ "type": "DATE",
6265
+ "subType": "SINGLE",
6266
+ "required": true,
6267
+ "dateConfig": {
6268
+ "allowFuture": false
6269
+ }
6270
+ }
6271
+ ]
6272
+ }
6273
+ },
6274
+ {
6275
+ "type": "GROUP",
6276
+ "subType": "SECTION",
6277
+ "sectionConfig": {
6278
+ "label": "Contact Information",
6279
+ "children": [
6280
+ {
6281
+ "name": "email",
6282
+ "label": "Email",
6283
+ "type": "TEXT_INPUT",
6284
+ "subType": "EMAIL",
6285
+ "required": true
6286
+ },
6287
+ {
6288
+ "name": "phone",
6289
+ "label": "Phone Number",
6290
+ "type": "TEXT_INPUT",
6291
+ "subType": "PHONE",
6292
+ "required": true
6293
+ }
6294
+ ]
6295
+ }
6296
+ },
6297
+ {
6298
+ "type": "GROUP",
6299
+ "subType": "SECTION",
6300
+ "sectionConfig": {
6301
+ "label": "Preferences",
6302
+ "children": [
6303
+ {
6304
+ "name": "notifications",
6305
+ "label": "Enable Email Notifications",
6306
+ "type": "SWITCH",
6307
+ "subType": "BOOL",
6308
+ "defaultValue": true
6309
+ },
6310
+ {
6311
+ "name": "interests",
6312
+ "label": "Interests",
6313
+ "type": "CHIP",
6314
+ "subType": "MULTIPLE",
6315
+ "optionConfig": {
6316
+ "optionList": [
6317
+ { "label": "Technology", "code": "TECH" },
6318
+ { "label": "Sports", "code": "SPORTS" },
6319
+ { "label": "Music", "code": "MUSIC" },
6320
+ { "label": "Travel", "code": "TRAVEL" }
6321
+ ]
6322
+ }
6323
+ }
6324
+ ]
6325
+ }
6326
+ }
6327
+ ],
6328
+ "showStep": true,
6329
+ "isHorizontal": true
6330
+ }
5659
6331
  }`,
5660
6332
  // Survey Form with Conditional Fields
5661
- surveyForm: `{
5662
- "entityType": "SURVEY",
5663
- "label": "Customer Satisfaction Survey",
5664
- "formType": "SECTION",
5665
- "sectionConfig": {
5666
- "children": [
5667
- {
5668
- "name": "overallRating",
5669
- "label": "Overall Experience",
5670
- "type": "RATING",
5671
- "subType": "STAR",
5672
- "required": true,
5673
- "ratingConfig": {
5674
- "maxRating": 5,
5675
- "allowHalf": false
5676
- }
5677
- },
5678
- {
5679
- "name": "wouldRecommend",
5680
- "label": "Would you recommend us?",
5681
- "type": "RADIO",
5682
- "subType": "SINGLE",
5683
- "required": true,
5684
- "optionConfig": {
5685
- "optionList": [
5686
- { "label": "Yes", "code": "YES" },
5687
- { "label": "No", "code": "NO" },
5688
- { "label": "Maybe", "code": "MAYBE" }
5689
- ]
5690
- }
5691
- },
5692
- {
5693
- "name": "reasonForNo",
5694
- "label": "Why not?",
5695
- "type": "TEXT_INPUT",
5696
- "subType": "LONG",
5697
- "visibilityExpression": "wouldRecommend === 'NO'",
5698
- "required": true
5699
- },
5700
- {
5701
- "name": "improvements",
5702
- "label": "What can we improve?",
5703
- "type": "CHECKBOX",
5704
- "subType": "LIST",
5705
- "optionConfig": {
5706
- "optionList": [
5707
- { "label": "Customer Service", "code": "SERVICE" },
5708
- { "label": "Product Quality", "code": "QUALITY" },
5709
- { "label": "Pricing", "code": "PRICE" },
5710
- { "label": "Delivery Speed", "code": "DELIVERY" }
5711
- ]
5712
- }
5713
- },
5714
- {
5715
- "name": "additionalComments",
5716
- "label": "Additional Comments",
5717
- "type": "TEXT_INPUT",
5718
- "subType": "LONG",
5719
- "textConfig": {
5720
- "length": {
5721
- "max": 1000
5722
- }
5723
- }
5724
- }
5725
- ]
5726
- }
6333
+ surveyForm: `{
6334
+ "entityType": "SURVEY",
6335
+ "label": "Customer Satisfaction Survey",
6336
+ "formType": "SECTION",
6337
+ "sectionConfig": {
6338
+ "children": [
6339
+ {
6340
+ "name": "overallRating",
6341
+ "label": "Overall Experience",
6342
+ "type": "RATING",
6343
+ "subType": "STAR",
6344
+ "required": true,
6345
+ "ratingConfig": {
6346
+ "maxRating": 5,
6347
+ "allowHalf": false
6348
+ }
6349
+ },
6350
+ {
6351
+ "name": "wouldRecommend",
6352
+ "label": "Would you recommend us?",
6353
+ "type": "RADIO",
6354
+ "subType": "SINGLE",
6355
+ "required": true,
6356
+ "optionConfig": {
6357
+ "optionList": [
6358
+ { "label": "Yes", "code": "YES" },
6359
+ { "label": "No", "code": "NO" },
6360
+ { "label": "Maybe", "code": "MAYBE" }
6361
+ ]
6362
+ }
6363
+ },
6364
+ {
6365
+ "name": "reasonForNo",
6366
+ "label": "Why not?",
6367
+ "type": "TEXT_INPUT",
6368
+ "subType": "LONG",
6369
+ "visibilityExpression": "wouldRecommend === 'NO'",
6370
+ "required": true
6371
+ },
6372
+ {
6373
+ "name": "improvements",
6374
+ "label": "What can we improve?",
6375
+ "type": "CHECKBOX",
6376
+ "subType": "LIST",
6377
+ "optionConfig": {
6378
+ "optionList": [
6379
+ { "label": "Customer Service", "code": "SERVICE" },
6380
+ { "label": "Product Quality", "code": "QUALITY" },
6381
+ { "label": "Pricing", "code": "PRICE" },
6382
+ { "label": "Delivery Speed", "code": "DELIVERY" }
6383
+ ]
6384
+ }
6385
+ },
6386
+ {
6387
+ "name": "additionalComments",
6388
+ "label": "Additional Comments",
6389
+ "type": "TEXT_INPUT",
6390
+ "subType": "LONG",
6391
+ "textConfig": {
6392
+ "length": {
6393
+ "max": 1000
6394
+ }
6395
+ }
6396
+ }
6397
+ ]
6398
+ }
5727
6399
  }`,
5728
6400
  // Job Application Form
5729
- jobApplicationForm: `{
5730
- "entityType": "JOB_APPLICATION",
5731
- "label": "Job Application",
5732
- "description": "Apply for a position at our company",
5733
- "formType": "STEPPER",
5734
- "stepperConfig": {
5735
- "children": [
5736
- {
5737
- "type": "GROUP",
5738
- "subType": "SECTION",
5739
- "sectionConfig": {
5740
- "label": "Personal Details",
5741
- "children": [
5742
- {
5743
- "type": "ROW",
5744
- "subType": "HORIZONTAL",
5745
- "children": [
5746
- {
5747
- "name": "firstName",
5748
- "label": "First Name",
5749
- "type": "TEXT_INPUT",
5750
- "subType": "SHORT",
5751
- "required": true
5752
- },
5753
- {
5754
- "name": "lastName",
5755
- "label": "Last Name",
5756
- "type": "TEXT_INPUT",
5757
- "subType": "SHORT",
5758
- "required": true
5759
- }
5760
- ]
5761
- },
5762
- {
5763
- "name": "email",
5764
- "label": "Email",
5765
- "type": "TEXT_INPUT",
5766
- "subType": "EMAIL",
5767
- "required": true
5768
- },
5769
- {
5770
- "name": "phone",
5771
- "label": "Phone",
5772
- "type": "TEXT_INPUT",
5773
- "subType": "PHONE",
5774
- "required": true
5775
- }
5776
- ]
5777
- }
5778
- },
5779
- {
5780
- "type": "GROUP",
5781
- "subType": "SECTION",
5782
- "sectionConfig": {
5783
- "label": "Experience",
5784
- "allowMulti": true,
5785
- "name": "experienceList",
5786
- "children": [
5787
- {
5788
- "name": "company",
5789
- "label": "Company Name",
5790
- "type": "TEXT_INPUT",
5791
- "subType": "SHORT",
5792
- "required": true
5793
- },
5794
- {
5795
- "name": "position",
5796
- "label": "Position",
5797
- "type": "TEXT_INPUT",
5798
- "subType": "SHORT",
5799
- "required": true
5800
- },
5801
- {
5802
- "type": "ROW",
5803
- "subType": "HORIZONTAL",
5804
- "children": [
5805
- {
5806
- "name": "startDate",
5807
- "label": "Start Date",
5808
- "type": "DATE",
5809
- "subType": "SINGLE",
5810
- "required": true
5811
- },
5812
- {
5813
- "name": "endDate",
5814
- "label": "End Date",
5815
- "type": "DATE",
5816
- "subType": "SINGLE"
5817
- }
5818
- ]
5819
- },
5820
- {
5821
- "name": "responsibilities",
5822
- "label": "Key Responsibilities",
5823
- "type": "TEXT_INPUT",
5824
- "subType": "LONG"
5825
- }
5826
- ]
5827
- }
5828
- },
5829
- {
5830
- "type": "GROUP",
5831
- "subType": "SECTION",
5832
- "sectionConfig": {
5833
- "label": "Skills & Qualifications",
5834
- "children": [
5835
- {
5836
- "name": "skills",
5837
- "label": "Technical Skills",
5838
- "type": "CHIP",
5839
- "subType": "MULTIPLE",
5840
- "optionConfig": {
5841
- "optionList": [
5842
- { "label": "JavaScript", "code": "JS" },
5843
- { "label": "TypeScript", "code": "TS" },
5844
- { "label": "Angular", "code": "ANGULAR" },
5845
- { "label": "React", "code": "REACT" },
5846
- { "label": "Node.js", "code": "NODE" },
5847
- { "label": "Python", "code": "PYTHON" }
5848
- ]
5849
- }
5850
- },
5851
- {
5852
- "name": "yearsOfExperience",
5853
- "label": "Years of Experience",
5854
- "type": "NUMBER_INPUT",
5855
- "subType": "INTEGER",
5856
- "required": true,
5857
- "numberConfig": {
5858
- "min": 0,
5859
- "max": 50
5860
- }
5861
- },
5862
- {
5863
- "name": "availableToStart",
5864
- "label": "Available to Start",
5865
- "type": "DATE",
5866
- "subType": "SINGLE",
5867
- "required": true,
5868
- "dateConfig": {
5869
- "allowFuture": true
5870
- }
5871
- }
5872
- ]
5873
- }
5874
- }
5875
- ],
5876
- "showStep": true,
5877
- "isHorizontal": true
5878
- }
5879
- }`
6401
+ jobApplicationForm: `{
6402
+ "entityType": "JOB_APPLICATION",
6403
+ "label": "Job Application",
6404
+ "description": "Apply for a position at our company",
6405
+ "formType": "STEPPER",
6406
+ "stepperConfig": {
6407
+ "children": [
6408
+ {
6409
+ "type": "GROUP",
6410
+ "subType": "SECTION",
6411
+ "sectionConfig": {
6412
+ "label": "Personal Details",
6413
+ "children": [
6414
+ {
6415
+ "type": "ROW",
6416
+ "subType": "HORIZONTAL",
6417
+ "children": [
6418
+ {
6419
+ "name": "firstName",
6420
+ "label": "First Name",
6421
+ "type": "TEXT_INPUT",
6422
+ "subType": "SHORT",
6423
+ "required": true
6424
+ },
6425
+ {
6426
+ "name": "lastName",
6427
+ "label": "Last Name",
6428
+ "type": "TEXT_INPUT",
6429
+ "subType": "SHORT",
6430
+ "required": true
6431
+ }
6432
+ ]
6433
+ },
6434
+ {
6435
+ "name": "email",
6436
+ "label": "Email",
6437
+ "type": "TEXT_INPUT",
6438
+ "subType": "EMAIL",
6439
+ "required": true
6440
+ },
6441
+ {
6442
+ "name": "phone",
6443
+ "label": "Phone",
6444
+ "type": "TEXT_INPUT",
6445
+ "subType": "PHONE",
6446
+ "required": true
6447
+ }
6448
+ ]
6449
+ }
6450
+ },
6451
+ {
6452
+ "type": "GROUP",
6453
+ "subType": "SECTION",
6454
+ "sectionConfig": {
6455
+ "label": "Experience",
6456
+ "allowMulti": true,
6457
+ "name": "experienceList",
6458
+ "children": [
6459
+ {
6460
+ "name": "company",
6461
+ "label": "Company Name",
6462
+ "type": "TEXT_INPUT",
6463
+ "subType": "SHORT",
6464
+ "required": true
6465
+ },
6466
+ {
6467
+ "name": "position",
6468
+ "label": "Position",
6469
+ "type": "TEXT_INPUT",
6470
+ "subType": "SHORT",
6471
+ "required": true
6472
+ },
6473
+ {
6474
+ "type": "ROW",
6475
+ "subType": "HORIZONTAL",
6476
+ "children": [
6477
+ {
6478
+ "name": "startDate",
6479
+ "label": "Start Date",
6480
+ "type": "DATE",
6481
+ "subType": "SINGLE",
6482
+ "required": true
6483
+ },
6484
+ {
6485
+ "name": "endDate",
6486
+ "label": "End Date",
6487
+ "type": "DATE",
6488
+ "subType": "SINGLE"
6489
+ }
6490
+ ]
6491
+ },
6492
+ {
6493
+ "name": "responsibilities",
6494
+ "label": "Key Responsibilities",
6495
+ "type": "TEXT_INPUT",
6496
+ "subType": "LONG"
6497
+ }
6498
+ ]
6499
+ }
6500
+ },
6501
+ {
6502
+ "type": "GROUP",
6503
+ "subType": "SECTION",
6504
+ "sectionConfig": {
6505
+ "label": "Skills & Qualifications",
6506
+ "children": [
6507
+ {
6508
+ "name": "skills",
6509
+ "label": "Technical Skills",
6510
+ "type": "CHIP",
6511
+ "subType": "MULTIPLE",
6512
+ "optionConfig": {
6513
+ "optionList": [
6514
+ { "label": "JavaScript", "code": "JS" },
6515
+ { "label": "TypeScript", "code": "TS" },
6516
+ { "label": "Angular", "code": "ANGULAR" },
6517
+ { "label": "React", "code": "REACT" },
6518
+ { "label": "Node.js", "code": "NODE" },
6519
+ { "label": "Python", "code": "PYTHON" }
6520
+ ]
6521
+ }
6522
+ },
6523
+ {
6524
+ "name": "yearsOfExperience",
6525
+ "label": "Years of Experience",
6526
+ "type": "NUMBER_INPUT",
6527
+ "subType": "INTEGER",
6528
+ "required": true,
6529
+ "numberConfig": {
6530
+ "min": 0,
6531
+ "max": 50
6532
+ }
6533
+ },
6534
+ {
6535
+ "name": "availableToStart",
6536
+ "label": "Available to Start",
6537
+ "type": "DATE",
6538
+ "subType": "SINGLE",
6539
+ "required": true,
6540
+ "dateConfig": {
6541
+ "allowFuture": true
6542
+ }
6543
+ }
6544
+ ]
6545
+ }
6546
+ }
6547
+ ],
6548
+ "showStep": true,
6549
+ "isHorizontal": true
6550
+ }
6551
+ }`,
6552
+ // Donor Form based on UI
6553
+ donorForm: `{
6554
+ "entityType": "DONOR",
6555
+ "label": "Donor Form",
6556
+ "formType": "SECTION",
6557
+ "sectionConfig": {
6558
+ "children": [
6559
+ {
6560
+ "type": "GROUP",
6561
+ "subType": "SECTION",
6562
+ "sectionConfig": {
6563
+ "label": "Donor Details",
6564
+ "name": "donorDetails",
6565
+ "children": [
6566
+ {
6567
+ "name": "legalName",
6568
+ "label": "Donor Legal Name",
6569
+ "type": "TEXT_INPUT",
6570
+ "subType": "SHORT",
6571
+ "required": true,
6572
+ "placeholder": "Enter the name"
6573
+ },
6574
+ {
6575
+ "type": "ROW",
6576
+ "subType": "HORIZONTAL",
6577
+ "children": [
6578
+ {
6579
+ "name": "shortCode",
6580
+ "label": "Donor Short Code",
6581
+ "type": "TEXT_INPUT",
6582
+ "subType": "SHORT",
6583
+ "colSpan": 3,
6584
+ "disabled": true,
6585
+ "defaultValue": "DLP-24-L1",
6586
+ "hint": "System-generated, unique internal code"
6587
+ },
6588
+ {
6589
+ "name": "donorType",
6590
+ "label": "Donor Type",
6591
+ "type": "DROPDOWN",
6592
+ "subType": "SINGLE",
6593
+ "colSpan": 3,
6594
+ "required": true,
6595
+ "placeholder": "Select",
6596
+ "optionConfig": {
6597
+ "optionList": [
6598
+ { "label": "Individual", "code": "INDIVIDUAL" },
6599
+ { "label": "Corporate", "code": "CORPORATE" },
6600
+ { "label": "Trust", "code": "TRUST" }
6601
+ ]
6602
+ }
6603
+ },
6604
+ {
6605
+ "name": "regNumber",
6606
+ "label": "Registration / Legal Entity Number",
6607
+ "type": "TEXT_INPUT",
6608
+ "subType": "SHORT",
6609
+ "colSpan": 3,
6610
+ "placeholder": "Type",
6611
+ "hint": "CIN / Trust Reg. No / Govt ID (if applicable)"
6612
+ },
6613
+ {
6614
+ "name": "country",
6615
+ "label": "Country of Incorporation",
6616
+ "type": "DROPDOWN",
6617
+ "subType": "SINGLE",
6618
+ "colSpan": 3,
6619
+ "required": true,
6620
+ "placeholder": "Select",
6621
+ "optionConfig": {
6622
+ "optionList": [
6623
+ { "label": "India", "code": "IN" },
6624
+ { "label": "USA", "code": "US" },
6625
+ { "label": "UK", "code": "UK" }
6626
+ ]
6627
+ }
6628
+ }
6629
+ ]
6630
+ },
6631
+ {
6632
+ "type": "ROW",
6633
+ "subType": "HORIZONTAL",
6634
+ "children": [
6635
+ {
6636
+ "name": "profilePicture",
6637
+ "label": "Profile Picture",
6638
+ "type": "FILE_UPLOAD",
6639
+ "subType": "SINGLE",
6640
+ "colSpan": 3,
6641
+ "attachmentConfig": {
6642
+ "multiple": false,
6643
+ "maxSizeMB": 2,
6644
+ "accept": "image/*",
6645
+ "acceptLabel": "JPG, PNG, SVG (max 2 MB)"
6646
+ }
6647
+ },
6648
+ {
6649
+ "name": "website",
6650
+ "label": "Organization Website (Optional)",
6651
+ "type": "TEXT_INPUT",
6652
+ "subType": "SHORT",
6653
+ "colSpan": 9,
6654
+ "placeholder": "Add your website link or LinkedIn profile",
6655
+ "hint": "Add your website link to automatically fetch your logo, powered by Clearbit."
6656
+ }
6657
+ ]
6658
+ }
6659
+ ]
6660
+ }
6661
+ },
6662
+ {
6663
+ "type": "GROUP",
6664
+ "subType": "SECTION",
6665
+ "sectionConfig": {
6666
+ "label": "Primary Contact",
6667
+ "allowMulti": true,
6668
+ "name": "contacts",
6669
+ "children": [
6670
+ {
6671
+ "type": "ROW",
6672
+ "subType": "HORIZONTAL",
6673
+ "children": [
6674
+ {
6675
+ "name": "contactName",
6676
+ "label": "Full Name",
6677
+ "type": "TEXT_INPUT",
6678
+ "subType": "SHORT",
6679
+ "colSpan": 6,
6680
+ "required": true,
6681
+ "placeholder": "Enter the name"
6682
+ },
6683
+ {
6684
+ "name": "designation",
6685
+ "label": "Designation/Title",
6686
+ "type": "DROPDOWN",
6687
+ "subType": "SINGLE",
6688
+ "colSpan": 6,
6689
+ "required": true,
6690
+ "placeholder": "Select",
6691
+ "optionConfig": {
6692
+ "optionList": [
6693
+ { "label": "CEO", "code": "CEO" },
6694
+ { "label": "Manager", "code": "MANAGER" },
6695
+ { "label": "Other", "code": "OTHER" }
6696
+ ]
6697
+ }
6698
+ }
6699
+ ]
6700
+ },
6701
+ {
6702
+ "type": "ROW",
6703
+ "subType": "HORIZONTAL",
6704
+ "children": [
6705
+ {
6706
+ "name": "email",
6707
+ "label": "Email Address",
6708
+ "type": "TEXT_INPUT",
6709
+ "subType": "EMAIL",
6710
+ "colSpan": 4,
6711
+ "required": true,
6712
+ "placeholder": "Enter your email address"
6713
+ },
6714
+ {
6715
+ "name": "phone",
6716
+ "label": "Phone Number",
6717
+ "type": "TEXT_INPUT",
6718
+ "subType": "PHONE",
6719
+ "colSpan": 4,
6720
+ "required": true,
6721
+ "placeholder": "+91 7007713990"
6722
+ },
6723
+ {
6724
+ "name": "communicationPreference",
6725
+ "label": "Communication Preference",
6726
+ "type": "RADIO",
6727
+ "subType": "SINGLE",
6728
+ "colSpan": 4,
6729
+ "required": true,
6730
+ "optionConfig": {
6731
+ "optionList": [
6732
+ { "label": "Email", "code": "EMAIL" },
6733
+ { "label": "Phone Number", "code": "PHONE" }
6734
+ ]
6735
+ }
6736
+ }
6737
+ ]
6738
+ }
6739
+ ]
6740
+ }
6741
+ }
6742
+ ]
6743
+ }
6744
+ }`,
6745
+ // ──────────────────────────────────────────────────────────────────────────
6746
+ // User Registration Form — Basic Details / Primary Address / User Role
6747
+ // ──────────────────────────────────────────────────────────────────────────
6748
+ userBasicDetailsForm: `{
6749
+ "entityType": "USER_REGISTRATION",
6750
+ "label": "User Registration",
6751
+ "description": "Fill in your details to create a new account",
6752
+ "formType": "SECTION",
6753
+ "sectionConfig": {
6754
+ "children": [
6755
+
6756
+ {
6757
+ "type": "GROUP",
6758
+ "subType": "SECTION",
6759
+ "sectionConfig": {
6760
+ "label": "Basic Details",
6761
+ "name": "basicDetails",
6762
+ "children": [
6763
+
6764
+ {
6765
+ "type": "ROW",
6766
+ "subType": "HORIZONTAL",
6767
+ "children": [
6768
+ {
6769
+ "name": "firstName",
6770
+ "label": "First Name",
6771
+ "type": "TEXT_INPUT",
6772
+ "subType": "SHORT",
6773
+ "required": true,
6774
+ "placeholder": "Enter first name"
6775
+ },
6776
+ {
6777
+ "name": "lastName",
6778
+ "label": "Last Name",
6779
+ "type": "TEXT_INPUT",
6780
+ "subType": "SHORT",
6781
+ "required": true,
6782
+ "placeholder": "Enter last name"
6783
+ }
6784
+ ]
6785
+ },
6786
+
6787
+ {
6788
+ "type": "ROW",
6789
+ "subType": "HORIZONTAL",
6790
+ "children": [
6791
+ {
6792
+ "name": "contactNo",
6793
+ "label": "Contact No",
6794
+ "type": "TEXT_INPUT",
6795
+ "subType": "PHONE",
6796
+ "required": true,
6797
+ "placeholder": "+91 9999999999",
6798
+ "hint": "Enter a valid 10-digit mobile number"
6799
+ },
6800
+ {
6801
+ "name": "emailId",
6802
+ "label": "Email ID",
6803
+ "type": "TEXT_INPUT",
6804
+ "subType": "EMAIL",
6805
+ "required": true,
6806
+ "placeholder": "example@domain.com"
6807
+ }
6808
+ ]
6809
+ },
6810
+
6811
+ {
6812
+ "type": "ROW",
6813
+ "subType": "HORIZONTAL",
6814
+ "children": [
6815
+ {
6816
+ "name": "loginId",
6817
+ "label": "Login / User ID",
6818
+ "type": "TEXT_INPUT",
6819
+ "subType": "SHORT",
6820
+ "required": true,
6821
+ "placeholder": "Choose a unique login ID",
6822
+ "hint": "Alphanumeric, no spaces",
6823
+ "textConfig": {
6824
+ "length": { "min": 4, "max": 30 },
6825
+ "pattern": "^[a-zA-Z0-9_]+$",
6826
+ "patternMessage": "Only letters, numbers and underscores are allowed"
6827
+ }
6828
+ },
6829
+ {
6830
+ "name": "password",
6831
+ "label": "Password",
6832
+ "type": "TEXT_INPUT",
6833
+ "subType": "PASSWORD",
6834
+ "required": true,
6835
+ "placeholder": "Min. 8 characters",
6836
+ "hint": "Use at least 8 characters with letters and numbers",
6837
+ "textConfig": {
6838
+ "length": { "min": 8, "max": 64 }
6839
+ }
6840
+ },
6841
+ {
6842
+ "name": "confirmPassword",
6843
+ "label": "Confirm Password",
6844
+ "type": "TEXT_INPUT",
6845
+ "subType": "PASSWORD",
6846
+ "required": true,
6847
+ "placeholder": "Re-enter your password",
6848
+ "textConfig": {
6849
+ "matchField": "password"
6850
+ }
6851
+ }
6852
+ ]
6853
+ }
6854
+
6855
+ ]
6856
+ }
6857
+ },
6858
+
6859
+ {
6860
+ "type": "GROUP",
6861
+ "subType": "SECTION",
6862
+ "sectionConfig": {
6863
+ "label": "Primary Address",
6864
+ "name": "primaryAddress",
6865
+ "children": [
6866
+
6867
+ {
6868
+ "type": "ROW",
6869
+ "subType": "HORIZONTAL",
6870
+ "children": [
6871
+ {
6872
+ "name": "addressLine1",
6873
+ "label": "Address Line 1",
6874
+ "type": "TEXT_INPUT",
6875
+ "subType": "SHORT",
6876
+ "required": true,
6877
+ "placeholder": "House / Flat No., Building, Street"
6878
+ },
6879
+ {
6880
+ "name": "addressLine2",
6881
+ "label": "Address Line 2",
6882
+ "type": "TEXT_INPUT",
6883
+ "subType": "SHORT",
6884
+ "placeholder": "Area / Locality / Colony (optional)"
6885
+ }
6886
+ ]
6887
+ },
6888
+
6889
+ {
6890
+ "type": "ROW",
6891
+ "subType": "HORIZONTAL",
6892
+ "children": [
6893
+ {
6894
+ "name": "country",
6895
+ "label": "Country",
6896
+ "type": "DROPDOWN",
6897
+ "subType": "SINGLE",
6898
+ "required": true,
6899
+ "placeholder": "Select Country",
6900
+ "optionConfig": {
6901
+ "optionList": [
6902
+ { "label": "India", "code": "REGION_COUNTRY.INDIA" },
6903
+ { "label": "USA", "code": "REGION_COUNTRY.USA" }
6904
+ ]
6905
+ }
6906
+ },
6907
+ {
6908
+ "name": "state",
6909
+ "label": "State",
6910
+ "type": "DROPDOWN",
6911
+ "subType": "SINGLE",
6912
+ "required": true,
6913
+ "placeholder": "Select State",
6914
+ "optionConfig": {
6915
+ "apiUrl": "https://dev.platformcommons.org/ctld/api/globalrefdata/v1?refClass=GREF.REGION_STATE&parentRefClass=GREF.REGION_COUNTRY",
6916
+ "labelPath": "label",
6917
+ "valuePath": "dataCode",
6918
+ "dependencies": {
6919
+ "parentRefData": "country"
6920
+ }
6921
+ }
6922
+ },
6923
+ {
6924
+ "name": "city",
6925
+ "label": "City",
6926
+ "type": "DROPDOWN",
6927
+ "subType": "SINGLE",
6928
+ "required": true,
6929
+ "placeholder": "Select City",
6930
+ "optionConfig": {
6931
+ "apiUrl": "https://dev.platformcommons.org/ctld/api/globalrefdata/v1?refClass=GREF.REGION_DISTRICT&parentRefClass=GREF.REGION_STATE",
6932
+ "labelPath": "label",
6933
+ "valuePath": "dataCode",
6934
+ "dependencies": {
6935
+ "parentRefData": "state"
6936
+ }
6937
+ }
6938
+ }
6939
+ ]
6940
+ },
6941
+
6942
+ {
6943
+ "type": "ROW",
6944
+ "subType": "HORIZONTAL",
6945
+ "children": [
6946
+ {
6947
+ "name": "block",
6948
+ "label": "Block / Taluka",
6949
+ "type": "DROPDOWN",
6950
+ "subType": "SINGLE",
6951
+ "placeholder": "Select Block",
6952
+ "optionConfig": {
6953
+ "apiUrl": "https://dev.platformcommons.org/ctld/api/globalrefdata/v1?refClass=GREF.REGION_BLOCK&parentRefClass=GREF.REGION_DISTRICT",
6954
+ "labelPath": "label",
6955
+ "valuePath": "dataCode",
6956
+ "dependencies": {
6957
+ "parentRefData": "city"
6958
+ }
6959
+ }
6960
+ },
6961
+ {
6962
+ "name": "pinCode",
6963
+ "label": "PIN Code",
6964
+ "type": "TEXT_INPUT",
6965
+ "subType": "SHORT",
6966
+ "required": true,
6967
+ "placeholder": "6-digit PIN code",
6968
+ "textConfig": {
6969
+ "length": { "min": 6, "max": 6 },
6970
+ "pattern": "^[0-9]{6}$",
6971
+ "patternMessage": "Enter a valid 6-digit PIN code"
6972
+ }
6973
+ }
6974
+ ]
6975
+ }
6976
+
6977
+ ]
6978
+ }
6979
+ },
6980
+
6981
+ {
6982
+ "type": "GROUP",
6983
+ "subType": "SECTION",
6984
+ "sectionConfig": {
6985
+ "label": "User Role",
6986
+ "name": "userRole",
6987
+ "type": "ROW",
6988
+ "subType": "HORIZONTAL",
6989
+ "children": [
6990
+ {
6991
+ "name": "role",
6992
+ "label": "User Role",
6993
+ "type": "DROPDOWN",
6994
+ "subType": "SINGLE",
6995
+ "required": true,
6996
+ "placeholder": "Select a role",
6997
+ "optionConfig": {
6998
+ "apiUrl": "https://dev.platformcommons.org/gateway/commons-report-service/api/v1/datasets/name/VMS_GET_TENANT_ROLES/execute?params=TENANT_ID=1823",
6999
+ "labelPath": "roleName",
7000
+ "valuePath": "code"
7001
+ }
7002
+ },
7003
+ {
7004
+ "name": "reportingRole",
7005
+ "label": "Reporting Role",
7006
+ "type": "DROPDOWN",
7007
+ "subType": "SINGLE",
7008
+ "required": true,
7009
+ "placeholder": "Select Reporting Role",
7010
+ "optionConfig": {
7011
+ "apiUrl": "https://dev.platformcommons.org/ctld/api/rolehierarchy/v1?criteria=roleCode.code='{{userRole}}' and isActive=1&includeAttributes=id,parentRoleCode",
7012
+ "dataPath": "payload",
7013
+ "labelPath": "parentRoleCode",
7014
+ "valuePath": "parentRoleCode",
7015
+ "dependencies": {
7016
+ "userRole": "role"
7017
+ }
7018
+ }
7019
+ },
7020
+ {
7021
+ "name": "reportingManager",
7022
+ "label": "Reporting Manager",
7023
+ "type": "DROPDOWN",
7024
+ "subType": "SINGLE",
7025
+ "required": true,
7026
+ "placeholder": "Select Reporting Manager",
7027
+ "optionConfig": {
7028
+ "apiUrl": "https://dev.platformcommons.org/ctld/api/tenant/user/role/criteria/v1?criteria=role.code='{{reportingRole}}' and isActive=1 and user.isActive=1",
7029
+ "dataPath": "payload",
7030
+ "labelPath": "user.userName",
7031
+ "valuePath": "user.id",
7032
+ "dependencies": {
7033
+ "reportingRole": "reportingRole"
7034
+ }
7035
+ }
7036
+ }
7037
+ ]
7038
+ }
7039
+ }
7040
+
7041
+ ]
7042
+ }
7043
+ }`,
7044
+ // ──────────────────────────────────────────────────────────────────────────
7045
+ // Document Upload Form — demonstrates the FILE_UPLOAD field type
7046
+ // ──────────────────────────────────────────────────────────────────────────
7047
+ documentUploadForm: `{
7048
+ "entityType": "DOCUMENT_UPLOAD",
7049
+ "label": "Documents",
7050
+ "description": "Upload the required documents",
7051
+ "formType": "SECTION",
7052
+ "sectionConfig": {
7053
+ "children": [
7054
+ {
7055
+ "type": "GROUP",
7056
+ "subType": "SECTION",
7057
+ "sectionConfig": {
7058
+ "label": "Documents",
7059
+ "name": "documents",
7060
+ "children": [
7061
+ {
7062
+ "name": "profilePicture",
7063
+ "label": "Profile Picture",
7064
+ "type": "FILE_UPLOAD",
7065
+ "subType": "SINGLE",
7066
+ "required": true,
7067
+ "attachmentConfig": {
7068
+ "multiple": false,
7069
+ "maxSizeMB": 5,
7070
+ "accept": ".jpg,.jpeg,.png,.webp,image/*",
7071
+ "acceptLabel": "JPG, PNG, WEBP"
7072
+ }
7073
+ },
7074
+ {
7075
+ "name": "certificates",
7076
+ "label": "Certificates",
7077
+ "type": "FILE_UPLOAD",
7078
+ "subType": "MULTIPLE",
7079
+ "attachmentConfig": {
7080
+ "multiple": true,
7081
+ "maxFiles": 5,
7082
+ "maxSizeMB": 10,
7083
+ "accept": ".pdf,.doc,.docx,.jpg,.jpeg,.png",
7084
+ "acceptLabel": "PDF, DOCX, JPG, PNG"
7085
+ }
7086
+ }
7087
+ ]
7088
+ }
7089
+ }
7090
+ ]
7091
+ }
7092
+ }`,
7093
+ // Demand Definition Form (matching user request image)
7094
+ demandDefinitionForm: `{
7095
+ "entityType": "DEMAND",
7096
+ "label": "Demand Definition",
7097
+ "formType": "SECTION",
7098
+ "sectionConfig": {
7099
+ "children": [
7100
+ {
7101
+ "type": "ROW",
7102
+ "subType": "HORIZONTAL",
7103
+ "children": [
7104
+ {
7105
+ "name": "beneficiaries",
7106
+ "label": "Required Beneficiaries",
7107
+ "type": "NUMBER_INPUT",
7108
+ "subType": "INTEGER",
7109
+ "required": true,
7110
+ "suffix": "Students",
7111
+ "placeholder": "0",
7112
+ "numberConfig": { "min": 0 }
7113
+ },
7114
+ {
7115
+ "name": "deliveryModel",
7116
+ "label": "Preferred Delivery Model",
7117
+ "type": "DROPDOWN",
7118
+ "subType": "SINGLE",
7119
+ "required": true,
7120
+ "placeholder": "Select",
7121
+ "optionConfig": {
7122
+ "optionList": [
7123
+ { "label": "Onsite Delivery", "code": "ONSITE" },
7124
+ { "label": "Hybrid Model", "code": "HYBRID" }
7125
+ ]
7126
+ }
7127
+ }
7128
+ ]
7129
+ },
7130
+ {
7131
+ "type": "ROW",
7132
+ "subType": "HORIZONTAL",
7133
+ "children": [
7134
+ {
7135
+ "name": "minAge",
7136
+ "label": "Min Target Age",
7137
+ "type": "NUMBER_INPUT",
7138
+ "subType": "INTEGER",
7139
+ "required": true,
7140
+ "placeholder": "0",
7141
+ "numberConfig": { "min": 0, "max": 100 },
7142
+ "onValidate": "!maxAge || minAge <= maxAge",
7143
+ "errorMessage": "Min Age cannot be greater than Max Age"
7144
+ },
7145
+ {
7146
+ "name": "maxAge",
7147
+ "label": "Max Target Age",
7148
+ "type": "NUMBER_INPUT",
7149
+ "subType": "INTEGER",
7150
+ "required": true,
7151
+ "placeholder": "100",
7152
+ "numberConfig": { "min": 0, "max": 100 },
7153
+ "onValidate": "!minAge || maxAge >= minAge",
7154
+ "errorMessage": "Max Age cannot be less than Min Age"
7155
+ }
7156
+ ]
7157
+ },
7158
+ {
7159
+ "type": "ROW",
7160
+ "subType": "HORIZONTAL",
7161
+ "children": [
7162
+ {
7163
+ "name": "male",
7164
+ "label": "Male Split (%)",
7165
+ "type": "NUMBER_INPUT",
7166
+ "subType": "INTEGER",
7167
+ "required": true,
7168
+ "suffix": "%",
7169
+ "placeholder": "50",
7170
+ "numberConfig": { "min": 0, "max": 100 },
7171
+ "onValidate": "(male || 0) + (female || 0) === 100",
7172
+ "errorMessage": "Male + Female % must equal 100%"
7173
+ },
7174
+ {
7175
+ "name": "female",
7176
+ "label": "Female Split (%)",
7177
+ "type": "NUMBER_INPUT",
7178
+ "subType": "INTEGER",
7179
+ "required": true,
7180
+ "suffix": "%",
7181
+ "placeholder": "50",
7182
+ "numberConfig": { "min": 0, "max": 100 },
7183
+ "onValidate": "(male || 0) + (female || 0) === 100",
7184
+ "errorMessage": "Male + Female % must equal 100%"
7185
+ }
7186
+ ]
7187
+ }
7188
+ ]
7189
+ }
7190
+ }`,
7191
+ // Project Info Form (matching user request image)
7192
+ // Project Form (Combined Info + Demand)
7193
+ projectInfoForm: `{
7194
+ "entityType": "PROJECT",
7195
+ "label": "UI_PLAYGROUND.SMART_FORM.PROJECT.TITLE",
7196
+ "formType": "SECTION",
7197
+ "sectionConfig": {
7198
+ "children": [
7199
+ {
7200
+ "type": "GROUP",
7201
+ "subType": "SECTION",
7202
+ "sectionConfig": {
7203
+ "label": "UI_PLAYGROUND.SMART_FORM.PROJECT.SECTION.INFO",
7204
+ "children": [
7205
+ {
7206
+ "type": "ROW",
7207
+ "subType": "HORIZONTAL",
7208
+ "children": [
7209
+ {
7210
+ "name": "projectName",
7211
+ "label": "UI_PLAYGROUND.SMART_FORM.PROJECT.NAME.LABEL",
7212
+ "placeholder": "UI_PLAYGROUND.SMART_FORM.PROJECT.NAME.PH",
7213
+ "type": "TEXT_INPUT",
7214
+ "subType": "SHORT",
7215
+ "required": true,
7216
+ "colSpan": 6
7217
+ },
7218
+ {
7219
+ "name": "programId",
7220
+ "label": "UI_PLAYGROUND.SMART_FORM.PROJECT.PROG_ID.LABEL",
7221
+ "type": "TEXT_INPUT",
7222
+ "subType": "SHORT",
7223
+ "defaultValue": "DLP-24-L1",
7224
+ "readonly": true,
7225
+ "colSpan": 6
7226
+ }
7227
+ ]
7228
+ },
7229
+ {
7230
+ "type": "ROW",
7231
+ "subType": "HORIZONTAL",
7232
+ "children": [
7233
+ {
7234
+ "name": "linkedProgram",
7235
+ "label": "UI_PLAYGROUND.SMART_FORM.PROJECT.LINKED_PROG.LABEL",
7236
+ "type": "TEXT_INPUT",
7237
+ "subType": "SHORT",
7238
+ "defaultValue": "National Skills Initiative",
7239
+ "readonly": true,
7240
+ "colSpan": 4
7241
+ },
7242
+ {
7243
+ "name": "linkedContract",
7244
+ "label": "UI_PLAYGROUND.SMART_FORM.PROJECT.LINKED_CONTRACT.LABEL",
7245
+ "type": "TEXT_INPUT",
7246
+ "subType": "SHORT",
7247
+ "defaultValue": "NSI-Gov-Oct23",
7248
+ "readonly": true,
7249
+ "colSpan": 4
7250
+ },
7251
+ {
7252
+ "name": "assignedDistrict",
7253
+ "label": "UI_PLAYGROUND.SMART_FORM.PROJECT.DISTRICT.LABEL",
7254
+ "type": "TEXT_INPUT",
7255
+ "subType": "SHORT",
7256
+ "defaultValue": "Bangalore Rural",
7257
+ "readonly": true,
7258
+ "colSpan": 4
7259
+ }
7260
+ ]
7261
+ }
7262
+ ]
7263
+ }
7264
+ },
7265
+ {
7266
+ "type": "GROUP",
7267
+ "subType": "SECTION",
7268
+ "sectionConfig": {
7269
+ "label": "UI_PLAYGROUND.SMART_FORM.PROJECT.SECTION.DEMAND",
7270
+ "children": [
7271
+ {
7272
+ "type": "ROW",
7273
+ "subType": "HORIZONTAL",
7274
+ "children": [
7275
+ {
7276
+ "name": "requiredBeneficiaries",
7277
+ "label": "UI_PLAYGROUND.SMART_FORM.PROJECT.BENEFICIARIES.LABEL",
7278
+ "placeholder": "UI_PLAYGROUND.SMART_FORM.PROJECT.BENEFICIARIES.PH",
7279
+ "type": "NUMBER_INPUT",
7280
+ "subType": "INTEGER",
7281
+ "required": true,
7282
+ "colSpan": 6,
7283
+ "numberConfig": {
7284
+ "min": 1
7285
+ }
7286
+ },
7287
+ {
7288
+ "name": "deliveryModel",
7289
+ "label": "UI_PLAYGROUND.SMART_FORM.PROJECT.DELIVERY_MODEL.LABEL",
7290
+ "type": "DROPDOWN",
7291
+ "subType": "SINGLE",
7292
+ "required": true,
7293
+ "colSpan": 6,
7294
+ "optionConfig": {
7295
+ "optionList": [
7296
+ {
7297
+ "label": "UI_PLAYGROUND.OPTION.IN_PERSON",
7298
+ "code": "IN_PERSON"
7299
+ },
7300
+ {
7301
+ "label": "UI_PLAYGROUND.OPTION.HYBRID",
7302
+ "code": "HYBRID"
7303
+ },
7304
+ {
7305
+ "label": "UI_PLAYGROUND.OPTION.ONLINE",
7306
+ "code": "ONLINE"
7307
+ }
7308
+ ]
7309
+ }
7310
+ }
7311
+ ]
7312
+ },
7313
+ {
7314
+ "type": "ROW",
7315
+ "subType": "HORIZONTAL",
7316
+ "children": [
7317
+ {
7318
+ "name": "minAge",
7319
+ "label": "UI_PLAYGROUND.SMART_FORM.PROJECT.AGE_MIN.LABEL",
7320
+ "type": "NUMBER_INPUT",
7321
+ "subType": "INTEGER",
7322
+ "required": true,
7323
+ "placeholder": "UI_PLAYGROUND.SMART_FORM.PROJECT.AGE_MIN.LABEL",
7324
+ "numberConfig": {
7325
+ "min": 0,
7326
+ "max": 100
7327
+ },
7328
+ "onValidate": "!maxAge || minAge <= maxAge",
7329
+ "errorMessage": "UI_PLAYGROUND.SMART_FORM.PROJECT.AGE_MIN.ERR"
7330
+ },
7331
+ {
7332
+ "name": "maxAge",
7333
+ "label": "UI_PLAYGROUND.SMART_FORM.PROJECT.AGE_MAX.LABEL",
7334
+ "type": "NUMBER_INPUT",
7335
+ "subType": "INTEGER",
7336
+ "required": true,
7337
+ "placeholder": "UI_PLAYGROUND.SMART_FORM.PROJECT.AGE_MAX.LABEL",
7338
+ "numberConfig": {
7339
+ "min": 0,
7340
+ "max": 100
7341
+ },
7342
+ "onValidate": "!minAge || maxAge >= minAge",
7343
+ "errorMessage": "UI_PLAYGROUND.SMART_FORM.PROJECT.AGE_MAX.ERR"
7344
+ }
7345
+ ]
7346
+ },
7347
+ {
7348
+ "type": "ROW",
7349
+ "subType": "HORIZONTAL",
7350
+ "children": [
7351
+ {
7352
+ "name": "maleSplit",
7353
+ "label": "UI_PLAYGROUND.SMART_FORM.PROJECT.MALE.LABEL",
7354
+ "type": "NUMBER_INPUT",
7355
+ "subType": "INTEGER",
7356
+ "required": true,
7357
+ "suffix": "%",
7358
+ "placeholder": "UI_PLAYGROUND.SMART_FORM.PROJECT.MALE.LABEL",
7359
+ "numberConfig": {
7360
+ "min": 0,
7361
+ "max": 100
7362
+ },
7363
+ "onValidate": "(maleSplit || 0) + (femaleSplit || 0) === 100",
7364
+ "errorMessage": "UI_PLAYGROUND.SMART_FORM.PROJECT.GENDER_SPLIT.ERR"
7365
+ },
7366
+ {
7367
+ "name": "femaleSplit",
7368
+ "label": "UI_PLAYGROUND.SMART_FORM.PROJECT.FEMALE.LABEL",
7369
+ "type": "NUMBER_INPUT",
7370
+ "subType": "INTEGER",
7371
+ "required": true,
7372
+ "suffix": "%",
7373
+ "placeholder": "UI_PLAYGROUND.SMART_FORM.PROJECT.FEMALE.LABEL",
7374
+ "numberConfig": {
7375
+ "min": 0,
7376
+ "max": 100
7377
+ },
7378
+ "onValidate": "(maleSplit || 0) + (femaleSplit || 0) === 100",
7379
+ "errorMessage": "UI_PLAYGROUND.SMART_FORM.PROJECT.GENDER_SPLIT.ERR"
7380
+ }
7381
+ ]
7382
+ }
7383
+ ]
7384
+ }
7385
+ }
7386
+ ]
7387
+ }
7388
+ }
7389
+ `,
5880
7390
  };
5881
7391
 
5882
7392
  var smartForm_examples = /*#__PURE__*/Object.freeze({
@@ -5892,5 +7402,5 @@ var smartForm_examples = /*#__PURE__*/Object.freeze({
5892
7402
  * Generated bundle index. Do not edit.
5893
7403
  */
5894
7404
 
5895
- export { AlertComponent, AlertModule, ButtonComponent, ButtonModule, CheckboxComponent, ConfigurableFormComponent, configurableForm_examples as ConfigurableFormExamples, ConfigurableFormModule, ConfirmationModalComponent, ConfirmationModalModule, DEFAULT_ITEMS_PER_PAGE, DEFAULT_PAGE_SIZE_OPTIONS, DatepickerComponent, DropdownComponent, ExpressionService, FilterComponent, FilterModule, FilterSidebarComponent, FilterSidebarModule, FormComponentsModule, InputComponent, MaterialModule, NAV_ORIENTATION_DEFAULT, NAV_VARIANT_DEFAULT, NavComponent, NavModule, PAGINATION_THEME_DARK, PAGINATION_THEME_DEFAULT, PaginationComponent, PaginationModule, RadioComponent, SearchComponent, SharedUiModule, SmartFormComponent, SmartFormController, smartForm_examples as SmartFormExamples, SmartFormModule, SmartTableComponent, SmartTableModule, SummaryCardComponent, SummaryCardModule, ToggleComponent, ValidationUtils, clearLocalStorage, clearSessionStorage, getLocalStorageItem, getSessionStorageItem, removeLocalStorageItem, removeSessionStorageItem, setLocalStorageItem, setSessionStorageItem };
7405
+ export { AlertComponent, AlertModule, ButtonComponent, ButtonModule, CheckboxComponent, ConfigurableFormComponent, configurableForm_examples as ConfigurableFormExamples, ConfigurableFormModule, ConfirmationModalComponent, ConfirmationModalModule, DEFAULT_ITEMS_PER_PAGE, DEFAULT_PAGE_SIZE_OPTIONS, DatepickerComponent, DropdownComponent, ExpressionService, FilterComponent, FilterModule, FilterSidebarComponent, FilterSidebarModule, FormComponentsModule, InputComponent, MaterialModule, NAV_ORIENTATION_DEFAULT, NAV_VARIANT_DEFAULT, NavComponent, NavModule, PAGINATION_THEME_DARK, PAGINATION_THEME_DEFAULT, PaginationComponent, PaginationModule, RadioComponent, SearchComponent, SharedUiModule, SmartFormComponent, SmartFormController, smartForm_examples as SmartFormExamples, SmartFormModule, SmartTableComponent, SmartTableModule, StringUtils, SummaryCardComponent, SummaryCardModule, ToggleComponent, ValidationUtils, clearLocalStorage, clearSessionStorage, getLocalStorageItem, getSessionStorageItem, removeLocalStorageItem, removeSessionStorageItem, setLocalStorageItem, setSessionStorageItem, translateConfig };
5896
7406
  //# sourceMappingURL=commons-shared-web-ui.mjs.map