commons-shared-web-ui 0.0.76 → 0.0.78
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.
- package/fesm2022/commons-shared-web-ui.mjs +405 -31
- package/fesm2022/commons-shared-web-ui.mjs.map +1 -1
- package/index.d.ts +100 -3
- package/package.json +1 -1
|
@@ -47,7 +47,7 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
|
|
47
47
|
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
|
48
48
|
import * as i3 from '@angular/common/http';
|
|
49
49
|
import { HttpParams, HttpHeaders } from '@angular/common/http';
|
|
50
|
-
import { Subject, BehaviorSubject, combineLatest, forkJoin, takeUntil as takeUntil$1
|
|
50
|
+
import { Subject, BehaviorSubject, combineLatest, forkJoin, of, takeUntil as takeUntil$1 } from 'rxjs';
|
|
51
51
|
import { takeUntil, map, finalize, debounceTime, distinctUntilChanged, catchError } from 'rxjs/operators';
|
|
52
52
|
import * as i13 from 'ngx-quill';
|
|
53
53
|
import { QuillModule } from 'ngx-quill';
|
|
@@ -1423,6 +1423,7 @@ class FormFieldComponent {
|
|
|
1423
1423
|
hostRef;
|
|
1424
1424
|
overlay;
|
|
1425
1425
|
dateAdapter;
|
|
1426
|
+
cdr;
|
|
1426
1427
|
config;
|
|
1427
1428
|
controller;
|
|
1428
1429
|
/**
|
|
@@ -1616,6 +1617,12 @@ class FormFieldComponent {
|
|
|
1616
1617
|
defaultStateSnapshot = { options: [], currentPage: 0, totalCount: 0, hasMorePages: false };
|
|
1617
1618
|
/** Tracks whether the current localOptionList is filtered by an active server-side search query */
|
|
1618
1619
|
_isServerSearchActive = false;
|
|
1620
|
+
/** True from the moment "Select All" is turned on until the user deselects a single option,
|
|
1621
|
+
* toggles it back off, or starts a new search. While active, each newly-arrived paginated
|
|
1622
|
+
* page (loadMoreDropdownOptions) is auto-merged into the selection — see loadDropdownOptions
|
|
1623
|
+
* — so the selected count keeps pace with what's been scrolled into view (e.g. 10 -> 20 -> 34)
|
|
1624
|
+
* instead of freezing at whatever was loaded the moment "Select All" was clicked. */
|
|
1625
|
+
_selectAllActive = false;
|
|
1619
1626
|
/** Dedicated cache of currently selected option objects */
|
|
1620
1627
|
selectedItems = [];
|
|
1621
1628
|
/** Cache of the latest dependency parameters for server-side autocomplete filtering */
|
|
@@ -1668,13 +1675,14 @@ class FormFieldComponent {
|
|
|
1668
1675
|
'__group__');
|
|
1669
1676
|
}
|
|
1670
1677
|
dropdownScrollStrategy;
|
|
1671
|
-
constructor(fb, expressionService, http, hostRef, overlay, dateAdapter) {
|
|
1678
|
+
constructor(fb, expressionService, http, hostRef, overlay, dateAdapter, cdr) {
|
|
1672
1679
|
this.fb = fb;
|
|
1673
1680
|
this.expressionService = expressionService;
|
|
1674
1681
|
this.http = http;
|
|
1675
1682
|
this.hostRef = hostRef;
|
|
1676
1683
|
this.overlay = overlay;
|
|
1677
1684
|
this.dateAdapter = dateAdapter;
|
|
1685
|
+
this.cdr = cdr;
|
|
1678
1686
|
this.dropdownScrollStrategy = this.overlay.scrollStrategies.reposition();
|
|
1679
1687
|
}
|
|
1680
1688
|
isFieldVisible(field, rowController) {
|
|
@@ -1736,6 +1744,7 @@ class FormFieldComponent {
|
|
|
1736
1744
|
this.registerControl();
|
|
1737
1745
|
this.setupRequiredExpression();
|
|
1738
1746
|
this.mergeSelectedOptionsIntoList();
|
|
1747
|
+
this._resolveLookupIfNeeded();
|
|
1739
1748
|
this._setupSiblingExclusion();
|
|
1740
1749
|
this.setupVisibility();
|
|
1741
1750
|
this.setupMinDateField();
|
|
@@ -2854,6 +2863,11 @@ class FormFieldComponent {
|
|
|
2854
2863
|
}
|
|
2855
2864
|
if (this.isDropdown) {
|
|
2856
2865
|
this.filteredOptionList = [...this.localOptionList];
|
|
2866
|
+
// Sticky "Select All": a newly-arrived paginated page gets auto-selected too, so
|
|
2867
|
+
// selection keeps pace with what's been scrolled/loaded into view.
|
|
2868
|
+
if (isLoadMore) {
|
|
2869
|
+
this.applySelectAllToLoadedOptions();
|
|
2870
|
+
}
|
|
2857
2871
|
}
|
|
2858
2872
|
},
|
|
2859
2873
|
error: err => console.error('Failed to load dropdown options:', err)
|
|
@@ -2926,14 +2940,17 @@ class FormFieldComponent {
|
|
|
2926
2940
|
}
|
|
2927
2941
|
getHeaders(customHeaders) {
|
|
2928
2942
|
let headers = new HttpHeaders();
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2943
|
+
const token = this.controller?.token;
|
|
2944
|
+
if (token) {
|
|
2945
|
+
const headerName = this.controller?.tokenHeader || 'Authorization';
|
|
2946
|
+
headers = headers.set(headerName, token);
|
|
2932
2947
|
}
|
|
2933
2948
|
// Merge custom headers from optionConfig (allows MFE to pass e.g. X-SESSIONID)
|
|
2934
2949
|
if (customHeaders) {
|
|
2935
2950
|
Object.entries(customHeaders).forEach(([key, val]) => {
|
|
2936
|
-
|
|
2951
|
+
if (val !== undefined && val !== null) {
|
|
2952
|
+
headers = headers.set(key, val);
|
|
2953
|
+
}
|
|
2937
2954
|
});
|
|
2938
2955
|
}
|
|
2939
2956
|
return headers;
|
|
@@ -2951,6 +2968,11 @@ class FormFieldComponent {
|
|
|
2951
2968
|
onCheckboxListChange(code, checked) {
|
|
2952
2969
|
if (!this.config.name)
|
|
2953
2970
|
return;
|
|
2971
|
+
if (!checked) {
|
|
2972
|
+
// Manually deselecting one item breaks the "everything loaded is selected" invariant —
|
|
2973
|
+
// stop auto-including future paginated pages until Select All is explicitly re-toggled.
|
|
2974
|
+
this._selectAllActive = false;
|
|
2975
|
+
}
|
|
2954
2976
|
const currentValue = (this.allowMulti
|
|
2955
2977
|
? (this.formGroup.get(this.config.name)?.value)
|
|
2956
2978
|
: this.controller.getFieldValue(this.config.name)) || [];
|
|
@@ -3217,6 +3239,9 @@ class FormFieldComponent {
|
|
|
3217
3239
|
executeDropdownSearch(query) {
|
|
3218
3240
|
const searchConfig = this.config.optionConfig?.searchConfig;
|
|
3219
3241
|
const mode = searchConfig?.mode || 'local';
|
|
3242
|
+
// A new search (server or local) narrows to an unrelated result set — don't carry over a
|
|
3243
|
+
// prior "Select All" and auto-select whatever this new query happens to load/show.
|
|
3244
|
+
this._selectAllActive = false;
|
|
3220
3245
|
if (mode === 'server') {
|
|
3221
3246
|
// A dependent field (optionConfig.dependencies) with no parent value yet has nothing to
|
|
3222
3247
|
// search against — searching now would fire with the parent filter missing entirely and
|
|
@@ -3323,6 +3348,119 @@ class FormFieldComponent {
|
|
|
3323
3348
|
this.addParsedOptions(parsed);
|
|
3324
3349
|
}
|
|
3325
3350
|
}
|
|
3351
|
+
/**
|
|
3352
|
+
* If the field's initial value is a primitive (not an object) and no
|
|
3353
|
+
* selectedOptionsData was provided, uses lookupConfig to fire a targeted
|
|
3354
|
+
* API call to resolve the display label for edit-mode prefilling.
|
|
3355
|
+
*/
|
|
3356
|
+
_resolveLookupIfNeeded() {
|
|
3357
|
+
if (!(this.isDropdown || this.isAutocomplete))
|
|
3358
|
+
return;
|
|
3359
|
+
const fieldName = this.config.name;
|
|
3360
|
+
if (!fieldName)
|
|
3361
|
+
return;
|
|
3362
|
+
const lookup = this.config.optionConfig?.lookupConfig;
|
|
3363
|
+
if (!lookup?.apiUrl)
|
|
3364
|
+
return;
|
|
3365
|
+
// Skip if selectedOptionsData already provided a pre-seeded object
|
|
3366
|
+
if (this.controller.selectedOptionsData?.[fieldName])
|
|
3367
|
+
return;
|
|
3368
|
+
// Skip only if selectedItems were already resolved with real option objects (object initialValue)
|
|
3369
|
+
const hasFullyResolvedItems = this.selectedItems.length > 0 &&
|
|
3370
|
+
this.selectedItems.every(item => item.value !== undefined && item.label !== String(item.code));
|
|
3371
|
+
if (hasFullyResolvedItems)
|
|
3372
|
+
return;
|
|
3373
|
+
const rawValue = this.controller.getFieldValue(fieldName);
|
|
3374
|
+
if (rawValue === null || rawValue === undefined || rawValue === '')
|
|
3375
|
+
return;
|
|
3376
|
+
// Determine if the value is primitive(s) that need resolution
|
|
3377
|
+
const isArray = Array.isArray(rawValue);
|
|
3378
|
+
const isPrimitive = !isArray && (typeof rawValue !== 'object');
|
|
3379
|
+
const isArrayOfPrimitives = isArray && rawValue.length > 0 &&
|
|
3380
|
+
rawValue.every((v) => typeof v !== 'object');
|
|
3381
|
+
if (!isPrimitive && !isArrayOfPrimitives)
|
|
3382
|
+
return;
|
|
3383
|
+
// Build the HTTP request
|
|
3384
|
+
const method = (lookup.apiMethod || 'GET').toUpperCase();
|
|
3385
|
+
const paramName = lookup.paramName || (isArrayOfPrimitives ? 'ids' : 'id');
|
|
3386
|
+
const lookupDataPath = lookup.dataPath ?? this.config.optionConfig?.dataPath;
|
|
3387
|
+
const headers = this.getHeaders(lookup.headers);
|
|
3388
|
+
let request$;
|
|
3389
|
+
if (!isArrayOfPrimitives && lookup.apiUrl.includes('{value}')) {
|
|
3390
|
+
// Single GET with URL placeholder: /api/users/{value}
|
|
3391
|
+
const url = lookup.apiUrl.replace('{value}', encodeURIComponent(String(rawValue)));
|
|
3392
|
+
request$ = method === 'POST'
|
|
3393
|
+
? this.http.post(url, lookup.apiPayload || {}, { headers })
|
|
3394
|
+
: this.http.get(url, { headers });
|
|
3395
|
+
}
|
|
3396
|
+
else if (method === 'POST') {
|
|
3397
|
+
const isArrayPayload = Array.isArray(lookup.apiPayload);
|
|
3398
|
+
if (isArrayPayload && lookup.paramName) {
|
|
3399
|
+
const paramName = lookup.paramName;
|
|
3400
|
+
// Query param POST: append paramName query params to URL and send array body (apiPayload)
|
|
3401
|
+
const separator = lookup.apiUrl.includes('?') ? '&' : '?';
|
|
3402
|
+
let queryParams = '';
|
|
3403
|
+
if (isArrayOfPrimitives) {
|
|
3404
|
+
queryParams = rawValue
|
|
3405
|
+
.map(v => `${encodeURIComponent(paramName)}=${encodeURIComponent(String(v))}`)
|
|
3406
|
+
.join('&');
|
|
3407
|
+
}
|
|
3408
|
+
else {
|
|
3409
|
+
queryParams = `${encodeURIComponent(paramName)}=${encodeURIComponent(String(rawValue))}`;
|
|
3410
|
+
}
|
|
3411
|
+
const url = `${lookup.apiUrl}${separator}${queryParams}`;
|
|
3412
|
+
request$ = this.http.post(url, lookup.apiPayload, { headers });
|
|
3413
|
+
}
|
|
3414
|
+
else if (!lookup.paramName) {
|
|
3415
|
+
// Direct body array POST (e.g. Center Types / Beneficiary Types): send raw IDs directly as POST body
|
|
3416
|
+
const body = isArrayOfPrimitives ? rawValue : [rawValue];
|
|
3417
|
+
request$ = this.http.post(lookup.apiUrl, body, { headers });
|
|
3418
|
+
}
|
|
3419
|
+
else {
|
|
3420
|
+
// Body object POST: merge value into apiPayload body object
|
|
3421
|
+
const value = isArrayOfPrimitives ? rawValue : rawValue;
|
|
3422
|
+
const body = { ...(lookup.apiPayload || {}), [paramName]: value };
|
|
3423
|
+
request$ = this.http.post(lookup.apiUrl, body, { headers });
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
else {
|
|
3427
|
+
// GET: append as query parameter
|
|
3428
|
+
const queryVal = isArrayOfPrimitives ? rawValue.join(',') : String(rawValue);
|
|
3429
|
+
const separator = lookup.apiUrl.includes('?') ? '&' : '?';
|
|
3430
|
+
const url = `${lookup.apiUrl}${separator}${paramName}=${encodeURIComponent(queryVal)}`;
|
|
3431
|
+
request$ = this.http.get(url, { headers });
|
|
3432
|
+
}
|
|
3433
|
+
request$.pipe(takeUntil(this.destroy$), catchError(err => {
|
|
3434
|
+
console.warn(`[SmartForm] lookupConfig resolution failed for field "${fieldName}":`, err);
|
|
3435
|
+
return of(null);
|
|
3436
|
+
})).subscribe(response => {
|
|
3437
|
+
if (!response)
|
|
3438
|
+
return;
|
|
3439
|
+
// Extract data using dataPath
|
|
3440
|
+
let items;
|
|
3441
|
+
if (lookupDataPath) {
|
|
3442
|
+
items = this.getValueByPath(response, lookupDataPath);
|
|
3443
|
+
}
|
|
3444
|
+
else {
|
|
3445
|
+
items = Array.isArray(response) ? response : response;
|
|
3446
|
+
}
|
|
3447
|
+
// Normalize to array
|
|
3448
|
+
const itemArray = Array.isArray(items) ? items : [items];
|
|
3449
|
+
// Parse using existing parseSingleOptionObject
|
|
3450
|
+
const parsed = itemArray
|
|
3451
|
+
.map((item) => this.parseSingleOptionObject(item))
|
|
3452
|
+
.filter((opt) => !!opt);
|
|
3453
|
+
if (parsed.length > 0) {
|
|
3454
|
+
this.selectedItems = parsed;
|
|
3455
|
+
this.addParsedOptions(parsed);
|
|
3456
|
+
// Sync autocomplete display value
|
|
3457
|
+
if (this.isAutocomplete) {
|
|
3458
|
+
this._syncAutocompleteDisplayValue();
|
|
3459
|
+
}
|
|
3460
|
+
this.cdr.markForCheck();
|
|
3461
|
+
}
|
|
3462
|
+
});
|
|
3463
|
+
}
|
|
3326
3464
|
/**
|
|
3327
3465
|
* Extracts option(s) and their corresponding raw codes from an initial value object or array of objects.
|
|
3328
3466
|
* If the value is a primitive but initialLabelField is configured, it looks up the sibling field value.
|
|
@@ -3474,6 +3612,7 @@ class FormFieldComponent {
|
|
|
3474
3612
|
toggleSelectAll(checked) {
|
|
3475
3613
|
if (!this.config.name)
|
|
3476
3614
|
return;
|
|
3615
|
+
this._selectAllActive = checked;
|
|
3477
3616
|
const currentValue = Array.isArray(this.value) ? [...this.value] : [];
|
|
3478
3617
|
const filteredCodes = this.filteredOptionList.map(opt => opt.code);
|
|
3479
3618
|
let newValue;
|
|
@@ -3488,6 +3627,18 @@ class FormFieldComponent {
|
|
|
3488
3627
|
}
|
|
3489
3628
|
this.updateValue(newValue);
|
|
3490
3629
|
}
|
|
3630
|
+
/** Merges every currently-loaded filtered option into the selection — used to keep an
|
|
3631
|
+
* already-active "Select All" in sync as more paginated pages arrive (see loadDropdownOptions). */
|
|
3632
|
+
applySelectAllToLoadedOptions() {
|
|
3633
|
+
if (!this.config.name || !this._selectAllActive)
|
|
3634
|
+
return;
|
|
3635
|
+
const currentValue = Array.isArray(this.value) ? this.value : [];
|
|
3636
|
+
const filteredCodes = this.filteredOptionList.map(opt => opt.code);
|
|
3637
|
+
const toAdd = filteredCodes.filter(code => !currentValue.includes(code));
|
|
3638
|
+
if (toAdd.length === 0)
|
|
3639
|
+
return;
|
|
3640
|
+
this.updateValue([...currentValue, ...toAdd]);
|
|
3641
|
+
}
|
|
3491
3642
|
// ── Type guards ──────────────────────────────────────────────────────────
|
|
3492
3643
|
get isTextField() { return this.config.type === 'TEXT_INPUT'; }
|
|
3493
3644
|
get isNumberField() { return this.config.type === 'NUMBER_INPUT'; }
|
|
@@ -4014,7 +4165,7 @@ class FormFieldComponent {
|
|
|
4014
4165
|
console.warn(`[SmartForm] libraryConfig.uploadConfig: required param "${def.key}" could not be resolved.`);
|
|
4015
4166
|
return null;
|
|
4016
4167
|
}
|
|
4017
|
-
if (value) {
|
|
4168
|
+
if (typeof value === 'string') {
|
|
4018
4169
|
params = params.set(def.key, value);
|
|
4019
4170
|
}
|
|
4020
4171
|
}
|
|
@@ -4715,13 +4866,13 @@ class FormFieldComponent {
|
|
|
4715
4866
|
updateLinkLocal();
|
|
4716
4867
|
}
|
|
4717
4868
|
}
|
|
4718
|
-
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 }, { token: i0.ElementRef }, { token: i4.Overlay }, { token: i5.DateAdapter }], target: i0.ɵɵFactoryTarget.Component });
|
|
4869
|
+
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 }, { token: i0.ElementRef }, { token: i4.Overlay }, { token: i5.DateAdapter }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
4719
4870
|
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", siblingFormArray: "siblingFormArray", siblingIndex: "siblingIndex" }, host: { listeners: { "document:click": "onDocumentClick()", "window:resize": "onWindowResize()", "document:keydown.escape": "onEscapeKey()" } }, providers: [{ provide: DateAdapter, useClass: GranularityDateAdapter }], viewQueries: [{ propertyName: "mediaDeviceInput", first: true, predicate: ["mediaDeviceInput"], descendants: true }, { propertyName: "libraryModalRef", first: true, predicate: ["libraryModal"], descendants: true }], ngImport: i0, template: "<div class=\"form-field mb-8px\" *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; trackBy: trackByFieldName\">\r\n <div class=\"row-field\" [style.gridColumn]=\"'span ' + getChildColSpan(child)\" *ngIf=\"child.isEnabled !== false\">\r\n <lib-form-field #rowFieldRef [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\"\r\n class=\"group-section-wrapper mb-20px\"\r\n [class.multi-save-active]=\"config.sectionConfig?.multiSaveConfig?.active\">\r\n\r\n <!-- Multi-Save: header row with label + top-right Add button -->\r\n <div class=\"multi-save-header d-flex justify-content-between align-items-center mb-24\"\r\n *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 heading (non-multiSave) -->\r\n <h3 class=\"group-label\"\r\n *ngIf=\"config.sectionConfig?.label && !config.sectionConfig?.multiSaveConfig?.active\">{{\r\n config.sectionConfig!.label }}</h3>\r\n\r\n <!-- \u2500\u2500 Standard (non-multiSave) repeater: accordion instances \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <ng-container *ngIf=\"!config.sectionConfig?.multiSaveConfig?.active\">\r\n <div *ngFor=\"let instance of instanceList; trackBy: trackByInstanceId; let i = index\"\r\n class=\"group-accordion-instance\"\r\n [class.is-expanded]=\"isGroupExpanded(i)\">\r\n\r\n <!-- Accordion header -->\r\n <div class=\"group-accordion-header\" (click)=\"toggleGroupAccordion(i)\"\r\n role=\"button\" [attr.aria-expanded]=\"isGroupExpanded(i)\">\r\n <div class=\"accordion-header-left d-flex align-items-center gap-10\">\r\n <span class=\"instance-badge\">{{ i + 1 }}</span>\r\n <span class=\"instance-title\">{{ config.sectionConfig!.label }} #{{ i + 1 }}</span>\r\n </div>\r\n <div class=\"accordion-header-right d-flex align-items-center gap-6\">\r\n <button type=\"button\" class=\"accordion-remove-btn\"\r\n *ngIf=\"instanceList.length > 1\"\r\n (click)=\"$event.stopPropagation(); removeGroupInstance(i)\"\r\n aria-label=\"Remove\">\r\n <mat-icon>delete_outline</mat-icon>\r\n </button>\r\n <mat-icon class=\"accordion-chevron\">\r\n {{ isGroupExpanded(i) ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}\r\n </mat-icon>\r\n </div>\r\n </div>\r\n\r\n <!-- Accordion body (always mounted so form controls survive collapse) -->\r\n <div class=\"group-accordion-body\" [hidden]=\"!isGroupExpanded(i)\">\r\n <div class=\"group-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.sectionConfig!.children\">\r\n <lib-form-field #groupFieldRef\r\n class=\"sf-col\"\r\n [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"isFieldVisible(field, instance.rowController)\"\r\n [config]=\"field\" [controller]=\"instance.rowController\" [formGroup]=\"instance.fg\"\r\n [allowMulti]=\"false\" [siblingFormArray]=\"groupFormArray\" [siblingIndex]=\"i\">\r\n </lib-form-field>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Full-width dashed Add button -->\r\n <button type=\"button\" class=\"btn-add-group\" (click)=\"addGroupInstance()\">\r\n <mat-icon>add</mat-icon> {{ addLabel }} {{ config.sectionConfig!.label }}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- \u2500\u2500 MultiSave: card instances \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 <ng-container *ngIf=\"config.sectionConfig?.multiSaveConfig?.active\">\r\n <div *ngFor=\"let instance of instanceList; trackBy: trackByInstanceId; let i = index\"\r\n class=\"group-instance position-relative mb-16 overflow-hidden\"\r\n [class.is-editing]=\"instance.isEditing\"\r\n [class.is-card]=\"!instance.isEditing\">\r\n\r\n <!-- Edit / new form view -->\r\n <div [hidden]=\"!instance.isEditing\">\r\n <div class=\"group-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.sectionConfig!.children\">\r\n <lib-form-field #groupFieldRef\r\n class=\"sf-col\"\r\n [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"isFieldVisible(field, instance.rowController)\"\r\n [config]=\"field\" [controller]=\"instance.rowController\" [formGroup]=\"instance.fg\"\r\n [allowMulti]=\"false\" [siblingFormArray]=\"groupFormArray\" [siblingIndex]=\"i\">\r\n </lib-form-field>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- Save / Cancel -->\r\n <div class=\"group-footer d-flex justify-content-end align-items-center gap-16 p-0-24\"\r\n *ngIf=\"config.sectionConfig?.multiSaveConfig?.active\">\r\n <span class=\"field-error color-error fs-0-75rem\" *ngIf=\"multiSaveError\">{{ multiSaveError }}</span>\r\n <div class=\"footer-actions d-flex gap-12\">\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 <!-- Card view (saved state) -->\r\n <ng-container *ngIf=\"!instance.isEditing\">\r\n <div class=\"card-view d-flex justify-content-between align-items-center p-18px-24px\"\r\n [class.is-expanded]=\"instance.isExpanded\">\r\n <div class=\"card-content flex-1 d-flex flex-column gap-4 overflow-hidden\">\r\n <span class=\"card-title font-weight-600 overflow-hidden fs-1rem c-111827\">{{\r\n instance.fg.get(config.sectionConfig!.multiSaveConfig!.summaryField || '')?.value\r\n || '\u2014' }}</span>\r\n </div>\r\n <div class=\"card-actions d-flex align-items-center gap-16 ml-20\">\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 </ng-container>\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\"\r\n class=\"group-section-wrapper mb-20px\">\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 <lib-form-field #singleGroupFieldRef\r\n class=\"sf-col\"\r\n [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"isFieldVisible(field)\"\r\n [config]=\"field\" [controller]=\"controller\" [formGroup]=\"groupFormGroup\" [allowMulti]=\"false\">\r\n </lib-form-field>\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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *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 position-relative d-flex align-items-center\">\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\"\r\n class=\"password-toggle position-absolute cursor-pointer d-flex align-items-center justify-content-center b-none border-none c-6B7280 p-0-25rem\"\r\n (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 position-relative d-flex w-100\" [class.readonly]=\"config.readonly\">\r\n <span class=\"input-prefix br-none\" *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 d-flex align-items-center font-weight-500\" *ngIf=\"config.suffix\">{{ config.suffix\r\n }}</span>\r\n\r\n <div class=\"readonly-icons position-absolute d-flex gap-8 pe-none\" *ngIf=\"config.readonly\">\r\n <mat-icon class=\"lock-icon\">lock</mat-icon>\r\n </div>\r\n\r\n <!-- Suffix Action Icons (clickable) -->\r\n <div class=\"suffix-action-icons position-absolute d-flex gap-8 align-items-center\"\r\n *ngIf=\"config.suffixActionIcons?.length && !config.readonly\">\r\n <mat-icon class=\"suffix-action-icon\"\r\n *ngFor=\"let action of config.suffixActionIcons\"\r\n [style.color]=\"action.color || null\"\r\n [title]=\"action.tooltip || ''\"\r\n (click)=\"onSuffixActionClick(action.actionId)\">\r\n {{ action.icon }}\r\n </mat-icon>\r\n </div>\r\n </div>\r\n\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n <div class=\"char-count-hint font-poppins font-weight-400 text-14 text-right c-6B7280\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"input-group position-relative d-flex w-100\" [class.readonly]=\"config.readonly\">\r\n <span class=\"input-prefix br-none\" *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 ?? null\" [max]=\"config.numberConfig?.max ?? null\"\r\n [step]=\"config.numberConfig?.step || 1\" [class.is-invalid]=\"errorMessage\" [readonly]=\"config.readonly\">\r\n\r\n <span class=\"input-suffix d-flex align-items-center font-weight-500\" *ngIf=\"config.suffix\">{{ config.suffix\r\n }}</span>\r\n\r\n <div class=\"readonly-icons position-absolute d-flex gap-8 pe-none\" *ngIf=\"config.readonly\">\r\n <mat-icon class=\"lock-icon\">lock</mat-icon>\r\n </div>\r\n\r\n <!-- Suffix Action Icons (clickable) -->\r\n <div class=\"suffix-action-icons position-absolute d-flex gap-8 align-items-center\"\r\n *ngIf=\"config.suffixActionIcons?.length && !config.readonly\">\r\n <mat-icon class=\"suffix-action-icon\"\r\n *ngFor=\"let action of config.suffixActionIcons\"\r\n [style.color]=\"action.color || null\"\r\n [title]=\"action.tooltip || ''\"\r\n (click)=\"onSuffixActionClick(action.actionId)\">\r\n {{ action.icon }}\r\n </mat-icon>\r\n </div>\r\n </div>\r\n\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"input-group position-relative d-flex w-100\" [class.readonly]=\"config.readonly\">\r\n <input matInput [matDatepicker]=\"datePicker\" class=\"field-input date-input has-icon-right\"\r\n [formControlName]=\"config.name!\" [min]=\"effectiveMinDate\" [max]=\"config.dateConfig?.maxDate\"\r\n [class.is-invalid]=\"errorMessage\" [placeholder]=\"config.placeholder || ''\"\r\n [readonly]=\"config.readonly || config.dateConfig?.inputReadonly\"\r\n (click)=\"!config.readonly && datePicker.open()\">\r\n <div class=\"date-icon-wrapper position-absolute d-flex align-items-center justify-content-center\"\r\n *ngIf=\"!config.readonly\">\r\n <mat-datepicker-toggle matSuffix [for]=\"datePicker\"></mat-datepicker-toggle>\r\n </div>\r\n <mat-datepicker #datePicker [startView]=\"datePickerStartView\"\r\n (opened)=\"onDatePickerOpened(datePicker)\"\r\n (yearSelected)=\"onDateYearSelected($event, datePicker)\"\r\n (monthSelected)=\"onDateMonthSelected($event, datePicker)\">\r\n </mat-datepicker>\r\n\r\n <div class=\"readonly-icons position-absolute d-flex gap-8 pe-none\" *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 c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <!-- DEFAULT variant: existing native input \u2014 unchanged -->\r\n <ng-container *ngIf=\"!isWheelVariant\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"input-group position-relative d-flex w-100\" [class.readonly]=\"config.readonly\">\r\n <input type=\"time\" class=\"field-input time-input\" [formControlName]=\"config.name!\"\r\n [min]=\"effectiveMinTime\" [max]=\"effectiveMaxTime\"\r\n [class.is-invalid]=\"errorMessage\" [readonly]=\"config.readonly || config.timeConfig?.inputReadonly\">\r\n\r\n <div class=\"readonly-icons position-absolute d-flex gap-8 pe-none\" *ngIf=\"config.readonly\">\r\n <mat-icon class=\"lock-icon\">lock</mat-icon>\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- WHEEL variant: drum-roll picker -->\r\n <lib-time-picker\r\n *ngIf=\"isWheelVariant\"\r\n [formControlName]=\"config.name!\"\r\n [label]=\"config.label || ''\"\r\n [placeholder]=\"config.timeConfig?.placeholder || ''\"\r\n [required]=\"config.required || false\"\r\n [disabled]=\"config.readonly || false\"\r\n [mode]=\"config.timeConfig?.mode || '12'\"\r\n [minuteStep]=\"config.timeConfig?.minuteStep || 1\"\r\n [minTime]=\"effectiveMinTime || undefined\"\r\n [maxTime]=\"config.timeConfig?.maxTime\"\r\n [errorMessage]=\"errorMessage\"\r\n [labels]=\"timePickerLabels\">\r\n </lib-time-picker>\r\n\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *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 position-relative d-flex align-items-center w-100\"\r\n [class.is-invalid]=\"errorMessage\" [class.readonly]=\"config.readonly\">\r\n <!-- Search icon -->\r\n <mat-icon class=\"ac-search-icon position-absolute fs-1-1rem c-9CA3AF pe-none\">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\"\r\n class=\"ac-clear-btn position-absolute d-flex align-items-center justify-content-center cursor-pointer rounded-50 b-none border-none c-9CA3AF p-0-2rem\"\r\n *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 <span class=\"ac-option-label\">{{ option.label }}</span>\r\n\r\n <!-- Dynamic display fields (image / email / phone / text) -->\r\n <div class=\"ac-display-fields d-flex flex-wrap gap-6 mt-2\" *ngIf=\"option['displayMeta']?.length\">\r\n <ng-container *ngFor=\"let field of option['displayMeta']\">\r\n\r\n <!-- Image avatar -->\r\n <span *ngIf=\"field.type === 'image' && field.value\" class=\"ac-df-item ac-df-image\">\r\n <img [src]=\"field.value\" [alt]=\"field.label || 'image'\" class=\"ac-df-avatar\">\r\n </span>\r\n\r\n <!-- Email -->\r\n <span *ngIf=\"field.type === 'email' && field.value\" class=\"ac-df-item ac-df-chip\">\r\n <mat-icon class=\"ac-df-icon\">mail_outline</mat-icon>\r\n <span *ngIf=\"field.label\" class=\"ac-df-label\">{{ field.label }}</span>\r\n {{ field.value }}\r\n </span>\r\n\r\n <!-- Phone -->\r\n <span *ngIf=\"field.type === 'phone' && field.value\" class=\"ac-df-item ac-df-chip\" [class]=\"field.className\">\r\n <mat-icon class=\"ac-df-icon\">phone</mat-icon>\r\n <span *ngIf=\"field.label\" class=\"ac-df-label\">{{ field.label }}</span>\r\n {{ field.value }}\r\n </span>\r\n\r\n <!-- Custom / Icon-based / Generic Text -->\r\n <span *ngIf=\"field.type !== 'image' && field.type !== 'email' && field.type !== 'phone' && field.value\" \r\n class=\"ac-df-item\" [class.ac-df-chip]=\"!!field.icon\" [class]=\"field.className\">\r\n <mat-icon class=\"ac-df-icon\" *ngIf=\"field.icon\">{{ field.icon }}</mat-icon>\r\n <span *ngIf=\"field.label\" class=\"ac-df-label\">{{ field.label }}</span>\r\n {{ field.value }}\r\n </span>\r\n\r\n </ng-container>\r\n </div>\r\n </mat-option>\r\n <mat-option *ngIf=\"filteredOptions.length === 0\" disabled class=\"ac-no-results fs-0-8125rem c-6B7280\">\r\n No results found\r\n </mat-option>\r\n </mat-autocomplete>\r\n\r\n <div class=\"readonly-icons position-absolute d-flex gap-8 pe-none\" *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 c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <!-- SINGLE SELECT: native select (default, non-searchable, non-paginated) -->\r\n <div *ngIf=\"config.subType === 'SINGLE' && !isCustomSingleDropdown\" class=\"native-select-wrapper\">\r\n <select class=\"field-input\" [formControlName]=\"config.name!\"\r\n [class.is-invalid]=\"errorMessage\" [disabled]=\"optionsLoading || config.disabled\">\r\n <option [ngValue]=\"null\" disabled selected>{{ optionsLoading ? (controller.labels['LBL_LOADING'] || 'Loading...') : (config.placeholder || selectPlaceholderLabel) }}</option>\r\n <option *ngFor=\"let option of displayLocalOptionList\" [value]=\"option.code\">\r\n {{ option.label }}\r\n </option>\r\n </select>\r\n <div *ngIf=\"optionsLoading\" class=\"native-select-spinner\" aria-hidden=\"true\"></div>\r\n </div>\r\n\r\n <!-- SINGLE SELECT: custom dropdown panel (searchable or paginated) -->\r\n <div *ngIf=\"config.subType === 'SINGLE' && isCustomSingleDropdown\" class=\"single-select-wrapper\"\r\n [class.is-invalid]=\"errorMessage\">\r\n\r\n <div #singleTrigger cdkOverlayOrigin #singleOrigin=\"cdkOverlayOrigin\"\r\n class=\"field-input single-select-trigger d-flex align-items-center justify-content-between cursor-pointer\"\r\n [class.ss-open]=\"isSingleDropdownOpen\"\r\n (click)=\"!optionsLoading && toggleSingleDropdown($event, singleTrigger)\">\r\n <span *ngIf=\"singleSelectedLabel && !optionsLoading\" class=\"single-select-value fs-0-9rem\">\r\n {{ singleSelectedLabel }}\r\n </span>\r\n <span *ngIf=\"!singleSelectedLabel || optionsLoading\" class=\"single-select-placeholder\">\r\n {{ optionsLoading ? (controller.labels['LBL_LOADING'] || 'Loading...') : (config.placeholder || selectPlaceholderLabel) }}\r\n </span>\r\n <div class=\"d-flex align-items-center\">\r\n <div *ngIf=\"optionsLoading\" class=\"mini-spinner me-2\" role=\"progressbar\" aria-label=\"Loading options\"></div>\r\n <mat-icon class=\"single-select-arrow\">\r\n {{ isSingleDropdownOpen ? expandLessLabel : expandMoreLabel }}\r\n </mat-icon>\r\n </div>\r\n </div>\r\n\r\n <ng-template cdkConnectedOverlay\r\n [cdkConnectedOverlayOrigin]=\"singleOrigin\"\r\n [cdkConnectedOverlayOpen]=\"isSingleDropdownOpen\"\r\n [cdkConnectedOverlayPositions]=\"dropdownOverlayPositions\"\r\n [cdkConnectedOverlayScrollStrategy]=\"dropdownScrollStrategy\"\r\n [cdkConnectedOverlayWidth]=\"dropdownOverlayWidth\"\r\n [cdkConnectedOverlayViewportMargin]=\"8\"\r\n [cdkConnectedOverlayPush]=\"true\"\r\n (overlayOutsideClick)=\"onDropdownOverlayOutsideClick($event, singleTrigger)\"\r\n (detach)=\"isSingleDropdownOpen = false\">\r\n <div class=\"single-select-panel\"\r\n (click)=\"$event.stopPropagation()\">\r\n\r\n <!-- Search input (shown only when searchConfig.enabled) -->\r\n <div class=\"dropdown-search-wrapper\" *ngIf=\"isSearchableDropdown\">\r\n <mat-icon class=\"dropdown-search-icon\">search</mat-icon>\r\n <input type=\"text\" class=\"dropdown-search-input\"\r\n [placeholder]=\"searchPlaceholderLabel\"\r\n [(ngModel)]=\"searchText\" [ngModelOptions]=\"{standalone: true}\"\r\n (input)=\"dropdownSearch$.next(searchText)\"\r\n (click)=\"$event.stopPropagation()\">\r\n <mat-icon *ngIf=\"searchText\" class=\"dropdown-search-clear cursor-pointer\"\r\n (click)=\"searchText = ''; dropdownSearch$.next(''); $event.stopPropagation()\">close</mat-icon>\r\n </div>\r\n\r\n <!-- Options list -->\r\n <div class=\"single-select-options-list\" (scroll)=\"onDropdownScroll($event)\">\r\n <div *ngIf=\"optionsLoading\" class=\"dropdown-loading-state\">\r\n <div class=\"dropdown-spinner\"></div>\r\n <span class=\"fs-0-875rem c-6B7280\">{{ controller.labels['LBL_LOADING'] || 'Loading...' }}</span>\r\n </div>\r\n\r\n <ng-container *ngIf=\"!optionsLoading\">\r\n <div *ngFor=\"let option of displayOptionList\"\r\n class=\"single-select-option d-flex align-items-center gap-8 cursor-pointer\"\r\n [class.selected]=\"isSingleOptionSelected(option.code)\"\r\n (click)=\"selectSingleOption(option)\">\r\n <span class=\"fs-0-875rem c-111827\">{{ option.label }}</span>\r\n <mat-icon *ngIf=\"isSingleOptionSelected(option.code)\" class=\"single-select-check\">check</mat-icon>\r\n </div>\r\n <div *ngIf=\"!filteredOptionList?.length && localOptionList?.length\"\r\n class=\"multi-select-empty fs-0-875rem c-6B7280\">\r\n {{ noMatchingOptionsLabel }}\r\n </div>\r\n <div *ngIf=\"!localOptionList?.length\"\r\n class=\"multi-select-empty fs-0-875rem c-6B7280\">\r\n {{ noOptionsAvailableLabel }}\r\n </div>\r\n\r\n <!-- Load More button / Pagination Spinner -->\r\n <div *ngIf=\"config.optionConfig?.pagination?.enabled && ((hasMoreDropdownPages && config.optionConfig?.pagination?.mode !== 'infiniteScroll') || isDropdownLoadingMore)\" class=\"dropdown-pagination-footer\">\r\n <button type=\"button\" class=\"btn-dropdown-load-more\"\r\n *ngIf=\"hasMoreDropdownPages && config.optionConfig?.pagination?.mode !== 'infiniteScroll' && !isDropdownLoadingMore\"\r\n (click)=\"loadMoreDropdownOptions(); $event.stopPropagation()\">\r\n {{ controller.labels['LBL_LOAD_MORE'] || 'Load More' }}\r\n </button>\r\n <div *ngIf=\"isDropdownLoadingMore\" class=\"dropdown-loading-more-state\">\r\n <div class=\"dropdown-spinner-small\"></div>\r\n <span class=\"fs-0-8125rem c-6B7280\">{{ controller.labels['LBL_LOADING_MORE'] || 'Loading more...' }}</span>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </ng-template>\r\n </div>\r\n\r\n <!-- MULTIPLE SELECT: custom panel with checkboxes -->\r\n <div *ngIf=\"config.subType === 'MULTIPLE'\" class=\"multi-select-wrapper\"\r\n [class.is-invalid]=\"errorMessage\">\r\n\r\n <div #multiTrigger cdkOverlayOrigin #multiOrigin=\"cdkOverlayOrigin\"\r\n class=\"field-input multi-select-trigger d-flex align-items-center justify-content-between cursor-pointer\"\r\n [class.ms-open]=\"isMultiDropdownOpen\"\r\n (click)=\"!optionsLoading && toggleMultiDropdown($event, multiTrigger)\">\r\n <span *ngIf=\"multiSelectedCount > 0 && !optionsLoading\" class=\"multi-select-value fs-0-9rem\">\r\n {{ multiSelectedCount }} selected\r\n </span>\r\n <span *ngIf=\"multiSelectedCount === 0 || optionsLoading\" class=\"multi-select-placeholder\">\r\n {{ optionsLoading ? (controller.labels['LBL_LOADING'] || 'Loading...') : (config.placeholder || selectPlaceholderLabel) }}\r\n </span>\r\n <div class=\"d-flex align-items-center\">\r\n <div *ngIf=\"optionsLoading\" class=\"mini-spinner me-2\" role=\"progressbar\" aria-label=\"Loading options\"></div>\r\n <mat-icon class=\"multi-select-arrow\">\r\n {{ isMultiDropdownOpen ? expandLessLabel : expandMoreLabel }}\r\n </mat-icon>\r\n </div>\r\n </div>\r\n\r\n <ng-template cdkConnectedOverlay\r\n [cdkConnectedOverlayOrigin]=\"multiOrigin\"\r\n [cdkConnectedOverlayOpen]=\"isMultiDropdownOpen\"\r\n [cdkConnectedOverlayPositions]=\"dropdownOverlayPositions\"\r\n [cdkConnectedOverlayScrollStrategy]=\"dropdownScrollStrategy\"\r\n [cdkConnectedOverlayWidth]=\"dropdownOverlayWidth\"\r\n [cdkConnectedOverlayViewportMargin]=\"8\"\r\n [cdkConnectedOverlayPush]=\"true\"\r\n (overlayOutsideClick)=\"onDropdownOverlayOutsideClick($event, multiTrigger)\"\r\n (detach)=\"isMultiDropdownOpen = false\">\r\n <div class=\"multi-select-panel\"\r\n (click)=\"$event.stopPropagation()\">\r\n\r\n <!-- Search input (shown only when searchConfig.enabled) -->\r\n <div class=\"dropdown-search-wrapper\" *ngIf=\"isSearchableDropdown\">\r\n <mat-icon class=\"dropdown-search-icon\">search</mat-icon>\r\n <input type=\"text\" class=\"dropdown-search-input\"\r\n [placeholder]=\"searchPlaceholderLabel\"\r\n [(ngModel)]=\"searchText\" [ngModelOptions]=\"{standalone: true}\"\r\n (input)=\"dropdownSearch$.next(searchText)\"\r\n (click)=\"$event.stopPropagation()\">\r\n <mat-icon *ngIf=\"searchText\" class=\"dropdown-search-clear cursor-pointer\"\r\n (click)=\"searchText = ''; dropdownSearch$.next(''); $event.stopPropagation()\">close</mat-icon>\r\n </div>\r\n\r\n <!-- Select All checkbox (shown only when showSelectAll is true) -->\r\n <label *ngIf=\"config.optionConfig?.showSelectAll && filteredOptionList?.length\"\r\n class=\"multi-select-option multi-select-all d-flex align-items-center gap-8 cursor-pointer\">\r\n <input type=\"checkbox\"\r\n [checked]=\"isAllSelected()\"\r\n [indeterminate]=\"isSomeSelected()\"\r\n [disabled]=\"!!config.disabled\"\r\n (change)=\"toggleSelectAll($any($event.target).checked)\">\r\n <span class=\"fs-0-875rem c-111827 fw-600\">{{ selectAllLabel }}</span>\r\n </label>\r\n\r\n <!-- Options list -->\r\n <div class=\"multi-select-options-list\" (scroll)=\"onDropdownScroll($event)\">\r\n <div *ngIf=\"optionsLoading\" class=\"dropdown-loading-state\">\r\n <div class=\"dropdown-spinner\"></div>\r\n <span class=\"fs-0-875rem c-6B7280\">{{ controller.labels['LBL_LOADING'] || 'Loading...' }}</span>\r\n </div>\r\n\r\n <ng-container *ngIf=\"!optionsLoading\">\r\n <label *ngFor=\"let option of filteredOptionList\"\r\n class=\"multi-select-option d-flex align-items-center gap-8 cursor-pointer\">\r\n <input type=\"checkbox\"\r\n [checked]=\"isChecked(option.code)\"\r\n [disabled]=\"!!config.disabled\"\r\n (change)=\"onCheckboxListChange(option.code, $any($event.target).checked)\">\r\n <span class=\"fs-0-875rem c-111827\">{{ option.label }}</span>\r\n </label>\r\n <div *ngIf=\"!filteredOptionList?.length && localOptionList?.length\"\r\n class=\"multi-select-empty fs-0-875rem c-6B7280\">\r\n {{ noMatchingOptionsLabel }}\r\n </div>\r\n <div *ngIf=\"!localOptionList?.length\"\r\n class=\"multi-select-empty fs-0-875rem c-6B7280\">\r\n {{ noOptionsAvailableLabel }}\r\n </div>\r\n\r\n <!-- Load More button / Pagination Spinner -->\r\n <div *ngIf=\"config.optionConfig?.pagination?.enabled && ((hasMoreDropdownPages && config.optionConfig?.pagination?.mode !== 'infiniteScroll') || isDropdownLoadingMore)\" class=\"dropdown-pagination-footer\">\r\n <button type=\"button\" class=\"btn-dropdown-load-more\"\r\n *ngIf=\"hasMoreDropdownPages && config.optionConfig?.pagination?.mode !== 'infiniteScroll' && !isDropdownLoadingMore\"\r\n (click)=\"loadMoreDropdownOptions(); $event.stopPropagation()\">\r\n {{ controller.labels['LBL_LOAD_MORE'] || 'Load More' }}\r\n </button>\r\n <div *ngIf=\"isDropdownLoadingMore\" class=\"dropdown-loading-more-state\">\r\n <div class=\"dropdown-spinner-small\"></div>\r\n <span class=\"fs-0-8125rem c-6B7280\">{{ controller.labels['LBL_LOADING_MORE'] || 'Loading more...' }}</span>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </ng-template>\r\n </div>\r\n\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"radio-group\" [class.is-invalid]=\"errorMessage\"\r\n [class]=\"config.optionConfig?.layout ? 'layout-' + config.optionConfig!.layout.toLowerCase() : ''\">\r\n <label *ngFor=\"let option of localOptionList\" class=\"radio-label\"\r\n [class.card-item]=\"config.subType === 'CARD'\"\r\n [class.selected]=\"formGroup.get(config.name!)?.value === option.code\"\r\n [style.gridColumn]=\"config.optionConfig?.layout?.toUpperCase() === 'COLUMN' ? 'span ' + getOptionColSpan(option) : null\">\r\n <input type=\"radio\" [formControlName]=\"config.name!\" [value]=\"option.code\">\r\n <div class=\"option-content d-flex flex-column gap-4 flex-1 text-left\">\r\n <span class=\"label-text text-16 c-1F2937\">{{ option.label }}</span>\r\n <span class=\"option-hint text-13 color-gray\" *ngIf=\"option.hint\">{{ option.hint }}</span>\r\n </div>\r\n </label>\r\n </div>\r\n\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label && config.subType === 'LIST'\"\r\n class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *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 d-flex align-items-center gap-8 cursor-pointer fs-0-875rem c-111827\">\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' || config.subType === 'CARD'\" class=\"checkbox-group d-flex flex-column gap-8\"\r\n [class.is-invalid]=\"errorMessage\"\r\n [class]=\"config.optionConfig?.layout ? 'layout-' + config.optionConfig!.layout.toLowerCase() : ''\">\r\n <label *ngFor=\"let option of localOptionList\"\r\n class=\"checkbox-label d-flex align-items-center gap-8 cursor-pointer fs-0-875rem c-111827\"\r\n [class.card-item]=\"config.subType === 'CARD'\" [class.selected]=\"isChecked(option.code)\"\r\n [style.gridColumn]=\"config.optionConfig?.layout?.toUpperCase() === 'COLUMN' ? 'span ' + getOptionColSpan(option) : null\">\r\n <input type=\"checkbox\" [checked]=\"isChecked(option.code)\" [disabled]=\"!!config.disabled\"\r\n (change)=\"onCheckboxListChange(option.code, $any($event.target).checked)\">\r\n <div class=\"option-content d-flex flex-column gap-4 flex-1 text-left\">\r\n <span class=\"label-text text-16 c-1F2937\">{{ option.label }}</span>\r\n <span class=\"option-hint text-13 color-gray\" *ngIf=\"option.hint\">{{ option.hint }}</span>\r\n </div>\r\n </label>\r\n </div>\r\n\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"chip-group d-flex flex-wrap gap-8\" [class.is-invalid]=\"errorMessage\">\r\n <label *ngFor=\"let option of localOptionList\"\r\n class=\"chip-label cursor-pointer p-6px-14px b-ffffff c-374151 b-1px-solid-D1D5DB br-20px fs-0-875rem\"\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 c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label class=\"switch-container d-flex justify-content-between align-items-center cursor-pointer\">\r\n <span class=\"field-label d-block font-poppins c-202124 fs-18px\">{{ config.label }}</span>\r\n <div class=\"switch position-relative\">\r\n <input type=\"checkbox\" [formControlName]=\"config.name!\" [class.is-invalid]=\"errorMessage\">\r\n <span class=\"slider position-absolute cursor-pointer\"></span>\r\n </div>\r\n </label>\r\n\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *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 d-block w-100\"\r\n [placeholder]=\"config.richTextConfig?.placeholder || config.placeholder || ''\"\r\n [styles]=\"{height: config.richTextConfig?.height || '200px'}\"\r\n [modules]=\"richTextModules\">\r\n </quill-editor>\r\n </div>\r\n\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n <div class=\"char-count-hint font-poppins font-weight-400 text-14 text-right c-6B7280\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"rating-group d-flex gap-4\" [class.is-invalid]=\"errorMessage\">\r\n <span *ngFor=\"let star of getStarArray()\" class=\"star d-inline-flex align-items-center cursor-pointer\"\r\n [class.filled]=\"isStarFilled(star)\" [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 c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">{{ config.label\r\n }}</label>\r\n <div class=\"generated-value fs-0-875rem p-0-625rem-0-875rem b-F3F4F6 b-1px-solid-E5E7EB br-8px c-6B7280\">{{ value ||\r\n '-' }}</div>\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <!-- Drop Zone -->\r\n <div\r\n class=\"upload-drop-zone d-flex flex-column align-items-center justify-content-center gap-8 cursor-pointer text-center p-32px-24px us-none\"\r\n [class.drag-over]=\"isDragOver\" [class.has-files]=\"value?.length\" [class.is-invalid]=\"errorMessage\"\r\n (dragover)=\"onDragOver($event)\" (dragleave)=\"onDragLeave($event)\" (drop)=\"onFileDrop($event)\"\r\n (click)=\"fileInput.click()\">\r\n\r\n <!-- Icon with accent-colour pill background -->\r\n <div class=\"upload-icon-wrap mb-4\">\r\n <div class=\"dropzone-icon-pill d-flex align-items-center justify-content-center\">\r\n <mat-icon class=\"upload-cloud-icon\">cloud_upload</mat-icon>\r\n </div>\r\n </div>\r\n\r\n <p class=\"upload-main-text font-weight-600 m-0 fs-0-9rem\">Drag & drop files here</p>\r\n <p class=\"upload-sub-text m-0 fs-0-8rem c-64748B\">or <span class=\"upload-link\">Browse files</span></p>\r\n <p class=\"upload-hint-text m-0 fs-0-78rem c-64748B\" *ngIf=\"config.attachmentConfig?.acceptLabel\">\r\n Supported: <span class=\"upload-formats font-weight-500\">{{ config.attachmentConfig!.acceptLabel }}</span>\r\n </p>\r\n <p class=\"upload-hint-text m-0 fs-0-78rem c-64748B\" *ngIf=\"!config.attachmentConfig?.acceptLabel && config.hint\">\r\n {{ config.hint }}\r\n </p>\r\n <span class=\"upload-size-badge fs-0-72rem\" *ngIf=\"config.attachmentConfig?.maxSizeMB\">\r\n Max {{ config.attachmentConfig!.maxSizeMB }}MB\r\n </span>\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 d-flex flex-column gap-8 mt-10\" *ngIf=\"value?.length\">\r\n <div *ngFor=\"let f of value; let i = index\"\r\n class=\"uploaded-item d-flex align-items-center gap-10 p-10px-14px br-8px\"\r\n [class.uploading]=\"f.isUploading\">\r\n\r\n <!-- Uploading spinner -->\r\n <ng-container *ngIf=\"f.isUploading; else fileReady\">\r\n <div class=\"upload-spinner rounded-50 b-2px-solid-E2E8F0\"></div>\r\n <div class=\"file-info flex-1 d-flex flex-column\">\r\n <span class=\"file-name font-weight-500 overflow-hidden fs-0-85rem\" [title]=\"f.name\">{{ f.name }}</span>\r\n <span class=\"file-size uploading-label fs-0-72rem\">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 <mat-icon class=\"file-type-icon\">{{ getFileIcon(f.type) }}</mat-icon>\r\n <img *ngIf=\"f.type?.startsWith('image') && f.dataUrl\" [src]=\"f.dataUrl\" class=\"file-thumb rounded-4\"\r\n alt=\"preview\">\r\n <div class=\"file-info flex-1 d-flex flex-column\">\r\n <span class=\"file-name font-weight-500 overflow-hidden fs-0-85rem\" [title]=\"f.name\">{{ f.name }}</span>\r\n <span class=\"file-size fs-0-72rem\">{{ formatFileSize(f.size) }}</span>\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Compact icon-only remove button -->\r\n <button type=\"button\" class=\"file-remove-btn d-flex align-items-center justify-content-center rounded-50\"\r\n [disabled]=\"f.isUploading\" (click)=\"!f.isUploading && removeUploadedFile(i)\"\r\n aria-label=\"Remove file\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Validation / file errors -->\r\n <span class=\"field-error color-error fs-0-75rem\" *ngIf=\"fileUploadError\">{{ fileUploadError }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *ngIf=\"errorMessage && !fileUploadError\">{{ errorMessage }}</span>\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\"\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 d-flex flex-column gap-6 p-0 border-none b-none\"\r\n [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 d-grid align-items-start\">\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 d-flex flex-column gap-20\">\r\n\r\n <!-- Header: title + max-items badge -->\r\n <div class=\"mu-header d-flex align-items-start flex-wrap gap-10\">\r\n <h3 class=\"mu-title m-0 c-111827 fs-1-35rem\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </h3>\r\n <span\r\n class=\"mu-badge d-inline-flex align-items-center rounded-20 color-white font-weight-600 fs-0-72rem p-4px-12px b-111827\"\r\n *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 m-0 fs-0-875rem c-6B7280\" *ngIf=\"config.attachmentConfig?.description\">\r\n {{ config.attachmentConfig?.description }}\r\n </p>\r\n <p class=\"mu-description m-0 fs-0-875rem c-6B7280\"\r\n *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 m-0 p-0 d-flex flex-column gap-8 ls-none\"\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\"\r\n class=\"mu-feature-item d-flex align-items-center gap-8 fs-0-875rem c-374151\">\r\n <mat-icon class=\"mu-check text-16 c-3B82F6\">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']\"\r\n class=\"mu-feature-item d-flex align-items-center gap-8 fs-0-875rem c-374151\">\r\n <mat-icon class=\"mu-check text-16 c-3B82F6\">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_1'] }}\r\n </li>\r\n <li *ngIf=\"controller.labels['LBL_MEDIA_FEATURE_2']\"\r\n class=\"mu-feature-item d-flex align-items-center gap-8 fs-0-875rem c-374151\">\r\n <mat-icon class=\"mu-check text-16 c-3B82F6\">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_2'] }}\r\n </li>\r\n <li *ngIf=\"controller.labels['LBL_MEDIA_FEATURE_3']\"\r\n class=\"mu-feature-item d-flex align-items-center gap-8 fs-0-875rem c-374151\">\r\n <mat-icon class=\"mu-check text-16 c-3B82F6\">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 position-relative\" (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 fs-18px\">add</mat-icon>\r\n </lib-button>\r\n\r\n <div class=\"media-dropdown position-absolute rounded-12 overflow-hidden b-ffffff b-1px-solid-E5E7EB\"\r\n *ngIf=\"showMediaMenu\" role=\"menu\" (click)=\"$event.stopPropagation()\">\r\n <!-- Video -->\r\n <button id=\"btn-media-video-{{ config.name }}\" type=\"button\"\r\n class=\"media-dropdown-item d-flex align-items-center gap-12 w-100 cursor-pointer text-left b-none border-none p-12px-16px bb-1px-solid-F3F4F6\"\r\n (click)=\"onMediaMenuVideo(); showMediaMenu = false\" role=\"menuitem\">\r\n <span\r\n class=\"media-drop-icon media-drop-icon--video d-flex align-items-center justify-content-center rounded-8\"><mat-icon>videocam</mat-icon></span>\r\n <span class=\"media-drop-text d-flex flex-column flex-1\">\r\n <span class=\"media-drop-label font-weight-600 fs-0-875rem c-111827\">{{\r\n controller.labels['LBL_MEDIA_VIDEO'] || 'Video' }}</span>\r\n <span class=\"media-drop-desc c-6B7280 fs-0-75rem\">{{ controller.labels['LBL_MEDIA_VIDEO_DESC'] || 'Add\r\n 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\"\r\n class=\"media-dropdown-item d-flex align-items-center gap-12 w-100 cursor-pointer text-left b-none border-none p-12px-16px bb-1px-solid-F3F4F6\"\r\n (click)=\"onMediaMenuDevice(); showMediaMenu = false\" role=\"menuitem\">\r\n <span\r\n class=\"media-drop-icon media-drop-icon--device d-flex align-items-center justify-content-center rounded-8\"><mat-icon>upload</mat-icon></span>\r\n <span class=\"media-drop-text d-flex flex-column flex-1\">\r\n <span class=\"media-drop-label font-weight-600 fs-0-875rem c-111827\">{{\r\n controller.labels['LBL_MEDIA_DEVICE'] || 'Upload from device'\r\n }}</span>\r\n <span class=\"media-drop-desc c-6B7280 fs-0-75rem\">{{ controller.labels['LBL_MEDIA_DEVICE_DESC'] ||\r\n '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\"\r\n class=\"media-dropdown-item d-flex align-items-center gap-12 w-100 cursor-pointer text-left b-none border-none p-12px-16px bb-1px-solid-F3F4F6\"\r\n (click)=\"onMediaMenuLibrary(); showMediaMenu = false\" role=\"menuitem\">\r\n <span\r\n class=\"media-drop-icon media-drop-icon--library d-flex align-items-center justify-content-center rounded-8\"><mat-icon>photo_library</mat-icon></span>\r\n <span class=\"media-drop-text d-flex flex-column flex-1\">\r\n <span class=\"media-drop-label font-weight-600 fs-0-875rem c-111827\">{{\r\n controller.labels['LBL_MEDIA_LIBRARY'] || 'Upload from library'\r\n }}</span>\r\n <span class=\"media-drop-desc c-6B7280 fs-0-75rem\">{{ controller.labels['LBL_MEDIA_LIBRARY_DESC'] ||\r\n '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 d-flex flex-column gap-8 p-16 rounded-10 b-FFFAF1 b-1px-solid-E5E7EB\"\r\n *ngIf=\"showYoutubeInput\">\r\n <label class=\"youtube-panel-label d-flex align-items-center gap-6 font-weight-600 fs-0-875rem c-111827\">\r\n {{ controller.labels['LBL_YOUTUBE_URL'] || 'Video URL' }}\r\n </label>\r\n <div class=\"youtube-input-row d-flex gap-8\">\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 color-error fs-0-75rem\" *ngIf=\"youtubeUrlError\">{{ youtubeUrlError }}</span>\r\n </div>\r\n\r\n <div\r\n class=\"media-upload-status d-flex align-items-center gap-8 mt-4 color-error rounded-8 font-weight-500 p-10px-14px b-FEF2F2 fs-0-85rem\"\r\n *ngIf=\"mediaUploadError\">\r\n <mat-icon class=\"status-icon fs-18px\">error_outline</mat-icon>\r\n <span>{{ mediaUploadError }}</span>\r\n </div>\r\n\r\n <span class=\"field-error color-error fs-0-75rem\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *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 d-flex flex-column gap-12\">\r\n\r\n <!-- Carousel (when items exist) -->\r\n <div class=\"media-carousel-section d-flex flex-column gap-12\" *ngIf=\"mediaItems.length\">\r\n <div\r\n class=\"media-carousel-main position-relative w-100 overflow-hidden d-flex align-items-center justify-content-center br-12px b-0F172A\">\r\n <button id=\"btn-carousel-prev-{{ config.name }}\" type=\"button\"\r\n class=\"carousel-nav carousel-nav--prev position-absolute rounded-50 cursor-pointer d-flex align-items-center justify-content-center border-none b-rgba-255-255-255-0-85\"\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 position-absolute d-flex align-items-center justify-content-center\"\r\n *ngFor=\"let item of mediaItems; let i = index\" [hidden]=\"i !== mediaCarouselIndex\">\r\n <div *ngIf=\"item.isUploading\"\r\n class=\"carousel-uploading d-flex flex-column align-items-center gap-12 c-94A3B8 fs-0-85rem\">\r\n <div class=\"carousel-spinner rounded-50 b-3px-solid-rgba-255-255-255-0-15\"></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 w-100 h-100 br-12px\" [src]=\"item.url | trustedUrl\" frameborder=\"0\"\r\n 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 w-100 h-100 br-12px\" [src]=\"item.url\" alt=\"Media\">\r\n </ng-container>\r\n <button id=\"btn-remove-media-{{ config.name }}-{{ i }}\" type=\"button\"\r\n class=\"carousel-remove-btn position-absolute rounded-50 cursor-pointer d-flex align-items-center justify-content-center border-none b-rgba-0-0-0-0-55\"\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\"\r\n class=\"carousel-nav carousel-nav--next position-absolute rounded-50 cursor-pointer d-flex align-items-center justify-content-center border-none b-rgba-255-255-255-0-85\"\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 position-absolute d-flex gap-6\">\r\n <span *ngFor=\"let item of mediaItems; let i = index\"\r\n class=\"carousel-dot rounded-50 cursor-pointer b-rgba-255-255-255-0-45\"\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 d-flex flex-wrap gap-8 pb-4px\">\r\n <div *ngFor=\"let item of mediaThumbnails; let i = index\"\r\n class=\"media-thumb rounded-8 overflow-hidden cursor-pointer d-flex align-items-center justify-content-center b-2px-solid-transparent b-E2E8F0\"\r\n [class.active]=\"i === mediaCarouselIndex\" (click)=\"mediaGoTo(i)\">\r\n <div *ngIf=\"item.isUploading\"\r\n class=\"thumb-uploading d-flex align-items-center justify-content-center w-100 h-100\">\r\n <div class=\"thumb-spinner rounded-50 b-2px-solid-E2E8F0\"></div>\r\n </div>\r\n <img *ngIf=\"!item.isUploading && item.mediaType === 'youtube' && item.thumbnailUrl\"\r\n [src]=\"item.thumbnailUrl\" class=\"thumb-img w-100 h-100\" alt=\"Video thumbnail\">\r\n <div *ngIf=\"!item.isUploading && item.mediaType === 'youtube' && !item.thumbnailUrl\"\r\n class=\"thumb-yt-placeholder d-flex align-items-center justify-content-center w-100 h-100 b-1E293B c-EF4444\">\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 w-100 h-100\" alt=\"Image thumbnail\">\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Empty right-side placeholder -->\r\n <div\r\n class=\"mu-right-empty d-flex flex-column align-items-center justify-content-center gap-10 h-100 text-center p-24 br-12px b-FFFAF1 c-94A3B8 b-2px-dashed-CBD5E1\"\r\n *ngIf=\"!mediaItems.length\" (click)=\"onMediaMenuDevice()\">\r\n <mat-icon class=\"mu-right-empty-icon fs-52px\">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 <!-- Wrapper is always in DOM (hidden) so @ViewChild can move it to body -->\r\n <div #libraryModal class=\"media-library-portal-host\" [class.is-open]=\"showLibraryModal\">\r\n\r\n <!-- Backdrop -->\r\n <div class=\"media-library-overlay\" (click)=\"closeLibraryModal()\"></div>\r\n\r\n <!-- Modal card -->\r\n <div class=\"media-library-modal d-flex flex-column overflow-hidden b-ffffff br-16px\"\r\n role=\"dialog\" aria-modal=\"true\">\r\n <div class=\"library-modal-header d-flex align-items-start justify-content-between p-24px-28px bb-1px-solid-E5E7EB\">\r\n <div class=\"header-left d-flex flex-column gap-8\">\r\n <h3 class=\"library-modal-title m-0 color-dark fs-1-25rem\">\r\n {{ controller.labels['LBL_ADD_IMAGES'] || 'Add Images' }}\r\n </h3>\r\n <p class=\"library-modal-subtitle m-0 color-gray fs-0-85rem\">\r\n {{ controller.labels['LBL_LIBRARY_MODAL_DESC'] || 'Select images from your library.' }}\r\n </p>\r\n </div>\r\n <button id=\"btn-close-library-{{ config.name }}\" type=\"button\"\r\n class=\"library-close-btn d-flex align-items-center justify-content-center cursor-pointer rounded-50 border-none b-none c-9CA3AF\"\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 rounded-50 b-3px-solid-E2E8F0\"></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 d-flex align-items-center gap-8 color-error b-FEF2F2 fs-0-875rem p-16px-24px\"\r\n *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 d-grid gap-16 flex-1 p-28px b-F9FAFB\" *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 position-relative rounded-12 overflow-hidden cursor-pointer bg-white b-3px-solid-transparent\"\r\n [class.selected]=\"isLibraryItemSelected(img)\" (click)=\"toggleLibraryItem(img)\">\r\n <img [src]=\"getLibraryItemUrl(img)\" class=\"library-grid-img w-100 h-100 d-block\" alt=\"Library image\">\r\n <div\r\n class=\"library-check position-absolute bg-white rounded-50 d-flex align-items-center justify-content-center c-3B82F6\"\r\n *ngIf=\"isLibraryItemSelected(img)\">\r\n <mat-icon>check_circle</mat-icon>\r\n </div>\r\n <div class=\"library-overlay-hover position-absolute b-rgba-59-130-246-0-12\"></div>\r\n </div>\r\n </div>\r\n\r\n <!-- Empty library -->\r\n <div\r\n class=\"library-empty d-flex flex-column align-items-center justify-content-center gap-12 flex-1 c-9CA3AF fs-0-875rem p-48px-24px\"\r\n *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 d-flex align-items-center justify-content-end bg-white p-20px-28px bt-1px-solid-E5E7EB\">\r\n <div class=\"library-footer-actions d-flex gap-12\">\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_CONTINUE'] || 'Continue' }}\r\n </lib-button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n\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 d-flex flex-column gap-6 gap-12\"\r\n [formGroup]=\"formGroup\">\r\n\r\n <!-- Field label -->\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n <p class=\"location-subtitle m-0 c-6B7280 fs-0-8125rem\" *ngIf=\"config.hint\">{{ config.hint }}</p>\r\n\r\n <!-- Three-tab bar -->\r\n <div class=\"location-tabs d-flex gap-12 mb-24\">\r\n <lib-button class=\"loc-tab-btn flex-1\" [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 flex-1\" [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 flex-1\" [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 d-flex flex-column gap-12\">\r\n\r\n <p class=\"loc-section-label m-0 font-weight-600 c-111827 fs-0-9rem\">\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 d-flex flex-column gap-8\" *ngIf=\"locationVenues.length > 0\">\r\n <div *ngFor=\"let venue of locationVenues; let i = index\"\r\n class=\"loc-venue-item d-flex align-items-center gap-10 p-10px-14px br-7px b-ffffff b-1px-solid-D1D5DB\">\r\n <mat-icon class=\"loc-venue-search-icon fs-18px c-9CA3AF\">search</mat-icon>\r\n <span class=\"loc-venue-text flex-1 overflow-hidden fs-0-875rem c-111827\">{{ venue.address || venue.name ||\r\n venue.description }}</span>\r\n <button type=\"button\"\r\n class=\"loc-action-btn loc-delete-btn d-flex align-items-center justify-content-center cursor-pointer rounded-50 b-none border-none p-4px\"\r\n (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 m-0 font-weight-600 fs-0-8125rem c-3B82F6\"\r\n *ngIf=\"locationVenues.length > 0 && config.locationConfig?.allowMulti\">\r\n {{ locationVenues.length }} {{ 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 position-relative\" *ngIf=\"!locationMaxReached\">\r\n <div class=\"loc-search-wrapper position-relative d-flex align-items-center mt-4\">\r\n <mat-icon class=\"loc-search-icon position-absolute fs-1-1rem c-9CA3AF pe-none\">search</mat-icon>\r\n <input\r\n class=\"field-input loc-search-input w-100 font-poppins flex-1 fs-0-875rem c-111827 br-7px br-8px bc-F3F4F6 pl-2-4rem bc-DC2626 pt-0-625rem pb-0-625rem pl-16px pr-16px bc-ffffff b-1px-solid-D1D5DB pr-3-5rem\"\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 position-absolute overflow-hidden br-8px b-ffffff b-1px-solid-D1D5DB\"\r\n *ngIf=\"locationShowSuggestions && locationSuggestions.length\">\r\n <div *ngFor=\"let sug of locationSuggestions\"\r\n class=\"loc-suggestion-item d-flex align-items-center gap-10 cursor-pointer p-10px-14px\"\r\n (mousedown)=\"onLocationSuggestionSelect(sug)\">\r\n <mat-icon class=\"loc-suggestion-icon fs-18px c-E53E3E\">place</mat-icon>\r\n <span class=\"loc-suggestion-text overflow-hidden fs-0-875rem c-374151\">{{ sug.description }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Add another button -->\r\n <button type=\"button\"\r\n class=\"loc-add-btn d-inline-flex align-items-center gap-6 cursor-pointer font-weight-600 p-0 b-none border-none fs-0-875rem c-1A56DB\"\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 overflow-hidden br-10px b-1px-solid-E5E7EB\"\r\n *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 w-100 d-block border-none\"\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 w-100 d-block border-none\"\r\n [style.height]=\"config.locationConfig?.mapHeight || '300px'\" [src]=\"getLocationMapEmbedUrl() | trustedUrl\"\r\n 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 m-0 text-center fs-0-78rem c-6B7280\">\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 d-flex flex-column gap-12\">\r\n <p class=\"loc-section-label m-0 font-weight-600 c-111827 fs-0-9rem\">\r\n {{ controller.labels['LBL_LOC_ONLINE_URL'] || 'Event URL' }}\r\n </p>\r\n <div class=\"loc-search-wrapper position-relative d-flex align-items-center mt-4\">\r\n <mat-icon class=\"loc-search-icon position-absolute fs-1-1rem c-9CA3AF pe-none\">link</mat-icon>\r\n <input\r\n class=\"field-input loc-search-input w-100 font-poppins flex-1 fs-0-875rem c-111827 br-7px br-8px bc-F3F4F6 pl-2-4rem bc-DC2626 pt-0-625rem pb-0-625rem pl-16px pr-16px bc-ffffff b-1px-solid-D1D5DB pr-3-5rem\"\r\n 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'\"\r\n class=\"loc-panel loc-tba-panel d-flex flex-column gap-12 justify-content-center\">\r\n <div\r\n class=\"loc-tba-content d-flex flex-column align-items-center justify-content-center text-center gap-12 p-32px-24px b-F9FAFB b-1px-dashed-D1D5DB br-10px\">\r\n <mat-icon class=\"loc-tba-icon fs-40px c-9CA3AF\">schedule</mat-icon>\r\n <p class=\"loc-tba-text m-0 c-6B7280 fs-0-9rem\">\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 color-error fs-0-75rem\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Link List 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 -->\r\n <div *ngIf=\"isLinkList\" class=\"field-wrapper d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ controller.labels[config.label] || config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <!-- Top Links list (if listPosition === 'top') -->\r\n <div *ngIf=\"linkListConfig?.listPosition === 'top' && linkListItems.length > 0\" class=\"link-list-container mb-8px\">\r\n <ul class=\"link-items-list p-0 m-0\" \r\n [style.display]=\"'grid'\"\r\n [style.gridTemplateColumns]=\"'repeat(12, 1fr)'\"\r\n [style.gap]=\"'12px 16px'\">\r\n <li *ngFor=\"let item of linkListItems; let idx = index\" \r\n class=\"link-list-item\"\r\n [style.gridColumn]=\"'span ' + (linkListConfig?.colSpan || 12)\">\r\n <!-- Normal display mode -->\r\n <ng-container *ngIf=\"editingLinkIndex !== idx\">\r\n <span class=\"link-url-text text-truncate font-weight-500 color-333\">{{ getLinkDisplayUrl(item) }}</span>\r\n <div class=\"link-item-actions d-flex align-items-center gap-12px\">\r\n <button type=\"button\" class=\"action-btn edit-btn\" \r\n *ngIf=\"linkListConfig?.editable !== false\" (click)=\"startEditLink(idx)\" [title]=\"controller.labels['COMMON.ACTIONS.EDIT'] || 'Edit'\">\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n <button type=\"button\" class=\"action-btn delete-btn\" \r\n *ngIf=\"linkListConfig?.deleteable !== false\" (click)=\"deleteLinkItem(idx)\" [title]=\"controller.labels['COMMON.ACTIONS.DELETE'] || 'Delete'\">\r\n <mat-icon>delete_outline</mat-icon>\r\n </button>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Inline Edit mode -->\r\n <ng-container *ngIf=\"editingLinkIndex === idx\">\r\n <div class=\"edit-input-wrapper d-flex align-items-center gap-8px w-100\">\r\n <input type=\"text\" class=\"field-input flex-1 py-4px\" [(ngModel)]=\"editingLinkValue\" \r\n [ngModelOptions]=\"{standalone: true}\" (keydown.enter)=\"saveEditLink(idx)\" (keydown.escape)=\"cancelEditLink()\"\r\n (input)=\"linkValidationError = ''\">\r\n <button type=\"button\" class=\"action-btn save-btn\" (click)=\"saveEditLink(idx)\" [title]=\"controller.labels['COMMON.ACTIONS.SAVE'] || 'Save'\">\r\n <mat-icon>check</mat-icon>\r\n </button>\r\n <button type=\"button\" class=\"action-btn cancel-btn\" (click)=\"cancelEditLink()\" [title]=\"controller.labels['COMMON.ACTIONS.CANCEL'] || 'Cancel'\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </ng-container>\r\n </li>\r\n </ul>\r\n </div>\r\n\r\n <!-- Input text + plus icon -->\r\n <div class=\"input-group position-relative d-flex w-100\">\r\n <input type=\"text\" class=\"field-input pe-40px\" [placeholder]=\"controller.labels[config.placeholder!] || config.placeholder || controller.labels['COMMON.ENTER_LINK'] || 'Enter link...'\"\r\n [(ngModel)]=\"newLinkInputValue\" [ngModelOptions]=\"{standalone: true}\" (keydown.enter)=\"addLinkItem($event)\"\r\n [disabled]=\"config.readonly || config.disabled\"\r\n (input)=\"linkValidationError = ''\">\r\n \r\n <button type=\"button\" class=\"position-absolute right-8px top-50-percent translate-y-minus-50 d-flex align-items-center justify-content-center b-none bg-none p-0 cursor-pointer color-primary\"\r\n [disabled]=\"config.readonly || config.disabled || !newLinkInputValue?.trim()\"\r\n (click)=\"addLinkItem()\" style=\"right: 8px; top: 50%; transform: translateY(-50%); z-index: 10;\">\r\n <mat-icon>add</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Validation error message -->\r\n <span class=\"field-error color-error fs-0-75rem mt-4px d-block\" *ngIf=\"linkValidationError\">{{ linkValidationError }}</span>\r\n\r\n <!-- Bottom Links list (if listPosition !== 'top') -->\r\n <div *ngIf=\"linkListConfig?.listPosition !== 'top' && linkListItems.length > 0\" class=\"link-list-container mt-8px\">\r\n <ul class=\"link-items-list p-0 m-0\" \r\n [style.display]=\"'grid'\"\r\n [style.gridTemplateColumns]=\"'repeat(12, 1fr)'\"\r\n [style.gap]=\"'12px 16px'\">\r\n <li *ngFor=\"let item of linkListItems; let idx = index\" \r\n class=\"link-list-item\"\r\n [style.gridColumn]=\"'span ' + (linkListConfig?.colSpan || 12)\">\r\n <!-- Normal display mode -->\r\n <ng-container *ngIf=\"editingLinkIndex !== idx\">\r\n <span class=\"link-url-text text-truncate font-weight-500 color-333\">{{ getLinkDisplayUrl(item) }}</span>\r\n <div class=\"link-item-actions d-flex align-items-center gap-12px\">\r\n <button type=\"button\" class=\"action-btn edit-btn\" \r\n *ngIf=\"linkListConfig?.editable !== false\" (click)=\"startEditLink(idx)\" [title]=\"controller.labels['COMMON.ACTIONS.EDIT'] || 'Edit'\">\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n <button type=\"button\" class=\"action-btn delete-btn\" \r\n *ngIf=\"linkListConfig?.deleteable !== false\" (click)=\"deleteLinkItem(idx)\" [title]=\"controller.labels['COMMON.ACTIONS.DELETE'] || 'Delete'\">\r\n <mat-icon>delete_outline</mat-icon>\r\n </button>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Inline Edit mode -->\r\n <ng-container *ngIf=\"editingLinkIndex === idx\">\r\n <div class=\"edit-input-wrapper d-flex align-items-center gap-8px w-100\">\r\n <input type=\"text\" class=\"field-input flex-1 py-4px\" [(ngModel)]=\"editingLinkValue\" \r\n [ngModelOptions]=\"{standalone: true}\" (keydown.enter)=\"saveEditLink(idx)\" (keydown.escape)=\"cancelEditLink()\"\r\n (input)=\"linkValidationError = ''\">\r\n <button type=\"button\" class=\"action-btn save-btn\" (click)=\"saveEditLink(idx)\" [title]=\"controller.labels['COMMON.ACTIONS.SAVE'] || 'Save'\">\r\n <mat-icon>check</mat-icon>\r\n </button>\r\n <button type=\"button\" class=\"action-btn cancel-btn\" (click)=\"cancelEditLink()\" [title]=\"controller.labels['COMMON.ACTIONS.CANCEL'] || 'Cancel'\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </ng-container>\r\n </li>\r\n </ul>\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-hint c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ controller.labels[config.hint] || config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n</div>", styles: [".d-flex{display:flex}.d-inline-flex{display:inline-flex}.d-grid{display:grid}.d-block{display:block}.d-none{display:none}.flex-column{flex-direction:column}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-wrap{flex-wrap:wrap}.flex-1{flex:1}.align-items-center{align-items:center}.align-items-start{align-items:flex-start}.align-items-end{align-items:flex-end}.justify-content-center{justify-content:center}.justify-content-between{justify-content:space-between}.justify-content-start{justify-content:flex-start}.justify-content-end{justify-content:flex-end}.grid-cols-12{grid-template-columns:repeat(12,1fr)}.w-100{width:100%}.h-100{height:100%}.position-relative{position:relative}.position-absolute{position:absolute}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-poppins{font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif)}.font-weight-400{font-weight:400}.font-weight-500{font-weight:500}.font-weight-600{font-weight:600}.text-13{font-size:13px}.text-14{font-size:14px}.text-16{font-size:16px}.color-white{color:#fff}.color-dark{color:#111827}.color-gray{color:#6b7280}.color-error{color:var(--cc-sf-error-text-color, #DC2626)}.bg-white{background-color:#fff}.bg-transparent{background-color:transparent}.m-0{margin:0}.mt-4{margin-top:4px}.mt-8{margin-top:8px}.mt-10{margin-top:10px}.mt-12{margin-top:12px}.mt-16{margin-top:16px}.mt-20{margin-top:20px}.mt-24{margin-top:24px}.mb-0{margin-bottom:0}.mb-4{margin-bottom:4px}.mb-8{margin-bottom:8px}.mb-10{margin-bottom:10px}.mb-12{margin-bottom:12px}.mb-16{margin-bottom:16px}.mb-20{margin-bottom:20px}.mb-24{margin-bottom:24px}.ml-16{margin-left:16px}.ml-20{margin-left:20px}.p-0{padding:0}.p-16{padding:16px}.p-20{padding:20px}.p-24{padding:24px}.pt-20{padding-top:20px}.pb-10{padding-bottom:10px}.gap-4{gap:4px}.gap-6{gap:6px}.gap-8{gap:8px}.gap-10{gap:10px}.gap-12{gap:12px}.gap-16{gap:16px}.gap-20{gap:20px}.rounded-4{border-radius:4px}.rounded-6{border-radius:6px}.rounded-8{border-radius:8px}.rounded-10{border-radius:10px}.rounded-12{border-radius:12px}.rounded-20{border-radius:20px}.rounded-24{border-radius:24px}.rounded-50{border-radius:50%}.cursor-pointer{cursor:pointer}.overflow-hidden{overflow:hidden}.resize-vertical{resize:vertical}.box-sizing-border{box-sizing:border-box}.border-none{border:none!important}.mb-8px{margin-bottom:8px!important}.mb-16px{margin-bottom:var(--cc-sf-grid-gap, 16px)!important}.c-DC2626{color:var(--cc-sf-label-required-color, #DC2626)!important}.ml-0-125rem{margin-left:.125rem!important}.fs-0-875rem{font-size:.875rem!important}.c-111827{color:var(--cc-sf-label-color, #111827)!important}.br-7px{border-radius:var(--cc-sf-input-radius, 7px)!important}.c-6B7280{color:var(--cc-sf-hint-color, #6B7280)!important}.fs-0-75rem{font-size:var(--cc-sf-error-text-size, .75rem)!important}.b-none{background:none!important}.p-32px-24px{padding:32px 24px!important}.us-none{-webkit-user-select:none!important;user-select:none!important}.c-1E293B{color:var(--cc-sf-label-color, #1E293B)!important}.c-3B82F6{color:var(--cc-sf-chip-selected-bg, #3B82F6)!important}.fs-0-78rem{font-size:.78rem!important}.p-10px-14px{padding:10px 14px!important}.fs-0-85rem{font-size:.85rem!important}.fs-0-72rem{font-size:.72rem!important}.c-94A3B8{color:#94a3b8!important}.p-4px{padding:4px!important}.br-8px{border-radius:var(--cc-sf-input-radius, 8px)!important}.bc-F3F4F6{background-color:var(--cc-sf-input-disabled-bg, #F3F4F6)!important}.br-none{border-right:none!important}.bl-none{border-left:none!important}.pe-none{pointer-events:none!important}.fs-1-1rem{font-size:1.1rem!important}.c-9CA3AF{color:var(--cc-sf-hint-color, #9CA3AF)!important}.pl-2-4rem{padding-left:2.4rem!important}.fs-0-8125rem{font-size:.8125rem!important}.ls-none{list-style:none!important}.br-12px{border-radius:var(--mu-carousel-radius, 12px)!important}.b-FFFAF1{background:var(--cc-sf-dropzone-bg, #FFFAF1)!important}.fs-18px{font-size:18px!important}.b-FEF2F2{background:var(--cc-sf-error-bg, #FEF2F2)!important}.bc-DC2626{border-color:var(--cc-sf-error-border, #DC2626)!important}.c-202124{color:var(--cc-sf-label-color, #202124)!important}.fs-18px{font-size:var(--cc-sf-label-size, 18px)!important}.mb-0-5rem{margin-bottom:.5rem!important}.pt-0-625rem{padding-top:var(--cc-sf-input-padding-y, .625rem)!important}.pb-0-625rem{padding-bottom:var(--cc-sf-input-padding-y, .625rem)!important}.pl-16px{padding-left:var(--cc-sf-input-padding-x, 16px)!important}.pr-16px{padding-right:var(--cc-sf-input-padding-x, 16px)!important}.bc-ffffff{background-color:var(--cc-sf-section-bg, #ffffff)!important}.b-1px-solid-D1D5DB{border:1px solid var(--cc-sf-input-border, #D1D5DB)!important}.fs-0-75rem{font-size:.75rem!important}.c-1F2937{color:var(--cc-sf-section-label-color, #1F2937)!important}.p-6px-14px{padding:var(--cc-sf-chip-padding, 6px 14px)!important}.b-ffffff{background:var(--loc-suggestion-bg, #ffffff)!important}.c-374151{color:var(--cc-sf-label-color, #374151)!important}.br-20px{border-radius:var(--cc-sf-chip-radius, 20px)!important}.fs-0-875rem{font-size:var(--cc-sf-btn-font-size, .875rem)!important}.bc-D1D5DB{background-color:var(--cc-sf-switch-track-off, #D1D5DB)!important}.pr-2-75rem{padding-right:2.75rem!important}.p-0-25rem{padding:.25rem!important}.p-0-625rem-0-875rem{padding:var(--cc-sf-generated-padding, .625rem .875rem)!important}.b-F3F4F6{background:var(--cc-sf-generated-bg, #F3F4F6)!important}.b-1px-solid-E5E7EB{border:1px solid var(--cc-sf-input-disabled-border, #E5E7EB)!important}.br-8px{border-radius:var(--cc-sf-uploaded-item-radius, 8px)!important}.c-6B7280{color:var(--ms-desc-color, #6B7280)!important}.mb-20px{margin-bottom:var(--cc-sf-section-gap, 20px)!important}.br-10px{border-radius:var(--cc-sf-input-radius, 10px)!important}.p-20px{padding:var(--cc-sf-section-padding, 20px)!important}.fs-1rem{font-size:1rem!important}.m-0-0-16px-0{margin:0 0 16px!important}.bb-2px-solid-E5E7EB{border-bottom:var(--cc-sf-section-label-border, 2px solid #E5E7EB)!important}.p-16px{padding:var(--cc-sf-instance-padding, 16px)!important}.b-F9FAFB{background:var(--loc-tba-bg, #F9FAFB)!important}.bb-1px-dashed-D1D5DB{border-bottom:var(--cc-sf-instance-divider, 1px dashed #D1D5DB)!important}.c-4B5563{color:var(--cc-sf-instance-num-color, #4B5563)!important}.fs-0-8125rem{font-size:var(--cc-sf-hint-size, .8125rem)!important}.pb-0{padding-bottom:0!important}.p-18px-24px{padding:18px 24px!important}.c-111827{color:var(--ms-title-color, #111827)!important}.bt-1px-solid-E5E7EB{border-top:1px solid #E5E7EB!important}.p-4px-10px{padding:4px 10px!important}.b-FFF5F5{background:var(--cc-sf-btn-remove-bg, #FFF5F5)!important}.c-E53E3E{color:var(--loc-delete-color, #E53E3E)!important}.b-1px-solid-FED7D7{border:var(--cc-sf-btn-remove-border, 1px solid #FED7D7)!important}.br-4px{border-radius:var(--cc-sf-btn-remove-radius, 4px)!important}.p-8px-16px{padding:8px 16px!important}.b-transparent{background:var(--cc-sf-btn-add-bg, transparent)!important}.c-3B82F6{color:var(--cc-sf-input-focus-border, #3B82F6)!important}.b-1px-dashed-CBD5E1{border:var(--cc-sf-btn-add-border, 1px dashed #CBD5E1)!important}.br-6px{border-radius:var(--cc-sf-btn-add-radius, 6px)!important}.b-1-5px-dashed-CBD5E1{border:var(--cc-sf-dropzone-border, 1.5px dashed #CBD5E1)!important}.br-12px{border-radius:var(--cc-sf-dropzone-radius, 12px)!important}.bc-FFFAF1{background-color:var(--cc-sf-dropzone-bg, #FFFAF1)!important}.c-94A3B8{color:var(--cc-sf-uploaded-remove-color, #94A3B8)!important}.fs-0-9rem{font-size:var(--cc-sf-input-font-size, .9rem)!important}.c-64748B{color:var(--cc-sf-dropzone-hint-color, #64748B)!important}.b-1px-solid-E2E8F0{border:var(--cc-sf-uploaded-item-border, 1px solid #E2E8F0)!important}.b-2px-solid-E2E8F0{border:2px solid #E2E8F0!important}.pr-3-5rem{padding-right:3.5rem!important}.p-0-0-875rem{padding:0 .875rem!important}.bc-FFFFFF{background-color:var(--cc-sf-input-bg, #FFFFFF)!important}.b-1-5px-solid-D1D5DB{border:var(--cc-sf-input-border, 1.5px solid #D1D5DB)!important}.mb-0-75rem{margin-bottom:.75rem!important}.mt-6px{margin-top:6px!important}.pr-2-4rem{padding-right:2.4rem!important}.p-0-2rem{padding:.2rem!important}.fs-1-35rem{font-size:1.35rem!important}.p-4px-12px{padding:4px 12px!important}.b-111827{background:var(--cc-sf-label-color, #111827)!important}.b-2px-dashed-CBD5E1{border:2px dashed var(--cc-sf-dropzone-border, #CBD5E1)!important}.fs-52px{font-size:52px!important}.p-12px-16px{padding:12px 16px!important}.bb-1px-solid-F3F4F6{border-bottom:1px solid var(--cc-sf-input-disabled-border, #F3F4F6)!important}.b-0F172A{background:var(--mu-carousel-bg, #0F172A)!important}.b-3px-solid-rgba-255-255-255-0-15{border:3px solid rgba(255,255,255,.15)!important}.b-rgba-255-255-255-0-85{background:#ffffffd9!important}.b-rgba-0-0-0-0-55{background:#0000008c!important}.b-rgba-255-255-255-0-45{background:#ffffff73!important}.pb-4px{padding-bottom:4px!important}.b-2px-solid-transparent{border:2px solid transparent!important}.b-E2E8F0{background:var(--mu-thumb-bg, #E2E8F0)!important}.b-1E293B{background:#1e293b!important}.c-EF4444{color:#ef4444!important}.b-rgba-0-0-0-0-5{background:#00000080!important}.br-16px{border-radius:var(--mu-modal-radius, 16px)!important}.p-24px-28px{padding:24px 28px!important}.bb-1px-solid-E5E7EB{border-bottom:1px solid var(--cc-sf-input-disabled-border, #E5E7EB)!important}.fs-1-25rem{font-size:1.25rem!important}.p-48px-24px{padding:48px 24px!important}.b-3px-solid-E2E8F0{border:3px solid #E2E8F0!important}.p-16px-24px{padding:16px 24px!important}.p-28px{padding:28px!important}.b-3px-solid-transparent{border:3px solid transparent!important}.b-rgba-59-130-246-0-12{background:#3b82f61f!important}.p-20px-28px{padding:20px 28px!important}.c-1A56DB{color:var(--loc-add-color, #1A56DB)!important}.b-1px-dashed-D1D5DB{border:1px dashed var(--cc-sf-input-disabled-border, #D1D5DB)!important}.fs-40px{font-size:40px!important}.c-9CA3AF{color:var(--loc-tba-icon-color, #9CA3AF)!important}.form-field{font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif)!important}.generated-value{word-break:break-all}:host{--cc-sf-input-border: #D1D5DB;--cc-sf-input-bg: #ffffff;--cc-sf-input-radius: 9px;--cc-sf-input-height: 44px;--cc-sf-label-color: #111827;--cc-sf-hint-color: #9CA3AF;--cc-sf-error-border: #EF4444;--cc-sf-error-bg: #FFF5F5;--cc-sf-accent-color: #6366F1;--cc-sf-input-focus-border: #6366F1;--cc-sf-input-hover-border: #A5B4FC;--cc-sf-input-placeholder: #C4C9D4;--cc-sf-input-disabled-bg: #F8F9FB;--cc-sf-input-disabled-border: #E5E7EB;--cc-sf-switch-track-on: #6366F1;--cc-sf-switch-track-off: #D1D5DB;--cc-sf-switch-thumb: #ffffff;--cc-sf-selected-color: #6366F1}.form-row{gap:var(--cc-sf-grid-gap, 16px)}.form-row.horizontal{display:flex;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-label{font-size:.8rem!important;font-weight:600!important;line-height:1.2;letter-spacing:.04em;text-transform:uppercase!important;color:#202124!important;margin-bottom:var(--cc-sf-label-mb, 0px)!important}.field-input,input.matInput,.mat-mdc-input-element{display:block;width:100%;height:var(--cc-sf-input-height)!important;padding:0 14px!important;font-family:inherit;font-size:.9rem;color:var(--cc-sf-label-color);background-color:var(--cc-sf-input-bg)!important;border:1.5px solid var(--cc-sf-input-border)!important;border-radius:var(--cc-sf-input-radius)!important;box-sizing:border-box;box-shadow:0 1px 2px #0000000a!important;transition:all .2s cubic-bezier(.4,0,.2,1)}.field-input::placeholder,input.matInput::placeholder,.mat-mdc-input-element::placeholder{font-weight:400;font-size:14px;color:var(--cc-sf-input-placeholder)}.field-input{opacity:var(--cc-sf-input-opacity, 1);line-height:var(--cc-sf-input-line-height, 1.5);transition:var(--cc-sf-input-transition, all .2s ease)}.field-input::placeholder{font-weight:var(--cc-sf-placeholder-weight, 400);font-size:var(--cc-sf-placeholder-size, 14px);line-height:var(--cc-sf-placeholder-line-height, 100%);color:var(--cc-sf-input-placeholder)}.field-input:hover:not(:disabled):not([readonly]){border-color:var(--cc-sf-input-hover-border)!important;box-shadow:0 1px 4px #6366f114!important}.field-input:focus{outline:none;border-color:var(--cc-sf-input-focus-border)!important;box-shadow:0 0 0 3px #6366f124,0 1px 4px #6366f11a!important;background-color:#fefeff!important}.field-input:disabled,.field-input[readonly]{background-color:var(--cc-sf-input-disabled-bg)!important;color:#9ca3af!important;cursor:not-allowed;border-color:var(--cc-sf-input-disabled-border)!important;box-shadow:none!important}.field-input.is-invalid{border-color:var(--cc-sf-error-border)!important;background-color:var(--cc-sf-error-bg)!important}.field-input.is-invalid:focus{box-shadow:0 0 0 3px #ef44441f,0 1px 4px #ef44441a!important}.field-input.textarea{resize:vertical;min-height:100px;height:auto;padding:12px 16px!important}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}.dropdown-search-wrapper{display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:1px solid var(--cc-sf-input-border, #E5E7EB);background:var(--cc-sf-input-bg, #ffffff)}.dropdown-search-wrapper .dropdown-search-icon{font-size:18px;width:18px;height:18px;color:var(--cc-sf-input-placeholder, #9CA3AF);flex-shrink:0}.dropdown-search-wrapper .dropdown-search-input{flex:1;border:none;outline:none;background:transparent;font-size:.875rem;color:var(--cc-sf-label-color, #111827);font-family:inherit}.dropdown-search-wrapper .dropdown-search-input::placeholder{color:var(--cc-sf-input-placeholder, #C4C9D4)}.dropdown-search-wrapper .dropdown-search-clear{font-size:18px;width:18px;height:18px;color:var(--cc-sf-input-placeholder, #9CA3AF);flex-shrink:0;transition:color .15s}.dropdown-search-wrapper .dropdown-search-clear:hover{color:var(--cc-sf-label-color, #111827)}.dropdown-pagination-footer{padding:8px 12px;border-top:1px solid var(--cc-sf-input-border, #E5E7EB);background:var(--cc-sf-input-bg, #ffffff);display:flex;justify-content:center;align-items:center}.dropdown-pagination-footer .btn-dropdown-load-more{width:100%;padding:6px 12px;font-size:.8125rem;font-weight:500;color:var(--cc-sf-accent-color, #6366F1);background:transparent;border:1px dashed var(--cc-sf-accent-color, #6366F1);border-radius:var(--cc-sf-input-radius, 6px);cursor:pointer;transition:all .2s ease}.dropdown-pagination-footer .btn-dropdown-load-more:hover{background:#6366f114}.dropdown-pagination-footer .dropdown-loading-more-state{display:flex;align-items:center;justify-content:center;gap:8px;padding:4px 0}.dropdown-pagination-footer .dropdown-loading-more-state .dropdown-spinner-small{width:14px;height:14px;border:2px solid var(--cc-sf-input-border, #E5E7EB);border-top-color:var(--cc-sf-accent-color, #6366F1);border-radius:50%;animation:spin .8s linear infinite}.multi-select-wrapper{position:relative}.multi-select-wrapper .multi-select-trigger{min-height:var(--cc-sf-input-height, 44px);height:auto;-webkit-user-select:none;user-select:none}.multi-select-wrapper .multi-select-trigger.ms-open{border-color:var(--cc-sf-input-focus-border, #6366F1)!important;box-shadow:0 0 0 3px #6366f124,0 1px 4px #6366f11a!important}.multi-select-wrapper .multi-select-trigger .multi-select-value{flex:1;color:var(--cc-sf-label-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.multi-select-wrapper .multi-select-trigger .multi-select-placeholder{flex:1;font-weight:400;font-size:14px;color:var(--cc-sf-input-placeholder, #C4C9D4)}.multi-select-wrapper .multi-select-trigger .multi-select-arrow{font-size:20px;width:20px;height:20px;line-height:20px;color:var(--cc-sf-label-color, #6B7280);flex-shrink:0}.multi-select-wrapper .multi-select-panel{position:relative;width:100%;max-width:100vw;box-sizing:border-box;background:var(--cc-sf-input-bg, #ffffff);border:1.5px solid var(--cc-sf-input-border, #D1D5DB);border-radius:var(--cc-sf-input-radius, 9px);box-shadow:0 8px 24px #0000001a,0 2px 6px #0000000f;z-index:1050;display:flex;flex-direction:column;overflow:hidden;max-height:320px}.multi-select-wrapper .multi-select-options-list{flex:1;overflow-y:auto;max-height:240px}.multi-select-wrapper .multi-select-options-list::-webkit-scrollbar{width:4px}.multi-select-wrapper .multi-select-options-list::-webkit-scrollbar-thumb{background:var(--cc-sf-input-border, #D1D5DB);border-radius:2px}.multi-select-wrapper .multi-select-option{padding:8px 12px;border-bottom:1px solid var(--cc-sf-input-border, #F3F4F6);transition:background .1s}.multi-select-wrapper .multi-select-option:last-of-type{border-bottom:none}.multi-select-wrapper .multi-select-option:hover{background:var(--cc-sf-input-hover-bg, #F9FAFB)}.multi-select-wrapper .multi-select-option input[type=checkbox]{flex-shrink:0;cursor:pointer;accent-color:var(--cc-sf-selected-color, #6366F1);width:15px;height:15px}.multi-select-wrapper .multi-select-option.multi-select-all{border-bottom:1px solid var(--cc-sf-input-border, #E5E7EB);background:var(--cc-sf-input-bg, #FAFBFC)}.multi-select-wrapper .multi-select-empty{padding:12px;text-align:center}.multi-select-wrapper.is-invalid .multi-select-trigger{border-color:var(--cc-sf-error-border, #EF4444)!important;background-color:var(--cc-sf-error-bg, #FFF5F5)!important}.single-select-wrapper{position:relative}.single-select-wrapper .single-select-trigger{min-height:var(--cc-sf-input-height, 44px);height:auto;-webkit-user-select:none;user-select:none}.single-select-wrapper .single-select-trigger.ss-open{border-color:var(--cc-sf-input-focus-border, #6366F1)!important;box-shadow:0 0 0 3px #6366f124,0 1px 4px #6366f11a!important}.single-select-wrapper .single-select-trigger .single-select-value{flex:1;color:var(--cc-sf-label-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.single-select-wrapper .single-select-trigger .single-select-placeholder{flex:1;font-weight:400;font-size:14px;color:var(--cc-sf-input-placeholder, #C4C9D4)}.single-select-wrapper .single-select-trigger .single-select-arrow{font-size:20px;width:20px;height:20px;line-height:20px;color:var(--cc-sf-label-color, #6B7280);flex-shrink:0}.single-select-wrapper .single-select-panel{position:relative;width:100%;max-width:100vw;box-sizing:border-box;background:var(--cc-sf-input-bg, #ffffff);border:1.5px solid var(--cc-sf-input-border, #D1D5DB);border-radius:var(--cc-sf-input-radius, 9px);box-shadow:0 8px 24px #0000001a,0 2px 6px #0000000f;z-index:1050;display:flex;flex-direction:column;overflow:hidden;max-height:320px}.single-select-wrapper .single-select-options-list{flex:1;overflow-y:auto;max-height:240px}.single-select-wrapper .single-select-options-list::-webkit-scrollbar{width:4px}.single-select-wrapper .single-select-options-list::-webkit-scrollbar-thumb{background:var(--cc-sf-input-border, #D1D5DB);border-radius:2px}.single-select-wrapper .single-select-option{padding:8px 12px;border-bottom:1px solid var(--cc-sf-input-border, #F3F4F6);transition:background .1s;justify-content:space-between}.single-select-wrapper .single-select-option:last-of-type{border-bottom:none}.single-select-wrapper .single-select-option:hover{background:var(--cc-sf-input-hover-bg, #F9FAFB)}.single-select-wrapper .single-select-option.selected{background:var(--cc-sf-input-hover-bg, #F0F5FF);color:var(--cc-sf-selected-color, #6366F1)}.single-select-wrapper .single-select-option .single-select-check{font-size:18px;width:18px;height:18px;color:var(--cc-sf-selected-color, #6366F1)}.single-select-wrapper.is-invalid .single-select-trigger{border-color:var(--cc-sf-error-border, #EF4444)!important;background-color:var(--cc-sf-error-bg, #FFF5F5)!important}.multi-select-panel,.single-select-panel{width:100%;background:var(--cc-sf-input-bg, #ffffff);border:1.5px solid var(--cc-sf-input-border, #D1D5DB);border-radius:var(--cc-sf-input-radius, 9px);box-shadow:0 8px 24px #0000001a,0 2px 6px #0000000f;display:flex;flex-direction:column;overflow:hidden;max-height:320px}.multi-select-options-list,.single-select-options-list{flex:1;overflow-y:auto;max-height:240px}.multi-select-options-list::-webkit-scrollbar,.single-select-options-list::-webkit-scrollbar{width:4px}.multi-select-options-list::-webkit-scrollbar-thumb,.single-select-options-list::-webkit-scrollbar-thumb{background:var(--cc-sf-input-border, #D1D5DB);border-radius:2px}.multi-select-option,.single-select-option{display:flex;align-items:center;gap:8px;cursor:pointer;padding:8px 12px;border-bottom:1px solid var(--cc-sf-input-border, #F3F4F6);transition:background .1s}.multi-select-option:last-of-type,.single-select-option:last-of-type{border-bottom:none}.multi-select-option:hover,.single-select-option:hover{background:var(--cc-sf-input-hover-bg, #F9FAFB)}.multi-select-option input[type=checkbox],.single-select-option input[type=checkbox]{flex-shrink:0;cursor:pointer;accent-color:var(--cc-sf-selected-color, #6366F1);width:15px;height:15px}.multi-select-option.multi-select-all{border-bottom:1px solid var(--cc-sf-input-border, #E5E7EB);background:var(--cc-sf-input-bg, #FAFBFC)}.single-select-option{justify-content:space-between}.single-select-option.selected{background:var(--cc-sf-input-hover-bg, #F0F5FF);color:var(--cc-sf-selected-color, #6366F1)}.single-select-option .single-select-check{font-size:18px;width:18px;height:18px;color:var(--cc-sf-selected-color, #6366F1)}.multi-select-empty{padding:12px;text-align:center}::ng-deep .cdk-overlay-container{z-index:1100}.char-count-hint{font-style:normal;line-height:100%;letter-spacing:.02em;margin-top:.4rem}.radio-group.layout-column,.checkbox-group.layout-column{display:grid!important;grid-template-columns:repeat(12,1fr);gap:16px;width:100%}.radio-group.layout-row,.checkbox-group.layout-row{flex-direction:column!important;gap:12px;width:100%}.radio-label input,.checkbox-label input{cursor:pointer;accent-color:var(--cc-sf-chip-selected-bg, #F05A28)}.radio-label.card-item,.checkbox-label.card-item{display:flex!important;flex-direction:row-reverse!important;justify-content:space-between!important;align-items:center!important;border:1px solid var(--cc-sf-input-disabled-border, #E5E7EB);border-radius:12px;padding:16px 20px;box-sizing:border-box;transition:all .2s ease;background:var(--cc-sf-input-bg, #ffffff);margin-bottom:0}.radio-label.card-item input,.checkbox-label.card-item input{margin-left:16px}.radio-label.card-item.selected,.checkbox-label.card-item.selected{border-color:var(--cc-sf-selected-color);background-color:#f05a280d}.radio-label.card-item .option-content .label-text,.checkbox-label.card-item .option-content .label-text,.checkbox-single .checkbox-label{font-weight:var(--cc-sf-label-weight, 500)}.chip-label{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-selected-color);color:var(--cc-sf-chip-selected-color, #ffffff);border-color:var(--cc-sf-selected-color)}.switch{width:50px;height:24px;display:inline-block}.switch input{opacity:0;width:0;height:0;position:absolute}.switch input:checked+.slider{background-color:var(--cc-sf-switch-track-on)!important}.switch input:checked+.slider:before{transform:translate(26px)}.switch .slider{inset:0;transition:.4s;background-color:var(--cc-sf-switch-track-off);border-radius:24px}.switch .slider:before{position:absolute;content:\"\";height:18px;width:18px;left:3px;bottom:3px;background-color:var(--cc-sf-switch-thumb);transition:.4s;border-radius:50%}.rating-group .star{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 .password-toggle{right:.625rem;top:50%;transform:translateY(-50%);line-height:1;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}.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-left:12px;padding-top:2px;padding-bottom:2px;border-left:var(--cc-sf-section-header-accent-width, 4px) solid var(--cc-sf-section-header-accent-color, #3B82F6);line-height:1.4}.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-accordion-instance{border:var(--cc-sf-instance-border, 1px solid #E5E7EB);border-radius:var(--cc-sf-section-border-radius-inner, 8px);margin-bottom:8px;overflow:hidden;transition:border-color .2s ease}.group-section-wrapper .group-accordion-instance:last-of-type{margin-bottom:0}.group-section-wrapper .group-accordion-instance .group-accordion-header{display:flex;justify-content:space-between;align-items:center;padding:10px 14px;background:var(--cc-sf-repeater-accordion-header-bg, #F9FAFB);cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .15s ease}.group-section-wrapper .group-accordion-instance .group-accordion-header:hover{background:var(--cc-sf-repeater-accordion-active-bg, #EFF6FF)}.group-section-wrapper .group-accordion-instance .group-accordion-header .instance-badge{width:22px;height:22px;border-radius:50%;background:var(--cc-sf-repeater-badge-bg, #E5E7EB);color:var(--cc-sf-repeater-badge-color, #374151);font-size:.75rem;font-weight:600;display:flex;align-items:center;justify-content:center;flex-shrink:0}.group-section-wrapper .group-accordion-instance .group-accordion-header .instance-title{font-size:var(--cc-sf-instance-num-size, .8125rem);font-weight:600;color:var(--cc-sf-repeater-accordion-header-color, #1F2937)}.group-section-wrapper .group-accordion-instance .group-accordion-header .accordion-remove-btn{background:none;border:none;cursor:pointer;color:var(--cc-sf-btn-remove-color, #E53E3E);padding:4px;border-radius:4px;line-height:1;display:flex;align-items:center;transition:background .15s ease}.group-section-wrapper .group-accordion-instance .group-accordion-header .accordion-remove-btn mat-icon{font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem}.group-section-wrapper .group-accordion-instance .group-accordion-header .accordion-remove-btn:hover{background:var(--cc-sf-btn-remove-hover-bg, #FED7D7)}.group-section-wrapper .group-accordion-instance .group-accordion-header .accordion-chevron{font-size:1.25rem;width:1.25rem;height:1.25rem;line-height:1.25rem;color:var(--cc-sf-instance-num-color, #4B5563)}.group-section-wrapper .group-accordion-instance .group-accordion-body{padding:var(--cc-sf-instance-padding, 16px);background:var(--cc-sf-instance-bg, #F9FAFB);border-top:var(--cc-sf-instance-divider, 1px dashed #D1D5DB)}.group-section-wrapper .btn-add-group{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:10px 20px;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-family:inherit;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)}.group-section-wrapper .btn-add-group mat-icon{font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem}.group-section-wrapper .btn-add-group:hover{background:var(--cc-sf-btn-add-hover-bg, #EFF6FF);border-color:var(--cc-sf-btn-add-hover-border, #BFDBFE)}.group-section-wrapper .group-instance:last-child{margin-bottom:0}.group-section-wrapper.multi-save-active{border:none;box-shadow:none;padding:0;background:transparent}.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.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 .card-content .card-title{white-space:nowrap;text-overflow:ellipsis}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-content .card-desc{line-height:1.4;display:-webkit-box;-webkit-line-clamp:1;line-clamp:1;-webkit-box-orient:vertical}.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 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)}.btn-remove{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{font-weight:var(--cc-sf-btn-font-weight, 600);transition:var(--cc-sf-btn-transition, all .2s ease)}.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{background-color:var(--cc-sf-dropzone-bg, #F8FAFC);border:var(--cc-sf-dropzone-border, 1.5px dashed #CBD5E1);border-radius:var(--cc-sf-dropzone-radius, 12px);transition:background-color .2s ease,border-color .2s ease}.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 .dropzone-icon-pill{width:52px;height:52px;border-radius:50%;background:var(--cc-sf-dropzone-icon-bg, rgba(59, 130, 246, .1))}.upload-icon-wrap mat-icon.upload-cloud-icon{font-size:28px;width:28px;height:28px;line-height:28px;color:var(--cc-sf-accent-color, #3B82F6)}.upload-main-text{color:var(--cc-sf-label-color, #1E293B)}.upload-sub-text{color:var(--cc-sf-hint-color, #64748B)}.upload-link{color:var(--cc-sf-dropzone-link-color, #3B82F6);font-weight:500}.upload-formats{color:var(--cc-sf-dropzone-link-color, #3B82F6)}.upload-size-badge{display:inline-block;padding:2px 8px;border-radius:20px;background:var(--cc-sf-input-disabled-bg, #F3F4F6);color:var(--cc-sf-hint-color, #6B7280);font-weight:500}.uploaded-item{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;flex-shrink:0}.uploaded-item .file-info{min-width:0;gap:2px}.uploaded-item .file-info .file-name{white-space:nowrap;text-overflow:ellipsis}.uploaded-item .file-remove-btn{flex-shrink:0;width:32px;height:32px;background:none;border:none;cursor:pointer;color:var(--cc-sf-uploaded-remove-color, #94A3B8);padding:0;display:flex;align-items:center;justify-content:center;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:not(:disabled){color:var(--cc-sf-uploaded-remove-hover-color, #DC2626);background:var(--cc-sf-uploaded-remove-hover-bg, #FEF2F2)}.uploaded-item .file-remove-btn:disabled{opacity:.4;cursor:not-allowed}.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-top-color:var(--cc-sf-accent-color, #3B82F6);animation:cc-spin .7s linear infinite}@keyframes cc-spin{to{transform:rotate(360deg)}}.uploading-label{font-style:italic}.input-group{align-items:stretch}.input-group .field-input{flex:1;width:auto}.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{cursor:default}.input-prefix,.input-suffix{display:flex!important;align-items:center;white-space:nowrap;padding:0 14px;background-color:var(--cc-sf-input-disabled-bg);border:1px solid var(--cc-sf-input-border);font-size:.875rem;color:var(--cc-sf-hint-color);-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)}.readonly-icons{right:.875rem;top:50%;transform:translateY(-50%)}.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)}.suffix-action-icons{right:.875rem;top:50%;transform:translateY(-50%);z-index:1;pointer-events:auto}.suffix-action-icons mat-icon.suffix-action-icon{font-size:1.125rem;width:1.125rem;height:1.125rem;line-height:1.125rem;color:var(--cc-sf-hint-color, #6B7280);cursor:pointer;transition:color .2s ease,transform .15s ease}.suffix-action-icons mat-icon.suffix-action-icon:hover{color:var(--cc-sf-text-color, #202124);transform:scale(1.15)}.date-icon-wrapper{right:.5rem;top:50%;transform:translateY(-50%);pointer-events:auto}.date-icon-wrapper .mat-icon-button{width:32px;height:32px;line-height:32px}.subfields-group-wrapper .subfields-row{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{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{font-weight:700}.autocomplete-wrapper .ac-input{padding-left:40px!important}.autocomplete-wrapper .ac-search-icon{left:.75rem;width:1.1rem;height:1.1rem;line-height:1.1rem;z-index:1;transition:color var(--cc-sf-input-transition, .2s ease)}.autocomplete-wrapper .ac-clear-btn{right:.6rem;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-accent-color, #3B82F6)}.autocomplete-wrapper.is-invalid .ac-input{border-color:var(--cc-sf-error-border)!important;background-color:var(--cc-sf-error-bg)}.autocomplete-wrapper.readonly .ac-input{background-color:var(--cc-sf-input-disabled-bg);color:var(--cc-sf-input-disabled-color, #6B7280);cursor:not-allowed;border-color:var(--cc-sf-input-disabled-border)!important}.ac-no-results{font-style:italic}::ng-deep .mat-mdc-autocomplete-panel,::ng-deep .mat-autocomplete-panel{background:var(--cc-sf-input-bg, #ffffff)!important;border-radius:var(--cc-sf-input-radius, 9px)!important;border:1px solid var(--cc-sf-input-disabled-border, #E5E7EB)!important;box-shadow:0 8px 24px #0000001a,0 2px 6px #0000000f!important;padding:4px 0!important;min-width:200px}::ng-deep .mat-mdc-autocomplete-panel mat-option,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option,::ng-deep .mat-mdc-autocomplete-panel .mat-option,::ng-deep .mat-autocomplete-panel mat-option,::ng-deep .mat-autocomplete-panel .mat-mdc-option,::ng-deep .mat-autocomplete-panel .mat-option{background:var(--cc-sf-input-bg, #ffffff)!important;color:var(--cc-sf-label-color, #111827)!important;font-size:.875rem!important;padding:10px 16px!important;min-height:40px!important;line-height:1.4!important;display:flex!important;flex-direction:column!important;align-items:flex-start!important;transition:background var(--cc-sf-input-transition, .2s ease)!important}::ng-deep .mat-mdc-autocomplete-panel mat-option:hover:not(.mat-option-disabled):not([disabled]),::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option:hover:not(.mat-option-disabled):not([disabled]),::ng-deep .mat-mdc-autocomplete-panel .mat-option:hover:not(.mat-option-disabled):not([disabled]),::ng-deep .mat-autocomplete-panel mat-option:hover:not(.mat-option-disabled):not([disabled]),::ng-deep .mat-autocomplete-panel .mat-mdc-option:hover:not(.mat-option-disabled):not([disabled]),::ng-deep .mat-autocomplete-panel .mat-option:hover:not(.mat-option-disabled):not([disabled]){background:var(--cc-sf-input-disabled-bg, #F3F4F6)!important}::ng-deep .mat-mdc-autocomplete-panel mat-option.mat-selected:not(.mat-option-multiple),::ng-deep .mat-mdc-autocomplete-panel mat-option.mdc-list-item--selected,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option.mat-selected:not(.mat-option-multiple),::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option.mdc-list-item--selected,::ng-deep .mat-mdc-autocomplete-panel .mat-option.mat-selected:not(.mat-option-multiple),::ng-deep .mat-mdc-autocomplete-panel .mat-option.mdc-list-item--selected,::ng-deep .mat-autocomplete-panel mat-option.mat-selected:not(.mat-option-multiple),::ng-deep .mat-autocomplete-panel mat-option.mdc-list-item--selected,::ng-deep .mat-autocomplete-panel .mat-mdc-option.mat-selected:not(.mat-option-multiple),::ng-deep .mat-autocomplete-panel .mat-mdc-option.mdc-list-item--selected,::ng-deep .mat-autocomplete-panel .mat-option.mat-selected:not(.mat-option-multiple),::ng-deep .mat-autocomplete-panel .mat-option.mdc-list-item--selected{background:var(--cc-sf-dropzone-hover-bg, #EFF6FF)!important;color:var(--cc-sf-selected-color, #6366F1)!important;font-weight:600!important}::ng-deep .mat-mdc-autocomplete-panel mat-option .ac-option-label,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .ac-option-label,::ng-deep .mat-mdc-autocomplete-panel .mat-option .ac-option-label,::ng-deep .mat-autocomplete-panel mat-option .ac-option-label,::ng-deep .mat-autocomplete-panel .mat-mdc-option .ac-option-label,::ng-deep .mat-autocomplete-panel .mat-option .ac-option-label{font-weight:500;color:var(--cc-sf-label-color, #111827);font-size:.875rem;display:block}::ng-deep .mat-mdc-autocomplete-panel mat-option .ac-display-fields,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .ac-display-fields,::ng-deep .mat-mdc-autocomplete-panel .mat-option .ac-display-fields,::ng-deep .mat-autocomplete-panel mat-option .ac-display-fields,::ng-deep .mat-autocomplete-panel .mat-mdc-option .ac-display-fields,::ng-deep .mat-autocomplete-panel .mat-option .ac-display-fields{align-items:center;line-height:1}::ng-deep .mat-mdc-autocomplete-panel mat-option .ac-df-item,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .ac-df-item,::ng-deep .mat-mdc-autocomplete-panel .mat-option .ac-df-item,::ng-deep .mat-autocomplete-panel mat-option .ac-df-item,::ng-deep .mat-autocomplete-panel .mat-mdc-option .ac-df-item,::ng-deep .mat-autocomplete-panel .mat-option .ac-df-item{display:inline-flex;align-items:center;font-size:.72rem;color:var(--cc-sf-hint-color, #6B7280);gap:3px}::ng-deep .mat-mdc-autocomplete-panel mat-option .ac-df-chip,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .ac-df-chip,::ng-deep .mat-mdc-autocomplete-panel .mat-option .ac-df-chip,::ng-deep .mat-autocomplete-panel mat-option .ac-df-chip,::ng-deep .mat-autocomplete-panel .mat-mdc-option .ac-df-chip,::ng-deep .mat-autocomplete-panel .mat-option .ac-df-chip{background:var(--cc-sf-input-disabled-bg, #F3F4F6);border-radius:4px;padding:2px 6px}::ng-deep .mat-mdc-autocomplete-panel mat-option .ac-df-text,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .ac-df-text,::ng-deep .mat-mdc-autocomplete-panel .mat-option .ac-df-text,::ng-deep .mat-autocomplete-panel mat-option .ac-df-text,::ng-deep .mat-autocomplete-panel .mat-mdc-option .ac-df-text,::ng-deep .mat-autocomplete-panel .mat-option .ac-df-text{color:var(--cc-sf-hint-color, #6B7280)}::ng-deep .mat-mdc-autocomplete-panel mat-option .ac-df-avatar,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .ac-df-avatar,::ng-deep .mat-mdc-autocomplete-panel .mat-option .ac-df-avatar,::ng-deep .mat-autocomplete-panel mat-option .ac-df-avatar,::ng-deep .mat-autocomplete-panel .mat-mdc-option .ac-df-avatar,::ng-deep .mat-autocomplete-panel .mat-option .ac-df-avatar{width:24px;height:24px;border-radius:50%;object-fit:cover;border:1px solid var(--cc-sf-input-border, #D1D5DB);vertical-align:middle}::ng-deep .mat-mdc-autocomplete-panel mat-option .ac-df-label,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .ac-df-label,::ng-deep .mat-mdc-autocomplete-panel .mat-option .ac-df-label,::ng-deep .mat-autocomplete-panel mat-option .ac-df-label,::ng-deep .mat-autocomplete-panel .mat-mdc-option .ac-df-label,::ng-deep .mat-autocomplete-panel .mat-option .ac-df-label{font-weight:600;color:var(--cc-sf-hint-color, #9CA3AF);margin-right:2px}::ng-deep .mat-mdc-autocomplete-panel mat-option .ac-df-icon,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .ac-df-icon,::ng-deep .mat-mdc-autocomplete-panel .mat-option .ac-df-icon,::ng-deep .mat-autocomplete-panel mat-option .ac-df-icon,::ng-deep .mat-autocomplete-panel .mat-mdc-option .ac-df-icon,::ng-deep .mat-autocomplete-panel .mat-option .ac-df-icon{font-size:11px;width:11px;height:11px;line-height:11px;color:var(--cc-sf-hint-color, #9CA3AF);flex-shrink:0}.mu-layout{grid-template-columns:1fr 1fr;gap:32px}@media(max-width:768px){.mu-layout{grid-template-columns:1fr}}.mu-title{font-weight:700;line-height:1.3}.mu-badge{white-space:nowrap;flex-shrink:0}.mu-description{line-height:1.6}.mu-feature-item .mu-check{width:16px;height:16px;line-height:16px;flex-shrink:0}.mu-right{min-height:260px}.mu-right-empty{min-height:250px;max-width:400px;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{width:52px;height:52px;line-height:52px;opacity:.3}.mu-right-empty p{margin:0;font-size:.85rem}.media-add-container{display:inline-block}.media-add-container ::ng-deep button{display:flex;align-items:center;gap:6px}.media-add-container ::ng-deep button .menu-chevron{width:18px;height:18px;line-height:18px;transition:transform .2s ease}.media-dropdown{top:calc(100% + 6px);left:0;z-index:200;min-width:240px;box-shadow:var(--mu-dropdown-shadow, 0 8px 32px rgba(0, 0, 0, .12));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{transition:background .15s ease}.media-dropdown-item:last-child{border-bottom:none}.media-dropdown-item:hover{background:var(--cc-sf-dropzone-hover-bg, #F0F9FF)}.media-drop-icon{width:36px;height:36px;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{gap:2px}.youtube-input-panel{animation:mu-fade-in .18s ease}.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{align-items:stretch}.media-menu-backdrop{position:fixed;inset:0;z-index:100}.media-upload-status{animation:mu-fade-in .2s ease}.media-upload-status .status-icon{width:18px;height:18px;line-height:18px}.media-carousel-main{max-width:400px;height:var(--mu-carousel-height, 250px)}.carousel-viewer{inset:0}.carousel-viewer .carousel-image{object-fit:cover}.carousel-viewer .carousel-spinner{width:36px;height:36px;border-top-color:var(--cc-sf-accent-color, #3B82F6);animation:cc-spin .7s linear infinite}.carousel-nav{top:50%;transform:translateY(-50%);z-index:10;width:40px;height:40px;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{top:10px;right:10px;z-index:10;width:32px;height:32px;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{bottom:10px;left:50%;transform:translate(-50%);z-index:10}.carousel-dot{width:8px;height:8px;transition:background .2s ease,transform .2s ease}.carousel-dot.active{background:#fff;transform:scale(1.3)}.media-thumbnail-strip{max-width:400px;overflow-x:auto}.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;transition:border-color .2s ease,transform .15s ease}.media-thumb.active{border-color:var(--mu-thumb-active-border, var(--cc-sf-accent-color, #3B82F6));transform:scale(1.04)}.media-thumb:hover:not(.active){border-color:var(--cc-sf-input-hover-border, #9CA3AF)}.media-thumb .thumb-img{object-fit:cover}.media-thumb .thumb-yt-placeholder mat-icon{font-size:28px;width:28px;height:28px;line-height:28px}.media-thumb .thumb-uploading .thumb-spinner{width:20px;height:20px;border-top-color:var(--cc-sf-accent-color, #3B82F6);animation:cc-spin .7s linear infinite}.media-library-portal-host{position:fixed;inset:0;z-index:9999;display:flex;align-items:center;justify-content:center;visibility:hidden;opacity:0;pointer-events:none;transition:opacity .2s ease,visibility .2s ease}.media-library-portal-host.is-open{visibility:visible;opacity:1;pointer-events:auto}.media-library-portal-host.is-open .media-library-modal{transform:scale(1) translateY(0)}.media-library-overlay{position:absolute;inset:0;background:#00000080;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.media-library-modal{position:relative;z-index:10000;width:90vw;max-width:900px;max-height:90vh;background:#fff;border-radius:16px;box-shadow:0 20px 25px -5px #0000001a,0 10px 10px -5px #0000000a;transform:scale(.95) translateY(10px);transition:transform .3s cubic-bezier(.175,.885,.32,1.275)}.library-modal-header{flex-shrink:0}.library-modal-title{font-weight:700}.library-modal-subtitle{line-height:1.5;max-width:600px}.library-close-btn{width:32px;height:32px;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 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-top-color:var(--cc-sf-accent-color, #3B82F6);animation:cc-spin .7s linear infinite}.library-error{flex-shrink:0}.library-error mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.library-grid{grid-template-columns:repeat(5,1fr);max-height:50vh;overflow-y:auto}.library-grid::-webkit-scrollbar{width:8px}.library-grid::-webkit-scrollbar-track{background:#f1f1f1}.library-grid::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:10px;border:2px solid #F1F1F1}.library-grid-item{aspect-ratio:1/1;transition:all .3s cubic-bezier(.4,0,.2,1);box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}.library-grid-item:hover{transform:translateY(-4px) scale(1.02);box-shadow:0 20px 25px -5px #0000001a,0 10px 10px -5px #0000000a}.library-grid-item.selected{border-color:var(--cc-sf-accent-color, #3B82F6)}.library-grid-item.selected .library-grid-img{opacity:.7}.library-grid-item:hover .library-overlay-hover{opacity:1}.library-grid-img{object-fit:cover}.library-overlay-hover{inset:0;opacity:0;transition:opacity .15s ease}.library-check{top:6px;right:6px;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{flex-shrink:0}.library-modal-footer .library-footer-actions ::ng-deep .cc-btn-primary{background-color:var(--cc-sf-accent-color, #3B82F6)!important;border-color:var(--cc-sf-accent-color, #3B82F6)!important;color:#fff!important;font-weight:600;padding-left:32px;padding-right:32px}.library-modal-footer .library-footer-actions ::ng-deep .cc-btn-primary:hover{background-color:var(--cc-sf-btn-primary-hover-bg, #2563EB)!important}.library-modal-footer .library-footer-actions ::ng-deep .cc-btn-primary:disabled{background-color:#93c5fd!important;cursor:not-allowed}.library-modal-footer .library-footer-actions ::ng-deep .cc-btn-outline{font-weight:600;padding-left:24px;padding-right:24px;border-color:#d1d5db;color:#374151}.library-modal-footer .library-footer-actions ::ng-deep .cc-btn-outline:hover{background-color:#f9fafb}.location-subtitle{line-height:1.5}.loc-tab-btn ::ng-deep button{width:100%}.loc-tab-btn ::ng-deep button:not(.cc-btn-warning){background-color:var(--cc-sf-input-bg, #ffffff)!important;color:var(--cc-sf-label-color, #000000)!important;border:1px solid var(--cc-sf-input-disabled-border, #E5E7EB)}.loc-tab-btn ::ng-deep button:not(.cc-btn-warning):hover{background-color:var(--cc-sf-input-disabled-bg, #F3F4F6)!important}.loc-venue-item{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{width:18px;height:18px;line-height:18px;flex-shrink:0}.loc-venue-text{white-space:nowrap;text-overflow:ellipsis}.loc-action-btn{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-search-icon{left:.75rem;width:1.1rem;height:1.1rem;line-height:1.1rem;z-index:1}.loc-suggestions-panel{top:calc(100% + 4px);left:0;right:0;z-index:300;box-shadow:0 8px 24px #0000001a;animation:mu-fade-in .15s ease;max-height:260px;overflow-y:auto}.loc-suggestion-item{transition:background .12s ease}.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{width:18px;height:18px;line-height:18px;flex-shrink:0}.loc-suggestion-text{white-space:nowrap;text-overflow:ellipsis}.loc-add-btn{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{box-shadow:0 2px 10px #0000000f}.loc-tba-panel{min-height:120px}.loc-tba-icon{width:40px;height:40px;line-height:40px;opacity:.6}.loc-tba-text{line-height:1.6;max-width:360px}.radio-label{display:flex!important}.radio-label .option-content{padding-left:10px}.link-list-container{border:none!important;background-color:transparent!important;padding:0!important;box-shadow:none!important;overflow:visible!important}.link-items-list{list-style:none}@media(max-width:640px){.link-items-list{grid-template-columns:1fr!important}.link-items-list .link-list-item{grid-column:span 12!important}}.link-list-item{display:flex;align-items:center;justify-content:space-between;padding:10px 16px;background-color:var(--cc-sf-link-item-bg, #f9fafb)!important;border:1px solid var(--cc-sf-link-item-border, #E5E7EB)!important;border-radius:var(--cc-sf-input-radius, 9px)!important;gap:12px;min-height:48px}.link-list-item .link-url-text{font-size:.875rem;color:var(--cc-sf-link-item-text, #111827);word-break:break-all}.link-list-item .action-btn{border:none;background:transparent;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;padding:0;border-radius:6px;transition:background .15s ease,color .15s ease}.link-list-item .action-btn mat-icon{font-size:22px;width:22px;height:22px;line-height:22px;display:flex;align-items:center;justify-content:center}.link-list-item .action-btn.edit-btn{color:var(--cc-sf-link-edit-color, #4b5563)}.link-list-item .action-btn.edit-btn:hover{color:var(--cc-sf-accent-color, #6366F1);background-color:#f3f4f6}.link-list-item .action-btn.delete-btn{color:var(--cc-sf-link-delete-color, #ef4444)}.link-list-item .action-btn.delete-btn:hover{background-color:var(--cc-sf-error-bg, #FFF5F5)}.link-list-item .action-btn.save-btn{color:var(--cc-sf-link-save-color, #16a34a)!important}.link-list-item .action-btn.save-btn:hover{background-color:#ecfdf5}.link-list-item .action-btn.cancel-btn{color:#ef4444!important}.link-list-item .action-btn.cancel-btn:hover{background-color:var(--cc-sf-error-bg, #FFF5F5)}.pe-40px{padding-right:40px!important}.dropdown-loading-state{display:flex;align-items:center;justify-content:center;padding:16px;gap:8px}.dropdown-spinner{width:20px;height:20px;border:2px solid #E2E8F0;border-top-color:var(--cc-sf-accent-color, #3B82F6);border-radius:50%;animation:cc-spin .7s linear infinite}.mini-spinner{width:16px;height:16px;border:2px solid #E2E8F0;border-top-color:var(--cc-sf-accent-color, #3B82F6);border-radius:50%;animation:cc-spin .7s linear infinite}.native-select-wrapper{display:block;width:100%;position:relative}.native-select-wrapper select{width:100%}.native-select-wrapper .native-select-spinner{position:absolute;right:2.5rem;top:calc(50% - 7px);width:14px;height:14px;border:2px solid #CBD5E1;border-top-color:var(--cc-sf-accent-color, #3B82F6);border-radius:50%;animation:cc-spin .7s linear infinite;pointer-events:none}\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.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.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { 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: i7.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: i7.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i7$1.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i7$1.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i7$1.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: i11.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: i11.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "directive", type: i4.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i4.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ButtonComponent, selector: "lib-button", inputs: ["variant", "type", "disabled", "width", "height", "borderRadius", "fontSize", "fontWeight", "backgroundColor", "color", "border", "icon", "labels"] }, { kind: "component", type: i13.QuillEditorComponent, selector: "quill-editor" }, { kind: "component", type: TimePickerComponent, selector: "lib-time-picker", inputs: ["config", "labels", "label", "placeholder", "variant", "mode", "minuteStep", "disabled", "required", "errorMessage", "minTime", "maxTime", "width", "height", "borderRadius", "fontSize", "gap", "fontFamily", "labelColor", "labelFontSize", "labelFontWeight", "backgroundColor", "borderColor", "borderWidth", "padding", "fontWeight", "color", "placeholderColor", "focusBorderColor", "errorColor", "disabledBackgroundColor", "disabledColor"], outputs: ["timeChange"] }, { kind: "component", type: FormFieldComponent, selector: "lib-form-field", inputs: ["config", "controller", "formGroup", "allowMulti", "siblingFormArray", "siblingIndex"] }, { kind: "pipe", type: TrustedUrlPipe, name: "trustedUrl" }] });
|
|
4720
4871
|
}
|
|
4721
4872
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FormFieldComponent, decorators: [{
|
|
4722
4873
|
type: Component,
|
|
4723
4874
|
args: [{ selector: 'lib-form-field', standalone: false, providers: [{ provide: DateAdapter, useClass: GranularityDateAdapter }], template: "<div class=\"form-field mb-8px\" *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; trackBy: trackByFieldName\">\r\n <div class=\"row-field\" [style.gridColumn]=\"'span ' + getChildColSpan(child)\" *ngIf=\"child.isEnabled !== false\">\r\n <lib-form-field #rowFieldRef [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\"\r\n class=\"group-section-wrapper mb-20px\"\r\n [class.multi-save-active]=\"config.sectionConfig?.multiSaveConfig?.active\">\r\n\r\n <!-- Multi-Save: header row with label + top-right Add button -->\r\n <div class=\"multi-save-header d-flex justify-content-between align-items-center mb-24\"\r\n *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 heading (non-multiSave) -->\r\n <h3 class=\"group-label\"\r\n *ngIf=\"config.sectionConfig?.label && !config.sectionConfig?.multiSaveConfig?.active\">{{\r\n config.sectionConfig!.label }}</h3>\r\n\r\n <!-- \u2500\u2500 Standard (non-multiSave) repeater: accordion instances \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <ng-container *ngIf=\"!config.sectionConfig?.multiSaveConfig?.active\">\r\n <div *ngFor=\"let instance of instanceList; trackBy: trackByInstanceId; let i = index\"\r\n class=\"group-accordion-instance\"\r\n [class.is-expanded]=\"isGroupExpanded(i)\">\r\n\r\n <!-- Accordion header -->\r\n <div class=\"group-accordion-header\" (click)=\"toggleGroupAccordion(i)\"\r\n role=\"button\" [attr.aria-expanded]=\"isGroupExpanded(i)\">\r\n <div class=\"accordion-header-left d-flex align-items-center gap-10\">\r\n <span class=\"instance-badge\">{{ i + 1 }}</span>\r\n <span class=\"instance-title\">{{ config.sectionConfig!.label }} #{{ i + 1 }}</span>\r\n </div>\r\n <div class=\"accordion-header-right d-flex align-items-center gap-6\">\r\n <button type=\"button\" class=\"accordion-remove-btn\"\r\n *ngIf=\"instanceList.length > 1\"\r\n (click)=\"$event.stopPropagation(); removeGroupInstance(i)\"\r\n aria-label=\"Remove\">\r\n <mat-icon>delete_outline</mat-icon>\r\n </button>\r\n <mat-icon class=\"accordion-chevron\">\r\n {{ isGroupExpanded(i) ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}\r\n </mat-icon>\r\n </div>\r\n </div>\r\n\r\n <!-- Accordion body (always mounted so form controls survive collapse) -->\r\n <div class=\"group-accordion-body\" [hidden]=\"!isGroupExpanded(i)\">\r\n <div class=\"group-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.sectionConfig!.children\">\r\n <lib-form-field #groupFieldRef\r\n class=\"sf-col\"\r\n [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"isFieldVisible(field, instance.rowController)\"\r\n [config]=\"field\" [controller]=\"instance.rowController\" [formGroup]=\"instance.fg\"\r\n [allowMulti]=\"false\" [siblingFormArray]=\"groupFormArray\" [siblingIndex]=\"i\">\r\n </lib-form-field>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Full-width dashed Add button -->\r\n <button type=\"button\" class=\"btn-add-group\" (click)=\"addGroupInstance()\">\r\n <mat-icon>add</mat-icon> {{ addLabel }} {{ config.sectionConfig!.label }}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- \u2500\u2500 MultiSave: card instances \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 <ng-container *ngIf=\"config.sectionConfig?.multiSaveConfig?.active\">\r\n <div *ngFor=\"let instance of instanceList; trackBy: trackByInstanceId; let i = index\"\r\n class=\"group-instance position-relative mb-16 overflow-hidden\"\r\n [class.is-editing]=\"instance.isEditing\"\r\n [class.is-card]=\"!instance.isEditing\">\r\n\r\n <!-- Edit / new form view -->\r\n <div [hidden]=\"!instance.isEditing\">\r\n <div class=\"group-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.sectionConfig!.children\">\r\n <lib-form-field #groupFieldRef\r\n class=\"sf-col\"\r\n [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"isFieldVisible(field, instance.rowController)\"\r\n [config]=\"field\" [controller]=\"instance.rowController\" [formGroup]=\"instance.fg\"\r\n [allowMulti]=\"false\" [siblingFormArray]=\"groupFormArray\" [siblingIndex]=\"i\">\r\n </lib-form-field>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- Save / Cancel -->\r\n <div class=\"group-footer d-flex justify-content-end align-items-center gap-16 p-0-24\"\r\n *ngIf=\"config.sectionConfig?.multiSaveConfig?.active\">\r\n <span class=\"field-error color-error fs-0-75rem\" *ngIf=\"multiSaveError\">{{ multiSaveError }}</span>\r\n <div class=\"footer-actions d-flex gap-12\">\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 <!-- Card view (saved state) -->\r\n <ng-container *ngIf=\"!instance.isEditing\">\r\n <div class=\"card-view d-flex justify-content-between align-items-center p-18px-24px\"\r\n [class.is-expanded]=\"instance.isExpanded\">\r\n <div class=\"card-content flex-1 d-flex flex-column gap-4 overflow-hidden\">\r\n <span class=\"card-title font-weight-600 overflow-hidden fs-1rem c-111827\">{{\r\n instance.fg.get(config.sectionConfig!.multiSaveConfig!.summaryField || '')?.value\r\n || '\u2014' }}</span>\r\n </div>\r\n <div class=\"card-actions d-flex align-items-center gap-16 ml-20\">\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 </ng-container>\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\"\r\n class=\"group-section-wrapper mb-20px\">\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 <lib-form-field #singleGroupFieldRef\r\n class=\"sf-col\"\r\n [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"isFieldVisible(field)\"\r\n [config]=\"field\" [controller]=\"controller\" [formGroup]=\"groupFormGroup\" [allowMulti]=\"false\">\r\n </lib-form-field>\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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *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 position-relative d-flex align-items-center\">\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\"\r\n class=\"password-toggle position-absolute cursor-pointer d-flex align-items-center justify-content-center b-none border-none c-6B7280 p-0-25rem\"\r\n (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 position-relative d-flex w-100\" [class.readonly]=\"config.readonly\">\r\n <span class=\"input-prefix br-none\" *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 d-flex align-items-center font-weight-500\" *ngIf=\"config.suffix\">{{ config.suffix\r\n }}</span>\r\n\r\n <div class=\"readonly-icons position-absolute d-flex gap-8 pe-none\" *ngIf=\"config.readonly\">\r\n <mat-icon class=\"lock-icon\">lock</mat-icon>\r\n </div>\r\n\r\n <!-- Suffix Action Icons (clickable) -->\r\n <div class=\"suffix-action-icons position-absolute d-flex gap-8 align-items-center\"\r\n *ngIf=\"config.suffixActionIcons?.length && !config.readonly\">\r\n <mat-icon class=\"suffix-action-icon\"\r\n *ngFor=\"let action of config.suffixActionIcons\"\r\n [style.color]=\"action.color || null\"\r\n [title]=\"action.tooltip || ''\"\r\n (click)=\"onSuffixActionClick(action.actionId)\">\r\n {{ action.icon }}\r\n </mat-icon>\r\n </div>\r\n </div>\r\n\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n <div class=\"char-count-hint font-poppins font-weight-400 text-14 text-right c-6B7280\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"input-group position-relative d-flex w-100\" [class.readonly]=\"config.readonly\">\r\n <span class=\"input-prefix br-none\" *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 ?? null\" [max]=\"config.numberConfig?.max ?? null\"\r\n [step]=\"config.numberConfig?.step || 1\" [class.is-invalid]=\"errorMessage\" [readonly]=\"config.readonly\">\r\n\r\n <span class=\"input-suffix d-flex align-items-center font-weight-500\" *ngIf=\"config.suffix\">{{ config.suffix\r\n }}</span>\r\n\r\n <div class=\"readonly-icons position-absolute d-flex gap-8 pe-none\" *ngIf=\"config.readonly\">\r\n <mat-icon class=\"lock-icon\">lock</mat-icon>\r\n </div>\r\n\r\n <!-- Suffix Action Icons (clickable) -->\r\n <div class=\"suffix-action-icons position-absolute d-flex gap-8 align-items-center\"\r\n *ngIf=\"config.suffixActionIcons?.length && !config.readonly\">\r\n <mat-icon class=\"suffix-action-icon\"\r\n *ngFor=\"let action of config.suffixActionIcons\"\r\n [style.color]=\"action.color || null\"\r\n [title]=\"action.tooltip || ''\"\r\n (click)=\"onSuffixActionClick(action.actionId)\">\r\n {{ action.icon }}\r\n </mat-icon>\r\n </div>\r\n </div>\r\n\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"input-group position-relative d-flex w-100\" [class.readonly]=\"config.readonly\">\r\n <input matInput [matDatepicker]=\"datePicker\" class=\"field-input date-input has-icon-right\"\r\n [formControlName]=\"config.name!\" [min]=\"effectiveMinDate\" [max]=\"config.dateConfig?.maxDate\"\r\n [class.is-invalid]=\"errorMessage\" [placeholder]=\"config.placeholder || ''\"\r\n [readonly]=\"config.readonly || config.dateConfig?.inputReadonly\"\r\n (click)=\"!config.readonly && datePicker.open()\">\r\n <div class=\"date-icon-wrapper position-absolute d-flex align-items-center justify-content-center\"\r\n *ngIf=\"!config.readonly\">\r\n <mat-datepicker-toggle matSuffix [for]=\"datePicker\"></mat-datepicker-toggle>\r\n </div>\r\n <mat-datepicker #datePicker [startView]=\"datePickerStartView\"\r\n (opened)=\"onDatePickerOpened(datePicker)\"\r\n (yearSelected)=\"onDateYearSelected($event, datePicker)\"\r\n (monthSelected)=\"onDateMonthSelected($event, datePicker)\">\r\n </mat-datepicker>\r\n\r\n <div class=\"readonly-icons position-absolute d-flex gap-8 pe-none\" *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 c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <!-- DEFAULT variant: existing native input \u2014 unchanged -->\r\n <ng-container *ngIf=\"!isWheelVariant\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"input-group position-relative d-flex w-100\" [class.readonly]=\"config.readonly\">\r\n <input type=\"time\" class=\"field-input time-input\" [formControlName]=\"config.name!\"\r\n [min]=\"effectiveMinTime\" [max]=\"effectiveMaxTime\"\r\n [class.is-invalid]=\"errorMessage\" [readonly]=\"config.readonly || config.timeConfig?.inputReadonly\">\r\n\r\n <div class=\"readonly-icons position-absolute d-flex gap-8 pe-none\" *ngIf=\"config.readonly\">\r\n <mat-icon class=\"lock-icon\">lock</mat-icon>\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- WHEEL variant: drum-roll picker -->\r\n <lib-time-picker\r\n *ngIf=\"isWheelVariant\"\r\n [formControlName]=\"config.name!\"\r\n [label]=\"config.label || ''\"\r\n [placeholder]=\"config.timeConfig?.placeholder || ''\"\r\n [required]=\"config.required || false\"\r\n [disabled]=\"config.readonly || false\"\r\n [mode]=\"config.timeConfig?.mode || '12'\"\r\n [minuteStep]=\"config.timeConfig?.minuteStep || 1\"\r\n [minTime]=\"effectiveMinTime || undefined\"\r\n [maxTime]=\"config.timeConfig?.maxTime\"\r\n [errorMessage]=\"errorMessage\"\r\n [labels]=\"timePickerLabels\">\r\n </lib-time-picker>\r\n\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *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 position-relative d-flex align-items-center w-100\"\r\n [class.is-invalid]=\"errorMessage\" [class.readonly]=\"config.readonly\">\r\n <!-- Search icon -->\r\n <mat-icon class=\"ac-search-icon position-absolute fs-1-1rem c-9CA3AF pe-none\">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\"\r\n class=\"ac-clear-btn position-absolute d-flex align-items-center justify-content-center cursor-pointer rounded-50 b-none border-none c-9CA3AF p-0-2rem\"\r\n *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 <span class=\"ac-option-label\">{{ option.label }}</span>\r\n\r\n <!-- Dynamic display fields (image / email / phone / text) -->\r\n <div class=\"ac-display-fields d-flex flex-wrap gap-6 mt-2\" *ngIf=\"option['displayMeta']?.length\">\r\n <ng-container *ngFor=\"let field of option['displayMeta']\">\r\n\r\n <!-- Image avatar -->\r\n <span *ngIf=\"field.type === 'image' && field.value\" class=\"ac-df-item ac-df-image\">\r\n <img [src]=\"field.value\" [alt]=\"field.label || 'image'\" class=\"ac-df-avatar\">\r\n </span>\r\n\r\n <!-- Email -->\r\n <span *ngIf=\"field.type === 'email' && field.value\" class=\"ac-df-item ac-df-chip\">\r\n <mat-icon class=\"ac-df-icon\">mail_outline</mat-icon>\r\n <span *ngIf=\"field.label\" class=\"ac-df-label\">{{ field.label }}</span>\r\n {{ field.value }}\r\n </span>\r\n\r\n <!-- Phone -->\r\n <span *ngIf=\"field.type === 'phone' && field.value\" class=\"ac-df-item ac-df-chip\" [class]=\"field.className\">\r\n <mat-icon class=\"ac-df-icon\">phone</mat-icon>\r\n <span *ngIf=\"field.label\" class=\"ac-df-label\">{{ field.label }}</span>\r\n {{ field.value }}\r\n </span>\r\n\r\n <!-- Custom / Icon-based / Generic Text -->\r\n <span *ngIf=\"field.type !== 'image' && field.type !== 'email' && field.type !== 'phone' && field.value\" \r\n class=\"ac-df-item\" [class.ac-df-chip]=\"!!field.icon\" [class]=\"field.className\">\r\n <mat-icon class=\"ac-df-icon\" *ngIf=\"field.icon\">{{ field.icon }}</mat-icon>\r\n <span *ngIf=\"field.label\" class=\"ac-df-label\">{{ field.label }}</span>\r\n {{ field.value }}\r\n </span>\r\n\r\n </ng-container>\r\n </div>\r\n </mat-option>\r\n <mat-option *ngIf=\"filteredOptions.length === 0\" disabled class=\"ac-no-results fs-0-8125rem c-6B7280\">\r\n No results found\r\n </mat-option>\r\n </mat-autocomplete>\r\n\r\n <div class=\"readonly-icons position-absolute d-flex gap-8 pe-none\" *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 c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <!-- SINGLE SELECT: native select (default, non-searchable, non-paginated) -->\r\n <div *ngIf=\"config.subType === 'SINGLE' && !isCustomSingleDropdown\" class=\"native-select-wrapper\">\r\n <select class=\"field-input\" [formControlName]=\"config.name!\"\r\n [class.is-invalid]=\"errorMessage\" [disabled]=\"optionsLoading || config.disabled\">\r\n <option [ngValue]=\"null\" disabled selected>{{ optionsLoading ? (controller.labels['LBL_LOADING'] || 'Loading...') : (config.placeholder || selectPlaceholderLabel) }}</option>\r\n <option *ngFor=\"let option of displayLocalOptionList\" [value]=\"option.code\">\r\n {{ option.label }}\r\n </option>\r\n </select>\r\n <div *ngIf=\"optionsLoading\" class=\"native-select-spinner\" aria-hidden=\"true\"></div>\r\n </div>\r\n\r\n <!-- SINGLE SELECT: custom dropdown panel (searchable or paginated) -->\r\n <div *ngIf=\"config.subType === 'SINGLE' && isCustomSingleDropdown\" class=\"single-select-wrapper\"\r\n [class.is-invalid]=\"errorMessage\">\r\n\r\n <div #singleTrigger cdkOverlayOrigin #singleOrigin=\"cdkOverlayOrigin\"\r\n class=\"field-input single-select-trigger d-flex align-items-center justify-content-between cursor-pointer\"\r\n [class.ss-open]=\"isSingleDropdownOpen\"\r\n (click)=\"!optionsLoading && toggleSingleDropdown($event, singleTrigger)\">\r\n <span *ngIf=\"singleSelectedLabel && !optionsLoading\" class=\"single-select-value fs-0-9rem\">\r\n {{ singleSelectedLabel }}\r\n </span>\r\n <span *ngIf=\"!singleSelectedLabel || optionsLoading\" class=\"single-select-placeholder\">\r\n {{ optionsLoading ? (controller.labels['LBL_LOADING'] || 'Loading...') : (config.placeholder || selectPlaceholderLabel) }}\r\n </span>\r\n <div class=\"d-flex align-items-center\">\r\n <div *ngIf=\"optionsLoading\" class=\"mini-spinner me-2\" role=\"progressbar\" aria-label=\"Loading options\"></div>\r\n <mat-icon class=\"single-select-arrow\">\r\n {{ isSingleDropdownOpen ? expandLessLabel : expandMoreLabel }}\r\n </mat-icon>\r\n </div>\r\n </div>\r\n\r\n <ng-template cdkConnectedOverlay\r\n [cdkConnectedOverlayOrigin]=\"singleOrigin\"\r\n [cdkConnectedOverlayOpen]=\"isSingleDropdownOpen\"\r\n [cdkConnectedOverlayPositions]=\"dropdownOverlayPositions\"\r\n [cdkConnectedOverlayScrollStrategy]=\"dropdownScrollStrategy\"\r\n [cdkConnectedOverlayWidth]=\"dropdownOverlayWidth\"\r\n [cdkConnectedOverlayViewportMargin]=\"8\"\r\n [cdkConnectedOverlayPush]=\"true\"\r\n (overlayOutsideClick)=\"onDropdownOverlayOutsideClick($event, singleTrigger)\"\r\n (detach)=\"isSingleDropdownOpen = false\">\r\n <div class=\"single-select-panel\"\r\n (click)=\"$event.stopPropagation()\">\r\n\r\n <!-- Search input (shown only when searchConfig.enabled) -->\r\n <div class=\"dropdown-search-wrapper\" *ngIf=\"isSearchableDropdown\">\r\n <mat-icon class=\"dropdown-search-icon\">search</mat-icon>\r\n <input type=\"text\" class=\"dropdown-search-input\"\r\n [placeholder]=\"searchPlaceholderLabel\"\r\n [(ngModel)]=\"searchText\" [ngModelOptions]=\"{standalone: true}\"\r\n (input)=\"dropdownSearch$.next(searchText)\"\r\n (click)=\"$event.stopPropagation()\">\r\n <mat-icon *ngIf=\"searchText\" class=\"dropdown-search-clear cursor-pointer\"\r\n (click)=\"searchText = ''; dropdownSearch$.next(''); $event.stopPropagation()\">close</mat-icon>\r\n </div>\r\n\r\n <!-- Options list -->\r\n <div class=\"single-select-options-list\" (scroll)=\"onDropdownScroll($event)\">\r\n <div *ngIf=\"optionsLoading\" class=\"dropdown-loading-state\">\r\n <div class=\"dropdown-spinner\"></div>\r\n <span class=\"fs-0-875rem c-6B7280\">{{ controller.labels['LBL_LOADING'] || 'Loading...' }}</span>\r\n </div>\r\n\r\n <ng-container *ngIf=\"!optionsLoading\">\r\n <div *ngFor=\"let option of displayOptionList\"\r\n class=\"single-select-option d-flex align-items-center gap-8 cursor-pointer\"\r\n [class.selected]=\"isSingleOptionSelected(option.code)\"\r\n (click)=\"selectSingleOption(option)\">\r\n <span class=\"fs-0-875rem c-111827\">{{ option.label }}</span>\r\n <mat-icon *ngIf=\"isSingleOptionSelected(option.code)\" class=\"single-select-check\">check</mat-icon>\r\n </div>\r\n <div *ngIf=\"!filteredOptionList?.length && localOptionList?.length\"\r\n class=\"multi-select-empty fs-0-875rem c-6B7280\">\r\n {{ noMatchingOptionsLabel }}\r\n </div>\r\n <div *ngIf=\"!localOptionList?.length\"\r\n class=\"multi-select-empty fs-0-875rem c-6B7280\">\r\n {{ noOptionsAvailableLabel }}\r\n </div>\r\n\r\n <!-- Load More button / Pagination Spinner -->\r\n <div *ngIf=\"config.optionConfig?.pagination?.enabled && ((hasMoreDropdownPages && config.optionConfig?.pagination?.mode !== 'infiniteScroll') || isDropdownLoadingMore)\" class=\"dropdown-pagination-footer\">\r\n <button type=\"button\" class=\"btn-dropdown-load-more\"\r\n *ngIf=\"hasMoreDropdownPages && config.optionConfig?.pagination?.mode !== 'infiniteScroll' && !isDropdownLoadingMore\"\r\n (click)=\"loadMoreDropdownOptions(); $event.stopPropagation()\">\r\n {{ controller.labels['LBL_LOAD_MORE'] || 'Load More' }}\r\n </button>\r\n <div *ngIf=\"isDropdownLoadingMore\" class=\"dropdown-loading-more-state\">\r\n <div class=\"dropdown-spinner-small\"></div>\r\n <span class=\"fs-0-8125rem c-6B7280\">{{ controller.labels['LBL_LOADING_MORE'] || 'Loading more...' }}</span>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </ng-template>\r\n </div>\r\n\r\n <!-- MULTIPLE SELECT: custom panel with checkboxes -->\r\n <div *ngIf=\"config.subType === 'MULTIPLE'\" class=\"multi-select-wrapper\"\r\n [class.is-invalid]=\"errorMessage\">\r\n\r\n <div #multiTrigger cdkOverlayOrigin #multiOrigin=\"cdkOverlayOrigin\"\r\n class=\"field-input multi-select-trigger d-flex align-items-center justify-content-between cursor-pointer\"\r\n [class.ms-open]=\"isMultiDropdownOpen\"\r\n (click)=\"!optionsLoading && toggleMultiDropdown($event, multiTrigger)\">\r\n <span *ngIf=\"multiSelectedCount > 0 && !optionsLoading\" class=\"multi-select-value fs-0-9rem\">\r\n {{ multiSelectedCount }} selected\r\n </span>\r\n <span *ngIf=\"multiSelectedCount === 0 || optionsLoading\" class=\"multi-select-placeholder\">\r\n {{ optionsLoading ? (controller.labels['LBL_LOADING'] || 'Loading...') : (config.placeholder || selectPlaceholderLabel) }}\r\n </span>\r\n <div class=\"d-flex align-items-center\">\r\n <div *ngIf=\"optionsLoading\" class=\"mini-spinner me-2\" role=\"progressbar\" aria-label=\"Loading options\"></div>\r\n <mat-icon class=\"multi-select-arrow\">\r\n {{ isMultiDropdownOpen ? expandLessLabel : expandMoreLabel }}\r\n </mat-icon>\r\n </div>\r\n </div>\r\n\r\n <ng-template cdkConnectedOverlay\r\n [cdkConnectedOverlayOrigin]=\"multiOrigin\"\r\n [cdkConnectedOverlayOpen]=\"isMultiDropdownOpen\"\r\n [cdkConnectedOverlayPositions]=\"dropdownOverlayPositions\"\r\n [cdkConnectedOverlayScrollStrategy]=\"dropdownScrollStrategy\"\r\n [cdkConnectedOverlayWidth]=\"dropdownOverlayWidth\"\r\n [cdkConnectedOverlayViewportMargin]=\"8\"\r\n [cdkConnectedOverlayPush]=\"true\"\r\n (overlayOutsideClick)=\"onDropdownOverlayOutsideClick($event, multiTrigger)\"\r\n (detach)=\"isMultiDropdownOpen = false\">\r\n <div class=\"multi-select-panel\"\r\n (click)=\"$event.stopPropagation()\">\r\n\r\n <!-- Search input (shown only when searchConfig.enabled) -->\r\n <div class=\"dropdown-search-wrapper\" *ngIf=\"isSearchableDropdown\">\r\n <mat-icon class=\"dropdown-search-icon\">search</mat-icon>\r\n <input type=\"text\" class=\"dropdown-search-input\"\r\n [placeholder]=\"searchPlaceholderLabel\"\r\n [(ngModel)]=\"searchText\" [ngModelOptions]=\"{standalone: true}\"\r\n (input)=\"dropdownSearch$.next(searchText)\"\r\n (click)=\"$event.stopPropagation()\">\r\n <mat-icon *ngIf=\"searchText\" class=\"dropdown-search-clear cursor-pointer\"\r\n (click)=\"searchText = ''; dropdownSearch$.next(''); $event.stopPropagation()\">close</mat-icon>\r\n </div>\r\n\r\n <!-- Select All checkbox (shown only when showSelectAll is true) -->\r\n <label *ngIf=\"config.optionConfig?.showSelectAll && filteredOptionList?.length\"\r\n class=\"multi-select-option multi-select-all d-flex align-items-center gap-8 cursor-pointer\">\r\n <input type=\"checkbox\"\r\n [checked]=\"isAllSelected()\"\r\n [indeterminate]=\"isSomeSelected()\"\r\n [disabled]=\"!!config.disabled\"\r\n (change)=\"toggleSelectAll($any($event.target).checked)\">\r\n <span class=\"fs-0-875rem c-111827 fw-600\">{{ selectAllLabel }}</span>\r\n </label>\r\n\r\n <!-- Options list -->\r\n <div class=\"multi-select-options-list\" (scroll)=\"onDropdownScroll($event)\">\r\n <div *ngIf=\"optionsLoading\" class=\"dropdown-loading-state\">\r\n <div class=\"dropdown-spinner\"></div>\r\n <span class=\"fs-0-875rem c-6B7280\">{{ controller.labels['LBL_LOADING'] || 'Loading...' }}</span>\r\n </div>\r\n\r\n <ng-container *ngIf=\"!optionsLoading\">\r\n <label *ngFor=\"let option of filteredOptionList\"\r\n class=\"multi-select-option d-flex align-items-center gap-8 cursor-pointer\">\r\n <input type=\"checkbox\"\r\n [checked]=\"isChecked(option.code)\"\r\n [disabled]=\"!!config.disabled\"\r\n (change)=\"onCheckboxListChange(option.code, $any($event.target).checked)\">\r\n <span class=\"fs-0-875rem c-111827\">{{ option.label }}</span>\r\n </label>\r\n <div *ngIf=\"!filteredOptionList?.length && localOptionList?.length\"\r\n class=\"multi-select-empty fs-0-875rem c-6B7280\">\r\n {{ noMatchingOptionsLabel }}\r\n </div>\r\n <div *ngIf=\"!localOptionList?.length\"\r\n class=\"multi-select-empty fs-0-875rem c-6B7280\">\r\n {{ noOptionsAvailableLabel }}\r\n </div>\r\n\r\n <!-- Load More button / Pagination Spinner -->\r\n <div *ngIf=\"config.optionConfig?.pagination?.enabled && ((hasMoreDropdownPages && config.optionConfig?.pagination?.mode !== 'infiniteScroll') || isDropdownLoadingMore)\" class=\"dropdown-pagination-footer\">\r\n <button type=\"button\" class=\"btn-dropdown-load-more\"\r\n *ngIf=\"hasMoreDropdownPages && config.optionConfig?.pagination?.mode !== 'infiniteScroll' && !isDropdownLoadingMore\"\r\n (click)=\"loadMoreDropdownOptions(); $event.stopPropagation()\">\r\n {{ controller.labels['LBL_LOAD_MORE'] || 'Load More' }}\r\n </button>\r\n <div *ngIf=\"isDropdownLoadingMore\" class=\"dropdown-loading-more-state\">\r\n <div class=\"dropdown-spinner-small\"></div>\r\n <span class=\"fs-0-8125rem c-6B7280\">{{ controller.labels['LBL_LOADING_MORE'] || 'Loading more...' }}</span>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </ng-template>\r\n </div>\r\n\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"radio-group\" [class.is-invalid]=\"errorMessage\"\r\n [class]=\"config.optionConfig?.layout ? 'layout-' + config.optionConfig!.layout.toLowerCase() : ''\">\r\n <label *ngFor=\"let option of localOptionList\" class=\"radio-label\"\r\n [class.card-item]=\"config.subType === 'CARD'\"\r\n [class.selected]=\"formGroup.get(config.name!)?.value === option.code\"\r\n [style.gridColumn]=\"config.optionConfig?.layout?.toUpperCase() === 'COLUMN' ? 'span ' + getOptionColSpan(option) : null\">\r\n <input type=\"radio\" [formControlName]=\"config.name!\" [value]=\"option.code\">\r\n <div class=\"option-content d-flex flex-column gap-4 flex-1 text-left\">\r\n <span class=\"label-text text-16 c-1F2937\">{{ option.label }}</span>\r\n <span class=\"option-hint text-13 color-gray\" *ngIf=\"option.hint\">{{ option.hint }}</span>\r\n </div>\r\n </label>\r\n </div>\r\n\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label && config.subType === 'LIST'\"\r\n class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *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 d-flex align-items-center gap-8 cursor-pointer fs-0-875rem c-111827\">\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' || config.subType === 'CARD'\" class=\"checkbox-group d-flex flex-column gap-8\"\r\n [class.is-invalid]=\"errorMessage\"\r\n [class]=\"config.optionConfig?.layout ? 'layout-' + config.optionConfig!.layout.toLowerCase() : ''\">\r\n <label *ngFor=\"let option of localOptionList\"\r\n class=\"checkbox-label d-flex align-items-center gap-8 cursor-pointer fs-0-875rem c-111827\"\r\n [class.card-item]=\"config.subType === 'CARD'\" [class.selected]=\"isChecked(option.code)\"\r\n [style.gridColumn]=\"config.optionConfig?.layout?.toUpperCase() === 'COLUMN' ? 'span ' + getOptionColSpan(option) : null\">\r\n <input type=\"checkbox\" [checked]=\"isChecked(option.code)\" [disabled]=\"!!config.disabled\"\r\n (change)=\"onCheckboxListChange(option.code, $any($event.target).checked)\">\r\n <div class=\"option-content d-flex flex-column gap-4 flex-1 text-left\">\r\n <span class=\"label-text text-16 c-1F2937\">{{ option.label }}</span>\r\n <span class=\"option-hint text-13 color-gray\" *ngIf=\"option.hint\">{{ option.hint }}</span>\r\n </div>\r\n </label>\r\n </div>\r\n\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"chip-group d-flex flex-wrap gap-8\" [class.is-invalid]=\"errorMessage\">\r\n <label *ngFor=\"let option of localOptionList\"\r\n class=\"chip-label cursor-pointer p-6px-14px b-ffffff c-374151 b-1px-solid-D1D5DB br-20px fs-0-875rem\"\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 c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label class=\"switch-container d-flex justify-content-between align-items-center cursor-pointer\">\r\n <span class=\"field-label d-block font-poppins c-202124 fs-18px\">{{ config.label }}</span>\r\n <div class=\"switch position-relative\">\r\n <input type=\"checkbox\" [formControlName]=\"config.name!\" [class.is-invalid]=\"errorMessage\">\r\n <span class=\"slider position-absolute cursor-pointer\"></span>\r\n </div>\r\n </label>\r\n\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *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 d-block w-100\"\r\n [placeholder]=\"config.richTextConfig?.placeholder || config.placeholder || ''\"\r\n [styles]=\"{height: config.richTextConfig?.height || '200px'}\"\r\n [modules]=\"richTextModules\">\r\n </quill-editor>\r\n </div>\r\n\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n <div class=\"char-count-hint font-poppins font-weight-400 text-14 text-right c-6B7280\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <div class=\"rating-group d-flex gap-4\" [class.is-invalid]=\"errorMessage\">\r\n <span *ngFor=\"let star of getStarArray()\" class=\"star d-inline-flex align-items-center cursor-pointer\"\r\n [class.filled]=\"isStarFilled(star)\" [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 c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *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 d-flex flex-column gap-6\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">{{ config.label\r\n }}</label>\r\n <div class=\"generated-value fs-0-875rem p-0-625rem-0-875rem b-F3F4F6 b-1px-solid-E5E7EB br-8px c-6B7280\">{{ value ||\r\n '-' }}</div>\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *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 d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <!-- Drop Zone -->\r\n <div\r\n class=\"upload-drop-zone d-flex flex-column align-items-center justify-content-center gap-8 cursor-pointer text-center p-32px-24px us-none\"\r\n [class.drag-over]=\"isDragOver\" [class.has-files]=\"value?.length\" [class.is-invalid]=\"errorMessage\"\r\n (dragover)=\"onDragOver($event)\" (dragleave)=\"onDragLeave($event)\" (drop)=\"onFileDrop($event)\"\r\n (click)=\"fileInput.click()\">\r\n\r\n <!-- Icon with accent-colour pill background -->\r\n <div class=\"upload-icon-wrap mb-4\">\r\n <div class=\"dropzone-icon-pill d-flex align-items-center justify-content-center\">\r\n <mat-icon class=\"upload-cloud-icon\">cloud_upload</mat-icon>\r\n </div>\r\n </div>\r\n\r\n <p class=\"upload-main-text font-weight-600 m-0 fs-0-9rem\">Drag & drop files here</p>\r\n <p class=\"upload-sub-text m-0 fs-0-8rem c-64748B\">or <span class=\"upload-link\">Browse files</span></p>\r\n <p class=\"upload-hint-text m-0 fs-0-78rem c-64748B\" *ngIf=\"config.attachmentConfig?.acceptLabel\">\r\n Supported: <span class=\"upload-formats font-weight-500\">{{ config.attachmentConfig!.acceptLabel }}</span>\r\n </p>\r\n <p class=\"upload-hint-text m-0 fs-0-78rem c-64748B\" *ngIf=\"!config.attachmentConfig?.acceptLabel && config.hint\">\r\n {{ config.hint }}\r\n </p>\r\n <span class=\"upload-size-badge fs-0-72rem\" *ngIf=\"config.attachmentConfig?.maxSizeMB\">\r\n Max {{ config.attachmentConfig!.maxSizeMB }}MB\r\n </span>\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 d-flex flex-column gap-8 mt-10\" *ngIf=\"value?.length\">\r\n <div *ngFor=\"let f of value; let i = index\"\r\n class=\"uploaded-item d-flex align-items-center gap-10 p-10px-14px br-8px\"\r\n [class.uploading]=\"f.isUploading\">\r\n\r\n <!-- Uploading spinner -->\r\n <ng-container *ngIf=\"f.isUploading; else fileReady\">\r\n <div class=\"upload-spinner rounded-50 b-2px-solid-E2E8F0\"></div>\r\n <div class=\"file-info flex-1 d-flex flex-column\">\r\n <span class=\"file-name font-weight-500 overflow-hidden fs-0-85rem\" [title]=\"f.name\">{{ f.name }}</span>\r\n <span class=\"file-size uploading-label fs-0-72rem\">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 <mat-icon class=\"file-type-icon\">{{ getFileIcon(f.type) }}</mat-icon>\r\n <img *ngIf=\"f.type?.startsWith('image') && f.dataUrl\" [src]=\"f.dataUrl\" class=\"file-thumb rounded-4\"\r\n alt=\"preview\">\r\n <div class=\"file-info flex-1 d-flex flex-column\">\r\n <span class=\"file-name font-weight-500 overflow-hidden fs-0-85rem\" [title]=\"f.name\">{{ f.name }}</span>\r\n <span class=\"file-size fs-0-72rem\">{{ formatFileSize(f.size) }}</span>\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Compact icon-only remove button -->\r\n <button type=\"button\" class=\"file-remove-btn d-flex align-items-center justify-content-center rounded-50\"\r\n [disabled]=\"f.isUploading\" (click)=\"!f.isUploading && removeUploadedFile(i)\"\r\n aria-label=\"Remove file\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Validation / file errors -->\r\n <span class=\"field-error color-error fs-0-75rem\" *ngIf=\"fileUploadError\">{{ fileUploadError }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *ngIf=\"errorMessage && !fileUploadError\">{{ errorMessage }}</span>\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\"\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 d-flex flex-column gap-6 p-0 border-none b-none\"\r\n [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 d-grid align-items-start\">\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 d-flex flex-column gap-20\">\r\n\r\n <!-- Header: title + max-items badge -->\r\n <div class=\"mu-header d-flex align-items-start flex-wrap gap-10\">\r\n <h3 class=\"mu-title m-0 c-111827 fs-1-35rem\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </h3>\r\n <span\r\n class=\"mu-badge d-inline-flex align-items-center rounded-20 color-white font-weight-600 fs-0-72rem p-4px-12px b-111827\"\r\n *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 m-0 fs-0-875rem c-6B7280\" *ngIf=\"config.attachmentConfig?.description\">\r\n {{ config.attachmentConfig?.description }}\r\n </p>\r\n <p class=\"mu-description m-0 fs-0-875rem c-6B7280\"\r\n *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 m-0 p-0 d-flex flex-column gap-8 ls-none\"\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\"\r\n class=\"mu-feature-item d-flex align-items-center gap-8 fs-0-875rem c-374151\">\r\n <mat-icon class=\"mu-check text-16 c-3B82F6\">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']\"\r\n class=\"mu-feature-item d-flex align-items-center gap-8 fs-0-875rem c-374151\">\r\n <mat-icon class=\"mu-check text-16 c-3B82F6\">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_1'] }}\r\n </li>\r\n <li *ngIf=\"controller.labels['LBL_MEDIA_FEATURE_2']\"\r\n class=\"mu-feature-item d-flex align-items-center gap-8 fs-0-875rem c-374151\">\r\n <mat-icon class=\"mu-check text-16 c-3B82F6\">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_2'] }}\r\n </li>\r\n <li *ngIf=\"controller.labels['LBL_MEDIA_FEATURE_3']\"\r\n class=\"mu-feature-item d-flex align-items-center gap-8 fs-0-875rem c-374151\">\r\n <mat-icon class=\"mu-check text-16 c-3B82F6\">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 position-relative\" (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 fs-18px\">add</mat-icon>\r\n </lib-button>\r\n\r\n <div class=\"media-dropdown position-absolute rounded-12 overflow-hidden b-ffffff b-1px-solid-E5E7EB\"\r\n *ngIf=\"showMediaMenu\" role=\"menu\" (click)=\"$event.stopPropagation()\">\r\n <!-- Video -->\r\n <button id=\"btn-media-video-{{ config.name }}\" type=\"button\"\r\n class=\"media-dropdown-item d-flex align-items-center gap-12 w-100 cursor-pointer text-left b-none border-none p-12px-16px bb-1px-solid-F3F4F6\"\r\n (click)=\"onMediaMenuVideo(); showMediaMenu = false\" role=\"menuitem\">\r\n <span\r\n class=\"media-drop-icon media-drop-icon--video d-flex align-items-center justify-content-center rounded-8\"><mat-icon>videocam</mat-icon></span>\r\n <span class=\"media-drop-text d-flex flex-column flex-1\">\r\n <span class=\"media-drop-label font-weight-600 fs-0-875rem c-111827\">{{\r\n controller.labels['LBL_MEDIA_VIDEO'] || 'Video' }}</span>\r\n <span class=\"media-drop-desc c-6B7280 fs-0-75rem\">{{ controller.labels['LBL_MEDIA_VIDEO_DESC'] || 'Add\r\n 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\"\r\n class=\"media-dropdown-item d-flex align-items-center gap-12 w-100 cursor-pointer text-left b-none border-none p-12px-16px bb-1px-solid-F3F4F6\"\r\n (click)=\"onMediaMenuDevice(); showMediaMenu = false\" role=\"menuitem\">\r\n <span\r\n class=\"media-drop-icon media-drop-icon--device d-flex align-items-center justify-content-center rounded-8\"><mat-icon>upload</mat-icon></span>\r\n <span class=\"media-drop-text d-flex flex-column flex-1\">\r\n <span class=\"media-drop-label font-weight-600 fs-0-875rem c-111827\">{{\r\n controller.labels['LBL_MEDIA_DEVICE'] || 'Upload from device'\r\n }}</span>\r\n <span class=\"media-drop-desc c-6B7280 fs-0-75rem\">{{ controller.labels['LBL_MEDIA_DEVICE_DESC'] ||\r\n '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\"\r\n class=\"media-dropdown-item d-flex align-items-center gap-12 w-100 cursor-pointer text-left b-none border-none p-12px-16px bb-1px-solid-F3F4F6\"\r\n (click)=\"onMediaMenuLibrary(); showMediaMenu = false\" role=\"menuitem\">\r\n <span\r\n class=\"media-drop-icon media-drop-icon--library d-flex align-items-center justify-content-center rounded-8\"><mat-icon>photo_library</mat-icon></span>\r\n <span class=\"media-drop-text d-flex flex-column flex-1\">\r\n <span class=\"media-drop-label font-weight-600 fs-0-875rem c-111827\">{{\r\n controller.labels['LBL_MEDIA_LIBRARY'] || 'Upload from library'\r\n }}</span>\r\n <span class=\"media-drop-desc c-6B7280 fs-0-75rem\">{{ controller.labels['LBL_MEDIA_LIBRARY_DESC'] ||\r\n '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 d-flex flex-column gap-8 p-16 rounded-10 b-FFFAF1 b-1px-solid-E5E7EB\"\r\n *ngIf=\"showYoutubeInput\">\r\n <label class=\"youtube-panel-label d-flex align-items-center gap-6 font-weight-600 fs-0-875rem c-111827\">\r\n {{ controller.labels['LBL_YOUTUBE_URL'] || 'Video URL' }}\r\n </label>\r\n <div class=\"youtube-input-row d-flex gap-8\">\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 color-error fs-0-75rem\" *ngIf=\"youtubeUrlError\">{{ youtubeUrlError }}</span>\r\n </div>\r\n\r\n <div\r\n class=\"media-upload-status d-flex align-items-center gap-8 mt-4 color-error rounded-8 font-weight-500 p-10px-14px b-FEF2F2 fs-0-85rem\"\r\n *ngIf=\"mediaUploadError\">\r\n <mat-icon class=\"status-icon fs-18px\">error_outline</mat-icon>\r\n <span>{{ mediaUploadError }}</span>\r\n </div>\r\n\r\n <span class=\"field-error color-error fs-0-75rem\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n <span class=\"field-hint c-6B7280 fs-0-75rem\" *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 d-flex flex-column gap-12\">\r\n\r\n <!-- Carousel (when items exist) -->\r\n <div class=\"media-carousel-section d-flex flex-column gap-12\" *ngIf=\"mediaItems.length\">\r\n <div\r\n class=\"media-carousel-main position-relative w-100 overflow-hidden d-flex align-items-center justify-content-center br-12px b-0F172A\">\r\n <button id=\"btn-carousel-prev-{{ config.name }}\" type=\"button\"\r\n class=\"carousel-nav carousel-nav--prev position-absolute rounded-50 cursor-pointer d-flex align-items-center justify-content-center border-none b-rgba-255-255-255-0-85\"\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 position-absolute d-flex align-items-center justify-content-center\"\r\n *ngFor=\"let item of mediaItems; let i = index\" [hidden]=\"i !== mediaCarouselIndex\">\r\n <div *ngIf=\"item.isUploading\"\r\n class=\"carousel-uploading d-flex flex-column align-items-center gap-12 c-94A3B8 fs-0-85rem\">\r\n <div class=\"carousel-spinner rounded-50 b-3px-solid-rgba-255-255-255-0-15\"></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 w-100 h-100 br-12px\" [src]=\"item.url | trustedUrl\" frameborder=\"0\"\r\n 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 w-100 h-100 br-12px\" [src]=\"item.url\" alt=\"Media\">\r\n </ng-container>\r\n <button id=\"btn-remove-media-{{ config.name }}-{{ i }}\" type=\"button\"\r\n class=\"carousel-remove-btn position-absolute rounded-50 cursor-pointer d-flex align-items-center justify-content-center border-none b-rgba-0-0-0-0-55\"\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\"\r\n class=\"carousel-nav carousel-nav--next position-absolute rounded-50 cursor-pointer d-flex align-items-center justify-content-center border-none b-rgba-255-255-255-0-85\"\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 position-absolute d-flex gap-6\">\r\n <span *ngFor=\"let item of mediaItems; let i = index\"\r\n class=\"carousel-dot rounded-50 cursor-pointer b-rgba-255-255-255-0-45\"\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 d-flex flex-wrap gap-8 pb-4px\">\r\n <div *ngFor=\"let item of mediaThumbnails; let i = index\"\r\n class=\"media-thumb rounded-8 overflow-hidden cursor-pointer d-flex align-items-center justify-content-center b-2px-solid-transparent b-E2E8F0\"\r\n [class.active]=\"i === mediaCarouselIndex\" (click)=\"mediaGoTo(i)\">\r\n <div *ngIf=\"item.isUploading\"\r\n class=\"thumb-uploading d-flex align-items-center justify-content-center w-100 h-100\">\r\n <div class=\"thumb-spinner rounded-50 b-2px-solid-E2E8F0\"></div>\r\n </div>\r\n <img *ngIf=\"!item.isUploading && item.mediaType === 'youtube' && item.thumbnailUrl\"\r\n [src]=\"item.thumbnailUrl\" class=\"thumb-img w-100 h-100\" alt=\"Video thumbnail\">\r\n <div *ngIf=\"!item.isUploading && item.mediaType === 'youtube' && !item.thumbnailUrl\"\r\n class=\"thumb-yt-placeholder d-flex align-items-center justify-content-center w-100 h-100 b-1E293B c-EF4444\">\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 w-100 h-100\" alt=\"Image thumbnail\">\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Empty right-side placeholder -->\r\n <div\r\n class=\"mu-right-empty d-flex flex-column align-items-center justify-content-center gap-10 h-100 text-center p-24 br-12px b-FFFAF1 c-94A3B8 b-2px-dashed-CBD5E1\"\r\n *ngIf=\"!mediaItems.length\" (click)=\"onMediaMenuDevice()\">\r\n <mat-icon class=\"mu-right-empty-icon fs-52px\">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 <!-- Wrapper is always in DOM (hidden) so @ViewChild can move it to body -->\r\n <div #libraryModal class=\"media-library-portal-host\" [class.is-open]=\"showLibraryModal\">\r\n\r\n <!-- Backdrop -->\r\n <div class=\"media-library-overlay\" (click)=\"closeLibraryModal()\"></div>\r\n\r\n <!-- Modal card -->\r\n <div class=\"media-library-modal d-flex flex-column overflow-hidden b-ffffff br-16px\"\r\n role=\"dialog\" aria-modal=\"true\">\r\n <div class=\"library-modal-header d-flex align-items-start justify-content-between p-24px-28px bb-1px-solid-E5E7EB\">\r\n <div class=\"header-left d-flex flex-column gap-8\">\r\n <h3 class=\"library-modal-title m-0 color-dark fs-1-25rem\">\r\n {{ controller.labels['LBL_ADD_IMAGES'] || 'Add Images' }}\r\n </h3>\r\n <p class=\"library-modal-subtitle m-0 color-gray fs-0-85rem\">\r\n {{ controller.labels['LBL_LIBRARY_MODAL_DESC'] || 'Select images from your library.' }}\r\n </p>\r\n </div>\r\n <button id=\"btn-close-library-{{ config.name }}\" type=\"button\"\r\n class=\"library-close-btn d-flex align-items-center justify-content-center cursor-pointer rounded-50 border-none b-none c-9CA3AF\"\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 rounded-50 b-3px-solid-E2E8F0\"></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 d-flex align-items-center gap-8 color-error b-FEF2F2 fs-0-875rem p-16px-24px\"\r\n *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 d-grid gap-16 flex-1 p-28px b-F9FAFB\" *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 position-relative rounded-12 overflow-hidden cursor-pointer bg-white b-3px-solid-transparent\"\r\n [class.selected]=\"isLibraryItemSelected(img)\" (click)=\"toggleLibraryItem(img)\">\r\n <img [src]=\"getLibraryItemUrl(img)\" class=\"library-grid-img w-100 h-100 d-block\" alt=\"Library image\">\r\n <div\r\n class=\"library-check position-absolute bg-white rounded-50 d-flex align-items-center justify-content-center c-3B82F6\"\r\n *ngIf=\"isLibraryItemSelected(img)\">\r\n <mat-icon>check_circle</mat-icon>\r\n </div>\r\n <div class=\"library-overlay-hover position-absolute b-rgba-59-130-246-0-12\"></div>\r\n </div>\r\n </div>\r\n\r\n <!-- Empty library -->\r\n <div\r\n class=\"library-empty d-flex flex-column align-items-center justify-content-center gap-12 flex-1 c-9CA3AF fs-0-875rem p-48px-24px\"\r\n *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 d-flex align-items-center justify-content-end bg-white p-20px-28px bt-1px-solid-E5E7EB\">\r\n <div class=\"library-footer-actions d-flex gap-12\">\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_CONTINUE'] || 'Continue' }}\r\n </lib-button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n\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 d-flex flex-column gap-6 gap-12\"\r\n [formGroup]=\"formGroup\">\r\n\r\n <!-- Field label -->\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n <p class=\"location-subtitle m-0 c-6B7280 fs-0-8125rem\" *ngIf=\"config.hint\">{{ config.hint }}</p>\r\n\r\n <!-- Three-tab bar -->\r\n <div class=\"location-tabs d-flex gap-12 mb-24\">\r\n <lib-button class=\"loc-tab-btn flex-1\" [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 flex-1\" [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 flex-1\" [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 d-flex flex-column gap-12\">\r\n\r\n <p class=\"loc-section-label m-0 font-weight-600 c-111827 fs-0-9rem\">\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 d-flex flex-column gap-8\" *ngIf=\"locationVenues.length > 0\">\r\n <div *ngFor=\"let venue of locationVenues; let i = index\"\r\n class=\"loc-venue-item d-flex align-items-center gap-10 p-10px-14px br-7px b-ffffff b-1px-solid-D1D5DB\">\r\n <mat-icon class=\"loc-venue-search-icon fs-18px c-9CA3AF\">search</mat-icon>\r\n <span class=\"loc-venue-text flex-1 overflow-hidden fs-0-875rem c-111827\">{{ venue.address || venue.name ||\r\n venue.description }}</span>\r\n <button type=\"button\"\r\n class=\"loc-action-btn loc-delete-btn d-flex align-items-center justify-content-center cursor-pointer rounded-50 b-none border-none p-4px\"\r\n (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 m-0 font-weight-600 fs-0-8125rem c-3B82F6\"\r\n *ngIf=\"locationVenues.length > 0 && config.locationConfig?.allowMulti\">\r\n {{ locationVenues.length }} {{ 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 position-relative\" *ngIf=\"!locationMaxReached\">\r\n <div class=\"loc-search-wrapper position-relative d-flex align-items-center mt-4\">\r\n <mat-icon class=\"loc-search-icon position-absolute fs-1-1rem c-9CA3AF pe-none\">search</mat-icon>\r\n <input\r\n class=\"field-input loc-search-input w-100 font-poppins flex-1 fs-0-875rem c-111827 br-7px br-8px bc-F3F4F6 pl-2-4rem bc-DC2626 pt-0-625rem pb-0-625rem pl-16px pr-16px bc-ffffff b-1px-solid-D1D5DB pr-3-5rem\"\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 position-absolute overflow-hidden br-8px b-ffffff b-1px-solid-D1D5DB\"\r\n *ngIf=\"locationShowSuggestions && locationSuggestions.length\">\r\n <div *ngFor=\"let sug of locationSuggestions\"\r\n class=\"loc-suggestion-item d-flex align-items-center gap-10 cursor-pointer p-10px-14px\"\r\n (mousedown)=\"onLocationSuggestionSelect(sug)\">\r\n <mat-icon class=\"loc-suggestion-icon fs-18px c-E53E3E\">place</mat-icon>\r\n <span class=\"loc-suggestion-text overflow-hidden fs-0-875rem c-374151\">{{ sug.description }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Add another button -->\r\n <button type=\"button\"\r\n class=\"loc-add-btn d-inline-flex align-items-center gap-6 cursor-pointer font-weight-600 p-0 b-none border-none fs-0-875rem c-1A56DB\"\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 overflow-hidden br-10px b-1px-solid-E5E7EB\"\r\n *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 w-100 d-block border-none\"\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 w-100 d-block border-none\"\r\n [style.height]=\"config.locationConfig?.mapHeight || '300px'\" [src]=\"getLocationMapEmbedUrl() | trustedUrl\"\r\n 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 m-0 text-center fs-0-78rem c-6B7280\">\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 d-flex flex-column gap-12\">\r\n <p class=\"loc-section-label m-0 font-weight-600 c-111827 fs-0-9rem\">\r\n {{ controller.labels['LBL_LOC_ONLINE_URL'] || 'Event URL' }}\r\n </p>\r\n <div class=\"loc-search-wrapper position-relative d-flex align-items-center mt-4\">\r\n <mat-icon class=\"loc-search-icon position-absolute fs-1-1rem c-9CA3AF pe-none\">link</mat-icon>\r\n <input\r\n class=\"field-input loc-search-input w-100 font-poppins flex-1 fs-0-875rem c-111827 br-7px br-8px bc-F3F4F6 pl-2-4rem bc-DC2626 pt-0-625rem pb-0-625rem pl-16px pr-16px bc-ffffff b-1px-solid-D1D5DB pr-3-5rem\"\r\n 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'\"\r\n class=\"loc-panel loc-tba-panel d-flex flex-column gap-12 justify-content-center\">\r\n <div\r\n class=\"loc-tba-content d-flex flex-column align-items-center justify-content-center text-center gap-12 p-32px-24px b-F9FAFB b-1px-dashed-D1D5DB br-10px\">\r\n <mat-icon class=\"loc-tba-icon fs-40px c-9CA3AF\">schedule</mat-icon>\r\n <p class=\"loc-tba-text m-0 c-6B7280 fs-0-9rem\">\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 color-error fs-0-75rem\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n <!-- \u2550\u2550 Link List 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 -->\r\n <div *ngIf=\"isLinkList\" class=\"field-wrapper d-flex flex-column gap-6\" [formGroup]=\"formGroup\">\r\n <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px\">\r\n {{ controller.labels[config.label] || config.label }}\r\n <span class=\"required c-DC2626 ml-0-125rem\" *ngIf=\"config.required\">*</span>\r\n </label>\r\n\r\n <!-- Top Links list (if listPosition === 'top') -->\r\n <div *ngIf=\"linkListConfig?.listPosition === 'top' && linkListItems.length > 0\" class=\"link-list-container mb-8px\">\r\n <ul class=\"link-items-list p-0 m-0\" \r\n [style.display]=\"'grid'\"\r\n [style.gridTemplateColumns]=\"'repeat(12, 1fr)'\"\r\n [style.gap]=\"'12px 16px'\">\r\n <li *ngFor=\"let item of linkListItems; let idx = index\" \r\n class=\"link-list-item\"\r\n [style.gridColumn]=\"'span ' + (linkListConfig?.colSpan || 12)\">\r\n <!-- Normal display mode -->\r\n <ng-container *ngIf=\"editingLinkIndex !== idx\">\r\n <span class=\"link-url-text text-truncate font-weight-500 color-333\">{{ getLinkDisplayUrl(item) }}</span>\r\n <div class=\"link-item-actions d-flex align-items-center gap-12px\">\r\n <button type=\"button\" class=\"action-btn edit-btn\" \r\n *ngIf=\"linkListConfig?.editable !== false\" (click)=\"startEditLink(idx)\" [title]=\"controller.labels['COMMON.ACTIONS.EDIT'] || 'Edit'\">\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n <button type=\"button\" class=\"action-btn delete-btn\" \r\n *ngIf=\"linkListConfig?.deleteable !== false\" (click)=\"deleteLinkItem(idx)\" [title]=\"controller.labels['COMMON.ACTIONS.DELETE'] || 'Delete'\">\r\n <mat-icon>delete_outline</mat-icon>\r\n </button>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Inline Edit mode -->\r\n <ng-container *ngIf=\"editingLinkIndex === idx\">\r\n <div class=\"edit-input-wrapper d-flex align-items-center gap-8px w-100\">\r\n <input type=\"text\" class=\"field-input flex-1 py-4px\" [(ngModel)]=\"editingLinkValue\" \r\n [ngModelOptions]=\"{standalone: true}\" (keydown.enter)=\"saveEditLink(idx)\" (keydown.escape)=\"cancelEditLink()\"\r\n (input)=\"linkValidationError = ''\">\r\n <button type=\"button\" class=\"action-btn save-btn\" (click)=\"saveEditLink(idx)\" [title]=\"controller.labels['COMMON.ACTIONS.SAVE'] || 'Save'\">\r\n <mat-icon>check</mat-icon>\r\n </button>\r\n <button type=\"button\" class=\"action-btn cancel-btn\" (click)=\"cancelEditLink()\" [title]=\"controller.labels['COMMON.ACTIONS.CANCEL'] || 'Cancel'\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </ng-container>\r\n </li>\r\n </ul>\r\n </div>\r\n\r\n <!-- Input text + plus icon -->\r\n <div class=\"input-group position-relative d-flex w-100\">\r\n <input type=\"text\" class=\"field-input pe-40px\" [placeholder]=\"controller.labels[config.placeholder!] || config.placeholder || controller.labels['COMMON.ENTER_LINK'] || 'Enter link...'\"\r\n [(ngModel)]=\"newLinkInputValue\" [ngModelOptions]=\"{standalone: true}\" (keydown.enter)=\"addLinkItem($event)\"\r\n [disabled]=\"config.readonly || config.disabled\"\r\n (input)=\"linkValidationError = ''\">\r\n \r\n <button type=\"button\" class=\"position-absolute right-8px top-50-percent translate-y-minus-50 d-flex align-items-center justify-content-center b-none bg-none p-0 cursor-pointer color-primary\"\r\n [disabled]=\"config.readonly || config.disabled || !newLinkInputValue?.trim()\"\r\n (click)=\"addLinkItem()\" style=\"right: 8px; top: 50%; transform: translateY(-50%); z-index: 10;\">\r\n <mat-icon>add</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Validation error message -->\r\n <span class=\"field-error color-error fs-0-75rem mt-4px d-block\" *ngIf=\"linkValidationError\">{{ linkValidationError }}</span>\r\n\r\n <!-- Bottom Links list (if listPosition !== 'top') -->\r\n <div *ngIf=\"linkListConfig?.listPosition !== 'top' && linkListItems.length > 0\" class=\"link-list-container mt-8px\">\r\n <ul class=\"link-items-list p-0 m-0\" \r\n [style.display]=\"'grid'\"\r\n [style.gridTemplateColumns]=\"'repeat(12, 1fr)'\"\r\n [style.gap]=\"'12px 16px'\">\r\n <li *ngFor=\"let item of linkListItems; let idx = index\" \r\n class=\"link-list-item\"\r\n [style.gridColumn]=\"'span ' + (linkListConfig?.colSpan || 12)\">\r\n <!-- Normal display mode -->\r\n <ng-container *ngIf=\"editingLinkIndex !== idx\">\r\n <span class=\"link-url-text text-truncate font-weight-500 color-333\">{{ getLinkDisplayUrl(item) }}</span>\r\n <div class=\"link-item-actions d-flex align-items-center gap-12px\">\r\n <button type=\"button\" class=\"action-btn edit-btn\" \r\n *ngIf=\"linkListConfig?.editable !== false\" (click)=\"startEditLink(idx)\" [title]=\"controller.labels['COMMON.ACTIONS.EDIT'] || 'Edit'\">\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n <button type=\"button\" class=\"action-btn delete-btn\" \r\n *ngIf=\"linkListConfig?.deleteable !== false\" (click)=\"deleteLinkItem(idx)\" [title]=\"controller.labels['COMMON.ACTIONS.DELETE'] || 'Delete'\">\r\n <mat-icon>delete_outline</mat-icon>\r\n </button>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Inline Edit mode -->\r\n <ng-container *ngIf=\"editingLinkIndex === idx\">\r\n <div class=\"edit-input-wrapper d-flex align-items-center gap-8px w-100\">\r\n <input type=\"text\" class=\"field-input flex-1 py-4px\" [(ngModel)]=\"editingLinkValue\" \r\n [ngModelOptions]=\"{standalone: true}\" (keydown.enter)=\"saveEditLink(idx)\" (keydown.escape)=\"cancelEditLink()\"\r\n (input)=\"linkValidationError = ''\">\r\n <button type=\"button\" class=\"action-btn save-btn\" (click)=\"saveEditLink(idx)\" [title]=\"controller.labels['COMMON.ACTIONS.SAVE'] || 'Save'\">\r\n <mat-icon>check</mat-icon>\r\n </button>\r\n <button type=\"button\" class=\"action-btn cancel-btn\" (click)=\"cancelEditLink()\" [title]=\"controller.labels['COMMON.ACTIONS.CANCEL'] || 'Cancel'\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </ng-container>\r\n </li>\r\n </ul>\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-hint c-6B7280 fs-0-75rem\" *ngIf=\"config.hint && !errorMessage\">{{ controller.labels[config.hint] || config.hint }}</span>\r\n <span class=\"field-error color-error fs-0-75rem\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\r\n </div>\r\n\r\n</div>", styles: [".d-flex{display:flex}.d-inline-flex{display:inline-flex}.d-grid{display:grid}.d-block{display:block}.d-none{display:none}.flex-column{flex-direction:column}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-wrap{flex-wrap:wrap}.flex-1{flex:1}.align-items-center{align-items:center}.align-items-start{align-items:flex-start}.align-items-end{align-items:flex-end}.justify-content-center{justify-content:center}.justify-content-between{justify-content:space-between}.justify-content-start{justify-content:flex-start}.justify-content-end{justify-content:flex-end}.grid-cols-12{grid-template-columns:repeat(12,1fr)}.w-100{width:100%}.h-100{height:100%}.position-relative{position:relative}.position-absolute{position:absolute}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-poppins{font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif)}.font-weight-400{font-weight:400}.font-weight-500{font-weight:500}.font-weight-600{font-weight:600}.text-13{font-size:13px}.text-14{font-size:14px}.text-16{font-size:16px}.color-white{color:#fff}.color-dark{color:#111827}.color-gray{color:#6b7280}.color-error{color:var(--cc-sf-error-text-color, #DC2626)}.bg-white{background-color:#fff}.bg-transparent{background-color:transparent}.m-0{margin:0}.mt-4{margin-top:4px}.mt-8{margin-top:8px}.mt-10{margin-top:10px}.mt-12{margin-top:12px}.mt-16{margin-top:16px}.mt-20{margin-top:20px}.mt-24{margin-top:24px}.mb-0{margin-bottom:0}.mb-4{margin-bottom:4px}.mb-8{margin-bottom:8px}.mb-10{margin-bottom:10px}.mb-12{margin-bottom:12px}.mb-16{margin-bottom:16px}.mb-20{margin-bottom:20px}.mb-24{margin-bottom:24px}.ml-16{margin-left:16px}.ml-20{margin-left:20px}.p-0{padding:0}.p-16{padding:16px}.p-20{padding:20px}.p-24{padding:24px}.pt-20{padding-top:20px}.pb-10{padding-bottom:10px}.gap-4{gap:4px}.gap-6{gap:6px}.gap-8{gap:8px}.gap-10{gap:10px}.gap-12{gap:12px}.gap-16{gap:16px}.gap-20{gap:20px}.rounded-4{border-radius:4px}.rounded-6{border-radius:6px}.rounded-8{border-radius:8px}.rounded-10{border-radius:10px}.rounded-12{border-radius:12px}.rounded-20{border-radius:20px}.rounded-24{border-radius:24px}.rounded-50{border-radius:50%}.cursor-pointer{cursor:pointer}.overflow-hidden{overflow:hidden}.resize-vertical{resize:vertical}.box-sizing-border{box-sizing:border-box}.border-none{border:none!important}.mb-8px{margin-bottom:8px!important}.mb-16px{margin-bottom:var(--cc-sf-grid-gap, 16px)!important}.c-DC2626{color:var(--cc-sf-label-required-color, #DC2626)!important}.ml-0-125rem{margin-left:.125rem!important}.fs-0-875rem{font-size:.875rem!important}.c-111827{color:var(--cc-sf-label-color, #111827)!important}.br-7px{border-radius:var(--cc-sf-input-radius, 7px)!important}.c-6B7280{color:var(--cc-sf-hint-color, #6B7280)!important}.fs-0-75rem{font-size:var(--cc-sf-error-text-size, .75rem)!important}.b-none{background:none!important}.p-32px-24px{padding:32px 24px!important}.us-none{-webkit-user-select:none!important;user-select:none!important}.c-1E293B{color:var(--cc-sf-label-color, #1E293B)!important}.c-3B82F6{color:var(--cc-sf-chip-selected-bg, #3B82F6)!important}.fs-0-78rem{font-size:.78rem!important}.p-10px-14px{padding:10px 14px!important}.fs-0-85rem{font-size:.85rem!important}.fs-0-72rem{font-size:.72rem!important}.c-94A3B8{color:#94a3b8!important}.p-4px{padding:4px!important}.br-8px{border-radius:var(--cc-sf-input-radius, 8px)!important}.bc-F3F4F6{background-color:var(--cc-sf-input-disabled-bg, #F3F4F6)!important}.br-none{border-right:none!important}.bl-none{border-left:none!important}.pe-none{pointer-events:none!important}.fs-1-1rem{font-size:1.1rem!important}.c-9CA3AF{color:var(--cc-sf-hint-color, #9CA3AF)!important}.pl-2-4rem{padding-left:2.4rem!important}.fs-0-8125rem{font-size:.8125rem!important}.ls-none{list-style:none!important}.br-12px{border-radius:var(--mu-carousel-radius, 12px)!important}.b-FFFAF1{background:var(--cc-sf-dropzone-bg, #FFFAF1)!important}.fs-18px{font-size:18px!important}.b-FEF2F2{background:var(--cc-sf-error-bg, #FEF2F2)!important}.bc-DC2626{border-color:var(--cc-sf-error-border, #DC2626)!important}.c-202124{color:var(--cc-sf-label-color, #202124)!important}.fs-18px{font-size:var(--cc-sf-label-size, 18px)!important}.mb-0-5rem{margin-bottom:.5rem!important}.pt-0-625rem{padding-top:var(--cc-sf-input-padding-y, .625rem)!important}.pb-0-625rem{padding-bottom:var(--cc-sf-input-padding-y, .625rem)!important}.pl-16px{padding-left:var(--cc-sf-input-padding-x, 16px)!important}.pr-16px{padding-right:var(--cc-sf-input-padding-x, 16px)!important}.bc-ffffff{background-color:var(--cc-sf-section-bg, #ffffff)!important}.b-1px-solid-D1D5DB{border:1px solid var(--cc-sf-input-border, #D1D5DB)!important}.fs-0-75rem{font-size:.75rem!important}.c-1F2937{color:var(--cc-sf-section-label-color, #1F2937)!important}.p-6px-14px{padding:var(--cc-sf-chip-padding, 6px 14px)!important}.b-ffffff{background:var(--loc-suggestion-bg, #ffffff)!important}.c-374151{color:var(--cc-sf-label-color, #374151)!important}.br-20px{border-radius:var(--cc-sf-chip-radius, 20px)!important}.fs-0-875rem{font-size:var(--cc-sf-btn-font-size, .875rem)!important}.bc-D1D5DB{background-color:var(--cc-sf-switch-track-off, #D1D5DB)!important}.pr-2-75rem{padding-right:2.75rem!important}.p-0-25rem{padding:.25rem!important}.p-0-625rem-0-875rem{padding:var(--cc-sf-generated-padding, .625rem .875rem)!important}.b-F3F4F6{background:var(--cc-sf-generated-bg, #F3F4F6)!important}.b-1px-solid-E5E7EB{border:1px solid var(--cc-sf-input-disabled-border, #E5E7EB)!important}.br-8px{border-radius:var(--cc-sf-uploaded-item-radius, 8px)!important}.c-6B7280{color:var(--ms-desc-color, #6B7280)!important}.mb-20px{margin-bottom:var(--cc-sf-section-gap, 20px)!important}.br-10px{border-radius:var(--cc-sf-input-radius, 10px)!important}.p-20px{padding:var(--cc-sf-section-padding, 20px)!important}.fs-1rem{font-size:1rem!important}.m-0-0-16px-0{margin:0 0 16px!important}.bb-2px-solid-E5E7EB{border-bottom:var(--cc-sf-section-label-border, 2px solid #E5E7EB)!important}.p-16px{padding:var(--cc-sf-instance-padding, 16px)!important}.b-F9FAFB{background:var(--loc-tba-bg, #F9FAFB)!important}.bb-1px-dashed-D1D5DB{border-bottom:var(--cc-sf-instance-divider, 1px dashed #D1D5DB)!important}.c-4B5563{color:var(--cc-sf-instance-num-color, #4B5563)!important}.fs-0-8125rem{font-size:var(--cc-sf-hint-size, .8125rem)!important}.pb-0{padding-bottom:0!important}.p-18px-24px{padding:18px 24px!important}.c-111827{color:var(--ms-title-color, #111827)!important}.bt-1px-solid-E5E7EB{border-top:1px solid #E5E7EB!important}.p-4px-10px{padding:4px 10px!important}.b-FFF5F5{background:var(--cc-sf-btn-remove-bg, #FFF5F5)!important}.c-E53E3E{color:var(--loc-delete-color, #E53E3E)!important}.b-1px-solid-FED7D7{border:var(--cc-sf-btn-remove-border, 1px solid #FED7D7)!important}.br-4px{border-radius:var(--cc-sf-btn-remove-radius, 4px)!important}.p-8px-16px{padding:8px 16px!important}.b-transparent{background:var(--cc-sf-btn-add-bg, transparent)!important}.c-3B82F6{color:var(--cc-sf-input-focus-border, #3B82F6)!important}.b-1px-dashed-CBD5E1{border:var(--cc-sf-btn-add-border, 1px dashed #CBD5E1)!important}.br-6px{border-radius:var(--cc-sf-btn-add-radius, 6px)!important}.b-1-5px-dashed-CBD5E1{border:var(--cc-sf-dropzone-border, 1.5px dashed #CBD5E1)!important}.br-12px{border-radius:var(--cc-sf-dropzone-radius, 12px)!important}.bc-FFFAF1{background-color:var(--cc-sf-dropzone-bg, #FFFAF1)!important}.c-94A3B8{color:var(--cc-sf-uploaded-remove-color, #94A3B8)!important}.fs-0-9rem{font-size:var(--cc-sf-input-font-size, .9rem)!important}.c-64748B{color:var(--cc-sf-dropzone-hint-color, #64748B)!important}.b-1px-solid-E2E8F0{border:var(--cc-sf-uploaded-item-border, 1px solid #E2E8F0)!important}.b-2px-solid-E2E8F0{border:2px solid #E2E8F0!important}.pr-3-5rem{padding-right:3.5rem!important}.p-0-0-875rem{padding:0 .875rem!important}.bc-FFFFFF{background-color:var(--cc-sf-input-bg, #FFFFFF)!important}.b-1-5px-solid-D1D5DB{border:var(--cc-sf-input-border, 1.5px solid #D1D5DB)!important}.mb-0-75rem{margin-bottom:.75rem!important}.mt-6px{margin-top:6px!important}.pr-2-4rem{padding-right:2.4rem!important}.p-0-2rem{padding:.2rem!important}.fs-1-35rem{font-size:1.35rem!important}.p-4px-12px{padding:4px 12px!important}.b-111827{background:var(--cc-sf-label-color, #111827)!important}.b-2px-dashed-CBD5E1{border:2px dashed var(--cc-sf-dropzone-border, #CBD5E1)!important}.fs-52px{font-size:52px!important}.p-12px-16px{padding:12px 16px!important}.bb-1px-solid-F3F4F6{border-bottom:1px solid var(--cc-sf-input-disabled-border, #F3F4F6)!important}.b-0F172A{background:var(--mu-carousel-bg, #0F172A)!important}.b-3px-solid-rgba-255-255-255-0-15{border:3px solid rgba(255,255,255,.15)!important}.b-rgba-255-255-255-0-85{background:#ffffffd9!important}.b-rgba-0-0-0-0-55{background:#0000008c!important}.b-rgba-255-255-255-0-45{background:#ffffff73!important}.pb-4px{padding-bottom:4px!important}.b-2px-solid-transparent{border:2px solid transparent!important}.b-E2E8F0{background:var(--mu-thumb-bg, #E2E8F0)!important}.b-1E293B{background:#1e293b!important}.c-EF4444{color:#ef4444!important}.b-rgba-0-0-0-0-5{background:#00000080!important}.br-16px{border-radius:var(--mu-modal-radius, 16px)!important}.p-24px-28px{padding:24px 28px!important}.bb-1px-solid-E5E7EB{border-bottom:1px solid var(--cc-sf-input-disabled-border, #E5E7EB)!important}.fs-1-25rem{font-size:1.25rem!important}.p-48px-24px{padding:48px 24px!important}.b-3px-solid-E2E8F0{border:3px solid #E2E8F0!important}.p-16px-24px{padding:16px 24px!important}.p-28px{padding:28px!important}.b-3px-solid-transparent{border:3px solid transparent!important}.b-rgba-59-130-246-0-12{background:#3b82f61f!important}.p-20px-28px{padding:20px 28px!important}.c-1A56DB{color:var(--loc-add-color, #1A56DB)!important}.b-1px-dashed-D1D5DB{border:1px dashed var(--cc-sf-input-disabled-border, #D1D5DB)!important}.fs-40px{font-size:40px!important}.c-9CA3AF{color:var(--loc-tba-icon-color, #9CA3AF)!important}.form-field{font-family:var(--cc-sf-font-family, \"Poppins\", sans-serif)!important}.generated-value{word-break:break-all}:host{--cc-sf-input-border: #D1D5DB;--cc-sf-input-bg: #ffffff;--cc-sf-input-radius: 9px;--cc-sf-input-height: 44px;--cc-sf-label-color: #111827;--cc-sf-hint-color: #9CA3AF;--cc-sf-error-border: #EF4444;--cc-sf-error-bg: #FFF5F5;--cc-sf-accent-color: #6366F1;--cc-sf-input-focus-border: #6366F1;--cc-sf-input-hover-border: #A5B4FC;--cc-sf-input-placeholder: #C4C9D4;--cc-sf-input-disabled-bg: #F8F9FB;--cc-sf-input-disabled-border: #E5E7EB;--cc-sf-switch-track-on: #6366F1;--cc-sf-switch-track-off: #D1D5DB;--cc-sf-switch-thumb: #ffffff;--cc-sf-selected-color: #6366F1}.form-row{gap:var(--cc-sf-grid-gap, 16px)}.form-row.horizontal{display:flex;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-label{font-size:.8rem!important;font-weight:600!important;line-height:1.2;letter-spacing:.04em;text-transform:uppercase!important;color:#202124!important;margin-bottom:var(--cc-sf-label-mb, 0px)!important}.field-input,input.matInput,.mat-mdc-input-element{display:block;width:100%;height:var(--cc-sf-input-height)!important;padding:0 14px!important;font-family:inherit;font-size:.9rem;color:var(--cc-sf-label-color);background-color:var(--cc-sf-input-bg)!important;border:1.5px solid var(--cc-sf-input-border)!important;border-radius:var(--cc-sf-input-radius)!important;box-sizing:border-box;box-shadow:0 1px 2px #0000000a!important;transition:all .2s cubic-bezier(.4,0,.2,1)}.field-input::placeholder,input.matInput::placeholder,.mat-mdc-input-element::placeholder{font-weight:400;font-size:14px;color:var(--cc-sf-input-placeholder)}.field-input{opacity:var(--cc-sf-input-opacity, 1);line-height:var(--cc-sf-input-line-height, 1.5);transition:var(--cc-sf-input-transition, all .2s ease)}.field-input::placeholder{font-weight:var(--cc-sf-placeholder-weight, 400);font-size:var(--cc-sf-placeholder-size, 14px);line-height:var(--cc-sf-placeholder-line-height, 100%);color:var(--cc-sf-input-placeholder)}.field-input:hover:not(:disabled):not([readonly]){border-color:var(--cc-sf-input-hover-border)!important;box-shadow:0 1px 4px #6366f114!important}.field-input:focus{outline:none;border-color:var(--cc-sf-input-focus-border)!important;box-shadow:0 0 0 3px #6366f124,0 1px 4px #6366f11a!important;background-color:#fefeff!important}.field-input:disabled,.field-input[readonly]{background-color:var(--cc-sf-input-disabled-bg)!important;color:#9ca3af!important;cursor:not-allowed;border-color:var(--cc-sf-input-disabled-border)!important;box-shadow:none!important}.field-input.is-invalid{border-color:var(--cc-sf-error-border)!important;background-color:var(--cc-sf-error-bg)!important}.field-input.is-invalid:focus{box-shadow:0 0 0 3px #ef44441f,0 1px 4px #ef44441a!important}.field-input.textarea{resize:vertical;min-height:100px;height:auto;padding:12px 16px!important}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}.dropdown-search-wrapper{display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:1px solid var(--cc-sf-input-border, #E5E7EB);background:var(--cc-sf-input-bg, #ffffff)}.dropdown-search-wrapper .dropdown-search-icon{font-size:18px;width:18px;height:18px;color:var(--cc-sf-input-placeholder, #9CA3AF);flex-shrink:0}.dropdown-search-wrapper .dropdown-search-input{flex:1;border:none;outline:none;background:transparent;font-size:.875rem;color:var(--cc-sf-label-color, #111827);font-family:inherit}.dropdown-search-wrapper .dropdown-search-input::placeholder{color:var(--cc-sf-input-placeholder, #C4C9D4)}.dropdown-search-wrapper .dropdown-search-clear{font-size:18px;width:18px;height:18px;color:var(--cc-sf-input-placeholder, #9CA3AF);flex-shrink:0;transition:color .15s}.dropdown-search-wrapper .dropdown-search-clear:hover{color:var(--cc-sf-label-color, #111827)}.dropdown-pagination-footer{padding:8px 12px;border-top:1px solid var(--cc-sf-input-border, #E5E7EB);background:var(--cc-sf-input-bg, #ffffff);display:flex;justify-content:center;align-items:center}.dropdown-pagination-footer .btn-dropdown-load-more{width:100%;padding:6px 12px;font-size:.8125rem;font-weight:500;color:var(--cc-sf-accent-color, #6366F1);background:transparent;border:1px dashed var(--cc-sf-accent-color, #6366F1);border-radius:var(--cc-sf-input-radius, 6px);cursor:pointer;transition:all .2s ease}.dropdown-pagination-footer .btn-dropdown-load-more:hover{background:#6366f114}.dropdown-pagination-footer .dropdown-loading-more-state{display:flex;align-items:center;justify-content:center;gap:8px;padding:4px 0}.dropdown-pagination-footer .dropdown-loading-more-state .dropdown-spinner-small{width:14px;height:14px;border:2px solid var(--cc-sf-input-border, #E5E7EB);border-top-color:var(--cc-sf-accent-color, #6366F1);border-radius:50%;animation:spin .8s linear infinite}.multi-select-wrapper{position:relative}.multi-select-wrapper .multi-select-trigger{min-height:var(--cc-sf-input-height, 44px);height:auto;-webkit-user-select:none;user-select:none}.multi-select-wrapper .multi-select-trigger.ms-open{border-color:var(--cc-sf-input-focus-border, #6366F1)!important;box-shadow:0 0 0 3px #6366f124,0 1px 4px #6366f11a!important}.multi-select-wrapper .multi-select-trigger .multi-select-value{flex:1;color:var(--cc-sf-label-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.multi-select-wrapper .multi-select-trigger .multi-select-placeholder{flex:1;font-weight:400;font-size:14px;color:var(--cc-sf-input-placeholder, #C4C9D4)}.multi-select-wrapper .multi-select-trigger .multi-select-arrow{font-size:20px;width:20px;height:20px;line-height:20px;color:var(--cc-sf-label-color, #6B7280);flex-shrink:0}.multi-select-wrapper .multi-select-panel{position:relative;width:100%;max-width:100vw;box-sizing:border-box;background:var(--cc-sf-input-bg, #ffffff);border:1.5px solid var(--cc-sf-input-border, #D1D5DB);border-radius:var(--cc-sf-input-radius, 9px);box-shadow:0 8px 24px #0000001a,0 2px 6px #0000000f;z-index:1050;display:flex;flex-direction:column;overflow:hidden;max-height:320px}.multi-select-wrapper .multi-select-options-list{flex:1;overflow-y:auto;max-height:240px}.multi-select-wrapper .multi-select-options-list::-webkit-scrollbar{width:4px}.multi-select-wrapper .multi-select-options-list::-webkit-scrollbar-thumb{background:var(--cc-sf-input-border, #D1D5DB);border-radius:2px}.multi-select-wrapper .multi-select-option{padding:8px 12px;border-bottom:1px solid var(--cc-sf-input-border, #F3F4F6);transition:background .1s}.multi-select-wrapper .multi-select-option:last-of-type{border-bottom:none}.multi-select-wrapper .multi-select-option:hover{background:var(--cc-sf-input-hover-bg, #F9FAFB)}.multi-select-wrapper .multi-select-option input[type=checkbox]{flex-shrink:0;cursor:pointer;accent-color:var(--cc-sf-selected-color, #6366F1);width:15px;height:15px}.multi-select-wrapper .multi-select-option.multi-select-all{border-bottom:1px solid var(--cc-sf-input-border, #E5E7EB);background:var(--cc-sf-input-bg, #FAFBFC)}.multi-select-wrapper .multi-select-empty{padding:12px;text-align:center}.multi-select-wrapper.is-invalid .multi-select-trigger{border-color:var(--cc-sf-error-border, #EF4444)!important;background-color:var(--cc-sf-error-bg, #FFF5F5)!important}.single-select-wrapper{position:relative}.single-select-wrapper .single-select-trigger{min-height:var(--cc-sf-input-height, 44px);height:auto;-webkit-user-select:none;user-select:none}.single-select-wrapper .single-select-trigger.ss-open{border-color:var(--cc-sf-input-focus-border, #6366F1)!important;box-shadow:0 0 0 3px #6366f124,0 1px 4px #6366f11a!important}.single-select-wrapper .single-select-trigger .single-select-value{flex:1;color:var(--cc-sf-label-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.single-select-wrapper .single-select-trigger .single-select-placeholder{flex:1;font-weight:400;font-size:14px;color:var(--cc-sf-input-placeholder, #C4C9D4)}.single-select-wrapper .single-select-trigger .single-select-arrow{font-size:20px;width:20px;height:20px;line-height:20px;color:var(--cc-sf-label-color, #6B7280);flex-shrink:0}.single-select-wrapper .single-select-panel{position:relative;width:100%;max-width:100vw;box-sizing:border-box;background:var(--cc-sf-input-bg, #ffffff);border:1.5px solid var(--cc-sf-input-border, #D1D5DB);border-radius:var(--cc-sf-input-radius, 9px);box-shadow:0 8px 24px #0000001a,0 2px 6px #0000000f;z-index:1050;display:flex;flex-direction:column;overflow:hidden;max-height:320px}.single-select-wrapper .single-select-options-list{flex:1;overflow-y:auto;max-height:240px}.single-select-wrapper .single-select-options-list::-webkit-scrollbar{width:4px}.single-select-wrapper .single-select-options-list::-webkit-scrollbar-thumb{background:var(--cc-sf-input-border, #D1D5DB);border-radius:2px}.single-select-wrapper .single-select-option{padding:8px 12px;border-bottom:1px solid var(--cc-sf-input-border, #F3F4F6);transition:background .1s;justify-content:space-between}.single-select-wrapper .single-select-option:last-of-type{border-bottom:none}.single-select-wrapper .single-select-option:hover{background:var(--cc-sf-input-hover-bg, #F9FAFB)}.single-select-wrapper .single-select-option.selected{background:var(--cc-sf-input-hover-bg, #F0F5FF);color:var(--cc-sf-selected-color, #6366F1)}.single-select-wrapper .single-select-option .single-select-check{font-size:18px;width:18px;height:18px;color:var(--cc-sf-selected-color, #6366F1)}.single-select-wrapper.is-invalid .single-select-trigger{border-color:var(--cc-sf-error-border, #EF4444)!important;background-color:var(--cc-sf-error-bg, #FFF5F5)!important}.multi-select-panel,.single-select-panel{width:100%;background:var(--cc-sf-input-bg, #ffffff);border:1.5px solid var(--cc-sf-input-border, #D1D5DB);border-radius:var(--cc-sf-input-radius, 9px);box-shadow:0 8px 24px #0000001a,0 2px 6px #0000000f;display:flex;flex-direction:column;overflow:hidden;max-height:320px}.multi-select-options-list,.single-select-options-list{flex:1;overflow-y:auto;max-height:240px}.multi-select-options-list::-webkit-scrollbar,.single-select-options-list::-webkit-scrollbar{width:4px}.multi-select-options-list::-webkit-scrollbar-thumb,.single-select-options-list::-webkit-scrollbar-thumb{background:var(--cc-sf-input-border, #D1D5DB);border-radius:2px}.multi-select-option,.single-select-option{display:flex;align-items:center;gap:8px;cursor:pointer;padding:8px 12px;border-bottom:1px solid var(--cc-sf-input-border, #F3F4F6);transition:background .1s}.multi-select-option:last-of-type,.single-select-option:last-of-type{border-bottom:none}.multi-select-option:hover,.single-select-option:hover{background:var(--cc-sf-input-hover-bg, #F9FAFB)}.multi-select-option input[type=checkbox],.single-select-option input[type=checkbox]{flex-shrink:0;cursor:pointer;accent-color:var(--cc-sf-selected-color, #6366F1);width:15px;height:15px}.multi-select-option.multi-select-all{border-bottom:1px solid var(--cc-sf-input-border, #E5E7EB);background:var(--cc-sf-input-bg, #FAFBFC)}.single-select-option{justify-content:space-between}.single-select-option.selected{background:var(--cc-sf-input-hover-bg, #F0F5FF);color:var(--cc-sf-selected-color, #6366F1)}.single-select-option .single-select-check{font-size:18px;width:18px;height:18px;color:var(--cc-sf-selected-color, #6366F1)}.multi-select-empty{padding:12px;text-align:center}::ng-deep .cdk-overlay-container{z-index:1100}.char-count-hint{font-style:normal;line-height:100%;letter-spacing:.02em;margin-top:.4rem}.radio-group.layout-column,.checkbox-group.layout-column{display:grid!important;grid-template-columns:repeat(12,1fr);gap:16px;width:100%}.radio-group.layout-row,.checkbox-group.layout-row{flex-direction:column!important;gap:12px;width:100%}.radio-label input,.checkbox-label input{cursor:pointer;accent-color:var(--cc-sf-chip-selected-bg, #F05A28)}.radio-label.card-item,.checkbox-label.card-item{display:flex!important;flex-direction:row-reverse!important;justify-content:space-between!important;align-items:center!important;border:1px solid var(--cc-sf-input-disabled-border, #E5E7EB);border-radius:12px;padding:16px 20px;box-sizing:border-box;transition:all .2s ease;background:var(--cc-sf-input-bg, #ffffff);margin-bottom:0}.radio-label.card-item input,.checkbox-label.card-item input{margin-left:16px}.radio-label.card-item.selected,.checkbox-label.card-item.selected{border-color:var(--cc-sf-selected-color);background-color:#f05a280d}.radio-label.card-item .option-content .label-text,.checkbox-label.card-item .option-content .label-text,.checkbox-single .checkbox-label{font-weight:var(--cc-sf-label-weight, 500)}.chip-label{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-selected-color);color:var(--cc-sf-chip-selected-color, #ffffff);border-color:var(--cc-sf-selected-color)}.switch{width:50px;height:24px;display:inline-block}.switch input{opacity:0;width:0;height:0;position:absolute}.switch input:checked+.slider{background-color:var(--cc-sf-switch-track-on)!important}.switch input:checked+.slider:before{transform:translate(26px)}.switch .slider{inset:0;transition:.4s;background-color:var(--cc-sf-switch-track-off);border-radius:24px}.switch .slider:before{position:absolute;content:\"\";height:18px;width:18px;left:3px;bottom:3px;background-color:var(--cc-sf-switch-thumb);transition:.4s;border-radius:50%}.rating-group .star{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 .password-toggle{right:.625rem;top:50%;transform:translateY(-50%);line-height:1;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}.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-left:12px;padding-top:2px;padding-bottom:2px;border-left:var(--cc-sf-section-header-accent-width, 4px) solid var(--cc-sf-section-header-accent-color, #3B82F6);line-height:1.4}.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-accordion-instance{border:var(--cc-sf-instance-border, 1px solid #E5E7EB);border-radius:var(--cc-sf-section-border-radius-inner, 8px);margin-bottom:8px;overflow:hidden;transition:border-color .2s ease}.group-section-wrapper .group-accordion-instance:last-of-type{margin-bottom:0}.group-section-wrapper .group-accordion-instance .group-accordion-header{display:flex;justify-content:space-between;align-items:center;padding:10px 14px;background:var(--cc-sf-repeater-accordion-header-bg, #F9FAFB);cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .15s ease}.group-section-wrapper .group-accordion-instance .group-accordion-header:hover{background:var(--cc-sf-repeater-accordion-active-bg, #EFF6FF)}.group-section-wrapper .group-accordion-instance .group-accordion-header .instance-badge{width:22px;height:22px;border-radius:50%;background:var(--cc-sf-repeater-badge-bg, #E5E7EB);color:var(--cc-sf-repeater-badge-color, #374151);font-size:.75rem;font-weight:600;display:flex;align-items:center;justify-content:center;flex-shrink:0}.group-section-wrapper .group-accordion-instance .group-accordion-header .instance-title{font-size:var(--cc-sf-instance-num-size, .8125rem);font-weight:600;color:var(--cc-sf-repeater-accordion-header-color, #1F2937)}.group-section-wrapper .group-accordion-instance .group-accordion-header .accordion-remove-btn{background:none;border:none;cursor:pointer;color:var(--cc-sf-btn-remove-color, #E53E3E);padding:4px;border-radius:4px;line-height:1;display:flex;align-items:center;transition:background .15s ease}.group-section-wrapper .group-accordion-instance .group-accordion-header .accordion-remove-btn mat-icon{font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem}.group-section-wrapper .group-accordion-instance .group-accordion-header .accordion-remove-btn:hover{background:var(--cc-sf-btn-remove-hover-bg, #FED7D7)}.group-section-wrapper .group-accordion-instance .group-accordion-header .accordion-chevron{font-size:1.25rem;width:1.25rem;height:1.25rem;line-height:1.25rem;color:var(--cc-sf-instance-num-color, #4B5563)}.group-section-wrapper .group-accordion-instance .group-accordion-body{padding:var(--cc-sf-instance-padding, 16px);background:var(--cc-sf-instance-bg, #F9FAFB);border-top:var(--cc-sf-instance-divider, 1px dashed #D1D5DB)}.group-section-wrapper .btn-add-group{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:10px 20px;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-family:inherit;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)}.group-section-wrapper .btn-add-group mat-icon{font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem}.group-section-wrapper .btn-add-group:hover{background:var(--cc-sf-btn-add-hover-bg, #EFF6FF);border-color:var(--cc-sf-btn-add-hover-border, #BFDBFE)}.group-section-wrapper .group-instance:last-child{margin-bottom:0}.group-section-wrapper.multi-save-active{border:none;box-shadow:none;padding:0;background:transparent}.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.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 .card-content .card-title{white-space:nowrap;text-overflow:ellipsis}.group-section-wrapper.multi-save-active .group-instance.is-card .card-view .card-content .card-desc{line-height:1.4;display:-webkit-box;-webkit-line-clamp:1;line-clamp:1;-webkit-box-orient:vertical}.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 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)}.btn-remove{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{font-weight:var(--cc-sf-btn-font-weight, 600);transition:var(--cc-sf-btn-transition, all .2s ease)}.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{background-color:var(--cc-sf-dropzone-bg, #F8FAFC);border:var(--cc-sf-dropzone-border, 1.5px dashed #CBD5E1);border-radius:var(--cc-sf-dropzone-radius, 12px);transition:background-color .2s ease,border-color .2s ease}.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 .dropzone-icon-pill{width:52px;height:52px;border-radius:50%;background:var(--cc-sf-dropzone-icon-bg, rgba(59, 130, 246, .1))}.upload-icon-wrap mat-icon.upload-cloud-icon{font-size:28px;width:28px;height:28px;line-height:28px;color:var(--cc-sf-accent-color, #3B82F6)}.upload-main-text{color:var(--cc-sf-label-color, #1E293B)}.upload-sub-text{color:var(--cc-sf-hint-color, #64748B)}.upload-link{color:var(--cc-sf-dropzone-link-color, #3B82F6);font-weight:500}.upload-formats{color:var(--cc-sf-dropzone-link-color, #3B82F6)}.upload-size-badge{display:inline-block;padding:2px 8px;border-radius:20px;background:var(--cc-sf-input-disabled-bg, #F3F4F6);color:var(--cc-sf-hint-color, #6B7280);font-weight:500}.uploaded-item{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;flex-shrink:0}.uploaded-item .file-info{min-width:0;gap:2px}.uploaded-item .file-info .file-name{white-space:nowrap;text-overflow:ellipsis}.uploaded-item .file-remove-btn{flex-shrink:0;width:32px;height:32px;background:none;border:none;cursor:pointer;color:var(--cc-sf-uploaded-remove-color, #94A3B8);padding:0;display:flex;align-items:center;justify-content:center;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:not(:disabled){color:var(--cc-sf-uploaded-remove-hover-color, #DC2626);background:var(--cc-sf-uploaded-remove-hover-bg, #FEF2F2)}.uploaded-item .file-remove-btn:disabled{opacity:.4;cursor:not-allowed}.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-top-color:var(--cc-sf-accent-color, #3B82F6);animation:cc-spin .7s linear infinite}@keyframes cc-spin{to{transform:rotate(360deg)}}.uploading-label{font-style:italic}.input-group{align-items:stretch}.input-group .field-input{flex:1;width:auto}.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{cursor:default}.input-prefix,.input-suffix{display:flex!important;align-items:center;white-space:nowrap;padding:0 14px;background-color:var(--cc-sf-input-disabled-bg);border:1px solid var(--cc-sf-input-border);font-size:.875rem;color:var(--cc-sf-hint-color);-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)}.readonly-icons{right:.875rem;top:50%;transform:translateY(-50%)}.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)}.suffix-action-icons{right:.875rem;top:50%;transform:translateY(-50%);z-index:1;pointer-events:auto}.suffix-action-icons mat-icon.suffix-action-icon{font-size:1.125rem;width:1.125rem;height:1.125rem;line-height:1.125rem;color:var(--cc-sf-hint-color, #6B7280);cursor:pointer;transition:color .2s ease,transform .15s ease}.suffix-action-icons mat-icon.suffix-action-icon:hover{color:var(--cc-sf-text-color, #202124);transform:scale(1.15)}.date-icon-wrapper{right:.5rem;top:50%;transform:translateY(-50%);pointer-events:auto}.date-icon-wrapper .mat-icon-button{width:32px;height:32px;line-height:32px}.subfields-group-wrapper .subfields-row{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{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{font-weight:700}.autocomplete-wrapper .ac-input{padding-left:40px!important}.autocomplete-wrapper .ac-search-icon{left:.75rem;width:1.1rem;height:1.1rem;line-height:1.1rem;z-index:1;transition:color var(--cc-sf-input-transition, .2s ease)}.autocomplete-wrapper .ac-clear-btn{right:.6rem;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-accent-color, #3B82F6)}.autocomplete-wrapper.is-invalid .ac-input{border-color:var(--cc-sf-error-border)!important;background-color:var(--cc-sf-error-bg)}.autocomplete-wrapper.readonly .ac-input{background-color:var(--cc-sf-input-disabled-bg);color:var(--cc-sf-input-disabled-color, #6B7280);cursor:not-allowed;border-color:var(--cc-sf-input-disabled-border)!important}.ac-no-results{font-style:italic}::ng-deep .mat-mdc-autocomplete-panel,::ng-deep .mat-autocomplete-panel{background:var(--cc-sf-input-bg, #ffffff)!important;border-radius:var(--cc-sf-input-radius, 9px)!important;border:1px solid var(--cc-sf-input-disabled-border, #E5E7EB)!important;box-shadow:0 8px 24px #0000001a,0 2px 6px #0000000f!important;padding:4px 0!important;min-width:200px}::ng-deep .mat-mdc-autocomplete-panel mat-option,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option,::ng-deep .mat-mdc-autocomplete-panel .mat-option,::ng-deep .mat-autocomplete-panel mat-option,::ng-deep .mat-autocomplete-panel .mat-mdc-option,::ng-deep .mat-autocomplete-panel .mat-option{background:var(--cc-sf-input-bg, #ffffff)!important;color:var(--cc-sf-label-color, #111827)!important;font-size:.875rem!important;padding:10px 16px!important;min-height:40px!important;line-height:1.4!important;display:flex!important;flex-direction:column!important;align-items:flex-start!important;transition:background var(--cc-sf-input-transition, .2s ease)!important}::ng-deep .mat-mdc-autocomplete-panel mat-option:hover:not(.mat-option-disabled):not([disabled]),::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option:hover:not(.mat-option-disabled):not([disabled]),::ng-deep .mat-mdc-autocomplete-panel .mat-option:hover:not(.mat-option-disabled):not([disabled]),::ng-deep .mat-autocomplete-panel mat-option:hover:not(.mat-option-disabled):not([disabled]),::ng-deep .mat-autocomplete-panel .mat-mdc-option:hover:not(.mat-option-disabled):not([disabled]),::ng-deep .mat-autocomplete-panel .mat-option:hover:not(.mat-option-disabled):not([disabled]){background:var(--cc-sf-input-disabled-bg, #F3F4F6)!important}::ng-deep .mat-mdc-autocomplete-panel mat-option.mat-selected:not(.mat-option-multiple),::ng-deep .mat-mdc-autocomplete-panel mat-option.mdc-list-item--selected,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option.mat-selected:not(.mat-option-multiple),::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option.mdc-list-item--selected,::ng-deep .mat-mdc-autocomplete-panel .mat-option.mat-selected:not(.mat-option-multiple),::ng-deep .mat-mdc-autocomplete-panel .mat-option.mdc-list-item--selected,::ng-deep .mat-autocomplete-panel mat-option.mat-selected:not(.mat-option-multiple),::ng-deep .mat-autocomplete-panel mat-option.mdc-list-item--selected,::ng-deep .mat-autocomplete-panel .mat-mdc-option.mat-selected:not(.mat-option-multiple),::ng-deep .mat-autocomplete-panel .mat-mdc-option.mdc-list-item--selected,::ng-deep .mat-autocomplete-panel .mat-option.mat-selected:not(.mat-option-multiple),::ng-deep .mat-autocomplete-panel .mat-option.mdc-list-item--selected{background:var(--cc-sf-dropzone-hover-bg, #EFF6FF)!important;color:var(--cc-sf-selected-color, #6366F1)!important;font-weight:600!important}::ng-deep .mat-mdc-autocomplete-panel mat-option .ac-option-label,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .ac-option-label,::ng-deep .mat-mdc-autocomplete-panel .mat-option .ac-option-label,::ng-deep .mat-autocomplete-panel mat-option .ac-option-label,::ng-deep .mat-autocomplete-panel .mat-mdc-option .ac-option-label,::ng-deep .mat-autocomplete-panel .mat-option .ac-option-label{font-weight:500;color:var(--cc-sf-label-color, #111827);font-size:.875rem;display:block}::ng-deep .mat-mdc-autocomplete-panel mat-option .ac-display-fields,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .ac-display-fields,::ng-deep .mat-mdc-autocomplete-panel .mat-option .ac-display-fields,::ng-deep .mat-autocomplete-panel mat-option .ac-display-fields,::ng-deep .mat-autocomplete-panel .mat-mdc-option .ac-display-fields,::ng-deep .mat-autocomplete-panel .mat-option .ac-display-fields{align-items:center;line-height:1}::ng-deep .mat-mdc-autocomplete-panel mat-option .ac-df-item,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .ac-df-item,::ng-deep .mat-mdc-autocomplete-panel .mat-option .ac-df-item,::ng-deep .mat-autocomplete-panel mat-option .ac-df-item,::ng-deep .mat-autocomplete-panel .mat-mdc-option .ac-df-item,::ng-deep .mat-autocomplete-panel .mat-option .ac-df-item{display:inline-flex;align-items:center;font-size:.72rem;color:var(--cc-sf-hint-color, #6B7280);gap:3px}::ng-deep .mat-mdc-autocomplete-panel mat-option .ac-df-chip,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .ac-df-chip,::ng-deep .mat-mdc-autocomplete-panel .mat-option .ac-df-chip,::ng-deep .mat-autocomplete-panel mat-option .ac-df-chip,::ng-deep .mat-autocomplete-panel .mat-mdc-option .ac-df-chip,::ng-deep .mat-autocomplete-panel .mat-option .ac-df-chip{background:var(--cc-sf-input-disabled-bg, #F3F4F6);border-radius:4px;padding:2px 6px}::ng-deep .mat-mdc-autocomplete-panel mat-option .ac-df-text,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .ac-df-text,::ng-deep .mat-mdc-autocomplete-panel .mat-option .ac-df-text,::ng-deep .mat-autocomplete-panel mat-option .ac-df-text,::ng-deep .mat-autocomplete-panel .mat-mdc-option .ac-df-text,::ng-deep .mat-autocomplete-panel .mat-option .ac-df-text{color:var(--cc-sf-hint-color, #6B7280)}::ng-deep .mat-mdc-autocomplete-panel mat-option .ac-df-avatar,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .ac-df-avatar,::ng-deep .mat-mdc-autocomplete-panel .mat-option .ac-df-avatar,::ng-deep .mat-autocomplete-panel mat-option .ac-df-avatar,::ng-deep .mat-autocomplete-panel .mat-mdc-option .ac-df-avatar,::ng-deep .mat-autocomplete-panel .mat-option .ac-df-avatar{width:24px;height:24px;border-radius:50%;object-fit:cover;border:1px solid var(--cc-sf-input-border, #D1D5DB);vertical-align:middle}::ng-deep .mat-mdc-autocomplete-panel mat-option .ac-df-label,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .ac-df-label,::ng-deep .mat-mdc-autocomplete-panel .mat-option .ac-df-label,::ng-deep .mat-autocomplete-panel mat-option .ac-df-label,::ng-deep .mat-autocomplete-panel .mat-mdc-option .ac-df-label,::ng-deep .mat-autocomplete-panel .mat-option .ac-df-label{font-weight:600;color:var(--cc-sf-hint-color, #9CA3AF);margin-right:2px}::ng-deep .mat-mdc-autocomplete-panel mat-option .ac-df-icon,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .ac-df-icon,::ng-deep .mat-mdc-autocomplete-panel .mat-option .ac-df-icon,::ng-deep .mat-autocomplete-panel mat-option .ac-df-icon,::ng-deep .mat-autocomplete-panel .mat-mdc-option .ac-df-icon,::ng-deep .mat-autocomplete-panel .mat-option .ac-df-icon{font-size:11px;width:11px;height:11px;line-height:11px;color:var(--cc-sf-hint-color, #9CA3AF);flex-shrink:0}.mu-layout{grid-template-columns:1fr 1fr;gap:32px}@media(max-width:768px){.mu-layout{grid-template-columns:1fr}}.mu-title{font-weight:700;line-height:1.3}.mu-badge{white-space:nowrap;flex-shrink:0}.mu-description{line-height:1.6}.mu-feature-item .mu-check{width:16px;height:16px;line-height:16px;flex-shrink:0}.mu-right{min-height:260px}.mu-right-empty{min-height:250px;max-width:400px;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{width:52px;height:52px;line-height:52px;opacity:.3}.mu-right-empty p{margin:0;font-size:.85rem}.media-add-container{display:inline-block}.media-add-container ::ng-deep button{display:flex;align-items:center;gap:6px}.media-add-container ::ng-deep button .menu-chevron{width:18px;height:18px;line-height:18px;transition:transform .2s ease}.media-dropdown{top:calc(100% + 6px);left:0;z-index:200;min-width:240px;box-shadow:var(--mu-dropdown-shadow, 0 8px 32px rgba(0, 0, 0, .12));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{transition:background .15s ease}.media-dropdown-item:last-child{border-bottom:none}.media-dropdown-item:hover{background:var(--cc-sf-dropzone-hover-bg, #F0F9FF)}.media-drop-icon{width:36px;height:36px;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{gap:2px}.youtube-input-panel{animation:mu-fade-in .18s ease}.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{align-items:stretch}.media-menu-backdrop{position:fixed;inset:0;z-index:100}.media-upload-status{animation:mu-fade-in .2s ease}.media-upload-status .status-icon{width:18px;height:18px;line-height:18px}.media-carousel-main{max-width:400px;height:var(--mu-carousel-height, 250px)}.carousel-viewer{inset:0}.carousel-viewer .carousel-image{object-fit:cover}.carousel-viewer .carousel-spinner{width:36px;height:36px;border-top-color:var(--cc-sf-accent-color, #3B82F6);animation:cc-spin .7s linear infinite}.carousel-nav{top:50%;transform:translateY(-50%);z-index:10;width:40px;height:40px;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{top:10px;right:10px;z-index:10;width:32px;height:32px;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{bottom:10px;left:50%;transform:translate(-50%);z-index:10}.carousel-dot{width:8px;height:8px;transition:background .2s ease,transform .2s ease}.carousel-dot.active{background:#fff;transform:scale(1.3)}.media-thumbnail-strip{max-width:400px;overflow-x:auto}.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;transition:border-color .2s ease,transform .15s ease}.media-thumb.active{border-color:var(--mu-thumb-active-border, var(--cc-sf-accent-color, #3B82F6));transform:scale(1.04)}.media-thumb:hover:not(.active){border-color:var(--cc-sf-input-hover-border, #9CA3AF)}.media-thumb .thumb-img{object-fit:cover}.media-thumb .thumb-yt-placeholder mat-icon{font-size:28px;width:28px;height:28px;line-height:28px}.media-thumb .thumb-uploading .thumb-spinner{width:20px;height:20px;border-top-color:var(--cc-sf-accent-color, #3B82F6);animation:cc-spin .7s linear infinite}.media-library-portal-host{position:fixed;inset:0;z-index:9999;display:flex;align-items:center;justify-content:center;visibility:hidden;opacity:0;pointer-events:none;transition:opacity .2s ease,visibility .2s ease}.media-library-portal-host.is-open{visibility:visible;opacity:1;pointer-events:auto}.media-library-portal-host.is-open .media-library-modal{transform:scale(1) translateY(0)}.media-library-overlay{position:absolute;inset:0;background:#00000080;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.media-library-modal{position:relative;z-index:10000;width:90vw;max-width:900px;max-height:90vh;background:#fff;border-radius:16px;box-shadow:0 20px 25px -5px #0000001a,0 10px 10px -5px #0000000a;transform:scale(.95) translateY(10px);transition:transform .3s cubic-bezier(.175,.885,.32,1.275)}.library-modal-header{flex-shrink:0}.library-modal-title{font-weight:700}.library-modal-subtitle{line-height:1.5;max-width:600px}.library-close-btn{width:32px;height:32px;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 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-top-color:var(--cc-sf-accent-color, #3B82F6);animation:cc-spin .7s linear infinite}.library-error{flex-shrink:0}.library-error mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.library-grid{grid-template-columns:repeat(5,1fr);max-height:50vh;overflow-y:auto}.library-grid::-webkit-scrollbar{width:8px}.library-grid::-webkit-scrollbar-track{background:#f1f1f1}.library-grid::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:10px;border:2px solid #F1F1F1}.library-grid-item{aspect-ratio:1/1;transition:all .3s cubic-bezier(.4,0,.2,1);box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}.library-grid-item:hover{transform:translateY(-4px) scale(1.02);box-shadow:0 20px 25px -5px #0000001a,0 10px 10px -5px #0000000a}.library-grid-item.selected{border-color:var(--cc-sf-accent-color, #3B82F6)}.library-grid-item.selected .library-grid-img{opacity:.7}.library-grid-item:hover .library-overlay-hover{opacity:1}.library-grid-img{object-fit:cover}.library-overlay-hover{inset:0;opacity:0;transition:opacity .15s ease}.library-check{top:6px;right:6px;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{flex-shrink:0}.library-modal-footer .library-footer-actions ::ng-deep .cc-btn-primary{background-color:var(--cc-sf-accent-color, #3B82F6)!important;border-color:var(--cc-sf-accent-color, #3B82F6)!important;color:#fff!important;font-weight:600;padding-left:32px;padding-right:32px}.library-modal-footer .library-footer-actions ::ng-deep .cc-btn-primary:hover{background-color:var(--cc-sf-btn-primary-hover-bg, #2563EB)!important}.library-modal-footer .library-footer-actions ::ng-deep .cc-btn-primary:disabled{background-color:#93c5fd!important;cursor:not-allowed}.library-modal-footer .library-footer-actions ::ng-deep .cc-btn-outline{font-weight:600;padding-left:24px;padding-right:24px;border-color:#d1d5db;color:#374151}.library-modal-footer .library-footer-actions ::ng-deep .cc-btn-outline:hover{background-color:#f9fafb}.location-subtitle{line-height:1.5}.loc-tab-btn ::ng-deep button{width:100%}.loc-tab-btn ::ng-deep button:not(.cc-btn-warning){background-color:var(--cc-sf-input-bg, #ffffff)!important;color:var(--cc-sf-label-color, #000000)!important;border:1px solid var(--cc-sf-input-disabled-border, #E5E7EB)}.loc-tab-btn ::ng-deep button:not(.cc-btn-warning):hover{background-color:var(--cc-sf-input-disabled-bg, #F3F4F6)!important}.loc-venue-item{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{width:18px;height:18px;line-height:18px;flex-shrink:0}.loc-venue-text{white-space:nowrap;text-overflow:ellipsis}.loc-action-btn{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-search-icon{left:.75rem;width:1.1rem;height:1.1rem;line-height:1.1rem;z-index:1}.loc-suggestions-panel{top:calc(100% + 4px);left:0;right:0;z-index:300;box-shadow:0 8px 24px #0000001a;animation:mu-fade-in .15s ease;max-height:260px;overflow-y:auto}.loc-suggestion-item{transition:background .12s ease}.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{width:18px;height:18px;line-height:18px;flex-shrink:0}.loc-suggestion-text{white-space:nowrap;text-overflow:ellipsis}.loc-add-btn{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{box-shadow:0 2px 10px #0000000f}.loc-tba-panel{min-height:120px}.loc-tba-icon{width:40px;height:40px;line-height:40px;opacity:.6}.loc-tba-text{line-height:1.6;max-width:360px}.radio-label{display:flex!important}.radio-label .option-content{padding-left:10px}.link-list-container{border:none!important;background-color:transparent!important;padding:0!important;box-shadow:none!important;overflow:visible!important}.link-items-list{list-style:none}@media(max-width:640px){.link-items-list{grid-template-columns:1fr!important}.link-items-list .link-list-item{grid-column:span 12!important}}.link-list-item{display:flex;align-items:center;justify-content:space-between;padding:10px 16px;background-color:var(--cc-sf-link-item-bg, #f9fafb)!important;border:1px solid var(--cc-sf-link-item-border, #E5E7EB)!important;border-radius:var(--cc-sf-input-radius, 9px)!important;gap:12px;min-height:48px}.link-list-item .link-url-text{font-size:.875rem;color:var(--cc-sf-link-item-text, #111827);word-break:break-all}.link-list-item .action-btn{border:none;background:transparent;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;padding:0;border-radius:6px;transition:background .15s ease,color .15s ease}.link-list-item .action-btn mat-icon{font-size:22px;width:22px;height:22px;line-height:22px;display:flex;align-items:center;justify-content:center}.link-list-item .action-btn.edit-btn{color:var(--cc-sf-link-edit-color, #4b5563)}.link-list-item .action-btn.edit-btn:hover{color:var(--cc-sf-accent-color, #6366F1);background-color:#f3f4f6}.link-list-item .action-btn.delete-btn{color:var(--cc-sf-link-delete-color, #ef4444)}.link-list-item .action-btn.delete-btn:hover{background-color:var(--cc-sf-error-bg, #FFF5F5)}.link-list-item .action-btn.save-btn{color:var(--cc-sf-link-save-color, #16a34a)!important}.link-list-item .action-btn.save-btn:hover{background-color:#ecfdf5}.link-list-item .action-btn.cancel-btn{color:#ef4444!important}.link-list-item .action-btn.cancel-btn:hover{background-color:var(--cc-sf-error-bg, #FFF5F5)}.pe-40px{padding-right:40px!important}.dropdown-loading-state{display:flex;align-items:center;justify-content:center;padding:16px;gap:8px}.dropdown-spinner{width:20px;height:20px;border:2px solid #E2E8F0;border-top-color:var(--cc-sf-accent-color, #3B82F6);border-radius:50%;animation:cc-spin .7s linear infinite}.mini-spinner{width:16px;height:16px;border:2px solid #E2E8F0;border-top-color:var(--cc-sf-accent-color, #3B82F6);border-radius:50%;animation:cc-spin .7s linear infinite}.native-select-wrapper{display:block;width:100%;position:relative}.native-select-wrapper select{width:100%}.native-select-wrapper .native-select-spinner{position:absolute;right:2.5rem;top:calc(50% - 7px);width:14px;height:14px;border:2px solid #CBD5E1;border-top-color:var(--cc-sf-accent-color, #3B82F6);border-radius:50%;animation:cc-spin .7s linear infinite;pointer-events:none}\n"] }]
|
|
4724
|
-
}], ctorParameters: () => [{ type: i1$2.FormBuilder }, { type: ExpressionService }, { type: i3.HttpClient }, { type: i0.ElementRef }, { type: i4.Overlay }, { type: i5.DateAdapter }], propDecorators: { config: [{
|
|
4875
|
+
}], ctorParameters: () => [{ type: i1$2.FormBuilder }, { type: ExpressionService }, { type: i3.HttpClient }, { type: i0.ElementRef }, { type: i4.Overlay }, { type: i5.DateAdapter }, { type: i0.ChangeDetectorRef }], propDecorators: { config: [{
|
|
4725
4876
|
type: Input
|
|
4726
4877
|
}], controller: [{
|
|
4727
4878
|
type: Input
|
|
@@ -5816,6 +5967,7 @@ class SmartFormComponent {
|
|
|
5816
5967
|
showLoader: raw['showLoader'] ?? raw['field']?.['showLoader'] ?? false,
|
|
5817
5968
|
searchConfig: fi['searchConfig'],
|
|
5818
5969
|
pagination: fi['pagination'],
|
|
5970
|
+
sortBy: da['sortBy'],
|
|
5819
5971
|
};
|
|
5820
5972
|
}
|
|
5821
5973
|
// Legacy flat format (backward compatibility with existing geographyConfig JSON)
|
|
@@ -5847,6 +5999,7 @@ class SmartFormComponent {
|
|
|
5847
5999
|
showLoader: raw['showLoader'] ?? false,
|
|
5848
6000
|
searchConfig: raw['searchConfig'],
|
|
5849
6001
|
pagination: raw['pagination'],
|
|
6002
|
+
sortBy: raw['sortBy'],
|
|
5850
6003
|
};
|
|
5851
6004
|
}
|
|
5852
6005
|
_fetchHierarchyTemplate(cfg) {
|
|
@@ -5921,6 +6074,16 @@ class SmartFormComponent {
|
|
|
5921
6074
|
labelPath: cfg.labelPath,
|
|
5922
6075
|
valuePath: cfg.valuePath,
|
|
5923
6076
|
showLoader: cfg.showLoader,
|
|
6077
|
+
// Every GEOGRAPHY_DYNAMIC level (Country/State/District/...) generates its own
|
|
6078
|
+
// optionConfig here rather than being declared in JSON, so a per-field "sortBy" in the
|
|
6079
|
+
// form config can never reach it — form-field.component.ts already supports sortBy for
|
|
6080
|
+
// any optionConfig, this was just never being set for hierarchy-generated fields.
|
|
6081
|
+
// Sorting by the same labelPath used to render the option is a reasonable default for
|
|
6082
|
+
// any geography list; override via geographyConfig.dataApi.sortBy if a specific
|
|
6083
|
+
// template needs different (or no) ordering.
|
|
6084
|
+
// ?? (not ||) so an explicit empty-string sortBy (opt out of sorting) isn't clobbered
|
|
6085
|
+
// back to the labelPath default.
|
|
6086
|
+
sortBy: cfg.sortBy ?? cfg.labelPath,
|
|
5924
6087
|
};
|
|
5925
6088
|
// Search/pagination are opt-in via geographyConfig.field.searchConfig/pagination — every
|
|
5926
6089
|
// generated level (Country/State/District/…) gets the same behavior since they all hit
|
|
@@ -6618,6 +6781,11 @@ class DropdownComponent {
|
|
|
6618
6781
|
selectionChange = new EventEmitter();
|
|
6619
6782
|
searchTermChange = new EventEmitter();
|
|
6620
6783
|
loadMore = new EventEmitter();
|
|
6784
|
+
// Fires every time the panel opens — lets a serverSearch consumer know it should reset
|
|
6785
|
+
// back to the unfiltered/first page, since reopening after a search otherwise keeps
|
|
6786
|
+
// showing that search's narrowed result set forever (this component doesn't own `options`
|
|
6787
|
+
// in serverSearch mode, so it can't refetch that on its own).
|
|
6788
|
+
opened = new EventEmitter();
|
|
6621
6789
|
viewport;
|
|
6622
6790
|
searchInput;
|
|
6623
6791
|
triggerEl;
|
|
@@ -6629,6 +6797,12 @@ class DropdownComponent {
|
|
|
6629
6797
|
menuPosition = { top: 0, left: 0, width: 0 };
|
|
6630
6798
|
onChange = () => { };
|
|
6631
6799
|
onTouched = () => { };
|
|
6800
|
+
// True from the moment "Select All" is turned on until the user either turns it back off
|
|
6801
|
+
// or manually deselects a single option. While active, every newly lazy-loaded page is
|
|
6802
|
+
// auto-merged into the selection (see ngOnChanges) so the count of selected items keeps
|
|
6803
|
+
// matching what's on screen (10 -> 20 -> ... -> 34) instead of freezing at whatever was
|
|
6804
|
+
// loaded at the moment "Select All" was clicked.
|
|
6805
|
+
selectAllActive = false;
|
|
6632
6806
|
search$ = new Subject();
|
|
6633
6807
|
constructor(el) {
|
|
6634
6808
|
this.el = el;
|
|
@@ -6652,6 +6826,11 @@ class DropdownComponent {
|
|
|
6652
6826
|
}
|
|
6653
6827
|
if (changes['config'] || changes['options']) {
|
|
6654
6828
|
this.filteredOptions = this.serverSearch ? this.options : this.validOptions;
|
|
6829
|
+
// Sticky "Select All": a newly-arrived lazy-loaded page gets auto-selected too,
|
|
6830
|
+
// so selection keeps pace with what's been scrolled into view.
|
|
6831
|
+
if (changes['options'] && !changes['options'].isFirstChange() && this.selectAllActive) {
|
|
6832
|
+
this.applySelectAllToLoadedOptions();
|
|
6833
|
+
}
|
|
6655
6834
|
}
|
|
6656
6835
|
if (changes['selectedValue']) {
|
|
6657
6836
|
const incoming = changes['selectedValue'].currentValue;
|
|
@@ -6749,6 +6928,15 @@ class DropdownComponent {
|
|
|
6749
6928
|
this.isOpen = !this.isOpen;
|
|
6750
6929
|
if (this.isOpen) {
|
|
6751
6930
|
this.focusedIndex = -1;
|
|
6931
|
+
// Reset any previous search so reopening shows the full/unfiltered list again
|
|
6932
|
+
// rather than whatever narrowed result was last searched for.
|
|
6933
|
+
if (this.searchTerm) {
|
|
6934
|
+
this.searchTerm = '';
|
|
6935
|
+
if (this.searchInput)
|
|
6936
|
+
this.searchInput.nativeElement.value = '';
|
|
6937
|
+
if (!this.serverSearch)
|
|
6938
|
+
this.filteredOptions = this.validOptions;
|
|
6939
|
+
}
|
|
6752
6940
|
setTimeout(() => {
|
|
6753
6941
|
if (this.searchable && this.searchInput) {
|
|
6754
6942
|
this.searchInput.nativeElement.focus();
|
|
@@ -6757,6 +6945,7 @@ class DropdownComponent {
|
|
|
6757
6945
|
this.viewport.elementRef.nativeElement.focus();
|
|
6758
6946
|
}
|
|
6759
6947
|
});
|
|
6948
|
+
this.opened.emit();
|
|
6760
6949
|
}
|
|
6761
6950
|
else {
|
|
6762
6951
|
this.onTouched();
|
|
@@ -6784,9 +6973,28 @@ class DropdownComponent {
|
|
|
6784
6973
|
if (selectable.length === 0)
|
|
6785
6974
|
return;
|
|
6786
6975
|
const currentValues = Array.isArray(this.value) ? this.value : [];
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
|
|
6976
|
+
if (this.isAllFilteredSelected) {
|
|
6977
|
+
this.selectAllActive = false;
|
|
6978
|
+
this.value = currentValues.filter(v => !selectable.includes(v));
|
|
6979
|
+
}
|
|
6980
|
+
else {
|
|
6981
|
+
this.selectAllActive = true;
|
|
6982
|
+
this.value = [...currentValues, ...selectable.filter(v => !currentValues.includes(v))];
|
|
6983
|
+
}
|
|
6984
|
+
this.onChange(this.value);
|
|
6985
|
+
this.selectionChange.emit(this.value);
|
|
6986
|
+
}
|
|
6987
|
+
/** Merges every currently-loaded selectable option into the selection — used to keep a
|
|
6988
|
+
* "Select All" that's already active in sync as more lazy-loaded pages arrive. */
|
|
6989
|
+
applySelectAllToLoadedOptions() {
|
|
6990
|
+
const selectable = this.filteredOptions.filter(o => !o.disabled).map(o => o.value);
|
|
6991
|
+
if (selectable.length === 0)
|
|
6992
|
+
return;
|
|
6993
|
+
const currentValues = Array.isArray(this.value) ? this.value : [];
|
|
6994
|
+
const merged = [...currentValues, ...selectable.filter(v => !currentValues.includes(v))];
|
|
6995
|
+
if (merged.length === currentValues.length)
|
|
6996
|
+
return;
|
|
6997
|
+
this.value = merged;
|
|
6790
6998
|
this.onChange(this.value);
|
|
6791
6999
|
this.selectionChange.emit(this.value);
|
|
6792
7000
|
}
|
|
@@ -6798,6 +7006,10 @@ class DropdownComponent {
|
|
|
6798
7006
|
const index = currentValues.indexOf(option.value);
|
|
6799
7007
|
if (index > -1) {
|
|
6800
7008
|
currentValues.splice(index, 1);
|
|
7009
|
+
// Manually deselecting one item breaks the "everything loaded is selected"
|
|
7010
|
+
// invariant — stop auto-including future lazy-loaded pages until Select All
|
|
7011
|
+
// is explicitly re-toggled.
|
|
7012
|
+
this.selectAllActive = false;
|
|
6801
7013
|
}
|
|
6802
7014
|
else {
|
|
6803
7015
|
currentValues.push(option.value);
|
|
@@ -6820,6 +7032,9 @@ class DropdownComponent {
|
|
|
6820
7032
|
onSearch(term) {
|
|
6821
7033
|
this.searchTerm = term;
|
|
6822
7034
|
if (this.serverSearch) {
|
|
7035
|
+
// A new server search yields an unrelated result set — don't carry over a prior
|
|
7036
|
+
// "Select All" and auto-select whatever this new query happens to load.
|
|
7037
|
+
this.selectAllActive = false;
|
|
6823
7038
|
this.search$.next(term);
|
|
6824
7039
|
this.focusedIndex = -1;
|
|
6825
7040
|
return;
|
|
@@ -6955,7 +7170,7 @@ class DropdownComponent {
|
|
|
6955
7170
|
};
|
|
6956
7171
|
}
|
|
6957
7172
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DropdownComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
6958
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: DropdownComponent, isStandalone: false, selector: "lib-dropdown", inputs: { config: "config", labels: "labels", options: "options", selectedValue: "selectedValue", placeholder: "placeholder", label: "label", multiple: "multiple", searchable: "searchable", clearable: "clearable", disabled: "disabled", required: "required", errorMessage: "errorMessage", serverSearch: "serverSearch", searchDebounceMs: "searchDebounceMs", lazyLoad: "lazyLoad", loading: "loading", loadingMore: "loadingMore", hasMore: "hasMore", width: "width", height: "height", borderRadius: "borderRadius", fontSize: "fontSize", gap: "gap", fontFamily: "fontFamily", labelColor: "labelColor", labelFontSize: "labelFontSize", labelFontWeight: "labelFontWeight", backgroundColor: "backgroundColor", borderColor: "borderColor", borderWidth: "borderWidth", padding: "padding", fontWeight: "fontWeight", color: "color", placeholderColor: "placeholderColor", focusBorderColor: "focusBorderColor", errorColor: "errorColor", disabledBackgroundColor: "disabledBackgroundColor", disabledColor: "disabledColor", boxShadow: "boxShadow" }, outputs: { selectionChange: "selectionChange", searchTermChange: "searchTermChange", loadMore: "loadMore" }, host: { listeners: { "keydown": "handleKeyboardEvent($event)" } }, providers: [
|
|
7173
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: DropdownComponent, isStandalone: false, selector: "lib-dropdown", inputs: { config: "config", labels: "labels", options: "options", selectedValue: "selectedValue", placeholder: "placeholder", label: "label", multiple: "multiple", searchable: "searchable", clearable: "clearable", disabled: "disabled", required: "required", errorMessage: "errorMessage", serverSearch: "serverSearch", searchDebounceMs: "searchDebounceMs", lazyLoad: "lazyLoad", loading: "loading", loadingMore: "loadingMore", hasMore: "hasMore", width: "width", height: "height", borderRadius: "borderRadius", fontSize: "fontSize", gap: "gap", fontFamily: "fontFamily", labelColor: "labelColor", labelFontSize: "labelFontSize", labelFontWeight: "labelFontWeight", backgroundColor: "backgroundColor", borderColor: "borderColor", borderWidth: "borderWidth", padding: "padding", fontWeight: "fontWeight", color: "color", placeholderColor: "placeholderColor", focusBorderColor: "focusBorderColor", errorColor: "errorColor", disabledBackgroundColor: "disabledBackgroundColor", disabledColor: "disabledColor", boxShadow: "boxShadow" }, outputs: { selectionChange: "selectionChange", searchTermChange: "searchTermChange", loadMore: "loadMore", opened: "opened" }, host: { listeners: { "keydown": "handleKeyboardEvent($event)" } }, providers: [
|
|
6959
7174
|
{
|
|
6960
7175
|
provide: NG_VALUE_ACCESSOR,
|
|
6961
7176
|
useExisting: forwardRef(() => DropdownComponent),
|
|
@@ -7056,6 +7271,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
7056
7271
|
type: Output
|
|
7057
7272
|
}], loadMore: [{
|
|
7058
7273
|
type: Output
|
|
7274
|
+
}], opened: [{
|
|
7275
|
+
type: Output
|
|
7059
7276
|
}], viewport: [{
|
|
7060
7277
|
type: ViewChild,
|
|
7061
7278
|
args: [CdkVirtualScrollViewport]
|
|
@@ -8833,16 +9050,35 @@ class FormSchemaTreeService {
|
|
|
8833
9050
|
}
|
|
8834
9051
|
}
|
|
8835
9052
|
const isVisible = field.isEnabled ?? field.visible ?? true;
|
|
9053
|
+
const enabled = isVisible && field.disabled !== true;
|
|
9054
|
+
// A section acts as a true parent checkbox: when it's off, none of its descendants can be
|
|
9055
|
+
// "on" in the UI, no matter what the raw schema says for them individually. Backend data can
|
|
9056
|
+
// disagree (e.g. a prior save wrote the section's own isEnabled=false but never cascaded that
|
|
9057
|
+
// down to every leaf field's isEnabled) — that's exactly the state that made Field
|
|
9058
|
+
// Configuration silently drop selected-looking fields, since the build step below prunes the
|
|
9059
|
+
// whole disabled branch regardless of children. Normalizing here keeps what the checkboxes
|
|
9060
|
+
// show in sync with what will actually survive into the next step.
|
|
9061
|
+
const normalizedFields = enabled ? fields : fields.map(f => ({ ...f, selected: false }));
|
|
9062
|
+
const normalizedSubsections = enabled ? subsections : this._disableSectionsDeep(subsections);
|
|
8836
9063
|
return {
|
|
8837
9064
|
label: field.sectionConfig?.label || field.label || field.name || 'Section',
|
|
8838
9065
|
sectionConfig: field.sectionConfig ?? null,
|
|
8839
9066
|
parentFieldConfig: field,
|
|
8840
|
-
enabled
|
|
9067
|
+
enabled,
|
|
8841
9068
|
expanded: false, // Collapse by default for better UX - user can expand as needed
|
|
8842
|
-
fields,
|
|
8843
|
-
subsections,
|
|
9069
|
+
fields: normalizedFields,
|
|
9070
|
+
subsections: normalizedSubsections,
|
|
8844
9071
|
};
|
|
8845
9072
|
}
|
|
9073
|
+
/** Recursively forces enabled=false and selected=false through an already-built subtree. */
|
|
9074
|
+
_disableSectionsDeep(sections) {
|
|
9075
|
+
return sections.map(s => ({
|
|
9076
|
+
...s,
|
|
9077
|
+
enabled: false,
|
|
9078
|
+
fields: s.fields.map(f => ({ ...f, selected: false })),
|
|
9079
|
+
subsections: this._disableSectionsDeep(s.subsections),
|
|
9080
|
+
}));
|
|
9081
|
+
}
|
|
8846
9082
|
_createDefaultSection(fields) {
|
|
8847
9083
|
return {
|
|
8848
9084
|
label: 'Fields',
|
|
@@ -8880,7 +9116,8 @@ class FormSchemaTreeService {
|
|
|
8880
9116
|
* on the parent GEOGRAPHY_DYNAMIC field, so they carry a distinguishing `name` for name-keyed
|
|
8881
9117
|
* matching in _applyGeographyNodeSelections rather than object identity. */
|
|
8882
9118
|
_buildGeographyFieldNodes(field) {
|
|
8883
|
-
const
|
|
9119
|
+
const masterNode = this._buildFieldNode(field);
|
|
9120
|
+
const nodes = [masterNode];
|
|
8884
9121
|
const geoNodes = field.geographyConfig?.field?.nodes ?? {};
|
|
8885
9122
|
for (const classCode of Object.keys(geoNodes)) {
|
|
8886
9123
|
const cfg = geoNodes[classCode] ?? {};
|
|
@@ -8889,7 +9126,19 @@ class FormSchemaTreeService {
|
|
|
8889
9126
|
label: cfg.label ?? classCode,
|
|
8890
9127
|
type: 'GEOGRAPHY_NODE',
|
|
8891
9128
|
};
|
|
8892
|
-
nodes.push({
|
|
9129
|
+
nodes.push({
|
|
9130
|
+
fieldConfig: synthetic,
|
|
9131
|
+
// A level can't be meaningfully selected while its master field is off — the
|
|
9132
|
+
// Configurator tree/actual form prune the whole GEOGRAPHY_DYNAMIC field, children
|
|
9133
|
+
// included, the moment the master is disabled. Live toggling already enforces this
|
|
9134
|
+
// (see FieldSelectionService._toggleFieldAtPath's nodePrefix cascade), but a schema
|
|
9135
|
+
// parsed straight from persisted data (page load, not a toggle) has to enforce it here
|
|
9136
|
+
// too, or a level whose own `hidden` flag was left `false` from before the master was
|
|
9137
|
+
// turned off would show checked under an unchecked parent.
|
|
9138
|
+
selected: masterNode.selected && cfg.hidden !== true,
|
|
9139
|
+
isLocked: false,
|
|
9140
|
+
isGeographyLevel: true,
|
|
9141
|
+
});
|
|
8893
9142
|
}
|
|
8894
9143
|
return nodes;
|
|
8895
9144
|
}
|
|
@@ -9238,7 +9487,7 @@ class FieldSelectionService {
|
|
|
9238
9487
|
* Initialize the store from a FormSchema.
|
|
9239
9488
|
*/
|
|
9240
9489
|
loadSchema(schema) {
|
|
9241
|
-
const groups = this.treeService.parseSchemaToSelectionGroups(schema);
|
|
9490
|
+
const groups = this._reconcileGroupsFromChildren(this.treeService.parseSchemaToSelectionGroups(schema));
|
|
9242
9491
|
this._state.set({ groups, originalSchema: schema });
|
|
9243
9492
|
}
|
|
9244
9493
|
/**
|
|
@@ -9248,7 +9497,7 @@ class FieldSelectionService {
|
|
|
9248
9497
|
*/
|
|
9249
9498
|
loadSchemaPreservingExpanded(schema) {
|
|
9250
9499
|
const oldGroups = this._state().groups;
|
|
9251
|
-
const newGroups = this.treeService.parseSchemaToSelectionGroups(schema);
|
|
9500
|
+
const newGroups = this._reconcileGroupsFromChildren(this.treeService.parseSchemaToSelectionGroups(schema));
|
|
9252
9501
|
// Merge old expansion states into new groups
|
|
9253
9502
|
const mergedGroups = newGroups.map((newGroup, gi) => ({
|
|
9254
9503
|
...newGroup,
|
|
@@ -9257,6 +9506,28 @@ class FieldSelectionService {
|
|
|
9257
9506
|
}));
|
|
9258
9507
|
this._state.set({ groups: mergedGroups, originalSchema: schema });
|
|
9259
9508
|
}
|
|
9509
|
+
/**
|
|
9510
|
+
* A freshly-parsed group/section's `enabled` comes straight from its persisted
|
|
9511
|
+
* FieldConfig.isEnabled — unlike toggleField()/toggleSection(), which always follow a mutation
|
|
9512
|
+
* with `_recomputeEnabledFromChildren()` to keep the section/group toggle in sync with its
|
|
9513
|
+
* children's actual selected state. Without this, a schema saved with e.g. Address.isEnabled:
|
|
9514
|
+
* false but its Country/State/District checkboxes individually selected (a state that toggling
|
|
9515
|
+
* from within the UI can never itself produce, but stale/pre-fix persisted data can) loads with
|
|
9516
|
+
* checked child boxes sitting under a section toggle that shows OFF — contradicting what the
|
|
9517
|
+
* checkboxes say and, worse, matching what the Configurator tree/actual form use to prune the
|
|
9518
|
+
* whole section. Runs the same reconciliation parse-time gets on every load, so the toggle
|
|
9519
|
+
* always reflects reality regardless of how the persisted schema got into this state.
|
|
9520
|
+
*/
|
|
9521
|
+
_reconcileGroupsFromChildren(groups) {
|
|
9522
|
+
return groups.map(g => {
|
|
9523
|
+
const sections = this._recomputeEnabledFromChildren(g.sections);
|
|
9524
|
+
return {
|
|
9525
|
+
...g,
|
|
9526
|
+
sections,
|
|
9527
|
+
enabled: sections.length === 0 ? g.enabled : (sections.some(s => s.enabled) || g.enabled),
|
|
9528
|
+
};
|
|
9529
|
+
});
|
|
9530
|
+
}
|
|
9260
9531
|
/**
|
|
9261
9532
|
* Toggle a group's enabled state. Disabling cascades to all sections + fields.
|
|
9262
9533
|
* When enabling, all fields are selected (unless locked).
|
|
@@ -9286,9 +9557,11 @@ class FieldSelectionService {
|
|
|
9286
9557
|
groups: s.groups.map((g, gi) => {
|
|
9287
9558
|
if (gi !== groupIndex)
|
|
9288
9559
|
return g;
|
|
9560
|
+
const sections = this._recomputeEnabledFromChildren(this._toggleSectionAtPath(g.sections, sectionPath, 0));
|
|
9289
9561
|
return {
|
|
9290
9562
|
...g,
|
|
9291
|
-
sections
|
|
9563
|
+
sections,
|
|
9564
|
+
enabled: sections.some(sec => sec.enabled),
|
|
9292
9565
|
};
|
|
9293
9566
|
}),
|
|
9294
9567
|
}));
|
|
@@ -9327,9 +9600,11 @@ class FieldSelectionService {
|
|
|
9327
9600
|
groups: s.groups.map((g, gi) => {
|
|
9328
9601
|
if (gi !== groupIndex)
|
|
9329
9602
|
return g;
|
|
9603
|
+
const sections = this._recomputeEnabledFromChildren(this._toggleFieldAtPath(g.sections, sectionPath, 0, fieldIndex));
|
|
9330
9604
|
return {
|
|
9331
9605
|
...g,
|
|
9332
|
-
sections
|
|
9606
|
+
sections,
|
|
9607
|
+
enabled: sections.some(sec => sec.enabled),
|
|
9333
9608
|
};
|
|
9334
9609
|
}),
|
|
9335
9610
|
}));
|
|
@@ -9364,6 +9639,30 @@ class FieldSelectionService {
|
|
|
9364
9639
|
subsections: this._enableAllSections(s.subsections),
|
|
9365
9640
|
}));
|
|
9366
9641
|
}
|
|
9642
|
+
/**
|
|
9643
|
+
* Re-derives `enabled` bottom-up for every section in the list, from its own current
|
|
9644
|
+
* fields/subsections — mirroring "Master Toggle Reflects Children" (already true for the
|
|
9645
|
+
* GROUP toggle vs. its sections) at every level: a section is ON as long as at least one of
|
|
9646
|
+
* its fields is selected or a subsection is ON. Without this, re-selecting a single field
|
|
9647
|
+
* after its parent section was switched off left the section's own toggle stuck OFF even
|
|
9648
|
+
* though a child was visibly selected again.
|
|
9649
|
+
*/
|
|
9650
|
+
_recomputeEnabledFromChildren(sections) {
|
|
9651
|
+
return sections.map(s => {
|
|
9652
|
+
const subsections = this._recomputeEnabledFromChildren(s.subsections);
|
|
9653
|
+
// A locked field can't be deselected by the user and is always effectively active
|
|
9654
|
+
// regardless of its raw `selected` value — see _toggleSectionAtPath's enable branch,
|
|
9655
|
+
// which sets `selected: !f.isLocked` (locked fields keep `selected: false` but still
|
|
9656
|
+
// render, so they must still count here or their section would wrongly derive as OFF).
|
|
9657
|
+
const anyFieldActive = s.fields.some(f => f.selected || f.isLocked);
|
|
9658
|
+
const anySubsectionEnabled = subsections.some(sub => sub.enabled);
|
|
9659
|
+
return {
|
|
9660
|
+
...s,
|
|
9661
|
+
enabled: anyFieldActive || anySubsectionEnabled,
|
|
9662
|
+
subsections,
|
|
9663
|
+
};
|
|
9664
|
+
});
|
|
9665
|
+
}
|
|
9367
9666
|
_toggleSectionAtPath(sections, path, depth) {
|
|
9368
9667
|
return sections.map((s, i) => {
|
|
9369
9668
|
if (i !== path[depth])
|
|
@@ -9405,11 +9704,49 @@ class FieldSelectionService {
|
|
|
9405
9704
|
if (i !== sectionPath[depth])
|
|
9406
9705
|
return s;
|
|
9407
9706
|
if (depth === sectionPath.length - 1) {
|
|
9707
|
+
const target = s.fields[fieldIndex];
|
|
9708
|
+
if (!target || target.isLocked)
|
|
9709
|
+
return s;
|
|
9710
|
+
const newSelected = !target.selected;
|
|
9711
|
+
const masterName = target.fieldConfig?.type === 'GEOGRAPHY_DYNAMIC'
|
|
9712
|
+
? target.fieldConfig.name
|
|
9713
|
+
: null;
|
|
9714
|
+
const nodePrefix = masterName ? `${masterName}__node__` : null;
|
|
9715
|
+
// If the toggled field is itself a synthetic per-level node (name pattern
|
|
9716
|
+
// `${master}__node__${classCode}`), find its master so it can be auto-selected
|
|
9717
|
+
// below — a level can't be meaningfully "on" while its parent field is off.
|
|
9718
|
+
// classCode itself commonly contains underscores (e.g. "GREF.REGION_COUNTRY"), so the
|
|
9719
|
+
// suffix after "__node__" can't be restricted to a no-underscore character class — that
|
|
9720
|
+
// previously made this match fail for every real geography level, silently breaking the
|
|
9721
|
+
// master-field cascade below (Country/State/District showed checked in Field Selection
|
|
9722
|
+
// but the whole Address field stayed disabled and got pruned from the Configurator tree
|
|
9723
|
+
// and the actual form).
|
|
9724
|
+
const ownNodeMatch = /^(.*)__node__.+$/.exec(target.fieldConfig.name ?? '');
|
|
9725
|
+
const ownMasterName = ownNodeMatch ? ownNodeMatch[1] : null;
|
|
9408
9726
|
return {
|
|
9409
9727
|
...s,
|
|
9410
|
-
fields: s.fields.map((f, fi) =>
|
|
9411
|
-
|
|
9412
|
-
|
|
9728
|
+
fields: s.fields.map((f, fi) => {
|
|
9729
|
+
if (fi === fieldIndex) {
|
|
9730
|
+
return { ...f, selected: newSelected };
|
|
9731
|
+
}
|
|
9732
|
+
// Toggling the GEOGRAPHY_DYNAMIC master field cascades to its synthetic
|
|
9733
|
+
// per-level children so a level can never be left "selected" while the
|
|
9734
|
+
// parent field it belongs to is disabled (which the Configurator tree
|
|
9735
|
+
// treats as pruning the whole field, children included).
|
|
9736
|
+
if (nodePrefix && f.fieldConfig.name?.startsWith(nodePrefix) && !f.isLocked) {
|
|
9737
|
+
return { ...f, selected: newSelected };
|
|
9738
|
+
}
|
|
9739
|
+
// Turning a per-level node ON re-enables its (currently off) master field
|
|
9740
|
+
// for the same reason, in the opposite direction.
|
|
9741
|
+
if (ownMasterName &&
|
|
9742
|
+
newSelected &&
|
|
9743
|
+
f.fieldConfig.name === ownMasterName &&
|
|
9744
|
+
!f.selected &&
|
|
9745
|
+
!f.isLocked) {
|
|
9746
|
+
return { ...f, selected: true };
|
|
9747
|
+
}
|
|
9748
|
+
return f;
|
|
9749
|
+
}),
|
|
9413
9750
|
};
|
|
9414
9751
|
}
|
|
9415
9752
|
return {
|
|
@@ -9490,15 +9827,27 @@ class SelectionFieldNodeComponent {
|
|
|
9490
9827
|
* pre-configured by the developer and shouldn't be toggled by business users.
|
|
9491
9828
|
*/
|
|
9492
9829
|
hideToggleForOptionTypes = false;
|
|
9830
|
+
/**
|
|
9831
|
+
* Set when the enclosing section is off. A field can never be meaningfully "selected" while
|
|
9832
|
+
* its parent section is disabled — the Field Configuration step prunes the whole disabled
|
|
9833
|
+
* branch regardless of individual field state — so the checkbox is locked out here to make
|
|
9834
|
+
* that constraint visible instead of letting the user set a checked state that won't survive.
|
|
9835
|
+
*/
|
|
9836
|
+
disabled = false;
|
|
9837
|
+
onCheckedChange() {
|
|
9838
|
+
if (this.disabled)
|
|
9839
|
+
return;
|
|
9840
|
+
this.toggleSelected.emit();
|
|
9841
|
+
}
|
|
9493
9842
|
get isOptionDrivenType() {
|
|
9494
9843
|
return OPTION_DRIVEN_FIELD_TYPES.has((this.field?.fieldConfig?.type ?? '').toUpperCase());
|
|
9495
9844
|
}
|
|
9496
9845
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SelectionFieldNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9497
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: SelectionFieldNodeComponent, isStandalone: false, selector: "lib-selection-field-node", inputs: { field: "field", hideToggleForOptionTypes: "hideToggleForOptionTypes" }, outputs: { toggleSelected: "toggleSelected" }, ngImport: i0, template: "<div\r\n class=\"fb-selection-field\"\r\n [class.fb-selection-field--selected]=\"field.selected\"\r\n [class.fb-selection-field--locked]=\"field.isLocked\"\r\n [class.fb-selection-field--option-type]=\"hideToggleForOptionTypes && isOptionDrivenType\"\r\n>\r\n <span class=\"fb-selection-field__label\">\r\n {{ field.fieldConfig.label || field.fieldConfig.name }}\r\n </span>\r\n\r\n @if (field.isLocked) {\r\n <span class=\"fb-selection-field__lock\" title=\"This field is locked\">\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"></rect>\r\n <path d=\"M7 11V7a5 5 0 0 1 10 0v4\"></path>\r\n </svg>\r\n </span>\r\n } @else if (hideToggleForOptionTypes && isOptionDrivenType) {\r\n <!-- Option-driven type: toggle hidden by config, show a badge instead -->\r\n <span class=\"fb-selection-field__option-badge\" title=\"Options are pre-configured\">\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <circle cx=\"12\" cy=\"12\" r=\"10\"></circle>\r\n <polyline points=\"12 8 12 12 14 14\"></polyline>\r\n </svg>\r\n </span>\r\n } @else {\r\n <lib-checkbox\r\n [checked]=\"field.selected\"\r\n [disabled]=\"
|
|
9846
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: SelectionFieldNodeComponent, isStandalone: false, selector: "lib-selection-field-node", inputs: { field: "field", hideToggleForOptionTypes: "hideToggleForOptionTypes", disabled: "disabled" }, outputs: { toggleSelected: "toggleSelected" }, ngImport: i0, template: "<div\r\n class=\"fb-selection-field\"\r\n [class.fb-selection-field--selected]=\"field.selected\"\r\n [class.fb-selection-field--locked]=\"field.isLocked\"\r\n [class.fb-selection-field--option-type]=\"hideToggleForOptionTypes && isOptionDrivenType\"\r\n [class.fb-selection-field--parent-disabled]=\"disabled\"\r\n>\r\n <span class=\"fb-selection-field__label\">\r\n {{ field.fieldConfig.label || field.fieldConfig.name }}\r\n </span>\r\n\r\n @if (field.isLocked) {\r\n <span class=\"fb-selection-field__lock\" title=\"This field is locked\">\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"></rect>\r\n <path d=\"M7 11V7a5 5 0 0 1 10 0v4\"></path>\r\n </svg>\r\n </span>\r\n } @else if (hideToggleForOptionTypes && isOptionDrivenType) {\r\n <!-- Option-driven type: toggle hidden by config, show a badge instead -->\r\n <span class=\"fb-selection-field__option-badge\" title=\"Options are pre-configured\">\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <circle cx=\"12\" cy=\"12\" r=\"10\"></circle>\r\n <polyline points=\"12 8 12 12 14 14\"></polyline>\r\n </svg>\r\n </span>\r\n } @else {\r\n <lib-checkbox\r\n [checked]=\"field.selected\"\r\n [disabled]=\"disabled\"\r\n (checkedChange)=\"onCheckedChange()\"\r\n />\r\n }\r\n</div>\r\n", styles: [".fb-selection-field{display:flex;align-items:center;justify-content:space-between;padding:var(--fb-fs-field-padding, 8px 12px);margin-bottom:var(--fb-fs-field-gap, 2px);border-radius:var(--fb-fs-field-radius, 6px);background:var(--fb-fs-field-bg, #ffffff);border:1px solid var(--fb-fs-field-border, #e5e7eb);transition:all .15s}.fb-selection-field:hover:not(.fb-selection-field--locked):not(.fb-selection-field--option-type){border-color:var(--fb-fs-field-hover-border, #3b82f6);background:var(--fb-fs-field-hover-bg, #f8fafc)}.fb-selection-field--selected{background:var(--fb-fs-field-selected-bg, #eff6ff);border-color:var(--fb-fs-field-selected-border, #bfdbfe)}.fb-selection-field--locked{background:var(--fb-fs-field-locked-bg, #f3f4f6);cursor:not-allowed}.fb-selection-field--locked .fb-selection-field__label{color:var(--fb-fs-field-locked-color, #6b7280);font-weight:500}.fb-selection-field--parent-disabled{opacity:.55;cursor:not-allowed}.fb-selection-field--option-type{background:var(--fb-fs-field-option-type-bg, #f9fafb);border-color:var(--fb-fs-field-option-type-border, #e5e7eb);cursor:default}.fb-selection-field--option-type .fb-selection-field__label{color:var(--fb-fs-field-option-type-color, #374151)}.fb-selection-field__label{font-size:var(--fb-fs-field-label-size, 13px);color:var(--fb-fs-field-label-color, #111827);flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fb-selection-field__lock{display:flex;color:var(--fb-fs-lock-color, #9ca3af);padding:2px}.fb-selection-field__option-badge{display:flex;align-items:center;color:var(--fb-fs-option-badge-color, #9ca3af);padding:2px;flex-shrink:0}\n"], dependencies: [{ kind: "component", type: CheckboxComponent, selector: "lib-checkbox", inputs: ["config", "labels", "label", "checked", "disabled", "required", "indeterminate", "options", "labelPosition", "color", "borderRadius", "value", "errorMessage", "width", "height", "fontSize", "fontWeight", "labelColor", "labelFontSize", "labelFontWeight", "gap", "fontFamily", "backgroundColor", "borderColor", "borderWidth", "padding", "placeholderColor", "focusBorderColor", "errorColor", "disabledBackgroundColor", "disabledColor", "boxShadow", "size", "checkedColor", "uncheckedColor", "groupLabelColor", "groupLabelFontSize", "groupLabelFontWeight"], outputs: ["checkedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9498
9847
|
}
|
|
9499
9848
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SelectionFieldNodeComponent, decorators: [{
|
|
9500
9849
|
type: Component,
|
|
9501
|
-
args: [{ selector: 'lib-selection-field-node', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\r\n class=\"fb-selection-field\"\r\n [class.fb-selection-field--selected]=\"field.selected\"\r\n [class.fb-selection-field--locked]=\"field.isLocked\"\r\n [class.fb-selection-field--option-type]=\"hideToggleForOptionTypes && isOptionDrivenType\"\r\n>\r\n <span class=\"fb-selection-field__label\">\r\n {{ field.fieldConfig.label || field.fieldConfig.name }}\r\n </span>\r\n\r\n @if (field.isLocked) {\r\n <span class=\"fb-selection-field__lock\" title=\"This field is locked\">\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"></rect>\r\n <path d=\"M7 11V7a5 5 0 0 1 10 0v4\"></path>\r\n </svg>\r\n </span>\r\n } @else if (hideToggleForOptionTypes && isOptionDrivenType) {\r\n <!-- Option-driven type: toggle hidden by config, show a badge instead -->\r\n <span class=\"fb-selection-field__option-badge\" title=\"Options are pre-configured\">\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <circle cx=\"12\" cy=\"12\" r=\"10\"></circle>\r\n <polyline points=\"12 8 12 12 14 14\"></polyline>\r\n </svg>\r\n </span>\r\n } @else {\r\n <lib-checkbox\r\n [checked]=\"field.selected\"\r\n [disabled]=\"
|
|
9850
|
+
args: [{ selector: 'lib-selection-field-node', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\r\n class=\"fb-selection-field\"\r\n [class.fb-selection-field--selected]=\"field.selected\"\r\n [class.fb-selection-field--locked]=\"field.isLocked\"\r\n [class.fb-selection-field--option-type]=\"hideToggleForOptionTypes && isOptionDrivenType\"\r\n [class.fb-selection-field--parent-disabled]=\"disabled\"\r\n>\r\n <span class=\"fb-selection-field__label\">\r\n {{ field.fieldConfig.label || field.fieldConfig.name }}\r\n </span>\r\n\r\n @if (field.isLocked) {\r\n <span class=\"fb-selection-field__lock\" title=\"This field is locked\">\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"></rect>\r\n <path d=\"M7 11V7a5 5 0 0 1 10 0v4\"></path>\r\n </svg>\r\n </span>\r\n } @else if (hideToggleForOptionTypes && isOptionDrivenType) {\r\n <!-- Option-driven type: toggle hidden by config, show a badge instead -->\r\n <span class=\"fb-selection-field__option-badge\" title=\"Options are pre-configured\">\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <circle cx=\"12\" cy=\"12\" r=\"10\"></circle>\r\n <polyline points=\"12 8 12 12 14 14\"></polyline>\r\n </svg>\r\n </span>\r\n } @else {\r\n <lib-checkbox\r\n [checked]=\"field.selected\"\r\n [disabled]=\"disabled\"\r\n (checkedChange)=\"onCheckedChange()\"\r\n />\r\n }\r\n</div>\r\n", styles: [".fb-selection-field{display:flex;align-items:center;justify-content:space-between;padding:var(--fb-fs-field-padding, 8px 12px);margin-bottom:var(--fb-fs-field-gap, 2px);border-radius:var(--fb-fs-field-radius, 6px);background:var(--fb-fs-field-bg, #ffffff);border:1px solid var(--fb-fs-field-border, #e5e7eb);transition:all .15s}.fb-selection-field:hover:not(.fb-selection-field--locked):not(.fb-selection-field--option-type){border-color:var(--fb-fs-field-hover-border, #3b82f6);background:var(--fb-fs-field-hover-bg, #f8fafc)}.fb-selection-field--selected{background:var(--fb-fs-field-selected-bg, #eff6ff);border-color:var(--fb-fs-field-selected-border, #bfdbfe)}.fb-selection-field--locked{background:var(--fb-fs-field-locked-bg, #f3f4f6);cursor:not-allowed}.fb-selection-field--locked .fb-selection-field__label{color:var(--fb-fs-field-locked-color, #6b7280);font-weight:500}.fb-selection-field--parent-disabled{opacity:.55;cursor:not-allowed}.fb-selection-field--option-type{background:var(--fb-fs-field-option-type-bg, #f9fafb);border-color:var(--fb-fs-field-option-type-border, #e5e7eb);cursor:default}.fb-selection-field--option-type .fb-selection-field__label{color:var(--fb-fs-field-option-type-color, #374151)}.fb-selection-field__label{font-size:var(--fb-fs-field-label-size, 13px);color:var(--fb-fs-field-label-color, #111827);flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fb-selection-field__lock{display:flex;color:var(--fb-fs-lock-color, #9ca3af);padding:2px}.fb-selection-field__option-badge{display:flex;align-items:center;color:var(--fb-fs-option-badge-color, #9ca3af);padding:2px;flex-shrink:0}\n"] }]
|
|
9502
9851
|
}], propDecorators: { field: [{
|
|
9503
9852
|
type: Input,
|
|
9504
9853
|
args: [{ required: true }]
|
|
@@ -9506,6 +9855,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
9506
9855
|
type: Output
|
|
9507
9856
|
}], hideToggleForOptionTypes: [{
|
|
9508
9857
|
type: Input
|
|
9858
|
+
}], disabled: [{
|
|
9859
|
+
type: Input
|
|
9509
9860
|
}] } });
|
|
9510
9861
|
|
|
9511
9862
|
class SelectionSectionNodeComponent {
|
|
@@ -9548,15 +9899,38 @@ class SelectionSectionNodeComponent {
|
|
|
9548
9899
|
trackByFieldIndex(index) {
|
|
9549
9900
|
return index;
|
|
9550
9901
|
}
|
|
9902
|
+
/**
|
|
9903
|
+
* A GEOGRAPHY_DYNAMIC field's per-level nodes (Country/State/District/...) are built as flat
|
|
9904
|
+
* entries immediately following their master in `section.fields` (see
|
|
9905
|
+
* FormSchemaTreeService._buildGeographyFieldNodes) — there's no real nested schema structure
|
|
9906
|
+
* to walk, so "master" here just means the nearest preceding non-level field.
|
|
9907
|
+
*/
|
|
9908
|
+
masterFieldFor(index) {
|
|
9909
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
9910
|
+
const candidate = this.section.fields[i];
|
|
9911
|
+
if (!candidate.isGeographyLevel)
|
|
9912
|
+
return candidate;
|
|
9913
|
+
}
|
|
9914
|
+
return null;
|
|
9915
|
+
}
|
|
9916
|
+
/** True for a geography level whose master field is currently unselected — locked out so the
|
|
9917
|
+
* user can't leave an individually-checked level under an unchecked (and therefore pruned)
|
|
9918
|
+
* master; see masterFieldFor(). */
|
|
9919
|
+
isFieldDisabled(field, index) {
|
|
9920
|
+
if (!field.isGeographyLevel)
|
|
9921
|
+
return false;
|
|
9922
|
+
const master = this.masterFieldFor(index);
|
|
9923
|
+
return !!master && !master.selected;
|
|
9924
|
+
}
|
|
9551
9925
|
trackBySubsectionIndex(index) {
|
|
9552
9926
|
return index;
|
|
9553
9927
|
}
|
|
9554
9928
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SelectionSectionNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9555
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: SelectionSectionNodeComponent, isStandalone: false, selector: "lib-selection-section-node", inputs: { section: "section", sectionIndex: "sectionIndex", depth: "depth", hideToggleForOptionTypes: "hideToggleForOptionTypes" }, outputs: { toggleEnabled: "toggleEnabled", toggleExpanded: "toggleExpanded", fieldToggle: "fieldToggle" }, ngImport: i0, template: "<div\r\n class=\"fb-selection-section\"\r\n [class.fb-selection-section--disabled]=\"!section.enabled\"\r\n [class.fb-selection-section--nested]=\"depth > 0\"\r\n>\r\n <div class=\"fb-selection-section__header\">\r\n <button\r\n class=\"fb-selection-section__expand-btn\"\r\n (click)=\"onToggleExpanded()\"\r\n [attr.aria-expanded]=\"section.expanded\"\r\n type=\"button\"\r\n >\r\n <svg\r\n class=\"fb-selection-section__chevron\"\r\n [class.fb-selection-section__chevron--open]=\"section.expanded\"\r\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"6 9 12 15 18 9\" stroke=\"currentColor\" stroke-width=\"2\"\r\n stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n </svg>\r\n </button>\r\n\r\n <div class=\"fb-selection-section__meta\">\r\n <span class=\"fb-selection-section__label\">{{ section.label }}</span>\r\n </div>\r\n\r\n @if (isLocked) {\r\n <span class=\"fb-selection-section__lock\" title=\"This section is locked\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"></rect>\r\n <path d=\"M7 11V7a5 5 0 0 1 10 0v4\"></path>\r\n </svg>\r\n </span>\r\n } @else {\r\n <lib-toggle\r\n [checked]=\"section.enabled\"\r\n [disabled]=\"false\"\r\n [attr.aria-label]=\"'Enable ' + section.label\"\r\n (toggleChange)=\"onToggleEnabled()\"\r\n />\r\n }\r\n </div>\r\n\r\n @if (section.expanded) {\r\n <div class=\"fb-selection-section__body\">\r\n @for (field of section.fields; track trackByFieldIndex($index); let fi = $index) {\r\n <lib-selection-field-node\r\n [field]=\"field\"\r\n [hideToggleForOptionTypes]=\"hideToggleForOptionTypes\"\r\n (toggleSelected)=\"onFieldToggle(fi)\"\r\n />\r\n }\r\n\r\n @for (sub of section.subsections; track trackBySubsectionIndex($index); let si = $index) {\r\n <lib-selection-section-node\r\n [section]=\"sub\"\r\n [sectionIndex]=\"si\"\r\n [depth]=\"depth + 1\"\r\n [hideToggleForOptionTypes]=\"hideToggleForOptionTypes\"\r\n (toggleEnabled)=\"onSubsectionToggleEnabled(si, $event)\"\r\n (toggleExpanded)=\"onSubsectionToggleExpanded(si, $event)\"\r\n (fieldToggle)=\"onSubsectionFieldToggle(si, $event)\"\r\n />\r\n }\r\n </div>\r\n }\r\n</div>\r\n", styles: [".fb-selection-section{border:1px solid var(--fb-fs-section-border, #e5e7eb);border-radius:var(--fb-fs-section-radius, 10px);background:var(--fb-fs-section-bg, #ffffff);overflow:hidden}@media(max-width:640px){.fb-selection-section{border-radius:6px}}.fb-selection-section--disabled .fb-selection-section__label{opacity:.55}.fb-selection-section--nested{border-color:var(--fb-fs-section-nested-border, #f3f4f6);background:var(--fb-fs-section-nested-bg, #fafafa)}.fb-selection-section__header{display:flex;align-items:center;gap:8px;padding:var(--fb-fs-section-header-padding, 10px 14px);cursor:pointer;-webkit-user-select:none;user-select:none}@media(max-width:640px){.fb-selection-section__header{padding:8px 10px;gap:6px}}.fb-selection-section__header:hover{background:var(--fb-fs-section-header-hover, #f9fafb)}.fb-selection-section__expand-btn{background:none;border:none;cursor:pointer;padding:2px;display:flex;color:var(--fb-fs-chevron-color, #9ca3af);flex-shrink:0}@media(max-width:640px){.fb-selection-section__expand-btn{padding:0}}.fb-selection-section__chevron{transition:transform .2s}.fb-selection-section__chevron--open{transform:rotate(180deg)}.fb-selection-section__meta{flex:1;display:flex;align-items:center;gap:8px;min-width:0}@media(max-width:640px){.fb-selection-section__meta{gap:6px}}.fb-selection-section__label{font-size:var(--fb-fs-section-label-size, 13px);font-weight:600;color:var(--fb-fs-section-label-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}@media(max-width:640px){.fb-selection-section__label{font-size:12px}}.fb-selection-section__lock{display:flex;color:var(--fb-fs-lock-color, #9ca3af);padding:2px}.fb-selection-section__body{padding:var(--fb-fs-section-body-padding, 4px 14px 14px 28px);display:flex;flex-direction:column;gap:var(--fb-fs-field-gap, 6px)}@media(max-width:768px){.fb-selection-section__body{padding:4px 12px 12px 24px;gap:5px}}@media(max-width:640px){.fb-selection-section__body{padding:4px 10px 10px 20px;gap:4px}}\n"], dependencies: [{ kind: "component", type: ToggleComponent, selector: "lib-toggle", inputs: ["config", "labels", "label", "checked", "disabled", "required", "labelPosition", "color", "labelColor", "uncheckedColor", "checkedColor", "thumbColor", "checkedThumbColor", "fontSize", "fontWeight", "fontFamily", "toggleWidth", "toggleHeight", "gap", "sliderColor", "labelFontSize", "labelFontWeight", "disabledColor"], outputs: ["toggleChange"] }, { kind: "component", type: SelectionSectionNodeComponent, selector: "lib-selection-section-node", inputs: ["section", "sectionIndex", "depth", "hideToggleForOptionTypes"], outputs: ["toggleEnabled", "toggleExpanded", "fieldToggle"] }, { kind: "component", type: SelectionFieldNodeComponent, selector: "lib-selection-field-node", inputs: ["field", "hideToggleForOptionTypes"], outputs: ["toggleSelected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9929
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: SelectionSectionNodeComponent, isStandalone: false, selector: "lib-selection-section-node", inputs: { section: "section", sectionIndex: "sectionIndex", depth: "depth", hideToggleForOptionTypes: "hideToggleForOptionTypes" }, outputs: { toggleEnabled: "toggleEnabled", toggleExpanded: "toggleExpanded", fieldToggle: "fieldToggle" }, ngImport: i0, template: "<div\r\n class=\"fb-selection-section\"\r\n [class.fb-selection-section--disabled]=\"!section.enabled\"\r\n [class.fb-selection-section--nested]=\"depth > 0\"\r\n>\r\n <div class=\"fb-selection-section__header\">\r\n <button\r\n class=\"fb-selection-section__expand-btn\"\r\n (click)=\"onToggleExpanded()\"\r\n [attr.aria-expanded]=\"section.expanded\"\r\n type=\"button\"\r\n >\r\n <svg\r\n class=\"fb-selection-section__chevron\"\r\n [class.fb-selection-section__chevron--open]=\"section.expanded\"\r\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"6 9 12 15 18 9\" stroke=\"currentColor\" stroke-width=\"2\"\r\n stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n </svg>\r\n </button>\r\n\r\n <div class=\"fb-selection-section__meta\">\r\n <span class=\"fb-selection-section__label\">{{ section.label }}</span>\r\n </div>\r\n\r\n @if (isLocked) {\r\n <span class=\"fb-selection-section__lock\" title=\"This section is locked\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"></rect>\r\n <path d=\"M7 11V7a5 5 0 0 1 10 0v4\"></path>\r\n </svg>\r\n </span>\r\n } @else {\r\n <lib-toggle\r\n [checked]=\"section.enabled\"\r\n [disabled]=\"false\"\r\n [attr.aria-label]=\"'Enable ' + section.label\"\r\n (toggleChange)=\"onToggleEnabled()\"\r\n />\r\n }\r\n </div>\r\n\r\n @if (section.expanded) {\r\n <div class=\"fb-selection-section__body\">\r\n @for (field of section.fields; track trackByFieldIndex($index); let fi = $index) {\r\n <lib-selection-field-node\r\n [field]=\"field\"\r\n [hideToggleForOptionTypes]=\"hideToggleForOptionTypes\"\r\n [disabled]=\"isFieldDisabled(field, fi)\"\r\n [class.fb-selection-section__field--geo-level]=\"field.isGeographyLevel\"\r\n (toggleSelected)=\"onFieldToggle(fi)\"\r\n />\r\n }\r\n\r\n @for (sub of section.subsections; track trackBySubsectionIndex($index); let si = $index) {\r\n <lib-selection-section-node\r\n [section]=\"sub\"\r\n [sectionIndex]=\"si\"\r\n [depth]=\"depth + 1\"\r\n [hideToggleForOptionTypes]=\"hideToggleForOptionTypes\"\r\n (toggleEnabled)=\"onSubsectionToggleEnabled(si, $event)\"\r\n (toggleExpanded)=\"onSubsectionToggleExpanded(si, $event)\"\r\n (fieldToggle)=\"onSubsectionFieldToggle(si, $event)\"\r\n />\r\n }\r\n </div>\r\n }\r\n</div>\r\n", styles: [".fb-selection-section{border:1px solid var(--fb-fs-section-border, #e5e7eb);border-radius:var(--fb-fs-section-radius, 10px);background:var(--fb-fs-section-bg, #ffffff);overflow:hidden}@media(max-width:640px){.fb-selection-section{border-radius:6px}}.fb-selection-section--disabled .fb-selection-section__label{opacity:.55}.fb-selection-section--nested{border-color:var(--fb-fs-section-nested-border, #f3f4f6);background:var(--fb-fs-section-nested-bg, #fafafa)}.fb-selection-section__header{display:flex;align-items:center;gap:8px;padding:var(--fb-fs-section-header-padding, 10px 14px);cursor:pointer;-webkit-user-select:none;user-select:none}@media(max-width:640px){.fb-selection-section__header{padding:8px 10px;gap:6px}}.fb-selection-section__header:hover{background:var(--fb-fs-section-header-hover, #f9fafb)}.fb-selection-section__expand-btn{background:none;border:none;cursor:pointer;padding:2px;display:flex;color:var(--fb-fs-chevron-color, #9ca3af);flex-shrink:0}@media(max-width:640px){.fb-selection-section__expand-btn{padding:0}}.fb-selection-section__chevron{transition:transform .2s}.fb-selection-section__chevron--open{transform:rotate(180deg)}.fb-selection-section__meta{flex:1;display:flex;align-items:center;gap:8px;min-width:0}@media(max-width:640px){.fb-selection-section__meta{gap:6px}}.fb-selection-section__label{font-size:var(--fb-fs-section-label-size, 13px);font-weight:600;color:var(--fb-fs-section-label-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}@media(max-width:640px){.fb-selection-section__label{font-size:12px}}.fb-selection-section__lock{display:flex;color:var(--fb-fs-lock-color, #9ca3af);padding:2px}.fb-selection-section__body{padding:var(--fb-fs-section-body-padding, 4px 14px 14px 28px);display:flex;flex-direction:column;gap:var(--fb-fs-field-gap, 6px)}@media(max-width:768px){.fb-selection-section__body{padding:4px 12px 12px 24px;gap:5px}}@media(max-width:640px){.fb-selection-section__body{padding:4px 10px 10px 20px;gap:4px}}.fb-selection-section__body ::ng-deep .fb-selection-section__field--geo-level{margin-left:var(--fb-fs-geo-level-indent, 20px);position:relative}.fb-selection-section__body ::ng-deep .fb-selection-section__field--geo-level:before{content:\"\";position:absolute;left:-12px;top:50%;width:8px;height:1px;background:var(--fb-fs-section-nested-border, #d1d5db)}\n"], dependencies: [{ kind: "component", type: ToggleComponent, selector: "lib-toggle", inputs: ["config", "labels", "label", "checked", "disabled", "required", "labelPosition", "color", "labelColor", "uncheckedColor", "checkedColor", "thumbColor", "checkedThumbColor", "fontSize", "fontWeight", "fontFamily", "toggleWidth", "toggleHeight", "gap", "sliderColor", "labelFontSize", "labelFontWeight", "disabledColor"], outputs: ["toggleChange"] }, { kind: "component", type: SelectionSectionNodeComponent, selector: "lib-selection-section-node", inputs: ["section", "sectionIndex", "depth", "hideToggleForOptionTypes"], outputs: ["toggleEnabled", "toggleExpanded", "fieldToggle"] }, { kind: "component", type: SelectionFieldNodeComponent, selector: "lib-selection-field-node", inputs: ["field", "hideToggleForOptionTypes", "disabled"], outputs: ["toggleSelected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9556
9930
|
}
|
|
9557
9931
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SelectionSectionNodeComponent, decorators: [{
|
|
9558
9932
|
type: Component,
|
|
9559
|
-
args: [{ selector: 'lib-selection-section-node', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\r\n class=\"fb-selection-section\"\r\n [class.fb-selection-section--disabled]=\"!section.enabled\"\r\n [class.fb-selection-section--nested]=\"depth > 0\"\r\n>\r\n <div class=\"fb-selection-section__header\">\r\n <button\r\n class=\"fb-selection-section__expand-btn\"\r\n (click)=\"onToggleExpanded()\"\r\n [attr.aria-expanded]=\"section.expanded\"\r\n type=\"button\"\r\n >\r\n <svg\r\n class=\"fb-selection-section__chevron\"\r\n [class.fb-selection-section__chevron--open]=\"section.expanded\"\r\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"6 9 12 15 18 9\" stroke=\"currentColor\" stroke-width=\"2\"\r\n stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n </svg>\r\n </button>\r\n\r\n <div class=\"fb-selection-section__meta\">\r\n <span class=\"fb-selection-section__label\">{{ section.label }}</span>\r\n </div>\r\n\r\n @if (isLocked) {\r\n <span class=\"fb-selection-section__lock\" title=\"This section is locked\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"></rect>\r\n <path d=\"M7 11V7a5 5 0 0 1 10 0v4\"></path>\r\n </svg>\r\n </span>\r\n } @else {\r\n <lib-toggle\r\n [checked]=\"section.enabled\"\r\n [disabled]=\"false\"\r\n [attr.aria-label]=\"'Enable ' + section.label\"\r\n (toggleChange)=\"onToggleEnabled()\"\r\n />\r\n }\r\n </div>\r\n\r\n @if (section.expanded) {\r\n <div class=\"fb-selection-section__body\">\r\n @for (field of section.fields; track trackByFieldIndex($index); let fi = $index) {\r\n <lib-selection-field-node\r\n [field]=\"field\"\r\n [hideToggleForOptionTypes]=\"hideToggleForOptionTypes\"\r\n (toggleSelected)=\"onFieldToggle(fi)\"\r\n />\r\n }\r\n\r\n @for (sub of section.subsections; track trackBySubsectionIndex($index); let si = $index) {\r\n <lib-selection-section-node\r\n [section]=\"sub\"\r\n [sectionIndex]=\"si\"\r\n [depth]=\"depth + 1\"\r\n [hideToggleForOptionTypes]=\"hideToggleForOptionTypes\"\r\n (toggleEnabled)=\"onSubsectionToggleEnabled(si, $event)\"\r\n (toggleExpanded)=\"onSubsectionToggleExpanded(si, $event)\"\r\n (fieldToggle)=\"onSubsectionFieldToggle(si, $event)\"\r\n />\r\n }\r\n </div>\r\n }\r\n</div>\r\n", styles: [".fb-selection-section{border:1px solid var(--fb-fs-section-border, #e5e7eb);border-radius:var(--fb-fs-section-radius, 10px);background:var(--fb-fs-section-bg, #ffffff);overflow:hidden}@media(max-width:640px){.fb-selection-section{border-radius:6px}}.fb-selection-section--disabled .fb-selection-section__label{opacity:.55}.fb-selection-section--nested{border-color:var(--fb-fs-section-nested-border, #f3f4f6);background:var(--fb-fs-section-nested-bg, #fafafa)}.fb-selection-section__header{display:flex;align-items:center;gap:8px;padding:var(--fb-fs-section-header-padding, 10px 14px);cursor:pointer;-webkit-user-select:none;user-select:none}@media(max-width:640px){.fb-selection-section__header{padding:8px 10px;gap:6px}}.fb-selection-section__header:hover{background:var(--fb-fs-section-header-hover, #f9fafb)}.fb-selection-section__expand-btn{background:none;border:none;cursor:pointer;padding:2px;display:flex;color:var(--fb-fs-chevron-color, #9ca3af);flex-shrink:0}@media(max-width:640px){.fb-selection-section__expand-btn{padding:0}}.fb-selection-section__chevron{transition:transform .2s}.fb-selection-section__chevron--open{transform:rotate(180deg)}.fb-selection-section__meta{flex:1;display:flex;align-items:center;gap:8px;min-width:0}@media(max-width:640px){.fb-selection-section__meta{gap:6px}}.fb-selection-section__label{font-size:var(--fb-fs-section-label-size, 13px);font-weight:600;color:var(--fb-fs-section-label-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}@media(max-width:640px){.fb-selection-section__label{font-size:12px}}.fb-selection-section__lock{display:flex;color:var(--fb-fs-lock-color, #9ca3af);padding:2px}.fb-selection-section__body{padding:var(--fb-fs-section-body-padding, 4px 14px 14px 28px);display:flex;flex-direction:column;gap:var(--fb-fs-field-gap, 6px)}@media(max-width:768px){.fb-selection-section__body{padding:4px 12px 12px 24px;gap:5px}}@media(max-width:640px){.fb-selection-section__body{padding:4px 10px 10px 20px;gap:4px}}\n"] }]
|
|
9933
|
+
args: [{ selector: 'lib-selection-section-node', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\r\n class=\"fb-selection-section\"\r\n [class.fb-selection-section--disabled]=\"!section.enabled\"\r\n [class.fb-selection-section--nested]=\"depth > 0\"\r\n>\r\n <div class=\"fb-selection-section__header\">\r\n <button\r\n class=\"fb-selection-section__expand-btn\"\r\n (click)=\"onToggleExpanded()\"\r\n [attr.aria-expanded]=\"section.expanded\"\r\n type=\"button\"\r\n >\r\n <svg\r\n class=\"fb-selection-section__chevron\"\r\n [class.fb-selection-section__chevron--open]=\"section.expanded\"\r\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"6 9 12 15 18 9\" stroke=\"currentColor\" stroke-width=\"2\"\r\n stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n </svg>\r\n </button>\r\n\r\n <div class=\"fb-selection-section__meta\">\r\n <span class=\"fb-selection-section__label\">{{ section.label }}</span>\r\n </div>\r\n\r\n @if (isLocked) {\r\n <span class=\"fb-selection-section__lock\" title=\"This section is locked\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"></rect>\r\n <path d=\"M7 11V7a5 5 0 0 1 10 0v4\"></path>\r\n </svg>\r\n </span>\r\n } @else {\r\n <lib-toggle\r\n [checked]=\"section.enabled\"\r\n [disabled]=\"false\"\r\n [attr.aria-label]=\"'Enable ' + section.label\"\r\n (toggleChange)=\"onToggleEnabled()\"\r\n />\r\n }\r\n </div>\r\n\r\n @if (section.expanded) {\r\n <div class=\"fb-selection-section__body\">\r\n @for (field of section.fields; track trackByFieldIndex($index); let fi = $index) {\r\n <lib-selection-field-node\r\n [field]=\"field\"\r\n [hideToggleForOptionTypes]=\"hideToggleForOptionTypes\"\r\n [disabled]=\"isFieldDisabled(field, fi)\"\r\n [class.fb-selection-section__field--geo-level]=\"field.isGeographyLevel\"\r\n (toggleSelected)=\"onFieldToggle(fi)\"\r\n />\r\n }\r\n\r\n @for (sub of section.subsections; track trackBySubsectionIndex($index); let si = $index) {\r\n <lib-selection-section-node\r\n [section]=\"sub\"\r\n [sectionIndex]=\"si\"\r\n [depth]=\"depth + 1\"\r\n [hideToggleForOptionTypes]=\"hideToggleForOptionTypes\"\r\n (toggleEnabled)=\"onSubsectionToggleEnabled(si, $event)\"\r\n (toggleExpanded)=\"onSubsectionToggleExpanded(si, $event)\"\r\n (fieldToggle)=\"onSubsectionFieldToggle(si, $event)\"\r\n />\r\n }\r\n </div>\r\n }\r\n</div>\r\n", styles: [".fb-selection-section{border:1px solid var(--fb-fs-section-border, #e5e7eb);border-radius:var(--fb-fs-section-radius, 10px);background:var(--fb-fs-section-bg, #ffffff);overflow:hidden}@media(max-width:640px){.fb-selection-section{border-radius:6px}}.fb-selection-section--disabled .fb-selection-section__label{opacity:.55}.fb-selection-section--nested{border-color:var(--fb-fs-section-nested-border, #f3f4f6);background:var(--fb-fs-section-nested-bg, #fafafa)}.fb-selection-section__header{display:flex;align-items:center;gap:8px;padding:var(--fb-fs-section-header-padding, 10px 14px);cursor:pointer;-webkit-user-select:none;user-select:none}@media(max-width:640px){.fb-selection-section__header{padding:8px 10px;gap:6px}}.fb-selection-section__header:hover{background:var(--fb-fs-section-header-hover, #f9fafb)}.fb-selection-section__expand-btn{background:none;border:none;cursor:pointer;padding:2px;display:flex;color:var(--fb-fs-chevron-color, #9ca3af);flex-shrink:0}@media(max-width:640px){.fb-selection-section__expand-btn{padding:0}}.fb-selection-section__chevron{transition:transform .2s}.fb-selection-section__chevron--open{transform:rotate(180deg)}.fb-selection-section__meta{flex:1;display:flex;align-items:center;gap:8px;min-width:0}@media(max-width:640px){.fb-selection-section__meta{gap:6px}}.fb-selection-section__label{font-size:var(--fb-fs-section-label-size, 13px);font-weight:600;color:var(--fb-fs-section-label-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}@media(max-width:640px){.fb-selection-section__label{font-size:12px}}.fb-selection-section__lock{display:flex;color:var(--fb-fs-lock-color, #9ca3af);padding:2px}.fb-selection-section__body{padding:var(--fb-fs-section-body-padding, 4px 14px 14px 28px);display:flex;flex-direction:column;gap:var(--fb-fs-field-gap, 6px)}@media(max-width:768px){.fb-selection-section__body{padding:4px 12px 12px 24px;gap:5px}}@media(max-width:640px){.fb-selection-section__body{padding:4px 10px 10px 20px;gap:4px}}.fb-selection-section__body ::ng-deep .fb-selection-section__field--geo-level{margin-left:var(--fb-fs-geo-level-indent, 20px);position:relative}.fb-selection-section__body ::ng-deep .fb-selection-section__field--geo-level:before{content:\"\";position:absolute;left:-12px;top:50%;width:8px;height:1px;background:var(--fb-fs-section-nested-border, #d1d5db)}\n"] }]
|
|
9560
9934
|
}], propDecorators: { section: [{
|
|
9561
9935
|
type: Input,
|
|
9562
9936
|
args: [{ required: true }]
|
|
@@ -12364,7 +12738,7 @@ class FilterSidebarComponent {
|
|
|
12364
12738
|
useExisting: forwardRef(() => FilterSidebarComponent),
|
|
12365
12739
|
multi: true
|
|
12366
12740
|
}
|
|
12367
|
-
], ngImport: i0, template: "<div class=\"cc-filter-sidebar\" [ngStyle]=\"sidebarStyles\" [class.collapsed]=\"isCollapsed\">\r\n\r\n <!-- Header -->\r\n <div class=\"cc-filter-header\" *ngIf=\"config.header?.visible !== false\">\r\n <div class=\"cc-header-content\">\r\n <span class=\"cc-header-title\" *ngIf=\"config.header?.title\">\r\n <span *ngIf=\"config.header?.icon\" [class]=\"config.header?.icon\" class=\"cc-header-icon material-icons\">\r\n <!-- {{config.header?.icon }} -->\r\n </span>\r\n {{ config.header?.title }}\r\n </span>\r\n <ng-content select=\"[header-actions]\"></ng-content>\r\n </div>\r\n <button type=\"button\" *ngIf=\"config.settings?.collapsible\" (click)=\"toggleCollapse()\" class=\"cc-icon-btn\"\r\n [attr.aria-label]=\"isCollapsed ? config.labels?.expandAriaLabel : config.labels?.collapseAriaLabel\">\r\n <span class=\"material-icons\">{{ isCollapsed ? 'menu_open' : 'menu' }}</span>\r\n </button>\r\n <button type=\"button\" *ngIf=\"config.header?.showClose && !isCollapsed\" (click)=\"onClose()\"\r\n class=\"cc-icon-btn cc-close-btn\" [attr.aria-label]=\"config.labels?.closeAriaLabel\">\r\n <span class=\"material-icons\">close</span>\r\n </button>\r\n <!-- Code snippet debug (optional) -->\r\n <button type=\"button\" *ngIf=\"config.settings?.showCodeSnippet\" (click)=\"onShowCodeSnippet()\" class=\"cc-icon-btn\"\r\n [attr.aria-label]=\"config.labels?.codeSnippetAriaLabel\">\r\n <span class=\"material-icons\">code</span>\r\n </button>\r\n </div>\r\n\r\n <!-- Content (Scrollable) -->\r\n <div class=\"cc-filter-content\" [class.hidden]=\"isCollapsed\">\r\n <ng-container *ngFor=\"let item of config.items; trackBy: trackByFn\">\r\n <ng-container *ngTemplateOutlet=\"itemTemplate; context: { item: item }\"></ng-container>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"cc-filter-footer\" *ngIf=\"config.footer?.visible !== false && !isCollapsed\">\r\n <lib-button variant=\"outline\" color=\"warn\" class=\"cc-btn-clear\"\r\n *ngIf=\"config.footer?.clearButton?.visible !== false\" (click)=\"clearFilters()\">\r\n {{ config.footer?.clearButton?.label }}\r\n </lib-button>\r\n <lib-button variant=\"primary\" color=\"warn\" class=\"cc-btn-apply\"\r\n *ngIf=\"config.footer?.applyButton?.visible !== false\" [disabled]=\"config.footer?.applyButton?.disabled ?? false\"\r\n (click)=\"applyFilters()\">\r\n {{ config.footer?.applyButton?.label }}\r\n </lib-button>\r\n </div>\r\n</div>\r\n\r\n<!-- Recursive Template for Items -->\r\n<ng-template #itemTemplate let-item=\"item\">\r\n <div class=\"cc-filter-item\" [ngClass]=\"['type-' + item.type]\"\r\n [style.display]=\"item.visible === false ? 'none' : 'block'\" [ngStyle]=\"item.styles\">\r\n\r\n <ng-container [ngSwitch]=\"item.type\">\r\n\r\n <!-- Input -->\r\n <lib-input *ngSwitchCase=\"'input'\" [config]=\"item.inputConfig\" [label]=\"item.label\"\r\n [ngModel]=\"filters[item.key]\" (valueChange)=\"onValueChange(item.key, $event)\">\r\n </lib-input>\r\n\r\n <!-- Dropdown -->\r\n <lib-dropdown *ngSwitchCase=\"'dropdown'\" [config]=\"item.dropdownConfig\" [label]=\"item.label\"\r\n [ngModel]=\"filters[item.key]\" (selectionChange)=\"onValueChange(item.key, $event)\">\r\n </lib-dropdown>\r\n\r\n <!-- Checkbox -->\r\n <lib-checkbox *ngSwitchCase=\"'checkbox'\" [config]=\"item.checkboxConfig\" [label]=\"item.label\"\r\n [checked]=\"!!filters[item.key]\" (checkedChange)=\"onValueChange(item.key, $event)\">\r\n </lib-checkbox>\r\n\r\n <!-- Radio -->\r\n <lib-radio *ngSwitchCase=\"'radio'\" [config]=\"item.radioConfig\" [label]=\"item.label\"\r\n [ngModel]=\"filters[item.key]\" (selectionChange)=\"onValueChange(item.key, $event)\">\r\n </lib-radio>\r\n\r\n <!-- Toggle -->\r\n <lib-toggle *ngSwitchCase=\"'toggle'\" [config]=\"item.toggleConfig\" [label]=\"item.label\"\r\n [checked]=\"filters[item.key]\" (toggleChange)=\"onValueChange(item.key, $event)\">\r\n </lib-toggle>\r\n\r\n <!-- Datepicker -->\r\n <lib-datepicker *ngSwitchCase=\"'datepicker'\" [config]=\"item.datepickerConfig\" [label]=\"item.label\"\r\n [ngModel]=\"filters[item.key]\" (dateChange)=\"onValueChange(item.key, $event)\">\r\n </lib-datepicker>\r\n\r\n <!-- Search -->\r\n <lib-search *ngSwitchCase=\"'active-search'\" [config]=\"item.searchConfig\" [label]=\"item.label\"\r\n [ngModel]=\"filters[item.key]\" (search)=\"onValueChange(item.key, $event)\">\r\n </lib-search>\r\n\r\n <!-- Group / Accordion -->\r\n <div *ngSwitchCase=\"'group'\" class=\"cc-accordion-item\" [class.expanded]=\"item.expanded\">\r\n <button type=\"button\" class=\"cc-accordion-header\" (click)=\"item.expanded = !item.expanded\">\r\n <span class=\"cc-accordion-title\">{{ item.label }}</span>\r\n <span class=\"material-icons cc-accordion-icon\">{{ item.expanded ? 'expand_less' : 'expand_more'\r\n }}</span>\r\n </button>\r\n\r\n <div class=\"cc-accordion-body\" *ngIf=\"item.expanded\">\r\n <ng-container *ngFor=\"let child of item.children; trackBy: trackByFn\">\r\n <ng-container *ngTemplateOutlet=\"itemTemplate; context: { item: child }\"></ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n <!-- Custom Divider -->\r\n <div *ngSwitchCase=\"'divider'\" class=\"cc-filter-divider\"></div>\r\n\r\n </ng-container>\r\n </div>\r\n</ng-template>", styles: [":host{display:block;height:100%;--cc-filter-sidebar-width: 100%;--cc-filter-sidebar-padding: 1.5rem;--cc-filter-sidebar-gap: 1.5rem;--cc-filter-sidebar-bg: #FEFEFE;--cc-filter-sidebar-border-radius: 0;--cc-filter-header-height: 3rem;--cc-filter-header-title-size: 1.125rem;--cc-filter-header-title-weight: 500;--cc-filter-header-title-color: #202124;--cc-filter-footer-padding: 1rem 0 0 0;--cc-filter-footer-gap: 1rem}.cc-filter-sidebar{display:flex;flex-direction:column;height:100%;width:var(--cc-filter-sidebar-width, 280px);background-color:var(--cc-filter-sidebar-bg, #FEFEFE);padding:var(--cc-filter-sidebar-padding, 1.5rem);box-sizing:border-box;overflow:hidden;border-radius:var(--cc-filter-sidebar-border-radius, 0);transition:width .3s ease,padding .3s ease}.cc-filter-sidebar.collapsed{width:60px!important;padding:1.5rem .5rem}.cc-filter-sidebar.collapsed .cc-filter-header{justify-content:center;margin-bottom:0}.cc-filter-sidebar.collapsed .cc-filter-header .cc-header-content,.cc-filter-sidebar.collapsed .cc-filter-header .cc-close-btn,.cc-filter-sidebar.collapsed .cc-filter-content{display:none}.cc-filter-header{display:flex;align-items:center;justify-content:space-between;min-height:var(--cc-filter-header-height, 3rem);margin-bottom:1rem;flex-shrink:0}.cc-filter-header .cc-header-content{display:flex;align-items:center;gap:.5rem;flex:1}.cc-filter-header .cc-header-title{font-size:var(--cc-filter-header-title-size, 1.125rem);font-weight:var(--cc-filter-header-title-weight, 500);color:var(--cc-filter-header-title-color, #202124);display:flex;align-items:center;gap:.5rem}.cc-filter-content{flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:var(--cc-filter-sidebar-gap, 1.5rem);padding-right:.25rem}.cc-filter-content::-webkit-scrollbar{width:6px}.cc-filter-content::-webkit-scrollbar-thumb{background-color:#e0e0e0;border-radius:3px}.cc-accordion-item{border-bottom:1px solid #E0E0E0}.cc-accordion-item:last-child{border-bottom:none}.cc-accordion-item .cc-accordion-header{display:flex;align-items:center;justify-content:space-between;width:100%;padding:.75rem 0;background:none;border:none;cursor:pointer;text-align:left;outline:none}.cc-accordion-item .cc-accordion-header .cc-accordion-title{font-size:.875rem;font-weight:500;color:#202124}.cc-accordion-item .cc-accordion-header .cc-accordion-icon{color:#5f6368;transition:transform .2s}.cc-accordion-item .cc-accordion-body{padding:0 0 1rem;display:flex;flex-direction:column;gap:1rem;animation:slideDown .2s ease-out}@keyframes slideDown{0%{opacity:0;transform:translateY(-5px)}to{opacity:1;transform:translateY(0)}}.cc-group-content{display:flex;flex-direction:column;gap:1rem}.cc-filter-divider{height:1px;background-color:#e0e0e0;margin:.5rem 0}.cc-filter-footer{display:flex;gap:var(--cc-filter-footer-gap, 1rem);padding:var(--cc-filter-footer-padding, 1rem 0 0 0);margin-top:auto;flex-shrink:0}.cc-filter-footer button{flex:1}.cc-icon-btn{background:none;border:none;cursor:pointer;color:#5f6368;padding:.5rem;border-radius:50%;display:flex;align-items:center;justify-content:center}.cc-icon-btn:hover{background-color:#0000000a}.cc-close-btn{margin-right:-.5rem}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: InputComponent, selector: "lib-input", inputs: ["config", "labels", "type", "label", "placeholder", "disabled", "required", "readonly", "clearable", "maxLength", "minLength", "min", "max", "pattern", "errorMessage", "helperText", "rows", "prefixIcon", "suffixIcon", "value", "width", "height", "borderRadius", "fontSize", "gap", "fontFamily", "labelColor", "labelFontSize", "labelFontWeight", "backgroundColor", "borderColor", "borderWidth", "padding", "fontWeight", "color", "placeholderColor", "focusBorderColor", "errorColor", "disabledBackgroundColor", "disabledColor", "boxShadow"], outputs: ["valueChange", "inputBlur", "inputFocus"] }, { kind: "component", type: DropdownComponent, selector: "lib-dropdown", inputs: ["config", "labels", "options", "selectedValue", "placeholder", "label", "multiple", "searchable", "clearable", "disabled", "required", "errorMessage", "serverSearch", "searchDebounceMs", "lazyLoad", "loading", "loadingMore", "hasMore", "width", "height", "borderRadius", "fontSize", "gap", "fontFamily", "labelColor", "labelFontSize", "labelFontWeight", "backgroundColor", "borderColor", "borderWidth", "padding", "fontWeight", "color", "placeholderColor", "focusBorderColor", "errorColor", "disabledBackgroundColor", "disabledColor", "boxShadow"], outputs: ["selectionChange", "searchTermChange", "loadMore"] }, { kind: "component", type: CheckboxComponent, selector: "lib-checkbox", inputs: ["config", "labels", "label", "checked", "disabled", "required", "indeterminate", "options", "labelPosition", "color", "borderRadius", "value", "errorMessage", "width", "height", "fontSize", "fontWeight", "labelColor", "labelFontSize", "labelFontWeight", "gap", "fontFamily", "backgroundColor", "borderColor", "borderWidth", "padding", "placeholderColor", "focusBorderColor", "errorColor", "disabledBackgroundColor", "disabledColor", "boxShadow", "size", "checkedColor", "uncheckedColor", "groupLabelColor", "groupLabelFontSize", "groupLabelFontWeight"], outputs: ["checkedChange"] }, { kind: "component", type: RadioComponent, selector: "lib-radio", inputs: ["config", "label", "options", "disabled", "required", "labelPosition", "color", "layout", "labels", "gap", "labelColor", "checkedColor", "uncheckedColor", "fontSize", "fontWeight", "fontFamily", "groupLabelColor", "groupLabelFontSize", "groupLabelFontWeight", "disabledColor", "errorColor", "size", "borderRadius", "labelFontSize", "labelFontWeight"], outputs: ["selectionChange"] }, { kind: "component", type: ToggleComponent, selector: "lib-toggle", inputs: ["config", "labels", "label", "checked", "disabled", "required", "labelPosition", "color", "labelColor", "uncheckedColor", "checkedColor", "thumbColor", "checkedThumbColor", "fontSize", "fontWeight", "fontFamily", "toggleWidth", "toggleHeight", "gap", "sliderColor", "labelFontSize", "labelFontWeight", "disabledColor"], outputs: ["toggleChange"] }, { kind: "component", type: DatepickerComponent, selector: "lib-datepicker", inputs: ["config", "labels", "label", "placeholder", "disabled", "required", "minDate", "maxDate", "isRange", "startView", "value", "startDate", "endDate", "errorMessage", "width", "height", "borderRadius", "fontSize", "gap", "fontFamily", "labelColor", "labelFontSize", "labelFontWeight", "backgroundColor", "borderColor", "borderWidth", "padding", "fontWeight", "color", "placeholderColor", "focusBorderColor", "errorColor", "disabledBackgroundColor", "disabledColor", "boxShadow"], outputs: ["dateChange"] }, { kind: "component", type: SearchComponent, selector: "lib-search", inputs: ["config", "labels", "placeholder", "label", "disabled", "debounceMs", "clearable", "value", "width", "height", "borderRadius", "fontSize", "gap", "fontFamily", "labelColor", "labelFontSize", "labelFontWeight", "backgroundColor", "borderColor", "borderWidth", "border", "padding", "fontWeight", "color", "textColor", "iconColor", "placeholderColor", "focusBorderColor", "disabledBackgroundColor", "disabledColor", "boxShadow"], outputs: ["search", "clear"] }, { kind: "component", type: ButtonComponent, selector: "lib-button", inputs: ["variant", "type", "disabled", "width", "height", "borderRadius", "fontSize", "fontWeight", "backgroundColor", "color", "border", "icon", "labels"] }] });
|
|
12741
|
+
], ngImport: i0, template: "<div class=\"cc-filter-sidebar\" [ngStyle]=\"sidebarStyles\" [class.collapsed]=\"isCollapsed\">\r\n\r\n <!-- Header -->\r\n <div class=\"cc-filter-header\" *ngIf=\"config.header?.visible !== false\">\r\n <div class=\"cc-header-content\">\r\n <span class=\"cc-header-title\" *ngIf=\"config.header?.title\">\r\n <span *ngIf=\"config.header?.icon\" [class]=\"config.header?.icon\" class=\"cc-header-icon material-icons\">\r\n <!-- {{config.header?.icon }} -->\r\n </span>\r\n {{ config.header?.title }}\r\n </span>\r\n <ng-content select=\"[header-actions]\"></ng-content>\r\n </div>\r\n <button type=\"button\" *ngIf=\"config.settings?.collapsible\" (click)=\"toggleCollapse()\" class=\"cc-icon-btn\"\r\n [attr.aria-label]=\"isCollapsed ? config.labels?.expandAriaLabel : config.labels?.collapseAriaLabel\">\r\n <span class=\"material-icons\">{{ isCollapsed ? 'menu_open' : 'menu' }}</span>\r\n </button>\r\n <button type=\"button\" *ngIf=\"config.header?.showClose && !isCollapsed\" (click)=\"onClose()\"\r\n class=\"cc-icon-btn cc-close-btn\" [attr.aria-label]=\"config.labels?.closeAriaLabel\">\r\n <span class=\"material-icons\">close</span>\r\n </button>\r\n <!-- Code snippet debug (optional) -->\r\n <button type=\"button\" *ngIf=\"config.settings?.showCodeSnippet\" (click)=\"onShowCodeSnippet()\" class=\"cc-icon-btn\"\r\n [attr.aria-label]=\"config.labels?.codeSnippetAriaLabel\">\r\n <span class=\"material-icons\">code</span>\r\n </button>\r\n </div>\r\n\r\n <!-- Content (Scrollable) -->\r\n <div class=\"cc-filter-content\" [class.hidden]=\"isCollapsed\">\r\n <ng-container *ngFor=\"let item of config.items; trackBy: trackByFn\">\r\n <ng-container *ngTemplateOutlet=\"itemTemplate; context: { item: item }\"></ng-container>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"cc-filter-footer\" *ngIf=\"config.footer?.visible !== false && !isCollapsed\">\r\n <lib-button variant=\"outline\" color=\"warn\" class=\"cc-btn-clear\"\r\n *ngIf=\"config.footer?.clearButton?.visible !== false\" (click)=\"clearFilters()\">\r\n {{ config.footer?.clearButton?.label }}\r\n </lib-button>\r\n <lib-button variant=\"primary\" color=\"warn\" class=\"cc-btn-apply\"\r\n *ngIf=\"config.footer?.applyButton?.visible !== false\" [disabled]=\"config.footer?.applyButton?.disabled ?? false\"\r\n (click)=\"applyFilters()\">\r\n {{ config.footer?.applyButton?.label }}\r\n </lib-button>\r\n </div>\r\n</div>\r\n\r\n<!-- Recursive Template for Items -->\r\n<ng-template #itemTemplate let-item=\"item\">\r\n <div class=\"cc-filter-item\" [ngClass]=\"['type-' + item.type]\"\r\n [style.display]=\"item.visible === false ? 'none' : 'block'\" [ngStyle]=\"item.styles\">\r\n\r\n <ng-container [ngSwitch]=\"item.type\">\r\n\r\n <!-- Input -->\r\n <lib-input *ngSwitchCase=\"'input'\" [config]=\"item.inputConfig\" [label]=\"item.label\"\r\n [ngModel]=\"filters[item.key]\" (valueChange)=\"onValueChange(item.key, $event)\">\r\n </lib-input>\r\n\r\n <!-- Dropdown -->\r\n <lib-dropdown *ngSwitchCase=\"'dropdown'\" [config]=\"item.dropdownConfig\" [label]=\"item.label\"\r\n [ngModel]=\"filters[item.key]\" (selectionChange)=\"onValueChange(item.key, $event)\">\r\n </lib-dropdown>\r\n\r\n <!-- Checkbox -->\r\n <lib-checkbox *ngSwitchCase=\"'checkbox'\" [config]=\"item.checkboxConfig\" [label]=\"item.label\"\r\n [checked]=\"!!filters[item.key]\" (checkedChange)=\"onValueChange(item.key, $event)\">\r\n </lib-checkbox>\r\n\r\n <!-- Radio -->\r\n <lib-radio *ngSwitchCase=\"'radio'\" [config]=\"item.radioConfig\" [label]=\"item.label\"\r\n [ngModel]=\"filters[item.key]\" (selectionChange)=\"onValueChange(item.key, $event)\">\r\n </lib-radio>\r\n\r\n <!-- Toggle -->\r\n <lib-toggle *ngSwitchCase=\"'toggle'\" [config]=\"item.toggleConfig\" [label]=\"item.label\"\r\n [checked]=\"filters[item.key]\" (toggleChange)=\"onValueChange(item.key, $event)\">\r\n </lib-toggle>\r\n\r\n <!-- Datepicker -->\r\n <lib-datepicker *ngSwitchCase=\"'datepicker'\" [config]=\"item.datepickerConfig\" [label]=\"item.label\"\r\n [ngModel]=\"filters[item.key]\" (dateChange)=\"onValueChange(item.key, $event)\">\r\n </lib-datepicker>\r\n\r\n <!-- Search -->\r\n <lib-search *ngSwitchCase=\"'active-search'\" [config]=\"item.searchConfig\" [label]=\"item.label\"\r\n [ngModel]=\"filters[item.key]\" (search)=\"onValueChange(item.key, $event)\">\r\n </lib-search>\r\n\r\n <!-- Group / Accordion -->\r\n <div *ngSwitchCase=\"'group'\" class=\"cc-accordion-item\" [class.expanded]=\"item.expanded\">\r\n <button type=\"button\" class=\"cc-accordion-header\" (click)=\"item.expanded = !item.expanded\">\r\n <span class=\"cc-accordion-title\">{{ item.label }}</span>\r\n <span class=\"material-icons cc-accordion-icon\">{{ item.expanded ? 'expand_less' : 'expand_more'\r\n }}</span>\r\n </button>\r\n\r\n <div class=\"cc-accordion-body\" *ngIf=\"item.expanded\">\r\n <ng-container *ngFor=\"let child of item.children; trackBy: trackByFn\">\r\n <ng-container *ngTemplateOutlet=\"itemTemplate; context: { item: child }\"></ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n <!-- Custom Divider -->\r\n <div *ngSwitchCase=\"'divider'\" class=\"cc-filter-divider\"></div>\r\n\r\n </ng-container>\r\n </div>\r\n</ng-template>", styles: [":host{display:block;height:100%;--cc-filter-sidebar-width: 100%;--cc-filter-sidebar-padding: 1.5rem;--cc-filter-sidebar-gap: 1.5rem;--cc-filter-sidebar-bg: #FEFEFE;--cc-filter-sidebar-border-radius: 0;--cc-filter-header-height: 3rem;--cc-filter-header-title-size: 1.125rem;--cc-filter-header-title-weight: 500;--cc-filter-header-title-color: #202124;--cc-filter-footer-padding: 1rem 0 0 0;--cc-filter-footer-gap: 1rem}.cc-filter-sidebar{display:flex;flex-direction:column;height:100%;width:var(--cc-filter-sidebar-width, 280px);background-color:var(--cc-filter-sidebar-bg, #FEFEFE);padding:var(--cc-filter-sidebar-padding, 1.5rem);box-sizing:border-box;overflow:hidden;border-radius:var(--cc-filter-sidebar-border-radius, 0);transition:width .3s ease,padding .3s ease}.cc-filter-sidebar.collapsed{width:60px!important;padding:1.5rem .5rem}.cc-filter-sidebar.collapsed .cc-filter-header{justify-content:center;margin-bottom:0}.cc-filter-sidebar.collapsed .cc-filter-header .cc-header-content,.cc-filter-sidebar.collapsed .cc-filter-header .cc-close-btn,.cc-filter-sidebar.collapsed .cc-filter-content{display:none}.cc-filter-header{display:flex;align-items:center;justify-content:space-between;min-height:var(--cc-filter-header-height, 3rem);margin-bottom:1rem;flex-shrink:0}.cc-filter-header .cc-header-content{display:flex;align-items:center;gap:.5rem;flex:1}.cc-filter-header .cc-header-title{font-size:var(--cc-filter-header-title-size, 1.125rem);font-weight:var(--cc-filter-header-title-weight, 500);color:var(--cc-filter-header-title-color, #202124);display:flex;align-items:center;gap:.5rem}.cc-filter-content{flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:var(--cc-filter-sidebar-gap, 1.5rem);padding-right:.25rem}.cc-filter-content::-webkit-scrollbar{width:6px}.cc-filter-content::-webkit-scrollbar-thumb{background-color:#e0e0e0;border-radius:3px}.cc-accordion-item{border-bottom:1px solid #E0E0E0}.cc-accordion-item:last-child{border-bottom:none}.cc-accordion-item .cc-accordion-header{display:flex;align-items:center;justify-content:space-between;width:100%;padding:.75rem 0;background:none;border:none;cursor:pointer;text-align:left;outline:none}.cc-accordion-item .cc-accordion-header .cc-accordion-title{font-size:.875rem;font-weight:500;color:#202124}.cc-accordion-item .cc-accordion-header .cc-accordion-icon{color:#5f6368;transition:transform .2s}.cc-accordion-item .cc-accordion-body{padding:0 0 1rem;display:flex;flex-direction:column;gap:1rem;animation:slideDown .2s ease-out}@keyframes slideDown{0%{opacity:0;transform:translateY(-5px)}to{opacity:1;transform:translateY(0)}}.cc-group-content{display:flex;flex-direction:column;gap:1rem}.cc-filter-divider{height:1px;background-color:#e0e0e0;margin:.5rem 0}.cc-filter-footer{display:flex;gap:var(--cc-filter-footer-gap, 1rem);padding:var(--cc-filter-footer-padding, 1rem 0 0 0);margin-top:auto;flex-shrink:0}.cc-filter-footer button{flex:1}.cc-icon-btn{background:none;border:none;cursor:pointer;color:#5f6368;padding:.5rem;border-radius:50%;display:flex;align-items:center;justify-content:center}.cc-icon-btn:hover{background-color:#0000000a}.cc-close-btn{margin-right:-.5rem}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: InputComponent, selector: "lib-input", inputs: ["config", "labels", "type", "label", "placeholder", "disabled", "required", "readonly", "clearable", "maxLength", "minLength", "min", "max", "pattern", "errorMessage", "helperText", "rows", "prefixIcon", "suffixIcon", "value", "width", "height", "borderRadius", "fontSize", "gap", "fontFamily", "labelColor", "labelFontSize", "labelFontWeight", "backgroundColor", "borderColor", "borderWidth", "padding", "fontWeight", "color", "placeholderColor", "focusBorderColor", "errorColor", "disabledBackgroundColor", "disabledColor", "boxShadow"], outputs: ["valueChange", "inputBlur", "inputFocus"] }, { kind: "component", type: DropdownComponent, selector: "lib-dropdown", inputs: ["config", "labels", "options", "selectedValue", "placeholder", "label", "multiple", "searchable", "clearable", "disabled", "required", "errorMessage", "serverSearch", "searchDebounceMs", "lazyLoad", "loading", "loadingMore", "hasMore", "width", "height", "borderRadius", "fontSize", "gap", "fontFamily", "labelColor", "labelFontSize", "labelFontWeight", "backgroundColor", "borderColor", "borderWidth", "padding", "fontWeight", "color", "placeholderColor", "focusBorderColor", "errorColor", "disabledBackgroundColor", "disabledColor", "boxShadow"], outputs: ["selectionChange", "searchTermChange", "loadMore", "opened"] }, { kind: "component", type: CheckboxComponent, selector: "lib-checkbox", inputs: ["config", "labels", "label", "checked", "disabled", "required", "indeterminate", "options", "labelPosition", "color", "borderRadius", "value", "errorMessage", "width", "height", "fontSize", "fontWeight", "labelColor", "labelFontSize", "labelFontWeight", "gap", "fontFamily", "backgroundColor", "borderColor", "borderWidth", "padding", "placeholderColor", "focusBorderColor", "errorColor", "disabledBackgroundColor", "disabledColor", "boxShadow", "size", "checkedColor", "uncheckedColor", "groupLabelColor", "groupLabelFontSize", "groupLabelFontWeight"], outputs: ["checkedChange"] }, { kind: "component", type: RadioComponent, selector: "lib-radio", inputs: ["config", "label", "options", "disabled", "required", "labelPosition", "color", "layout", "labels", "gap", "labelColor", "checkedColor", "uncheckedColor", "fontSize", "fontWeight", "fontFamily", "groupLabelColor", "groupLabelFontSize", "groupLabelFontWeight", "disabledColor", "errorColor", "size", "borderRadius", "labelFontSize", "labelFontWeight"], outputs: ["selectionChange"] }, { kind: "component", type: ToggleComponent, selector: "lib-toggle", inputs: ["config", "labels", "label", "checked", "disabled", "required", "labelPosition", "color", "labelColor", "uncheckedColor", "checkedColor", "thumbColor", "checkedThumbColor", "fontSize", "fontWeight", "fontFamily", "toggleWidth", "toggleHeight", "gap", "sliderColor", "labelFontSize", "labelFontWeight", "disabledColor"], outputs: ["toggleChange"] }, { kind: "component", type: DatepickerComponent, selector: "lib-datepicker", inputs: ["config", "labels", "label", "placeholder", "disabled", "required", "minDate", "maxDate", "isRange", "startView", "value", "startDate", "endDate", "errorMessage", "width", "height", "borderRadius", "fontSize", "gap", "fontFamily", "labelColor", "labelFontSize", "labelFontWeight", "backgroundColor", "borderColor", "borderWidth", "padding", "fontWeight", "color", "placeholderColor", "focusBorderColor", "errorColor", "disabledBackgroundColor", "disabledColor", "boxShadow"], outputs: ["dateChange"] }, { kind: "component", type: SearchComponent, selector: "lib-search", inputs: ["config", "labels", "placeholder", "label", "disabled", "debounceMs", "clearable", "value", "width", "height", "borderRadius", "fontSize", "gap", "fontFamily", "labelColor", "labelFontSize", "labelFontWeight", "backgroundColor", "borderColor", "borderWidth", "border", "padding", "fontWeight", "color", "textColor", "iconColor", "placeholderColor", "focusBorderColor", "disabledBackgroundColor", "disabledColor", "boxShadow"], outputs: ["search", "clear"] }, { kind: "component", type: ButtonComponent, selector: "lib-button", inputs: ["variant", "type", "disabled", "width", "height", "borderRadius", "fontSize", "fontWeight", "backgroundColor", "color", "border", "icon", "labels"] }] });
|
|
12368
12742
|
}
|
|
12369
12743
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FilterSidebarComponent, decorators: [{
|
|
12370
12744
|
type: Component,
|
|
@@ -13357,7 +13731,7 @@ class FilterComponent {
|
|
|
13357
13731
|
return item.key || index;
|
|
13358
13732
|
}
|
|
13359
13733
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FilterComponent, deps: [{ token: i0.ElementRef }, { token: i1$4.Router }, { token: i1$4.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Component });
|
|
13360
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: FilterComponent, isStandalone: false, selector: "lib-filter", inputs: { config: "config", activeFilters: "activeFilters", columns: "columns", labels: "labels", theme: "theme" }, outputs: { filterChange: "filterChange", columnChange: "columnChange", toggle: "toggle" }, host: { listeners: { "document:click": "onClickOutside($event)" } }, ngImport: i0, template: "<div class=\"cc-filter-container\" [class.is-open]=\"isOpen\" [ngStyle]=\"containerStyles\">\r\n\r\n <!-- Trigger Button -->\r\n <lib-button variant=\"outline\" (click)=\"togglePanel()\" class=\"cc-filter-trigger\"\r\n [class.active]=\"activeFilterCount > 0\">\r\n <span class=\"fas fa-filter\"></span> {{ labels?.filterBtn || 'Filter' }}\r\n <span *ngIf=\"activeFilterCount > 0\" class=\"cc-badge\">{{ activeFilterCount }}</span>\r\n </lib-button>\r\n\r\n <!-- Dropdown Panel -->\r\n <div class=\"cc-filter-panel mat-elevation-z4\" *ngIf=\"isOpen\" (click)=\"$event.stopPropagation()\">\r\n\r\n <!-- Header / Tabs -->\r\n <div class=\"cc-panel-header\">\r\n <div class=\"cc-tabs\">\r\n <div class=\"cc-tab\" [class.active]=\"activeTab === 'filters'\" (click)=\"setActiveTab('filters')\">\r\n {{ labels.filterBtn }}\r\n </div>\r\n <!-- Hide columns tab if no columns config provided -->\r\n <div class=\"cc-tab\" [class.active]=\"activeTab === 'columns'\" (click)=\"setActiveTab('columns')\"\r\n *ngIf=\"columns?.length\">\r\n {{ labels.columns }}\r\n </div>\r\n </div>\r\n <button type=\"button\" class=\"cc-icon-btn\" (click)=\"togglePanel()\">\r\n <span class=\"material-icons\">close</span>\r\n </button>\r\n </div>\r\n\r\n <!-- Filters Tab Content -->\r\n <div class=\"cc-panel-content\" *ngIf=\"activeTab === 'filters'\">\r\n\r\n <!-- Dynamic Items -->\r\n <ng-container *ngFor=\"let item of config.items; trackBy: trackByFn\">\r\n <ng-container *ngTemplateOutlet=\"itemTemplate; context: { item: item }\"></ng-container>\r\n </ng-container>\r\n\r\n </div>\r\n\r\n <!-- Columns Tab Content -->\r\n <div class=\"cc-panel-content\" *ngIf=\"activeTab === 'columns'\">\r\n <div class=\"cc-columns-actions\">\r\n <lib-button variant=\"outline\" class=\"cc-btn-text\" (click)=\"toggleAllColumns(true)\">{{ labels.showAll\r\n }}</lib-button>\r\n <lib-button variant=\"outline\" class=\"cc-btn-text\" (click)=\"toggleAllColumns(false)\">{{ labels.hideAll\r\n }}</lib-button>\r\n </div>\r\n <div class=\"cc-columns-list\">\r\n <div *ngFor=\"let col of tempColumns\" class=\"cc-column-item\">\r\n <lib-checkbox [label]=\"col.label\" [checked]=\"col.visible\" (checkedChange)=\"toggleColumn(col.id)\">\r\n </lib-checkbox>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Footer Actions -->\r\n <div class=\"cc-panel-footer\">\r\n <lib-button variant=\"outline\" color=\"warn\" class=\"cc-btn-text text-danger\" (click)=\"clearAll()\">\r\n {{ labels.clear }}\r\n </lib-button>\r\n <lib-button class=\"ms-2\" variant=\"primary\" (click)=\"apply()\">\r\n {{ labels.apply }}\r\n </lib-button>\r\n </div>\r\n\r\n </div>\r\n</div>\r\n\r\n<!-- Recursive Template for Items -->\r\n<ng-template #itemTemplate let-item=\"item\">\r\n <div class=\"cc-filter-item\" [ngClass]=\"['type-' + item.type]\"\r\n [style.display]=\"item.visible === false ? 'none' : 'block'\" [ngStyle]=\"item.styles\">\r\n\r\n <ng-container [ngSwitch]=\"item.type\">\r\n\r\n <!-- Input -->\r\n <lib-input *ngSwitchCase=\"'input'\" [config]=\"item.inputConfig\" [label]=\"item.label\"\r\n [ngModel]=\"tempFilters[item.key]\" (valueChange)=\"onFilterChange(item.key, $event)\">\r\n </lib-input>\r\n\r\n <!-- Dropdown -->\r\n <lib-dropdown *ngSwitchCase=\"'dropdown'\" [config]=\"item.dropdownConfig\" [label]=\"item.label\"\r\n [ngModel]=\"tempFilters[item.key]\" (selectionChange)=\"onFilterChange(item.key, $event)\">\r\n </lib-dropdown>\r\n\r\n <!-- Checkbox -->\r\n <lib-checkbox *ngSwitchCase=\"'checkbox'\" [config]=\"item.checkboxConfig\" [label]=\"item.label\"\r\n [ngModel]=\"tempFilters[item.key]\" (checkedChange)=\"onFilterChange(item.key, $event)\">\r\n </lib-checkbox>\r\n\r\n <!-- Radio -->\r\n <lib-radio *ngSwitchCase=\"'radio'\" [config]=\"item.radioConfig\" [label]=\"item.label\"\r\n [ngModel]=\"tempFilters[item.key]\" (selectionChange)=\"onFilterChange(item.key, $event)\">\r\n </lib-radio>\r\n\r\n <!-- Toggle -->\r\n <lib-toggle *ngSwitchCase=\"'toggle'\" [config]=\"item.toggleConfig\" [label]=\"item.label\"\r\n [checked]=\"tempFilters[item.key]\" (toggleChange)=\"onFilterChange(item.key, $event)\">\r\n </lib-toggle>\r\n\r\n <!-- Datepicker -->\r\n <lib-datepicker *ngSwitchCase=\"'datepicker'\" [config]=\"item.datepickerConfig\" [label]=\"item.label\"\r\n [ngModel]=\"tempFilters[item.key]\" (dateChange)=\"onFilterChange(item.key, $event)\">\r\n </lib-datepicker>\r\n\r\n <!-- Search -->\r\n <lib-search *ngSwitchCase=\"'active-search'\" [config]=\"item.searchConfig\" [label]=\"item.label\"\r\n [ngModel]=\"tempFilters[item.key]\" (search)=\"onFilterChange(item.key, $event)\">\r\n </lib-search>\r\n\r\n <!-- Group / Accordion -->\r\n <div *ngSwitchCase=\"'group'\" class=\"cc-accordion-item\" [class.expanded]=\"item.expanded\">\r\n <button type=\"button\" class=\"cc-accordion-header\" (click)=\"item.expanded = !item.expanded\">\r\n <span class=\"cc-accordion-title\">{{ item.label }}</span>\r\n <span class=\"material-icons cc-accordion-icon\">{{ item.expanded ? 'expand_less' : 'expand_more'\r\n }}</span>\r\n </button>\r\n\r\n <div class=\"cc-accordion-body\" *ngIf=\"item.expanded\">\r\n <ng-container *ngFor=\"let child of item.children; trackBy: trackByFn\">\r\n <ng-container *ngTemplateOutlet=\"itemTemplate; context: { item: child }\"></ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n <!-- Custom Divider -->\r\n <div *ngSwitchCase=\"'divider'\" class=\"cc-filter-divider\"></div>\r\n\r\n </ng-container>\r\n </div>\r\n</ng-template>", styles: [".cc-filter-container{position:relative;display:inline-block;font-family:var(--cc-filter-font-family, \"Roboto\", sans-serif)}.cc-filter-container .cc-filter-trigger ::ng-deep button{display:inline-flex;align-items:center;justify-content:center;gap:8px;width:var(--cc-filter-btn-width, 103px);height:var(--cc-filter-btn-height, 44px);padding:0 16px;background-color:var(--cc-filter-btn-bg, #FCFCFB);border:var(--cc-filter-btn-border, 1px solid #E8EAED);border-radius:var(--cc-filter-btn-radius, 8px);color:var(--cc-filter-btn-text-color, #5F6368);font-size:14px;font-weight:500;transition:all .2s ease}.cc-filter-container .cc-filter-trigger ::ng-deep button mat-icon,.cc-filter-container .cc-filter-trigger ::ng-deep button .material-icons{color:#5f6368}.cc-filter-container .cc-filter-trigger ::ng-deep button:hover{background-color:var(--cc-filter-btn-hover-bg, #f1f3f4)}.cc-filter-container .cc-filter-trigger.active ::ng-deep button{background-color:var(--cc-filter-btn-active-bg, #e3f2fd);color:var(--cc-filter-btn-active-text, #1976d2);border-color:var(--cc-filter-btn-active-text, #1976d2)}.cc-filter-container .cc-filter-trigger.active ::ng-deep button mat-icon,.cc-filter-container .cc-filter-trigger.active ::ng-deep button .material-icons{color:var(--cc-filter-btn-active-text, #1976d2)}.cc-filter-container .cc-filter-trigger .cc-badge{background-color:var(--cc-filter-btn-badge-bg, #f44336);color:var(--cc-filter-btn-badge-text, #fff);font-size:10px;font-weight:700;padding:2px 6px;border-radius:10px;min-width:16px;text-align:center;margin-left:4px;line-height:1}.cc-filter-container .cc-filter-panel{position:absolute;top:calc(100% + 8px);right:0;z-index:1000;width:var(--cc-filter-panel-width, 320px);min-width:var(--cc-filter-panel-min-width, 480px)!important;padding:var(--cc-filter-panel-padding, 0);background-color:var(--cc-filter-panel-bg, #fff);border:var(--cc-filter-panel-border, 1px solid #e0e0e0);box-shadow:var(--cc-filter-panel-shadow, 0 4px 12px rgba(0, 0, 0, .15));border-radius:var(--cc-filter-panel-radius, 8px);display:flex;flex-direction:column;overflow:hidden}.cc-filter-container .cc-filter-panel.hidden{display:none}.cc-filter-container .cc-filter-panel .cc-panel-header{display:flex;align-items:center;justify-content:space-between;padding:0 1rem;height:var(--cc-filter-panel-header-height, 48px);border-bottom:1px solid #E0E0E0;background-color:var(--cc-filter-panel-header-bg, #fafafa)}.cc-filter-container .cc-tabs{display:flex;gap:1rem}.cc-filter-container .cc-tabs .cc-tab{cursor:pointer;padding:.5rem 0;font-weight:500;color:#5f6368;border-bottom:2px solid transparent;font-size:14px}.cc-filter-container .cc-tabs .cc-tab.active{color:var(--cc-filter-tab-active-text, #1976d2);border-bottom:var(--cc-filter-tab-border-bottom, 2px solid #1976d2)}.cc-filter-container .cc-icon-btn{background:none;border:none;cursor:pointer;color:#5f6368;padding:.25rem;border-radius:50%;display:flex;align-items:center;justify-content:center}.cc-filter-container .cc-icon-btn:hover{background-color:#0000000a}.cc-filter-container .cc-panel-content{padding:16px;max-height:400px;overflow-y:auto;display:flex;flex-direction:column;gap:var(--cc-filter-panel-gap, 1rem)}.cc-filter-container .cc-filter-section{margin-bottom:16px}.cc-filter-container .cc-filter-section .cc-section-title{font-size:13px;font-weight:600;margin-bottom:8px;color:var(--cc-filter-section-title, #333);text-transform:uppercase;letter-spacing:.5px}.cc-filter-container .cc-filter-section .cc-control-group{display:flex;flex-direction:column;gap:8px}.cc-filter-container .cc-filter-section .cc-control-group label{display:flex;align-items:center;gap:8px;font-size:14px;color:var(--cc-filter-option-text, #555);cursor:pointer}.cc-filter-container .cc-filter-section .cc-control-group label:hover{color:#000}.cc-filter-container .cc-accordion-item{border-bottom:1px solid #E0E0E0}.cc-filter-container .cc-accordion-item:last-child{border-bottom:none}.cc-filter-container .cc-accordion-item .cc-accordion-header{display:flex;align-items:center;justify-content:space-between;width:100%;padding:.75rem 0;background:none;border:none;cursor:pointer;text-align:left;outline:none}.cc-filter-container .cc-accordion-item .cc-accordion-header .cc-accordion-title{font-size:.875rem;font-weight:500;color:#202124}.cc-filter-container .cc-accordion-item .cc-accordion-header .cc-accordion-icon{color:#5f6368;transition:transform .2s}.cc-filter-container .cc-accordion-item .cc-accordion-body{padding:0 0 1rem;display:flex;flex-direction:column;gap:1rem;animation:slideDown .2s ease-out}.cc-filter-container .cc-filter-divider{height:1px;background-color:#e0e0e0;margin:.5rem 0}.cc-filter-container .cc-panel-footer{padding:12px 16px;background-color:var(--cc-filter-footer-bg, #fff);border-top:var(--cc-filter-footer-border-top, 1px solid #e0e0e0);display:flex;justify-content:flex-end;align-items:center;gap:10px}.cc-filter-container .cc-columns-actions{display:flex;gap:.5rem;margin-bottom:.5rem}.cc-filter-container .cc-columns-list{display:flex;flex-direction:column;gap:.5rem}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ButtonComponent, selector: "lib-button", inputs: ["variant", "type", "disabled", "width", "height", "borderRadius", "fontSize", "fontWeight", "backgroundColor", "color", "border", "icon", "labels"] }, { kind: "component", type: InputComponent, selector: "lib-input", inputs: ["config", "labels", "type", "label", "placeholder", "disabled", "required", "readonly", "clearable", "maxLength", "minLength", "min", "max", "pattern", "errorMessage", "helperText", "rows", "prefixIcon", "suffixIcon", "value", "width", "height", "borderRadius", "fontSize", "gap", "fontFamily", "labelColor", "labelFontSize", "labelFontWeight", "backgroundColor", "borderColor", "borderWidth", "padding", "fontWeight", "color", "placeholderColor", "focusBorderColor", "errorColor", "disabledBackgroundColor", "disabledColor", "boxShadow"], outputs: ["valueChange", "inputBlur", "inputFocus"] }, { kind: "component", type: DropdownComponent, selector: "lib-dropdown", inputs: ["config", "labels", "options", "selectedValue", "placeholder", "label", "multiple", "searchable", "clearable", "disabled", "required", "errorMessage", "serverSearch", "searchDebounceMs", "lazyLoad", "loading", "loadingMore", "hasMore", "width", "height", "borderRadius", "fontSize", "gap", "fontFamily", "labelColor", "labelFontSize", "labelFontWeight", "backgroundColor", "borderColor", "borderWidth", "padding", "fontWeight", "color", "placeholderColor", "focusBorderColor", "errorColor", "disabledBackgroundColor", "disabledColor", "boxShadow"], outputs: ["selectionChange", "searchTermChange", "loadMore"] }, { kind: "component", type: CheckboxComponent, selector: "lib-checkbox", inputs: ["config", "labels", "label", "checked", "disabled", "required", "indeterminate", "options", "labelPosition", "color", "borderRadius", "value", "errorMessage", "width", "height", "fontSize", "fontWeight", "labelColor", "labelFontSize", "labelFontWeight", "gap", "fontFamily", "backgroundColor", "borderColor", "borderWidth", "padding", "placeholderColor", "focusBorderColor", "errorColor", "disabledBackgroundColor", "disabledColor", "boxShadow", "size", "checkedColor", "uncheckedColor", "groupLabelColor", "groupLabelFontSize", "groupLabelFontWeight"], outputs: ["checkedChange"] }, { kind: "component", type: RadioComponent, selector: "lib-radio", inputs: ["config", "label", "options", "disabled", "required", "labelPosition", "color", "layout", "labels", "gap", "labelColor", "checkedColor", "uncheckedColor", "fontSize", "fontWeight", "fontFamily", "groupLabelColor", "groupLabelFontSize", "groupLabelFontWeight", "disabledColor", "errorColor", "size", "borderRadius", "labelFontSize", "labelFontWeight"], outputs: ["selectionChange"] }, { kind: "component", type: ToggleComponent, selector: "lib-toggle", inputs: ["config", "labels", "label", "checked", "disabled", "required", "labelPosition", "color", "labelColor", "uncheckedColor", "checkedColor", "thumbColor", "checkedThumbColor", "fontSize", "fontWeight", "fontFamily", "toggleWidth", "toggleHeight", "gap", "sliderColor", "labelFontSize", "labelFontWeight", "disabledColor"], outputs: ["toggleChange"] }, { kind: "component", type: DatepickerComponent, selector: "lib-datepicker", inputs: ["config", "labels", "label", "placeholder", "disabled", "required", "minDate", "maxDate", "isRange", "startView", "value", "startDate", "endDate", "errorMessage", "width", "height", "borderRadius", "fontSize", "gap", "fontFamily", "labelColor", "labelFontSize", "labelFontWeight", "backgroundColor", "borderColor", "borderWidth", "padding", "fontWeight", "color", "placeholderColor", "focusBorderColor", "errorColor", "disabledBackgroundColor", "disabledColor", "boxShadow"], outputs: ["dateChange"] }, { kind: "component", type: SearchComponent, selector: "lib-search", inputs: ["config", "labels", "placeholder", "label", "disabled", "debounceMs", "clearable", "value", "width", "height", "borderRadius", "fontSize", "gap", "fontFamily", "labelColor", "labelFontSize", "labelFontWeight", "backgroundColor", "borderColor", "borderWidth", "border", "padding", "fontWeight", "color", "textColor", "iconColor", "placeholderColor", "focusBorderColor", "disabledBackgroundColor", "disabledColor", "boxShadow"], outputs: ["search", "clear"] }] });
|
|
13734
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: FilterComponent, isStandalone: false, selector: "lib-filter", inputs: { config: "config", activeFilters: "activeFilters", columns: "columns", labels: "labels", theme: "theme" }, outputs: { filterChange: "filterChange", columnChange: "columnChange", toggle: "toggle" }, host: { listeners: { "document:click": "onClickOutside($event)" } }, ngImport: i0, template: "<div class=\"cc-filter-container\" [class.is-open]=\"isOpen\" [ngStyle]=\"containerStyles\">\r\n\r\n <!-- Trigger Button -->\r\n <lib-button variant=\"outline\" (click)=\"togglePanel()\" class=\"cc-filter-trigger\"\r\n [class.active]=\"activeFilterCount > 0\">\r\n <span class=\"fas fa-filter\"></span> {{ labels?.filterBtn || 'Filter' }}\r\n <span *ngIf=\"activeFilterCount > 0\" class=\"cc-badge\">{{ activeFilterCount }}</span>\r\n </lib-button>\r\n\r\n <!-- Dropdown Panel -->\r\n <div class=\"cc-filter-panel mat-elevation-z4\" *ngIf=\"isOpen\" (click)=\"$event.stopPropagation()\">\r\n\r\n <!-- Header / Tabs -->\r\n <div class=\"cc-panel-header\">\r\n <div class=\"cc-tabs\">\r\n <div class=\"cc-tab\" [class.active]=\"activeTab === 'filters'\" (click)=\"setActiveTab('filters')\">\r\n {{ labels.filterBtn }}\r\n </div>\r\n <!-- Hide columns tab if no columns config provided -->\r\n <div class=\"cc-tab\" [class.active]=\"activeTab === 'columns'\" (click)=\"setActiveTab('columns')\"\r\n *ngIf=\"columns?.length\">\r\n {{ labels.columns }}\r\n </div>\r\n </div>\r\n <button type=\"button\" class=\"cc-icon-btn\" (click)=\"togglePanel()\">\r\n <span class=\"material-icons\">close</span>\r\n </button>\r\n </div>\r\n\r\n <!-- Filters Tab Content -->\r\n <div class=\"cc-panel-content\" *ngIf=\"activeTab === 'filters'\">\r\n\r\n <!-- Dynamic Items -->\r\n <ng-container *ngFor=\"let item of config.items; trackBy: trackByFn\">\r\n <ng-container *ngTemplateOutlet=\"itemTemplate; context: { item: item }\"></ng-container>\r\n </ng-container>\r\n\r\n </div>\r\n\r\n <!-- Columns Tab Content -->\r\n <div class=\"cc-panel-content\" *ngIf=\"activeTab === 'columns'\">\r\n <div class=\"cc-columns-actions\">\r\n <lib-button variant=\"outline\" class=\"cc-btn-text\" (click)=\"toggleAllColumns(true)\">{{ labels.showAll\r\n }}</lib-button>\r\n <lib-button variant=\"outline\" class=\"cc-btn-text\" (click)=\"toggleAllColumns(false)\">{{ labels.hideAll\r\n }}</lib-button>\r\n </div>\r\n <div class=\"cc-columns-list\">\r\n <div *ngFor=\"let col of tempColumns\" class=\"cc-column-item\">\r\n <lib-checkbox [label]=\"col.label\" [checked]=\"col.visible\" (checkedChange)=\"toggleColumn(col.id)\">\r\n </lib-checkbox>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Footer Actions -->\r\n <div class=\"cc-panel-footer\">\r\n <lib-button variant=\"outline\" color=\"warn\" class=\"cc-btn-text text-danger\" (click)=\"clearAll()\">\r\n {{ labels.clear }}\r\n </lib-button>\r\n <lib-button class=\"ms-2\" variant=\"primary\" (click)=\"apply()\">\r\n {{ labels.apply }}\r\n </lib-button>\r\n </div>\r\n\r\n </div>\r\n</div>\r\n\r\n<!-- Recursive Template for Items -->\r\n<ng-template #itemTemplate let-item=\"item\">\r\n <div class=\"cc-filter-item\" [ngClass]=\"['type-' + item.type]\"\r\n [style.display]=\"item.visible === false ? 'none' : 'block'\" [ngStyle]=\"item.styles\">\r\n\r\n <ng-container [ngSwitch]=\"item.type\">\r\n\r\n <!-- Input -->\r\n <lib-input *ngSwitchCase=\"'input'\" [config]=\"item.inputConfig\" [label]=\"item.label\"\r\n [ngModel]=\"tempFilters[item.key]\" (valueChange)=\"onFilterChange(item.key, $event)\">\r\n </lib-input>\r\n\r\n <!-- Dropdown -->\r\n <lib-dropdown *ngSwitchCase=\"'dropdown'\" [config]=\"item.dropdownConfig\" [label]=\"item.label\"\r\n [ngModel]=\"tempFilters[item.key]\" (selectionChange)=\"onFilterChange(item.key, $event)\">\r\n </lib-dropdown>\r\n\r\n <!-- Checkbox -->\r\n <lib-checkbox *ngSwitchCase=\"'checkbox'\" [config]=\"item.checkboxConfig\" [label]=\"item.label\"\r\n [ngModel]=\"tempFilters[item.key]\" (checkedChange)=\"onFilterChange(item.key, $event)\">\r\n </lib-checkbox>\r\n\r\n <!-- Radio -->\r\n <lib-radio *ngSwitchCase=\"'radio'\" [config]=\"item.radioConfig\" [label]=\"item.label\"\r\n [ngModel]=\"tempFilters[item.key]\" (selectionChange)=\"onFilterChange(item.key, $event)\">\r\n </lib-radio>\r\n\r\n <!-- Toggle -->\r\n <lib-toggle *ngSwitchCase=\"'toggle'\" [config]=\"item.toggleConfig\" [label]=\"item.label\"\r\n [checked]=\"tempFilters[item.key]\" (toggleChange)=\"onFilterChange(item.key, $event)\">\r\n </lib-toggle>\r\n\r\n <!-- Datepicker -->\r\n <lib-datepicker *ngSwitchCase=\"'datepicker'\" [config]=\"item.datepickerConfig\" [label]=\"item.label\"\r\n [ngModel]=\"tempFilters[item.key]\" (dateChange)=\"onFilterChange(item.key, $event)\">\r\n </lib-datepicker>\r\n\r\n <!-- Search -->\r\n <lib-search *ngSwitchCase=\"'active-search'\" [config]=\"item.searchConfig\" [label]=\"item.label\"\r\n [ngModel]=\"tempFilters[item.key]\" (search)=\"onFilterChange(item.key, $event)\">\r\n </lib-search>\r\n\r\n <!-- Group / Accordion -->\r\n <div *ngSwitchCase=\"'group'\" class=\"cc-accordion-item\" [class.expanded]=\"item.expanded\">\r\n <button type=\"button\" class=\"cc-accordion-header\" (click)=\"item.expanded = !item.expanded\">\r\n <span class=\"cc-accordion-title\">{{ item.label }}</span>\r\n <span class=\"material-icons cc-accordion-icon\">{{ item.expanded ? 'expand_less' : 'expand_more'\r\n }}</span>\r\n </button>\r\n\r\n <div class=\"cc-accordion-body\" *ngIf=\"item.expanded\">\r\n <ng-container *ngFor=\"let child of item.children; trackBy: trackByFn\">\r\n <ng-container *ngTemplateOutlet=\"itemTemplate; context: { item: child }\"></ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n <!-- Custom Divider -->\r\n <div *ngSwitchCase=\"'divider'\" class=\"cc-filter-divider\"></div>\r\n\r\n </ng-container>\r\n </div>\r\n</ng-template>", styles: [".cc-filter-container{position:relative;display:inline-block;font-family:var(--cc-filter-font-family, \"Roboto\", sans-serif)}.cc-filter-container .cc-filter-trigger ::ng-deep button{display:inline-flex;align-items:center;justify-content:center;gap:8px;width:var(--cc-filter-btn-width, 103px);height:var(--cc-filter-btn-height, 44px);padding:0 16px;background-color:var(--cc-filter-btn-bg, #FCFCFB);border:var(--cc-filter-btn-border, 1px solid #E8EAED);border-radius:var(--cc-filter-btn-radius, 8px);color:var(--cc-filter-btn-text-color, #5F6368);font-size:14px;font-weight:500;transition:all .2s ease}.cc-filter-container .cc-filter-trigger ::ng-deep button mat-icon,.cc-filter-container .cc-filter-trigger ::ng-deep button .material-icons{color:#5f6368}.cc-filter-container .cc-filter-trigger ::ng-deep button:hover{background-color:var(--cc-filter-btn-hover-bg, #f1f3f4)}.cc-filter-container .cc-filter-trigger.active ::ng-deep button{background-color:var(--cc-filter-btn-active-bg, #e3f2fd);color:var(--cc-filter-btn-active-text, #1976d2);border-color:var(--cc-filter-btn-active-text, #1976d2)}.cc-filter-container .cc-filter-trigger.active ::ng-deep button mat-icon,.cc-filter-container .cc-filter-trigger.active ::ng-deep button .material-icons{color:var(--cc-filter-btn-active-text, #1976d2)}.cc-filter-container .cc-filter-trigger .cc-badge{background-color:var(--cc-filter-btn-badge-bg, #f44336);color:var(--cc-filter-btn-badge-text, #fff);font-size:10px;font-weight:700;padding:2px 6px;border-radius:10px;min-width:16px;text-align:center;margin-left:4px;line-height:1}.cc-filter-container .cc-filter-panel{position:absolute;top:calc(100% + 8px);right:0;z-index:1000;width:var(--cc-filter-panel-width, 320px);min-width:var(--cc-filter-panel-min-width, 480px)!important;padding:var(--cc-filter-panel-padding, 0);background-color:var(--cc-filter-panel-bg, #fff);border:var(--cc-filter-panel-border, 1px solid #e0e0e0);box-shadow:var(--cc-filter-panel-shadow, 0 4px 12px rgba(0, 0, 0, .15));border-radius:var(--cc-filter-panel-radius, 8px);display:flex;flex-direction:column;overflow:hidden}.cc-filter-container .cc-filter-panel.hidden{display:none}.cc-filter-container .cc-filter-panel .cc-panel-header{display:flex;align-items:center;justify-content:space-between;padding:0 1rem;height:var(--cc-filter-panel-header-height, 48px);border-bottom:1px solid #E0E0E0;background-color:var(--cc-filter-panel-header-bg, #fafafa)}.cc-filter-container .cc-tabs{display:flex;gap:1rem}.cc-filter-container .cc-tabs .cc-tab{cursor:pointer;padding:.5rem 0;font-weight:500;color:#5f6368;border-bottom:2px solid transparent;font-size:14px}.cc-filter-container .cc-tabs .cc-tab.active{color:var(--cc-filter-tab-active-text, #1976d2);border-bottom:var(--cc-filter-tab-border-bottom, 2px solid #1976d2)}.cc-filter-container .cc-icon-btn{background:none;border:none;cursor:pointer;color:#5f6368;padding:.25rem;border-radius:50%;display:flex;align-items:center;justify-content:center}.cc-filter-container .cc-icon-btn:hover{background-color:#0000000a}.cc-filter-container .cc-panel-content{padding:16px;max-height:400px;overflow-y:auto;display:flex;flex-direction:column;gap:var(--cc-filter-panel-gap, 1rem)}.cc-filter-container .cc-filter-section{margin-bottom:16px}.cc-filter-container .cc-filter-section .cc-section-title{font-size:13px;font-weight:600;margin-bottom:8px;color:var(--cc-filter-section-title, #333);text-transform:uppercase;letter-spacing:.5px}.cc-filter-container .cc-filter-section .cc-control-group{display:flex;flex-direction:column;gap:8px}.cc-filter-container .cc-filter-section .cc-control-group label{display:flex;align-items:center;gap:8px;font-size:14px;color:var(--cc-filter-option-text, #555);cursor:pointer}.cc-filter-container .cc-filter-section .cc-control-group label:hover{color:#000}.cc-filter-container .cc-accordion-item{border-bottom:1px solid #E0E0E0}.cc-filter-container .cc-accordion-item:last-child{border-bottom:none}.cc-filter-container .cc-accordion-item .cc-accordion-header{display:flex;align-items:center;justify-content:space-between;width:100%;padding:.75rem 0;background:none;border:none;cursor:pointer;text-align:left;outline:none}.cc-filter-container .cc-accordion-item .cc-accordion-header .cc-accordion-title{font-size:.875rem;font-weight:500;color:#202124}.cc-filter-container .cc-accordion-item .cc-accordion-header .cc-accordion-icon{color:#5f6368;transition:transform .2s}.cc-filter-container .cc-accordion-item .cc-accordion-body{padding:0 0 1rem;display:flex;flex-direction:column;gap:1rem;animation:slideDown .2s ease-out}.cc-filter-container .cc-filter-divider{height:1px;background-color:#e0e0e0;margin:.5rem 0}.cc-filter-container .cc-panel-footer{padding:12px 16px;background-color:var(--cc-filter-footer-bg, #fff);border-top:var(--cc-filter-footer-border-top, 1px solid #e0e0e0);display:flex;justify-content:flex-end;align-items:center;gap:10px}.cc-filter-container .cc-columns-actions{display:flex;gap:.5rem;margin-bottom:.5rem}.cc-filter-container .cc-columns-list{display:flex;flex-direction:column;gap:.5rem}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ButtonComponent, selector: "lib-button", inputs: ["variant", "type", "disabled", "width", "height", "borderRadius", "fontSize", "fontWeight", "backgroundColor", "color", "border", "icon", "labels"] }, { kind: "component", type: InputComponent, selector: "lib-input", inputs: ["config", "labels", "type", "label", "placeholder", "disabled", "required", "readonly", "clearable", "maxLength", "minLength", "min", "max", "pattern", "errorMessage", "helperText", "rows", "prefixIcon", "suffixIcon", "value", "width", "height", "borderRadius", "fontSize", "gap", "fontFamily", "labelColor", "labelFontSize", "labelFontWeight", "backgroundColor", "borderColor", "borderWidth", "padding", "fontWeight", "color", "placeholderColor", "focusBorderColor", "errorColor", "disabledBackgroundColor", "disabledColor", "boxShadow"], outputs: ["valueChange", "inputBlur", "inputFocus"] }, { kind: "component", type: DropdownComponent, selector: "lib-dropdown", inputs: ["config", "labels", "options", "selectedValue", "placeholder", "label", "multiple", "searchable", "clearable", "disabled", "required", "errorMessage", "serverSearch", "searchDebounceMs", "lazyLoad", "loading", "loadingMore", "hasMore", "width", "height", "borderRadius", "fontSize", "gap", "fontFamily", "labelColor", "labelFontSize", "labelFontWeight", "backgroundColor", "borderColor", "borderWidth", "padding", "fontWeight", "color", "placeholderColor", "focusBorderColor", "errorColor", "disabledBackgroundColor", "disabledColor", "boxShadow"], outputs: ["selectionChange", "searchTermChange", "loadMore", "opened"] }, { kind: "component", type: CheckboxComponent, selector: "lib-checkbox", inputs: ["config", "labels", "label", "checked", "disabled", "required", "indeterminate", "options", "labelPosition", "color", "borderRadius", "value", "errorMessage", "width", "height", "fontSize", "fontWeight", "labelColor", "labelFontSize", "labelFontWeight", "gap", "fontFamily", "backgroundColor", "borderColor", "borderWidth", "padding", "placeholderColor", "focusBorderColor", "errorColor", "disabledBackgroundColor", "disabledColor", "boxShadow", "size", "checkedColor", "uncheckedColor", "groupLabelColor", "groupLabelFontSize", "groupLabelFontWeight"], outputs: ["checkedChange"] }, { kind: "component", type: RadioComponent, selector: "lib-radio", inputs: ["config", "label", "options", "disabled", "required", "labelPosition", "color", "layout", "labels", "gap", "labelColor", "checkedColor", "uncheckedColor", "fontSize", "fontWeight", "fontFamily", "groupLabelColor", "groupLabelFontSize", "groupLabelFontWeight", "disabledColor", "errorColor", "size", "borderRadius", "labelFontSize", "labelFontWeight"], outputs: ["selectionChange"] }, { kind: "component", type: ToggleComponent, selector: "lib-toggle", inputs: ["config", "labels", "label", "checked", "disabled", "required", "labelPosition", "color", "labelColor", "uncheckedColor", "checkedColor", "thumbColor", "checkedThumbColor", "fontSize", "fontWeight", "fontFamily", "toggleWidth", "toggleHeight", "gap", "sliderColor", "labelFontSize", "labelFontWeight", "disabledColor"], outputs: ["toggleChange"] }, { kind: "component", type: DatepickerComponent, selector: "lib-datepicker", inputs: ["config", "labels", "label", "placeholder", "disabled", "required", "minDate", "maxDate", "isRange", "startView", "value", "startDate", "endDate", "errorMessage", "width", "height", "borderRadius", "fontSize", "gap", "fontFamily", "labelColor", "labelFontSize", "labelFontWeight", "backgroundColor", "borderColor", "borderWidth", "padding", "fontWeight", "color", "placeholderColor", "focusBorderColor", "errorColor", "disabledBackgroundColor", "disabledColor", "boxShadow"], outputs: ["dateChange"] }, { kind: "component", type: SearchComponent, selector: "lib-search", inputs: ["config", "labels", "placeholder", "label", "disabled", "debounceMs", "clearable", "value", "width", "height", "borderRadius", "fontSize", "gap", "fontFamily", "labelColor", "labelFontSize", "labelFontWeight", "backgroundColor", "borderColor", "borderWidth", "border", "padding", "fontWeight", "color", "textColor", "iconColor", "placeholderColor", "focusBorderColor", "disabledBackgroundColor", "disabledColor", "boxShadow"], outputs: ["search", "clear"] }] });
|
|
13361
13735
|
}
|
|
13362
13736
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FilterComponent, decorators: [{
|
|
13363
13737
|
type: Component,
|