commons-shared-web-ui 0.0.17 → 0.0.19

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.
@@ -4745,7 +4745,7 @@ class FormFieldComponent {
4745
4745
  const current = this.value || [];
4746
4746
  const idx = current.indexOf(entry);
4747
4747
  if (idx !== -1) {
4748
- const updatedEntry = { ...entry, dataUrl: resolvedUrl, isUploading: false };
4748
+ const updatedEntry = { ...entry, dataUrl: resolvedUrl, id: response?.id, isUploading: false };
4749
4749
  const updatedList = [...current];
4750
4750
  updatedList[idx] = updatedEntry;
4751
4751
  this.updateValue(updatedList);
@@ -5364,11 +5364,11 @@ class FormFieldComponent {
5364
5364
  }
5365
5365
  }
5366
5366
  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 });
5367
- 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" }, viewQueries: [{ propertyName: "mediaDeviceInput", first: true, predicate: ["mediaDeviceInput"], descendants: true }], 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 [class.multi-save-active]=\"config.sectionConfig?.multiSaveConfig?.active\">\r\n\r\n <!-- Multi-Save Header with Add button (top-right style) -->\r\n <div class=\"multi-save-header\" *ngIf=\"config.sectionConfig?.multiSaveConfig?.active\">\r\n <h3 class=\"group-label\" *ngIf=\"config.sectionConfig?.label\">{{ config.sectionConfig!.label }}</h3>\r\n <lib-button [variant]=\"'outline'\" [icon]=\"{type: 'material', value: 'add'}\" (click)=\"addGroupInstance()\"\r\n class=\"btn-add-multi\">\r\n {{ addMultiLabel }}\r\n </lib-button>\r\n </div>\r\n\r\n <!-- Standard Header for non-multiSave -->\r\n <h3 class=\"group-label\" *ngIf=\"config.sectionConfig?.label && !config.sectionConfig?.multiSaveConfig?.active\">{{\r\n config.sectionConfig!.label }}</h3>\r\n\r\n <div *ngFor=\"let instance of instanceList; trackBy: trackByInstanceId; let i = index\" class=\"group-instance\"\r\n [class.is-editing]=\"instance.isEditing\"\r\n [class.is-card]=\"config.sectionConfig?.multiSaveConfig?.active && !instance.isEditing\">\r\n\r\n <!-- 1. EDIT MODE / UNSAVED / STANDARD STATE -->\r\n <div [hidden]=\"config.sectionConfig?.multiSaveConfig?.active && !instance.isEditing\">\r\n <!-- Instance header \u2014 show remove only when more than 1 instance -->\r\n <div class=\"group-header\" *ngIf=\"!config.sectionConfig?.multiSaveConfig?.active && 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 <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\r\n <!-- SAVE / CANCEL BUTTONS for MultiSave in Editing phase -->\r\n <div class=\"group-footer\" *ngIf=\"config.sectionConfig?.multiSaveConfig?.active\">\r\n <span class=\"field-error\" *ngIf=\"multiSaveError\">{{ multiSaveError }}</span>\r\n <div class=\"footer-actions\">\r\n <lib-button [variant]=\"'outline'\" (click)=\"cancelGroupInstance(i)\">Cancel</lib-button>\r\n <lib-button [variant]=\"'primary'\" (click)=\"saveGroupInstance(i)\">Save</lib-button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 2. CARD VIEW (Saved State) -->\r\n <ng-container *ngIf=\"config.sectionConfig?.multiSaveConfig?.active && !instance.isEditing\">\r\n <div class=\"card-view\" [class.is-expanded]=\"instance.isExpanded\">\r\n <div class=\"card-content\">\r\n <span class=\"card-title\">{{ instance.fg.get(config.sectionConfig.multiSaveConfig.summaryField || '')?.value\r\n || '\u2014' }}</span>\r\n <span class=\"card-desc\" *ngIf=\"config.sectionConfig.multiSaveConfig.descriptionField\">\r\n {{ instance.fg.get(config.sectionConfig.multiSaveConfig.descriptionField)?.value }}\r\n </span>\r\n </div>\r\n <div class=\"card-actions\">\r\n <mat-icon class=\"icon-delete\" (click)=\"removeGroupInstance(i, true)\">delete_outline</mat-icon>\r\n <mat-icon class=\"icon-edit\" (click)=\"editGroupInstance(i)\">edit_outline</mat-icon>\r\n <mat-icon class=\"icon-expand\" (click)=\"toggleExpandGroupInstance(i)\">\r\n {{ instance.isExpanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}\r\n </mat-icon>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- Standard Add Button for non-multiSave -->\r\n <lib-button *ngIf=\"!config.sectionConfig?.multiSaveConfig?.active\" [variant]=\"'outline'\"\r\n [icon]=\"{type: 'material', value: 'add'}\" (click)=\"addGroupInstance()\" 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 class=\"char-count-hint\" *ngIf=\"showCharCount\">\r\n {{ remainingCharacters }} characters remaining\r\n </div>\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 <div class=\"input-group\" [class.readonly]=\"config.readonly\">\r\n <input matInput [matDatepicker]=\"datePicker\" class=\"field-input date-input has-icon-right\"\r\n [formControlName]=\"config.name!\" [min]=\"config.dateConfig?.minDate\" [max]=\"config.dateConfig?.maxDate\"\r\n [class.is-invalid]=\"errorMessage\" [placeholder]=\"config.placeholder || ''\" [readonly]=\"config.readonly\"\r\n (click)=\"!config.readonly && datePicker.open()\">\r\n <div class=\"date-icon-wrapper\" *ngIf=\"!config.readonly\">\r\n <mat-datepicker-toggle matSuffix [for]=\"datePicker\"></mat-datepicker-toggle>\r\n </div>\r\n <mat-datepicker #datePicker></mat-datepicker>\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 Time 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=\"isTimeField\" 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 <input type=\"time\" class=\"field-input time-input\" [formControlName]=\"config.name!\"\r\n [class.is-invalid]=\"errorMessage\" [readonly]=\"!!config.readonly\">\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 Autocomplete \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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=\"isAutocomplete\" 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 <!-- Hidden real control (stores the code value) -->\r\n <input type=\"hidden\" [formControlName]=\"config.name!\">\r\n\r\n <div class=\"autocomplete-wrapper\" [class.is-invalid]=\"errorMessage\" [class.readonly]=\"config.readonly\">\r\n <!-- Search icon -->\r\n <mat-icon class=\"ac-search-icon\">search</mat-icon>\r\n\r\n <input class=\"field-input ac-input\" [formControl]=\"autocompleteInputCtrl\" [matAutocomplete]=\"auto\"\r\n [placeholder]=\"config.placeholder || 'Search\u2026'\" [readonly]=\"!!config.readonly\" [class.is-invalid]=\"errorMessage\"\r\n (blur)=\"onAutocompleteClear()\" autocomplete=\"off\">\r\n\r\n <!-- Clear button -->\r\n <button type=\"button\" class=\"ac-clear-btn\" *ngIf=\"autocompleteInputCtrl.value && !config.readonly\"\r\n (click)=\"autocompleteInputCtrl.setValue(''); updateValue(null)\" tabindex=\"-1\" aria-label=\"Clear\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n\r\n <mat-autocomplete #auto=\"matAutocomplete\" [panelWidth]=\"'auto'\">\r\n <mat-option *ngFor=\"let option of filteredOptions\" [value]=\"option.label\"\r\n (onSelectionChange)=\"onAutocompleteSelected(option)\">\r\n {{ option.label }}\r\n </mat-option>\r\n <mat-option *ngIf=\"filteredOptions.length === 0\" disabled class=\"ac-no-results\">\r\n No results found\r\n </mat-option>\r\n </mat-autocomplete>\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 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)\" hidden>\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 Rich Text \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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=\"isRichText\" class=\"field-wrapper component-rich-text\" [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=\"rich-text-container\" [class.is-invalid]=\"errorMessage\">\r\n <quill-editor [formControlName]=\"config.name!\" class=\"rich-text-editor\"\r\n [placeholder]=\"config.richTextConfig?.placeholder || config.placeholder || ''\"\r\n [styles]=\"{height: config.richTextConfig?.height || '200px'}\">\r\n </quill-editor>\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 class=\"char-count-hint\" *ngIf=\"showCharCount\">\r\n {{ remainingCharacters }} characters remaining\r\n </div>\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\" hidden [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\" [class.uploading]=\"f.isUploading\">\r\n\r\n <!-- Uploading spinner (shown while API call is in progress) -->\r\n <ng-container *ngIf=\"f.isUploading; else fileReady\">\r\n <div class=\"upload-spinner\"></div>\r\n <div class=\"file-info\">\r\n <span class=\"file-name\" [title]=\"f.name\">{{ f.name }}</span>\r\n <span class=\"file-size uploading-label\">Uploading...</span>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Normal state once upload is done -->\r\n <ng-template #fileReady>\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 </ng-template>\r\n\r\n <!-- Remove button \u2014 disabled while uploading -->\r\n <lib-button [variant]=\"'danger-outline'\" [disabled]=\"f.isUploading\"\r\n (click)=\"!f.isUploading && 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 <!-- \u2550\u2550 Media Upload (Type 2) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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=\"isMediaUpload\" class=\"field-wrapper media-upload-wrapper\" [formGroup]=\"formGroup\">\r\n\r\n <!-- Hidden file input lives outside *ngIf \u2014 triggered via ViewChild -->\r\n <input #mediaDeviceInput type=\"file\" hidden multiple accept=\"image/*\" (change)=\"onMediaFileSelected($event)\">\r\n\r\n <!-- Two-column layout -->\r\n <div class=\"mu-layout\">\r\n\r\n <!-- \u2500\u2500 LEFT PANEL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"mu-left\">\r\n\r\n <!-- Header: title + max-items badge -->\r\n <div class=\"mu-header\">\r\n <h3 class=\"mu-title\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </h3>\r\n <span class=\"mu-badge\" *ngIf=\"config.attachmentConfig?.maxFiles\">\r\n {{ controller.labels['LBL_MEDIA_MAX_PREFIX'] || 'Maximum' }}\r\n {{ config.attachmentConfig?.maxFiles }}\r\n {{ controller.labels['LBL_MEDIA_MAX_SUFFIX'] || 'Image & Videos' }}\r\n </span>\r\n </div>\r\n\r\n <!-- Description -->\r\n <p class=\"mu-description\" *ngIf=\"config.attachmentConfig?.description\">\r\n {{ config.attachmentConfig?.description }}\r\n </p>\r\n <p class=\"mu-description\" *ngIf=\"!config.attachmentConfig?.description && controller.labels['LBL_MEDIA_DESC']\">\r\n {{ controller.labels['LBL_MEDIA_DESC'] }}\r\n </p>\r\n\r\n <!-- Feature bullet list -->\r\n <ul class=\"mu-features\"\r\n *ngIf=\"config.attachmentConfig?.features?.length || controller.labels['LBL_MEDIA_FEATURE_1']\">\r\n <ng-container *ngIf=\"config.attachmentConfig?.features?.length\">\r\n <li *ngFor=\"let f of config.attachmentConfig?.features\" class=\"mu-feature-item\">\r\n <mat-icon class=\"mu-check\">check</mat-icon>{{ f }}\r\n </li>\r\n </ng-container>\r\n <ng-container *ngIf=\"!config.attachmentConfig?.features?.length\">\r\n <li *ngIf=\"controller.labels['LBL_MEDIA_FEATURE_1']\" class=\"mu-feature-item\">\r\n <mat-icon class=\"mu-check\">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_1'] }}\r\n </li>\r\n <li *ngIf=\"controller.labels['LBL_MEDIA_FEATURE_2']\" class=\"mu-feature-item\">\r\n <mat-icon class=\"mu-check\">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_2'] }}\r\n </li>\r\n <li *ngIf=\"controller.labels['LBL_MEDIA_FEATURE_3']\" class=\"mu-feature-item\">\r\n <mat-icon class=\"mu-check\">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_3'] }}\r\n </li>\r\n </ng-container>\r\n </ul>\r\n\r\n <!-- Backdrop to close dropdown on outside click -->\r\n <div class=\"media-menu-backdrop\" *ngIf=\"showMediaMenu\"\r\n (click)=\"$event.stopPropagation(); showMediaMenu = false\"></div>\r\n\r\n <!-- Add Media button + dropdown -->\r\n <div class=\"media-add-container\" (click)=\"showMediaMenu = !showMediaMenu\">\r\n <lib-button id=\"btn-add-media-{{ config.name }}\" [variant]=\"'warning'\"\r\n [icon]=\"{type: 'material', value: 'add_photo_alternate'}\">\r\n {{ controller.labels['LBL_ADD_MEDIA'] || 'Add media' }}\r\n <mat-icon class=\"menu-chevron\">add</mat-icon>\r\n </lib-button>\r\n\r\n <div class=\"media-dropdown\" *ngIf=\"showMediaMenu\" role=\"menu\" (click)=\"$event.stopPropagation()\">\r\n <!-- Video -->\r\n <button id=\"btn-media-video-{{ config.name }}\" type=\"button\" class=\"media-dropdown-item\"\r\n (click)=\"onMediaMenuVideo(); showMediaMenu = false\" role=\"menuitem\">\r\n <span class=\"media-drop-icon media-drop-icon--video\"><mat-icon>videocam</mat-icon></span>\r\n <span class=\"media-drop-text\">\r\n <span class=\"media-drop-label\">{{ controller.labels['LBL_MEDIA_VIDEO'] || 'Video' }}</span>\r\n <span class=\"media-drop-desc\">{{ controller.labels['LBL_MEDIA_VIDEO_DESC'] || 'Add YouTube URL'\r\n }}</span>\r\n </span>\r\n </button>\r\n <!-- Device -->\r\n <button id=\"btn-media-device-{{ config.name }}\" type=\"button\" class=\"media-dropdown-item\"\r\n (click)=\"onMediaMenuDevice(); showMediaMenu = false\" role=\"menuitem\">\r\n <span class=\"media-drop-icon media-drop-icon--device\"><mat-icon>upload</mat-icon></span>\r\n <span class=\"media-drop-text\">\r\n <span class=\"media-drop-label\">{{ controller.labels['LBL_MEDIA_DEVICE'] || 'Upload from device'\r\n }}</span>\r\n <span class=\"media-drop-desc\">{{ controller.labels['LBL_MEDIA_DEVICE_DESC'] || 'Select images from your\r\n computer' }}</span>\r\n </span>\r\n </button>\r\n <!-- Library -->\r\n <button id=\"btn-media-library-{{ config.name }}\" type=\"button\" class=\"media-dropdown-item\"\r\n (click)=\"onMediaMenuLibrary(); showMediaMenu = false\" role=\"menuitem\">\r\n <span class=\"media-drop-icon media-drop-icon--library\"><mat-icon>photo_library</mat-icon></span>\r\n <span class=\"media-drop-text\">\r\n <span class=\"media-drop-label\">{{ controller.labels['LBL_MEDIA_LIBRARY'] || 'Upload from library'\r\n }}</span>\r\n <span class=\"media-drop-desc\">{{ controller.labels['LBL_MEDIA_LIBRARY_DESC'] || 'Choose from default\r\n images' }}</span>\r\n </span>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- YouTube URL Input (inline below button) -->\r\n <div class=\"youtube-input-panel\" *ngIf=\"showYoutubeInput\">\r\n <label class=\"youtube-panel-label\">\r\n {{ controller.labels['LBL_YOUTUBE_URL'] || 'Video URL' }}\r\n </label>\r\n <div class=\"youtube-input-row\">\r\n <input id=\"input-youtube-url-{{ config.name }}\" type=\"url\" class=\"field-input youtube-url-input\"\r\n [(ngModel)]=\"youtubeUrlInput\" [ngModelOptions]=\"{standalone: true}\"\r\n [placeholder]=\"controller.labels['PH_YOUTUBE_URL'] || 'Video URL'\" (keyup.enter)=\"addYoutubeMedia()\">\r\n <lib-button id=\"btn-add-youtube-{{ config.name }}\" [variant]=\"'secondary'\" (click)=\"addYoutubeMedia()\">\r\n {{ controller.labels['LBL_ADD'] || 'Add' }}\r\n </lib-button>\r\n </div>\r\n <span class=\"field-error\" *ngIf=\"youtubeUrlError\">{{ youtubeUrlError }}</span>\r\n </div>\r\n\r\n <div class=\"media-upload-status\" *ngIf=\"mediaUploadError\">\r\n <mat-icon class=\"status-icon\">error_outline</mat-icon>\r\n <span>{{ mediaUploadError }}</span>\r\n </div>\r\n\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n </div>\r\n <!-- end left panel -->\r\n\r\n <!-- \u2500\u2500 RIGHT PANEL (carousel) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"mu-right\">\r\n\r\n <!-- Carousel (when items exist) -->\r\n <div class=\"media-carousel-section\" *ngIf=\"mediaItems.length\">\r\n <div class=\"media-carousel-main\">\r\n <button id=\"btn-carousel-prev-{{ config.name }}\" type=\"button\" class=\"carousel-nav carousel-nav--prev\"\r\n (click)=\"mediaCarouselPrev()\" [disabled]=\"mediaCarouselIndex === 0\" aria-label=\"Previous\">\r\n <mat-icon>chevron_left</mat-icon>\r\n </button>\r\n\r\n <div class=\"carousel-viewer\" *ngFor=\"let item of mediaItems; let i = index\"\r\n [hidden]=\"i !== mediaCarouselIndex\">\r\n <div *ngIf=\"item.isUploading\" class=\"carousel-uploading\">\r\n <div class=\"carousel-spinner\"></div>\r\n <span>{{ controller.labels['LBL_UPLOADING'] || 'Uploading\u2026' }}</span>\r\n </div>\r\n <ng-container *ngIf=\"!item.isUploading && item.mediaType === 'youtube'\">\r\n <iframe class=\"carousel-iframe\" [src]=\"item.url | trustedUrl\" frameborder=\"0\" allowfullscreen\r\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\">\r\n </iframe>\r\n </ng-container>\r\n <ng-container *ngIf=\"!item.isUploading && item.mediaType === 'image'\">\r\n <img class=\"carousel-image\" [src]=\"item.url\" alt=\"Media\">\r\n </ng-container>\r\n <button id=\"btn-remove-media-{{ config.name }}-{{ i }}\" type=\"button\" class=\"carousel-remove-btn\"\r\n [disabled]=\"item.isUploading\" (click)=\"removeMediaItem(i)\" aria-label=\"Remove\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <button id=\"btn-carousel-next-{{ config.name }}\" type=\"button\" class=\"carousel-nav carousel-nav--next\"\r\n (click)=\"mediaCarouselNext()\" [disabled]=\"mediaCarouselIndex === mediaItems.length - 1\" aria-label=\"Next\">\r\n <mat-icon>chevron_right</mat-icon>\r\n </button>\r\n\r\n <div class=\"carousel-dots\">\r\n <span *ngFor=\"let item of mediaItems; let i = index\" class=\"carousel-dot\"\r\n [class.active]=\"i === mediaCarouselIndex\" (click)=\"mediaGoTo(i)\"></span>\r\n </div>\r\n </div>\r\n\r\n <!-- Thumbnail strip -->\r\n <div class=\"media-thumbnail-strip\">\r\n <div *ngFor=\"let item of mediaThumbnails; let i = index\" class=\"media-thumb\"\r\n [class.active]=\"i === mediaCarouselIndex\" (click)=\"mediaGoTo(i)\">\r\n <div *ngIf=\"item.isUploading\" class=\"thumb-uploading\">\r\n <div class=\"thumb-spinner\"></div>\r\n </div>\r\n <img *ngIf=\"!item.isUploading && item.mediaType === 'youtube' && item.thumbnailUrl\"\r\n [src]=\"item.thumbnailUrl\" class=\"thumb-img\" alt=\"Video thumbnail\">\r\n <div *ngIf=\"!item.isUploading && item.mediaType === 'youtube' && !item.thumbnailUrl\"\r\n class=\"thumb-yt-placeholder\">\r\n <mat-icon>play_circle</mat-icon>\r\n </div>\r\n <img *ngIf=\"!item.isUploading && item.mediaType === 'image' && item.url\" [src]=\"item.url\"\r\n class=\"thumb-img\" alt=\"Image thumbnail\">\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Empty right-side placeholder -->\r\n <div class=\"mu-right-empty\" *ngIf=\"!mediaItems.length\" (click)=\"onMediaMenuDevice()\">\r\n <mat-icon class=\"mu-right-empty-icon\">perm_media</mat-icon>\r\n <p>{{ controller.labels['LBL_ADD_MEDIA'] || 'Add media' }}</p>\r\n </div>\r\n\r\n </div>\r\n <!-- end right panel -->\r\n\r\n </div><!-- end mu-layout -->\r\n </div>\r\n\r\n\r\n <!-- \u2550\u2550 Library Image Picker Modal \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 class=\"media-library-overlay\" *ngIf=\"showLibraryModal\" (click)=\"closeLibraryModal()\"></div>\r\n <div class=\"media-library-modal\" *ngIf=\"showLibraryModal\" role=\"dialog\" aria-modal=\"true\">\r\n <div class=\"library-modal-header\">\r\n <h3 class=\"library-modal-title\">\r\n <mat-icon>photo_library</mat-icon>\r\n {{ controller.labels['LBL_LIBRARY_TITLE'] || 'Media Library' }}\r\n </h3>\r\n <button id=\"btn-close-library-{{ config.name }}\" type=\"button\" class=\"library-close-btn\"\r\n (click)=\"closeLibraryModal()\" aria-label=\"Close\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Loading -->\r\n <div class=\"library-loading\" *ngIf=\"libraryLoading\">\r\n <div class=\"lib-spinner\"></div>\r\n <span>{{ controller.labels['LBL_LOADING'] || 'Loading\u2026' }}</span>\r\n </div>\r\n\r\n <!-- Error -->\r\n <div class=\"library-error\" *ngIf=\"libraryError && !libraryLoading\">\r\n <mat-icon>error_outline</mat-icon>\r\n {{ libraryError }}\r\n </div>\r\n\r\n <!-- Image grid -->\r\n <div class=\"library-grid\" *ngIf=\"!libraryLoading && libraryImages.length\">\r\n <div *ngFor=\"let img of libraryImages; let li = index\" id=\"lib-img-{{ config.name }}-{{ li }}\"\r\n class=\"library-grid-item\" [class.selected]=\"isLibraryItemSelected(img)\" (click)=\"toggleLibraryItem(img)\">\r\n <img [src]=\"getLibraryItemUrl(img)\" class=\"library-grid-img\" alt=\"Library image\">\r\n <div class=\"library-check\" *ngIf=\"isLibraryItemSelected(img)\">\r\n <mat-icon>check_circle</mat-icon>\r\n </div>\r\n <div class=\"library-overlay-hover\"></div>\r\n </div>\r\n </div>\r\n\r\n <!-- Empty library -->\r\n <div class=\"library-empty\" *ngIf=\"!libraryLoading && !libraryError && libraryImages.length === 0\">\r\n <mat-icon>image_not_supported</mat-icon>\r\n <span>{{ controller.labels['LBL_LIBRARY_EMPTY'] || 'No images found in library.' }}</span>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"library-modal-footer\">\r\n <span class=\"library-selected-count\">\r\n {{ librarySelectedIds.size }} {{ controller.labels['LBL_LIBRARY_SELECTED'] || 'selected' }}\r\n </span>\r\n <div class=\"library-footer-actions\">\r\n <lib-button id=\"btn-library-cancel-{{ config.name }}\" [variant]=\"'outline'\" (click)=\"closeLibraryModal()\">\r\n {{ controller.labels['LBL_CANCEL'] || 'Cancel' }}\r\n </lib-button>\r\n <lib-button id=\"btn-library-confirm-{{ config.name }}\" [variant]=\"'primary'\"\r\n [disabled]=\"librarySelectedIds.size === 0\" (click)=\"confirmLibrarySelection()\">\r\n {{ controller.labels['LBL_LIBRARY_ADD_SELECTED'] || 'Add Selected' }}\r\n </lib-button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Location Field \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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=\"isLocation\" class=\"field-wrapper location-field-wrapper\" [formGroup]=\"formGroup\">\r\n\r\n <!-- Field label -->\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 <p class=\"location-subtitle\" *ngIf=\"config.hint\">{{ config.hint }}</p>\r\n\r\n <!-- Three-tab bar -->\r\n <div class=\"location-tabs\">\r\n <lib-button class=\"loc-tab-btn\" [variant]=\"locationActiveTab === 'VENUE' ? 'warning' : 'outline'\"\r\n (click)=\"onLocationTabChange('VENUE')\">\r\n {{ controller.labels['LBL_LOC_VENUE'] || 'Venue' }}\r\n </lib-button>\r\n <lib-button class=\"loc-tab-btn\" [variant]=\"locationActiveTab === 'ONLINE' ? 'warning' : 'outline'\"\r\n (click)=\"onLocationTabChange('ONLINE')\">\r\n {{ controller.labels['LBL_LOC_ONLINE'] || 'Online Event' }}\r\n </lib-button>\r\n <lib-button class=\"loc-tab-btn\" [variant]=\"locationActiveTab === 'TBA' ? 'warning' : 'outline'\"\r\n (click)=\"onLocationTabChange('TBA')\">\r\n {{ controller.labels['LBL_LOC_TBA'] || 'To be Announced' }}\r\n </lib-button>\r\n </div>\r\n\r\n <!-- VENUE TAB -->\r\n <div *ngIf=\"locationActiveTab === 'VENUE'\" class=\"loc-panel loc-venue-panel\">\r\n\r\n <p class=\"loc-section-label\">\r\n {{ controller.labels['LBL_LOC_ADDRESS'] || 'Location address' }}\r\n </p>\r\n\r\n <!-- Added venue rows -->\r\n <div class=\"loc-venue-list\" *ngIf=\"locationVenues.length > 0\">\r\n <div *ngFor=\"let venue of locationVenues; let i = index\" class=\"loc-venue-item\">\r\n <mat-icon class=\"loc-venue-search-icon\">search</mat-icon>\r\n <span class=\"loc-venue-text\">{{ venue.address || venue.name || venue.description }}</span>\r\n <button type=\"button\" class=\"loc-action-btn loc-delete-btn\" (click)=\"removeLocationVenue(i)\">\r\n <mat-icon>delete_outline</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Location count badge -->\r\n <p class=\"loc-count-text\" *ngIf=\"locationVenues.length > 0 && config.locationConfig?.allowMulti\">\r\n {{ locationVenues.length }}&nbsp;{{ controller.labels['LBL_LOC_COUNT_SUFFIX'] || 'Locations Added!' }}\r\n </p>\r\n\r\n <!-- Search input (hide when max reached) -->\r\n <div class=\"loc-search-container\" *ngIf=\"!locationMaxReached\">\r\n <div class=\"loc-search-wrapper\">\r\n <mat-icon class=\"loc-search-icon\">search</mat-icon>\r\n <input class=\"field-input loc-search-input\"\r\n [placeholder]=\"config.locationConfig?.venuePlaceholder || (controller.labels['PH_LOC_VENUE'] || 'Type to search venue...')\"\r\n [value]=\"locationSearchText\" (input)=\"handleLocationSearchInput($event)\" (blur)=\"hideLocationSuggestions()\"\r\n autocomplete=\"off\" [class.is-invalid]=\"errorMessage\">\r\n </div>\r\n <!-- Suggestions dropdown -->\r\n <div class=\"loc-suggestions-panel\" *ngIf=\"locationShowSuggestions && locationSuggestions.length\">\r\n <div *ngFor=\"let sug of locationSuggestions\" class=\"loc-suggestion-item\"\r\n (mousedown)=\"onLocationSuggestionSelect(sug)\">\r\n <mat-icon class=\"loc-suggestion-icon\">place</mat-icon>\r\n <span class=\"loc-suggestion-text\">{{ sug.description }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Add another button -->\r\n <button type=\"button\" class=\"loc-add-btn\"\r\n *ngIf=\"locationVenues.length > 0 && !locationMaxReached && config.locationConfig?.allowMulti\">\r\n <mat-icon>add_circle_outline</mat-icon>\r\n <span>{{ controller.labels['LBL_LOC_ADD_ANOTHER'] || 'Add another Location' }}</span>\r\n </button>\r\n\r\n <!-- Map -->\r\n <div class=\"loc-map-container\" *ngIf=\"config.locationConfig?.showMap !== false\">\r\n <ng-container *ngIf=\"config.locationConfig?.googleMapsApiKey; else simpleEmbed\">\r\n <div [id]=\"'loc-map-' + config.name\" class=\"loc-map-frame\"\r\n [style.height]=\"config.locationConfig?.mapHeight || '300px'\"></div>\r\n </ng-container>\r\n <ng-template #simpleEmbed>\r\n <iframe class=\"loc-map-frame\" [style.height]=\"config.locationConfig?.mapHeight || '300px'\"\r\n [src]=\"getLocationMapEmbedUrl() | trustedUrl\" frameborder=\"0\" allowfullscreen loading=\"lazy\">\r\n </iframe>\r\n </ng-template>\r\n </div>\r\n\r\n <!-- Map hint -->\r\n <p class=\"loc-map-hint\">\r\n {{ controller.labels['LBL_LOC_MAP_HINT'] || 'Type the venue address. A map will appear to assist you.' }}\r\n </p>\r\n </div>\r\n\r\n <!-- ONLINE TAB -->\r\n <div *ngIf=\"locationActiveTab === 'ONLINE'\" class=\"loc-panel loc-online-panel\">\r\n <p class=\"loc-section-label\">\r\n {{ controller.labels['LBL_LOC_ONLINE_URL'] || 'Event URL' }}\r\n </p>\r\n <div class=\"loc-search-wrapper\">\r\n <mat-icon class=\"loc-search-icon\">link</mat-icon>\r\n <input class=\"field-input loc-search-input\" type=\"url\"\r\n [placeholder]=\"config.locationConfig?.onlinePlaceholder || (controller.labels['PH_LOC_ONLINE'] || 'https://zoom.us/j/...')\"\r\n [value]=\"locationOnlineUrl\" (input)=\"onLocationUrlChange($any($event.target).value)\"\r\n [class.is-invalid]=\"errorMessage\">\r\n </div>\r\n </div>\r\n\r\n <!-- TBA TAB -->\r\n <div *ngIf=\"locationActiveTab === 'TBA'\" class=\"loc-panel loc-tba-panel\">\r\n <div class=\"loc-tba-content\">\r\n <mat-icon class=\"loc-tba-icon\">schedule</mat-icon>\r\n <p class=\"loc-tba-text\">\r\n {{ controller.labels['LBL_LOC_TBA_DESC'] || \"This event's location is yet to be announced. Check back later\r\n for updates.\" }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <!-- Hidden real form control -->\r\n <input type=\"hidden\" [formControlName]=\"config.name!\">\r\n\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</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;color:var(--cc-sf-label-color, #202124);font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif);font-weight:var(--cc-sf-label-weight, 500);font-size:var(--cc-sf-label-size, 18px);line-height:var(--cc-sf-label-line-height, 100%);letter-spacing:var(--cc-sf-label-letter-spacing, 0%);margin-bottom:.5rem}.field-label .required{color:var(--cc-sf-label-required-color, #DC2626);margin-left:.125rem}.field-input{width:100%;opacity:var(--cc-sf-input-opacity, 1);gap:var(--cc-sf-input-gap, 10px);padding-top:var(--cc-sf-input-padding-y, .625rem);padding-bottom:var(--cc-sf-input-padding-y, .625rem);padding-left:var(--cc-sf-input-padding-x, 16px);padding-right:var(--cc-sf-input-padding-x, 16px);font-size:var(--cc-sf-input-font-size, .875rem);line-height:var(--cc-sf-input-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, 1px solid #D1D5DB);border-radius:var(--cc-sf-input-radius, 7px);box-shadow:var(--cc-sf-input-shadow, none);transition:var(--cc-sf-input-transition, all .2s ease);font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif)}.field-input::placeholder{font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif);font-weight:var(--cc-sf-placeholder-weight, 400);font-size:var(--cc-sf-placeholder-size, 16px);line-height:var(--cc-sf-placeholder-line-height, 100%);letter-spacing:var(--cc-sf-placeholder-letter-spacing, 0%);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)}input[type=time].time-input{cursor:pointer}input[type=time].time-input::-webkit-calendar-picker-indicator{cursor:pointer;opacity:.7;filter:invert(30%)}input[type=time].time-input::-webkit-calendar-picker-indicator:hover{opacity:1}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)}.char-count-hint{font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif);font-weight:400;font-style:normal;font-size:14px;line-height:100%;letter-spacing:.02em;text-align:right;color:var(--cc-sf-hint-color, #6B7280);margin-top:4px}.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)}.group-section-wrapper.multi-save-active{border:none;box-shadow:none;padding:0;background:transparent}.group-section-wrapper.multi-save-active .group-label{border:none;padding-bottom:0;margin-bottom:0}.group-section-wrapper.multi-save-active .multi-save-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px}.group-section-wrapper.multi-save-active .multi-save-header .btn-add-multi ::ng-deep button{color:var(--ms-btn-add-color, #3B82F6);font-weight:600;font-size:.875rem;padding:8px 12px}.group-section-wrapper.multi-save-active .multi-save-header .btn-add-multi ::ng-deep button:hover{color:var(--ms-btn-add-hover, #2563EB);background-color:var(--cc-sf-btn-add-hover-bg, #EFF6FF)}.group-section-wrapper.multi-save-active .group-instance{background:var(--ms-card-bg, #ffffff);border:1px solid var(--ms-card-border, #E5E7EB);border-radius:var(--ms-card-radius, 10px);box-shadow:var(--ms-card-shadow, 0 1px 4px rgba(0, 0, 0, .05));padding:0;margin-bottom:16px;overflow:hidden}.group-section-wrapper.multi-save-active .group-instance.is-editing{padding:24px}.group-section-wrapper.multi-save-active .group-instance.is-card{cursor:pointer;transition:all .2s ease-in-out}.group-section-wrapper.multi-save-active .group-instance.is-card:hover{box-shadow:var(--ms-card-shadow-hover, 0 8px 24px rgba(0, 0, 0, .08));border-color:var(--cc-sf-input-focus-border, #3B82F6)}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view{display:flex;justify-content:space-between;align-items:center;padding:18px 24px}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-content{flex:1;display:flex;flex-direction:column;gap:4px;overflow:hidden}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-content .card-title{font-size:1rem;font-weight:600;color:var(--ms-title-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-content .card-desc{font-size:.875rem;color:var(--ms-desc-color, #6B7280);line-height:1.4;display:-webkit-box;-webkit-line-clamp:1;line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view.is-expanded .card-content .card-desc{-webkit-line-clamp:unset;line-clamp:unset}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions{display:flex;align-items:center;gap:16px;margin-left:20px}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions mat-icon{font-size:22px;width:22px;height:22px;color:var(--cc-sf-hint-color, #9CA3AF);transition:color .2s}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions mat-icon.icon-delete:hover{color:var(--cc-sf-error-border, #DC2626)}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions mat-icon.icon-edit:hover{color:var(--cc-sf-input-focus-border, #3B82F6)}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions mat-icon.icon-expand{color:var(--cc-sf-input-disabled-border, #E5E7EB)}.group-section-wrapper.multi-save-active .group-footer{display:flex;justify-content:space-between;align-items:center;gap:16px;margin-top:24px;padding-top:20px;border-top:1px solid var(--cc-sf-instance-divider, #E5E7EB)}.group-section-wrapper.multi-save-active .group-footer .footer-actions{display:flex;gap:12px}.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, #FFFAF1);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)}.uploaded-item.uploading{background:var(--cc-sf-uploaded-uploading-bg, #F8FAFC);border-color:var(--cc-sf-uploaded-uploading-border, #CBD5E1);opacity:.85}.upload-spinner{width:20px;height:20px;flex-shrink:0;border:2px solid var(--cc-sf-spinner-track, #E2E8F0);border-top-color:var(--cc-sf-spinner-color, #3B82F6);border-radius:50%;animation:cc-spin .7s linear infinite}@keyframes cc-spin{to{transform:rotate(360deg)}}.rich-text-editor{display:block;width:100%}.uploading-label{color:var(--cc-sf-spinner-color, #3B82F6)!important;font-style:italic}.input-group{position:relative;display:flex;align-items:stretch;width:100%}.input-group .field-input{flex:1;border-radius:var(--cc-sf-input-radius, 8px)}.input-prefix+.input-group .field-input{border-top-left-radius:0;border-bottom-left-radius:0}.input-group .field-input:has(+.input-suffix){border-top-right-radius:0;border-bottom-right-radius:0}.input-group .field-input.has-icon-right{padding-right:3rem}.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)}.date-icon-wrapper{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);display:flex;align-items:center;justify-content:center;pointer-events:auto}.date-icon-wrapper .mat-icon-button{width:32px;height:32px;line-height:32px}.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)}.autocomplete-wrapper{position:relative;display:flex;align-items:center;width:100%}.autocomplete-wrapper .ac-search-icon{position:absolute;left:.75rem;font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem;color:var(--cc-sf-hint-color, #9CA3AF);pointer-events:none;z-index:1;transition:color var(--cc-sf-input-transition, .2s ease)}.autocomplete-wrapper .ac-input{flex:1;padding-left:2.4rem;padding-right:2.4rem}.autocomplete-wrapper .ac-clear-btn{position:absolute;right:.6rem;display:flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:.2rem;border-radius:50%;color:var(--cc-sf-hint-color, #9CA3AF);transition:color .15s ease,background .15s ease}.autocomplete-wrapper .ac-clear-btn mat-icon{font-size:1rem;width:1rem;height:1rem;line-height:1rem}.autocomplete-wrapper .ac-clear-btn:hover{color:var(--cc-sf-label-color, #374151);background:var(--cc-sf-input-disabled-bg, #F3F4F6)}.autocomplete-wrapper .ac-clear-btn:focus{outline:none}.autocomplete-wrapper:focus-within .ac-search-icon{color:var(--cc-sf-input-focus-border, #3B82F6)}.autocomplete-wrapper.is-invalid .ac-input{border-color:var(--cc-sf-error-border, #DC2626);background-color:var(--cc-sf-error-bg, #FEF2F2)}.autocomplete-wrapper.readonly .ac-input{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)}.ac-no-results{font-style:italic;font-size:.8125rem;color:var(--cc-sf-hint-color, #6B7280)}.media-upload-wrapper{padding:0;border:none;background:none}.mu-layout{display:grid;grid-template-columns:1fr 1fr;gap:32px;align-items:flex-start}@media(max-width:768px){.mu-layout{grid-template-columns:1fr}}.mu-left{display:flex;flex-direction:column;gap:20px}.mu-header{display:flex;align-items:flex-start;flex-wrap:wrap;gap:10px}.mu-title{margin:0;font-size:1.35rem;font-weight:700;color:var(--cc-sf-label-color, #111827);line-height:1.3}.mu-badge{display:inline-flex;align-items:center;padding:4px 12px;border-radius:20px;background:var(--cc-sf-label-color, #111827);color:#fff;font-size:.72rem;font-weight:600;white-space:nowrap;flex-shrink:0}.mu-description{margin:0;font-size:.875rem;color:var(--cc-sf-hint-color, #6B7280);line-height:1.6}.mu-features{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.mu-feature-item{display:flex;align-items:center;gap:8px;font-size:.875rem;color:var(--cc-sf-hint-color, #374151)}.mu-feature-item .mu-check{font-size:16px;width:16px;height:16px;line-height:16px;color:var(--cc-sf-chip-selected-bg, #3B82F6);flex-shrink:0}.mu-right{display:flex;flex-direction:column;gap:12px;min-height:260px}.mu-right-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;height:100%;min-height:250px;max-width:400px;border:2px dashed var(--cc-sf-dropzone-border, #CBD5E1);border-radius:var(--mu-carousel-radius, 12px);background:var(--cc-sf-dropzone-bg, #FFFAF1);text-align:center;color:var(--cc-sf-hint-color, #94A3B8);padding:24px;box-shadow:0 2px 10px #0000000d;transition:box-shadow .2s ease}.mu-right-empty:hover{cursor:pointer;box-shadow:0 4px 16px #0000001a}.mu-right-empty .mu-right-empty-icon{font-size:52px;width:52px;height:52px;line-height:52px;opacity:.3}.mu-right-empty p{margin:0;font-size:.85rem}.media-add-container{position:relative;display:inline-block}.media-add-container ::ng-deep button{display:flex;align-items:center;gap:6px}.media-add-container ::ng-deep button .menu-chevron{font-size:18px;width:18px;height:18px;line-height:18px;transition:transform .2s ease}.media-dropdown{position:absolute;top:calc(100% + 6px);left:0;z-index:200;min-width:240px;background:var(--mu-dropdown-bg, #ffffff);border:var(--mu-dropdown-border, 1px solid #E5E7EB);border-radius:12px;box-shadow:var(--mu-dropdown-shadow, 0 8px 32px rgba(0, 0, 0, .12));overflow:hidden;animation:mu-fade-in .15s ease}@keyframes mu-fade-in{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}.media-dropdown-item{display:flex;align-items:center;gap:12px;width:100%;padding:12px 16px;background:none;border:none;border-bottom:1px solid var(--cc-sf-input-disabled-border, #F3F4F6);cursor:pointer;text-align:left;transition:background .15s ease;font-family:var(--cc-sf-font-family, inherit)}.media-dropdown-item:last-child{border-bottom:none}.media-dropdown-item:hover{background:var(--cc-sf-dropzone-hover-bg, #F0F9FF)}.media-drop-icon{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:8px;flex-shrink:0}.media-drop-icon mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.media-drop-icon--video{background:var(--mu-icon-video-bg, #FFF0F0);color:var(--mu-icon-video-color, #EF4444)}.media-drop-icon--device{background:var(--mu-icon-device-bg, #EFF6FF);color:var(--mu-icon-device-color, #3B82F6)}.media-drop-icon--library{background:var(--mu-icon-library-bg, #F0FDF4);color:var(--mu-icon-library-color, #22C55E)}.media-drop-text{display:flex;flex-direction:column;gap:2px;flex:1}.media-drop-label{font-size:.875rem;font-weight:600;color:var(--cc-sf-label-color, #111827)}.media-drop-desc{font-size:.75rem;color:var(--cc-sf-hint-color, #6B7280)}.youtube-input-panel{display:flex;flex-direction:column;gap:8px;padding:16px;background:var(--cc-sf-dropzone-bg, #FFFAF1);border:1px solid var(--cc-sf-input-border, #E5E7EB);border-radius:10px;animation:mu-fade-in .18s ease}.youtube-panel-label{display:flex;align-items:center;gap:6px;font-size:.875rem;font-weight:600;color:var(--cc-sf-label-color, #111827)}.youtube-panel-label mat-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--mu-icon-video-color, #EF4444)}.youtube-input-row{display:flex;gap:8px;align-items:stretch}.youtube-input-row .youtube-url-input{flex:1}.media-menu-backdrop{position:fixed;inset:0;z-index:100}.media-upload-status{display:flex;align-items:center;gap:8px;padding:10px 14px;margin-top:4px;background:var(--cc-sf-error-bg, #FEF2F2);color:var(--cc-sf-error-text-color, #DC2626);border-radius:8px;font-size:.85rem;font-weight:500;animation:mu-fade-in .2s ease}.media-upload-status .status-icon{font-size:18px;width:18px;height:18px;line-height:18px}.media-carousel-section{display:flex;flex-direction:column;gap:12px}.media-carousel-main{position:relative;width:100%;max-width:400px;height:var(--mu-carousel-height, 250px);background:var(--mu-carousel-bg, #0F172A);border-radius:var(--mu-carousel-radius, 12px);overflow:hidden;display:flex;align-items:center;justify-content:center}.carousel-viewer{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.carousel-viewer .carousel-image{width:100%;height:100%;object-fit:cover;border-radius:var(--mu-carousel-radius, 12px)}.carousel-viewer .carousel-iframe{width:100%;height:100%;border-radius:var(--mu-carousel-radius, 12px)}.carousel-viewer .carousel-uploading{display:flex;flex-direction:column;align-items:center;gap:12px;color:#94a3b8;font-size:.85rem}.carousel-viewer .carousel-spinner{width:36px;height:36px;border:3px solid rgba(255,255,255,.15);border-top-color:#3b82f6;border-radius:50%;animation:cc-spin .7s linear infinite}.carousel-nav{position:absolute;top:50%;transform:translateY(-50%);z-index:10;width:40px;height:40px;border-radius:50%;background:#ffffffd9;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 8px #0003;transition:background .2s ease,opacity .2s ease;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.carousel-nav mat-icon{font-size:22px;width:22px;height:22px;line-height:22px;color:#1e293b}.carousel-nav:hover:not(:disabled){background:#fff}.carousel-nav:disabled{opacity:.3;cursor:not-allowed}.carousel-nav--prev{left:12px}.carousel-nav--next{right:12px}.carousel-remove-btn{position:absolute;top:10px;right:10px;z-index:10;width:32px;height:32px;border-radius:50%;background:#0000008c;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .2s ease}.carousel-remove-btn mat-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:#fff}.carousel-remove-btn:hover:not(:disabled){background:#dc2626d9}.carousel-remove-btn:disabled{opacity:.4;cursor:not-allowed}.carousel-dots{position:absolute;bottom:10px;left:50%;transform:translate(-50%);display:flex;gap:6px;z-index:10}.carousel-dot{width:8px;height:8px;border-radius:50%;background:#ffffff73;cursor:pointer;transition:background .2s ease,transform .2s ease}.carousel-dot.active{background:#fff;transform:scale(1.3)}.media-thumbnail-strip{display:flex;flex-wrap:wrap;max-width:400px;gap:8px;overflow-x:auto;padding-bottom:4px}.media-thumbnail-strip::-webkit-scrollbar{height:4px}.media-thumbnail-strip::-webkit-scrollbar-thumb{background:var(--cc-sf-input-disabled-border, #D1D5DB);border-radius:2px}.media-thumb{flex-shrink:0;width:72px;height:52px;border-radius:8px;overflow:hidden;cursor:pointer;border:2px solid transparent;background:var(--mu-thumb-bg, #E2E8F0);display:flex;align-items:center;justify-content:center;transition:border-color .2s ease,transform .15s ease}.media-thumb.active{border-color:var(--mu-thumb-active-border, #3B82F6);transform:scale(1.04)}.media-thumb:hover:not(.active){border-color:var(--cc-sf-input-hover-border, #9CA3AF)}.media-thumb .thumb-img{width:100%;height:100%;object-fit:cover}.media-thumb .thumb-yt-placeholder{display:flex;align-items:center;justify-content:center;width:100%;height:100%;background:#1e293b;color:#ef4444}.media-thumb .thumb-yt-placeholder mat-icon{font-size:28px;width:28px;height:28px;line-height:28px}.media-thumb .thumb-uploading{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.media-thumb .thumb-uploading .thumb-spinner{width:20px;height:20px;border:2px solid #E2E8F0;border-top-color:#3b82f6;border-radius:50%;animation:cc-spin .7s linear infinite}.media-library-overlay{position:fixed;inset:0;background:#00000080;z-index:999;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:mu-fade-in .2s ease}.media-library-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1000;width:min(90vw,780px);max-height:85vh;background:var(--mu-modal-bg, #ffffff);border-radius:var(--mu-modal-radius, 16px);box-shadow:var(--mu-modal-shadow, 0 20px 60px rgba(0, 0, 0, .2));display:flex;flex-direction:column;overflow:hidden;animation:mu-modal-in .22s cubic-bezier(.34,1.56,.64,1)}@keyframes mu-modal-in{0%{opacity:0;transform:translate(-50%,-48%) scale(.95)}to{opacity:1;transform:translate(-50%,-50%) scale(1)}}.library-modal-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px 16px;border-bottom:1px solid var(--cc-sf-input-disabled-border, #F3F4F6);flex-shrink:0}.library-modal-title{display:flex;align-items:center;gap:8px;margin:0;font-size:1.05rem;font-weight:700;color:var(--cc-sf-label-color, #111827)}.library-modal-title mat-icon{font-size:22px;width:22px;height:22px;line-height:22px;color:var(--mu-icon-library-color, #22C55E)}.library-close-btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;background:none;cursor:pointer;border-radius:50%;color:var(--cc-sf-hint-color, #9CA3AF);transition:background .15s ease,color .15s ease}.library-close-btn mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.library-close-btn:hover{background:var(--cc-sf-input-disabled-bg, #F3F4F6);color:var(--cc-sf-label-color, #374151)}.library-loading,.library-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:48px 24px;color:var(--cc-sf-hint-color, #9CA3AF);font-size:.875rem;flex:1}.library-loading mat-icon,.library-empty mat-icon{font-size:40px;width:40px;height:40px;line-height:40px;opacity:.5}.lib-spinner{width:36px;height:36px;border:3px solid #E2E8F0;border-top-color:#3b82f6;border-radius:50%;animation:cc-spin .7s linear infinite}.library-error{display:flex;align-items:center;gap:8px;padding:16px 24px;background:var(--cc-sf-error-bg, #FEF2F2);color:var(--cc-sf-error-text-color, #DC2626);font-size:.875rem;flex-shrink:0}.library-error mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.library-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;padding:24px;overflow-y:auto;flex:1}.library-grid::-webkit-scrollbar{width:6px}.library-grid::-webkit-scrollbar-thumb{background:var(--cc-sf-input-disabled-border, #D1D5DB);border-radius:3px}.library-grid-item{position:relative;aspect-ratio:4/3;border-radius:10px;overflow:hidden;cursor:pointer;border:2.5px solid transparent;transition:border-color .2s ease,transform .15s ease}.library-grid-item.selected{border-color:var(--cc-sf-chip-selected-bg, #3B82F6);transform:scale(.97)}.library-grid-item:hover .library-overlay-hover{opacity:1}.library-grid-img{width:100%;height:100%;object-fit:cover;display:block}.library-overlay-hover{position:absolute;inset:0;background:#3b82f61f;opacity:0;transition:opacity .15s ease}.library-check{position:absolute;top:6px;right:6px;color:var(--cc-sf-chip-selected-bg, #3B82F6);background:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;width:22px;height:22px;box-shadow:0 1px 4px #00000026}.library-check mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}.library-modal-footer{display:flex;align-items:center;justify-content:space-between;padding:16px 24px 20px;border-top:1px solid var(--cc-sf-input-disabled-border, #F3F4F6);flex-shrink:0}.library-selected-count{font-size:.875rem;font-weight:600;color:var(--cc-sf-hint-color, #6B7280)}.library-footer-actions{display:flex;gap:10px}.location-field-wrapper{gap:12px}.location-subtitle{margin:0;font-size:var(--cc-sf-hint-size, .8125rem);color:var(--cc-sf-hint-color, #6B7280);line-height:1.5}.location-tabs{display:flex;gap:12px;margin-bottom:24px}.loc-tab-btn{flex:1}.loc-tab-btn ::ng-deep button{width:100%}.loc-tab-btn ::ng-deep button:not(.cc-btn-warning){background-color:#fff!important;color:#000!important;border:1px solid #E5E7EB}.loc-tab-btn ::ng-deep button:not(.cc-btn-warning):hover{background-color:#f3f4f6!important}.loc-panel{display:flex;flex-direction:column;gap:12px}.loc-section-label{margin:0;font-size:var(--cc-sf-label-size, .9rem);font-weight:600;color:var(--cc-sf-label-color, #111827)}.loc-venue-list{display:flex;flex-direction:column;gap:8px}.loc-venue-item{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--loc-venue-item-bg, #ffffff);border:1px solid var(--loc-venue-item-border, #D1D5DB);border-radius:var(--cc-sf-input-radius, 7px);transition:box-shadow .15s ease,border-color .15s ease}.loc-venue-item:hover{box-shadow:0 2px 8px #0000000f;border-color:var(--cc-sf-input-hover-border, #9CA3AF)}.loc-venue-search-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--cc-sf-hint-color, #9CA3AF);flex-shrink:0}.loc-venue-text{flex:1;font-size:var(--cc-sf-input-font-size, .875rem);color:var(--cc-sf-input-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.loc-action-btn{display:flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:4px;border-radius:50%;transition:background .15s ease,color .15s ease;flex-shrink:0}.loc-action-btn mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}.loc-action-btn.loc-delete-btn{color:var(--loc-delete-color, #E53E3E)}.loc-action-btn.loc-delete-btn:hover{background:var(--cc-sf-error-bg, #FEF2F2)}.loc-action-btn.loc-edit-btn{color:var(--cc-sf-hint-color, #9CA3AF)}.loc-action-btn.loc-edit-btn:hover{color:var(--cc-sf-input-focus-border, #3B82F6);background:var(--cc-sf-dropzone-hover-bg, #EFF6FF)}.loc-count-text{margin:0;font-size:.8125rem;font-weight:600;color:var(--cc-sf-input-focus-border, #3B82F6)}.loc-search-container{position:relative}.loc-search-wrapper{position:relative;display:flex;align-items:center}.loc-search-icon{position:absolute;left:.75rem;font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem;color:var(--cc-sf-hint-color, #9CA3AF);pointer-events:none;z-index:1}.loc-search-input{flex:1;padding-left:2.4rem!important}.loc-suggestions-panel{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:300;background:var(--loc-suggestion-bg, #ffffff);border:1px solid var(--cc-sf-input-border, #D1D5DB);border-radius:var(--cc-sf-input-radius, 8px);box-shadow:0 8px 24px #0000001a;overflow:hidden;animation:mu-fade-in .15s ease;max-height:260px;overflow-y:auto}.loc-suggestion-item{display:flex;align-items:center;gap:10px;padding:10px 14px;cursor:pointer;transition:background .12s ease;font-family:var(--cc-sf-font-family, inherit)}.loc-suggestion-item:hover,.loc-suggestion-item:focus{background:var(--loc-suggestion-hover-bg, #F0F9FF)}.loc-suggestion-item:not(:last-child){border-bottom:1px solid var(--cc-sf-input-disabled-border, #F3F4F6)}.loc-suggestion-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--loc-delete-color, #E53E3E);flex-shrink:0}.loc-suggestion-text{font-size:var(--cc-sf-input-font-size, .875rem);color:var(--cc-sf-label-color, #374151);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.loc-add-btn{display:inline-flex;align-items:center;gap:6px;background:none;border:none;cursor:pointer;color:var(--loc-add-color, #1A56DB);font-family:var(--cc-sf-font-family, inherit);font-size:var(--cc-sf-input-font-size, .875rem);font-weight:600;padding:0;transition:opacity .15s ease}.loc-add-btn mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.loc-add-btn:hover{opacity:.8}.loc-map-container{border-radius:var(--loc-map-radius, 10px);overflow:hidden;border:1px solid var(--cc-sf-input-disabled-border, #E5E7EB);box-shadow:0 2px 10px #0000000f}.loc-map-frame{width:100%;display:block;border:none}.loc-map-hint{margin:0;font-size:.78rem;color:var(--cc-sf-hint-color, #6B7280);text-align:center}.loc-tba-panel{min-height:120px;justify-content:center}.loc-tba-content{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:12px;padding:32px 24px;background:var(--loc-tba-bg, #F9FAFB);border:1px dashed var(--cc-sf-input-disabled-border, #D1D5DB);border-radius:var(--cc-sf-input-radius, 10px)}.loc-tba-icon{font-size:40px;width:40px;height:40px;line-height:40px;color:var(--loc-tba-icon-color, #9CA3AF);opacity:.6}.loc-tba-text{margin:0;font-size:var(--cc-sf-input-font-size, .9rem);color:var(--cc-sf-hint-color, #6B7280);line-height:1.6;max-width:360px}.loc-online-panel .loc-search-wrapper{margin-top:4px}\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.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i5.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { 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: i9.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "directive", type: i9.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "component", type: ButtonComponent, selector: "lib-button", inputs: ["variant", "type", "disabled", "width", "height", "borderRadius", "fontSize", "fontWeight", "backgroundColor", "color", "border", "icon", "labels"] }, { kind: "component", type: i11.QuillEditorComponent, selector: "quill-editor" }, { kind: "component", type: FormFieldComponent, selector: "lib-form-field", inputs: ["config", "controller", "formGroup", "allowMulti"] }, { kind: "pipe", type: TrustedUrlPipe, name: "trustedUrl" }] });
5367
+ 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" }, viewQueries: [{ propertyName: "mediaDeviceInput", first: true, predicate: ["mediaDeviceInput"], descendants: true }], 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 [class.multi-save-active]=\"config.sectionConfig?.multiSaveConfig?.active\">\r\n\r\n <!-- Multi-Save Header with Add button (top-right style) -->\r\n <div class=\"multi-save-header\" *ngIf=\"config.sectionConfig?.multiSaveConfig?.active\">\r\n <h3 class=\"group-label\" *ngIf=\"config.sectionConfig?.label\">{{ config.sectionConfig!.label }}</h3>\r\n <lib-button [variant]=\"'outline'\" [icon]=\"{type: 'material', value: 'add'}\" (click)=\"addGroupInstance()\"\r\n class=\"btn-add-multi\">\r\n {{ addMultiLabel }}\r\n </lib-button>\r\n </div>\r\n\r\n <!-- Standard Header for non-multiSave -->\r\n <h3 class=\"group-label\" *ngIf=\"config.sectionConfig?.label && !config.sectionConfig?.multiSaveConfig?.active\">{{\r\n config.sectionConfig!.label }}</h3>\r\n\r\n <div *ngFor=\"let instance of instanceList; trackBy: trackByInstanceId; let i = index\" class=\"group-instance\"\r\n [class.is-editing]=\"instance.isEditing\"\r\n [class.is-card]=\"config.sectionConfig?.multiSaveConfig?.active && !instance.isEditing\">\r\n\r\n <!-- 1. EDIT MODE / UNSAVED / STANDARD STATE -->\r\n <div [hidden]=\"config.sectionConfig?.multiSaveConfig?.active && !instance.isEditing\">\r\n <!-- Instance header \u2014 show remove only when more than 1 instance -->\r\n <div class=\"group-header\" *ngIf=\"!config.sectionConfig?.multiSaveConfig?.active && 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 <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\r\n <!-- SAVE / CANCEL BUTTONS for MultiSave in Editing phase -->\r\n <div class=\"group-footer\" *ngIf=\"config.sectionConfig?.multiSaveConfig?.active\">\r\n <span class=\"field-error\" *ngIf=\"multiSaveError\">{{ multiSaveError }}</span>\r\n <div class=\"footer-actions\">\r\n <lib-button [variant]=\"'outline'\" (click)=\"cancelGroupInstance(i)\">Cancel</lib-button>\r\n <lib-button [variant]=\"'primary'\" (click)=\"saveGroupInstance(i)\">Save</lib-button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 2. CARD VIEW (Saved State) -->\r\n <ng-container *ngIf=\"config.sectionConfig?.multiSaveConfig?.active && !instance.isEditing\">\r\n <div class=\"card-view\" [class.is-expanded]=\"instance.isExpanded\">\r\n <div class=\"card-content\">\r\n <span class=\"card-title\">{{ instance.fg.get(config.sectionConfig.multiSaveConfig.summaryField || '')?.value\r\n || '\u2014' }}</span>\r\n <!-- <span class=\"card-desc\" *ngIf=\"config.sectionConfig.multiSaveConfig.descriptionField\"\r\n [innerHTML]=\"instance.fg.get(config.sectionConfig.multiSaveConfig.descriptionField)?.value\">\r\n </span> -->\r\n </div>\r\n <div class=\"card-actions\">\r\n <mat-icon class=\"icon-delete\" (click)=\"removeGroupInstance(i, true)\">delete_outline</mat-icon>\r\n <mat-icon class=\"icon-edit\" (click)=\"editGroupInstance(i)\">edit_outline</mat-icon>\r\n <mat-icon class=\"icon-expand\" (click)=\"toggleExpandGroupInstance(i)\">\r\n {{ instance.isExpanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}\r\n </mat-icon>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- Standard Add Button for non-multiSave -->\r\n <lib-button *ngIf=\"!config.sectionConfig?.multiSaveConfig?.active\" [variant]=\"'outline'\"\r\n [icon]=\"{type: 'material', value: 'add'}\" (click)=\"addGroupInstance()\" 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 class=\"char-count-hint\" *ngIf=\"showCharCount\">\r\n {{ remainingCharacters }} characters remaining\r\n </div>\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 <div class=\"input-group\" [class.readonly]=\"config.readonly\">\r\n <input matInput [matDatepicker]=\"datePicker\" class=\"field-input date-input has-icon-right\"\r\n [formControlName]=\"config.name!\" [min]=\"config.dateConfig?.minDate\" [max]=\"config.dateConfig?.maxDate\"\r\n [class.is-invalid]=\"errorMessage\" [placeholder]=\"config.placeholder || ''\" [readonly]=\"config.readonly\"\r\n (click)=\"!config.readonly && datePicker.open()\">\r\n <div class=\"date-icon-wrapper\" *ngIf=\"!config.readonly\">\r\n <mat-datepicker-toggle matSuffix [for]=\"datePicker\"></mat-datepicker-toggle>\r\n </div>\r\n <mat-datepicker #datePicker></mat-datepicker>\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 Time 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=\"isTimeField\" 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 <input type=\"time\" class=\"field-input time-input\" [formControlName]=\"config.name!\"\r\n [class.is-invalid]=\"errorMessage\" [readonly]=\"!!config.readonly\">\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 Autocomplete \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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=\"isAutocomplete\" 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 <!-- Hidden real control (stores the code value) -->\r\n <input type=\"hidden\" [formControlName]=\"config.name!\">\r\n\r\n <div class=\"autocomplete-wrapper\" [class.is-invalid]=\"errorMessage\" [class.readonly]=\"config.readonly\">\r\n <!-- Search icon -->\r\n <mat-icon class=\"ac-search-icon\">search</mat-icon>\r\n\r\n <input class=\"field-input ac-input\" [formControl]=\"autocompleteInputCtrl\" [matAutocomplete]=\"auto\"\r\n [placeholder]=\"config.placeholder || 'Search\u2026'\" [readonly]=\"!!config.readonly\" [class.is-invalid]=\"errorMessage\"\r\n (blur)=\"onAutocompleteClear()\" autocomplete=\"off\">\r\n\r\n <!-- Clear button -->\r\n <button type=\"button\" class=\"ac-clear-btn\" *ngIf=\"autocompleteInputCtrl.value && !config.readonly\"\r\n (click)=\"autocompleteInputCtrl.setValue(''); updateValue(null)\" tabindex=\"-1\" aria-label=\"Clear\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n\r\n <mat-autocomplete #auto=\"matAutocomplete\" [panelWidth]=\"'auto'\">\r\n <mat-option *ngFor=\"let option of filteredOptions\" [value]=\"option.label\"\r\n (onSelectionChange)=\"onAutocompleteSelected(option)\">\r\n {{ option.label }}\r\n </mat-option>\r\n <mat-option *ngIf=\"filteredOptions.length === 0\" disabled class=\"ac-no-results\">\r\n No results found\r\n </mat-option>\r\n </mat-autocomplete>\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 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)\" hidden>\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 Rich Text \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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=\"isRichText\" class=\"field-wrapper component-rich-text\" [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=\"rich-text-container\" [class.is-invalid]=\"errorMessage\">\r\n <quill-editor [formControlName]=\"config.name!\" class=\"rich-text-editor\"\r\n [placeholder]=\"config.richTextConfig?.placeholder || config.placeholder || ''\"\r\n [styles]=\"{height: config.richTextConfig?.height || '200px'}\">\r\n </quill-editor>\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 class=\"char-count-hint\" *ngIf=\"showCharCount\">\r\n {{ remainingCharacters }} characters remaining\r\n </div>\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\" hidden [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\" [class.uploading]=\"f.isUploading\">\r\n\r\n <!-- Uploading spinner (shown while API call is in progress) -->\r\n <ng-container *ngIf=\"f.isUploading; else fileReady\">\r\n <div class=\"upload-spinner\"></div>\r\n <div class=\"file-info\">\r\n <span class=\"file-name\" [title]=\"f.name\">{{ f.name }}</span>\r\n <span class=\"file-size uploading-label\">Uploading...</span>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Normal state once upload is done -->\r\n <ng-template #fileReady>\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 </ng-template>\r\n\r\n <!-- Remove button \u2014 disabled while uploading -->\r\n <lib-button [variant]=\"'danger-outline'\" [disabled]=\"f.isUploading\"\r\n (click)=\"!f.isUploading && 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 <!-- \u2550\u2550 Media Upload (Type 2) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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=\"isMediaUpload\" class=\"field-wrapper media-upload-wrapper\" [formGroup]=\"formGroup\">\r\n\r\n <!-- Hidden file input lives outside *ngIf \u2014 triggered via ViewChild -->\r\n <input #mediaDeviceInput type=\"file\" hidden multiple accept=\"image/*\" (change)=\"onMediaFileSelected($event)\">\r\n\r\n <!-- Two-column layout -->\r\n <div class=\"mu-layout\">\r\n\r\n <!-- \u2500\u2500 LEFT PANEL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"mu-left\">\r\n\r\n <!-- Header: title + max-items badge -->\r\n <div class=\"mu-header\">\r\n <h3 class=\"mu-title\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </h3>\r\n <span class=\"mu-badge\" *ngIf=\"config.attachmentConfig?.maxFiles\">\r\n {{ controller.labels['LBL_MEDIA_MAX_PREFIX'] || 'Maximum' }}\r\n {{ config.attachmentConfig?.maxFiles }}\r\n {{ controller.labels['LBL_MEDIA_MAX_SUFFIX'] || 'Image & Videos' }}\r\n </span>\r\n </div>\r\n\r\n <!-- Description -->\r\n <p class=\"mu-description\" *ngIf=\"config.attachmentConfig?.description\">\r\n {{ config.attachmentConfig?.description }}\r\n </p>\r\n <p class=\"mu-description\" *ngIf=\"!config.attachmentConfig?.description && controller.labels['LBL_MEDIA_DESC']\">\r\n {{ controller.labels['LBL_MEDIA_DESC'] }}\r\n </p>\r\n\r\n <!-- Feature bullet list -->\r\n <ul class=\"mu-features\"\r\n *ngIf=\"config.attachmentConfig?.features?.length || controller.labels['LBL_MEDIA_FEATURE_1']\">\r\n <ng-container *ngIf=\"config.attachmentConfig?.features?.length\">\r\n <li *ngFor=\"let f of config.attachmentConfig?.features\" class=\"mu-feature-item\">\r\n <mat-icon class=\"mu-check\">check</mat-icon>{{ f }}\r\n </li>\r\n </ng-container>\r\n <ng-container *ngIf=\"!config.attachmentConfig?.features?.length\">\r\n <li *ngIf=\"controller.labels['LBL_MEDIA_FEATURE_1']\" class=\"mu-feature-item\">\r\n <mat-icon class=\"mu-check\">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_1'] }}\r\n </li>\r\n <li *ngIf=\"controller.labels['LBL_MEDIA_FEATURE_2']\" class=\"mu-feature-item\">\r\n <mat-icon class=\"mu-check\">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_2'] }}\r\n </li>\r\n <li *ngIf=\"controller.labels['LBL_MEDIA_FEATURE_3']\" class=\"mu-feature-item\">\r\n <mat-icon class=\"mu-check\">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_3'] }}\r\n </li>\r\n </ng-container>\r\n </ul>\r\n\r\n <!-- Backdrop to close dropdown on outside click -->\r\n <div class=\"media-menu-backdrop\" *ngIf=\"showMediaMenu\"\r\n (click)=\"$event.stopPropagation(); showMediaMenu = false\"></div>\r\n\r\n <!-- Add Media button + dropdown -->\r\n <div class=\"media-add-container\" (click)=\"showMediaMenu = !showMediaMenu\">\r\n <lib-button id=\"btn-add-media-{{ config.name }}\" [variant]=\"'warning'\"\r\n [icon]=\"{type: 'material', value: 'add_photo_alternate'}\">\r\n {{ controller.labels['LBL_ADD_MEDIA'] || 'Add media' }}\r\n <mat-icon class=\"menu-chevron\">add</mat-icon>\r\n </lib-button>\r\n\r\n <div class=\"media-dropdown\" *ngIf=\"showMediaMenu\" role=\"menu\" (click)=\"$event.stopPropagation()\">\r\n <!-- Video -->\r\n <button id=\"btn-media-video-{{ config.name }}\" type=\"button\" class=\"media-dropdown-item\"\r\n (click)=\"onMediaMenuVideo(); showMediaMenu = false\" role=\"menuitem\">\r\n <span class=\"media-drop-icon media-drop-icon--video\"><mat-icon>videocam</mat-icon></span>\r\n <span class=\"media-drop-text\">\r\n <span class=\"media-drop-label\">{{ controller.labels['LBL_MEDIA_VIDEO'] || 'Video' }}</span>\r\n <span class=\"media-drop-desc\">{{ controller.labels['LBL_MEDIA_VIDEO_DESC'] || 'Add YouTube URL'\r\n }}</span>\r\n </span>\r\n </button>\r\n <!-- Device -->\r\n <button id=\"btn-media-device-{{ config.name }}\" type=\"button\" class=\"media-dropdown-item\"\r\n (click)=\"onMediaMenuDevice(); showMediaMenu = false\" role=\"menuitem\">\r\n <span class=\"media-drop-icon media-drop-icon--device\"><mat-icon>upload</mat-icon></span>\r\n <span class=\"media-drop-text\">\r\n <span class=\"media-drop-label\">{{ controller.labels['LBL_MEDIA_DEVICE'] || 'Upload from device'\r\n }}</span>\r\n <span class=\"media-drop-desc\">{{ controller.labels['LBL_MEDIA_DEVICE_DESC'] || 'Select images from your\r\n computer' }}</span>\r\n </span>\r\n </button>\r\n <!-- Library -->\r\n <button id=\"btn-media-library-{{ config.name }}\" type=\"button\" class=\"media-dropdown-item\"\r\n (click)=\"onMediaMenuLibrary(); showMediaMenu = false\" role=\"menuitem\">\r\n <span class=\"media-drop-icon media-drop-icon--library\"><mat-icon>photo_library</mat-icon></span>\r\n <span class=\"media-drop-text\">\r\n <span class=\"media-drop-label\">{{ controller.labels['LBL_MEDIA_LIBRARY'] || 'Upload from library'\r\n }}</span>\r\n <span class=\"media-drop-desc\">{{ controller.labels['LBL_MEDIA_LIBRARY_DESC'] || 'Choose from default\r\n images' }}</span>\r\n </span>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- YouTube URL Input (inline below button) -->\r\n <div class=\"youtube-input-panel\" *ngIf=\"showYoutubeInput\">\r\n <label class=\"youtube-panel-label\">\r\n {{ controller.labels['LBL_YOUTUBE_URL'] || 'Video URL' }}\r\n </label>\r\n <div class=\"youtube-input-row\">\r\n <input id=\"input-youtube-url-{{ config.name }}\" type=\"url\" class=\"field-input youtube-url-input\"\r\n [(ngModel)]=\"youtubeUrlInput\" [ngModelOptions]=\"{standalone: true}\"\r\n [placeholder]=\"controller.labels['PH_YOUTUBE_URL'] || 'Video URL'\" (keyup.enter)=\"addYoutubeMedia()\">\r\n <lib-button id=\"btn-add-youtube-{{ config.name }}\" [variant]=\"'secondary'\" (click)=\"addYoutubeMedia()\">\r\n {{ controller.labels['LBL_ADD'] || 'Add' }}\r\n </lib-button>\r\n </div>\r\n <span class=\"field-error\" *ngIf=\"youtubeUrlError\">{{ youtubeUrlError }}</span>\r\n </div>\r\n\r\n <div class=\"media-upload-status\" *ngIf=\"mediaUploadError\">\r\n <mat-icon class=\"status-icon\">error_outline</mat-icon>\r\n <span>{{ mediaUploadError }}</span>\r\n </div>\r\n\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n </div>\r\n <!-- end left panel -->\r\n\r\n <!-- \u2500\u2500 RIGHT PANEL (carousel) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"mu-right\">\r\n\r\n <!-- Carousel (when items exist) -->\r\n <div class=\"media-carousel-section\" *ngIf=\"mediaItems.length\">\r\n <div class=\"media-carousel-main\">\r\n <button id=\"btn-carousel-prev-{{ config.name }}\" type=\"button\" class=\"carousel-nav carousel-nav--prev\"\r\n (click)=\"mediaCarouselPrev()\" [disabled]=\"mediaCarouselIndex === 0\" aria-label=\"Previous\">\r\n <mat-icon>chevron_left</mat-icon>\r\n </button>\r\n\r\n <div class=\"carousel-viewer\" *ngFor=\"let item of mediaItems; let i = index\"\r\n [hidden]=\"i !== mediaCarouselIndex\">\r\n <div *ngIf=\"item.isUploading\" class=\"carousel-uploading\">\r\n <div class=\"carousel-spinner\"></div>\r\n <span>{{ controller.labels['LBL_UPLOADING'] || 'Uploading\u2026' }}</span>\r\n </div>\r\n <ng-container *ngIf=\"!item.isUploading && item.mediaType === 'youtube'\">\r\n <iframe class=\"carousel-iframe\" [src]=\"item.url | trustedUrl\" frameborder=\"0\" allowfullscreen\r\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\">\r\n </iframe>\r\n </ng-container>\r\n <ng-container *ngIf=\"!item.isUploading && item.mediaType === 'image'\">\r\n <img class=\"carousel-image\" [src]=\"item.url\" alt=\"Media\">\r\n </ng-container>\r\n <button id=\"btn-remove-media-{{ config.name }}-{{ i }}\" type=\"button\" class=\"carousel-remove-btn\"\r\n [disabled]=\"item.isUploading\" (click)=\"removeMediaItem(i)\" aria-label=\"Remove\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <button id=\"btn-carousel-next-{{ config.name }}\" type=\"button\" class=\"carousel-nav carousel-nav--next\"\r\n (click)=\"mediaCarouselNext()\" [disabled]=\"mediaCarouselIndex === mediaItems.length - 1\" aria-label=\"Next\">\r\n <mat-icon>chevron_right</mat-icon>\r\n </button>\r\n\r\n <div class=\"carousel-dots\">\r\n <span *ngFor=\"let item of mediaItems; let i = index\" class=\"carousel-dot\"\r\n [class.active]=\"i === mediaCarouselIndex\" (click)=\"mediaGoTo(i)\"></span>\r\n </div>\r\n </div>\r\n\r\n <!-- Thumbnail strip -->\r\n <div class=\"media-thumbnail-strip\">\r\n <div *ngFor=\"let item of mediaThumbnails; let i = index\" class=\"media-thumb\"\r\n [class.active]=\"i === mediaCarouselIndex\" (click)=\"mediaGoTo(i)\">\r\n <div *ngIf=\"item.isUploading\" class=\"thumb-uploading\">\r\n <div class=\"thumb-spinner\"></div>\r\n </div>\r\n <img *ngIf=\"!item.isUploading && item.mediaType === 'youtube' && item.thumbnailUrl\"\r\n [src]=\"item.thumbnailUrl\" class=\"thumb-img\" alt=\"Video thumbnail\">\r\n <div *ngIf=\"!item.isUploading && item.mediaType === 'youtube' && !item.thumbnailUrl\"\r\n class=\"thumb-yt-placeholder\">\r\n <mat-icon>play_circle</mat-icon>\r\n </div>\r\n <img *ngIf=\"!item.isUploading && item.mediaType === 'image' && item.url\" [src]=\"item.url\"\r\n class=\"thumb-img\" alt=\"Image thumbnail\">\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Empty right-side placeholder -->\r\n <div class=\"mu-right-empty\" *ngIf=\"!mediaItems.length\" (click)=\"onMediaMenuDevice()\">\r\n <mat-icon class=\"mu-right-empty-icon\">perm_media</mat-icon>\r\n <p>{{ controller.labels['LBL_ADD_MEDIA'] || 'Add media' }}</p>\r\n </div>\r\n\r\n </div>\r\n <!-- end right panel -->\r\n\r\n </div><!-- end mu-layout -->\r\n </div>\r\n\r\n\r\n <!-- \u2550\u2550 Library Image Picker Modal \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 class=\"media-library-overlay\" *ngIf=\"showLibraryModal\" (click)=\"closeLibraryModal()\"></div>\r\n <div class=\"media-library-modal\" *ngIf=\"showLibraryModal\" role=\"dialog\" aria-modal=\"true\">\r\n <div class=\"library-modal-header\">\r\n <h3 class=\"library-modal-title\">\r\n <mat-icon>photo_library</mat-icon>\r\n {{ controller.labels['LBL_LIBRARY_TITLE'] || 'Media Library' }}\r\n </h3>\r\n <button id=\"btn-close-library-{{ config.name }}\" type=\"button\" class=\"library-close-btn\"\r\n (click)=\"closeLibraryModal()\" aria-label=\"Close\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Loading -->\r\n <div class=\"library-loading\" *ngIf=\"libraryLoading\">\r\n <div class=\"lib-spinner\"></div>\r\n <span>{{ controller.labels['LBL_LOADING'] || 'Loading\u2026' }}</span>\r\n </div>\r\n\r\n <!-- Error -->\r\n <div class=\"library-error\" *ngIf=\"libraryError && !libraryLoading\">\r\n <mat-icon>error_outline</mat-icon>\r\n {{ libraryError }}\r\n </div>\r\n\r\n <!-- Image grid -->\r\n <div class=\"library-grid\" *ngIf=\"!libraryLoading && libraryImages.length\">\r\n <div *ngFor=\"let img of libraryImages; let li = index\" id=\"lib-img-{{ config.name }}-{{ li }}\"\r\n class=\"library-grid-item\" [class.selected]=\"isLibraryItemSelected(img)\" (click)=\"toggleLibraryItem(img)\">\r\n <img [src]=\"getLibraryItemUrl(img)\" class=\"library-grid-img\" alt=\"Library image\">\r\n <div class=\"library-check\" *ngIf=\"isLibraryItemSelected(img)\">\r\n <mat-icon>check_circle</mat-icon>\r\n </div>\r\n <div class=\"library-overlay-hover\"></div>\r\n </div>\r\n </div>\r\n\r\n <!-- Empty library -->\r\n <div class=\"library-empty\" *ngIf=\"!libraryLoading && !libraryError && libraryImages.length === 0\">\r\n <mat-icon>image_not_supported</mat-icon>\r\n <span>{{ controller.labels['LBL_LIBRARY_EMPTY'] || 'No images found in library.' }}</span>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"library-modal-footer\">\r\n <span class=\"library-selected-count\">\r\n {{ librarySelectedIds.size }} {{ controller.labels['LBL_LIBRARY_SELECTED'] || 'selected' }}\r\n </span>\r\n <div class=\"library-footer-actions\">\r\n <lib-button id=\"btn-library-cancel-{{ config.name }}\" [variant]=\"'outline'\" (click)=\"closeLibraryModal()\">\r\n {{ controller.labels['LBL_CANCEL'] || 'Cancel' }}\r\n </lib-button>\r\n <lib-button id=\"btn-library-confirm-{{ config.name }}\" [variant]=\"'primary'\"\r\n [disabled]=\"librarySelectedIds.size === 0\" (click)=\"confirmLibrarySelection()\">\r\n {{ controller.labels['LBL_LIBRARY_ADD_SELECTED'] || 'Add Selected' }}\r\n </lib-button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Location Field \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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=\"isLocation\" class=\"field-wrapper location-field-wrapper\" [formGroup]=\"formGroup\">\r\n\r\n <!-- Field label -->\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 <p class=\"location-subtitle\" *ngIf=\"config.hint\">{{ config.hint }}</p>\r\n\r\n <!-- Three-tab bar -->\r\n <div class=\"location-tabs\">\r\n <lib-button class=\"loc-tab-btn\" [variant]=\"locationActiveTab === 'VENUE' ? 'warning' : 'outline'\"\r\n (click)=\"onLocationTabChange('VENUE')\">\r\n {{ controller.labels['LBL_LOC_VENUE'] || 'Venue' }}\r\n </lib-button>\r\n <lib-button class=\"loc-tab-btn\" [variant]=\"locationActiveTab === 'ONLINE' ? 'warning' : 'outline'\"\r\n (click)=\"onLocationTabChange('ONLINE')\">\r\n {{ controller.labels['LBL_LOC_ONLINE'] || 'Online Event' }}\r\n </lib-button>\r\n <lib-button class=\"loc-tab-btn\" [variant]=\"locationActiveTab === 'TBA' ? 'warning' : 'outline'\"\r\n (click)=\"onLocationTabChange('TBA')\">\r\n {{ controller.labels['LBL_LOC_TBA'] || 'To be Announced' }}\r\n </lib-button>\r\n </div>\r\n\r\n <!-- VENUE TAB -->\r\n <div *ngIf=\"locationActiveTab === 'VENUE'\" class=\"loc-panel loc-venue-panel\">\r\n\r\n <p class=\"loc-section-label\">\r\n {{ controller.labels['LBL_LOC_ADDRESS'] || 'Location address' }}\r\n </p>\r\n\r\n <!-- Added venue rows -->\r\n <div class=\"loc-venue-list\" *ngIf=\"locationVenues.length > 0\">\r\n <div *ngFor=\"let venue of locationVenues; let i = index\" class=\"loc-venue-item\">\r\n <mat-icon class=\"loc-venue-search-icon\">search</mat-icon>\r\n <span class=\"loc-venue-text\">{{ venue.address || venue.name || venue.description }}</span>\r\n <button type=\"button\" class=\"loc-action-btn loc-delete-btn\" (click)=\"removeLocationVenue(i)\">\r\n <mat-icon>delete_outline</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Location count badge -->\r\n <p class=\"loc-count-text\" *ngIf=\"locationVenues.length > 0 && config.locationConfig?.allowMulti\">\r\n {{ locationVenues.length }}&nbsp;{{ controller.labels['LBL_LOC_COUNT_SUFFIX'] || 'Locations Added!' }}\r\n </p>\r\n\r\n <!-- Search input (hide when max reached) -->\r\n <div class=\"loc-search-container\" *ngIf=\"!locationMaxReached\">\r\n <div class=\"loc-search-wrapper\">\r\n <mat-icon class=\"loc-search-icon\">search</mat-icon>\r\n <input class=\"field-input loc-search-input\"\r\n [placeholder]=\"config.locationConfig?.venuePlaceholder || (controller.labels['PH_LOC_VENUE'] || 'Type to search venue...')\"\r\n [value]=\"locationSearchText\" (input)=\"handleLocationSearchInput($event)\" (blur)=\"hideLocationSuggestions()\"\r\n autocomplete=\"off\" [class.is-invalid]=\"errorMessage\">\r\n </div>\r\n <!-- Suggestions dropdown -->\r\n <div class=\"loc-suggestions-panel\" *ngIf=\"locationShowSuggestions && locationSuggestions.length\">\r\n <div *ngFor=\"let sug of locationSuggestions\" class=\"loc-suggestion-item\"\r\n (mousedown)=\"onLocationSuggestionSelect(sug)\">\r\n <mat-icon class=\"loc-suggestion-icon\">place</mat-icon>\r\n <span class=\"loc-suggestion-text\">{{ sug.description }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Add another button -->\r\n <button type=\"button\" class=\"loc-add-btn\"\r\n *ngIf=\"locationVenues.length > 0 && !locationMaxReached && config.locationConfig?.allowMulti\">\r\n <mat-icon>add_circle_outline</mat-icon>\r\n <span>{{ controller.labels['LBL_LOC_ADD_ANOTHER'] || 'Add another Location' }}</span>\r\n </button>\r\n\r\n <!-- Map -->\r\n <div class=\"loc-map-container\" *ngIf=\"config.locationConfig?.showMap !== false\">\r\n <ng-container *ngIf=\"config.locationConfig?.googleMapsApiKey; else simpleEmbed\">\r\n <div [id]=\"'loc-map-' + config.name\" class=\"loc-map-frame\"\r\n [style.height]=\"config.locationConfig?.mapHeight || '300px'\"></div>\r\n </ng-container>\r\n <ng-template #simpleEmbed>\r\n <iframe class=\"loc-map-frame\" [style.height]=\"config.locationConfig?.mapHeight || '300px'\"\r\n [src]=\"getLocationMapEmbedUrl() | trustedUrl\" frameborder=\"0\" allowfullscreen loading=\"lazy\">\r\n </iframe>\r\n </ng-template>\r\n </div>\r\n\r\n <!-- Map hint -->\r\n <p class=\"loc-map-hint\">\r\n {{ controller.labels['LBL_LOC_MAP_HINT'] || 'Type the venue address. A map will appear to assist you.' }}\r\n </p>\r\n </div>\r\n\r\n <!-- ONLINE TAB -->\r\n <div *ngIf=\"locationActiveTab === 'ONLINE'\" class=\"loc-panel loc-online-panel\">\r\n <p class=\"loc-section-label\">\r\n {{ controller.labels['LBL_LOC_ONLINE_URL'] || 'Event URL' }}\r\n </p>\r\n <div class=\"loc-search-wrapper\">\r\n <mat-icon class=\"loc-search-icon\">link</mat-icon>\r\n <input class=\"field-input loc-search-input\" type=\"url\"\r\n [placeholder]=\"config.locationConfig?.onlinePlaceholder || (controller.labels['PH_LOC_ONLINE'] || 'https://zoom.us/j/...')\"\r\n [value]=\"locationOnlineUrl\" (input)=\"onLocationUrlChange($any($event.target).value)\"\r\n [class.is-invalid]=\"errorMessage\">\r\n </div>\r\n </div>\r\n\r\n <!-- TBA TAB -->\r\n <div *ngIf=\"locationActiveTab === 'TBA'\" class=\"loc-panel loc-tba-panel\">\r\n <div class=\"loc-tba-content\">\r\n <mat-icon class=\"loc-tba-icon\">schedule</mat-icon>\r\n <p class=\"loc-tba-text\">\r\n {{ controller.labels['LBL_LOC_TBA_DESC'] || \"This event's location is yet to be announced. Check back later\r\n for updates.\" }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <!-- Hidden real form control -->\r\n <input type=\"hidden\" [formControlName]=\"config.name!\">\r\n\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</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;color:var(--cc-sf-label-color, #202124);font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif);font-weight:var(--cc-sf-label-weight, 500);font-size:var(--cc-sf-label-size, 18px);line-height:var(--cc-sf-label-line-height, 100%);letter-spacing:var(--cc-sf-label-letter-spacing, 0%);margin-bottom:.5rem}.field-label .required{color:var(--cc-sf-label-required-color, #DC2626);margin-left:.125rem}.field-input{width:100%;opacity:var(--cc-sf-input-opacity, 1);gap:var(--cc-sf-input-gap, 10px);padding-top:var(--cc-sf-input-padding-y, .625rem);padding-bottom:var(--cc-sf-input-padding-y, .625rem);padding-left:var(--cc-sf-input-padding-x, 16px);padding-right:var(--cc-sf-input-padding-x, 16px);font-size:var(--cc-sf-input-font-size, .875rem);line-height:var(--cc-sf-input-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, 1px solid #D1D5DB);border-radius:var(--cc-sf-input-radius, 7px);box-shadow:var(--cc-sf-input-shadow, none);transition:var(--cc-sf-input-transition, all .2s ease);font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif)}.field-input::placeholder{font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif);font-weight:var(--cc-sf-placeholder-weight, 400);font-size:var(--cc-sf-placeholder-size, 16px);line-height:var(--cc-sf-placeholder-line-height, 100%);letter-spacing:var(--cc-sf-placeholder-letter-spacing, 0%);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)}input[type=time].time-input{cursor:pointer}input[type=time].time-input::-webkit-calendar-picker-indicator{cursor:pointer;opacity:.7;filter:invert(30%)}input[type=time].time-input::-webkit-calendar-picker-indicator:hover{opacity:1}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)}.char-count-hint{font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif);font-weight:400;font-style:normal;font-size:14px;line-height:100%;letter-spacing:.02em;text-align:right;color:var(--cc-sf-hint-color, #6B7280);margin-top:4px}.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)}.group-section-wrapper.multi-save-active{border:none;box-shadow:none;padding:0;background:transparent}.group-section-wrapper.multi-save-active .group-label{border:none;padding-bottom:0;margin-bottom:0}.group-section-wrapper.multi-save-active .multi-save-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px}.group-section-wrapper.multi-save-active .multi-save-header .btn-add-multi ::ng-deep button{color:var(--ms-btn-add-color, #3B82F6);font-weight:600;font-size:.875rem;padding:8px 12px}.group-section-wrapper.multi-save-active .multi-save-header .btn-add-multi ::ng-deep button:hover{color:var(--ms-btn-add-hover, #2563EB);background-color:var(--cc-sf-btn-add-hover-bg, #EFF6FF)}.group-section-wrapper.multi-save-active .group-instance{background:var(--ms-card-bg, #ffffff);border:1px solid var(--ms-card-border, #E5E7EB);border-radius:var(--ms-card-radius, 10px);box-shadow:var(--ms-card-shadow, 0 1px 4px rgba(0, 0, 0, .05));padding:0;margin-bottom:16px;overflow:hidden}.group-section-wrapper.multi-save-active .group-instance.is-editing{padding:24px}.group-section-wrapper.multi-save-active .group-instance.is-card{cursor:pointer;transition:all .2s ease-in-out}.group-section-wrapper.multi-save-active .group-instance.is-card:hover{box-shadow:var(--ms-card-shadow-hover, 0 8px 24px rgba(0, 0, 0, .08));border-color:var(--cc-sf-input-focus-border, #3B82F6)}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view{display:flex;justify-content:space-between;align-items:center;padding:18px 24px}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-content{flex:1;display:flex;flex-direction:column;gap:4px;overflow:hidden}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-content .card-title{font-size:1rem;font-weight:600;color:var(--ms-title-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-content .card-desc{font-size:.875rem;color:var(--ms-desc-color, #6B7280);line-height:1.4;display:-webkit-box;-webkit-line-clamp:1;line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view.is-expanded .card-content .card-desc{-webkit-line-clamp:unset;line-clamp:unset}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions{display:flex;align-items:center;gap:16px;margin-left:20px}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions mat-icon{font-size:22px;width:22px;height:22px;color:var(--cc-sf-hint-color, #9CA3AF);transition:color .2s}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions mat-icon.icon-delete:hover{color:var(--cc-sf-error-border, #DC2626)}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions mat-icon.icon-edit:hover{color:var(--cc-sf-input-focus-border, #3B82F6)}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions mat-icon.icon-expand{color:var(--cc-sf-input-disabled-border, #E5E7EB)}.group-section-wrapper.multi-save-active .group-footer{display:flex;justify-content:space-between;align-items:center;gap:16px;margin-top:24px;padding-top:20px;border-top:1px solid var(--cc-sf-instance-divider, #E5E7EB)}.group-section-wrapper.multi-save-active .group-footer .footer-actions{display:flex;gap:12px}.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, #FFFAF1);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)}.uploaded-item.uploading{background:var(--cc-sf-uploaded-uploading-bg, #F8FAFC);border-color:var(--cc-sf-uploaded-uploading-border, #CBD5E1);opacity:.85}.upload-spinner{width:20px;height:20px;flex-shrink:0;border:2px solid var(--cc-sf-spinner-track, #E2E8F0);border-top-color:var(--cc-sf-spinner-color, #3B82F6);border-radius:50%;animation:cc-spin .7s linear infinite}@keyframes cc-spin{to{transform:rotate(360deg)}}.rich-text-editor{display:block;width:100%}.uploading-label{color:var(--cc-sf-spinner-color, #3B82F6)!important;font-style:italic}.input-group{position:relative;display:flex;align-items:stretch;width:100%}.input-group .field-input{flex:1;border-radius:var(--cc-sf-input-radius, 8px)}.input-prefix+.input-group .field-input{border-top-left-radius:0;border-bottom-left-radius:0}.input-group .field-input:has(+.input-suffix){border-top-right-radius:0;border-bottom-right-radius:0}.input-group .field-input.has-icon-right{padding-right:3rem}.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)}.date-icon-wrapper{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);display:flex;align-items:center;justify-content:center;pointer-events:auto}.date-icon-wrapper .mat-icon-button{width:32px;height:32px;line-height:32px}.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)}.autocomplete-wrapper{position:relative;display:flex;align-items:center;width:100%}.autocomplete-wrapper .ac-search-icon{position:absolute;left:.75rem;font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem;color:var(--cc-sf-hint-color, #9CA3AF);pointer-events:none;z-index:1;transition:color var(--cc-sf-input-transition, .2s ease)}.autocomplete-wrapper .ac-input{flex:1;padding-left:2.4rem;padding-right:2.4rem}.autocomplete-wrapper .ac-clear-btn{position:absolute;right:.6rem;display:flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:.2rem;border-radius:50%;color:var(--cc-sf-hint-color, #9CA3AF);transition:color .15s ease,background .15s ease}.autocomplete-wrapper .ac-clear-btn mat-icon{font-size:1rem;width:1rem;height:1rem;line-height:1rem}.autocomplete-wrapper .ac-clear-btn:hover{color:var(--cc-sf-label-color, #374151);background:var(--cc-sf-input-disabled-bg, #F3F4F6)}.autocomplete-wrapper .ac-clear-btn:focus{outline:none}.autocomplete-wrapper:focus-within .ac-search-icon{color:var(--cc-sf-input-focus-border, #3B82F6)}.autocomplete-wrapper.is-invalid .ac-input{border-color:var(--cc-sf-error-border, #DC2626);background-color:var(--cc-sf-error-bg, #FEF2F2)}.autocomplete-wrapper.readonly .ac-input{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)}.ac-no-results{font-style:italic;font-size:.8125rem;color:var(--cc-sf-hint-color, #6B7280)}.media-upload-wrapper{padding:0;border:none;background:none}.mu-layout{display:grid;grid-template-columns:1fr 1fr;gap:32px;align-items:flex-start}@media(max-width:768px){.mu-layout{grid-template-columns:1fr}}.mu-left{display:flex;flex-direction:column;gap:20px}.mu-header{display:flex;align-items:flex-start;flex-wrap:wrap;gap:10px}.mu-title{margin:0;font-size:1.35rem;font-weight:700;color:var(--cc-sf-label-color, #111827);line-height:1.3}.mu-badge{display:inline-flex;align-items:center;padding:4px 12px;border-radius:20px;background:var(--cc-sf-label-color, #111827);color:#fff;font-size:.72rem;font-weight:600;white-space:nowrap;flex-shrink:0}.mu-description{margin:0;font-size:.875rem;color:var(--cc-sf-hint-color, #6B7280);line-height:1.6}.mu-features{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.mu-feature-item{display:flex;align-items:center;gap:8px;font-size:.875rem;color:var(--cc-sf-hint-color, #374151)}.mu-feature-item .mu-check{font-size:16px;width:16px;height:16px;line-height:16px;color:var(--cc-sf-chip-selected-bg, #3B82F6);flex-shrink:0}.mu-right{display:flex;flex-direction:column;gap:12px;min-height:260px}.mu-right-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;height:100%;min-height:250px;max-width:400px;border:2px dashed var(--cc-sf-dropzone-border, #CBD5E1);border-radius:var(--mu-carousel-radius, 12px);background:var(--cc-sf-dropzone-bg, #FFFAF1);text-align:center;color:var(--cc-sf-hint-color, #94A3B8);padding:24px;box-shadow:0 2px 10px #0000000d;transition:box-shadow .2s ease}.mu-right-empty:hover{cursor:pointer;box-shadow:0 4px 16px #0000001a}.mu-right-empty .mu-right-empty-icon{font-size:52px;width:52px;height:52px;line-height:52px;opacity:.3}.mu-right-empty p{margin:0;font-size:.85rem}.media-add-container{position:relative;display:inline-block}.media-add-container ::ng-deep button{display:flex;align-items:center;gap:6px}.media-add-container ::ng-deep button .menu-chevron{font-size:18px;width:18px;height:18px;line-height:18px;transition:transform .2s ease}.media-dropdown{position:absolute;top:calc(100% + 6px);left:0;z-index:200;min-width:240px;background:var(--mu-dropdown-bg, #ffffff);border:var(--mu-dropdown-border, 1px solid #E5E7EB);border-radius:12px;box-shadow:var(--mu-dropdown-shadow, 0 8px 32px rgba(0, 0, 0, .12));overflow:hidden;animation:mu-fade-in .15s ease}@keyframes mu-fade-in{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}.media-dropdown-item{display:flex;align-items:center;gap:12px;width:100%;padding:12px 16px;background:none;border:none;border-bottom:1px solid var(--cc-sf-input-disabled-border, #F3F4F6);cursor:pointer;text-align:left;transition:background .15s ease;font-family:var(--cc-sf-font-family, inherit)}.media-dropdown-item:last-child{border-bottom:none}.media-dropdown-item:hover{background:var(--cc-sf-dropzone-hover-bg, #F0F9FF)}.media-drop-icon{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:8px;flex-shrink:0}.media-drop-icon mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.media-drop-icon--video{background:var(--mu-icon-video-bg, #FFF0F0);color:var(--mu-icon-video-color, #EF4444)}.media-drop-icon--device{background:var(--mu-icon-device-bg, #EFF6FF);color:var(--mu-icon-device-color, #3B82F6)}.media-drop-icon--library{background:var(--mu-icon-library-bg, #F0FDF4);color:var(--mu-icon-library-color, #22C55E)}.media-drop-text{display:flex;flex-direction:column;gap:2px;flex:1}.media-drop-label{font-size:.875rem;font-weight:600;color:var(--cc-sf-label-color, #111827)}.media-drop-desc{font-size:.75rem;color:var(--cc-sf-hint-color, #6B7280)}.youtube-input-panel{display:flex;flex-direction:column;gap:8px;padding:16px;background:var(--cc-sf-dropzone-bg, #FFFAF1);border:1px solid var(--cc-sf-input-border, #E5E7EB);border-radius:10px;animation:mu-fade-in .18s ease}.youtube-panel-label{display:flex;align-items:center;gap:6px;font-size:.875rem;font-weight:600;color:var(--cc-sf-label-color, #111827)}.youtube-panel-label mat-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--mu-icon-video-color, #EF4444)}.youtube-input-row{display:flex;gap:8px;align-items:stretch}.youtube-input-row .youtube-url-input{flex:1}.media-menu-backdrop{position:fixed;inset:0;z-index:100}.media-upload-status{display:flex;align-items:center;gap:8px;padding:10px 14px;margin-top:4px;background:var(--cc-sf-error-bg, #FEF2F2);color:var(--cc-sf-error-text-color, #DC2626);border-radius:8px;font-size:.85rem;font-weight:500;animation:mu-fade-in .2s ease}.media-upload-status .status-icon{font-size:18px;width:18px;height:18px;line-height:18px}.media-carousel-section{display:flex;flex-direction:column;gap:12px}.media-carousel-main{position:relative;width:100%;max-width:400px;height:var(--mu-carousel-height, 250px);background:var(--mu-carousel-bg, #0F172A);border-radius:var(--mu-carousel-radius, 12px);overflow:hidden;display:flex;align-items:center;justify-content:center}.carousel-viewer{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.carousel-viewer .carousel-image{width:100%;height:100%;object-fit:cover;border-radius:var(--mu-carousel-radius, 12px)}.carousel-viewer .carousel-iframe{width:100%;height:100%;border-radius:var(--mu-carousel-radius, 12px)}.carousel-viewer .carousel-uploading{display:flex;flex-direction:column;align-items:center;gap:12px;color:#94a3b8;font-size:.85rem}.carousel-viewer .carousel-spinner{width:36px;height:36px;border:3px solid rgba(255,255,255,.15);border-top-color:#3b82f6;border-radius:50%;animation:cc-spin .7s linear infinite}.carousel-nav{position:absolute;top:50%;transform:translateY(-50%);z-index:10;width:40px;height:40px;border-radius:50%;background:#ffffffd9;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 8px #0003;transition:background .2s ease,opacity .2s ease;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.carousel-nav mat-icon{font-size:22px;width:22px;height:22px;line-height:22px;color:#1e293b}.carousel-nav:hover:not(:disabled){background:#fff}.carousel-nav:disabled{opacity:.3;cursor:not-allowed}.carousel-nav--prev{left:12px}.carousel-nav--next{right:12px}.carousel-remove-btn{position:absolute;top:10px;right:10px;z-index:10;width:32px;height:32px;border-radius:50%;background:#0000008c;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .2s ease}.carousel-remove-btn mat-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:#fff}.carousel-remove-btn:hover:not(:disabled){background:#dc2626d9}.carousel-remove-btn:disabled{opacity:.4;cursor:not-allowed}.carousel-dots{position:absolute;bottom:10px;left:50%;transform:translate(-50%);display:flex;gap:6px;z-index:10}.carousel-dot{width:8px;height:8px;border-radius:50%;background:#ffffff73;cursor:pointer;transition:background .2s ease,transform .2s ease}.carousel-dot.active{background:#fff;transform:scale(1.3)}.media-thumbnail-strip{display:flex;flex-wrap:wrap;max-width:400px;gap:8px;overflow-x:auto;padding-bottom:4px}.media-thumbnail-strip::-webkit-scrollbar{height:4px}.media-thumbnail-strip::-webkit-scrollbar-thumb{background:var(--cc-sf-input-disabled-border, #D1D5DB);border-radius:2px}.media-thumb{flex-shrink:0;width:72px;height:52px;border-radius:8px;overflow:hidden;cursor:pointer;border:2px solid transparent;background:var(--mu-thumb-bg, #E2E8F0);display:flex;align-items:center;justify-content:center;transition:border-color .2s ease,transform .15s ease}.media-thumb.active{border-color:var(--mu-thumb-active-border, #3B82F6);transform:scale(1.04)}.media-thumb:hover:not(.active){border-color:var(--cc-sf-input-hover-border, #9CA3AF)}.media-thumb .thumb-img{width:100%;height:100%;object-fit:cover}.media-thumb .thumb-yt-placeholder{display:flex;align-items:center;justify-content:center;width:100%;height:100%;background:#1e293b;color:#ef4444}.media-thumb .thumb-yt-placeholder mat-icon{font-size:28px;width:28px;height:28px;line-height:28px}.media-thumb .thumb-uploading{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.media-thumb .thumb-uploading .thumb-spinner{width:20px;height:20px;border:2px solid #E2E8F0;border-top-color:#3b82f6;border-radius:50%;animation:cc-spin .7s linear infinite}.media-library-overlay{position:fixed;inset:0;background:#00000080;z-index:999;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:mu-fade-in .2s ease}.media-library-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1000;width:min(90vw,780px);max-height:85vh;background:var(--mu-modal-bg, #ffffff);border-radius:var(--mu-modal-radius, 16px);box-shadow:var(--mu-modal-shadow, 0 20px 60px rgba(0, 0, 0, .2));display:flex;flex-direction:column;overflow:hidden;animation:mu-modal-in .22s cubic-bezier(.34,1.56,.64,1)}@keyframes mu-modal-in{0%{opacity:0;transform:translate(-50%,-48%) scale(.95)}to{opacity:1;transform:translate(-50%,-50%) scale(1)}}.library-modal-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px 16px;border-bottom:1px solid var(--cc-sf-input-disabled-border, #F3F4F6);flex-shrink:0}.library-modal-title{display:flex;align-items:center;gap:8px;margin:0;font-size:1.05rem;font-weight:700;color:var(--cc-sf-label-color, #111827)}.library-modal-title mat-icon{font-size:22px;width:22px;height:22px;line-height:22px;color:var(--mu-icon-library-color, #22C55E)}.library-close-btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;background:none;cursor:pointer;border-radius:50%;color:var(--cc-sf-hint-color, #9CA3AF);transition:background .15s ease,color .15s ease}.library-close-btn mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.library-close-btn:hover{background:var(--cc-sf-input-disabled-bg, #F3F4F6);color:var(--cc-sf-label-color, #374151)}.library-loading,.library-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:48px 24px;color:var(--cc-sf-hint-color, #9CA3AF);font-size:.875rem;flex:1}.library-loading mat-icon,.library-empty mat-icon{font-size:40px;width:40px;height:40px;line-height:40px;opacity:.5}.lib-spinner{width:36px;height:36px;border:3px solid #E2E8F0;border-top-color:#3b82f6;border-radius:50%;animation:cc-spin .7s linear infinite}.library-error{display:flex;align-items:center;gap:8px;padding:16px 24px;background:var(--cc-sf-error-bg, #FEF2F2);color:var(--cc-sf-error-text-color, #DC2626);font-size:.875rem;flex-shrink:0}.library-error mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.library-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;padding:24px;overflow-y:auto;flex:1}.library-grid::-webkit-scrollbar{width:6px}.library-grid::-webkit-scrollbar-thumb{background:var(--cc-sf-input-disabled-border, #D1D5DB);border-radius:3px}.library-grid-item{position:relative;aspect-ratio:4/3;border-radius:10px;overflow:hidden;cursor:pointer;border:2.5px solid transparent;transition:border-color .2s ease,transform .15s ease}.library-grid-item.selected{border-color:var(--cc-sf-chip-selected-bg, #3B82F6);transform:scale(.97)}.library-grid-item:hover .library-overlay-hover{opacity:1}.library-grid-img{width:100%;height:100%;object-fit:cover;display:block}.library-overlay-hover{position:absolute;inset:0;background:#3b82f61f;opacity:0;transition:opacity .15s ease}.library-check{position:absolute;top:6px;right:6px;color:var(--cc-sf-chip-selected-bg, #3B82F6);background:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;width:22px;height:22px;box-shadow:0 1px 4px #00000026}.library-check mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}.library-modal-footer{display:flex;align-items:center;justify-content:space-between;padding:16px 24px 20px;border-top:1px solid var(--cc-sf-input-disabled-border, #F3F4F6);flex-shrink:0}.library-selected-count{font-size:.875rem;font-weight:600;color:var(--cc-sf-hint-color, #6B7280)}.library-footer-actions{display:flex;gap:10px}.location-field-wrapper{gap:12px}.location-subtitle{margin:0;font-size:var(--cc-sf-hint-size, .8125rem);color:var(--cc-sf-hint-color, #6B7280);line-height:1.5}.location-tabs{display:flex;gap:12px;margin-bottom:24px}.loc-tab-btn{flex:1}.loc-tab-btn ::ng-deep button{width:100%}.loc-tab-btn ::ng-deep button:not(.cc-btn-warning){background-color:#fff!important;color:#000!important;border:1px solid #E5E7EB}.loc-tab-btn ::ng-deep button:not(.cc-btn-warning):hover{background-color:#f3f4f6!important}.loc-panel{display:flex;flex-direction:column;gap:12px}.loc-section-label{margin:0;font-size:var(--cc-sf-label-size, .9rem);font-weight:600;color:var(--cc-sf-label-color, #111827)}.loc-venue-list{display:flex;flex-direction:column;gap:8px}.loc-venue-item{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--loc-venue-item-bg, #ffffff);border:1px solid var(--loc-venue-item-border, #D1D5DB);border-radius:var(--cc-sf-input-radius, 7px);transition:box-shadow .15s ease,border-color .15s ease}.loc-venue-item:hover{box-shadow:0 2px 8px #0000000f;border-color:var(--cc-sf-input-hover-border, #9CA3AF)}.loc-venue-search-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--cc-sf-hint-color, #9CA3AF);flex-shrink:0}.loc-venue-text{flex:1;font-size:var(--cc-sf-input-font-size, .875rem);color:var(--cc-sf-input-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.loc-action-btn{display:flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:4px;border-radius:50%;transition:background .15s ease,color .15s ease;flex-shrink:0}.loc-action-btn mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}.loc-action-btn.loc-delete-btn{color:var(--loc-delete-color, #E53E3E)}.loc-action-btn.loc-delete-btn:hover{background:var(--cc-sf-error-bg, #FEF2F2)}.loc-action-btn.loc-edit-btn{color:var(--cc-sf-hint-color, #9CA3AF)}.loc-action-btn.loc-edit-btn:hover{color:var(--cc-sf-input-focus-border, #3B82F6);background:var(--cc-sf-dropzone-hover-bg, #EFF6FF)}.loc-count-text{margin:0;font-size:.8125rem;font-weight:600;color:var(--cc-sf-input-focus-border, #3B82F6)}.loc-search-container{position:relative}.loc-search-wrapper{position:relative;display:flex;align-items:center}.loc-search-icon{position:absolute;left:.75rem;font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem;color:var(--cc-sf-hint-color, #9CA3AF);pointer-events:none;z-index:1}.loc-search-input{flex:1;padding-left:2.4rem!important}.loc-suggestions-panel{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:300;background:var(--loc-suggestion-bg, #ffffff);border:1px solid var(--cc-sf-input-border, #D1D5DB);border-radius:var(--cc-sf-input-radius, 8px);box-shadow:0 8px 24px #0000001a;overflow:hidden;animation:mu-fade-in .15s ease;max-height:260px;overflow-y:auto}.loc-suggestion-item{display:flex;align-items:center;gap:10px;padding:10px 14px;cursor:pointer;transition:background .12s ease;font-family:var(--cc-sf-font-family, inherit)}.loc-suggestion-item:hover,.loc-suggestion-item:focus{background:var(--loc-suggestion-hover-bg, #F0F9FF)}.loc-suggestion-item:not(:last-child){border-bottom:1px solid var(--cc-sf-input-disabled-border, #F3F4F6)}.loc-suggestion-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--loc-delete-color, #E53E3E);flex-shrink:0}.loc-suggestion-text{font-size:var(--cc-sf-input-font-size, .875rem);color:var(--cc-sf-label-color, #374151);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.loc-add-btn{display:inline-flex;align-items:center;gap:6px;background:none;border:none;cursor:pointer;color:var(--loc-add-color, #1A56DB);font-family:var(--cc-sf-font-family, inherit);font-size:var(--cc-sf-input-font-size, .875rem);font-weight:600;padding:0;transition:opacity .15s ease}.loc-add-btn mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.loc-add-btn:hover{opacity:.8}.loc-map-container{border-radius:var(--loc-map-radius, 10px);overflow:hidden;border:1px solid var(--cc-sf-input-disabled-border, #E5E7EB);box-shadow:0 2px 10px #0000000f}.loc-map-frame{width:100%;display:block;border:none}.loc-map-hint{margin:0;font-size:.78rem;color:var(--cc-sf-hint-color, #6B7280);text-align:center}.loc-tba-panel{min-height:120px;justify-content:center}.loc-tba-content{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:12px;padding:32px 24px;background:var(--loc-tba-bg, #F9FAFB);border:1px dashed var(--cc-sf-input-disabled-border, #D1D5DB);border-radius:var(--cc-sf-input-radius, 10px)}.loc-tba-icon{font-size:40px;width:40px;height:40px;line-height:40px;color:var(--loc-tba-icon-color, #9CA3AF);opacity:.6}.loc-tba-text{margin:0;font-size:var(--cc-sf-input-font-size, .9rem);color:var(--cc-sf-hint-color, #6B7280);line-height:1.6;max-width:360px}.loc-online-panel .loc-search-wrapper{margin-top:4px}\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.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i5.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { 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: i9.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "directive", type: i9.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "component", type: ButtonComponent, selector: "lib-button", inputs: ["variant", "type", "disabled", "width", "height", "borderRadius", "fontSize", "fontWeight", "backgroundColor", "color", "border", "icon", "labels"] }, { kind: "component", type: i11.QuillEditorComponent, selector: "quill-editor" }, { kind: "component", type: FormFieldComponent, selector: "lib-form-field", inputs: ["config", "controller", "formGroup", "allowMulti"] }, { kind: "pipe", type: TrustedUrlPipe, name: "trustedUrl" }] });
5368
5368
  }
5369
5369
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FormFieldComponent, decorators: [{
5370
5370
  type: Component,
5371
- 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 [class.multi-save-active]=\"config.sectionConfig?.multiSaveConfig?.active\">\r\n\r\n <!-- Multi-Save Header with Add button (top-right style) -->\r\n <div class=\"multi-save-header\" *ngIf=\"config.sectionConfig?.multiSaveConfig?.active\">\r\n <h3 class=\"group-label\" *ngIf=\"config.sectionConfig?.label\">{{ config.sectionConfig!.label }}</h3>\r\n <lib-button [variant]=\"'outline'\" [icon]=\"{type: 'material', value: 'add'}\" (click)=\"addGroupInstance()\"\r\n class=\"btn-add-multi\">\r\n {{ addMultiLabel }}\r\n </lib-button>\r\n </div>\r\n\r\n <!-- Standard Header for non-multiSave -->\r\n <h3 class=\"group-label\" *ngIf=\"config.sectionConfig?.label && !config.sectionConfig?.multiSaveConfig?.active\">{{\r\n config.sectionConfig!.label }}</h3>\r\n\r\n <div *ngFor=\"let instance of instanceList; trackBy: trackByInstanceId; let i = index\" class=\"group-instance\"\r\n [class.is-editing]=\"instance.isEditing\"\r\n [class.is-card]=\"config.sectionConfig?.multiSaveConfig?.active && !instance.isEditing\">\r\n\r\n <!-- 1. EDIT MODE / UNSAVED / STANDARD STATE -->\r\n <div [hidden]=\"config.sectionConfig?.multiSaveConfig?.active && !instance.isEditing\">\r\n <!-- Instance header \u2014 show remove only when more than 1 instance -->\r\n <div class=\"group-header\" *ngIf=\"!config.sectionConfig?.multiSaveConfig?.active && 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 <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\r\n <!-- SAVE / CANCEL BUTTONS for MultiSave in Editing phase -->\r\n <div class=\"group-footer\" *ngIf=\"config.sectionConfig?.multiSaveConfig?.active\">\r\n <span class=\"field-error\" *ngIf=\"multiSaveError\">{{ multiSaveError }}</span>\r\n <div class=\"footer-actions\">\r\n <lib-button [variant]=\"'outline'\" (click)=\"cancelGroupInstance(i)\">Cancel</lib-button>\r\n <lib-button [variant]=\"'primary'\" (click)=\"saveGroupInstance(i)\">Save</lib-button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 2. CARD VIEW (Saved State) -->\r\n <ng-container *ngIf=\"config.sectionConfig?.multiSaveConfig?.active && !instance.isEditing\">\r\n <div class=\"card-view\" [class.is-expanded]=\"instance.isExpanded\">\r\n <div class=\"card-content\">\r\n <span class=\"card-title\">{{ instance.fg.get(config.sectionConfig.multiSaveConfig.summaryField || '')?.value\r\n || '\u2014' }}</span>\r\n <span class=\"card-desc\" *ngIf=\"config.sectionConfig.multiSaveConfig.descriptionField\">\r\n {{ instance.fg.get(config.sectionConfig.multiSaveConfig.descriptionField)?.value }}\r\n </span>\r\n </div>\r\n <div class=\"card-actions\">\r\n <mat-icon class=\"icon-delete\" (click)=\"removeGroupInstance(i, true)\">delete_outline</mat-icon>\r\n <mat-icon class=\"icon-edit\" (click)=\"editGroupInstance(i)\">edit_outline</mat-icon>\r\n <mat-icon class=\"icon-expand\" (click)=\"toggleExpandGroupInstance(i)\">\r\n {{ instance.isExpanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}\r\n </mat-icon>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- Standard Add Button for non-multiSave -->\r\n <lib-button *ngIf=\"!config.sectionConfig?.multiSaveConfig?.active\" [variant]=\"'outline'\"\r\n [icon]=\"{type: 'material', value: 'add'}\" (click)=\"addGroupInstance()\" 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 class=\"char-count-hint\" *ngIf=\"showCharCount\">\r\n {{ remainingCharacters }} characters remaining\r\n </div>\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 <div class=\"input-group\" [class.readonly]=\"config.readonly\">\r\n <input matInput [matDatepicker]=\"datePicker\" class=\"field-input date-input has-icon-right\"\r\n [formControlName]=\"config.name!\" [min]=\"config.dateConfig?.minDate\" [max]=\"config.dateConfig?.maxDate\"\r\n [class.is-invalid]=\"errorMessage\" [placeholder]=\"config.placeholder || ''\" [readonly]=\"config.readonly\"\r\n (click)=\"!config.readonly && datePicker.open()\">\r\n <div class=\"date-icon-wrapper\" *ngIf=\"!config.readonly\">\r\n <mat-datepicker-toggle matSuffix [for]=\"datePicker\"></mat-datepicker-toggle>\r\n </div>\r\n <mat-datepicker #datePicker></mat-datepicker>\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 Time 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=\"isTimeField\" 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 <input type=\"time\" class=\"field-input time-input\" [formControlName]=\"config.name!\"\r\n [class.is-invalid]=\"errorMessage\" [readonly]=\"!!config.readonly\">\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 Autocomplete \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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=\"isAutocomplete\" 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 <!-- Hidden real control (stores the code value) -->\r\n <input type=\"hidden\" [formControlName]=\"config.name!\">\r\n\r\n <div class=\"autocomplete-wrapper\" [class.is-invalid]=\"errorMessage\" [class.readonly]=\"config.readonly\">\r\n <!-- Search icon -->\r\n <mat-icon class=\"ac-search-icon\">search</mat-icon>\r\n\r\n <input class=\"field-input ac-input\" [formControl]=\"autocompleteInputCtrl\" [matAutocomplete]=\"auto\"\r\n [placeholder]=\"config.placeholder || 'Search\u2026'\" [readonly]=\"!!config.readonly\" [class.is-invalid]=\"errorMessage\"\r\n (blur)=\"onAutocompleteClear()\" autocomplete=\"off\">\r\n\r\n <!-- Clear button -->\r\n <button type=\"button\" class=\"ac-clear-btn\" *ngIf=\"autocompleteInputCtrl.value && !config.readonly\"\r\n (click)=\"autocompleteInputCtrl.setValue(''); updateValue(null)\" tabindex=\"-1\" aria-label=\"Clear\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n\r\n <mat-autocomplete #auto=\"matAutocomplete\" [panelWidth]=\"'auto'\">\r\n <mat-option *ngFor=\"let option of filteredOptions\" [value]=\"option.label\"\r\n (onSelectionChange)=\"onAutocompleteSelected(option)\">\r\n {{ option.label }}\r\n </mat-option>\r\n <mat-option *ngIf=\"filteredOptions.length === 0\" disabled class=\"ac-no-results\">\r\n No results found\r\n </mat-option>\r\n </mat-autocomplete>\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 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)\" hidden>\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 Rich Text \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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=\"isRichText\" class=\"field-wrapper component-rich-text\" [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=\"rich-text-container\" [class.is-invalid]=\"errorMessage\">\r\n <quill-editor [formControlName]=\"config.name!\" class=\"rich-text-editor\"\r\n [placeholder]=\"config.richTextConfig?.placeholder || config.placeholder || ''\"\r\n [styles]=\"{height: config.richTextConfig?.height || '200px'}\">\r\n </quill-editor>\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 class=\"char-count-hint\" *ngIf=\"showCharCount\">\r\n {{ remainingCharacters }} characters remaining\r\n </div>\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\" hidden [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\" [class.uploading]=\"f.isUploading\">\r\n\r\n <!-- Uploading spinner (shown while API call is in progress) -->\r\n <ng-container *ngIf=\"f.isUploading; else fileReady\">\r\n <div class=\"upload-spinner\"></div>\r\n <div class=\"file-info\">\r\n <span class=\"file-name\" [title]=\"f.name\">{{ f.name }}</span>\r\n <span class=\"file-size uploading-label\">Uploading...</span>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Normal state once upload is done -->\r\n <ng-template #fileReady>\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 </ng-template>\r\n\r\n <!-- Remove button \u2014 disabled while uploading -->\r\n <lib-button [variant]=\"'danger-outline'\" [disabled]=\"f.isUploading\"\r\n (click)=\"!f.isUploading && 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 <!-- \u2550\u2550 Media Upload (Type 2) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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=\"isMediaUpload\" class=\"field-wrapper media-upload-wrapper\" [formGroup]=\"formGroup\">\r\n\r\n <!-- Hidden file input lives outside *ngIf \u2014 triggered via ViewChild -->\r\n <input #mediaDeviceInput type=\"file\" hidden multiple accept=\"image/*\" (change)=\"onMediaFileSelected($event)\">\r\n\r\n <!-- Two-column layout -->\r\n <div class=\"mu-layout\">\r\n\r\n <!-- \u2500\u2500 LEFT PANEL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"mu-left\">\r\n\r\n <!-- Header: title + max-items badge -->\r\n <div class=\"mu-header\">\r\n <h3 class=\"mu-title\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </h3>\r\n <span class=\"mu-badge\" *ngIf=\"config.attachmentConfig?.maxFiles\">\r\n {{ controller.labels['LBL_MEDIA_MAX_PREFIX'] || 'Maximum' }}\r\n {{ config.attachmentConfig?.maxFiles }}\r\n {{ controller.labels['LBL_MEDIA_MAX_SUFFIX'] || 'Image & Videos' }}\r\n </span>\r\n </div>\r\n\r\n <!-- Description -->\r\n <p class=\"mu-description\" *ngIf=\"config.attachmentConfig?.description\">\r\n {{ config.attachmentConfig?.description }}\r\n </p>\r\n <p class=\"mu-description\" *ngIf=\"!config.attachmentConfig?.description && controller.labels['LBL_MEDIA_DESC']\">\r\n {{ controller.labels['LBL_MEDIA_DESC'] }}\r\n </p>\r\n\r\n <!-- Feature bullet list -->\r\n <ul class=\"mu-features\"\r\n *ngIf=\"config.attachmentConfig?.features?.length || controller.labels['LBL_MEDIA_FEATURE_1']\">\r\n <ng-container *ngIf=\"config.attachmentConfig?.features?.length\">\r\n <li *ngFor=\"let f of config.attachmentConfig?.features\" class=\"mu-feature-item\">\r\n <mat-icon class=\"mu-check\">check</mat-icon>{{ f }}\r\n </li>\r\n </ng-container>\r\n <ng-container *ngIf=\"!config.attachmentConfig?.features?.length\">\r\n <li *ngIf=\"controller.labels['LBL_MEDIA_FEATURE_1']\" class=\"mu-feature-item\">\r\n <mat-icon class=\"mu-check\">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_1'] }}\r\n </li>\r\n <li *ngIf=\"controller.labels['LBL_MEDIA_FEATURE_2']\" class=\"mu-feature-item\">\r\n <mat-icon class=\"mu-check\">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_2'] }}\r\n </li>\r\n <li *ngIf=\"controller.labels['LBL_MEDIA_FEATURE_3']\" class=\"mu-feature-item\">\r\n <mat-icon class=\"mu-check\">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_3'] }}\r\n </li>\r\n </ng-container>\r\n </ul>\r\n\r\n <!-- Backdrop to close dropdown on outside click -->\r\n <div class=\"media-menu-backdrop\" *ngIf=\"showMediaMenu\"\r\n (click)=\"$event.stopPropagation(); showMediaMenu = false\"></div>\r\n\r\n <!-- Add Media button + dropdown -->\r\n <div class=\"media-add-container\" (click)=\"showMediaMenu = !showMediaMenu\">\r\n <lib-button id=\"btn-add-media-{{ config.name }}\" [variant]=\"'warning'\"\r\n [icon]=\"{type: 'material', value: 'add_photo_alternate'}\">\r\n {{ controller.labels['LBL_ADD_MEDIA'] || 'Add media' }}\r\n <mat-icon class=\"menu-chevron\">add</mat-icon>\r\n </lib-button>\r\n\r\n <div class=\"media-dropdown\" *ngIf=\"showMediaMenu\" role=\"menu\" (click)=\"$event.stopPropagation()\">\r\n <!-- Video -->\r\n <button id=\"btn-media-video-{{ config.name }}\" type=\"button\" class=\"media-dropdown-item\"\r\n (click)=\"onMediaMenuVideo(); showMediaMenu = false\" role=\"menuitem\">\r\n <span class=\"media-drop-icon media-drop-icon--video\"><mat-icon>videocam</mat-icon></span>\r\n <span class=\"media-drop-text\">\r\n <span class=\"media-drop-label\">{{ controller.labels['LBL_MEDIA_VIDEO'] || 'Video' }}</span>\r\n <span class=\"media-drop-desc\">{{ controller.labels['LBL_MEDIA_VIDEO_DESC'] || 'Add YouTube URL'\r\n }}</span>\r\n </span>\r\n </button>\r\n <!-- Device -->\r\n <button id=\"btn-media-device-{{ config.name }}\" type=\"button\" class=\"media-dropdown-item\"\r\n (click)=\"onMediaMenuDevice(); showMediaMenu = false\" role=\"menuitem\">\r\n <span class=\"media-drop-icon media-drop-icon--device\"><mat-icon>upload</mat-icon></span>\r\n <span class=\"media-drop-text\">\r\n <span class=\"media-drop-label\">{{ controller.labels['LBL_MEDIA_DEVICE'] || 'Upload from device'\r\n }}</span>\r\n <span class=\"media-drop-desc\">{{ controller.labels['LBL_MEDIA_DEVICE_DESC'] || 'Select images from your\r\n computer' }}</span>\r\n </span>\r\n </button>\r\n <!-- Library -->\r\n <button id=\"btn-media-library-{{ config.name }}\" type=\"button\" class=\"media-dropdown-item\"\r\n (click)=\"onMediaMenuLibrary(); showMediaMenu = false\" role=\"menuitem\">\r\n <span class=\"media-drop-icon media-drop-icon--library\"><mat-icon>photo_library</mat-icon></span>\r\n <span class=\"media-drop-text\">\r\n <span class=\"media-drop-label\">{{ controller.labels['LBL_MEDIA_LIBRARY'] || 'Upload from library'\r\n }}</span>\r\n <span class=\"media-drop-desc\">{{ controller.labels['LBL_MEDIA_LIBRARY_DESC'] || 'Choose from default\r\n images' }}</span>\r\n </span>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- YouTube URL Input (inline below button) -->\r\n <div class=\"youtube-input-panel\" *ngIf=\"showYoutubeInput\">\r\n <label class=\"youtube-panel-label\">\r\n {{ controller.labels['LBL_YOUTUBE_URL'] || 'Video URL' }}\r\n </label>\r\n <div class=\"youtube-input-row\">\r\n <input id=\"input-youtube-url-{{ config.name }}\" type=\"url\" class=\"field-input youtube-url-input\"\r\n [(ngModel)]=\"youtubeUrlInput\" [ngModelOptions]=\"{standalone: true}\"\r\n [placeholder]=\"controller.labels['PH_YOUTUBE_URL'] || 'Video URL'\" (keyup.enter)=\"addYoutubeMedia()\">\r\n <lib-button id=\"btn-add-youtube-{{ config.name }}\" [variant]=\"'secondary'\" (click)=\"addYoutubeMedia()\">\r\n {{ controller.labels['LBL_ADD'] || 'Add' }}\r\n </lib-button>\r\n </div>\r\n <span class=\"field-error\" *ngIf=\"youtubeUrlError\">{{ youtubeUrlError }}</span>\r\n </div>\r\n\r\n <div class=\"media-upload-status\" *ngIf=\"mediaUploadError\">\r\n <mat-icon class=\"status-icon\">error_outline</mat-icon>\r\n <span>{{ mediaUploadError }}</span>\r\n </div>\r\n\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n </div>\r\n <!-- end left panel -->\r\n\r\n <!-- \u2500\u2500 RIGHT PANEL (carousel) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"mu-right\">\r\n\r\n <!-- Carousel (when items exist) -->\r\n <div class=\"media-carousel-section\" *ngIf=\"mediaItems.length\">\r\n <div class=\"media-carousel-main\">\r\n <button id=\"btn-carousel-prev-{{ config.name }}\" type=\"button\" class=\"carousel-nav carousel-nav--prev\"\r\n (click)=\"mediaCarouselPrev()\" [disabled]=\"mediaCarouselIndex === 0\" aria-label=\"Previous\">\r\n <mat-icon>chevron_left</mat-icon>\r\n </button>\r\n\r\n <div class=\"carousel-viewer\" *ngFor=\"let item of mediaItems; let i = index\"\r\n [hidden]=\"i !== mediaCarouselIndex\">\r\n <div *ngIf=\"item.isUploading\" class=\"carousel-uploading\">\r\n <div class=\"carousel-spinner\"></div>\r\n <span>{{ controller.labels['LBL_UPLOADING'] || 'Uploading\u2026' }}</span>\r\n </div>\r\n <ng-container *ngIf=\"!item.isUploading && item.mediaType === 'youtube'\">\r\n <iframe class=\"carousel-iframe\" [src]=\"item.url | trustedUrl\" frameborder=\"0\" allowfullscreen\r\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\">\r\n </iframe>\r\n </ng-container>\r\n <ng-container *ngIf=\"!item.isUploading && item.mediaType === 'image'\">\r\n <img class=\"carousel-image\" [src]=\"item.url\" alt=\"Media\">\r\n </ng-container>\r\n <button id=\"btn-remove-media-{{ config.name }}-{{ i }}\" type=\"button\" class=\"carousel-remove-btn\"\r\n [disabled]=\"item.isUploading\" (click)=\"removeMediaItem(i)\" aria-label=\"Remove\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <button id=\"btn-carousel-next-{{ config.name }}\" type=\"button\" class=\"carousel-nav carousel-nav--next\"\r\n (click)=\"mediaCarouselNext()\" [disabled]=\"mediaCarouselIndex === mediaItems.length - 1\" aria-label=\"Next\">\r\n <mat-icon>chevron_right</mat-icon>\r\n </button>\r\n\r\n <div class=\"carousel-dots\">\r\n <span *ngFor=\"let item of mediaItems; let i = index\" class=\"carousel-dot\"\r\n [class.active]=\"i === mediaCarouselIndex\" (click)=\"mediaGoTo(i)\"></span>\r\n </div>\r\n </div>\r\n\r\n <!-- Thumbnail strip -->\r\n <div class=\"media-thumbnail-strip\">\r\n <div *ngFor=\"let item of mediaThumbnails; let i = index\" class=\"media-thumb\"\r\n [class.active]=\"i === mediaCarouselIndex\" (click)=\"mediaGoTo(i)\">\r\n <div *ngIf=\"item.isUploading\" class=\"thumb-uploading\">\r\n <div class=\"thumb-spinner\"></div>\r\n </div>\r\n <img *ngIf=\"!item.isUploading && item.mediaType === 'youtube' && item.thumbnailUrl\"\r\n [src]=\"item.thumbnailUrl\" class=\"thumb-img\" alt=\"Video thumbnail\">\r\n <div *ngIf=\"!item.isUploading && item.mediaType === 'youtube' && !item.thumbnailUrl\"\r\n class=\"thumb-yt-placeholder\">\r\n <mat-icon>play_circle</mat-icon>\r\n </div>\r\n <img *ngIf=\"!item.isUploading && item.mediaType === 'image' && item.url\" [src]=\"item.url\"\r\n class=\"thumb-img\" alt=\"Image thumbnail\">\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Empty right-side placeholder -->\r\n <div class=\"mu-right-empty\" *ngIf=\"!mediaItems.length\" (click)=\"onMediaMenuDevice()\">\r\n <mat-icon class=\"mu-right-empty-icon\">perm_media</mat-icon>\r\n <p>{{ controller.labels['LBL_ADD_MEDIA'] || 'Add media' }}</p>\r\n </div>\r\n\r\n </div>\r\n <!-- end right panel -->\r\n\r\n </div><!-- end mu-layout -->\r\n </div>\r\n\r\n\r\n <!-- \u2550\u2550 Library Image Picker Modal \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 class=\"media-library-overlay\" *ngIf=\"showLibraryModal\" (click)=\"closeLibraryModal()\"></div>\r\n <div class=\"media-library-modal\" *ngIf=\"showLibraryModal\" role=\"dialog\" aria-modal=\"true\">\r\n <div class=\"library-modal-header\">\r\n <h3 class=\"library-modal-title\">\r\n <mat-icon>photo_library</mat-icon>\r\n {{ controller.labels['LBL_LIBRARY_TITLE'] || 'Media Library' }}\r\n </h3>\r\n <button id=\"btn-close-library-{{ config.name }}\" type=\"button\" class=\"library-close-btn\"\r\n (click)=\"closeLibraryModal()\" aria-label=\"Close\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Loading -->\r\n <div class=\"library-loading\" *ngIf=\"libraryLoading\">\r\n <div class=\"lib-spinner\"></div>\r\n <span>{{ controller.labels['LBL_LOADING'] || 'Loading\u2026' }}</span>\r\n </div>\r\n\r\n <!-- Error -->\r\n <div class=\"library-error\" *ngIf=\"libraryError && !libraryLoading\">\r\n <mat-icon>error_outline</mat-icon>\r\n {{ libraryError }}\r\n </div>\r\n\r\n <!-- Image grid -->\r\n <div class=\"library-grid\" *ngIf=\"!libraryLoading && libraryImages.length\">\r\n <div *ngFor=\"let img of libraryImages; let li = index\" id=\"lib-img-{{ config.name }}-{{ li }}\"\r\n class=\"library-grid-item\" [class.selected]=\"isLibraryItemSelected(img)\" (click)=\"toggleLibraryItem(img)\">\r\n <img [src]=\"getLibraryItemUrl(img)\" class=\"library-grid-img\" alt=\"Library image\">\r\n <div class=\"library-check\" *ngIf=\"isLibraryItemSelected(img)\">\r\n <mat-icon>check_circle</mat-icon>\r\n </div>\r\n <div class=\"library-overlay-hover\"></div>\r\n </div>\r\n </div>\r\n\r\n <!-- Empty library -->\r\n <div class=\"library-empty\" *ngIf=\"!libraryLoading && !libraryError && libraryImages.length === 0\">\r\n <mat-icon>image_not_supported</mat-icon>\r\n <span>{{ controller.labels['LBL_LIBRARY_EMPTY'] || 'No images found in library.' }}</span>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"library-modal-footer\">\r\n <span class=\"library-selected-count\">\r\n {{ librarySelectedIds.size }} {{ controller.labels['LBL_LIBRARY_SELECTED'] || 'selected' }}\r\n </span>\r\n <div class=\"library-footer-actions\">\r\n <lib-button id=\"btn-library-cancel-{{ config.name }}\" [variant]=\"'outline'\" (click)=\"closeLibraryModal()\">\r\n {{ controller.labels['LBL_CANCEL'] || 'Cancel' }}\r\n </lib-button>\r\n <lib-button id=\"btn-library-confirm-{{ config.name }}\" [variant]=\"'primary'\"\r\n [disabled]=\"librarySelectedIds.size === 0\" (click)=\"confirmLibrarySelection()\">\r\n {{ controller.labels['LBL_LIBRARY_ADD_SELECTED'] || 'Add Selected' }}\r\n </lib-button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Location Field \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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=\"isLocation\" class=\"field-wrapper location-field-wrapper\" [formGroup]=\"formGroup\">\r\n\r\n <!-- Field label -->\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 <p class=\"location-subtitle\" *ngIf=\"config.hint\">{{ config.hint }}</p>\r\n\r\n <!-- Three-tab bar -->\r\n <div class=\"location-tabs\">\r\n <lib-button class=\"loc-tab-btn\" [variant]=\"locationActiveTab === 'VENUE' ? 'warning' : 'outline'\"\r\n (click)=\"onLocationTabChange('VENUE')\">\r\n {{ controller.labels['LBL_LOC_VENUE'] || 'Venue' }}\r\n </lib-button>\r\n <lib-button class=\"loc-tab-btn\" [variant]=\"locationActiveTab === 'ONLINE' ? 'warning' : 'outline'\"\r\n (click)=\"onLocationTabChange('ONLINE')\">\r\n {{ controller.labels['LBL_LOC_ONLINE'] || 'Online Event' }}\r\n </lib-button>\r\n <lib-button class=\"loc-tab-btn\" [variant]=\"locationActiveTab === 'TBA' ? 'warning' : 'outline'\"\r\n (click)=\"onLocationTabChange('TBA')\">\r\n {{ controller.labels['LBL_LOC_TBA'] || 'To be Announced' }}\r\n </lib-button>\r\n </div>\r\n\r\n <!-- VENUE TAB -->\r\n <div *ngIf=\"locationActiveTab === 'VENUE'\" class=\"loc-panel loc-venue-panel\">\r\n\r\n <p class=\"loc-section-label\">\r\n {{ controller.labels['LBL_LOC_ADDRESS'] || 'Location address' }}\r\n </p>\r\n\r\n <!-- Added venue rows -->\r\n <div class=\"loc-venue-list\" *ngIf=\"locationVenues.length > 0\">\r\n <div *ngFor=\"let venue of locationVenues; let i = index\" class=\"loc-venue-item\">\r\n <mat-icon class=\"loc-venue-search-icon\">search</mat-icon>\r\n <span class=\"loc-venue-text\">{{ venue.address || venue.name || venue.description }}</span>\r\n <button type=\"button\" class=\"loc-action-btn loc-delete-btn\" (click)=\"removeLocationVenue(i)\">\r\n <mat-icon>delete_outline</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Location count badge -->\r\n <p class=\"loc-count-text\" *ngIf=\"locationVenues.length > 0 && config.locationConfig?.allowMulti\">\r\n {{ locationVenues.length }}&nbsp;{{ controller.labels['LBL_LOC_COUNT_SUFFIX'] || 'Locations Added!' }}\r\n </p>\r\n\r\n <!-- Search input (hide when max reached) -->\r\n <div class=\"loc-search-container\" *ngIf=\"!locationMaxReached\">\r\n <div class=\"loc-search-wrapper\">\r\n <mat-icon class=\"loc-search-icon\">search</mat-icon>\r\n <input class=\"field-input loc-search-input\"\r\n [placeholder]=\"config.locationConfig?.venuePlaceholder || (controller.labels['PH_LOC_VENUE'] || 'Type to search venue...')\"\r\n [value]=\"locationSearchText\" (input)=\"handleLocationSearchInput($event)\" (blur)=\"hideLocationSuggestions()\"\r\n autocomplete=\"off\" [class.is-invalid]=\"errorMessage\">\r\n </div>\r\n <!-- Suggestions dropdown -->\r\n <div class=\"loc-suggestions-panel\" *ngIf=\"locationShowSuggestions && locationSuggestions.length\">\r\n <div *ngFor=\"let sug of locationSuggestions\" class=\"loc-suggestion-item\"\r\n (mousedown)=\"onLocationSuggestionSelect(sug)\">\r\n <mat-icon class=\"loc-suggestion-icon\">place</mat-icon>\r\n <span class=\"loc-suggestion-text\">{{ sug.description }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Add another button -->\r\n <button type=\"button\" class=\"loc-add-btn\"\r\n *ngIf=\"locationVenues.length > 0 && !locationMaxReached && config.locationConfig?.allowMulti\">\r\n <mat-icon>add_circle_outline</mat-icon>\r\n <span>{{ controller.labels['LBL_LOC_ADD_ANOTHER'] || 'Add another Location' }}</span>\r\n </button>\r\n\r\n <!-- Map -->\r\n <div class=\"loc-map-container\" *ngIf=\"config.locationConfig?.showMap !== false\">\r\n <ng-container *ngIf=\"config.locationConfig?.googleMapsApiKey; else simpleEmbed\">\r\n <div [id]=\"'loc-map-' + config.name\" class=\"loc-map-frame\"\r\n [style.height]=\"config.locationConfig?.mapHeight || '300px'\"></div>\r\n </ng-container>\r\n <ng-template #simpleEmbed>\r\n <iframe class=\"loc-map-frame\" [style.height]=\"config.locationConfig?.mapHeight || '300px'\"\r\n [src]=\"getLocationMapEmbedUrl() | trustedUrl\" frameborder=\"0\" allowfullscreen loading=\"lazy\">\r\n </iframe>\r\n </ng-template>\r\n </div>\r\n\r\n <!-- Map hint -->\r\n <p class=\"loc-map-hint\">\r\n {{ controller.labels['LBL_LOC_MAP_HINT'] || 'Type the venue address. A map will appear to assist you.' }}\r\n </p>\r\n </div>\r\n\r\n <!-- ONLINE TAB -->\r\n <div *ngIf=\"locationActiveTab === 'ONLINE'\" class=\"loc-panel loc-online-panel\">\r\n <p class=\"loc-section-label\">\r\n {{ controller.labels['LBL_LOC_ONLINE_URL'] || 'Event URL' }}\r\n </p>\r\n <div class=\"loc-search-wrapper\">\r\n <mat-icon class=\"loc-search-icon\">link</mat-icon>\r\n <input class=\"field-input loc-search-input\" type=\"url\"\r\n [placeholder]=\"config.locationConfig?.onlinePlaceholder || (controller.labels['PH_LOC_ONLINE'] || 'https://zoom.us/j/...')\"\r\n [value]=\"locationOnlineUrl\" (input)=\"onLocationUrlChange($any($event.target).value)\"\r\n [class.is-invalid]=\"errorMessage\">\r\n </div>\r\n </div>\r\n\r\n <!-- TBA TAB -->\r\n <div *ngIf=\"locationActiveTab === 'TBA'\" class=\"loc-panel loc-tba-panel\">\r\n <div class=\"loc-tba-content\">\r\n <mat-icon class=\"loc-tba-icon\">schedule</mat-icon>\r\n <p class=\"loc-tba-text\">\r\n {{ controller.labels['LBL_LOC_TBA_DESC'] || \"This event's location is yet to be announced. Check back later\r\n for updates.\" }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <!-- Hidden real form control -->\r\n <input type=\"hidden\" [formControlName]=\"config.name!\">\r\n\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</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;color:var(--cc-sf-label-color, #202124);font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif);font-weight:var(--cc-sf-label-weight, 500);font-size:var(--cc-sf-label-size, 18px);line-height:var(--cc-sf-label-line-height, 100%);letter-spacing:var(--cc-sf-label-letter-spacing, 0%);margin-bottom:.5rem}.field-label .required{color:var(--cc-sf-label-required-color, #DC2626);margin-left:.125rem}.field-input{width:100%;opacity:var(--cc-sf-input-opacity, 1);gap:var(--cc-sf-input-gap, 10px);padding-top:var(--cc-sf-input-padding-y, .625rem);padding-bottom:var(--cc-sf-input-padding-y, .625rem);padding-left:var(--cc-sf-input-padding-x, 16px);padding-right:var(--cc-sf-input-padding-x, 16px);font-size:var(--cc-sf-input-font-size, .875rem);line-height:var(--cc-sf-input-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, 1px solid #D1D5DB);border-radius:var(--cc-sf-input-radius, 7px);box-shadow:var(--cc-sf-input-shadow, none);transition:var(--cc-sf-input-transition, all .2s ease);font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif)}.field-input::placeholder{font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif);font-weight:var(--cc-sf-placeholder-weight, 400);font-size:var(--cc-sf-placeholder-size, 16px);line-height:var(--cc-sf-placeholder-line-height, 100%);letter-spacing:var(--cc-sf-placeholder-letter-spacing, 0%);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)}input[type=time].time-input{cursor:pointer}input[type=time].time-input::-webkit-calendar-picker-indicator{cursor:pointer;opacity:.7;filter:invert(30%)}input[type=time].time-input::-webkit-calendar-picker-indicator:hover{opacity:1}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)}.char-count-hint{font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif);font-weight:400;font-style:normal;font-size:14px;line-height:100%;letter-spacing:.02em;text-align:right;color:var(--cc-sf-hint-color, #6B7280);margin-top:4px}.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)}.group-section-wrapper.multi-save-active{border:none;box-shadow:none;padding:0;background:transparent}.group-section-wrapper.multi-save-active .group-label{border:none;padding-bottom:0;margin-bottom:0}.group-section-wrapper.multi-save-active .multi-save-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px}.group-section-wrapper.multi-save-active .multi-save-header .btn-add-multi ::ng-deep button{color:var(--ms-btn-add-color, #3B82F6);font-weight:600;font-size:.875rem;padding:8px 12px}.group-section-wrapper.multi-save-active .multi-save-header .btn-add-multi ::ng-deep button:hover{color:var(--ms-btn-add-hover, #2563EB);background-color:var(--cc-sf-btn-add-hover-bg, #EFF6FF)}.group-section-wrapper.multi-save-active .group-instance{background:var(--ms-card-bg, #ffffff);border:1px solid var(--ms-card-border, #E5E7EB);border-radius:var(--ms-card-radius, 10px);box-shadow:var(--ms-card-shadow, 0 1px 4px rgba(0, 0, 0, .05));padding:0;margin-bottom:16px;overflow:hidden}.group-section-wrapper.multi-save-active .group-instance.is-editing{padding:24px}.group-section-wrapper.multi-save-active .group-instance.is-card{cursor:pointer;transition:all .2s ease-in-out}.group-section-wrapper.multi-save-active .group-instance.is-card:hover{box-shadow:var(--ms-card-shadow-hover, 0 8px 24px rgba(0, 0, 0, .08));border-color:var(--cc-sf-input-focus-border, #3B82F6)}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view{display:flex;justify-content:space-between;align-items:center;padding:18px 24px}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-content{flex:1;display:flex;flex-direction:column;gap:4px;overflow:hidden}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-content .card-title{font-size:1rem;font-weight:600;color:var(--ms-title-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-content .card-desc{font-size:.875rem;color:var(--ms-desc-color, #6B7280);line-height:1.4;display:-webkit-box;-webkit-line-clamp:1;line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view.is-expanded .card-content .card-desc{-webkit-line-clamp:unset;line-clamp:unset}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions{display:flex;align-items:center;gap:16px;margin-left:20px}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions mat-icon{font-size:22px;width:22px;height:22px;color:var(--cc-sf-hint-color, #9CA3AF);transition:color .2s}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions mat-icon.icon-delete:hover{color:var(--cc-sf-error-border, #DC2626)}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions mat-icon.icon-edit:hover{color:var(--cc-sf-input-focus-border, #3B82F6)}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions mat-icon.icon-expand{color:var(--cc-sf-input-disabled-border, #E5E7EB)}.group-section-wrapper.multi-save-active .group-footer{display:flex;justify-content:space-between;align-items:center;gap:16px;margin-top:24px;padding-top:20px;border-top:1px solid var(--cc-sf-instance-divider, #E5E7EB)}.group-section-wrapper.multi-save-active .group-footer .footer-actions{display:flex;gap:12px}.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, #FFFAF1);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)}.uploaded-item.uploading{background:var(--cc-sf-uploaded-uploading-bg, #F8FAFC);border-color:var(--cc-sf-uploaded-uploading-border, #CBD5E1);opacity:.85}.upload-spinner{width:20px;height:20px;flex-shrink:0;border:2px solid var(--cc-sf-spinner-track, #E2E8F0);border-top-color:var(--cc-sf-spinner-color, #3B82F6);border-radius:50%;animation:cc-spin .7s linear infinite}@keyframes cc-spin{to{transform:rotate(360deg)}}.rich-text-editor{display:block;width:100%}.uploading-label{color:var(--cc-sf-spinner-color, #3B82F6)!important;font-style:italic}.input-group{position:relative;display:flex;align-items:stretch;width:100%}.input-group .field-input{flex:1;border-radius:var(--cc-sf-input-radius, 8px)}.input-prefix+.input-group .field-input{border-top-left-radius:0;border-bottom-left-radius:0}.input-group .field-input:has(+.input-suffix){border-top-right-radius:0;border-bottom-right-radius:0}.input-group .field-input.has-icon-right{padding-right:3rem}.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)}.date-icon-wrapper{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);display:flex;align-items:center;justify-content:center;pointer-events:auto}.date-icon-wrapper .mat-icon-button{width:32px;height:32px;line-height:32px}.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)}.autocomplete-wrapper{position:relative;display:flex;align-items:center;width:100%}.autocomplete-wrapper .ac-search-icon{position:absolute;left:.75rem;font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem;color:var(--cc-sf-hint-color, #9CA3AF);pointer-events:none;z-index:1;transition:color var(--cc-sf-input-transition, .2s ease)}.autocomplete-wrapper .ac-input{flex:1;padding-left:2.4rem;padding-right:2.4rem}.autocomplete-wrapper .ac-clear-btn{position:absolute;right:.6rem;display:flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:.2rem;border-radius:50%;color:var(--cc-sf-hint-color, #9CA3AF);transition:color .15s ease,background .15s ease}.autocomplete-wrapper .ac-clear-btn mat-icon{font-size:1rem;width:1rem;height:1rem;line-height:1rem}.autocomplete-wrapper .ac-clear-btn:hover{color:var(--cc-sf-label-color, #374151);background:var(--cc-sf-input-disabled-bg, #F3F4F6)}.autocomplete-wrapper .ac-clear-btn:focus{outline:none}.autocomplete-wrapper:focus-within .ac-search-icon{color:var(--cc-sf-input-focus-border, #3B82F6)}.autocomplete-wrapper.is-invalid .ac-input{border-color:var(--cc-sf-error-border, #DC2626);background-color:var(--cc-sf-error-bg, #FEF2F2)}.autocomplete-wrapper.readonly .ac-input{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)}.ac-no-results{font-style:italic;font-size:.8125rem;color:var(--cc-sf-hint-color, #6B7280)}.media-upload-wrapper{padding:0;border:none;background:none}.mu-layout{display:grid;grid-template-columns:1fr 1fr;gap:32px;align-items:flex-start}@media(max-width:768px){.mu-layout{grid-template-columns:1fr}}.mu-left{display:flex;flex-direction:column;gap:20px}.mu-header{display:flex;align-items:flex-start;flex-wrap:wrap;gap:10px}.mu-title{margin:0;font-size:1.35rem;font-weight:700;color:var(--cc-sf-label-color, #111827);line-height:1.3}.mu-badge{display:inline-flex;align-items:center;padding:4px 12px;border-radius:20px;background:var(--cc-sf-label-color, #111827);color:#fff;font-size:.72rem;font-weight:600;white-space:nowrap;flex-shrink:0}.mu-description{margin:0;font-size:.875rem;color:var(--cc-sf-hint-color, #6B7280);line-height:1.6}.mu-features{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.mu-feature-item{display:flex;align-items:center;gap:8px;font-size:.875rem;color:var(--cc-sf-hint-color, #374151)}.mu-feature-item .mu-check{font-size:16px;width:16px;height:16px;line-height:16px;color:var(--cc-sf-chip-selected-bg, #3B82F6);flex-shrink:0}.mu-right{display:flex;flex-direction:column;gap:12px;min-height:260px}.mu-right-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;height:100%;min-height:250px;max-width:400px;border:2px dashed var(--cc-sf-dropzone-border, #CBD5E1);border-radius:var(--mu-carousel-radius, 12px);background:var(--cc-sf-dropzone-bg, #FFFAF1);text-align:center;color:var(--cc-sf-hint-color, #94A3B8);padding:24px;box-shadow:0 2px 10px #0000000d;transition:box-shadow .2s ease}.mu-right-empty:hover{cursor:pointer;box-shadow:0 4px 16px #0000001a}.mu-right-empty .mu-right-empty-icon{font-size:52px;width:52px;height:52px;line-height:52px;opacity:.3}.mu-right-empty p{margin:0;font-size:.85rem}.media-add-container{position:relative;display:inline-block}.media-add-container ::ng-deep button{display:flex;align-items:center;gap:6px}.media-add-container ::ng-deep button .menu-chevron{font-size:18px;width:18px;height:18px;line-height:18px;transition:transform .2s ease}.media-dropdown{position:absolute;top:calc(100% + 6px);left:0;z-index:200;min-width:240px;background:var(--mu-dropdown-bg, #ffffff);border:var(--mu-dropdown-border, 1px solid #E5E7EB);border-radius:12px;box-shadow:var(--mu-dropdown-shadow, 0 8px 32px rgba(0, 0, 0, .12));overflow:hidden;animation:mu-fade-in .15s ease}@keyframes mu-fade-in{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}.media-dropdown-item{display:flex;align-items:center;gap:12px;width:100%;padding:12px 16px;background:none;border:none;border-bottom:1px solid var(--cc-sf-input-disabled-border, #F3F4F6);cursor:pointer;text-align:left;transition:background .15s ease;font-family:var(--cc-sf-font-family, inherit)}.media-dropdown-item:last-child{border-bottom:none}.media-dropdown-item:hover{background:var(--cc-sf-dropzone-hover-bg, #F0F9FF)}.media-drop-icon{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:8px;flex-shrink:0}.media-drop-icon mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.media-drop-icon--video{background:var(--mu-icon-video-bg, #FFF0F0);color:var(--mu-icon-video-color, #EF4444)}.media-drop-icon--device{background:var(--mu-icon-device-bg, #EFF6FF);color:var(--mu-icon-device-color, #3B82F6)}.media-drop-icon--library{background:var(--mu-icon-library-bg, #F0FDF4);color:var(--mu-icon-library-color, #22C55E)}.media-drop-text{display:flex;flex-direction:column;gap:2px;flex:1}.media-drop-label{font-size:.875rem;font-weight:600;color:var(--cc-sf-label-color, #111827)}.media-drop-desc{font-size:.75rem;color:var(--cc-sf-hint-color, #6B7280)}.youtube-input-panel{display:flex;flex-direction:column;gap:8px;padding:16px;background:var(--cc-sf-dropzone-bg, #FFFAF1);border:1px solid var(--cc-sf-input-border, #E5E7EB);border-radius:10px;animation:mu-fade-in .18s ease}.youtube-panel-label{display:flex;align-items:center;gap:6px;font-size:.875rem;font-weight:600;color:var(--cc-sf-label-color, #111827)}.youtube-panel-label mat-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--mu-icon-video-color, #EF4444)}.youtube-input-row{display:flex;gap:8px;align-items:stretch}.youtube-input-row .youtube-url-input{flex:1}.media-menu-backdrop{position:fixed;inset:0;z-index:100}.media-upload-status{display:flex;align-items:center;gap:8px;padding:10px 14px;margin-top:4px;background:var(--cc-sf-error-bg, #FEF2F2);color:var(--cc-sf-error-text-color, #DC2626);border-radius:8px;font-size:.85rem;font-weight:500;animation:mu-fade-in .2s ease}.media-upload-status .status-icon{font-size:18px;width:18px;height:18px;line-height:18px}.media-carousel-section{display:flex;flex-direction:column;gap:12px}.media-carousel-main{position:relative;width:100%;max-width:400px;height:var(--mu-carousel-height, 250px);background:var(--mu-carousel-bg, #0F172A);border-radius:var(--mu-carousel-radius, 12px);overflow:hidden;display:flex;align-items:center;justify-content:center}.carousel-viewer{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.carousel-viewer .carousel-image{width:100%;height:100%;object-fit:cover;border-radius:var(--mu-carousel-radius, 12px)}.carousel-viewer .carousel-iframe{width:100%;height:100%;border-radius:var(--mu-carousel-radius, 12px)}.carousel-viewer .carousel-uploading{display:flex;flex-direction:column;align-items:center;gap:12px;color:#94a3b8;font-size:.85rem}.carousel-viewer .carousel-spinner{width:36px;height:36px;border:3px solid rgba(255,255,255,.15);border-top-color:#3b82f6;border-radius:50%;animation:cc-spin .7s linear infinite}.carousel-nav{position:absolute;top:50%;transform:translateY(-50%);z-index:10;width:40px;height:40px;border-radius:50%;background:#ffffffd9;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 8px #0003;transition:background .2s ease,opacity .2s ease;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.carousel-nav mat-icon{font-size:22px;width:22px;height:22px;line-height:22px;color:#1e293b}.carousel-nav:hover:not(:disabled){background:#fff}.carousel-nav:disabled{opacity:.3;cursor:not-allowed}.carousel-nav--prev{left:12px}.carousel-nav--next{right:12px}.carousel-remove-btn{position:absolute;top:10px;right:10px;z-index:10;width:32px;height:32px;border-radius:50%;background:#0000008c;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .2s ease}.carousel-remove-btn mat-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:#fff}.carousel-remove-btn:hover:not(:disabled){background:#dc2626d9}.carousel-remove-btn:disabled{opacity:.4;cursor:not-allowed}.carousel-dots{position:absolute;bottom:10px;left:50%;transform:translate(-50%);display:flex;gap:6px;z-index:10}.carousel-dot{width:8px;height:8px;border-radius:50%;background:#ffffff73;cursor:pointer;transition:background .2s ease,transform .2s ease}.carousel-dot.active{background:#fff;transform:scale(1.3)}.media-thumbnail-strip{display:flex;flex-wrap:wrap;max-width:400px;gap:8px;overflow-x:auto;padding-bottom:4px}.media-thumbnail-strip::-webkit-scrollbar{height:4px}.media-thumbnail-strip::-webkit-scrollbar-thumb{background:var(--cc-sf-input-disabled-border, #D1D5DB);border-radius:2px}.media-thumb{flex-shrink:0;width:72px;height:52px;border-radius:8px;overflow:hidden;cursor:pointer;border:2px solid transparent;background:var(--mu-thumb-bg, #E2E8F0);display:flex;align-items:center;justify-content:center;transition:border-color .2s ease,transform .15s ease}.media-thumb.active{border-color:var(--mu-thumb-active-border, #3B82F6);transform:scale(1.04)}.media-thumb:hover:not(.active){border-color:var(--cc-sf-input-hover-border, #9CA3AF)}.media-thumb .thumb-img{width:100%;height:100%;object-fit:cover}.media-thumb .thumb-yt-placeholder{display:flex;align-items:center;justify-content:center;width:100%;height:100%;background:#1e293b;color:#ef4444}.media-thumb .thumb-yt-placeholder mat-icon{font-size:28px;width:28px;height:28px;line-height:28px}.media-thumb .thumb-uploading{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.media-thumb .thumb-uploading .thumb-spinner{width:20px;height:20px;border:2px solid #E2E8F0;border-top-color:#3b82f6;border-radius:50%;animation:cc-spin .7s linear infinite}.media-library-overlay{position:fixed;inset:0;background:#00000080;z-index:999;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:mu-fade-in .2s ease}.media-library-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1000;width:min(90vw,780px);max-height:85vh;background:var(--mu-modal-bg, #ffffff);border-radius:var(--mu-modal-radius, 16px);box-shadow:var(--mu-modal-shadow, 0 20px 60px rgba(0, 0, 0, .2));display:flex;flex-direction:column;overflow:hidden;animation:mu-modal-in .22s cubic-bezier(.34,1.56,.64,1)}@keyframes mu-modal-in{0%{opacity:0;transform:translate(-50%,-48%) scale(.95)}to{opacity:1;transform:translate(-50%,-50%) scale(1)}}.library-modal-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px 16px;border-bottom:1px solid var(--cc-sf-input-disabled-border, #F3F4F6);flex-shrink:0}.library-modal-title{display:flex;align-items:center;gap:8px;margin:0;font-size:1.05rem;font-weight:700;color:var(--cc-sf-label-color, #111827)}.library-modal-title mat-icon{font-size:22px;width:22px;height:22px;line-height:22px;color:var(--mu-icon-library-color, #22C55E)}.library-close-btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;background:none;cursor:pointer;border-radius:50%;color:var(--cc-sf-hint-color, #9CA3AF);transition:background .15s ease,color .15s ease}.library-close-btn mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.library-close-btn:hover{background:var(--cc-sf-input-disabled-bg, #F3F4F6);color:var(--cc-sf-label-color, #374151)}.library-loading,.library-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:48px 24px;color:var(--cc-sf-hint-color, #9CA3AF);font-size:.875rem;flex:1}.library-loading mat-icon,.library-empty mat-icon{font-size:40px;width:40px;height:40px;line-height:40px;opacity:.5}.lib-spinner{width:36px;height:36px;border:3px solid #E2E8F0;border-top-color:#3b82f6;border-radius:50%;animation:cc-spin .7s linear infinite}.library-error{display:flex;align-items:center;gap:8px;padding:16px 24px;background:var(--cc-sf-error-bg, #FEF2F2);color:var(--cc-sf-error-text-color, #DC2626);font-size:.875rem;flex-shrink:0}.library-error mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.library-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;padding:24px;overflow-y:auto;flex:1}.library-grid::-webkit-scrollbar{width:6px}.library-grid::-webkit-scrollbar-thumb{background:var(--cc-sf-input-disabled-border, #D1D5DB);border-radius:3px}.library-grid-item{position:relative;aspect-ratio:4/3;border-radius:10px;overflow:hidden;cursor:pointer;border:2.5px solid transparent;transition:border-color .2s ease,transform .15s ease}.library-grid-item.selected{border-color:var(--cc-sf-chip-selected-bg, #3B82F6);transform:scale(.97)}.library-grid-item:hover .library-overlay-hover{opacity:1}.library-grid-img{width:100%;height:100%;object-fit:cover;display:block}.library-overlay-hover{position:absolute;inset:0;background:#3b82f61f;opacity:0;transition:opacity .15s ease}.library-check{position:absolute;top:6px;right:6px;color:var(--cc-sf-chip-selected-bg, #3B82F6);background:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;width:22px;height:22px;box-shadow:0 1px 4px #00000026}.library-check mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}.library-modal-footer{display:flex;align-items:center;justify-content:space-between;padding:16px 24px 20px;border-top:1px solid var(--cc-sf-input-disabled-border, #F3F4F6);flex-shrink:0}.library-selected-count{font-size:.875rem;font-weight:600;color:var(--cc-sf-hint-color, #6B7280)}.library-footer-actions{display:flex;gap:10px}.location-field-wrapper{gap:12px}.location-subtitle{margin:0;font-size:var(--cc-sf-hint-size, .8125rem);color:var(--cc-sf-hint-color, #6B7280);line-height:1.5}.location-tabs{display:flex;gap:12px;margin-bottom:24px}.loc-tab-btn{flex:1}.loc-tab-btn ::ng-deep button{width:100%}.loc-tab-btn ::ng-deep button:not(.cc-btn-warning){background-color:#fff!important;color:#000!important;border:1px solid #E5E7EB}.loc-tab-btn ::ng-deep button:not(.cc-btn-warning):hover{background-color:#f3f4f6!important}.loc-panel{display:flex;flex-direction:column;gap:12px}.loc-section-label{margin:0;font-size:var(--cc-sf-label-size, .9rem);font-weight:600;color:var(--cc-sf-label-color, #111827)}.loc-venue-list{display:flex;flex-direction:column;gap:8px}.loc-venue-item{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--loc-venue-item-bg, #ffffff);border:1px solid var(--loc-venue-item-border, #D1D5DB);border-radius:var(--cc-sf-input-radius, 7px);transition:box-shadow .15s ease,border-color .15s ease}.loc-venue-item:hover{box-shadow:0 2px 8px #0000000f;border-color:var(--cc-sf-input-hover-border, #9CA3AF)}.loc-venue-search-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--cc-sf-hint-color, #9CA3AF);flex-shrink:0}.loc-venue-text{flex:1;font-size:var(--cc-sf-input-font-size, .875rem);color:var(--cc-sf-input-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.loc-action-btn{display:flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:4px;border-radius:50%;transition:background .15s ease,color .15s ease;flex-shrink:0}.loc-action-btn mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}.loc-action-btn.loc-delete-btn{color:var(--loc-delete-color, #E53E3E)}.loc-action-btn.loc-delete-btn:hover{background:var(--cc-sf-error-bg, #FEF2F2)}.loc-action-btn.loc-edit-btn{color:var(--cc-sf-hint-color, #9CA3AF)}.loc-action-btn.loc-edit-btn:hover{color:var(--cc-sf-input-focus-border, #3B82F6);background:var(--cc-sf-dropzone-hover-bg, #EFF6FF)}.loc-count-text{margin:0;font-size:.8125rem;font-weight:600;color:var(--cc-sf-input-focus-border, #3B82F6)}.loc-search-container{position:relative}.loc-search-wrapper{position:relative;display:flex;align-items:center}.loc-search-icon{position:absolute;left:.75rem;font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem;color:var(--cc-sf-hint-color, #9CA3AF);pointer-events:none;z-index:1}.loc-search-input{flex:1;padding-left:2.4rem!important}.loc-suggestions-panel{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:300;background:var(--loc-suggestion-bg, #ffffff);border:1px solid var(--cc-sf-input-border, #D1D5DB);border-radius:var(--cc-sf-input-radius, 8px);box-shadow:0 8px 24px #0000001a;overflow:hidden;animation:mu-fade-in .15s ease;max-height:260px;overflow-y:auto}.loc-suggestion-item{display:flex;align-items:center;gap:10px;padding:10px 14px;cursor:pointer;transition:background .12s ease;font-family:var(--cc-sf-font-family, inherit)}.loc-suggestion-item:hover,.loc-suggestion-item:focus{background:var(--loc-suggestion-hover-bg, #F0F9FF)}.loc-suggestion-item:not(:last-child){border-bottom:1px solid var(--cc-sf-input-disabled-border, #F3F4F6)}.loc-suggestion-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--loc-delete-color, #E53E3E);flex-shrink:0}.loc-suggestion-text{font-size:var(--cc-sf-input-font-size, .875rem);color:var(--cc-sf-label-color, #374151);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.loc-add-btn{display:inline-flex;align-items:center;gap:6px;background:none;border:none;cursor:pointer;color:var(--loc-add-color, #1A56DB);font-family:var(--cc-sf-font-family, inherit);font-size:var(--cc-sf-input-font-size, .875rem);font-weight:600;padding:0;transition:opacity .15s ease}.loc-add-btn mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.loc-add-btn:hover{opacity:.8}.loc-map-container{border-radius:var(--loc-map-radius, 10px);overflow:hidden;border:1px solid var(--cc-sf-input-disabled-border, #E5E7EB);box-shadow:0 2px 10px #0000000f}.loc-map-frame{width:100%;display:block;border:none}.loc-map-hint{margin:0;font-size:.78rem;color:var(--cc-sf-hint-color, #6B7280);text-align:center}.loc-tba-panel{min-height:120px;justify-content:center}.loc-tba-content{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:12px;padding:32px 24px;background:var(--loc-tba-bg, #F9FAFB);border:1px dashed var(--cc-sf-input-disabled-border, #D1D5DB);border-radius:var(--cc-sf-input-radius, 10px)}.loc-tba-icon{font-size:40px;width:40px;height:40px;line-height:40px;color:var(--loc-tba-icon-color, #9CA3AF);opacity:.6}.loc-tba-text{margin:0;font-size:var(--cc-sf-input-font-size, .9rem);color:var(--cc-sf-hint-color, #6B7280);line-height:1.6;max-width:360px}.loc-online-panel .loc-search-wrapper{margin-top:4px}\n"] }]
5371
+ 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 [class.multi-save-active]=\"config.sectionConfig?.multiSaveConfig?.active\">\r\n\r\n <!-- Multi-Save Header with Add button (top-right style) -->\r\n <div class=\"multi-save-header\" *ngIf=\"config.sectionConfig?.multiSaveConfig?.active\">\r\n <h3 class=\"group-label\" *ngIf=\"config.sectionConfig?.label\">{{ config.sectionConfig!.label }}</h3>\r\n <lib-button [variant]=\"'outline'\" [icon]=\"{type: 'material', value: 'add'}\" (click)=\"addGroupInstance()\"\r\n class=\"btn-add-multi\">\r\n {{ addMultiLabel }}\r\n </lib-button>\r\n </div>\r\n\r\n <!-- Standard Header for non-multiSave -->\r\n <h3 class=\"group-label\" *ngIf=\"config.sectionConfig?.label && !config.sectionConfig?.multiSaveConfig?.active\">{{\r\n config.sectionConfig!.label }}</h3>\r\n\r\n <div *ngFor=\"let instance of instanceList; trackBy: trackByInstanceId; let i = index\" class=\"group-instance\"\r\n [class.is-editing]=\"instance.isEditing\"\r\n [class.is-card]=\"config.sectionConfig?.multiSaveConfig?.active && !instance.isEditing\">\r\n\r\n <!-- 1. EDIT MODE / UNSAVED / STANDARD STATE -->\r\n <div [hidden]=\"config.sectionConfig?.multiSaveConfig?.active && !instance.isEditing\">\r\n <!-- Instance header \u2014 show remove only when more than 1 instance -->\r\n <div class=\"group-header\" *ngIf=\"!config.sectionConfig?.multiSaveConfig?.active && 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 <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\r\n <!-- SAVE / CANCEL BUTTONS for MultiSave in Editing phase -->\r\n <div class=\"group-footer\" *ngIf=\"config.sectionConfig?.multiSaveConfig?.active\">\r\n <span class=\"field-error\" *ngIf=\"multiSaveError\">{{ multiSaveError }}</span>\r\n <div class=\"footer-actions\">\r\n <lib-button [variant]=\"'outline'\" (click)=\"cancelGroupInstance(i)\">Cancel</lib-button>\r\n <lib-button [variant]=\"'primary'\" (click)=\"saveGroupInstance(i)\">Save</lib-button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 2. CARD VIEW (Saved State) -->\r\n <ng-container *ngIf=\"config.sectionConfig?.multiSaveConfig?.active && !instance.isEditing\">\r\n <div class=\"card-view\" [class.is-expanded]=\"instance.isExpanded\">\r\n <div class=\"card-content\">\r\n <span class=\"card-title\">{{ instance.fg.get(config.sectionConfig.multiSaveConfig.summaryField || '')?.value\r\n || '\u2014' }}</span>\r\n <!-- <span class=\"card-desc\" *ngIf=\"config.sectionConfig.multiSaveConfig.descriptionField\"\r\n [innerHTML]=\"instance.fg.get(config.sectionConfig.multiSaveConfig.descriptionField)?.value\">\r\n </span> -->\r\n </div>\r\n <div class=\"card-actions\">\r\n <mat-icon class=\"icon-delete\" (click)=\"removeGroupInstance(i, true)\">delete_outline</mat-icon>\r\n <mat-icon class=\"icon-edit\" (click)=\"editGroupInstance(i)\">edit_outline</mat-icon>\r\n <mat-icon class=\"icon-expand\" (click)=\"toggleExpandGroupInstance(i)\">\r\n {{ instance.isExpanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}\r\n </mat-icon>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- Standard Add Button for non-multiSave -->\r\n <lib-button *ngIf=\"!config.sectionConfig?.multiSaveConfig?.active\" [variant]=\"'outline'\"\r\n [icon]=\"{type: 'material', value: 'add'}\" (click)=\"addGroupInstance()\" 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 class=\"char-count-hint\" *ngIf=\"showCharCount\">\r\n {{ remainingCharacters }} characters remaining\r\n </div>\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 <div class=\"input-group\" [class.readonly]=\"config.readonly\">\r\n <input matInput [matDatepicker]=\"datePicker\" class=\"field-input date-input has-icon-right\"\r\n [formControlName]=\"config.name!\" [min]=\"config.dateConfig?.minDate\" [max]=\"config.dateConfig?.maxDate\"\r\n [class.is-invalid]=\"errorMessage\" [placeholder]=\"config.placeholder || ''\" [readonly]=\"config.readonly\"\r\n (click)=\"!config.readonly && datePicker.open()\">\r\n <div class=\"date-icon-wrapper\" *ngIf=\"!config.readonly\">\r\n <mat-datepicker-toggle matSuffix [for]=\"datePicker\"></mat-datepicker-toggle>\r\n </div>\r\n <mat-datepicker #datePicker></mat-datepicker>\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 Time 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=\"isTimeField\" 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 <input type=\"time\" class=\"field-input time-input\" [formControlName]=\"config.name!\"\r\n [class.is-invalid]=\"errorMessage\" [readonly]=\"!!config.readonly\">\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 Autocomplete \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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=\"isAutocomplete\" 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 <!-- Hidden real control (stores the code value) -->\r\n <input type=\"hidden\" [formControlName]=\"config.name!\">\r\n\r\n <div class=\"autocomplete-wrapper\" [class.is-invalid]=\"errorMessage\" [class.readonly]=\"config.readonly\">\r\n <!-- Search icon -->\r\n <mat-icon class=\"ac-search-icon\">search</mat-icon>\r\n\r\n <input class=\"field-input ac-input\" [formControl]=\"autocompleteInputCtrl\" [matAutocomplete]=\"auto\"\r\n [placeholder]=\"config.placeholder || 'Search\u2026'\" [readonly]=\"!!config.readonly\" [class.is-invalid]=\"errorMessage\"\r\n (blur)=\"onAutocompleteClear()\" autocomplete=\"off\">\r\n\r\n <!-- Clear button -->\r\n <button type=\"button\" class=\"ac-clear-btn\" *ngIf=\"autocompleteInputCtrl.value && !config.readonly\"\r\n (click)=\"autocompleteInputCtrl.setValue(''); updateValue(null)\" tabindex=\"-1\" aria-label=\"Clear\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n\r\n <mat-autocomplete #auto=\"matAutocomplete\" [panelWidth]=\"'auto'\">\r\n <mat-option *ngFor=\"let option of filteredOptions\" [value]=\"option.label\"\r\n (onSelectionChange)=\"onAutocompleteSelected(option)\">\r\n {{ option.label }}\r\n </mat-option>\r\n <mat-option *ngIf=\"filteredOptions.length === 0\" disabled class=\"ac-no-results\">\r\n No results found\r\n </mat-option>\r\n </mat-autocomplete>\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 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)\" hidden>\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 Rich Text \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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=\"isRichText\" class=\"field-wrapper component-rich-text\" [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=\"rich-text-container\" [class.is-invalid]=\"errorMessage\">\r\n <quill-editor [formControlName]=\"config.name!\" class=\"rich-text-editor\"\r\n [placeholder]=\"config.richTextConfig?.placeholder || config.placeholder || ''\"\r\n [styles]=\"{height: config.richTextConfig?.height || '200px'}\">\r\n </quill-editor>\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 class=\"char-count-hint\" *ngIf=\"showCharCount\">\r\n {{ remainingCharacters }} characters remaining\r\n </div>\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\" hidden [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\" [class.uploading]=\"f.isUploading\">\r\n\r\n <!-- Uploading spinner (shown while API call is in progress) -->\r\n <ng-container *ngIf=\"f.isUploading; else fileReady\">\r\n <div class=\"upload-spinner\"></div>\r\n <div class=\"file-info\">\r\n <span class=\"file-name\" [title]=\"f.name\">{{ f.name }}</span>\r\n <span class=\"file-size uploading-label\">Uploading...</span>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Normal state once upload is done -->\r\n <ng-template #fileReady>\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 </ng-template>\r\n\r\n <!-- Remove button \u2014 disabled while uploading -->\r\n <lib-button [variant]=\"'danger-outline'\" [disabled]=\"f.isUploading\"\r\n (click)=\"!f.isUploading && 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 <!-- \u2550\u2550 Media Upload (Type 2) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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=\"isMediaUpload\" class=\"field-wrapper media-upload-wrapper\" [formGroup]=\"formGroup\">\r\n\r\n <!-- Hidden file input lives outside *ngIf \u2014 triggered via ViewChild -->\r\n <input #mediaDeviceInput type=\"file\" hidden multiple accept=\"image/*\" (change)=\"onMediaFileSelected($event)\">\r\n\r\n <!-- Two-column layout -->\r\n <div class=\"mu-layout\">\r\n\r\n <!-- \u2500\u2500 LEFT PANEL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"mu-left\">\r\n\r\n <!-- Header: title + max-items badge -->\r\n <div class=\"mu-header\">\r\n <h3 class=\"mu-title\">\r\n {{ config.label }}\r\n <span class=\"required\" *ngIf=\"config.required\">*</span>\r\n </h3>\r\n <span class=\"mu-badge\" *ngIf=\"config.attachmentConfig?.maxFiles\">\r\n {{ controller.labels['LBL_MEDIA_MAX_PREFIX'] || 'Maximum' }}\r\n {{ config.attachmentConfig?.maxFiles }}\r\n {{ controller.labels['LBL_MEDIA_MAX_SUFFIX'] || 'Image & Videos' }}\r\n </span>\r\n </div>\r\n\r\n <!-- Description -->\r\n <p class=\"mu-description\" *ngIf=\"config.attachmentConfig?.description\">\r\n {{ config.attachmentConfig?.description }}\r\n </p>\r\n <p class=\"mu-description\" *ngIf=\"!config.attachmentConfig?.description && controller.labels['LBL_MEDIA_DESC']\">\r\n {{ controller.labels['LBL_MEDIA_DESC'] }}\r\n </p>\r\n\r\n <!-- Feature bullet list -->\r\n <ul class=\"mu-features\"\r\n *ngIf=\"config.attachmentConfig?.features?.length || controller.labels['LBL_MEDIA_FEATURE_1']\">\r\n <ng-container *ngIf=\"config.attachmentConfig?.features?.length\">\r\n <li *ngFor=\"let f of config.attachmentConfig?.features\" class=\"mu-feature-item\">\r\n <mat-icon class=\"mu-check\">check</mat-icon>{{ f }}\r\n </li>\r\n </ng-container>\r\n <ng-container *ngIf=\"!config.attachmentConfig?.features?.length\">\r\n <li *ngIf=\"controller.labels['LBL_MEDIA_FEATURE_1']\" class=\"mu-feature-item\">\r\n <mat-icon class=\"mu-check\">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_1'] }}\r\n </li>\r\n <li *ngIf=\"controller.labels['LBL_MEDIA_FEATURE_2']\" class=\"mu-feature-item\">\r\n <mat-icon class=\"mu-check\">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_2'] }}\r\n </li>\r\n <li *ngIf=\"controller.labels['LBL_MEDIA_FEATURE_3']\" class=\"mu-feature-item\">\r\n <mat-icon class=\"mu-check\">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_3'] }}\r\n </li>\r\n </ng-container>\r\n </ul>\r\n\r\n <!-- Backdrop to close dropdown on outside click -->\r\n <div class=\"media-menu-backdrop\" *ngIf=\"showMediaMenu\"\r\n (click)=\"$event.stopPropagation(); showMediaMenu = false\"></div>\r\n\r\n <!-- Add Media button + dropdown -->\r\n <div class=\"media-add-container\" (click)=\"showMediaMenu = !showMediaMenu\">\r\n <lib-button id=\"btn-add-media-{{ config.name }}\" [variant]=\"'warning'\"\r\n [icon]=\"{type: 'material', value: 'add_photo_alternate'}\">\r\n {{ controller.labels['LBL_ADD_MEDIA'] || 'Add media' }}\r\n <mat-icon class=\"menu-chevron\">add</mat-icon>\r\n </lib-button>\r\n\r\n <div class=\"media-dropdown\" *ngIf=\"showMediaMenu\" role=\"menu\" (click)=\"$event.stopPropagation()\">\r\n <!-- Video -->\r\n <button id=\"btn-media-video-{{ config.name }}\" type=\"button\" class=\"media-dropdown-item\"\r\n (click)=\"onMediaMenuVideo(); showMediaMenu = false\" role=\"menuitem\">\r\n <span class=\"media-drop-icon media-drop-icon--video\"><mat-icon>videocam</mat-icon></span>\r\n <span class=\"media-drop-text\">\r\n <span class=\"media-drop-label\">{{ controller.labels['LBL_MEDIA_VIDEO'] || 'Video' }}</span>\r\n <span class=\"media-drop-desc\">{{ controller.labels['LBL_MEDIA_VIDEO_DESC'] || 'Add YouTube URL'\r\n }}</span>\r\n </span>\r\n </button>\r\n <!-- Device -->\r\n <button id=\"btn-media-device-{{ config.name }}\" type=\"button\" class=\"media-dropdown-item\"\r\n (click)=\"onMediaMenuDevice(); showMediaMenu = false\" role=\"menuitem\">\r\n <span class=\"media-drop-icon media-drop-icon--device\"><mat-icon>upload</mat-icon></span>\r\n <span class=\"media-drop-text\">\r\n <span class=\"media-drop-label\">{{ controller.labels['LBL_MEDIA_DEVICE'] || 'Upload from device'\r\n }}</span>\r\n <span class=\"media-drop-desc\">{{ controller.labels['LBL_MEDIA_DEVICE_DESC'] || 'Select images from your\r\n computer' }}</span>\r\n </span>\r\n </button>\r\n <!-- Library -->\r\n <button id=\"btn-media-library-{{ config.name }}\" type=\"button\" class=\"media-dropdown-item\"\r\n (click)=\"onMediaMenuLibrary(); showMediaMenu = false\" role=\"menuitem\">\r\n <span class=\"media-drop-icon media-drop-icon--library\"><mat-icon>photo_library</mat-icon></span>\r\n <span class=\"media-drop-text\">\r\n <span class=\"media-drop-label\">{{ controller.labels['LBL_MEDIA_LIBRARY'] || 'Upload from library'\r\n }}</span>\r\n <span class=\"media-drop-desc\">{{ controller.labels['LBL_MEDIA_LIBRARY_DESC'] || 'Choose from default\r\n images' }}</span>\r\n </span>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- YouTube URL Input (inline below button) -->\r\n <div class=\"youtube-input-panel\" *ngIf=\"showYoutubeInput\">\r\n <label class=\"youtube-panel-label\">\r\n {{ controller.labels['LBL_YOUTUBE_URL'] || 'Video URL' }}\r\n </label>\r\n <div class=\"youtube-input-row\">\r\n <input id=\"input-youtube-url-{{ config.name }}\" type=\"url\" class=\"field-input youtube-url-input\"\r\n [(ngModel)]=\"youtubeUrlInput\" [ngModelOptions]=\"{standalone: true}\"\r\n [placeholder]=\"controller.labels['PH_YOUTUBE_URL'] || 'Video URL'\" (keyup.enter)=\"addYoutubeMedia()\">\r\n <lib-button id=\"btn-add-youtube-{{ config.name }}\" [variant]=\"'secondary'\" (click)=\"addYoutubeMedia()\">\r\n {{ controller.labels['LBL_ADD'] || 'Add' }}\r\n </lib-button>\r\n </div>\r\n <span class=\"field-error\" *ngIf=\"youtubeUrlError\">{{ youtubeUrlError }}</span>\r\n </div>\r\n\r\n <div class=\"media-upload-status\" *ngIf=\"mediaUploadError\">\r\n <mat-icon class=\"status-icon\">error_outline</mat-icon>\r\n <span>{{ mediaUploadError }}</span>\r\n </div>\r\n\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n </div>\r\n <!-- end left panel -->\r\n\r\n <!-- \u2500\u2500 RIGHT PANEL (carousel) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"mu-right\">\r\n\r\n <!-- Carousel (when items exist) -->\r\n <div class=\"media-carousel-section\" *ngIf=\"mediaItems.length\">\r\n <div class=\"media-carousel-main\">\r\n <button id=\"btn-carousel-prev-{{ config.name }}\" type=\"button\" class=\"carousel-nav carousel-nav--prev\"\r\n (click)=\"mediaCarouselPrev()\" [disabled]=\"mediaCarouselIndex === 0\" aria-label=\"Previous\">\r\n <mat-icon>chevron_left</mat-icon>\r\n </button>\r\n\r\n <div class=\"carousel-viewer\" *ngFor=\"let item of mediaItems; let i = index\"\r\n [hidden]=\"i !== mediaCarouselIndex\">\r\n <div *ngIf=\"item.isUploading\" class=\"carousel-uploading\">\r\n <div class=\"carousel-spinner\"></div>\r\n <span>{{ controller.labels['LBL_UPLOADING'] || 'Uploading\u2026' }}</span>\r\n </div>\r\n <ng-container *ngIf=\"!item.isUploading && item.mediaType === 'youtube'\">\r\n <iframe class=\"carousel-iframe\" [src]=\"item.url | trustedUrl\" frameborder=\"0\" allowfullscreen\r\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\">\r\n </iframe>\r\n </ng-container>\r\n <ng-container *ngIf=\"!item.isUploading && item.mediaType === 'image'\">\r\n <img class=\"carousel-image\" [src]=\"item.url\" alt=\"Media\">\r\n </ng-container>\r\n <button id=\"btn-remove-media-{{ config.name }}-{{ i }}\" type=\"button\" class=\"carousel-remove-btn\"\r\n [disabled]=\"item.isUploading\" (click)=\"removeMediaItem(i)\" aria-label=\"Remove\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <button id=\"btn-carousel-next-{{ config.name }}\" type=\"button\" class=\"carousel-nav carousel-nav--next\"\r\n (click)=\"mediaCarouselNext()\" [disabled]=\"mediaCarouselIndex === mediaItems.length - 1\" aria-label=\"Next\">\r\n <mat-icon>chevron_right</mat-icon>\r\n </button>\r\n\r\n <div class=\"carousel-dots\">\r\n <span *ngFor=\"let item of mediaItems; let i = index\" class=\"carousel-dot\"\r\n [class.active]=\"i === mediaCarouselIndex\" (click)=\"mediaGoTo(i)\"></span>\r\n </div>\r\n </div>\r\n\r\n <!-- Thumbnail strip -->\r\n <div class=\"media-thumbnail-strip\">\r\n <div *ngFor=\"let item of mediaThumbnails; let i = index\" class=\"media-thumb\"\r\n [class.active]=\"i === mediaCarouselIndex\" (click)=\"mediaGoTo(i)\">\r\n <div *ngIf=\"item.isUploading\" class=\"thumb-uploading\">\r\n <div class=\"thumb-spinner\"></div>\r\n </div>\r\n <img *ngIf=\"!item.isUploading && item.mediaType === 'youtube' && item.thumbnailUrl\"\r\n [src]=\"item.thumbnailUrl\" class=\"thumb-img\" alt=\"Video thumbnail\">\r\n <div *ngIf=\"!item.isUploading && item.mediaType === 'youtube' && !item.thumbnailUrl\"\r\n class=\"thumb-yt-placeholder\">\r\n <mat-icon>play_circle</mat-icon>\r\n </div>\r\n <img *ngIf=\"!item.isUploading && item.mediaType === 'image' && item.url\" [src]=\"item.url\"\r\n class=\"thumb-img\" alt=\"Image thumbnail\">\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Empty right-side placeholder -->\r\n <div class=\"mu-right-empty\" *ngIf=\"!mediaItems.length\" (click)=\"onMediaMenuDevice()\">\r\n <mat-icon class=\"mu-right-empty-icon\">perm_media</mat-icon>\r\n <p>{{ controller.labels['LBL_ADD_MEDIA'] || 'Add media' }}</p>\r\n </div>\r\n\r\n </div>\r\n <!-- end right panel -->\r\n\r\n </div><!-- end mu-layout -->\r\n </div>\r\n\r\n\r\n <!-- \u2550\u2550 Library Image Picker Modal \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 class=\"media-library-overlay\" *ngIf=\"showLibraryModal\" (click)=\"closeLibraryModal()\"></div>\r\n <div class=\"media-library-modal\" *ngIf=\"showLibraryModal\" role=\"dialog\" aria-modal=\"true\">\r\n <div class=\"library-modal-header\">\r\n <h3 class=\"library-modal-title\">\r\n <mat-icon>photo_library</mat-icon>\r\n {{ controller.labels['LBL_LIBRARY_TITLE'] || 'Media Library' }}\r\n </h3>\r\n <button id=\"btn-close-library-{{ config.name }}\" type=\"button\" class=\"library-close-btn\"\r\n (click)=\"closeLibraryModal()\" aria-label=\"Close\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Loading -->\r\n <div class=\"library-loading\" *ngIf=\"libraryLoading\">\r\n <div class=\"lib-spinner\"></div>\r\n <span>{{ controller.labels['LBL_LOADING'] || 'Loading\u2026' }}</span>\r\n </div>\r\n\r\n <!-- Error -->\r\n <div class=\"library-error\" *ngIf=\"libraryError && !libraryLoading\">\r\n <mat-icon>error_outline</mat-icon>\r\n {{ libraryError }}\r\n </div>\r\n\r\n <!-- Image grid -->\r\n <div class=\"library-grid\" *ngIf=\"!libraryLoading && libraryImages.length\">\r\n <div *ngFor=\"let img of libraryImages; let li = index\" id=\"lib-img-{{ config.name }}-{{ li }}\"\r\n class=\"library-grid-item\" [class.selected]=\"isLibraryItemSelected(img)\" (click)=\"toggleLibraryItem(img)\">\r\n <img [src]=\"getLibraryItemUrl(img)\" class=\"library-grid-img\" alt=\"Library image\">\r\n <div class=\"library-check\" *ngIf=\"isLibraryItemSelected(img)\">\r\n <mat-icon>check_circle</mat-icon>\r\n </div>\r\n <div class=\"library-overlay-hover\"></div>\r\n </div>\r\n </div>\r\n\r\n <!-- Empty library -->\r\n <div class=\"library-empty\" *ngIf=\"!libraryLoading && !libraryError && libraryImages.length === 0\">\r\n <mat-icon>image_not_supported</mat-icon>\r\n <span>{{ controller.labels['LBL_LIBRARY_EMPTY'] || 'No images found in library.' }}</span>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"library-modal-footer\">\r\n <span class=\"library-selected-count\">\r\n {{ librarySelectedIds.size }} {{ controller.labels['LBL_LIBRARY_SELECTED'] || 'selected' }}\r\n </span>\r\n <div class=\"library-footer-actions\">\r\n <lib-button id=\"btn-library-cancel-{{ config.name }}\" [variant]=\"'outline'\" (click)=\"closeLibraryModal()\">\r\n {{ controller.labels['LBL_CANCEL'] || 'Cancel' }}\r\n </lib-button>\r\n <lib-button id=\"btn-library-confirm-{{ config.name }}\" [variant]=\"'primary'\"\r\n [disabled]=\"librarySelectedIds.size === 0\" (click)=\"confirmLibrarySelection()\">\r\n {{ controller.labels['LBL_LIBRARY_ADD_SELECTED'] || 'Add Selected' }}\r\n </lib-button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Location Field \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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=\"isLocation\" class=\"field-wrapper location-field-wrapper\" [formGroup]=\"formGroup\">\r\n\r\n <!-- Field label -->\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 <p class=\"location-subtitle\" *ngIf=\"config.hint\">{{ config.hint }}</p>\r\n\r\n <!-- Three-tab bar -->\r\n <div class=\"location-tabs\">\r\n <lib-button class=\"loc-tab-btn\" [variant]=\"locationActiveTab === 'VENUE' ? 'warning' : 'outline'\"\r\n (click)=\"onLocationTabChange('VENUE')\">\r\n {{ controller.labels['LBL_LOC_VENUE'] || 'Venue' }}\r\n </lib-button>\r\n <lib-button class=\"loc-tab-btn\" [variant]=\"locationActiveTab === 'ONLINE' ? 'warning' : 'outline'\"\r\n (click)=\"onLocationTabChange('ONLINE')\">\r\n {{ controller.labels['LBL_LOC_ONLINE'] || 'Online Event' }}\r\n </lib-button>\r\n <lib-button class=\"loc-tab-btn\" [variant]=\"locationActiveTab === 'TBA' ? 'warning' : 'outline'\"\r\n (click)=\"onLocationTabChange('TBA')\">\r\n {{ controller.labels['LBL_LOC_TBA'] || 'To be Announced' }}\r\n </lib-button>\r\n </div>\r\n\r\n <!-- VENUE TAB -->\r\n <div *ngIf=\"locationActiveTab === 'VENUE'\" class=\"loc-panel loc-venue-panel\">\r\n\r\n <p class=\"loc-section-label\">\r\n {{ controller.labels['LBL_LOC_ADDRESS'] || 'Location address' }}\r\n </p>\r\n\r\n <!-- Added venue rows -->\r\n <div class=\"loc-venue-list\" *ngIf=\"locationVenues.length > 0\">\r\n <div *ngFor=\"let venue of locationVenues; let i = index\" class=\"loc-venue-item\">\r\n <mat-icon class=\"loc-venue-search-icon\">search</mat-icon>\r\n <span class=\"loc-venue-text\">{{ venue.address || venue.name || venue.description }}</span>\r\n <button type=\"button\" class=\"loc-action-btn loc-delete-btn\" (click)=\"removeLocationVenue(i)\">\r\n <mat-icon>delete_outline</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Location count badge -->\r\n <p class=\"loc-count-text\" *ngIf=\"locationVenues.length > 0 && config.locationConfig?.allowMulti\">\r\n {{ locationVenues.length }}&nbsp;{{ controller.labels['LBL_LOC_COUNT_SUFFIX'] || 'Locations Added!' }}\r\n </p>\r\n\r\n <!-- Search input (hide when max reached) -->\r\n <div class=\"loc-search-container\" *ngIf=\"!locationMaxReached\">\r\n <div class=\"loc-search-wrapper\">\r\n <mat-icon class=\"loc-search-icon\">search</mat-icon>\r\n <input class=\"field-input loc-search-input\"\r\n [placeholder]=\"config.locationConfig?.venuePlaceholder || (controller.labels['PH_LOC_VENUE'] || 'Type to search venue...')\"\r\n [value]=\"locationSearchText\" (input)=\"handleLocationSearchInput($event)\" (blur)=\"hideLocationSuggestions()\"\r\n autocomplete=\"off\" [class.is-invalid]=\"errorMessage\">\r\n </div>\r\n <!-- Suggestions dropdown -->\r\n <div class=\"loc-suggestions-panel\" *ngIf=\"locationShowSuggestions && locationSuggestions.length\">\r\n <div *ngFor=\"let sug of locationSuggestions\" class=\"loc-suggestion-item\"\r\n (mousedown)=\"onLocationSuggestionSelect(sug)\">\r\n <mat-icon class=\"loc-suggestion-icon\">place</mat-icon>\r\n <span class=\"loc-suggestion-text\">{{ sug.description }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Add another button -->\r\n <button type=\"button\" class=\"loc-add-btn\"\r\n *ngIf=\"locationVenues.length > 0 && !locationMaxReached && config.locationConfig?.allowMulti\">\r\n <mat-icon>add_circle_outline</mat-icon>\r\n <span>{{ controller.labels['LBL_LOC_ADD_ANOTHER'] || 'Add another Location' }}</span>\r\n </button>\r\n\r\n <!-- Map -->\r\n <div class=\"loc-map-container\" *ngIf=\"config.locationConfig?.showMap !== false\">\r\n <ng-container *ngIf=\"config.locationConfig?.googleMapsApiKey; else simpleEmbed\">\r\n <div [id]=\"'loc-map-' + config.name\" class=\"loc-map-frame\"\r\n [style.height]=\"config.locationConfig?.mapHeight || '300px'\"></div>\r\n </ng-container>\r\n <ng-template #simpleEmbed>\r\n <iframe class=\"loc-map-frame\" [style.height]=\"config.locationConfig?.mapHeight || '300px'\"\r\n [src]=\"getLocationMapEmbedUrl() | trustedUrl\" frameborder=\"0\" allowfullscreen loading=\"lazy\">\r\n </iframe>\r\n </ng-template>\r\n </div>\r\n\r\n <!-- Map hint -->\r\n <p class=\"loc-map-hint\">\r\n {{ controller.labels['LBL_LOC_MAP_HINT'] || 'Type the venue address. A map will appear to assist you.' }}\r\n </p>\r\n </div>\r\n\r\n <!-- ONLINE TAB -->\r\n <div *ngIf=\"locationActiveTab === 'ONLINE'\" class=\"loc-panel loc-online-panel\">\r\n <p class=\"loc-section-label\">\r\n {{ controller.labels['LBL_LOC_ONLINE_URL'] || 'Event URL' }}\r\n </p>\r\n <div class=\"loc-search-wrapper\">\r\n <mat-icon class=\"loc-search-icon\">link</mat-icon>\r\n <input class=\"field-input loc-search-input\" type=\"url\"\r\n [placeholder]=\"config.locationConfig?.onlinePlaceholder || (controller.labels['PH_LOC_ONLINE'] || 'https://zoom.us/j/...')\"\r\n [value]=\"locationOnlineUrl\" (input)=\"onLocationUrlChange($any($event.target).value)\"\r\n [class.is-invalid]=\"errorMessage\">\r\n </div>\r\n </div>\r\n\r\n <!-- TBA TAB -->\r\n <div *ngIf=\"locationActiveTab === 'TBA'\" class=\"loc-panel loc-tba-panel\">\r\n <div class=\"loc-tba-content\">\r\n <mat-icon class=\"loc-tba-icon\">schedule</mat-icon>\r\n <p class=\"loc-tba-text\">\r\n {{ controller.labels['LBL_LOC_TBA_DESC'] || \"This event's location is yet to be announced. Check back later\r\n for updates.\" }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <!-- Hidden real form control -->\r\n <input type=\"hidden\" [formControlName]=\"config.name!\">\r\n\r\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</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;color:var(--cc-sf-label-color, #202124);font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif);font-weight:var(--cc-sf-label-weight, 500);font-size:var(--cc-sf-label-size, 18px);line-height:var(--cc-sf-label-line-height, 100%);letter-spacing:var(--cc-sf-label-letter-spacing, 0%);margin-bottom:.5rem}.field-label .required{color:var(--cc-sf-label-required-color, #DC2626);margin-left:.125rem}.field-input{width:100%;opacity:var(--cc-sf-input-opacity, 1);gap:var(--cc-sf-input-gap, 10px);padding-top:var(--cc-sf-input-padding-y, .625rem);padding-bottom:var(--cc-sf-input-padding-y, .625rem);padding-left:var(--cc-sf-input-padding-x, 16px);padding-right:var(--cc-sf-input-padding-x, 16px);font-size:var(--cc-sf-input-font-size, .875rem);line-height:var(--cc-sf-input-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, 1px solid #D1D5DB);border-radius:var(--cc-sf-input-radius, 7px);box-shadow:var(--cc-sf-input-shadow, none);transition:var(--cc-sf-input-transition, all .2s ease);font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif)}.field-input::placeholder{font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif);font-weight:var(--cc-sf-placeholder-weight, 400);font-size:var(--cc-sf-placeholder-size, 16px);line-height:var(--cc-sf-placeholder-line-height, 100%);letter-spacing:var(--cc-sf-placeholder-letter-spacing, 0%);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)}input[type=time].time-input{cursor:pointer}input[type=time].time-input::-webkit-calendar-picker-indicator{cursor:pointer;opacity:.7;filter:invert(30%)}input[type=time].time-input::-webkit-calendar-picker-indicator:hover{opacity:1}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)}.char-count-hint{font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif);font-weight:400;font-style:normal;font-size:14px;line-height:100%;letter-spacing:.02em;text-align:right;color:var(--cc-sf-hint-color, #6B7280);margin-top:4px}.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)}.group-section-wrapper.multi-save-active{border:none;box-shadow:none;padding:0;background:transparent}.group-section-wrapper.multi-save-active .group-label{border:none;padding-bottom:0;margin-bottom:0}.group-section-wrapper.multi-save-active .multi-save-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px}.group-section-wrapper.multi-save-active .multi-save-header .btn-add-multi ::ng-deep button{color:var(--ms-btn-add-color, #3B82F6);font-weight:600;font-size:.875rem;padding:8px 12px}.group-section-wrapper.multi-save-active .multi-save-header .btn-add-multi ::ng-deep button:hover{color:var(--ms-btn-add-hover, #2563EB);background-color:var(--cc-sf-btn-add-hover-bg, #EFF6FF)}.group-section-wrapper.multi-save-active .group-instance{background:var(--ms-card-bg, #ffffff);border:1px solid var(--ms-card-border, #E5E7EB);border-radius:var(--ms-card-radius, 10px);box-shadow:var(--ms-card-shadow, 0 1px 4px rgba(0, 0, 0, .05));padding:0;margin-bottom:16px;overflow:hidden}.group-section-wrapper.multi-save-active .group-instance.is-editing{padding:24px}.group-section-wrapper.multi-save-active .group-instance.is-card{cursor:pointer;transition:all .2s ease-in-out}.group-section-wrapper.multi-save-active .group-instance.is-card:hover{box-shadow:var(--ms-card-shadow-hover, 0 8px 24px rgba(0, 0, 0, .08));border-color:var(--cc-sf-input-focus-border, #3B82F6)}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view{display:flex;justify-content:space-between;align-items:center;padding:18px 24px}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-content{flex:1;display:flex;flex-direction:column;gap:4px;overflow:hidden}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-content .card-title{font-size:1rem;font-weight:600;color:var(--ms-title-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-content .card-desc{font-size:.875rem;color:var(--ms-desc-color, #6B7280);line-height:1.4;display:-webkit-box;-webkit-line-clamp:1;line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view.is-expanded .card-content .card-desc{-webkit-line-clamp:unset;line-clamp:unset}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions{display:flex;align-items:center;gap:16px;margin-left:20px}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions mat-icon{font-size:22px;width:22px;height:22px;color:var(--cc-sf-hint-color, #9CA3AF);transition:color .2s}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions mat-icon.icon-delete:hover{color:var(--cc-sf-error-border, #DC2626)}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions mat-icon.icon-edit:hover{color:var(--cc-sf-input-focus-border, #3B82F6)}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-actions mat-icon.icon-expand{color:var(--cc-sf-input-disabled-border, #E5E7EB)}.group-section-wrapper.multi-save-active .group-footer{display:flex;justify-content:space-between;align-items:center;gap:16px;margin-top:24px;padding-top:20px;border-top:1px solid var(--cc-sf-instance-divider, #E5E7EB)}.group-section-wrapper.multi-save-active .group-footer .footer-actions{display:flex;gap:12px}.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, #FFFAF1);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)}.uploaded-item.uploading{background:var(--cc-sf-uploaded-uploading-bg, #F8FAFC);border-color:var(--cc-sf-uploaded-uploading-border, #CBD5E1);opacity:.85}.upload-spinner{width:20px;height:20px;flex-shrink:0;border:2px solid var(--cc-sf-spinner-track, #E2E8F0);border-top-color:var(--cc-sf-spinner-color, #3B82F6);border-radius:50%;animation:cc-spin .7s linear infinite}@keyframes cc-spin{to{transform:rotate(360deg)}}.rich-text-editor{display:block;width:100%}.uploading-label{color:var(--cc-sf-spinner-color, #3B82F6)!important;font-style:italic}.input-group{position:relative;display:flex;align-items:stretch;width:100%}.input-group .field-input{flex:1;border-radius:var(--cc-sf-input-radius, 8px)}.input-prefix+.input-group .field-input{border-top-left-radius:0;border-bottom-left-radius:0}.input-group .field-input:has(+.input-suffix){border-top-right-radius:0;border-bottom-right-radius:0}.input-group .field-input.has-icon-right{padding-right:3rem}.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)}.date-icon-wrapper{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);display:flex;align-items:center;justify-content:center;pointer-events:auto}.date-icon-wrapper .mat-icon-button{width:32px;height:32px;line-height:32px}.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)}.autocomplete-wrapper{position:relative;display:flex;align-items:center;width:100%}.autocomplete-wrapper .ac-search-icon{position:absolute;left:.75rem;font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem;color:var(--cc-sf-hint-color, #9CA3AF);pointer-events:none;z-index:1;transition:color var(--cc-sf-input-transition, .2s ease)}.autocomplete-wrapper .ac-input{flex:1;padding-left:2.4rem;padding-right:2.4rem}.autocomplete-wrapper .ac-clear-btn{position:absolute;right:.6rem;display:flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:.2rem;border-radius:50%;color:var(--cc-sf-hint-color, #9CA3AF);transition:color .15s ease,background .15s ease}.autocomplete-wrapper .ac-clear-btn mat-icon{font-size:1rem;width:1rem;height:1rem;line-height:1rem}.autocomplete-wrapper .ac-clear-btn:hover{color:var(--cc-sf-label-color, #374151);background:var(--cc-sf-input-disabled-bg, #F3F4F6)}.autocomplete-wrapper .ac-clear-btn:focus{outline:none}.autocomplete-wrapper:focus-within .ac-search-icon{color:var(--cc-sf-input-focus-border, #3B82F6)}.autocomplete-wrapper.is-invalid .ac-input{border-color:var(--cc-sf-error-border, #DC2626);background-color:var(--cc-sf-error-bg, #FEF2F2)}.autocomplete-wrapper.readonly .ac-input{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)}.ac-no-results{font-style:italic;font-size:.8125rem;color:var(--cc-sf-hint-color, #6B7280)}.media-upload-wrapper{padding:0;border:none;background:none}.mu-layout{display:grid;grid-template-columns:1fr 1fr;gap:32px;align-items:flex-start}@media(max-width:768px){.mu-layout{grid-template-columns:1fr}}.mu-left{display:flex;flex-direction:column;gap:20px}.mu-header{display:flex;align-items:flex-start;flex-wrap:wrap;gap:10px}.mu-title{margin:0;font-size:1.35rem;font-weight:700;color:var(--cc-sf-label-color, #111827);line-height:1.3}.mu-badge{display:inline-flex;align-items:center;padding:4px 12px;border-radius:20px;background:var(--cc-sf-label-color, #111827);color:#fff;font-size:.72rem;font-weight:600;white-space:nowrap;flex-shrink:0}.mu-description{margin:0;font-size:.875rem;color:var(--cc-sf-hint-color, #6B7280);line-height:1.6}.mu-features{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.mu-feature-item{display:flex;align-items:center;gap:8px;font-size:.875rem;color:var(--cc-sf-hint-color, #374151)}.mu-feature-item .mu-check{font-size:16px;width:16px;height:16px;line-height:16px;color:var(--cc-sf-chip-selected-bg, #3B82F6);flex-shrink:0}.mu-right{display:flex;flex-direction:column;gap:12px;min-height:260px}.mu-right-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;height:100%;min-height:250px;max-width:400px;border:2px dashed var(--cc-sf-dropzone-border, #CBD5E1);border-radius:var(--mu-carousel-radius, 12px);background:var(--cc-sf-dropzone-bg, #FFFAF1);text-align:center;color:var(--cc-sf-hint-color, #94A3B8);padding:24px;box-shadow:0 2px 10px #0000000d;transition:box-shadow .2s ease}.mu-right-empty:hover{cursor:pointer;box-shadow:0 4px 16px #0000001a}.mu-right-empty .mu-right-empty-icon{font-size:52px;width:52px;height:52px;line-height:52px;opacity:.3}.mu-right-empty p{margin:0;font-size:.85rem}.media-add-container{position:relative;display:inline-block}.media-add-container ::ng-deep button{display:flex;align-items:center;gap:6px}.media-add-container ::ng-deep button .menu-chevron{font-size:18px;width:18px;height:18px;line-height:18px;transition:transform .2s ease}.media-dropdown{position:absolute;top:calc(100% + 6px);left:0;z-index:200;min-width:240px;background:var(--mu-dropdown-bg, #ffffff);border:var(--mu-dropdown-border, 1px solid #E5E7EB);border-radius:12px;box-shadow:var(--mu-dropdown-shadow, 0 8px 32px rgba(0, 0, 0, .12));overflow:hidden;animation:mu-fade-in .15s ease}@keyframes mu-fade-in{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}.media-dropdown-item{display:flex;align-items:center;gap:12px;width:100%;padding:12px 16px;background:none;border:none;border-bottom:1px solid var(--cc-sf-input-disabled-border, #F3F4F6);cursor:pointer;text-align:left;transition:background .15s ease;font-family:var(--cc-sf-font-family, inherit)}.media-dropdown-item:last-child{border-bottom:none}.media-dropdown-item:hover{background:var(--cc-sf-dropzone-hover-bg, #F0F9FF)}.media-drop-icon{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:8px;flex-shrink:0}.media-drop-icon mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.media-drop-icon--video{background:var(--mu-icon-video-bg, #FFF0F0);color:var(--mu-icon-video-color, #EF4444)}.media-drop-icon--device{background:var(--mu-icon-device-bg, #EFF6FF);color:var(--mu-icon-device-color, #3B82F6)}.media-drop-icon--library{background:var(--mu-icon-library-bg, #F0FDF4);color:var(--mu-icon-library-color, #22C55E)}.media-drop-text{display:flex;flex-direction:column;gap:2px;flex:1}.media-drop-label{font-size:.875rem;font-weight:600;color:var(--cc-sf-label-color, #111827)}.media-drop-desc{font-size:.75rem;color:var(--cc-sf-hint-color, #6B7280)}.youtube-input-panel{display:flex;flex-direction:column;gap:8px;padding:16px;background:var(--cc-sf-dropzone-bg, #FFFAF1);border:1px solid var(--cc-sf-input-border, #E5E7EB);border-radius:10px;animation:mu-fade-in .18s ease}.youtube-panel-label{display:flex;align-items:center;gap:6px;font-size:.875rem;font-weight:600;color:var(--cc-sf-label-color, #111827)}.youtube-panel-label mat-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--mu-icon-video-color, #EF4444)}.youtube-input-row{display:flex;gap:8px;align-items:stretch}.youtube-input-row .youtube-url-input{flex:1}.media-menu-backdrop{position:fixed;inset:0;z-index:100}.media-upload-status{display:flex;align-items:center;gap:8px;padding:10px 14px;margin-top:4px;background:var(--cc-sf-error-bg, #FEF2F2);color:var(--cc-sf-error-text-color, #DC2626);border-radius:8px;font-size:.85rem;font-weight:500;animation:mu-fade-in .2s ease}.media-upload-status .status-icon{font-size:18px;width:18px;height:18px;line-height:18px}.media-carousel-section{display:flex;flex-direction:column;gap:12px}.media-carousel-main{position:relative;width:100%;max-width:400px;height:var(--mu-carousel-height, 250px);background:var(--mu-carousel-bg, #0F172A);border-radius:var(--mu-carousel-radius, 12px);overflow:hidden;display:flex;align-items:center;justify-content:center}.carousel-viewer{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.carousel-viewer .carousel-image{width:100%;height:100%;object-fit:cover;border-radius:var(--mu-carousel-radius, 12px)}.carousel-viewer .carousel-iframe{width:100%;height:100%;border-radius:var(--mu-carousel-radius, 12px)}.carousel-viewer .carousel-uploading{display:flex;flex-direction:column;align-items:center;gap:12px;color:#94a3b8;font-size:.85rem}.carousel-viewer .carousel-spinner{width:36px;height:36px;border:3px solid rgba(255,255,255,.15);border-top-color:#3b82f6;border-radius:50%;animation:cc-spin .7s linear infinite}.carousel-nav{position:absolute;top:50%;transform:translateY(-50%);z-index:10;width:40px;height:40px;border-radius:50%;background:#ffffffd9;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 8px #0003;transition:background .2s ease,opacity .2s ease;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.carousel-nav mat-icon{font-size:22px;width:22px;height:22px;line-height:22px;color:#1e293b}.carousel-nav:hover:not(:disabled){background:#fff}.carousel-nav:disabled{opacity:.3;cursor:not-allowed}.carousel-nav--prev{left:12px}.carousel-nav--next{right:12px}.carousel-remove-btn{position:absolute;top:10px;right:10px;z-index:10;width:32px;height:32px;border-radius:50%;background:#0000008c;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .2s ease}.carousel-remove-btn mat-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:#fff}.carousel-remove-btn:hover:not(:disabled){background:#dc2626d9}.carousel-remove-btn:disabled{opacity:.4;cursor:not-allowed}.carousel-dots{position:absolute;bottom:10px;left:50%;transform:translate(-50%);display:flex;gap:6px;z-index:10}.carousel-dot{width:8px;height:8px;border-radius:50%;background:#ffffff73;cursor:pointer;transition:background .2s ease,transform .2s ease}.carousel-dot.active{background:#fff;transform:scale(1.3)}.media-thumbnail-strip{display:flex;flex-wrap:wrap;max-width:400px;gap:8px;overflow-x:auto;padding-bottom:4px}.media-thumbnail-strip::-webkit-scrollbar{height:4px}.media-thumbnail-strip::-webkit-scrollbar-thumb{background:var(--cc-sf-input-disabled-border, #D1D5DB);border-radius:2px}.media-thumb{flex-shrink:0;width:72px;height:52px;border-radius:8px;overflow:hidden;cursor:pointer;border:2px solid transparent;background:var(--mu-thumb-bg, #E2E8F0);display:flex;align-items:center;justify-content:center;transition:border-color .2s ease,transform .15s ease}.media-thumb.active{border-color:var(--mu-thumb-active-border, #3B82F6);transform:scale(1.04)}.media-thumb:hover:not(.active){border-color:var(--cc-sf-input-hover-border, #9CA3AF)}.media-thumb .thumb-img{width:100%;height:100%;object-fit:cover}.media-thumb .thumb-yt-placeholder{display:flex;align-items:center;justify-content:center;width:100%;height:100%;background:#1e293b;color:#ef4444}.media-thumb .thumb-yt-placeholder mat-icon{font-size:28px;width:28px;height:28px;line-height:28px}.media-thumb .thumb-uploading{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.media-thumb .thumb-uploading .thumb-spinner{width:20px;height:20px;border:2px solid #E2E8F0;border-top-color:#3b82f6;border-radius:50%;animation:cc-spin .7s linear infinite}.media-library-overlay{position:fixed;inset:0;background:#00000080;z-index:999;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:mu-fade-in .2s ease}.media-library-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1000;width:min(90vw,780px);max-height:85vh;background:var(--mu-modal-bg, #ffffff);border-radius:var(--mu-modal-radius, 16px);box-shadow:var(--mu-modal-shadow, 0 20px 60px rgba(0, 0, 0, .2));display:flex;flex-direction:column;overflow:hidden;animation:mu-modal-in .22s cubic-bezier(.34,1.56,.64,1)}@keyframes mu-modal-in{0%{opacity:0;transform:translate(-50%,-48%) scale(.95)}to{opacity:1;transform:translate(-50%,-50%) scale(1)}}.library-modal-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px 16px;border-bottom:1px solid var(--cc-sf-input-disabled-border, #F3F4F6);flex-shrink:0}.library-modal-title{display:flex;align-items:center;gap:8px;margin:0;font-size:1.05rem;font-weight:700;color:var(--cc-sf-label-color, #111827)}.library-modal-title mat-icon{font-size:22px;width:22px;height:22px;line-height:22px;color:var(--mu-icon-library-color, #22C55E)}.library-close-btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;background:none;cursor:pointer;border-radius:50%;color:var(--cc-sf-hint-color, #9CA3AF);transition:background .15s ease,color .15s ease}.library-close-btn mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.library-close-btn:hover{background:var(--cc-sf-input-disabled-bg, #F3F4F6);color:var(--cc-sf-label-color, #374151)}.library-loading,.library-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:48px 24px;color:var(--cc-sf-hint-color, #9CA3AF);font-size:.875rem;flex:1}.library-loading mat-icon,.library-empty mat-icon{font-size:40px;width:40px;height:40px;line-height:40px;opacity:.5}.lib-spinner{width:36px;height:36px;border:3px solid #E2E8F0;border-top-color:#3b82f6;border-radius:50%;animation:cc-spin .7s linear infinite}.library-error{display:flex;align-items:center;gap:8px;padding:16px 24px;background:var(--cc-sf-error-bg, #FEF2F2);color:var(--cc-sf-error-text-color, #DC2626);font-size:.875rem;flex-shrink:0}.library-error mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.library-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;padding:24px;overflow-y:auto;flex:1}.library-grid::-webkit-scrollbar{width:6px}.library-grid::-webkit-scrollbar-thumb{background:var(--cc-sf-input-disabled-border, #D1D5DB);border-radius:3px}.library-grid-item{position:relative;aspect-ratio:4/3;border-radius:10px;overflow:hidden;cursor:pointer;border:2.5px solid transparent;transition:border-color .2s ease,transform .15s ease}.library-grid-item.selected{border-color:var(--cc-sf-chip-selected-bg, #3B82F6);transform:scale(.97)}.library-grid-item:hover .library-overlay-hover{opacity:1}.library-grid-img{width:100%;height:100%;object-fit:cover;display:block}.library-overlay-hover{position:absolute;inset:0;background:#3b82f61f;opacity:0;transition:opacity .15s ease}.library-check{position:absolute;top:6px;right:6px;color:var(--cc-sf-chip-selected-bg, #3B82F6);background:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;width:22px;height:22px;box-shadow:0 1px 4px #00000026}.library-check mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}.library-modal-footer{display:flex;align-items:center;justify-content:space-between;padding:16px 24px 20px;border-top:1px solid var(--cc-sf-input-disabled-border, #F3F4F6);flex-shrink:0}.library-selected-count{font-size:.875rem;font-weight:600;color:var(--cc-sf-hint-color, #6B7280)}.library-footer-actions{display:flex;gap:10px}.location-field-wrapper{gap:12px}.location-subtitle{margin:0;font-size:var(--cc-sf-hint-size, .8125rem);color:var(--cc-sf-hint-color, #6B7280);line-height:1.5}.location-tabs{display:flex;gap:12px;margin-bottom:24px}.loc-tab-btn{flex:1}.loc-tab-btn ::ng-deep button{width:100%}.loc-tab-btn ::ng-deep button:not(.cc-btn-warning){background-color:#fff!important;color:#000!important;border:1px solid #E5E7EB}.loc-tab-btn ::ng-deep button:not(.cc-btn-warning):hover{background-color:#f3f4f6!important}.loc-panel{display:flex;flex-direction:column;gap:12px}.loc-section-label{margin:0;font-size:var(--cc-sf-label-size, .9rem);font-weight:600;color:var(--cc-sf-label-color, #111827)}.loc-venue-list{display:flex;flex-direction:column;gap:8px}.loc-venue-item{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--loc-venue-item-bg, #ffffff);border:1px solid var(--loc-venue-item-border, #D1D5DB);border-radius:var(--cc-sf-input-radius, 7px);transition:box-shadow .15s ease,border-color .15s ease}.loc-venue-item:hover{box-shadow:0 2px 8px #0000000f;border-color:var(--cc-sf-input-hover-border, #9CA3AF)}.loc-venue-search-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--cc-sf-hint-color, #9CA3AF);flex-shrink:0}.loc-venue-text{flex:1;font-size:var(--cc-sf-input-font-size, .875rem);color:var(--cc-sf-input-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.loc-action-btn{display:flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:4px;border-radius:50%;transition:background .15s ease,color .15s ease;flex-shrink:0}.loc-action-btn mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}.loc-action-btn.loc-delete-btn{color:var(--loc-delete-color, #E53E3E)}.loc-action-btn.loc-delete-btn:hover{background:var(--cc-sf-error-bg, #FEF2F2)}.loc-action-btn.loc-edit-btn{color:var(--cc-sf-hint-color, #9CA3AF)}.loc-action-btn.loc-edit-btn:hover{color:var(--cc-sf-input-focus-border, #3B82F6);background:var(--cc-sf-dropzone-hover-bg, #EFF6FF)}.loc-count-text{margin:0;font-size:.8125rem;font-weight:600;color:var(--cc-sf-input-focus-border, #3B82F6)}.loc-search-container{position:relative}.loc-search-wrapper{position:relative;display:flex;align-items:center}.loc-search-icon{position:absolute;left:.75rem;font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem;color:var(--cc-sf-hint-color, #9CA3AF);pointer-events:none;z-index:1}.loc-search-input{flex:1;padding-left:2.4rem!important}.loc-suggestions-panel{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:300;background:var(--loc-suggestion-bg, #ffffff);border:1px solid var(--cc-sf-input-border, #D1D5DB);border-radius:var(--cc-sf-input-radius, 8px);box-shadow:0 8px 24px #0000001a;overflow:hidden;animation:mu-fade-in .15s ease;max-height:260px;overflow-y:auto}.loc-suggestion-item{display:flex;align-items:center;gap:10px;padding:10px 14px;cursor:pointer;transition:background .12s ease;font-family:var(--cc-sf-font-family, inherit)}.loc-suggestion-item:hover,.loc-suggestion-item:focus{background:var(--loc-suggestion-hover-bg, #F0F9FF)}.loc-suggestion-item:not(:last-child){border-bottom:1px solid var(--cc-sf-input-disabled-border, #F3F4F6)}.loc-suggestion-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--loc-delete-color, #E53E3E);flex-shrink:0}.loc-suggestion-text{font-size:var(--cc-sf-input-font-size, .875rem);color:var(--cc-sf-label-color, #374151);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.loc-add-btn{display:inline-flex;align-items:center;gap:6px;background:none;border:none;cursor:pointer;color:var(--loc-add-color, #1A56DB);font-family:var(--cc-sf-font-family, inherit);font-size:var(--cc-sf-input-font-size, .875rem);font-weight:600;padding:0;transition:opacity .15s ease}.loc-add-btn mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.loc-add-btn:hover{opacity:.8}.loc-map-container{border-radius:var(--loc-map-radius, 10px);overflow:hidden;border:1px solid var(--cc-sf-input-disabled-border, #E5E7EB);box-shadow:0 2px 10px #0000000f}.loc-map-frame{width:100%;display:block;border:none}.loc-map-hint{margin:0;font-size:.78rem;color:var(--cc-sf-hint-color, #6B7280);text-align:center}.loc-tba-panel{min-height:120px;justify-content:center}.loc-tba-content{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:12px;padding:32px 24px;background:var(--loc-tba-bg, #F9FAFB);border:1px dashed var(--cc-sf-input-disabled-border, #D1D5DB);border-radius:var(--cc-sf-input-radius, 10px)}.loc-tba-icon{font-size:40px;width:40px;height:40px;line-height:40px;color:var(--loc-tba-icon-color, #9CA3AF);opacity:.6}.loc-tba-text{margin:0;font-size:var(--cc-sf-input-font-size, .9rem);color:var(--cc-sf-hint-color, #6B7280);line-height:1.6;max-width:360px}.loc-online-panel .loc-search-wrapper{margin-top:4px}\n"] }]
5372
5372
  }], ctorParameters: () => [{ type: i1$2.FormBuilder }, { type: ExpressionService }, { type: i3.HttpClient }], propDecorators: { config: [{
5373
5373
  type: Input
5374
5374
  }], controller: [{
@@ -5473,6 +5473,7 @@ class SmartFormComponent {
5473
5473
  expressionService;
5474
5474
  http;
5475
5475
  snackbarService;
5476
+ router;
5476
5477
  formJson;
5477
5478
  initialValues;
5478
5479
  enableDraftAutoSave = false;
@@ -5485,18 +5486,26 @@ class SmartFormComponent {
5485
5486
  mode = 'CREATE';
5486
5487
  submit = new EventEmitter();
5487
5488
  draftSave = new EventEmitter();
5489
+ /**
5490
+ * Emitted when a button with a custom `type` (not 'cancel', 'draft', or
5491
+ * 'submit') is clicked. Payload contains the button `id` and the current
5492
+ * form data snapshot.
5493
+ */
5494
+ actionClick = new EventEmitter();
5488
5495
  formSchema;
5489
5496
  formGroup;
5490
5497
  fieldList = [];
5491
5498
  isStepper = false;
5492
5499
  currentStep = 0;
5493
5500
  isLoading = false;
5494
- constructor(fb, controller, expressionService, http, snackbarService) {
5501
+ isDraftLoading = false;
5502
+ constructor(fb, controller, expressionService, http, snackbarService, router) {
5495
5503
  this.fb = fb;
5496
5504
  this.controller = controller;
5497
5505
  this.expressionService = expressionService;
5498
5506
  this.http = http;
5499
5507
  this.snackbarService = snackbarService;
5508
+ this.router = router;
5500
5509
  }
5501
5510
  ngOnInit() {
5502
5511
  this.parseFormJson();
@@ -5599,16 +5608,68 @@ class SmartFormComponent {
5599
5608
  }
5600
5609
  if (!this.validate())
5601
5610
  return;
5602
- const formData = this.collectFormData();
5603
- this.isLoading = true;
5604
- if (this.formSchema.submitConfig?.apiUrl) {
5605
- this.submitToApi(formData);
5606
- }
5607
- else {
5608
- this.submit.emit(formData);
5609
- this.isLoading = false;
5611
+ this.submitToApi(this.collectFormData(), 'submit');
5612
+ }
5613
+ // ───────────────────────────────────────────────────────────────────────────
5614
+ // Cancel
5615
+ // ───────────────────────────────────────────────────────────────────────────
5616
+ /**
5617
+ * Universal action handler for any button click.
5618
+ * One handler decides how to process based on action.kind.
5619
+ */
5620
+ handleButtonClick(btn) {
5621
+ const action = btn.action;
5622
+ if (!action)
5623
+ return;
5624
+ switch (action.kind) {
5625
+ case 'submit':
5626
+ this.handleSubmit();
5627
+ break;
5628
+ case 'draft':
5629
+ this.submitToApi(this.collectFormData(), 'draft', btn);
5630
+ break;
5631
+ case 'navigate':
5632
+ this.navigateTo(action.redirectUrl);
5633
+ break;
5634
+ case 'api':
5635
+ this.fireActionApiCall(action);
5636
+ break;
5637
+ case 'emit':
5638
+ this.actionClick.emit({ id: btn.id, formData: this.collectFormData() });
5639
+ break;
5640
+ case 'next':
5641
+ if (this.validate())
5642
+ this.nextStep();
5643
+ break;
5644
+ case 'prev':
5645
+ this.previousStep();
5646
+ break;
5610
5647
  }
5611
5648
  }
5649
+ // ── Action-Redirects & API ──────────────────────────────────────────────────
5650
+ fireActionApiCall(action) {
5651
+ if (!action.apiUrl)
5652
+ return;
5653
+ const headers = this.getHeaders();
5654
+ const method = action.method || 'POST';
5655
+ const body = action.extraPayload || {};
5656
+ this.isLoading = true;
5657
+ this.http.request(method, action.apiUrl, { body, headers }).subscribe({
5658
+ next: (response) => {
5659
+ this.isLoading = false;
5660
+ const msg = action.successMessage || 'Action successful';
5661
+ this.showAlert('success', msg, action.snackbarConfig);
5662
+ if (action.redirectUrl)
5663
+ this.navigateTo(action.redirectUrl);
5664
+ },
5665
+ error: (err) => {
5666
+ this.isLoading = false;
5667
+ const msg = action.errorMessage || 'Action failed';
5668
+ this.showAlert('error', msg, action.snackbarConfig);
5669
+ console.error('API Action error:', err);
5670
+ }
5671
+ });
5672
+ }
5612
5673
  /**
5613
5674
  * Constructs nested payload by checking field properties on form controls.
5614
5675
  */
@@ -5661,47 +5722,63 @@ class SmartFormComponent {
5661
5722
  return merged;
5662
5723
  }
5663
5724
  buildNestedPayload(rawValue, fields) {
5725
+ if (rawValue === null || typeof rawValue !== 'object')
5726
+ return rawValue;
5664
5727
  const payload = {};
5665
- fields.forEach(field => {
5666
- if (field.type === 'ROW' && field.children) {
5667
- Object.assign(payload, this.buildNestedPayload(rawValue, field.children));
5668
- }
5669
- else if (field.type === 'GROUP' && field.sectionConfig?.children) {
5670
- // FormFieldComponent generates unnamed groups using their label inside the root FormGroup.
5671
- const generatedKey = field.sectionConfig.label
5672
- ? field.sectionConfig.label.replace(/(?:^\w|[A-Z]|\b\w)/g, (w, i) => i === 0 ? w.toLowerCase() : w.toUpperCase()).replace(/\s+/g, '')
5673
- : '';
5674
- const groupKey = field.sectionConfig.name || field.name || generatedKey || '__group__';
5675
- const groupRawValue = rawValue[groupKey];
5676
- if (groupRawValue !== undefined) {
5677
- // Identify if it's purely a visual section vs an explicit structural data block
5678
- const isStructural = field.sectionConfig.name || field.name || field.sectionConfig.allowMulti;
5679
- if (!isStructural) {
5680
- // Visual section: Flatten its contents directly onto the target payload layer
5681
- Object.assign(payload, this.buildNestedPayload(groupRawValue, field.sectionConfig.children));
5728
+ const processedKeys = new Set();
5729
+ const processFields = (fieldList) => {
5730
+ fieldList.forEach(field => {
5731
+ if (field.type === 'ROW' && field.children) {
5732
+ processFields(field.children);
5733
+ }
5734
+ else if (field.type === 'GROUP' && field.sectionConfig?.children) {
5735
+ // FormFieldComponent generates unnamed groups using their label inside the root FormGroup.
5736
+ const generatedKey = field.sectionConfig.label
5737
+ ? field.sectionConfig.label.replace(/(?:^\w|[A-Z]|\b\w)/g, (w, i) => i === 0 ? w.toLowerCase() : w.toUpperCase()).replace(/\s+/g, '')
5738
+ : '';
5739
+ const groupKey = field.sectionConfig.name || field.name || generatedKey || '__group__';
5740
+ processedKeys.add(groupKey);
5741
+ const groupRawValue = rawValue[groupKey];
5742
+ if (groupRawValue !== undefined) {
5743
+ // Identify if it's purely a visual section vs an explicit structural data block
5744
+ const isStructural = field.sectionConfig.name || field.name || field.sectionConfig.allowMulti;
5745
+ if (!isStructural) {
5746
+ // Visual section: Flatten its contents directly onto the target payload layer
5747
+ Object.assign(payload, this.buildNestedPayload(groupRawValue, field.sectionConfig.children));
5748
+ }
5749
+ else {
5750
+ // Structural block: process nested mappings / array instances
5751
+ const nestedData = (field.sectionConfig.allowMulti && Array.isArray(groupRawValue))
5752
+ ? groupRawValue.map(item => this.buildNestedPayload(item, field.sectionConfig.children))
5753
+ : this.buildNestedPayload(groupRawValue, field.sectionConfig.children);
5754
+ if (field.payloadPath) {
5755
+ this.setNestedValue(payload, field.payloadPath, nestedData);
5756
+ }
5757
+ else {
5758
+ payload[groupKey] = nestedData;
5759
+ }
5760
+ }
5682
5761
  }
5683
- else {
5684
- // Structural block: process nested mappings / array instances
5685
- const nestedData = (field.sectionConfig.allowMulti && Array.isArray(groupRawValue))
5686
- ? groupRawValue.map(item => this.buildNestedPayload(item, field.sectionConfig.children))
5687
- : this.buildNestedPayload(groupRawValue, field.sectionConfig.children);
5762
+ }
5763
+ else if (field.name) {
5764
+ processedKeys.add(field.name);
5765
+ if (rawValue[field.name] !== undefined) {
5766
+ const val = rawValue[field.name];
5688
5767
  if (field.payloadPath) {
5689
- this.setNestedValue(payload, field.payloadPath, nestedData);
5768
+ this.setNestedValue(payload, field.payloadPath, val);
5690
5769
  }
5691
5770
  else {
5692
- payload[groupKey] = nestedData;
5771
+ payload[field.name] = val;
5693
5772
  }
5694
5773
  }
5695
5774
  }
5696
- }
5697
- else if (field.name && rawValue[field.name] !== undefined) {
5698
- const val = rawValue[field.name];
5699
- if (field.payloadPath) {
5700
- this.setNestedValue(payload, field.payloadPath, val);
5701
- }
5702
- else {
5703
- payload[field.name] = val;
5704
- }
5775
+ });
5776
+ };
5777
+ processFields(fields);
5778
+ // Preserve any hidden tracking keys (e.g. id, uuid, isEdit) that were present in the initial data
5779
+ Object.keys(rawValue).forEach(key => {
5780
+ if (!processedKeys.has(key)) {
5781
+ payload[key] = rawValue[key];
5705
5782
  }
5706
5783
  });
5707
5784
  return payload;
@@ -5776,32 +5853,66 @@ class SmartFormComponent {
5776
5853
  }
5777
5854
  }, 100);
5778
5855
  }
5779
- submitToApi(formData) {
5856
+ submitToApi(formData, actionType = 'submit', btn) {
5780
5857
  const isEdit = this.mode === 'EDIT';
5781
- const config = isEdit ? this.formSchema.editConfig : this.formSchema.submitConfig;
5782
- if (!config || (!config.apiUrl && !config.submitApiUrl)) {
5783
- this.submit.emit(formData);
5784
- this.isLoading = false;
5858
+ const configLine = isEdit ? this.formSchema.editConfig : this.formSchema.submitConfig;
5859
+ const action = btn?.action;
5860
+ // Build the final payload, merging draft extraPayload if applicable
5861
+ let finalPayload = formData;
5862
+ if (actionType === 'draft' && action?.extraPayload) {
5863
+ finalPayload = this.deepMerge(finalPayload, action.extraPayload);
5864
+ }
5865
+ // Fallback for emitted outputs if no API URL is defined centrally
5866
+ if (!configLine || (!configLine.apiUrl && !configLine.submitApiUrl)) {
5867
+ if (actionType === 'draft') {
5868
+ this.draftSave.emit(JSON.stringify(finalPayload));
5869
+ if (action?.redirectUrl)
5870
+ this.navigateTo(action.redirectUrl);
5871
+ }
5872
+ else {
5873
+ this.submit.emit(finalPayload);
5874
+ }
5785
5875
  return;
5786
5876
  }
5787
- const apiUrl = isEdit ? config.submitApiUrl : config.apiUrl;
5788
- const method = isEdit ? (config.submitMethod || 'PATCH') : (config.method || 'POST');
5877
+ const apiUrl = isEdit ? configLine.submitApiUrl : configLine.apiUrl;
5878
+ const method = isEdit ? (configLine.submitMethod || 'PATCH') : (configLine.method || 'POST');
5789
5879
  const headers = this.getHeaders();
5790
- this.http.request(method, apiUrl, { body: formData, headers }).subscribe({
5791
- next: response => {
5792
- this.showAlert('success', config.successMessage || 'Form submitted successfully', config.snackbarConfig);
5793
- this.submit.emit(response);
5794
- this.isLoading = false;
5795
- if (config.redirectUrl) {
5796
- setTimeout(() => {
5797
- window.location.href = config.redirectUrl;
5798
- }, 1500);
5880
+ // Messages and redirect URLs (Prefer button action overrides if present)
5881
+ const successMsg = actionType === 'draft'
5882
+ ? (action?.successMessage || 'Draft saved successfully')
5883
+ : (action?.successMessage || configLine.successMessage || 'Form submitted successfully');
5884
+ const errorMsg = actionType === 'draft'
5885
+ ? (action?.errorMessage || 'Failed to save draft')
5886
+ : (action?.errorMessage || configLine.errorMessage || 'Failed to submit form');
5887
+ const redirectUrl = actionType === 'draft'
5888
+ ? (action?.redirectUrl || configLine.redirectUrl)
5889
+ : (action?.redirectUrl || configLine.redirectUrl);
5890
+ if (actionType === 'draft') {
5891
+ this.isDraftLoading = true;
5892
+ }
5893
+ else {
5894
+ this.isLoading = true;
5895
+ }
5896
+ this.http.request(method, apiUrl, { body: finalPayload, headers }).subscribe({
5897
+ next: (response) => {
5898
+ this.showAlert('success', successMsg, action?.snackbarConfig || configLine.snackbarConfig);
5899
+ if (actionType === 'draft') {
5900
+ this.draftSave.emit(JSON.stringify(response));
5901
+ this.isDraftLoading = false;
5902
+ }
5903
+ else {
5904
+ this.submit.emit(response);
5905
+ this.isLoading = false;
5906
+ }
5907
+ if (redirectUrl) {
5908
+ setTimeout(() => this.navigateTo(redirectUrl), 1500);
5799
5909
  }
5800
5910
  },
5801
- error: err => {
5802
- this.showAlert('error', config.errorMessage || 'Failed to submit form', config.snackbarConfig);
5803
- console.error('Submit error:', err);
5911
+ error: (err) => {
5912
+ this.showAlert('error', errorMsg, action?.snackbarConfig || configLine.snackbarConfig);
5913
+ console.error(actionType === 'draft' ? 'Draft save error:' : 'Submit error:', err);
5804
5914
  this.isLoading = false;
5915
+ this.isDraftLoading = false;
5805
5916
  }
5806
5917
  });
5807
5918
  }
@@ -5844,6 +5955,9 @@ class SmartFormComponent {
5844
5955
  return this.labels[key] || key;
5845
5956
  }
5846
5957
  get submitLabel() {
5958
+ const btn = this.getButtonByActionKind('submit');
5959
+ if (btn?.label)
5960
+ return this.labels[btn.label] || btn.label;
5847
5961
  const key = this.formSchema?.labels?.submitLabel || 'Submit';
5848
5962
  return this.labels[key] || key;
5849
5963
  }
@@ -5851,13 +5965,55 @@ class SmartFormComponent {
5851
5965
  const key = this.formSchema?.labels?.previousLabel || 'Previous';
5852
5966
  return this.labels[key] || key;
5853
5967
  }
5854
- 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 }, { token: SnackbarService }], target: i0.ɵɵFactoryTarget.Component });
5855
- 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", mode: "mode" }, 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\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%;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))}.smart-form-wrapper .form-alert-feedback{margin-bottom:1rem}.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"] }] });
5968
+ // ── Action Bar helpers ─────────────────────────────────────────────────────
5969
+ get actionBarConfig() {
5970
+ return this.formSchema?.actionBarConfig;
5971
+ }
5972
+ /**
5973
+ * Returns buttons for a given alignment, sorted by `order` (stable).
5974
+ */
5975
+ getButtonsForAlignment(alignment) {
5976
+ return (this.actionBarConfig?.buttons || [])
5977
+ .filter(b => !b.hidden && (b.alignment ?? 'right') === alignment)
5978
+ .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
5979
+ }
5980
+ getButtonLabel(btn) {
5981
+ if (btn.action?.kind === 'submit' && this.isStepper && this.canGoNext) {
5982
+ return this.nextLabel;
5983
+ }
5984
+ const key = btn.label || btn.id;
5985
+ return this.labels[key] || key;
5986
+ }
5987
+ isButtonDisabled(btn) {
5988
+ if (btn.disabled)
5989
+ return true;
5990
+ const kind = btn.action?.kind;
5991
+ if (kind === 'submit' || kind === 'draft' || kind === 'api') {
5992
+ return this.isLoading || this.isDraftLoading;
5993
+ }
5994
+ return false;
5995
+ }
5996
+ getButtonByActionKind(kind) {
5997
+ return (this.actionBarConfig?.buttons || []).find(b => b.action?.kind === kind);
5998
+ }
5999
+ // ── Navigation helper ──────────────────────────────────────────────────────
6000
+ navigateTo(url) {
6001
+ if (!url)
6002
+ return;
6003
+ if (url.startsWith('http://') || url.startsWith('https://')) {
6004
+ window.location.href = url;
6005
+ }
6006
+ else {
6007
+ this.router.navigateByUrl(url);
6008
+ }
6009
+ }
6010
+ 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 }, { token: SnackbarService }, { token: i1$1.Router }], target: i0.ɵɵFactoryTarget.Component });
6011
+ 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", mode: "mode" }, outputs: { submit: "submit", draftSave: "draftSave", actionClick: "actionClick" }, providers: [SmartFormController], usesOnChanges: true, ngImport: i0, template: "<div class=\"smart-form-container\">\r\n <div class=\"smart-form-wrapper\" *ngIf=\"formSchema\">\r\n\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 <!-- \u2500\u2500 Form Actions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"form-actions\" *ngIf=\"formSchema.showActions !== false\">\r\n\r\n <!-- LEFT GROUP -->\r\n <div class=\"action-group action-group--left\">\r\n <!-- Optional: Navigational buttons like 'Previous' for stepper can be explicitly added to config, \r\n but we'll keep the logic if they are provided in the buttons array via action.kind === 'prev' -->\r\n <lib-button\r\n *ngFor=\"let btn of getButtonsForAlignment('left')\"\r\n [variant]=\"$any(btn.variant) || 'outline'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </div>\r\n\r\n <!-- RIGHT GROUP -->\r\n <div class=\"action-group action-group--right\">\r\n <lib-button\r\n *ngFor=\"let btn of getButtonsForAlignment('right')\"\r\n [variant]=\"$any(btn.variant) || 'primary'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n</div>", styles: [".smart-form-container{width:100%;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))}.smart-form-wrapper .form-alert-feedback{margin-bottom:1rem}.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:space-between;align-items:center;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 .action-group{display:flex;align-items:center;gap:var(--cc-sf-actions-gap, 12px)}.form-actions .action-group--left{justify-content:flex-start}.form-actions .action-group--right{justify-content:flex-end;margin-left:auto}\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"] }] });
5856
6012
  }
5857
6013
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormComponent, decorators: [{
5858
6014
  type: Component,
5859
- 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\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%;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))}.smart-form-wrapper .form-alert-feedback{margin-bottom:1rem}.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"] }]
5860
- }], ctorParameters: () => [{ type: i1$2.FormBuilder }, { type: SmartFormController }, { type: ExpressionService }, { type: i3.HttpClient }, { type: SnackbarService }], propDecorators: { formJson: [{
6015
+ 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\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 <!-- \u2500\u2500 Form Actions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"form-actions\" *ngIf=\"formSchema.showActions !== false\">\r\n\r\n <!-- LEFT GROUP -->\r\n <div class=\"action-group action-group--left\">\r\n <!-- Optional: Navigational buttons like 'Previous' for stepper can be explicitly added to config, \r\n but we'll keep the logic if they are provided in the buttons array via action.kind === 'prev' -->\r\n <lib-button\r\n *ngFor=\"let btn of getButtonsForAlignment('left')\"\r\n [variant]=\"$any(btn.variant) || 'outline'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </div>\r\n\r\n <!-- RIGHT GROUP -->\r\n <div class=\"action-group action-group--right\">\r\n <lib-button\r\n *ngFor=\"let btn of getButtonsForAlignment('right')\"\r\n [variant]=\"$any(btn.variant) || 'primary'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n</div>", styles: [".smart-form-container{width:100%;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))}.smart-form-wrapper .form-alert-feedback{margin-bottom:1rem}.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:space-between;align-items:center;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 .action-group{display:flex;align-items:center;gap:var(--cc-sf-actions-gap, 12px)}.form-actions .action-group--left{justify-content:flex-start}.form-actions .action-group--right{justify-content:flex-end;margin-left:auto}\n"] }]
6016
+ }], ctorParameters: () => [{ type: i1$2.FormBuilder }, { type: SmartFormController }, { type: ExpressionService }, { type: i3.HttpClient }, { type: SnackbarService }, { type: i1$1.Router }], propDecorators: { formJson: [{
5861
6017
  type: Input
5862
6018
  }], initialValues: [{
5863
6019
  type: Input
@@ -5871,6 +6027,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
5871
6027
  type: Output
5872
6028
  }], draftSave: [{
5873
6029
  type: Output
6030
+ }], actionClick: [{
6031
+ type: Output
5874
6032
  }] } });
5875
6033
 
5876
6034
  class SmartFormModule {
@@ -5950,6 +6108,8 @@ class SideNavComponent {
5950
6108
  showTooltips = true;
5951
6109
  /** Position of the tooltip */
5952
6110
  tooltipPosition = DEFAULT_SIDE_NAV_TOOLTIP_POSITION;
6111
+ /** Optional dictionary for label translation */
6112
+ labels;
5953
6113
  itemClicked = new EventEmitter();
5954
6114
  /** Emits whenever the collapsed state changes (supports two-way binding via [(collapsed)]) */
5955
6115
  collapsedChange = new EventEmitter();
@@ -5959,25 +6119,40 @@ class SideNavComponent {
5959
6119
  }
5960
6120
  filteredSections = [];
5961
6121
  ngOnChanges(changes) {
5962
- if (changes['sections'] || changes['userRoles']) {
5963
- this.filterSections();
6122
+ if (changes['sections'] || changes['userRoles'] || changes['labels']) {
6123
+ this.filterAndMapSections();
5964
6124
  }
5965
6125
  }
5966
- filterSections() {
6126
+ filterAndMapSections() {
5967
6127
  if (!this.sections) {
5968
6128
  this.filteredSections = [];
5969
6129
  return;
5970
6130
  }
5971
- if (!this.userRoles || this.userRoles.length === 0) {
5972
- this.filteredSections = structuredClone(this.sections);
5973
- return;
5974
- }
5975
6131
  const clonedSections = structuredClone(this.sections);
5976
6132
  this.filteredSections = clonedSections.map(section => {
6133
+ // Map section heading if labels are provided
6134
+ if (this.labels && section.heading) {
6135
+ section.heading = this.labels[section.heading] || section.heading;
6136
+ }
6137
+ // Filter and map items
5977
6138
  section.items = section.items.filter(item => {
5978
- if (!item.roles || item.roles.length === 0)
5979
- return true;
5980
- return item.roles.some(role => this.userRoles?.includes(role));
6139
+ if (this.userRoles && this.userRoles.length > 0 && item.roles && item.roles.length > 0) {
6140
+ const hasRole = item.roles.some(role => this.userRoles?.includes(role));
6141
+ if (!hasRole)
6142
+ return false;
6143
+ }
6144
+ return true;
6145
+ }).map((item) => {
6146
+ // Map item labels and tooltips
6147
+ if (this.labels) {
6148
+ const mappedLabel = this.labels[item.labelKey || item.label] || item.labelKey || item.label;
6149
+ return {
6150
+ ...item,
6151
+ label: mappedLabel,
6152
+ tooltip: this.labels[item.tooltip || item.labelKey] || mappedLabel
6153
+ };
6154
+ }
6155
+ return item;
5981
6156
  });
5982
6157
  return section;
5983
6158
  }).filter(section => section.items.length > 0);
@@ -6026,7 +6201,7 @@ class SideNavComponent {
6026
6201
  return styles;
6027
6202
  }
6028
6203
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SideNavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6029
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SideNavComponent, isStandalone: false, selector: "lib-side-nav", inputs: { sections: "sections", userRoles: "userRoles", activeId: "activeId", styleConfig: "styleConfig", collapsed: "collapsed", width: "width", collapsedWidth: "collapsedWidth", showCollapseToggle: "showCollapseToggle", hideIconsWhenExpanded: "hideIconsWhenExpanded", showTooltips: "showTooltips", tooltipPosition: "tooltipPosition" }, outputs: { itemClicked: "itemClicked", collapsedChange: "collapsedChange" }, host: { properties: { "class.cc-side-nav-host-collapsed": "this.isHostCollapsed" } }, usesOnChanges: true, ngImport: i0, template: "<nav class=\"cc-side-nav\" [class.collapsed]=\"collapsed\" role=\"navigation\" [ngStyle]=\"customStyles\">\r\n\r\n <!-- Fallback standalone toggle if no sections exist -->\r\n <div class=\"cc-side-nav-toggle-wrap standalone\" *ngIf=\"filteredSections.length === 0 && showCollapseToggle\">\r\n <button class=\"cc-side-nav-toggle\" (click)=\"toggleCollapse()\" type=\"button\"\r\n [attr.aria-label]=\"collapsed ? 'Expand navigation' : 'Collapse navigation'\">\r\n <span class=\"material-icons cc-side-nav-toggle-icon\">\r\n {{ collapsed ? 'menu_open' : 'menu' }}\r\n </span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"cc-side-nav-section\" *ngFor=\"let section of filteredSections; let i = index\">\r\n\r\n <div class=\"cc-side-nav-heading-row\" *ngIf=\"section.heading || (i === 0 && showCollapseToggle)\">\r\n <h3 class=\"cc-side-nav-heading\" *ngIf=\"section.heading && !collapsed\">\r\n {{ section.heading }}\r\n </h3>\r\n\r\n <div class=\"cc-side-nav-toggle-wrap\" *ngIf=\"i === 0 && showCollapseToggle\"\r\n [class.no-heading]=\"!section.heading || collapsed\">\r\n <button class=\"cc-side-nav-toggle\" (click)=\"toggleCollapse()\" type=\"button\"\r\n [attr.aria-label]=\"collapsed ? 'Expand navigation' : 'Collapse navigation'\">\r\n <span class=\"material-icons cc-side-nav-toggle-icon\">\r\n {{ collapsed ? 'menu_open' : 'menu' }}\r\n </span>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <ul class=\"cc-side-nav-list\">\r\n <li *ngFor=\"let item of section.items\" class=\"cc-side-nav-list-item\">\r\n <a (click)=\"onItemClick(item, $event)\" class=\"cc-side-nav-link\" [class.active]=\"item.id === activeId\"\r\n [class.disabled]=\"item.disabled\" [matTooltip]=\"item.tooltip || item.label\"\r\n [matTooltipDisabled]=\"!showTooltips\" [matTooltipPosition]=\"tooltipPosition\"\r\n [matTooltipClass]=\"'cc-side-nav-tooltip'\" [attr.aria-current]=\"(item.id === activeId) ? 'page' : null\"\r\n [attr.aria-disabled]=\"item.disabled ? true : null\">\r\n <span *ngIf=\"item.icon && (collapsed || !hideIconsWhenExpanded)\" class=\"cc-side-nav-icon\">\r\n\r\n <span *ngIf=\"!item.icon.includes('fa') && !item.icon.includes('bi')\" class=\"material-icons\">\r\n {{ item.icon }}\r\n </span>\r\n\r\n <i *ngIf=\"item.icon.includes('fa') || item.icon.includes('bi')\" [class]=\"item.icon\"></i>\r\n\r\n </span>\r\n <span class=\"cc-side-nav-label\" *ngIf=\"!collapsed\">{{ item.label }}</span>\r\n <span class=\"cc-side-nav-arrow material-icons\"\r\n *ngIf=\"!collapsed && (item.id === activeId) && item.showArrow !== false\">chevron_right</span>\r\n </a>\r\n </li>\r\n </ul>\r\n\r\n </div>\r\n\r\n</nav>", styles: [":host{display:block;height:100%;overflow-y:auto;overflow-x:hidden;width:var(--cc-side-nav-width, 220px);transition:width .25s ease;--cc-side-nav-bg: rgba(249, 200, 14, .0509803922);--cc-side-nav-width: 220px;--cc-side-nav-collapsed-width: 56px;--cc-side-nav-gap-sections: 24px;--cc-side-nav-padding: 16px;--cc-side-nav-font-family: Poppins, sans-serif;--cc-side-nav-heading-font-weight: 500;--cc-side-nav-heading-font-size: 16px;--cc-side-nav-heading-color: #3C4043;--cc-side-nav-item-gap: 4px;--cc-side-nav-item-padding: 12px 16px;--cc-side-nav-item-border-radius: 8px;--cc-side-nav-item-font-weight: 400;--cc-side-nav-item-font-size: 14px;--cc-side-nav-item-color: #5F6368;--cc-side-nav-item-hover-bg: rgba(230, 62, 48, .05);--cc-side-nav-item-hover-color: #3C4043;--cc-side-nav-active-bg: #E63E30;--cc-side-nav-active-color: #FFFFFF;--cc-side-nav-active-font-weight: 500;--cc-side-nav-active-hover-bg: #D4382B;--cc-side-nav-disabled-opacity: .5;--cc-side-nav-tooltip-bg: rgba(0, 0, 0, .8);--cc-side-nav-tooltip-color: #FFFFFF;--cc-side-nav-tooltip-padding: 8px 12px;--cc-side-nav-tooltip-border-radius: 6px;--cc-side-nav-tooltip-font-size: 12px}:host.cc-side-nav-host-collapsed{width:var(--cc-side-nav-collapsed-width, 56px)}:host .cc-side-nav-heading-row{display:flex;align-items:center;justify-content:space-between;min-height:28px}:host .cc-side-nav-toggle{background:transparent;border:none;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;color:var(--cc-side-nav-item-color, #5F6368);transition:color .2s ease,background .2s ease;border-radius:6px}:host .cc-side-nav-toggle:hover{background:#0000000d}:host .cc-side-nav-toggle .material-icons{font-size:20px}:host::-webkit-scrollbar{width:3px}:host::-webkit-scrollbar-track{background:transparent}:host::-webkit-scrollbar-thumb{background:#00000026;border-radius:2px}:host::-webkit-scrollbar-thumb:hover{background:#0000004d}.cc-side-nav{display:flex;flex-direction:column;gap:var(--cc-side-nav-gap-sections, 24px);padding:var(--cc-side-nav-padding, 16px);width:var(--cc-side-nav-width, 220px);min-width:var(--cc-side-nav-width, 220px);box-sizing:border-box;background-color:var(--cc-side-nav-bg, rgba(249, 200, 14, .0509803922));min-height:100%;transition:width .25s ease,min-width .25s ease,padding .25s ease;overflow:hidden}.cc-side-nav.collapsed{width:var(--cc-side-nav-collapsed-width, 56px);min-width:var(--cc-side-nav-collapsed-width, 56px);padding:var(--cc-side-nav-padding, 16px) 8px}.cc-side-nav.collapsed .cc-side-nav-link{justify-content:center;gap:0;padding:10px}.cc-side-nav.collapsed .cc-side-nav-icon{font-size:20px}.cc-side-nav-section{display:flex;flex-direction:column;gap:12px}.cc-side-nav-heading{margin:0;padding:0 16px;font-family:var(--cc-side-nav-font-family, \"Poppins\", sans-serif);font-weight:var(--cc-side-nav-heading-font-weight, 500);font-style:normal;font-size:var(--cc-side-nav-heading-font-size, 16px);text-transform:uppercase;color:var(--cc-side-nav-heading-color, #3C4043);flex:1}.cc-side-nav-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--cc-side-nav-item-gap, 4px)}.cc-side-nav-list-item{margin:0;padding:0}.cc-side-nav-link{display:flex;align-items:center;gap:12px;padding:var(--cc-side-nav-item-padding, 12px 16px);text-decoration:none;cursor:pointer;border-radius:var(--cc-side-nav-item-border-radius, 8px);transition:background-color .2s ease,opacity .2s ease;font-family:var(--cc-side-nav-font-family, \"Poppins\", sans-serif);font-weight:var(--cc-side-nav-item-font-weight, 400);font-style:normal;font-size:var(--cc-side-nav-item-font-size, 14px);color:var(--cc-side-nav-item-color, #5F6368)}.cc-side-nav-link:hover:not(.disabled){background-color:var(--cc-side-nav-item-hover-bg, rgba(230, 62, 48, .05));color:var(--cc-side-nav-item-hover-color, #3C4043)}.cc-side-nav-link.active{background:var(--cc-side-nav-active-bg, #E63E30);opacity:1;border-radius:var(--cc-side-nav-item-border-radius, 8px);font-weight:var(--cc-side-nav-active-font-weight, 500);color:var(--cc-side-nav-active-color, #FFFFFF)}.cc-side-nav-link.active:hover{background-color:var(--cc-side-nav-active-hover-bg, #D4382B)}.cc-side-nav-link.disabled{cursor:not-allowed;opacity:var(--cc-side-nav-disabled-opacity, .5)}.cc-side-nav-icon{display:flex;align-items:center;justify-content:center;font-size:16px}.cc-side-nav-header{display:flex;justify-content:flex-end;padding:8px 16px}.cc-side-nav-label{flex:1;white-space:nowrap;overflow:hidden}.collapsed .cc-side-nav-label{display:none}.cc-side-nav-arrow{margin-left:auto;font-size:18px;opacity:.7}.cc-side-nav-link.active .cc-side-nav-arrow{color:var(--cc-side-nav-active-color, #FFFFFF);opacity:1}::ng-deep .cc-side-nav-tooltip{background-color:var(--cc-side-nav-tooltip-bg, rgba(0, 0, 0, .8))!important;color:var(--cc-side-nav-tooltip-color, #FFFFFF)!important;font-family:var(--cc-side-nav-font-family, \"Poppins\", sans-serif)!important;font-size:var(--cc-side-nav-tooltip-font-size, 12px)!important;padding:var(--cc-side-nav-tooltip-padding, 8px 12px)!important;border-radius:var(--cc-side-nav-tooltip-border-radius, 6px)!important;box-shadow:0 4px 12px #00000026;margin-left:8px!important}.cc-side-nav-toggle-wrap{display:flex;align-items:center;justify-content:flex-end}.cc-side-nav-toggle-wrap.standalone{padding-bottom:8px}.cc-side-nav-toggle-wrap.no-heading{flex:1}.collapsed .cc-side-nav-toggle-wrap{justify-content:center;width:100%}.cc-side-nav-toggle{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:50%;background-color:transparent;cursor:pointer;color:var(--cc-side-nav-item-color, #5F6368);transition:background-color .2s ease,color .2s ease}.cc-side-nav-toggle:hover{background-color:var(--cc-side-nav-item-hover-bg, rgba(0, 0, 0, .06));color:var(--cc-side-nav-heading-color, #3C4043)}.cc-side-nav-toggle-icon{font-size:18px;line-height:1;display:block;transition:transform .25s ease}\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.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i2$3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
6204
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SideNavComponent, isStandalone: false, selector: "lib-side-nav", inputs: { sections: "sections", userRoles: "userRoles", activeId: "activeId", styleConfig: "styleConfig", collapsed: "collapsed", width: "width", collapsedWidth: "collapsedWidth", showCollapseToggle: "showCollapseToggle", hideIconsWhenExpanded: "hideIconsWhenExpanded", showTooltips: "showTooltips", tooltipPosition: "tooltipPosition", labels: "labels" }, outputs: { itemClicked: "itemClicked", collapsedChange: "collapsedChange" }, host: { properties: { "class.cc-side-nav-host-collapsed": "this.isHostCollapsed" } }, usesOnChanges: true, ngImport: i0, template: "<nav class=\"cc-side-nav\" [class.collapsed]=\"collapsed\" role=\"navigation\" [ngStyle]=\"customStyles\">\r\n\r\n <!-- Fallback standalone toggle if no sections exist -->\r\n <div class=\"cc-side-nav-toggle-wrap standalone\" *ngIf=\"filteredSections.length === 0 && showCollapseToggle\">\r\n <button class=\"cc-side-nav-toggle\" (click)=\"toggleCollapse()\" type=\"button\"\r\n [attr.aria-label]=\"collapsed ? 'Expand navigation' : 'Collapse navigation'\">\r\n <span class=\"material-icons cc-side-nav-toggle-icon\">\r\n {{ collapsed ? 'menu_open' : 'menu' }}\r\n </span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"cc-side-nav-section\" *ngFor=\"let section of filteredSections; let i = index\">\r\n\r\n <div class=\"cc-side-nav-heading-row\" *ngIf=\"section.heading || (i === 0 && showCollapseToggle)\">\r\n <h3 class=\"cc-side-nav-heading\" *ngIf=\"section.heading && !collapsed\">\r\n {{ section.heading }}\r\n </h3>\r\n\r\n <div class=\"cc-side-nav-toggle-wrap\" *ngIf=\"i === 0 && showCollapseToggle\"\r\n [class.no-heading]=\"!section.heading || collapsed\">\r\n <button class=\"cc-side-nav-toggle\" (click)=\"toggleCollapse()\" type=\"button\"\r\n [attr.aria-label]=\"collapsed ? 'Expand navigation' : 'Collapse navigation'\">\r\n <span class=\"material-icons cc-side-nav-toggle-icon\">\r\n {{ collapsed ? 'menu_open' : 'menu' }}\r\n </span>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <ul class=\"cc-side-nav-list\">\r\n <li *ngFor=\"let item of section.items\" class=\"cc-side-nav-list-item\">\r\n <a (click)=\"onItemClick(item, $event)\" class=\"cc-side-nav-link\" [class.active]=\"item.id === activeId\"\r\n [class.disabled]=\"item.disabled\" [matTooltip]=\"item.tooltip || item.label\"\r\n [matTooltipDisabled]=\"!showTooltips\" [matTooltipPosition]=\"tooltipPosition\"\r\n [matTooltipClass]=\"'cc-side-nav-tooltip'\" [attr.aria-current]=\"(item.id === activeId) ? 'page' : null\"\r\n [attr.aria-disabled]=\"item.disabled ? true : null\">\r\n <span *ngIf=\"item.icon && (collapsed || !hideIconsWhenExpanded)\" class=\"cc-side-nav-icon\">\r\n\r\n <span *ngIf=\"!item.icon.includes('fa') && !item.icon.includes('bi')\" class=\"material-icons\">\r\n {{ item.icon }}\r\n </span>\r\n\r\n <i *ngIf=\"item.icon.includes('fa') || item.icon.includes('bi')\" [class]=\"item.icon\"></i>\r\n\r\n </span>\r\n <span class=\"cc-side-nav-label\" *ngIf=\"!collapsed\">{{ item.label }}</span>\r\n <span class=\"cc-side-nav-arrow material-icons\"\r\n *ngIf=\"!collapsed && (item.id === activeId) && item.showArrow !== false\">chevron_right</span>\r\n </a>\r\n </li>\r\n </ul>\r\n\r\n </div>\r\n\r\n</nav>", styles: [":host{display:block;height:100%;overflow-y:auto;overflow-x:hidden;width:var(--cc-side-nav-width, 220px);transition:width .25s ease;--cc-side-nav-bg: rgba(249, 200, 14, .0509803922);--cc-side-nav-width: 220px;--cc-side-nav-collapsed-width: 56px;--cc-side-nav-gap-sections: 24px;--cc-side-nav-padding: 16px;--cc-side-nav-font-family: Poppins, sans-serif;--cc-side-nav-heading-font-weight: 500;--cc-side-nav-heading-font-size: 16px;--cc-side-nav-heading-color: #3C4043;--cc-side-nav-item-gap: 4px;--cc-side-nav-item-padding: 12px 16px;--cc-side-nav-item-border-radius: 8px;--cc-side-nav-item-font-weight: 400;--cc-side-nav-item-font-size: 14px;--cc-side-nav-item-color: #5F6368;--cc-side-nav-item-hover-bg: rgba(230, 62, 48, .05);--cc-side-nav-item-hover-color: #3C4043;--cc-side-nav-active-bg: #E63E30;--cc-side-nav-active-color: #FFFFFF;--cc-side-nav-active-font-weight: 500;--cc-side-nav-active-hover-bg: #D4382B;--cc-side-nav-disabled-opacity: .5;--cc-side-nav-tooltip-bg: rgba(0, 0, 0, .8);--cc-side-nav-tooltip-color: #FFFFFF;--cc-side-nav-tooltip-padding: 8px 12px;--cc-side-nav-tooltip-border-radius: 6px;--cc-side-nav-tooltip-font-size: 12px}:host.cc-side-nav-host-collapsed{width:var(--cc-side-nav-collapsed-width, 56px)}:host .cc-side-nav-heading-row{display:flex;align-items:center;justify-content:space-between;min-height:28px}:host .cc-side-nav-toggle{background:transparent;border:none;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;color:var(--cc-side-nav-item-color, #5F6368);transition:color .2s ease,background .2s ease;border-radius:6px}:host .cc-side-nav-toggle:hover{background:#0000000d}:host .cc-side-nav-toggle .material-icons{font-size:20px}:host::-webkit-scrollbar{width:3px}:host::-webkit-scrollbar-track{background:transparent}:host::-webkit-scrollbar-thumb{background:#00000026;border-radius:2px}:host::-webkit-scrollbar-thumb:hover{background:#0000004d}.cc-side-nav{display:flex;flex-direction:column;gap:var(--cc-side-nav-gap-sections, 24px);padding:var(--cc-side-nav-padding, 16px);width:var(--cc-side-nav-width, 220px);min-width:var(--cc-side-nav-width, 220px);box-sizing:border-box;background-color:var(--cc-side-nav-bg, rgba(249, 200, 14, .0509803922));min-height:100%;transition:width .25s ease,min-width .25s ease,padding .25s ease;overflow:hidden}.cc-side-nav.collapsed{width:var(--cc-side-nav-collapsed-width, 56px);min-width:var(--cc-side-nav-collapsed-width, 56px);padding:var(--cc-side-nav-padding, 16px) 8px}.cc-side-nav.collapsed .cc-side-nav-link{justify-content:center;gap:0;padding:10px}.cc-side-nav.collapsed .cc-side-nav-icon{font-size:20px}.cc-side-nav-section{display:flex;flex-direction:column;gap:12px}.cc-side-nav-heading{margin:0;padding:0 16px;font-family:var(--cc-side-nav-font-family, \"Poppins\", sans-serif);font-weight:var(--cc-side-nav-heading-font-weight, 500);font-style:normal;font-size:var(--cc-side-nav-heading-font-size, 16px);text-transform:uppercase;color:var(--cc-side-nav-heading-color, #3C4043);flex:1}.cc-side-nav-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--cc-side-nav-item-gap, 4px)}.cc-side-nav-list-item{margin:0;padding:0}.cc-side-nav-link{display:flex;align-items:center;gap:12px;padding:var(--cc-side-nav-item-padding, 12px 16px);text-decoration:none;cursor:pointer;border-radius:var(--cc-side-nav-item-border-radius, 8px);transition:background-color .2s ease,opacity .2s ease;font-family:var(--cc-side-nav-font-family, \"Poppins\", sans-serif);font-weight:var(--cc-side-nav-item-font-weight, 400);font-style:normal;font-size:var(--cc-side-nav-item-font-size, 14px);color:var(--cc-side-nav-item-color, #5F6368)}.cc-side-nav-link:hover:not(.disabled){background-color:var(--cc-side-nav-item-hover-bg, rgba(230, 62, 48, .05));color:var(--cc-side-nav-item-hover-color, #3C4043)}.cc-side-nav-link.active{background:var(--cc-side-nav-active-bg, #E63E30);opacity:1;border-radius:var(--cc-side-nav-item-border-radius, 8px);font-weight:var(--cc-side-nav-active-font-weight, 500);color:var(--cc-side-nav-active-color, #FFFFFF)}.cc-side-nav-link.active:hover{background-color:var(--cc-side-nav-active-hover-bg, #D4382B)}.cc-side-nav-link.disabled{cursor:not-allowed;opacity:var(--cc-side-nav-disabled-opacity, .5)}.cc-side-nav-icon{display:flex;align-items:center;justify-content:center;font-size:16px}.cc-side-nav-header{display:flex;justify-content:flex-end;padding:8px 16px}.cc-side-nav-label{flex:1;white-space:nowrap;overflow:hidden}.collapsed .cc-side-nav-label{display:none}.cc-side-nav-arrow{margin-left:auto;font-size:18px;opacity:.7}.cc-side-nav-link.active .cc-side-nav-arrow{color:var(--cc-side-nav-active-color, #FFFFFF);opacity:1}::ng-deep .cc-side-nav-tooltip{background-color:var(--cc-side-nav-tooltip-bg, rgba(0, 0, 0, .8))!important;color:var(--cc-side-nav-tooltip-color, #FFFFFF)!important;font-family:var(--cc-side-nav-font-family, \"Poppins\", sans-serif)!important;font-size:var(--cc-side-nav-tooltip-font-size, 12px)!important;padding:var(--cc-side-nav-tooltip-padding, 8px 12px)!important;border-radius:var(--cc-side-nav-tooltip-border-radius, 6px)!important;box-shadow:0 4px 12px #00000026;margin-left:8px!important}.cc-side-nav-toggle-wrap{display:flex;align-items:center;justify-content:flex-end}.cc-side-nav-toggle-wrap.standalone{padding-bottom:8px}.cc-side-nav-toggle-wrap.no-heading{flex:1}.collapsed .cc-side-nav-toggle-wrap{justify-content:center;width:100%}.cc-side-nav-toggle{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:50%;background-color:transparent;cursor:pointer;color:var(--cc-side-nav-item-color, #5F6368);transition:background-color .2s ease,color .2s ease}.cc-side-nav-toggle:hover{background-color:var(--cc-side-nav-item-hover-bg, rgba(0, 0, 0, .06));color:var(--cc-side-nav-heading-color, #3C4043)}.cc-side-nav-toggle-icon{font-size:18px;line-height:1;display:block;transition:transform .25s ease}\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.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i2$3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
6030
6205
  }
6031
6206
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SideNavComponent, decorators: [{
6032
6207
  type: Component,
@@ -6053,6 +6228,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
6053
6228
  type: Input
6054
6229
  }], tooltipPosition: [{
6055
6230
  type: Input
6231
+ }], labels: [{
6232
+ type: Input
6056
6233
  }], itemClicked: [{
6057
6234
  type: Output
6058
6235
  }], collapsedChange: [{
@@ -6984,9 +7161,13 @@ class SmartTableComponent {
6984
7161
  let request$; // Observable
6985
7162
  if (totalCountConfig?.source === 'separate' && totalCountConfig.apiUrl) {
6986
7163
  const headers = this.getHeaders();
7164
+ const method = this.config.apiMethod || 'GET';
7165
+ const body = this.config.apiPayload || {};
7166
+ const dataRequest$ = method === 'POST' ? this.http.post(this.config.apiUrl, body, { params, headers }) : this.http.get(this.config.apiUrl, { params, headers });
7167
+ const countRequest$ = method === 'POST' ? this.http.post(totalCountConfig.apiUrl, body, { params, headers }) : this.http.get(totalCountConfig.apiUrl, { params, headers });
6987
7168
  request$ = forkJoin({
6988
- data: this.http.get(this.config.apiUrl, { params, headers }),
6989
- count: this.http.get(totalCountConfig.apiUrl, { params, headers })
7169
+ data: dataRequest$,
7170
+ count: countRequest$
6990
7171
  }).pipe(map(({ data, count }) => {
6991
7172
  const dataPath = this.config.dataResponsePath !== undefined ? this.config.dataResponsePath : '';
6992
7173
  return {
@@ -6997,7 +7178,10 @@ class SmartTableComponent {
6997
7178
  }
6998
7179
  else {
6999
7180
  const headers = this.getHeaders();
7000
- request$ = this.http.get(this.config.apiUrl, { params, headers }).pipe(map(response => {
7181
+ const method = this.config.apiMethod || 'GET';
7182
+ const body = this.config.apiPayload || {};
7183
+ const baseRequest$ = method === 'POST' ? this.http.post(this.config.apiUrl, body, { params, headers }) : this.http.get(this.config.apiUrl, { params, headers });
7184
+ request$ = baseRequest$.pipe(map(response => {
7001
7185
  const dataPath = this.config.dataResponsePath !== undefined ? this.config.dataResponsePath : '';
7002
7186
  const totalPath = totalCountConfig?.responsePath || '';
7003
7187
  return {
@@ -7241,7 +7425,12 @@ class SmartTableComponent {
7241
7425
  params = params.set(paramName, baseValue);
7242
7426
  }
7243
7427
  }
7244
- this.http.get(filter.apiUrl, { headers, params }).subscribe({
7428
+ const method = filter.apiMethod || 'GET';
7429
+ const body = filter.apiPayload || {};
7430
+ const request$ = method === 'POST'
7431
+ ? this.http.post(filter.apiUrl, body, { headers, params })
7432
+ : this.http.get(filter.apiUrl, { headers, params });
7433
+ request$.subscribe({
7245
7434
  next: (response) => {
7246
7435
  const data = filter.dataPath ? this.getValueByPath(response, filter.dataPath) : response;
7247
7436
  if (!Array.isArray(data)) {