commons-shared-web-ui 0.0.54 → 0.0.56
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 +1135 -61
- package/fesm2022/commons-shared-web-ui.mjs.map +1 -1
- package/index.d.ts +246 -9
- package/package.json +1 -1
- package/src/lib/modules/button-dropdown/button-dropdown.theme.scss +87 -0
- package/src/lib/modules/time-picker/time-picker.theme.scss +87 -0
- package/src/lib/styles/global.scss +10 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { NgModule, Injectable, Input, HostBinding, Component,
|
|
2
|
+
import { NgModule, Injectable, Input, HostBinding, Component, EventEmitter, ViewChild, Output, forwardRef, SecurityContext, Pipe, HostListener, inject, ViewChildren, Directive, signal, computed, ChangeDetectionStrategy, InjectionToken, ChangeDetectorRef, LOCALE_ID } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
4
|
import { CommonModule, formatDate } from '@angular/common';
|
|
5
5
|
import * as i1$2 from '@angular/forms';
|
|
6
|
-
import { FormControl, Validators, FormArray, FormGroup, ReactiveFormsModule, FormsModule
|
|
6
|
+
import { NG_VALUE_ACCESSOR, FormControl, Validators, FormArray, FormGroup, ReactiveFormsModule, FormsModule } from '@angular/forms';
|
|
7
7
|
import { MatCardModule } from '@angular/material/card';
|
|
8
8
|
import * as i1$3 from '@angular/material/snack-bar';
|
|
9
9
|
import { MatSnackBarModule, MAT_SNACK_BAR_DATA, MatSnackBarRef } from '@angular/material/snack-bar';
|
|
@@ -548,6 +548,814 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
548
548
|
type: Input
|
|
549
549
|
}] } });
|
|
550
550
|
|
|
551
|
+
class TimeWheelPanelComponent {
|
|
552
|
+
zone;
|
|
553
|
+
mode = '12';
|
|
554
|
+
value = '';
|
|
555
|
+
labels;
|
|
556
|
+
minuteStep = 0;
|
|
557
|
+
minTime; // "HH:mm" 24h
|
|
558
|
+
maxTime; // "HH:mm" 24h
|
|
559
|
+
confirmed = new EventEmitter();
|
|
560
|
+
cancelled = new EventEmitter();
|
|
561
|
+
hourWheel;
|
|
562
|
+
minuteWheel;
|
|
563
|
+
periodWheel;
|
|
564
|
+
// ─── Geometry (single source of truth; keep in sync with SCSS) ───
|
|
565
|
+
static ITEM_HEIGHT = 36;
|
|
566
|
+
static VISIBLE_ROWS = 3;
|
|
567
|
+
ITEM_HEIGHT = TimeWheelPanelComponent.ITEM_HEIGHT;
|
|
568
|
+
SPACERS = (TimeWheelPanelComponent.VISIBLE_ROWS - 1) / 2; // leading == trailing
|
|
569
|
+
spacerRows = Array.from({ length: this.SPACERS }, (_, i) => i);
|
|
570
|
+
get containerHeight() {
|
|
571
|
+
return TimeWheelPanelComponent.VISIBLE_ROWS * this.ITEM_HEIGHT;
|
|
572
|
+
}
|
|
573
|
+
hours = [];
|
|
574
|
+
minutes = [];
|
|
575
|
+
periods = ['AM', 'PM'];
|
|
576
|
+
// How many times each list is repeated in the DOM for the infinite loop.
|
|
577
|
+
// Computed per-list so one repeat band is always >= the viewport height.
|
|
578
|
+
hourRepeats = [0, 1, 2];
|
|
579
|
+
minuteRepeats = [0, 1, 2];
|
|
580
|
+
selectedHour = '09';
|
|
581
|
+
selectedMinute = '00';
|
|
582
|
+
selectedPeriod = 'AM';
|
|
583
|
+
disabledHours = new Set();
|
|
584
|
+
disabledMinutes = new Set();
|
|
585
|
+
disabledPeriods = new Set();
|
|
586
|
+
isInitialScrollDone = false;
|
|
587
|
+
scrollListeners = {};
|
|
588
|
+
idleTimers = {};
|
|
589
|
+
rafIds = {};
|
|
590
|
+
static IDLE_MS = 90;
|
|
591
|
+
constructor(zone) {
|
|
592
|
+
this.zone = zone;
|
|
593
|
+
}
|
|
594
|
+
ngOnInit() {
|
|
595
|
+
this.generateHours();
|
|
596
|
+
this.generateMinutes();
|
|
597
|
+
this.parseInitialValue(this.value);
|
|
598
|
+
this.computeConstraints();
|
|
599
|
+
}
|
|
600
|
+
ngOnChanges(changes) {
|
|
601
|
+
if (changes['mode'] || changes['minuteStep'] || changes['minTime'] || changes['maxTime']) {
|
|
602
|
+
this.generateHours();
|
|
603
|
+
this.generateMinutes();
|
|
604
|
+
this.computeConstraints();
|
|
605
|
+
if (this.isInitialScrollDone) {
|
|
606
|
+
this.zone.runOutsideAngular(() => {
|
|
607
|
+
this.scrollToValue('hour', this.selectedHour, false);
|
|
608
|
+
this.scrollToValue('minute', this.selectedMinute, false);
|
|
609
|
+
if (this.mode === '12') {
|
|
610
|
+
this.scrollToValue('period', this.selectedPeriod, false);
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
ngAfterViewInit() {
|
|
617
|
+
setTimeout(() => {
|
|
618
|
+
this.scrollToValue('hour', this.selectedHour, false);
|
|
619
|
+
this.scrollToValue('minute', this.selectedMinute, false);
|
|
620
|
+
if (this.mode === '12') {
|
|
621
|
+
this.scrollToValue('period', this.selectedPeriod, false);
|
|
622
|
+
}
|
|
623
|
+
this.updateWheelEffects(this.hourWheel.nativeElement);
|
|
624
|
+
this.updateWheelEffects(this.minuteWheel.nativeElement);
|
|
625
|
+
if (this.periodWheel) {
|
|
626
|
+
this.updateWheelEffects(this.periodWheel.nativeElement);
|
|
627
|
+
}
|
|
628
|
+
this.isInitialScrollDone = true;
|
|
629
|
+
}, 150);
|
|
630
|
+
// Native scroll drives everything. On each scroll event we (rAF-throttled)
|
|
631
|
+
// repaint the 3D wheel effect, and after the wheel goes idle we read the
|
|
632
|
+
// settled value + do the seamless wrap-around reposition.
|
|
633
|
+
this.zone.runOutsideAngular(() => {
|
|
634
|
+
this.attachScroll('hour', this.hourWheel.nativeElement);
|
|
635
|
+
this.attachScroll('minute', this.minuteWheel.nativeElement);
|
|
636
|
+
if (this.periodWheel) {
|
|
637
|
+
this.attachScroll('period', this.periodWheel.nativeElement);
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
ngOnDestroy() {
|
|
642
|
+
const map = {
|
|
643
|
+
hour: this.hourWheel,
|
|
644
|
+
minute: this.minuteWheel,
|
|
645
|
+
period: this.periodWheel
|
|
646
|
+
};
|
|
647
|
+
Object.keys(this.scrollListeners).forEach(type => {
|
|
648
|
+
const el = map[type]?.nativeElement;
|
|
649
|
+
const listener = this.scrollListeners[type];
|
|
650
|
+
if (el && listener) {
|
|
651
|
+
el.removeEventListener('scroll', listener);
|
|
652
|
+
}
|
|
653
|
+
const raf = this.rafIds[type];
|
|
654
|
+
if (raf)
|
|
655
|
+
cancelAnimationFrame(raf);
|
|
656
|
+
const timer = this.idleTimers[type];
|
|
657
|
+
if (timer)
|
|
658
|
+
clearTimeout(timer);
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
attachScroll(type, element) {
|
|
662
|
+
const listener = () => {
|
|
663
|
+
// Throttle the visual repaint to one per frame.
|
|
664
|
+
if (!this.rafIds[type]) {
|
|
665
|
+
this.rafIds[type] = requestAnimationFrame(() => {
|
|
666
|
+
this.rafIds[type] = undefined;
|
|
667
|
+
this.updateWheelEffects(element);
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
// Debounce the "settled" read.
|
|
671
|
+
const existing = this.idleTimers[type];
|
|
672
|
+
if (existing)
|
|
673
|
+
clearTimeout(existing);
|
|
674
|
+
this.idleTimers[type] = setTimeout(() => {
|
|
675
|
+
this.onScrollIdle(type, element);
|
|
676
|
+
}, TimeWheelPanelComponent.IDLE_MS);
|
|
677
|
+
};
|
|
678
|
+
this.scrollListeners[type] = listener;
|
|
679
|
+
element.addEventListener('scroll', listener, { passive: true });
|
|
680
|
+
}
|
|
681
|
+
generateHours() {
|
|
682
|
+
if (this.mode === '12') {
|
|
683
|
+
this.hours = Array.from({ length: 12 }, (_, i) => (i + 1).toString().padStart(2, '0'));
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
this.hours = Array.from({ length: 24 }, (_, i) => i.toString().padStart(2, '0'));
|
|
687
|
+
}
|
|
688
|
+
this.hourRepeats = this.buildRepeats(this.hours.length);
|
|
689
|
+
}
|
|
690
|
+
generateMinutes() {
|
|
691
|
+
const step = Math.max(1, this.minuteStep);
|
|
692
|
+
this.minutes = [];
|
|
693
|
+
for (let i = 0; i < 60; i += step) {
|
|
694
|
+
this.minutes.push(i.toString().padStart(2, '0'));
|
|
695
|
+
}
|
|
696
|
+
this.minuteRepeats = this.buildRepeats(this.minutes.length);
|
|
697
|
+
}
|
|
698
|
+
// Odd repeat count so that one buffer band (N*ITEM_HEIGHT) on each side of the
|
|
699
|
+
// central band is always >= the viewport height. Common case (N=12/24/60) → 3.
|
|
700
|
+
buildRepeats(n) {
|
|
701
|
+
const band = n * this.ITEM_HEIGHT;
|
|
702
|
+
const count = Math.max(3, 2 * Math.ceil(this.containerHeight / band) + 1);
|
|
703
|
+
return Array.from({ length: count }, (_, i) => i);
|
|
704
|
+
}
|
|
705
|
+
centralBandStart(list, repeats) {
|
|
706
|
+
// Index (in the repeated DOM list) of the first item of the central band.
|
|
707
|
+
return Math.floor(repeats.length / 2) * list.length;
|
|
708
|
+
}
|
|
709
|
+
parseInitialValue(valStr) {
|
|
710
|
+
if (!valStr) {
|
|
711
|
+
this.setDefaultTime();
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
const twelveRegex = /^(\d{1,2}):(\d{2})\s*(AM|PM)$/i;
|
|
715
|
+
const twentyFourRegex = /^(\d{1,2}):(\d{2})$/;
|
|
716
|
+
let match = valStr.trim().match(twelveRegex);
|
|
717
|
+
if (match) {
|
|
718
|
+
const hh = match[1].padStart(2, '0');
|
|
719
|
+
const mm = match[2];
|
|
720
|
+
const period = match[3].toUpperCase();
|
|
721
|
+
if (this.mode === '12') {
|
|
722
|
+
this.selectedHour = hh;
|
|
723
|
+
this.selectedMinute = this.findClosestMinute(mm);
|
|
724
|
+
this.selectedPeriod = period;
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
// Convert to 24h
|
|
728
|
+
let hNum = parseInt(hh, 10);
|
|
729
|
+
if (period === 'PM' && hNum !== 12)
|
|
730
|
+
hNum += 12;
|
|
731
|
+
if (period === 'AM' && hNum === 12)
|
|
732
|
+
hNum = 0;
|
|
733
|
+
this.selectedHour = hNum.toString().padStart(2, '0');
|
|
734
|
+
this.selectedMinute = this.findClosestMinute(mm);
|
|
735
|
+
}
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
match = valStr.trim().match(twentyFourRegex);
|
|
739
|
+
if (match) {
|
|
740
|
+
const hh = match[1].padStart(2, '0');
|
|
741
|
+
const mm = match[2];
|
|
742
|
+
if (this.mode === '24') {
|
|
743
|
+
this.selectedHour = hh;
|
|
744
|
+
this.selectedMinute = this.findClosestMinute(mm);
|
|
745
|
+
}
|
|
746
|
+
else {
|
|
747
|
+
// Convert to 12h
|
|
748
|
+
let hNum = parseInt(hh, 10);
|
|
749
|
+
const period = hNum >= 12 ? 'PM' : 'AM';
|
|
750
|
+
if (hNum > 12)
|
|
751
|
+
hNum -= 12;
|
|
752
|
+
if (hNum === 0)
|
|
753
|
+
hNum = 12;
|
|
754
|
+
this.selectedHour = hNum.toString().padStart(2, '0');
|
|
755
|
+
this.selectedMinute = this.findClosestMinute(mm);
|
|
756
|
+
this.selectedPeriod = period;
|
|
757
|
+
}
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
this.setDefaultTime();
|
|
761
|
+
}
|
|
762
|
+
findClosestMinute(mmStr) {
|
|
763
|
+
const mmVal = parseInt(mmStr, 10);
|
|
764
|
+
if (isNaN(mmVal))
|
|
765
|
+
return '00';
|
|
766
|
+
let closest = this.minutes[0];
|
|
767
|
+
let minDiff = 60;
|
|
768
|
+
for (const m of this.minutes) {
|
|
769
|
+
const mVal = parseInt(m, 10);
|
|
770
|
+
const diff = Math.abs(mVal - mmVal);
|
|
771
|
+
if (diff < minDiff) {
|
|
772
|
+
minDiff = diff;
|
|
773
|
+
closest = m;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
return closest;
|
|
777
|
+
}
|
|
778
|
+
setDefaultTime() {
|
|
779
|
+
if (this.mode === '12') {
|
|
780
|
+
this.selectedHour = '09';
|
|
781
|
+
this.selectedMinute = '00';
|
|
782
|
+
this.selectedPeriod = 'AM';
|
|
783
|
+
}
|
|
784
|
+
else {
|
|
785
|
+
this.selectedHour = '09';
|
|
786
|
+
this.selectedMinute = '00';
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
toMinutes(h, m) {
|
|
790
|
+
return h * 60 + m;
|
|
791
|
+
}
|
|
792
|
+
parseLimit(hhmm) {
|
|
793
|
+
const parts = hhmm.split(':');
|
|
794
|
+
return {
|
|
795
|
+
h: parseInt(parts[0], 10) || 0,
|
|
796
|
+
m: parseInt(parts[1], 10) || 0
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
to24hrHour(displayHour, period) {
|
|
800
|
+
const h = parseInt(displayHour, 10);
|
|
801
|
+
if (period === 'AM') {
|
|
802
|
+
return h === 12 ? 0 : h;
|
|
803
|
+
}
|
|
804
|
+
else {
|
|
805
|
+
return h === 12 ? 12 : h + 12;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
computeConstraints() {
|
|
809
|
+
this.disabledHours.clear();
|
|
810
|
+
this.disabledMinutes.clear();
|
|
811
|
+
this.disabledPeriods.clear();
|
|
812
|
+
if (!this.minTime && !this.maxTime)
|
|
813
|
+
return;
|
|
814
|
+
let minMin = 0;
|
|
815
|
+
let maxMin = 23 * 60 + 59;
|
|
816
|
+
if (this.minTime) {
|
|
817
|
+
const parsed = this.parseLimit(this.minTime);
|
|
818
|
+
minMin = this.toMinutes(parsed.h, parsed.m);
|
|
819
|
+
}
|
|
820
|
+
if (this.maxTime) {
|
|
821
|
+
const parsed = this.parseLimit(this.maxTime);
|
|
822
|
+
maxMin = this.toMinutes(parsed.h, parsed.m);
|
|
823
|
+
}
|
|
824
|
+
if (minMin > maxMin) {
|
|
825
|
+
// Invalid config
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
if (this.mode === '24') {
|
|
829
|
+
for (const h of this.hours) {
|
|
830
|
+
const hNum = parseInt(h, 10);
|
|
831
|
+
const hourStart = this.toMinutes(hNum, 0);
|
|
832
|
+
const hourEnd = this.toMinutes(hNum, 59);
|
|
833
|
+
if (hourEnd < minMin || hourStart > maxMin) {
|
|
834
|
+
this.disabledHours.add(h);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
const selH = parseInt(this.selectedHour, 10);
|
|
838
|
+
for (const m of this.minutes) {
|
|
839
|
+
const mNum = parseInt(m, 10);
|
|
840
|
+
const total = this.toMinutes(selH, mNum);
|
|
841
|
+
if (total < minMin || total > maxMin) {
|
|
842
|
+
this.disabledMinutes.add(m);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
// 12-hour mode logic
|
|
848
|
+
const amCoversAny = minMin < 12 * 60;
|
|
849
|
+
const pmCoversAny = maxMin >= 12 * 60;
|
|
850
|
+
if (!amCoversAny)
|
|
851
|
+
this.disabledPeriods.add('AM');
|
|
852
|
+
if (!pmCoversAny)
|
|
853
|
+
this.disabledPeriods.add('PM');
|
|
854
|
+
for (const h of this.hours) {
|
|
855
|
+
const hNum = parseInt(h, 10);
|
|
856
|
+
const h24_AM = hNum === 12 ? 0 : hNum;
|
|
857
|
+
const amStart = this.toMinutes(h24_AM, 0);
|
|
858
|
+
const amEnd = this.toMinutes(h24_AM, 59);
|
|
859
|
+
const amValid = amEnd >= minMin && amStart <= maxMin && amCoversAny;
|
|
860
|
+
const h24_PM = hNum === 12 ? 12 : hNum + 12;
|
|
861
|
+
const pmStart = this.toMinutes(h24_PM, 0);
|
|
862
|
+
const pmEnd = this.toMinutes(h24_PM, 59);
|
|
863
|
+
const pmValid = pmEnd >= minMin && pmStart <= maxMin && pmCoversAny;
|
|
864
|
+
if (this.selectedPeriod === 'AM' && !amValid)
|
|
865
|
+
this.disabledHours.add(h);
|
|
866
|
+
if (this.selectedPeriod === 'PM' && !pmValid)
|
|
867
|
+
this.disabledHours.add(h);
|
|
868
|
+
}
|
|
869
|
+
const selH24 = this.to24hrHour(this.selectedHour, this.selectedPeriod);
|
|
870
|
+
for (const m of this.minutes) {
|
|
871
|
+
const mNum = parseInt(m, 10);
|
|
872
|
+
const total = this.toMinutes(selH24, mNum);
|
|
873
|
+
if (total < minMin || total > maxMin) {
|
|
874
|
+
this.disabledMinutes.add(m);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
updateWheelEffects(element) {
|
|
879
|
+
const scrollTop = element.scrollTop;
|
|
880
|
+
const items = element.children;
|
|
881
|
+
for (let i = 0; i < items.length; i++) {
|
|
882
|
+
const item = items[i];
|
|
883
|
+
if (item.classList.contains('cc-twp-spacer'))
|
|
884
|
+
continue;
|
|
885
|
+
const itemTop = (i - this.SPACERS) * this.ITEM_HEIGHT;
|
|
886
|
+
const distance = Math.abs(itemTop - scrollTop);
|
|
887
|
+
const maxDist = this.ITEM_HEIGHT * 2;
|
|
888
|
+
const ratio = Math.max(0, 1 - distance / maxDist);
|
|
889
|
+
// Flatter than the old drum look: gentle scale + opacity fade only.
|
|
890
|
+
const scale = 0.85 + 0.15 * ratio;
|
|
891
|
+
const opacity = 0.4 + 0.6 * ratio;
|
|
892
|
+
item.style.transform = `scale(${scale}) translateZ(0)`;
|
|
893
|
+
item.style.opacity = String(opacity);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
listFor(type) {
|
|
897
|
+
if (type === 'hour')
|
|
898
|
+
return this.hours;
|
|
899
|
+
if (type === 'minute')
|
|
900
|
+
return this.minutes;
|
|
901
|
+
return this.periods;
|
|
902
|
+
}
|
|
903
|
+
repeatsFor(type) {
|
|
904
|
+
if (type === 'hour')
|
|
905
|
+
return this.hourRepeats;
|
|
906
|
+
if (type === 'minute')
|
|
907
|
+
return this.minuteRepeats;
|
|
908
|
+
return [0];
|
|
909
|
+
}
|
|
910
|
+
isDisabled(type, val) {
|
|
911
|
+
if (type === 'hour')
|
|
912
|
+
return this.disabledHours.has(val);
|
|
913
|
+
if (type === 'minute')
|
|
914
|
+
return this.disabledMinutes.has(val);
|
|
915
|
+
return this.disabledPeriods.has(val);
|
|
916
|
+
}
|
|
917
|
+
// Called after native inertial scrolling settles.
|
|
918
|
+
onScrollIdle(type, element) {
|
|
919
|
+
if (!this.isInitialScrollDone)
|
|
920
|
+
return;
|
|
921
|
+
const list = this.listFor(type);
|
|
922
|
+
const N = list.length;
|
|
923
|
+
const looping = type === 'hour' || type === 'minute';
|
|
924
|
+
// 1. Seamless wrap-around: keep the resting position inside the central band.
|
|
925
|
+
if (looping) {
|
|
926
|
+
const band = N * this.ITEM_HEIGHT;
|
|
927
|
+
const start = this.centralBandStart(list, this.repeatsFor(type)) * this.ITEM_HEIGHT;
|
|
928
|
+
const end = start + band;
|
|
929
|
+
if (element.scrollTop < start || element.scrollTop >= end) {
|
|
930
|
+
const originalScrollBehavior = element.style.scrollBehavior;
|
|
931
|
+
element.style.scrollBehavior = 'auto';
|
|
932
|
+
if (element.scrollTop < start) {
|
|
933
|
+
element.scrollTop += band;
|
|
934
|
+
}
|
|
935
|
+
else {
|
|
936
|
+
element.scrollTop -= band;
|
|
937
|
+
}
|
|
938
|
+
// Force layout reflow
|
|
939
|
+
const _ = element.scrollTop;
|
|
940
|
+
element.style.scrollBehavior = originalScrollBehavior;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
// 2. Read the settled value.
|
|
944
|
+
let index = Math.round(element.scrollTop / this.ITEM_HEIGHT);
|
|
945
|
+
let origIndex = looping ? ((index % N) + N) % N : Math.max(0, Math.min(N - 1, index));
|
|
946
|
+
let val = list[origIndex];
|
|
947
|
+
// 3. If it landed on a disabled value, snap to the nearest enabled one.
|
|
948
|
+
if (val && this.isDisabled(type, val)) {
|
|
949
|
+
const enabled = this.nearestEnabled(type, origIndex);
|
|
950
|
+
if (enabled != null) {
|
|
951
|
+
this.scrollToValue(type, list[enabled], true);
|
|
952
|
+
origIndex = enabled;
|
|
953
|
+
val = list[enabled];
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
else {
|
|
957
|
+
// Enforce a clean rest position (native mandatory-snap usually already did).
|
|
958
|
+
const target = index * this.ITEM_HEIGHT;
|
|
959
|
+
if (Math.abs(element.scrollTop - target) > 0.5) {
|
|
960
|
+
this.scrollToValue(type, val, true);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
if (!val)
|
|
964
|
+
return;
|
|
965
|
+
// 4. Commit the selection inside Angular's zone.
|
|
966
|
+
if (type === 'hour' && this.selectedHour !== val) {
|
|
967
|
+
this.zone.run(() => {
|
|
968
|
+
this.selectedHour = val;
|
|
969
|
+
this.computeConstraints();
|
|
970
|
+
this.adjustSelectionOnConstraintChange();
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
else if (type === 'minute' && this.selectedMinute !== val) {
|
|
974
|
+
this.zone.run(() => {
|
|
975
|
+
this.selectedMinute = val;
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
else if (type === 'period' && this.selectedPeriod !== val) {
|
|
979
|
+
this.zone.run(() => {
|
|
980
|
+
this.selectedPeriod = val;
|
|
981
|
+
this.computeConstraints();
|
|
982
|
+
this.adjustSelectionOnConstraintChange();
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
// Search outward from origIndex for the closest enabled value index.
|
|
987
|
+
nearestEnabled(type, origIndex) {
|
|
988
|
+
const list = this.listFor(type);
|
|
989
|
+
const N = list.length;
|
|
990
|
+
const looping = type === 'hour' || type === 'minute';
|
|
991
|
+
if (!this.isDisabled(type, list[origIndex]))
|
|
992
|
+
return origIndex;
|
|
993
|
+
for (let offset = 1; offset < N; offset++) {
|
|
994
|
+
const up = looping ? (origIndex + offset) % N : origIndex + offset;
|
|
995
|
+
if (up >= 0 && up < N && !this.isDisabled(type, list[up]))
|
|
996
|
+
return up;
|
|
997
|
+
const down = looping ? ((origIndex - offset) % N + N) % N : origIndex - offset;
|
|
998
|
+
if (down >= 0 && down < N && !this.isDisabled(type, list[down]))
|
|
999
|
+
return down;
|
|
1000
|
+
}
|
|
1001
|
+
return null;
|
|
1002
|
+
}
|
|
1003
|
+
adjustSelectionOnConstraintChange() {
|
|
1004
|
+
if (this.mode === '12') {
|
|
1005
|
+
if (this.disabledPeriods.has(this.selectedPeriod)) {
|
|
1006
|
+
const alt = this.periods.find(p => !this.disabledPeriods.has(p));
|
|
1007
|
+
if (alt) {
|
|
1008
|
+
this.selectedPeriod = alt;
|
|
1009
|
+
this.scrollToValue('period', alt, true);
|
|
1010
|
+
this.computeConstraints();
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
if (this.disabledHours.has(this.selectedHour)) {
|
|
1014
|
+
const validHour = this.hours.find(h => !this.disabledHours.has(h));
|
|
1015
|
+
if (validHour) {
|
|
1016
|
+
this.selectedHour = validHour;
|
|
1017
|
+
this.scrollToValue('hour', validHour, true);
|
|
1018
|
+
this.computeConstraints();
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
else {
|
|
1023
|
+
if (this.disabledHours.has(this.selectedHour)) {
|
|
1024
|
+
const validHour = this.hours.find(h => !this.disabledHours.has(h));
|
|
1025
|
+
if (validHour) {
|
|
1026
|
+
this.selectedHour = validHour;
|
|
1027
|
+
this.scrollToValue('hour', validHour, true);
|
|
1028
|
+
this.computeConstraints();
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
if (this.disabledMinutes.has(this.selectedMinute)) {
|
|
1033
|
+
const validMinute = this.minutes.find(m => !this.disabledMinutes.has(m));
|
|
1034
|
+
if (validMinute) {
|
|
1035
|
+
this.selectedMinute = validMinute;
|
|
1036
|
+
this.scrollToValue('minute', validMinute, true);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
selectValue(type, val) {
|
|
1041
|
+
if (type === 'hour') {
|
|
1042
|
+
if (this.disabledHours.has(val))
|
|
1043
|
+
return;
|
|
1044
|
+
this.selectedHour = val;
|
|
1045
|
+
}
|
|
1046
|
+
else if (type === 'minute') {
|
|
1047
|
+
if (this.disabledMinutes.has(val))
|
|
1048
|
+
return;
|
|
1049
|
+
this.selectedMinute = val;
|
|
1050
|
+
}
|
|
1051
|
+
else if (type === 'period') {
|
|
1052
|
+
if (this.disabledPeriods.has(val))
|
|
1053
|
+
return;
|
|
1054
|
+
this.selectedPeriod = val;
|
|
1055
|
+
}
|
|
1056
|
+
this.scrollToValue(type, val, true);
|
|
1057
|
+
this.computeConstraints();
|
|
1058
|
+
this.adjustSelectionOnConstraintChange();
|
|
1059
|
+
}
|
|
1060
|
+
scrollToValue(type, val, smooth) {
|
|
1061
|
+
let element = null;
|
|
1062
|
+
if (type === 'hour')
|
|
1063
|
+
element = this.hourWheel?.nativeElement ?? null;
|
|
1064
|
+
else if (type === 'minute')
|
|
1065
|
+
element = this.minuteWheel?.nativeElement ?? null;
|
|
1066
|
+
else
|
|
1067
|
+
element = this.periodWheel?.nativeElement ?? null;
|
|
1068
|
+
if (!element)
|
|
1069
|
+
return;
|
|
1070
|
+
const list = this.listFor(type);
|
|
1071
|
+
const index = list.indexOf(val);
|
|
1072
|
+
if (index === -1)
|
|
1073
|
+
return;
|
|
1074
|
+
const looping = type === 'hour' || type === 'minute';
|
|
1075
|
+
const base = looping ? this.centralBandStart(list, this.repeatsFor(type)) : 0;
|
|
1076
|
+
const targetIndex = base + index;
|
|
1077
|
+
const originalBehavior = element.style.scrollBehavior;
|
|
1078
|
+
element.style.scrollBehavior = smooth ? 'smooth' : 'auto';
|
|
1079
|
+
element.scrollTop = targetIndex * this.ITEM_HEIGHT;
|
|
1080
|
+
// Force layout reflow
|
|
1081
|
+
const _ = element.scrollTop;
|
|
1082
|
+
element.style.scrollBehavior = originalBehavior;
|
|
1083
|
+
}
|
|
1084
|
+
onCancel() {
|
|
1085
|
+
this.cancelled.emit();
|
|
1086
|
+
}
|
|
1087
|
+
onConfirm() {
|
|
1088
|
+
this.confirmed.emit(this.getFormattedTime());
|
|
1089
|
+
}
|
|
1090
|
+
getFormattedTime() {
|
|
1091
|
+
if (this.mode === '24') {
|
|
1092
|
+
return `${this.selectedHour}:${this.selectedMinute}`;
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
return `${this.selectedHour}:${this.selectedMinute} ${this.selectedPeriod}`;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TimeWheelPanelComponent, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
|
|
1099
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: TimeWheelPanelComponent, isStandalone: false, selector: "lib-time-wheel-panel", inputs: { mode: "mode", value: "value", labels: "labels", minuteStep: "minuteStep", minTime: "minTime", maxTime: "maxTime" }, outputs: { confirmed: "confirmed", cancelled: "cancelled" }, viewQueries: [{ propertyName: "hourWheel", first: true, predicate: ["hourWheel"], descendants: true }, { propertyName: "minuteWheel", first: true, predicate: ["minuteWheel"], descendants: true }, { propertyName: "periodWheel", first: true, predicate: ["periodWheel"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"cc-twp-dialog\">\r\n <!-- Subtle Configurable Title Header -->\r\n <div class=\"cc-twp-header\">\r\n <span class=\"cc-twp-title\">{{ labels?.selectTimeTitle || 'Select Time' }}</span>\r\n </div>\r\n\r\n <!-- Digital Preview -->\r\n <div class=\"cc-twp-preview\">\r\n <span class=\"cc-twp-preview-num\">{{ selectedHour }}</span>\r\n <span class=\"cc-twp-preview-colon\">:</span>\r\n <span class=\"cc-twp-preview-num\">{{ selectedMinute }}</span>\r\n <span class=\"cc-twp-preview-period\" *ngIf=\"mode === '12'\"> {{ selectedPeriod }}</span>\r\n </div>\r\n\r\n <!-- Wheel Area -->\r\n <div class=\"cc-twp-wheels\" [class.mode-24]=\"mode === '24'\">\r\n <div class=\"cc-twp-highlight-bar\"></div>\r\n\r\n <!-- Hour Column -->\r\n <div class=\"cc-twp-col\">\r\n <span class=\"cc-twp-col-label\">{{ labels?.hourLabel || 'Hour' }}</span>\r\n <div class=\"cc-twp-scroll\" #hourWheel>\r\n <div class=\"cc-twp-spacer\" *ngFor=\"let s of spacerRows\"></div>\r\n <ng-container *ngFor=\"let rep of hourRepeats\">\r\n <div *ngFor=\"let h of hours\"\r\n class=\"cc-twp-item\"\r\n [class.active]=\"selectedHour === h\"\r\n [class.disabled]=\"disabledHours.has(h)\"\r\n [attr.aria-disabled]=\"disabledHours.has(h)\"\r\n (click)=\"selectValue('hour', h)\">{{ h }}</div>\r\n </ng-container>\r\n <div class=\"cc-twp-spacer\" *ngFor=\"let s of spacerRows\"></div>\r\n </div>\r\n </div>\r\n\r\n <!-- Minute Column -->\r\n <div class=\"cc-twp-col\">\r\n <span class=\"cc-twp-col-label\">{{ labels?.minuteLabel || 'Minute' }}</span>\r\n <div class=\"cc-twp-scroll\" #minuteWheel>\r\n <div class=\"cc-twp-spacer\" *ngFor=\"let s of spacerRows\"></div>\r\n <ng-container *ngFor=\"let rep of minuteRepeats\">\r\n <div *ngFor=\"let m of minutes\"\r\n class=\"cc-twp-item\"\r\n [class.active]=\"selectedMinute === m\"\r\n [class.disabled]=\"disabledMinutes.has(m)\"\r\n [attr.aria-disabled]=\"disabledMinutes.has(m)\"\r\n (click)=\"selectValue('minute', m)\">{{ m }}</div>\r\n </ng-container>\r\n <div class=\"cc-twp-spacer\" *ngFor=\"let s of spacerRows\"></div>\r\n </div>\r\n </div>\r\n\r\n <!-- AM/PM Column \u2014 12hr mode only -->\r\n <div class=\"cc-twp-col\" *ngIf=\"mode === '12'\">\r\n <span class=\"cc-twp-col-label\">{{ labels?.periodLabel || 'AM/PM' }}</span>\r\n <div class=\"cc-twp-scroll\" #periodWheel>\r\n <div class=\"cc-twp-spacer\" *ngFor=\"let s of spacerRows\"></div>\r\n <div *ngFor=\"let p of periods\"\r\n class=\"cc-twp-item\"\r\n [class.active]=\"selectedPeriod === p\"\r\n [class.disabled]=\"disabledPeriods.has(p)\"\r\n [attr.aria-disabled]=\"disabledPeriods.has(p)\"\r\n (click)=\"selectValue('period', p)\">{{ p }}</div>\r\n <div class=\"cc-twp-spacer\" *ngFor=\"let s of spacerRows\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"cc-twp-footer\">\r\n <button class=\"cc-twp-btn cc-twp-btn-cancel\" (click)=\"onCancel()\">\r\n {{ labels?.cancelLabel || 'Cancel' }}\r\n </button>\r\n <button class=\"cc-twp-btn cc-twp-btn-confirm\" (click)=\"onConfirm()\">\r\n {{ labels?.confirmLabel || 'Confirm' }}\r\n </button>\r\n </div>\r\n</div>\r\n", styles: ["@charset \"UTF-8\";.cc-twp-dialog{background-color:var(--cc-modal-bg, #ffffff);border-radius:var(--cc-modal-radius, .75rem);box-shadow:var(--cc-modal-shadow, 0 .25rem 1.5rem rgba(0, 0, 0, .15));border:var(--cc-modal-border, none);width:280px;box-sizing:border-box;display:flex;flex-direction:column;gap:16px;padding:18px;font-family:inherit}.cc-twp-header{display:flex;justify-content:center;align-items:center;margin-bottom:4px}.cc-twp-title{font-size:14px;font-weight:600;color:var(--cc-timepicker-text-primary, #111827);letter-spacing:-.2px;-webkit-user-select:none;user-select:none}.cc-twp-preview{display:flex;align-items:center;justify-content:center;background-color:var(--cc-timepicker-accent-light, rgba(0, 0, 0, .04));border:1px solid var(--cc-timepicker-accent-border, transparent);padding:10px 18px;border-radius:10px;gap:6px}.cc-twp-preview-num{font-size:26px;font-weight:700;color:var(--cc-timepicker-accent, #111827);font-variant-numeric:tabular-nums;letter-spacing:-.5px}.cc-twp-preview-colon{font-size:24px;font-weight:600;color:var(--cc-timepicker-accent, #111827);margin-top:-4px}.cc-twp-preview-period{font-size:15px;font-weight:600;color:var(--cc-timepicker-text-muted, #6b7280);margin-left:4px;align-self:flex-end;margin-bottom:4px}.cc-twp-wheels{position:relative;display:flex;justify-content:space-between;border:1px solid var(--cc-timepicker-wheels-border, transparent);border-radius:10px;padding:6px 12px;overflow:hidden;height:140px;box-sizing:border-box;background-color:var(--cc-modal-bg, #ffffff)}.cc-twp-wheels:before,.cc-twp-wheels:after{content:\"\";position:absolute;left:0;right:0;height:30px;pointer-events:none;z-index:4}.cc-twp-wheels:before{top:26px;background:linear-gradient(to bottom,var(--cc-modal-bg, #ffffff) 20%,rgba(255,255,255,0) 100%)}.cc-twp-wheels:after{bottom:6px;background:linear-gradient(to top,var(--cc-modal-bg, #ffffff) 20%,rgba(255,255,255,0) 100%)}.cc-twp-highlight-bar{position:absolute;height:36px;top:62px;left:6px;right:6px;background-color:var(--cc-timepicker-accent-light, rgba(0, 0, 0, .05));border:1px solid var(--cc-timepicker-accent-border, transparent);border-radius:8px;pointer-events:none;z-index:2}.cc-twp-col{flex:1;display:flex;flex-direction:column;align-items:center;position:relative;z-index:3}.cc-twp-col-label{font-size:10px;font-weight:400;text-transform:uppercase;color:var(--cc-timepicker-text-muted, #9ca3af);letter-spacing:.5px;margin-bottom:6px;height:14px;line-height:14px;-webkit-user-select:none;user-select:none}.cc-twp-scroll{width:100%;height:108px;overflow-y:scroll;scroll-snap-type:y mandatory;scrollbar-width:none;-ms-overflow-style:none}.cc-twp-scroll::-webkit-scrollbar{display:none}.cc-twp-item,.cc-twp-spacer{height:36px;line-height:36px;text-align:center;box-sizing:border-box}.cc-twp-spacer{pointer-events:none}.cc-twp-item{font-size:var(--cc-timepicker-item-font-size, 1rem);font-weight:500;color:var(--cc-timepicker-text-muted, #9ca3af);cursor:pointer;scroll-snap-align:center;-webkit-user-select:none;user-select:none;font-variant-numeric:tabular-nums;backface-visibility:hidden;transition:color .15s ease-out,font-weight .15s ease-out}.cc-twp-item:hover:not(.disabled){color:var(--cc-timepicker-text-primary, #111827)}.cc-twp-item.active{color:var(--cc-timepicker-accent, #111827);font-weight:700}.cc-twp-item.disabled{opacity:.25;cursor:not-allowed;pointer-events:none}.cc-twp-footer{display:flex;gap:12px;justify-content:space-between;padding-top:4px}.cc-twp-btn{flex:1;padding:9px 16px;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s ease;border:1px solid transparent;outline:none}.cc-twp-btn-cancel{background-color:var(--cc-timepicker-btn-cancel-bg, #f3f4f6);border-color:var(--cc-timepicker-btn-cancel-border, #f3f4f6);color:var(--cc-timepicker-text-primary, #111827)}.cc-twp-btn-cancel:hover{background-color:var(--cc-timepicker-btn-cancel-hover, #e5e7eb)}.cc-twp-btn-confirm{background-color:var(--cc-timepicker-btn-confirm-bg, #111827);color:#fff}.cc-twp-btn-confirm:hover{background-color:var(--cc-timepicker-btn-confirm-hover, #000000)}\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"] }] });
|
|
1100
|
+
}
|
|
1101
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TimeWheelPanelComponent, decorators: [{
|
|
1102
|
+
type: Component,
|
|
1103
|
+
args: [{ selector: 'lib-time-wheel-panel', standalone: false, template: "<div class=\"cc-twp-dialog\">\r\n <!-- Subtle Configurable Title Header -->\r\n <div class=\"cc-twp-header\">\r\n <span class=\"cc-twp-title\">{{ labels?.selectTimeTitle || 'Select Time' }}</span>\r\n </div>\r\n\r\n <!-- Digital Preview -->\r\n <div class=\"cc-twp-preview\">\r\n <span class=\"cc-twp-preview-num\">{{ selectedHour }}</span>\r\n <span class=\"cc-twp-preview-colon\">:</span>\r\n <span class=\"cc-twp-preview-num\">{{ selectedMinute }}</span>\r\n <span class=\"cc-twp-preview-period\" *ngIf=\"mode === '12'\"> {{ selectedPeriod }}</span>\r\n </div>\r\n\r\n <!-- Wheel Area -->\r\n <div class=\"cc-twp-wheels\" [class.mode-24]=\"mode === '24'\">\r\n <div class=\"cc-twp-highlight-bar\"></div>\r\n\r\n <!-- Hour Column -->\r\n <div class=\"cc-twp-col\">\r\n <span class=\"cc-twp-col-label\">{{ labels?.hourLabel || 'Hour' }}</span>\r\n <div class=\"cc-twp-scroll\" #hourWheel>\r\n <div class=\"cc-twp-spacer\" *ngFor=\"let s of spacerRows\"></div>\r\n <ng-container *ngFor=\"let rep of hourRepeats\">\r\n <div *ngFor=\"let h of hours\"\r\n class=\"cc-twp-item\"\r\n [class.active]=\"selectedHour === h\"\r\n [class.disabled]=\"disabledHours.has(h)\"\r\n [attr.aria-disabled]=\"disabledHours.has(h)\"\r\n (click)=\"selectValue('hour', h)\">{{ h }}</div>\r\n </ng-container>\r\n <div class=\"cc-twp-spacer\" *ngFor=\"let s of spacerRows\"></div>\r\n </div>\r\n </div>\r\n\r\n <!-- Minute Column -->\r\n <div class=\"cc-twp-col\">\r\n <span class=\"cc-twp-col-label\">{{ labels?.minuteLabel || 'Minute' }}</span>\r\n <div class=\"cc-twp-scroll\" #minuteWheel>\r\n <div class=\"cc-twp-spacer\" *ngFor=\"let s of spacerRows\"></div>\r\n <ng-container *ngFor=\"let rep of minuteRepeats\">\r\n <div *ngFor=\"let m of minutes\"\r\n class=\"cc-twp-item\"\r\n [class.active]=\"selectedMinute === m\"\r\n [class.disabled]=\"disabledMinutes.has(m)\"\r\n [attr.aria-disabled]=\"disabledMinutes.has(m)\"\r\n (click)=\"selectValue('minute', m)\">{{ m }}</div>\r\n </ng-container>\r\n <div class=\"cc-twp-spacer\" *ngFor=\"let s of spacerRows\"></div>\r\n </div>\r\n </div>\r\n\r\n <!-- AM/PM Column \u2014 12hr mode only -->\r\n <div class=\"cc-twp-col\" *ngIf=\"mode === '12'\">\r\n <span class=\"cc-twp-col-label\">{{ labels?.periodLabel || 'AM/PM' }}</span>\r\n <div class=\"cc-twp-scroll\" #periodWheel>\r\n <div class=\"cc-twp-spacer\" *ngFor=\"let s of spacerRows\"></div>\r\n <div *ngFor=\"let p of periods\"\r\n class=\"cc-twp-item\"\r\n [class.active]=\"selectedPeriod === p\"\r\n [class.disabled]=\"disabledPeriods.has(p)\"\r\n [attr.aria-disabled]=\"disabledPeriods.has(p)\"\r\n (click)=\"selectValue('period', p)\">{{ p }}</div>\r\n <div class=\"cc-twp-spacer\" *ngFor=\"let s of spacerRows\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"cc-twp-footer\">\r\n <button class=\"cc-twp-btn cc-twp-btn-cancel\" (click)=\"onCancel()\">\r\n {{ labels?.cancelLabel || 'Cancel' }}\r\n </button>\r\n <button class=\"cc-twp-btn cc-twp-btn-confirm\" (click)=\"onConfirm()\">\r\n {{ labels?.confirmLabel || 'Confirm' }}\r\n </button>\r\n </div>\r\n</div>\r\n", styles: ["@charset \"UTF-8\";.cc-twp-dialog{background-color:var(--cc-modal-bg, #ffffff);border-radius:var(--cc-modal-radius, .75rem);box-shadow:var(--cc-modal-shadow, 0 .25rem 1.5rem rgba(0, 0, 0, .15));border:var(--cc-modal-border, none);width:280px;box-sizing:border-box;display:flex;flex-direction:column;gap:16px;padding:18px;font-family:inherit}.cc-twp-header{display:flex;justify-content:center;align-items:center;margin-bottom:4px}.cc-twp-title{font-size:14px;font-weight:600;color:var(--cc-timepicker-text-primary, #111827);letter-spacing:-.2px;-webkit-user-select:none;user-select:none}.cc-twp-preview{display:flex;align-items:center;justify-content:center;background-color:var(--cc-timepicker-accent-light, rgba(0, 0, 0, .04));border:1px solid var(--cc-timepicker-accent-border, transparent);padding:10px 18px;border-radius:10px;gap:6px}.cc-twp-preview-num{font-size:26px;font-weight:700;color:var(--cc-timepicker-accent, #111827);font-variant-numeric:tabular-nums;letter-spacing:-.5px}.cc-twp-preview-colon{font-size:24px;font-weight:600;color:var(--cc-timepicker-accent, #111827);margin-top:-4px}.cc-twp-preview-period{font-size:15px;font-weight:600;color:var(--cc-timepicker-text-muted, #6b7280);margin-left:4px;align-self:flex-end;margin-bottom:4px}.cc-twp-wheels{position:relative;display:flex;justify-content:space-between;border:1px solid var(--cc-timepicker-wheels-border, transparent);border-radius:10px;padding:6px 12px;overflow:hidden;height:140px;box-sizing:border-box;background-color:var(--cc-modal-bg, #ffffff)}.cc-twp-wheels:before,.cc-twp-wheels:after{content:\"\";position:absolute;left:0;right:0;height:30px;pointer-events:none;z-index:4}.cc-twp-wheels:before{top:26px;background:linear-gradient(to bottom,var(--cc-modal-bg, #ffffff) 20%,rgba(255,255,255,0) 100%)}.cc-twp-wheels:after{bottom:6px;background:linear-gradient(to top,var(--cc-modal-bg, #ffffff) 20%,rgba(255,255,255,0) 100%)}.cc-twp-highlight-bar{position:absolute;height:36px;top:62px;left:6px;right:6px;background-color:var(--cc-timepicker-accent-light, rgba(0, 0, 0, .05));border:1px solid var(--cc-timepicker-accent-border, transparent);border-radius:8px;pointer-events:none;z-index:2}.cc-twp-col{flex:1;display:flex;flex-direction:column;align-items:center;position:relative;z-index:3}.cc-twp-col-label{font-size:10px;font-weight:400;text-transform:uppercase;color:var(--cc-timepicker-text-muted, #9ca3af);letter-spacing:.5px;margin-bottom:6px;height:14px;line-height:14px;-webkit-user-select:none;user-select:none}.cc-twp-scroll{width:100%;height:108px;overflow-y:scroll;scroll-snap-type:y mandatory;scrollbar-width:none;-ms-overflow-style:none}.cc-twp-scroll::-webkit-scrollbar{display:none}.cc-twp-item,.cc-twp-spacer{height:36px;line-height:36px;text-align:center;box-sizing:border-box}.cc-twp-spacer{pointer-events:none}.cc-twp-item{font-size:var(--cc-timepicker-item-font-size, 1rem);font-weight:500;color:var(--cc-timepicker-text-muted, #9ca3af);cursor:pointer;scroll-snap-align:center;-webkit-user-select:none;user-select:none;font-variant-numeric:tabular-nums;backface-visibility:hidden;transition:color .15s ease-out,font-weight .15s ease-out}.cc-twp-item:hover:not(.disabled){color:var(--cc-timepicker-text-primary, #111827)}.cc-twp-item.active{color:var(--cc-timepicker-accent, #111827);font-weight:700}.cc-twp-item.disabled{opacity:.25;cursor:not-allowed;pointer-events:none}.cc-twp-footer{display:flex;gap:12px;justify-content:space-between;padding-top:4px}.cc-twp-btn{flex:1;padding:9px 16px;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s ease;border:1px solid transparent;outline:none}.cc-twp-btn-cancel{background-color:var(--cc-timepicker-btn-cancel-bg, #f3f4f6);border-color:var(--cc-timepicker-btn-cancel-border, #f3f4f6);color:var(--cc-timepicker-text-primary, #111827)}.cc-twp-btn-cancel:hover{background-color:var(--cc-timepicker-btn-cancel-hover, #e5e7eb)}.cc-twp-btn-confirm{background-color:var(--cc-timepicker-btn-confirm-bg, #111827);color:#fff}.cc-twp-btn-confirm:hover{background-color:var(--cc-timepicker-btn-confirm-hover, #000000)}\n"] }]
|
|
1104
|
+
}], ctorParameters: () => [{ type: i0.NgZone }], propDecorators: { mode: [{
|
|
1105
|
+
type: Input
|
|
1106
|
+
}], value: [{
|
|
1107
|
+
type: Input
|
|
1108
|
+
}], labels: [{
|
|
1109
|
+
type: Input
|
|
1110
|
+
}], minuteStep: [{
|
|
1111
|
+
type: Input
|
|
1112
|
+
}], minTime: [{
|
|
1113
|
+
type: Input
|
|
1114
|
+
}], maxTime: [{
|
|
1115
|
+
type: Input
|
|
1116
|
+
}], confirmed: [{
|
|
1117
|
+
type: Output
|
|
1118
|
+
}], cancelled: [{
|
|
1119
|
+
type: Output
|
|
1120
|
+
}], hourWheel: [{
|
|
1121
|
+
type: ViewChild,
|
|
1122
|
+
args: ['hourWheel']
|
|
1123
|
+
}], minuteWheel: [{
|
|
1124
|
+
type: ViewChild,
|
|
1125
|
+
args: ['minuteWheel']
|
|
1126
|
+
}], periodWheel: [{
|
|
1127
|
+
type: ViewChild,
|
|
1128
|
+
args: ['periodWheel']
|
|
1129
|
+
}] } });
|
|
1130
|
+
|
|
1131
|
+
class TimePickerComponent {
|
|
1132
|
+
config;
|
|
1133
|
+
labels;
|
|
1134
|
+
label = '';
|
|
1135
|
+
placeholder = '';
|
|
1136
|
+
variant = 'wheel';
|
|
1137
|
+
mode = '12';
|
|
1138
|
+
minuteStep = 0;
|
|
1139
|
+
disabled = false;
|
|
1140
|
+
required = false;
|
|
1141
|
+
errorMessage = '';
|
|
1142
|
+
minTime;
|
|
1143
|
+
maxTime;
|
|
1144
|
+
// Style inputs (to mirror datepicker pattern)
|
|
1145
|
+
width;
|
|
1146
|
+
height;
|
|
1147
|
+
borderRadius;
|
|
1148
|
+
fontSize;
|
|
1149
|
+
gap;
|
|
1150
|
+
fontFamily;
|
|
1151
|
+
labelColor;
|
|
1152
|
+
labelFontSize;
|
|
1153
|
+
labelFontWeight;
|
|
1154
|
+
backgroundColor;
|
|
1155
|
+
borderColor;
|
|
1156
|
+
borderWidth;
|
|
1157
|
+
padding;
|
|
1158
|
+
fontWeight;
|
|
1159
|
+
color;
|
|
1160
|
+
placeholderColor;
|
|
1161
|
+
focusBorderColor;
|
|
1162
|
+
errorColor;
|
|
1163
|
+
disabledBackgroundColor;
|
|
1164
|
+
disabledColor;
|
|
1165
|
+
timeChange = new EventEmitter();
|
|
1166
|
+
displayValue = '';
|
|
1167
|
+
isOpen = false;
|
|
1168
|
+
onChange = () => { };
|
|
1169
|
+
onTouched = () => { };
|
|
1170
|
+
ngOnInit() {
|
|
1171
|
+
this.updateFromConfig();
|
|
1172
|
+
}
|
|
1173
|
+
ngOnChanges(changes) {
|
|
1174
|
+
if (changes['config']) {
|
|
1175
|
+
this.updateFromConfig();
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
updateFromConfig() {
|
|
1179
|
+
if (this.config) {
|
|
1180
|
+
this.label = this.config.label ?? this.label;
|
|
1181
|
+
this.placeholder = this.config.placeholder ?? this.placeholder;
|
|
1182
|
+
this.variant = this.config.variant ?? this.variant;
|
|
1183
|
+
this.mode = this.config.mode ?? this.mode;
|
|
1184
|
+
this.minuteStep = this.config.minuteStep ?? this.minuteStep;
|
|
1185
|
+
this.disabled = this.config.disabled ?? this.disabled;
|
|
1186
|
+
this.required = this.config.required ?? this.required;
|
|
1187
|
+
this.errorMessage = this.config.errorMessage ?? this.errorMessage;
|
|
1188
|
+
this.width = this.config.width ?? this.width;
|
|
1189
|
+
this.height = this.config.height ?? this.height;
|
|
1190
|
+
this.borderRadius = this.config.borderRadius ?? this.borderRadius;
|
|
1191
|
+
this.fontSize = this.config.fontSize ?? this.fontSize;
|
|
1192
|
+
this.gap = this.config.gap ?? this.gap;
|
|
1193
|
+
this.fontFamily = this.config.fontFamily ?? this.fontFamily;
|
|
1194
|
+
this.labelColor = this.config.labelColor ?? this.labelColor;
|
|
1195
|
+
this.labelFontSize = this.config.labelFontSize ?? this.labelFontSize;
|
|
1196
|
+
this.labelFontWeight = this.config.labelFontWeight ?? this.labelFontWeight;
|
|
1197
|
+
this.backgroundColor = this.config.backgroundColor ?? this.backgroundColor;
|
|
1198
|
+
this.borderColor = this.config.borderColor ?? this.borderColor;
|
|
1199
|
+
this.borderWidth = this.config.borderWidth ?? this.borderWidth;
|
|
1200
|
+
this.padding = this.config.padding ?? this.padding;
|
|
1201
|
+
this.fontWeight = this.config.fontWeight ?? this.fontWeight;
|
|
1202
|
+
this.color = this.config.color ?? this.color;
|
|
1203
|
+
this.placeholderColor = this.config.placeholderColor ?? this.placeholderColor;
|
|
1204
|
+
this.focusBorderColor = this.config.focusBorderColor ?? this.focusBorderColor;
|
|
1205
|
+
this.errorColor = this.config.errorColor ?? this.errorColor;
|
|
1206
|
+
this.disabledBackgroundColor = this.config.disabledBackgroundColor ?? this.disabledBackgroundColor;
|
|
1207
|
+
this.disabledColor = this.config.disabledColor ?? this.disabledColor;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
writeValue(value) {
|
|
1211
|
+
this.displayValue = value || '';
|
|
1212
|
+
}
|
|
1213
|
+
registerOnChange(fn) {
|
|
1214
|
+
this.onChange = fn;
|
|
1215
|
+
}
|
|
1216
|
+
registerOnTouched(fn) {
|
|
1217
|
+
this.onTouched = fn;
|
|
1218
|
+
}
|
|
1219
|
+
setDisabledState(isDisabled) {
|
|
1220
|
+
this.disabled = isDisabled;
|
|
1221
|
+
}
|
|
1222
|
+
togglePanel() {
|
|
1223
|
+
if (this.disabled)
|
|
1224
|
+
return;
|
|
1225
|
+
this.isOpen = !this.isOpen;
|
|
1226
|
+
}
|
|
1227
|
+
closePanel() {
|
|
1228
|
+
this.isOpen = false;
|
|
1229
|
+
}
|
|
1230
|
+
onConfirmed(timeString) {
|
|
1231
|
+
this.displayValue = timeString;
|
|
1232
|
+
this.onChange(timeString);
|
|
1233
|
+
this.timeChange.emit(timeString);
|
|
1234
|
+
this.isOpen = false;
|
|
1235
|
+
this.onTouched();
|
|
1236
|
+
}
|
|
1237
|
+
onCancelled() {
|
|
1238
|
+
this.isOpen = false;
|
|
1239
|
+
this.onTouched();
|
|
1240
|
+
}
|
|
1241
|
+
get wrapperStyles() {
|
|
1242
|
+
return {
|
|
1243
|
+
'width': this.width ? `${this.width}` : undefined,
|
|
1244
|
+
'--cc-timepicker-label-gap': this.gap ? `${this.gap}` : undefined,
|
|
1245
|
+
'--cc-timepicker-font-family': this.fontFamily ? `${this.fontFamily}` : undefined
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
get labelStyles() {
|
|
1249
|
+
return {
|
|
1250
|
+
'--cc-timepicker-label-color': this.labelColor ? `${this.labelColor}` : undefined,
|
|
1251
|
+
'--cc-timepicker-label-font-size': this.labelFontSize ? `${this.labelFontSize}` : undefined,
|
|
1252
|
+
'--cc-timepicker-label-font-weight': this.labelFontWeight ? `${this.labelFontWeight}` : undefined
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
get fieldStyles() {
|
|
1256
|
+
return {
|
|
1257
|
+
'--cc-timepicker-bg': this.backgroundColor ? `${this.backgroundColor}` : undefined,
|
|
1258
|
+
'--cc-timepicker-border-radius': this.borderRadius ? `${this.borderRadius}` : undefined,
|
|
1259
|
+
'--cc-timepicker-border-color': this.borderColor ? `${this.borderColor}` : undefined,
|
|
1260
|
+
'--cc-timepicker-border-width': this.borderWidth ? `${this.borderWidth}` : undefined,
|
|
1261
|
+
'--cc-timepicker-height': this.height ? `${this.height}` : undefined,
|
|
1262
|
+
'--cc-timepicker-padding': this.padding ? `${this.padding}` : undefined,
|
|
1263
|
+
'--cc-timepicker-font-size': this.fontSize ? `${this.fontSize}` : undefined,
|
|
1264
|
+
'--cc-timepicker-font-weight': this.fontWeight ? `${this.fontWeight}` : undefined,
|
|
1265
|
+
'--cc-timepicker-color': this.color ? `${this.color}` : undefined,
|
|
1266
|
+
'--cc-timepicker-placeholder-color': this.placeholderColor ? `${this.placeholderColor}` : undefined,
|
|
1267
|
+
'--cc-timepicker-focus-border-color': this.focusBorderColor ? `${this.focusBorderColor}` : undefined,
|
|
1268
|
+
'--cc-timepicker-error-color': this.errorColor ? `${this.errorColor}` : undefined,
|
|
1269
|
+
'--cc-timepicker-disabled-bg': this.disabledBackgroundColor ? `${this.disabledBackgroundColor}` : undefined,
|
|
1270
|
+
'--cc-timepicker-disabled-color': this.disabledColor ? `${this.disabledColor}` : undefined
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TimePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1274
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: TimePickerComponent, isStandalone: false, selector: "lib-time-picker", inputs: { config: "config", labels: "labels", label: "label", placeholder: "placeholder", variant: "variant", mode: "mode", minuteStep: "minuteStep", disabled: "disabled", required: "required", errorMessage: "errorMessage", minTime: "minTime", maxTime: "maxTime", 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" }, outputs: { timeChange: "timeChange" }, providers: [
|
|
1275
|
+
{
|
|
1276
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1277
|
+
useExisting: forwardRef(() => TimePickerComponent),
|
|
1278
|
+
multi: true
|
|
1279
|
+
}
|
|
1280
|
+
], usesOnChanges: true, ngImport: i0, template: "<div class=\"cc-timepicker-wrapper\" [ngStyle]=\"wrapperStyles\">\r\n <label *ngIf=\"label\" class=\"cc-timepicker-label\" [ngStyle]=\"labelStyles\">\r\n {{ label }}\r\n <span *ngIf=\"required\" class=\"cc-timepicker-required\">{{ labels?.requiredMarker || '*' }}</span>\r\n </label>\r\n\r\n <!-- Trigger Field -->\r\n <div class=\"cc-timepicker-field\"\r\n [class.focused]=\"isOpen\"\r\n [class.disabled]=\"disabled\"\r\n [class.error]=\"errorMessage\"\r\n [ngStyle]=\"fieldStyles\"\r\n (click)=\"!disabled && togglePanel()\">\r\n <span class=\"cc-timepicker-value\" [class.is-placeholder]=\"!displayValue\">\r\n {{ displayValue || placeholder || 'Select time' }}\r\n </span>\r\n <span class=\"cc-timepicker-icon material-icons\">schedule</span>\r\n </div>\r\n\r\n <!-- Centered Modal Overlay -->\r\n <div class=\"cc-timepicker-overlay\" *ngIf=\"isOpen\" (click)=\"onCancelled()\">\r\n <div class=\"cc-timepicker-modal-content\" (click)=\"$event.stopPropagation()\">\r\n <lib-time-wheel-panel\r\n [mode]=\"mode\"\r\n [value]=\"displayValue\"\r\n [labels]=\"labels\"\r\n [minuteStep]=\"minuteStep\"\r\n [minTime]=\"minTime\"\r\n [maxTime]=\"maxTime\"\r\n (confirmed)=\"onConfirmed($event)\"\r\n (cancelled)=\"onCancelled()\">\r\n </lib-time-wheel-panel>\r\n </div>\r\n </div>\r\n\r\n <div class=\"cc-error-message\" *ngIf=\"errorMessage\">{{ errorMessage }}</div>\r\n</div>\r\n", styles: ["@charset \"UTF-8\";.cc-timepicker-wrapper{display:flex;flex-direction:column;gap:var(--cc-timepicker-label-gap, .5rem);font-family:var(--cc-timepicker-font-family, inherit);position:relative;width:100%}.cc-timepicker-label{font-size:var(--cc-timepicker-label-font-size, .875rem);font-weight:var(--cc-timepicker-label-font-weight, 500);color:var(--cc-timepicker-label-color, #202124);display:flex;align-items:center}.cc-timepicker-required{color:var(--cc-timepicker-required-color, #D93025);margin-left:.125rem}.cc-timepicker-field{display:flex;align-items:center;justify-content:space-between;height:var(--cc-timepicker-height, 2.5rem);padding:var(--cc-timepicker-padding, .375rem 1rem);border-radius:var(--cc-timepicker-border-radius, .4375rem);border:var(--cc-timepicker-border-width, 1px) solid var(--cc-timepicker-border-color, #BDC1C6);background-color:var(--cc-timepicker-bg, #FEFEFE);cursor:pointer;box-sizing:border-box;transition:border-color .2s ease,box-shadow .2s ease;-webkit-user-select:none;user-select:none}.cc-timepicker-field:hover:not(.disabled){border-color:var(--cc-timepicker-focus-border-color, #1A73E8)}.cc-timepicker-field.focused{border-color:var(--cc-timepicker-focus-border-color, #1A73E8);box-shadow:0 0 0 2px #1a73e833}.cc-timepicker-field.disabled{background-color:var(--cc-timepicker-disabled-bg, #F1F3F4);border-color:var(--cc-timepicker-border-color, #BDC1C6);cursor:not-allowed}.cc-timepicker-value{font-size:var(--cc-timepicker-font-size, .875rem);font-weight:var(--cc-timepicker-font-weight, 400);color:var(--cc-timepicker-color, #202124);font-variant-numeric:tabular-nums}.cc-timepicker-field.disabled .cc-timepicker-value{color:var(--cc-timepicker-disabled-color, #80868B)}.cc-timepicker-value.is-placeholder{color:var(--cc-timepicker-placeholder-color, #80868B)}.cc-timepicker-icon{font-size:20px;color:var(--cc-timepicker-icon-color, #5F6368);display:flex;align-items:center;justify-content:center}.cc-timepicker-field.disabled .cc-timepicker-icon{color:var(--cc-timepicker-disabled-color, #80868B)}.cc-timepicker-overlay{position:fixed;inset:0;background-color:#0006;display:flex;align-items:center;justify-content:center;z-index:9999}.cc-timepicker-modal-content{display:block}.cc-error-message{font-size:.75rem;color:var(--cc-timepicker-error-color, #D93025);margin-top:.25rem}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: TimeWheelPanelComponent, selector: "lib-time-wheel-panel", inputs: ["mode", "value", "labels", "minuteStep", "minTime", "maxTime"], outputs: ["confirmed", "cancelled"] }] });
|
|
1281
|
+
}
|
|
1282
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TimePickerComponent, decorators: [{
|
|
1283
|
+
type: Component,
|
|
1284
|
+
args: [{ selector: 'lib-time-picker', standalone: false, providers: [
|
|
1285
|
+
{
|
|
1286
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1287
|
+
useExisting: forwardRef(() => TimePickerComponent),
|
|
1288
|
+
multi: true
|
|
1289
|
+
}
|
|
1290
|
+
], template: "<div class=\"cc-timepicker-wrapper\" [ngStyle]=\"wrapperStyles\">\r\n <label *ngIf=\"label\" class=\"cc-timepicker-label\" [ngStyle]=\"labelStyles\">\r\n {{ label }}\r\n <span *ngIf=\"required\" class=\"cc-timepicker-required\">{{ labels?.requiredMarker || '*' }}</span>\r\n </label>\r\n\r\n <!-- Trigger Field -->\r\n <div class=\"cc-timepicker-field\"\r\n [class.focused]=\"isOpen\"\r\n [class.disabled]=\"disabled\"\r\n [class.error]=\"errorMessage\"\r\n [ngStyle]=\"fieldStyles\"\r\n (click)=\"!disabled && togglePanel()\">\r\n <span class=\"cc-timepicker-value\" [class.is-placeholder]=\"!displayValue\">\r\n {{ displayValue || placeholder || 'Select time' }}\r\n </span>\r\n <span class=\"cc-timepicker-icon material-icons\">schedule</span>\r\n </div>\r\n\r\n <!-- Centered Modal Overlay -->\r\n <div class=\"cc-timepicker-overlay\" *ngIf=\"isOpen\" (click)=\"onCancelled()\">\r\n <div class=\"cc-timepicker-modal-content\" (click)=\"$event.stopPropagation()\">\r\n <lib-time-wheel-panel\r\n [mode]=\"mode\"\r\n [value]=\"displayValue\"\r\n [labels]=\"labels\"\r\n [minuteStep]=\"minuteStep\"\r\n [minTime]=\"minTime\"\r\n [maxTime]=\"maxTime\"\r\n (confirmed)=\"onConfirmed($event)\"\r\n (cancelled)=\"onCancelled()\">\r\n </lib-time-wheel-panel>\r\n </div>\r\n </div>\r\n\r\n <div class=\"cc-error-message\" *ngIf=\"errorMessage\">{{ errorMessage }}</div>\r\n</div>\r\n", styles: ["@charset \"UTF-8\";.cc-timepicker-wrapper{display:flex;flex-direction:column;gap:var(--cc-timepicker-label-gap, .5rem);font-family:var(--cc-timepicker-font-family, inherit);position:relative;width:100%}.cc-timepicker-label{font-size:var(--cc-timepicker-label-font-size, .875rem);font-weight:var(--cc-timepicker-label-font-weight, 500);color:var(--cc-timepicker-label-color, #202124);display:flex;align-items:center}.cc-timepicker-required{color:var(--cc-timepicker-required-color, #D93025);margin-left:.125rem}.cc-timepicker-field{display:flex;align-items:center;justify-content:space-between;height:var(--cc-timepicker-height, 2.5rem);padding:var(--cc-timepicker-padding, .375rem 1rem);border-radius:var(--cc-timepicker-border-radius, .4375rem);border:var(--cc-timepicker-border-width, 1px) solid var(--cc-timepicker-border-color, #BDC1C6);background-color:var(--cc-timepicker-bg, #FEFEFE);cursor:pointer;box-sizing:border-box;transition:border-color .2s ease,box-shadow .2s ease;-webkit-user-select:none;user-select:none}.cc-timepicker-field:hover:not(.disabled){border-color:var(--cc-timepicker-focus-border-color, #1A73E8)}.cc-timepicker-field.focused{border-color:var(--cc-timepicker-focus-border-color, #1A73E8);box-shadow:0 0 0 2px #1a73e833}.cc-timepicker-field.disabled{background-color:var(--cc-timepicker-disabled-bg, #F1F3F4);border-color:var(--cc-timepicker-border-color, #BDC1C6);cursor:not-allowed}.cc-timepicker-value{font-size:var(--cc-timepicker-font-size, .875rem);font-weight:var(--cc-timepicker-font-weight, 400);color:var(--cc-timepicker-color, #202124);font-variant-numeric:tabular-nums}.cc-timepicker-field.disabled .cc-timepicker-value{color:var(--cc-timepicker-disabled-color, #80868B)}.cc-timepicker-value.is-placeholder{color:var(--cc-timepicker-placeholder-color, #80868B)}.cc-timepicker-icon{font-size:20px;color:var(--cc-timepicker-icon-color, #5F6368);display:flex;align-items:center;justify-content:center}.cc-timepicker-field.disabled .cc-timepicker-icon{color:var(--cc-timepicker-disabled-color, #80868B)}.cc-timepicker-overlay{position:fixed;inset:0;background-color:#0006;display:flex;align-items:center;justify-content:center;z-index:9999}.cc-timepicker-modal-content{display:block}.cc-error-message{font-size:.75rem;color:var(--cc-timepicker-error-color, #D93025);margin-top:.25rem}\n"] }]
|
|
1291
|
+
}], propDecorators: { config: [{
|
|
1292
|
+
type: Input
|
|
1293
|
+
}], labels: [{
|
|
1294
|
+
type: Input
|
|
1295
|
+
}], label: [{
|
|
1296
|
+
type: Input
|
|
1297
|
+
}], placeholder: [{
|
|
1298
|
+
type: Input
|
|
1299
|
+
}], variant: [{
|
|
1300
|
+
type: Input
|
|
1301
|
+
}], mode: [{
|
|
1302
|
+
type: Input
|
|
1303
|
+
}], minuteStep: [{
|
|
1304
|
+
type: Input
|
|
1305
|
+
}], disabled: [{
|
|
1306
|
+
type: Input
|
|
1307
|
+
}], required: [{
|
|
1308
|
+
type: Input
|
|
1309
|
+
}], errorMessage: [{
|
|
1310
|
+
type: Input
|
|
1311
|
+
}], minTime: [{
|
|
1312
|
+
type: Input
|
|
1313
|
+
}], maxTime: [{
|
|
1314
|
+
type: Input
|
|
1315
|
+
}], width: [{
|
|
1316
|
+
type: Input
|
|
1317
|
+
}], height: [{
|
|
1318
|
+
type: Input
|
|
1319
|
+
}], borderRadius: [{
|
|
1320
|
+
type: Input
|
|
1321
|
+
}], fontSize: [{
|
|
1322
|
+
type: Input
|
|
1323
|
+
}], gap: [{
|
|
1324
|
+
type: Input
|
|
1325
|
+
}], fontFamily: [{
|
|
1326
|
+
type: Input
|
|
1327
|
+
}], labelColor: [{
|
|
1328
|
+
type: Input
|
|
1329
|
+
}], labelFontSize: [{
|
|
1330
|
+
type: Input
|
|
1331
|
+
}], labelFontWeight: [{
|
|
1332
|
+
type: Input
|
|
1333
|
+
}], backgroundColor: [{
|
|
1334
|
+
type: Input
|
|
1335
|
+
}], borderColor: [{
|
|
1336
|
+
type: Input
|
|
1337
|
+
}], borderWidth: [{
|
|
1338
|
+
type: Input
|
|
1339
|
+
}], padding: [{
|
|
1340
|
+
type: Input
|
|
1341
|
+
}], fontWeight: [{
|
|
1342
|
+
type: Input
|
|
1343
|
+
}], color: [{
|
|
1344
|
+
type: Input
|
|
1345
|
+
}], placeholderColor: [{
|
|
1346
|
+
type: Input
|
|
1347
|
+
}], focusBorderColor: [{
|
|
1348
|
+
type: Input
|
|
1349
|
+
}], errorColor: [{
|
|
1350
|
+
type: Input
|
|
1351
|
+
}], disabledBackgroundColor: [{
|
|
1352
|
+
type: Input
|
|
1353
|
+
}], disabledColor: [{
|
|
1354
|
+
type: Input
|
|
1355
|
+
}], timeChange: [{
|
|
1356
|
+
type: Output
|
|
1357
|
+
}] } });
|
|
1358
|
+
|
|
551
1359
|
/**
|
|
552
1360
|
* Bypasses Angular's DomSanitizer for resource URLs (e.g. YouTube embed iframes).
|
|
553
1361
|
* Used only for trusted URLs such as YouTube embed links derived from user-provided video IDs.
|
|
@@ -716,6 +1524,11 @@ class FormFieldComponent {
|
|
|
716
1524
|
_nextInstanceId = 0;
|
|
717
1525
|
/** Tracks open accordion panels for standard (non-multiSave) GROUP repeaters. */
|
|
718
1526
|
expandedGroupInstances = new Set();
|
|
1527
|
+
// ── LINK_LIST support ──────────────────────────────────────────────────
|
|
1528
|
+
newLinkInputValue = '';
|
|
1529
|
+
editingLinkIndex = null;
|
|
1530
|
+
editingLinkValue = '';
|
|
1531
|
+
linkValidationError = '';
|
|
719
1532
|
/**
|
|
720
1533
|
* Key used to register the GROUP control on the parent formGroup.
|
|
721
1534
|
* Priority: sectionConfig.name > field.name > camelCase(label) > '__group__'
|
|
@@ -2096,6 +2909,22 @@ class FormFieldComponent {
|
|
|
2096
2909
|
get isNumberField() { return this.config.type === 'NUMBER_INPUT'; }
|
|
2097
2910
|
get isDateField() { return this.config.type === 'DATE'; }
|
|
2098
2911
|
get isTimeField() { return this.config.type === 'TIME'; }
|
|
2912
|
+
get isWheelVariant() {
|
|
2913
|
+
return this.config.type === 'TIME' && this.config.timeConfig?.variant === 'wheel';
|
|
2914
|
+
}
|
|
2915
|
+
get timePickerLabels() {
|
|
2916
|
+
const labels = this.controller.labels || {};
|
|
2917
|
+
return {
|
|
2918
|
+
selectTimeTitle: labels['TIME_PICKER.SELECT_TIME'],
|
|
2919
|
+
hourLabel: labels['TIME_PICKER.HOUR'],
|
|
2920
|
+
minuteLabel: labels['TIME_PICKER.MINUTE'],
|
|
2921
|
+
periodLabel: labels['TIME_PICKER.PERIOD'],
|
|
2922
|
+
amLabel: labels['TIME_PICKER.AM'],
|
|
2923
|
+
pmLabel: labels['TIME_PICKER.PM'],
|
|
2924
|
+
confirmLabel: labels['COMMON.ACTIONS.CONFIRM'],
|
|
2925
|
+
cancelLabel: labels['COMMON.ACTIONS.CANCEL'],
|
|
2926
|
+
};
|
|
2927
|
+
}
|
|
2099
2928
|
get isDropdown() { return this.config.type === 'DROPDOWN'; }
|
|
2100
2929
|
get isAutocomplete() { return this.config.type === 'AUTOCOMPLETE'; }
|
|
2101
2930
|
get isFileUpload() { return this.config.type === 'FILE_UPLOAD'; }
|
|
@@ -2110,6 +2939,7 @@ class FormFieldComponent {
|
|
|
2110
2939
|
get isRow() { return this.config.type === 'ROW'; }
|
|
2111
2940
|
get isGroup() { return this.config.type === 'GROUP'; }
|
|
2112
2941
|
get isLocation() { return this.config.type === 'LOCATION'; }
|
|
2942
|
+
get isLinkList() { return this.config.type === 'LINK_LIST'; }
|
|
2113
2943
|
// ── AUTOCOMPLETE helpers ────────────────────────────────────────────────
|
|
2114
2944
|
/**
|
|
2115
2945
|
* Initialise the separate display-control that drives the mat-autocomplete
|
|
@@ -3112,12 +3942,201 @@ class FormFieldComponent {
|
|
|
3112
3942
|
this._googleMap.fitBounds(bounds);
|
|
3113
3943
|
}
|
|
3114
3944
|
}
|
|
3945
|
+
// ── LINK_LIST helpers ──────────────────────────────────────────────────
|
|
3946
|
+
get linkListConfig() {
|
|
3947
|
+
return this.config.linkListConfig;
|
|
3948
|
+
}
|
|
3949
|
+
get linkListItems() {
|
|
3950
|
+
const format = this.linkListConfig?.valueFormat || 'string';
|
|
3951
|
+
const val = this.value;
|
|
3952
|
+
if (!val)
|
|
3953
|
+
return [];
|
|
3954
|
+
if (format === 'string') {
|
|
3955
|
+
if (typeof val === 'string') {
|
|
3956
|
+
const sep = this.linkListConfig?.separator || ',';
|
|
3957
|
+
return val.split(sep).map(s => s.trim()).filter(Boolean);
|
|
3958
|
+
}
|
|
3959
|
+
return [];
|
|
3960
|
+
}
|
|
3961
|
+
else if (format === 'array') {
|
|
3962
|
+
return Array.isArray(val) ? val : [];
|
|
3963
|
+
}
|
|
3964
|
+
else if (format === 'object') {
|
|
3965
|
+
return Array.isArray(val) ? val : [];
|
|
3966
|
+
}
|
|
3967
|
+
return [];
|
|
3968
|
+
}
|
|
3969
|
+
getLinkDisplayUrl(item) {
|
|
3970
|
+
if (!item)
|
|
3971
|
+
return '';
|
|
3972
|
+
const format = this.linkListConfig?.valueFormat || 'string';
|
|
3973
|
+
if (format === 'object') {
|
|
3974
|
+
const urlKey = this.linkListConfig?.urlKey || 'linkUrl';
|
|
3975
|
+
return item[urlKey] || '';
|
|
3976
|
+
}
|
|
3977
|
+
return String(item);
|
|
3978
|
+
}
|
|
3979
|
+
addLinkItem(event) {
|
|
3980
|
+
if (event) {
|
|
3981
|
+
event.preventDefault();
|
|
3982
|
+
event.stopPropagation();
|
|
3983
|
+
}
|
|
3984
|
+
const val = this.newLinkInputValue?.trim();
|
|
3985
|
+
if (!val)
|
|
3986
|
+
return;
|
|
3987
|
+
// Normalize URL
|
|
3988
|
+
const formattedUrl = /^https?:\/\//i.test(val) ? val : 'https://' + val;
|
|
3989
|
+
// Validate URL pattern
|
|
3990
|
+
const patternStr = this.linkListConfig?.pattern || '^https?://([a-zA-Z0-9\\-]+\\.)+[a-zA-Z]{2,}(/\\S*)?$';
|
|
3991
|
+
try {
|
|
3992
|
+
const regex = new RegExp(patternStr, 'i');
|
|
3993
|
+
if (!regex.test(formattedUrl)) {
|
|
3994
|
+
this.linkValidationError = this.linkListConfig?.patternMessage || this.controller.labels['COMMON.INVALID_URL'] || 'Invalid URL format';
|
|
3995
|
+
return;
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
catch (e) {
|
|
3999
|
+
console.error('Invalid pattern regex in LinkListConfig', e);
|
|
4000
|
+
}
|
|
4001
|
+
this.linkValidationError = '';
|
|
4002
|
+
const format = this.linkListConfig?.valueFormat || 'string';
|
|
4003
|
+
const currentItems = [...this.linkListItems];
|
|
4004
|
+
if (format === 'string') {
|
|
4005
|
+
currentItems.push(formattedUrl);
|
|
4006
|
+
const sep = this.linkListConfig?.separator || ',';
|
|
4007
|
+
this.updateValue(currentItems.join(sep));
|
|
4008
|
+
}
|
|
4009
|
+
else if (format === 'array') {
|
|
4010
|
+
currentItems.push(formattedUrl);
|
|
4011
|
+
this.updateValue(currentItems);
|
|
4012
|
+
}
|
|
4013
|
+
else if (format === 'object') {
|
|
4014
|
+
const urlKey = this.linkListConfig?.urlKey || 'linkUrl';
|
|
4015
|
+
currentItems.push({ [urlKey]: formattedUrl, id: 0 });
|
|
4016
|
+
this.updateValue(currentItems);
|
|
4017
|
+
}
|
|
4018
|
+
this.newLinkInputValue = '';
|
|
4019
|
+
}
|
|
4020
|
+
deleteLinkItem(index) {
|
|
4021
|
+
const format = this.linkListConfig?.valueFormat || 'string';
|
|
4022
|
+
const currentItems = [...this.linkListItems];
|
|
4023
|
+
const itemToRemove = currentItems[index];
|
|
4024
|
+
const deleteLinkLocal = () => {
|
|
4025
|
+
currentItems.splice(index, 1);
|
|
4026
|
+
if (format === 'string') {
|
|
4027
|
+
const sep = this.linkListConfig?.separator || ',';
|
|
4028
|
+
this.updateValue(currentItems.length > 0 ? currentItems.join(sep) : null);
|
|
4029
|
+
}
|
|
4030
|
+
else if (format === 'array') {
|
|
4031
|
+
this.updateValue(currentItems.length > 0 ? currentItems : null);
|
|
4032
|
+
}
|
|
4033
|
+
else if (format === 'object') {
|
|
4034
|
+
this.updateValue(currentItems.length > 0 ? currentItems : null);
|
|
4035
|
+
}
|
|
4036
|
+
};
|
|
4037
|
+
const deleteApiUrl = this.linkListConfig?.deleteApiUrl;
|
|
4038
|
+
if (format === 'object' && itemToRemove && itemToRemove.id && deleteApiUrl) {
|
|
4039
|
+
let resolvedUrl = deleteApiUrl;
|
|
4040
|
+
const idField = this.linkListConfig?.idField || 'id';
|
|
4041
|
+
const itemId = itemToRemove[idField] || '';
|
|
4042
|
+
if (resolvedUrl.includes(':id')) {
|
|
4043
|
+
resolvedUrl = resolvedUrl.replace(':id', String(itemId));
|
|
4044
|
+
}
|
|
4045
|
+
else if (resolvedUrl.includes('{id}')) {
|
|
4046
|
+
resolvedUrl = resolvedUrl.replace('{id}', String(itemId));
|
|
4047
|
+
}
|
|
4048
|
+
this.http.delete(resolvedUrl, { headers: this.getHeaders() }).subscribe({
|
|
4049
|
+
next: () => {
|
|
4050
|
+
deleteLinkLocal();
|
|
4051
|
+
},
|
|
4052
|
+
error: (err) => {
|
|
4053
|
+
console.error('Failed to delete link via API', err);
|
|
4054
|
+
}
|
|
4055
|
+
});
|
|
4056
|
+
}
|
|
4057
|
+
else {
|
|
4058
|
+
deleteLinkLocal();
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
startEditLink(index) {
|
|
4062
|
+
this.editingLinkIndex = index;
|
|
4063
|
+
const item = this.linkListItems[index];
|
|
4064
|
+
this.editingLinkValue = this.getLinkDisplayUrl(item);
|
|
4065
|
+
}
|
|
4066
|
+
cancelEditLink() {
|
|
4067
|
+
this.editingLinkIndex = null;
|
|
4068
|
+
this.editingLinkValue = '';
|
|
4069
|
+
}
|
|
4070
|
+
saveEditLink(index) {
|
|
4071
|
+
const val = this.editingLinkValue?.trim();
|
|
4072
|
+
if (!val)
|
|
4073
|
+
return;
|
|
4074
|
+
const formattedUrl = /^https?:\/\//i.test(val) ? val : 'https://' + val;
|
|
4075
|
+
// Validate URL pattern
|
|
4076
|
+
const patternStr = this.linkListConfig?.pattern || '^https?://([a-zA-Z0-9\\-]+\\.)+[a-zA-Z]{2,}(/\\S*)?$';
|
|
4077
|
+
try {
|
|
4078
|
+
const regex = new RegExp(patternStr, 'i');
|
|
4079
|
+
if (!regex.test(formattedUrl)) {
|
|
4080
|
+
this.linkValidationError = this.linkListConfig?.patternMessage || this.controller.labels['COMMON.INVALID_URL'] || 'Invalid URL format';
|
|
4081
|
+
return;
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
catch (e) {
|
|
4085
|
+
console.error('Invalid pattern regex in LinkListConfig', e);
|
|
4086
|
+
}
|
|
4087
|
+
this.linkValidationError = '';
|
|
4088
|
+
const format = this.linkListConfig?.valueFormat || 'string';
|
|
4089
|
+
const currentItems = [...this.linkListItems];
|
|
4090
|
+
const itemToEdit = currentItems[index];
|
|
4091
|
+
const updateLinkLocal = () => {
|
|
4092
|
+
if (format === 'string') {
|
|
4093
|
+
currentItems[index] = formattedUrl;
|
|
4094
|
+
const sep = this.linkListConfig?.separator || ',';
|
|
4095
|
+
this.updateValue(currentItems.join(sep));
|
|
4096
|
+
}
|
|
4097
|
+
else if (format === 'array') {
|
|
4098
|
+
currentItems[index] = formattedUrl;
|
|
4099
|
+
this.updateValue(currentItems);
|
|
4100
|
+
}
|
|
4101
|
+
else if (format === 'object') {
|
|
4102
|
+
const urlKey = this.linkListConfig?.urlKey || 'linkUrl';
|
|
4103
|
+
currentItems[index] = { ...itemToEdit, [urlKey]: formattedUrl };
|
|
4104
|
+
this.updateValue(currentItems);
|
|
4105
|
+
}
|
|
4106
|
+
this.cancelEditLink();
|
|
4107
|
+
};
|
|
4108
|
+
const editApiUrl = this.linkListConfig?.editApiUrl;
|
|
4109
|
+
if (format === 'object' && itemToEdit && itemToEdit.id && editApiUrl) {
|
|
4110
|
+
let resolvedUrl = editApiUrl;
|
|
4111
|
+
const idField = this.linkListConfig?.idField || 'id';
|
|
4112
|
+
const itemId = itemToEdit[idField] || '';
|
|
4113
|
+
if (resolvedUrl.includes(':id')) {
|
|
4114
|
+
resolvedUrl = resolvedUrl.replace(':id', String(itemId));
|
|
4115
|
+
}
|
|
4116
|
+
else if (resolvedUrl.includes('{id}')) {
|
|
4117
|
+
resolvedUrl = resolvedUrl.replace('{id}', String(itemId));
|
|
4118
|
+
}
|
|
4119
|
+
const urlKey = this.linkListConfig?.urlKey || 'linkUrl';
|
|
4120
|
+
const body = { ...itemToEdit, [urlKey]: formattedUrl };
|
|
4121
|
+
this.http.put(resolvedUrl, body, { headers: this.getHeaders() }).subscribe({
|
|
4122
|
+
next: () => {
|
|
4123
|
+
updateLinkLocal();
|
|
4124
|
+
},
|
|
4125
|
+
error: (err) => {
|
|
4126
|
+
console.error('Failed to edit link via API', err);
|
|
4127
|
+
}
|
|
4128
|
+
});
|
|
4129
|
+
}
|
|
4130
|
+
else {
|
|
4131
|
+
updateLinkLocal();
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
3115
4134
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FormFieldComponent, deps: [{ token: i1$2.FormBuilder }, { token: ExpressionService }, { token: i3.HttpClient }], target: i0.ɵɵFactoryTarget.Component });
|
|
3116
|
-
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()", "document:keydown.escape": "onEscapeKey()" } }, 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\">\r\n <div class=\"row-field\" [style.gridColumn]=\"'span ' + getChildColSpan(child)\" *ngIf=\"child.isEnabled !== false\">\r\n <lib-form-field [config]=\"child\" [controller]=\"controller\" [formGroup]=\"formGroup\" [allowMulti]=\"allowMulti\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- \u2550\u2550 GROUP \u2014 allowMulti (repeater) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isGroup && config.sectionConfig?.allowMulti\"\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 <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"instance.rowController\" [formGroup]=\"instance.fg\"\r\n [allowMulti]=\"false\" [siblingFormArray]=\"groupFormArray\" [siblingIndex]=\"i\">\r\n </lib-form-field>\r\n </div>\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 <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"instance.rowController\" [formGroup]=\"instance.fg\"\r\n [allowMulti]=\"false\" [siblingFormArray]=\"groupFormArray\" [siblingIndex]=\"i\">\r\n </lib-form-field>\r\n </div>\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 <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\" *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"groupFormGroup\" [allowMulti]=\"false\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n\r\n <!-- \u2550\u2550 Text Input \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isTextField\" class=\"field-wrapper 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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">\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></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 <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px mb-0-5rem\">\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\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 mb-0-5rem\">\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 mb-0-5rem\">\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) -->\r\n <select *ngIf=\"config.subType === 'SINGLE' && !isSearchableDropdown\" class=\"field-input\" [formControlName]=\"config.name!\"\r\n [class.is-invalid]=\"errorMessage\">\r\n <option [ngValue]=\"null\" disabled selected>{{ 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\r\n <!-- SINGLE SELECT: custom searchable dropdown -->\r\n <div *ngIf=\"config.subType === 'SINGLE' && isSearchableDropdown\" class=\"single-select-wrapper\"\r\n [class.is-invalid]=\"errorMessage\">\r\n\r\n <div 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)=\"toggleSingleDropdown($event)\">\r\n <span *ngIf=\"singleSelectedLabel\" class=\"single-select-value fs-0-9rem\">\r\n {{ singleSelectedLabel }}\r\n </span>\r\n <span *ngIf=\"!singleSelectedLabel\" class=\"single-select-placeholder\">\r\n {{ config.placeholder || selectPlaceholderLabel }}\r\n </span>\r\n <mat-icon class=\"single-select-arrow\">\r\n {{ isSingleDropdownOpen ? expandLessLabel : expandMoreLabel }}\r\n </mat-icon>\r\n </div>\r\n\r\n <div class=\"single-select-panel\" *ngIf=\"isSingleDropdownOpen\"\r\n (click)=\"$event.stopPropagation()\">\r\n\r\n <!-- Search input -->\r\n <div class=\"dropdown-search-wrapper\">\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\">\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]=\"formGroup.get(config.name!)?.value === option.code\"\r\n (click)=\"selectSingleOption(option)\">\r\n <span class=\"fs-0-875rem c-111827\">{{ option.label }}</span>\r\n <mat-icon *ngIf=\"formGroup.get(config.name!)?.value === 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 </div>\r\n </div>\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 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)=\"toggleMultiDropdown($event)\">\r\n <span *ngIf=\"multiSelectedCount > 0\" class=\"multi-select-value fs-0-9rem\">\r\n {{ multiSelectedCount }} selected\r\n </span>\r\n <span *ngIf=\"multiSelectedCount === 0\" class=\"multi-select-placeholder\">\r\n {{ config.placeholder || selectPlaceholderLabel }}\r\n </span>\r\n <mat-icon class=\"multi-select-arrow\">\r\n {{ isMultiDropdownOpen ? expandLessLabel : expandMoreLabel }}\r\n </mat-icon>\r\n </div>\r\n\r\n <div class=\"multi-select-panel\" *ngIf=\"isMultiDropdownOpen\"\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\">\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 </div>\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 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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">{{ 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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">{{ 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 mb-0-5rem\">\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 mb-0-5rem\">\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</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}: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;line-height:1;letter-spacing:.04em;text-transform:uppercase;color:#202124!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)}.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:absolute;top:calc(100% + 4px);left:0;right:0;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:absolute;top:calc(100% + 4px);left:0;right:0;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}.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}\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.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$2.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i5.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i7.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i7.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i7.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "directive", type: i8.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "directive", type: i9.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "component", type: ButtonComponent, selector: "lib-button", inputs: ["variant", "type", "disabled", "width", "height", "borderRadius", "fontSize", "fontWeight", "backgroundColor", "color", "border", "icon", "labels"] }, { kind: "component", type: i11.QuillEditorComponent, selector: "quill-editor" }, { kind: "component", type: FormFieldComponent, selector: "lib-form-field", inputs: ["config", "controller", "formGroup", "allowMulti", "siblingFormArray", "siblingIndex"] }, { kind: "pipe", type: TrustedUrlPipe, name: "trustedUrl" }] });
|
|
4135
|
+
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()", "document:keydown.escape": "onEscapeKey()" } }, 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\">\r\n <div class=\"row-field\" [style.gridColumn]=\"'span ' + getChildColSpan(child)\" *ngIf=\"child.isEnabled !== false\">\r\n <lib-form-field [config]=\"child\" [controller]=\"controller\" [formGroup]=\"formGroup\" [allowMulti]=\"allowMulti\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- \u2550\u2550 GROUP \u2014 allowMulti (repeater) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isGroup && config.sectionConfig?.allowMulti\"\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 <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"instance.rowController\" [formGroup]=\"instance.fg\"\r\n [allowMulti]=\"false\" [siblingFormArray]=\"groupFormArray\" [siblingIndex]=\"i\">\r\n </lib-form-field>\r\n </div>\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 <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"instance.rowController\" [formGroup]=\"instance.fg\"\r\n [allowMulti]=\"false\" [siblingFormArray]=\"groupFormArray\" [siblingIndex]=\"i\">\r\n </lib-form-field>\r\n </div>\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 <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\" *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"groupFormGroup\" [allowMulti]=\"false\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n\r\n <!-- \u2550\u2550 Text Input \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isTextField\" class=\"field-wrapper 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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">\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></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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">\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) -->\r\n <select *ngIf=\"config.subType === 'SINGLE' && !isSearchableDropdown\" class=\"field-input\" [formControlName]=\"config.name!\"\r\n [class.is-invalid]=\"errorMessage\">\r\n <option [ngValue]=\"null\" disabled selected>{{ 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\r\n <!-- SINGLE SELECT: custom searchable dropdown -->\r\n <div *ngIf=\"config.subType === 'SINGLE' && isSearchableDropdown\" class=\"single-select-wrapper\"\r\n [class.is-invalid]=\"errorMessage\">\r\n\r\n <div 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)=\"toggleSingleDropdown($event)\">\r\n <span *ngIf=\"singleSelectedLabel\" class=\"single-select-value fs-0-9rem\">\r\n {{ singleSelectedLabel }}\r\n </span>\r\n <span *ngIf=\"!singleSelectedLabel\" class=\"single-select-placeholder\">\r\n {{ config.placeholder || selectPlaceholderLabel }}\r\n </span>\r\n <mat-icon class=\"single-select-arrow\">\r\n {{ isSingleDropdownOpen ? expandLessLabel : expandMoreLabel }}\r\n </mat-icon>\r\n </div>\r\n\r\n <div class=\"single-select-panel\" *ngIf=\"isSingleDropdownOpen\"\r\n (click)=\"$event.stopPropagation()\">\r\n\r\n <!-- Search input -->\r\n <div class=\"dropdown-search-wrapper\">\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\">\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]=\"formGroup.get(config.name!)?.value === option.code\"\r\n (click)=\"selectSingleOption(option)\">\r\n <span class=\"fs-0-875rem c-111827\">{{ option.label }}</span>\r\n <mat-icon *ngIf=\"formGroup.get(config.name!)?.value === 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 </div>\r\n </div>\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 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)=\"toggleMultiDropdown($event)\">\r\n <span *ngIf=\"multiSelectedCount > 0\" class=\"multi-select-value fs-0-9rem\">\r\n {{ multiSelectedCount }} selected\r\n </span>\r\n <span *ngIf=\"multiSelectedCount === 0\" class=\"multi-select-placeholder\">\r\n {{ config.placeholder || selectPlaceholderLabel }}\r\n </span>\r\n <mat-icon class=\"multi-select-arrow\">\r\n {{ isMultiDropdownOpen ? expandLessLabel : expandMoreLabel }}\r\n </mat-icon>\r\n </div>\r\n\r\n <div class=\"multi-select-panel\" *ngIf=\"isMultiDropdownOpen\"\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\">\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 </div>\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 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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">{{ 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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">{{ 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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">\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}: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;line-height:1;letter-spacing:.04em;text-transform:uppercase;color:#202124!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)}.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:absolute;top:calc(100% + 4px);left:0;right:0;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:absolute;top:calc(100% + 4px);left:0;right:0;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}.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}\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: i5.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i7.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i7.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i7.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "directive", type: i8.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "directive", type: i9.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "component", type: ButtonComponent, selector: "lib-button", inputs: ["variant", "type", "disabled", "width", "height", "borderRadius", "fontSize", "fontWeight", "backgroundColor", "color", "border", "icon", "labels"] }, { kind: "component", type: i11.QuillEditorComponent, selector: "quill-editor" }, { kind: "component", type: 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" }] });
|
|
3117
4136
|
}
|
|
3118
4137
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FormFieldComponent, decorators: [{
|
|
3119
4138
|
type: Component,
|
|
3120
|
-
args: [{ selector: 'lib-form-field', standalone: false, 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\">\r\n <div class=\"row-field\" [style.gridColumn]=\"'span ' + getChildColSpan(child)\" *ngIf=\"child.isEnabled !== false\">\r\n <lib-form-field [config]=\"child\" [controller]=\"controller\" [formGroup]=\"formGroup\" [allowMulti]=\"allowMulti\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- \u2550\u2550 GROUP \u2014 allowMulti (repeater) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isGroup && config.sectionConfig?.allowMulti\"\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 <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"instance.rowController\" [formGroup]=\"instance.fg\"\r\n [allowMulti]=\"false\" [siblingFormArray]=\"groupFormArray\" [siblingIndex]=\"i\">\r\n </lib-form-field>\r\n </div>\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 <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"instance.rowController\" [formGroup]=\"instance.fg\"\r\n [allowMulti]=\"false\" [siblingFormArray]=\"groupFormArray\" [siblingIndex]=\"i\">\r\n </lib-form-field>\r\n </div>\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 <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\" *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"groupFormGroup\" [allowMulti]=\"false\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n\r\n <!-- \u2550\u2550 Text Input \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isTextField\" class=\"field-wrapper 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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">\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></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 <label *ngIf=\"config.label\" class=\"field-label d-block font-poppins c-202124 fs-18px mb-0-5rem\">\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\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 mb-0-5rem\">\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 mb-0-5rem\">\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) -->\r\n <select *ngIf=\"config.subType === 'SINGLE' && !isSearchableDropdown\" class=\"field-input\" [formControlName]=\"config.name!\"\r\n [class.is-invalid]=\"errorMessage\">\r\n <option [ngValue]=\"null\" disabled selected>{{ 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\r\n <!-- SINGLE SELECT: custom searchable dropdown -->\r\n <div *ngIf=\"config.subType === 'SINGLE' && isSearchableDropdown\" class=\"single-select-wrapper\"\r\n [class.is-invalid]=\"errorMessage\">\r\n\r\n <div 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)=\"toggleSingleDropdown($event)\">\r\n <span *ngIf=\"singleSelectedLabel\" class=\"single-select-value fs-0-9rem\">\r\n {{ singleSelectedLabel }}\r\n </span>\r\n <span *ngIf=\"!singleSelectedLabel\" class=\"single-select-placeholder\">\r\n {{ config.placeholder || selectPlaceholderLabel }}\r\n </span>\r\n <mat-icon class=\"single-select-arrow\">\r\n {{ isSingleDropdownOpen ? expandLessLabel : expandMoreLabel }}\r\n </mat-icon>\r\n </div>\r\n\r\n <div class=\"single-select-panel\" *ngIf=\"isSingleDropdownOpen\"\r\n (click)=\"$event.stopPropagation()\">\r\n\r\n <!-- Search input -->\r\n <div class=\"dropdown-search-wrapper\">\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\">\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]=\"formGroup.get(config.name!)?.value === option.code\"\r\n (click)=\"selectSingleOption(option)\">\r\n <span class=\"fs-0-875rem c-111827\">{{ option.label }}</span>\r\n <mat-icon *ngIf=\"formGroup.get(config.name!)?.value === 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 </div>\r\n </div>\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 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)=\"toggleMultiDropdown($event)\">\r\n <span *ngIf=\"multiSelectedCount > 0\" class=\"multi-select-value fs-0-9rem\">\r\n {{ multiSelectedCount }} selected\r\n </span>\r\n <span *ngIf=\"multiSelectedCount === 0\" class=\"multi-select-placeholder\">\r\n {{ config.placeholder || selectPlaceholderLabel }}\r\n </span>\r\n <mat-icon class=\"multi-select-arrow\">\r\n {{ isMultiDropdownOpen ? expandLessLabel : expandMoreLabel }}\r\n </mat-icon>\r\n </div>\r\n\r\n <div class=\"multi-select-panel\" *ngIf=\"isMultiDropdownOpen\"\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\">\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 </div>\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 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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">{{ 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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">{{ 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 mb-0-5rem\">\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 mb-0-5rem\">\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</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}: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;line-height:1;letter-spacing:.04em;text-transform:uppercase;color:#202124!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)}.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:absolute;top:calc(100% + 4px);left:0;right:0;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:absolute;top:calc(100% + 4px);left:0;right:0;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}.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}\n"] }]
|
|
4139
|
+
args: [{ selector: 'lib-form-field', standalone: false, 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\">\r\n <div class=\"row-field\" [style.gridColumn]=\"'span ' + getChildColSpan(child)\" *ngIf=\"child.isEnabled !== false\">\r\n <lib-form-field [config]=\"child\" [controller]=\"controller\" [formGroup]=\"formGroup\" [allowMulti]=\"allowMulti\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- \u2550\u2550 GROUP \u2014 allowMulti (repeater) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isGroup && config.sectionConfig?.allowMulti\"\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 <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"instance.rowController\" [formGroup]=\"instance.fg\"\r\n [allowMulti]=\"false\" [siblingFormArray]=\"groupFormArray\" [siblingIndex]=\"i\">\r\n </lib-form-field>\r\n </div>\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 <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"instance.rowController\" [formGroup]=\"instance.fg\"\r\n [allowMulti]=\"false\" [siblingFormArray]=\"groupFormArray\" [siblingIndex]=\"i\">\r\n </lib-form-field>\r\n </div>\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 <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\" *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"groupFormGroup\" [allowMulti]=\"false\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n\r\n <!-- \u2550\u2550 Text Input \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div *ngIf=\"isTextField\" class=\"field-wrapper 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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">\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></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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">\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) -->\r\n <select *ngIf=\"config.subType === 'SINGLE' && !isSearchableDropdown\" class=\"field-input\" [formControlName]=\"config.name!\"\r\n [class.is-invalid]=\"errorMessage\">\r\n <option [ngValue]=\"null\" disabled selected>{{ 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\r\n <!-- SINGLE SELECT: custom searchable dropdown -->\r\n <div *ngIf=\"config.subType === 'SINGLE' && isSearchableDropdown\" class=\"single-select-wrapper\"\r\n [class.is-invalid]=\"errorMessage\">\r\n\r\n <div 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)=\"toggleSingleDropdown($event)\">\r\n <span *ngIf=\"singleSelectedLabel\" class=\"single-select-value fs-0-9rem\">\r\n {{ singleSelectedLabel }}\r\n </span>\r\n <span *ngIf=\"!singleSelectedLabel\" class=\"single-select-placeholder\">\r\n {{ config.placeholder || selectPlaceholderLabel }}\r\n </span>\r\n <mat-icon class=\"single-select-arrow\">\r\n {{ isSingleDropdownOpen ? expandLessLabel : expandMoreLabel }}\r\n </mat-icon>\r\n </div>\r\n\r\n <div class=\"single-select-panel\" *ngIf=\"isSingleDropdownOpen\"\r\n (click)=\"$event.stopPropagation()\">\r\n\r\n <!-- Search input -->\r\n <div class=\"dropdown-search-wrapper\">\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\">\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]=\"formGroup.get(config.name!)?.value === option.code\"\r\n (click)=\"selectSingleOption(option)\">\r\n <span class=\"fs-0-875rem c-111827\">{{ option.label }}</span>\r\n <mat-icon *ngIf=\"formGroup.get(config.name!)?.value === 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 </div>\r\n </div>\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 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)=\"toggleMultiDropdown($event)\">\r\n <span *ngIf=\"multiSelectedCount > 0\" class=\"multi-select-value fs-0-9rem\">\r\n {{ multiSelectedCount }} selected\r\n </span>\r\n <span *ngIf=\"multiSelectedCount === 0\" class=\"multi-select-placeholder\">\r\n {{ config.placeholder || selectPlaceholderLabel }}\r\n </span>\r\n <mat-icon class=\"multi-select-arrow\">\r\n {{ isMultiDropdownOpen ? expandLessLabel : expandMoreLabel }}\r\n </mat-icon>\r\n </div>\r\n\r\n <div class=\"multi-select-panel\" *ngIf=\"isMultiDropdownOpen\"\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\">\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 </div>\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 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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">{{ 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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">{{ 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 mb-0-5rem\">\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 mb-0-5rem\">\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 mb-0-5rem\">\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}: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;line-height:1;letter-spacing:.04em;text-transform:uppercase;color:#202124!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)}.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:absolute;top:calc(100% + 4px);left:0;right:0;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:absolute;top:calc(100% + 4px);left:0;right:0;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}.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}\n"] }]
|
|
3121
4140
|
}], ctorParameters: () => [{ type: i1$2.FormBuilder }, { type: ExpressionService }, { type: i3.HttpClient }], propDecorators: { config: [{
|
|
3122
4141
|
type: Input
|
|
3123
4142
|
}], controller: [{
|
|
@@ -3305,11 +4324,11 @@ class FormSectionComponent {
|
|
|
3305
4324
|
return result;
|
|
3306
4325
|
}
|
|
3307
4326
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FormSectionComponent, deps: [{ token: i1$2.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
|
|
3308
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: FormSectionComponent, isStandalone: false, selector: "lib-form-section", inputs: { config: "config", controller: "controller", formGroup: "formGroup" }, ngImport: i0, template: "<div class=\"form-section-container\">\r\n <h3 class=\"section-label\" *ngIf=\"config.label && !config.allowMulti\">{{ config.label }}</h3>\r\n\r\n <!-- \u2550\u2550 Repeater (allowMulti) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <ng-container *ngIf=\"config.allowMulti\">\r\n <h3 class=\"section-label\">{{ config.label }}</h3>\r\n\r\n <div *ngFor=\"let instanceGroup of instanceGroups; let i = index\"\r\n class=\"accordion-instance\"\r\n [class.is-expanded]=\"isExpanded(i)\">\r\n\r\n <!-- Accordion header (always visible) -->\r\n <div class=\"accordion-header\" (click)=\"toggleInstance(i)\" role=\"button\" [attr.aria-expanded]=\"isExpanded(i)\">\r\n <div class=\"accordion-header-left\">\r\n <span class=\"instance-badge\">{{ i + 1 }}</span>\r\n <span class=\"instance-title\">{{ config.label }} #{{ i + 1 }}</span>\r\n </div>\r\n <div class=\"accordion-header-right\">\r\n <button type=\"button\" class=\"accordion-remove-btn\"\r\n *ngIf=\"instanceGroups.length > 1\"\r\n (click)=\"$event.stopPropagation(); removeInstance(i)\"\r\n aria-label=\"Remove instance\">\r\n <mat-icon>delete_outline</mat-icon>\r\n </button>\r\n <mat-icon class=\"accordion-chevron\">\r\n {{ isExpanded(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=\"accordion-body\" [hidden]=\"!isExpanded(i)\">\r\n <div class=\"section-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.children\">\r\n <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"instanceGroup\"\r\n [siblingFormArray]=\"repeaterFormArray\" [siblingIndex]=\"i\">\r\n </lib-form-field>\r\n </div>\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-section\" (click)=\"addInstance()\">\r\n <mat-icon>add</mat-icon> Add {{ config.label }}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- \u2550\u2550 Non-repeater (single instance) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <ng-container *ngIf=\"!config.allowMulti\">\r\n <div class=\"section-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.children\">\r\n <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"flatFormGroup\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </ng-container>\r\n\r\n</div>\r\n", styles: ["@keyframes section-card-in{0%{opacity:0;
|
|
4327
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: FormSectionComponent, isStandalone: false, selector: "lib-form-section", inputs: { config: "config", controller: "controller", formGroup: "formGroup" }, ngImport: i0, template: "<div class=\"form-section-container\">\r\n <h3 class=\"section-label\" *ngIf=\"config.label && !config.allowMulti\">{{ config.label }}</h3>\r\n\r\n <!-- \u2550\u2550 Repeater (allowMulti) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <ng-container *ngIf=\"config.allowMulti\">\r\n <h3 class=\"section-label\">{{ config.label }}</h3>\r\n\r\n <div *ngFor=\"let instanceGroup of instanceGroups; let i = index\"\r\n class=\"accordion-instance\"\r\n [class.is-expanded]=\"isExpanded(i)\">\r\n\r\n <!-- Accordion header (always visible) -->\r\n <div class=\"accordion-header\" (click)=\"toggleInstance(i)\" role=\"button\" [attr.aria-expanded]=\"isExpanded(i)\">\r\n <div class=\"accordion-header-left\">\r\n <span class=\"instance-badge\">{{ i + 1 }}</span>\r\n <span class=\"instance-title\">{{ config.label }} #{{ i + 1 }}</span>\r\n </div>\r\n <div class=\"accordion-header-right\">\r\n <button type=\"button\" class=\"accordion-remove-btn\"\r\n *ngIf=\"instanceGroups.length > 1\"\r\n (click)=\"$event.stopPropagation(); removeInstance(i)\"\r\n aria-label=\"Remove instance\">\r\n <mat-icon>delete_outline</mat-icon>\r\n </button>\r\n <mat-icon class=\"accordion-chevron\">\r\n {{ isExpanded(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=\"accordion-body\" [hidden]=\"!isExpanded(i)\">\r\n <div class=\"section-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.children\">\r\n <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"instanceGroup\"\r\n [siblingFormArray]=\"repeaterFormArray\" [siblingIndex]=\"i\">\r\n </lib-form-field>\r\n </div>\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-section\" (click)=\"addInstance()\">\r\n <mat-icon>add</mat-icon> Add {{ config.label }}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- \u2550\u2550 Non-repeater (single instance) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <ng-container *ngIf=\"!config.allowMulti\">\r\n <div class=\"section-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.children\">\r\n <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"flatFormGroup\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </ng-container>\r\n\r\n</div>\r\n", styles: ["@keyframes section-card-in{0%{opacity:0;margin-top:10px}to{opacity:1;margin-top:0}}.form-section-container{background:#fff;border:1px solid #EAECF0;border-radius:14px;padding:22px 24px 24px;margin-bottom:14px;box-shadow:0 1px 2px #0000000a,0 4px 12px #6366f10a;animation:section-card-in .3s cubic-bezier(.4,0,.2,1) both;transition:box-shadow .2s ease}.form-section-container:hover{box-shadow:0 1px 2px #0000000a,0 6px 20px #6366f114}.form-section-container:last-child{margin-bottom:0}.form-section-container .section-label{display:flex;align-items:center;gap:10px;font-size:.9375rem;font-weight:700;color:#111827;margin:0 0 20px;padding-bottom:14px;border-bottom:1px solid #F3F4F6;letter-spacing:-.01em}.form-section-container .section-label:before{content:\"\";display:inline-block;width:4px;height:18px;border-radius:4px;background:linear-gradient(180deg,#6366f1,#8b5cf6);flex-shrink:0}.form-section-container .section-fields.sf-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:18px 20px;align-items:start}@media(max-width:640px){.form-section-container .section-fields.sf-grid{grid-template-columns:1fr}.form-section-container .section-fields.sf-grid .sf-col{grid-column:span 12!important}}.form-section-container .accordion-instance{border:1px solid #E8EAF0;border-radius:10px;margin-bottom:10px;overflow:hidden;transition:border-color .2s ease,box-shadow .2s ease;background:#fafbff}.form-section-container .accordion-instance:last-of-type{margin-bottom:0}.form-section-container .accordion-instance.is-expanded{border-color:#c7d2fe;box-shadow:0 0 0 3px #6366f112}.form-section-container .accordion-instance .accordion-header{display:flex;justify-content:space-between;align-items:center;padding:11px 16px;background:#f8f9ff;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .15s ease}.form-section-container .accordion-instance .accordion-header:hover{background:#eef0ff}.form-section-container .accordion-instance .accordion-header .accordion-header-left{display:flex;align-items:center;gap:10px}.form-section-container .accordion-instance .accordion-header .instance-badge{width:24px;height:24px;border-radius:6px;background:linear-gradient(135deg,#6366f1,#8b5cf6);color:#fff;font-size:.75rem;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0}.form-section-container .accordion-instance .accordion-header .instance-title{font-size:.8125rem;font-weight:600;color:#1f2937}.form-section-container .accordion-instance .accordion-header .accordion-header-right{display:flex;align-items:center;gap:6px}.form-section-container .accordion-instance .accordion-header .accordion-remove-btn{background:none;border:none;cursor:pointer;color:#ef4444;padding:4px 6px;border-radius:6px;line-height:1;display:flex;align-items:center;transition:background .15s ease,color .15s ease}.form-section-container .accordion-instance .accordion-header .accordion-remove-btn mat-icon{font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem}.form-section-container .accordion-instance .accordion-header .accordion-remove-btn:hover{background:#fee2e2;color:#dc2626}.form-section-container .accordion-instance .accordion-header .accordion-chevron{font-size:1.25rem;width:1.25rem;height:1.25rem;line-height:1.25rem;color:#6b7280;transition:transform .2s ease}.form-section-container .accordion-instance .accordion-body{padding:18px 16px;background:#fff;border-top:1px solid #E8EAF0}.form-section-container .btn-add-section{display:flex;align-items:center;justify-content:center;gap:7px;width:100%;padding:11px 20px;margin-top:14px;background:transparent;color:#6366f1;border:1.5px dashed #C7D2FE;border-radius:8px;cursor:pointer;font-family:inherit;font-size:.8125rem;font-weight:600;transition:all .2s ease;letter-spacing:.01em}.form-section-container .btn-add-section mat-icon{font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem}.form-section-container .btn-add-section:hover{background:#eef2ff;border-color:#a5b4fc;border-style:solid;transform:translateY(-1px);box-shadow:0 2px 8px #6366f11f}.form-section-container .btn-add-section:active{transform:translateY(0)}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: FormFieldComponent, selector: "lib-form-field", inputs: ["config", "controller", "formGroup", "allowMulti", "siblingFormArray", "siblingIndex"] }] });
|
|
3309
4328
|
}
|
|
3310
4329
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FormSectionComponent, decorators: [{
|
|
3311
4330
|
type: Component,
|
|
3312
|
-
args: [{ selector: 'lib-form-section', standalone: false, template: "<div class=\"form-section-container\">\r\n <h3 class=\"section-label\" *ngIf=\"config.label && !config.allowMulti\">{{ config.label }}</h3>\r\n\r\n <!-- \u2550\u2550 Repeater (allowMulti) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <ng-container *ngIf=\"config.allowMulti\">\r\n <h3 class=\"section-label\">{{ config.label }}</h3>\r\n\r\n <div *ngFor=\"let instanceGroup of instanceGroups; let i = index\"\r\n class=\"accordion-instance\"\r\n [class.is-expanded]=\"isExpanded(i)\">\r\n\r\n <!-- Accordion header (always visible) -->\r\n <div class=\"accordion-header\" (click)=\"toggleInstance(i)\" role=\"button\" [attr.aria-expanded]=\"isExpanded(i)\">\r\n <div class=\"accordion-header-left\">\r\n <span class=\"instance-badge\">{{ i + 1 }}</span>\r\n <span class=\"instance-title\">{{ config.label }} #{{ i + 1 }}</span>\r\n </div>\r\n <div class=\"accordion-header-right\">\r\n <button type=\"button\" class=\"accordion-remove-btn\"\r\n *ngIf=\"instanceGroups.length > 1\"\r\n (click)=\"$event.stopPropagation(); removeInstance(i)\"\r\n aria-label=\"Remove instance\">\r\n <mat-icon>delete_outline</mat-icon>\r\n </button>\r\n <mat-icon class=\"accordion-chevron\">\r\n {{ isExpanded(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=\"accordion-body\" [hidden]=\"!isExpanded(i)\">\r\n <div class=\"section-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.children\">\r\n <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"instanceGroup\"\r\n [siblingFormArray]=\"repeaterFormArray\" [siblingIndex]=\"i\">\r\n </lib-form-field>\r\n </div>\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-section\" (click)=\"addInstance()\">\r\n <mat-icon>add</mat-icon> Add {{ config.label }}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- \u2550\u2550 Non-repeater (single instance) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <ng-container *ngIf=\"!config.allowMulti\">\r\n <div class=\"section-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.children\">\r\n <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"flatFormGroup\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </ng-container>\r\n\r\n</div>\r\n", styles: ["@keyframes section-card-in{0%{opacity:0;
|
|
4331
|
+
args: [{ selector: 'lib-form-section', standalone: false, template: "<div class=\"form-section-container\">\r\n <h3 class=\"section-label\" *ngIf=\"config.label && !config.allowMulti\">{{ config.label }}</h3>\r\n\r\n <!-- \u2550\u2550 Repeater (allowMulti) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <ng-container *ngIf=\"config.allowMulti\">\r\n <h3 class=\"section-label\">{{ config.label }}</h3>\r\n\r\n <div *ngFor=\"let instanceGroup of instanceGroups; let i = index\"\r\n class=\"accordion-instance\"\r\n [class.is-expanded]=\"isExpanded(i)\">\r\n\r\n <!-- Accordion header (always visible) -->\r\n <div class=\"accordion-header\" (click)=\"toggleInstance(i)\" role=\"button\" [attr.aria-expanded]=\"isExpanded(i)\">\r\n <div class=\"accordion-header-left\">\r\n <span class=\"instance-badge\">{{ i + 1 }}</span>\r\n <span class=\"instance-title\">{{ config.label }} #{{ i + 1 }}</span>\r\n </div>\r\n <div class=\"accordion-header-right\">\r\n <button type=\"button\" class=\"accordion-remove-btn\"\r\n *ngIf=\"instanceGroups.length > 1\"\r\n (click)=\"$event.stopPropagation(); removeInstance(i)\"\r\n aria-label=\"Remove instance\">\r\n <mat-icon>delete_outline</mat-icon>\r\n </button>\r\n <mat-icon class=\"accordion-chevron\">\r\n {{ isExpanded(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=\"accordion-body\" [hidden]=\"!isExpanded(i)\">\r\n <div class=\"section-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.children\">\r\n <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"instanceGroup\"\r\n [siblingFormArray]=\"repeaterFormArray\" [siblingIndex]=\"i\">\r\n </lib-form-field>\r\n </div>\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-section\" (click)=\"addInstance()\">\r\n <mat-icon>add</mat-icon> Add {{ config.label }}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- \u2550\u2550 Non-repeater (single instance) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <ng-container *ngIf=\"!config.allowMulti\">\r\n <div class=\"section-fields sf-grid\">\r\n <ng-container *ngFor=\"let field of config.children\">\r\n <div class=\"sf-col\" [style.gridColumn]=\"'span ' + (field.colSpan || 12)\"\r\n *ngIf=\"field.isEnabled !== false\">\r\n <lib-form-field [config]=\"field\" [controller]=\"controller\" [formGroup]=\"flatFormGroup\">\r\n </lib-form-field>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </ng-container>\r\n\r\n</div>\r\n", styles: ["@keyframes section-card-in{0%{opacity:0;margin-top:10px}to{opacity:1;margin-top:0}}.form-section-container{background:#fff;border:1px solid #EAECF0;border-radius:14px;padding:22px 24px 24px;margin-bottom:14px;box-shadow:0 1px 2px #0000000a,0 4px 12px #6366f10a;animation:section-card-in .3s cubic-bezier(.4,0,.2,1) both;transition:box-shadow .2s ease}.form-section-container:hover{box-shadow:0 1px 2px #0000000a,0 6px 20px #6366f114}.form-section-container:last-child{margin-bottom:0}.form-section-container .section-label{display:flex;align-items:center;gap:10px;font-size:.9375rem;font-weight:700;color:#111827;margin:0 0 20px;padding-bottom:14px;border-bottom:1px solid #F3F4F6;letter-spacing:-.01em}.form-section-container .section-label:before{content:\"\";display:inline-block;width:4px;height:18px;border-radius:4px;background:linear-gradient(180deg,#6366f1,#8b5cf6);flex-shrink:0}.form-section-container .section-fields.sf-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:18px 20px;align-items:start}@media(max-width:640px){.form-section-container .section-fields.sf-grid{grid-template-columns:1fr}.form-section-container .section-fields.sf-grid .sf-col{grid-column:span 12!important}}.form-section-container .accordion-instance{border:1px solid #E8EAF0;border-radius:10px;margin-bottom:10px;overflow:hidden;transition:border-color .2s ease,box-shadow .2s ease;background:#fafbff}.form-section-container .accordion-instance:last-of-type{margin-bottom:0}.form-section-container .accordion-instance.is-expanded{border-color:#c7d2fe;box-shadow:0 0 0 3px #6366f112}.form-section-container .accordion-instance .accordion-header{display:flex;justify-content:space-between;align-items:center;padding:11px 16px;background:#f8f9ff;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .15s ease}.form-section-container .accordion-instance .accordion-header:hover{background:#eef0ff}.form-section-container .accordion-instance .accordion-header .accordion-header-left{display:flex;align-items:center;gap:10px}.form-section-container .accordion-instance .accordion-header .instance-badge{width:24px;height:24px;border-radius:6px;background:linear-gradient(135deg,#6366f1,#8b5cf6);color:#fff;font-size:.75rem;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0}.form-section-container .accordion-instance .accordion-header .instance-title{font-size:.8125rem;font-weight:600;color:#1f2937}.form-section-container .accordion-instance .accordion-header .accordion-header-right{display:flex;align-items:center;gap:6px}.form-section-container .accordion-instance .accordion-header .accordion-remove-btn{background:none;border:none;cursor:pointer;color:#ef4444;padding:4px 6px;border-radius:6px;line-height:1;display:flex;align-items:center;transition:background .15s ease,color .15s ease}.form-section-container .accordion-instance .accordion-header .accordion-remove-btn mat-icon{font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem}.form-section-container .accordion-instance .accordion-header .accordion-remove-btn:hover{background:#fee2e2;color:#dc2626}.form-section-container .accordion-instance .accordion-header .accordion-chevron{font-size:1.25rem;width:1.25rem;height:1.25rem;line-height:1.25rem;color:#6b7280;transition:transform .2s ease}.form-section-container .accordion-instance .accordion-body{padding:18px 16px;background:#fff;border-top:1px solid #E8EAF0}.form-section-container .btn-add-section{display:flex;align-items:center;justify-content:center;gap:7px;width:100%;padding:11px 20px;margin-top:14px;background:transparent;color:#6366f1;border:1.5px dashed #C7D2FE;border-radius:8px;cursor:pointer;font-family:inherit;font-size:.8125rem;font-weight:600;transition:all .2s ease;letter-spacing:.01em}.form-section-container .btn-add-section mat-icon{font-size:1.1rem;width:1.1rem;height:1.1rem;line-height:1.1rem}.form-section-container .btn-add-section:hover{background:#eef2ff;border-color:#a5b4fc;border-style:solid;transform:translateY(-1px);box-shadow:0 2px 8px #6366f11f}.form-section-container .btn-add-section:active{transform:translateY(0)}\n"] }]
|
|
3313
4332
|
}], ctorParameters: () => [{ type: i1$2.FormBuilder }], propDecorators: { config: [{
|
|
3314
4333
|
type: Input
|
|
3315
4334
|
}], controller: [{
|
|
@@ -4360,11 +5379,11 @@ class SmartFormComponent {
|
|
|
4360
5379
|
return suffix.split('_').map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ');
|
|
4361
5380
|
}
|
|
4362
5381
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormComponent, deps: [{ token: i1$2.FormBuilder }, { token: SmartFormController }, { token: ExpressionService }, { token: i3.HttpClient }, { token: SnackbarService }, { token: i1$4.Router }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
4363
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SmartFormComponent, isStandalone: false, selector: "lib-smart-form", inputs: { formJson: "formJson", initialValues: "initialValues", enableDraftAutoSave: "enableDraftAutoSave", labels: "labels", mode: "mode", readOnly: "readOnly", selectedOptionsData: "selectedOptionsData" }, outputs: { submit: "submit", draftSave: "draftSave", actionClick: "actionClick", valueChange: "valueChange", fileAdded: "fileAdded", fileUploadFinished: "fileUploadFinished", fileRemoved: "fileRemoved", suffixActionClick: "suffixActionClick", stepChange: "stepChange" }, providers: [SmartFormController], viewQueries: [{ propertyName: "formFieldComponents", predicate: FormFieldComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"smart-form-container\">\r\n\r\n <!-- \u2550\u2550 Skeleton loader \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div class=\"smart-form-skeleton\" *ngIf=\"!isFormReady\" aria-hidden=\"true\">\r\n\r\n <!-- Title + description -->\r\n <div class=\"skel-header\">\r\n <div class=\"skel-block skel-title\" style=\"--sd: 0s\"></div>\r\n <div class=\"skel-block skel-desc\" style=\"--sd: 0.07s\"></div>\r\n </div>\r\n\r\n <!-- Section 1 \u2014 two full-width fields -->\r\n <div class=\"skel-section\">\r\n <div class=\"skel-block skel-section-label\" style=\"--sd: 0.1s; width: 32%\"></div>\r\n <div class=\"skel-fields skel-fields--two\">\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.13s; width: 55%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.15s\"></div>\r\n </div>\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.16s; width: 45%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.18s\"></div>\r\n </div>\r\n <div class=\"skel-field skel-field--full\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.2s; width: 38%\"></div>\r\n <div class=\"skel-block skel-input skel-input--textarea\" style=\"--sd: 0.22s\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Section 2 \u2014 three columns -->\r\n <div class=\"skel-section\">\r\n <div class=\"skel-block skel-section-label\" style=\"--sd: 0.25s; width: 24%\"></div>\r\n <div class=\"skel-fields skel-fields--three\">\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.28s; width: 60%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.30s\"></div>\r\n </div>\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.31s; width: 50%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.33s\"></div>\r\n </div>\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.34s; width: 65%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.36s\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Section 3 \u2014 two columns + chip row -->\r\n <div class=\"skel-section\">\r\n <div class=\"skel-block skel-section-label\" style=\"--sd: 0.38s; width: 28%\"></div>\r\n <div class=\"skel-fields skel-fields--two\">\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.40s; width: 48%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.42s\"></div>\r\n </div>\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.43s; width: 55%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.45s\"></div>\r\n </div>\r\n </div>\r\n <div class=\"skel-chips\">\r\n <div class=\"skel-block skel-chip\" style=\"--sd: 0.47s; width: 72px\"></div>\r\n <div class=\"skel-block skel-chip\" style=\"--sd: 0.49s; width: 96px\"></div>\r\n <div class=\"skel-block skel-chip\" style=\"--sd: 0.51s; width: 80px\"></div>\r\n <div class=\"skel-block skel-chip\" style=\"--sd: 0.53s; width: 64px\"></div>\r\n </div>\r\n </div>\r\n\r\n <!-- Action bar -->\r\n <div class=\"skel-actions\">\r\n <div class=\"skel-block skel-btn\" style=\"--sd: 0.55s; width: 88px\"></div>\r\n <div class=\"skel-block skel-btn skel-btn--primary\" style=\"--sd: 0.58s; width: 120px\"></div>\r\n </div>\r\n </div>\r\n\r\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 SECTION STEPPER layout \u2014 stepper nav card + per-step form panels.\r\n Rendered when formSchema.sectionStepper === true.\r\n The nav and each step section are separate cards (no outer wrapper card).\r\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <ng-container *ngIf=\"formSchema && isFormReady && isSectionStepper\">\r\n\r\n <!-- Horizontal stepper nav card \u2014 hidden for a single step (nothing to step between) -->\r\n <div class=\"section-stepper-nav\" *ngIf=\"sectionSteps.length > 1\">\r\n <div class=\"section-stepper-track\">\r\n <div\r\n *ngFor=\"let step of sectionSteps; let i = index\"\r\n class=\"section-stepper-step\"\r\n [class.ss-active]=\"i === currentSectionStep\"\r\n [class.ss-completed]=\"i < currentSectionStep && stepValidationStates[i] !== 'warning'\"\r\n [class.ss-warning]=\"i < currentSectionStep && stepValidationStates[i] === 'warning'\"\r\n [attr.data-tooltip]=\"step.sectionConfig?.label || 'Step ' + (i + 1)\"\r\n (click)=\"goToSectionStep(i)\">\r\n\r\n <!-- Connector line \u2014 left side (hidden for first step) -->\r\n <div class=\"ss-connector ss-connector--left\" *ngIf=\"i > 0\"></div>\r\n\r\n <!-- Step badge -->\r\n <div class=\"ss-badge\">\r\n <!-- Green checkmark: visited and valid -->\r\n <svg *ngIf=\"i < currentSectionStep && stepValidationStates[i] !== 'warning'\"\r\n width=\"13\" height=\"13\" viewBox=\"0 0 24 24\"\r\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\"\r\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <polyline points=\"20 6 9 17 4 12\"></polyline>\r\n </svg>\r\n <!-- Warning: bold ! is the most visible option inside a 38px circle badge -->\r\n <span *ngIf=\"i < currentSectionStep && stepValidationStates[i] === 'warning'\"\r\n class=\"ss-warning-mark\">!</span>\r\n <!-- Number: current or future step -->\r\n <span *ngIf=\"i >= currentSectionStep\">{{ i + 1 }}</span>\r\n </div>\r\n\r\n <!-- Step label (truncated; full text revealed by CSS tooltip) -->\r\n <div class=\"ss-label\" [title]=\"step.sectionConfig?.label || 'Step ' + (i + 1)\">\r\n {{ step.sectionConfig?.label || 'Step ' + (i + 1) }}\r\n </div>\r\n\r\n <!-- Connector line \u2014 right side (hidden for last step) -->\r\n <div class=\"ss-connector ss-connector--right\" *ngIf=\"i < sectionSteps.length - 1\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Step panels \u2014 all mounted, only active one is visible (preserves form data) -->\r\n <form [formGroup]=\"formGroup\" class=\"smart-form ss-form\">\r\n <div\r\n *ngFor=\"let step of sectionSteps; let i = index\"\r\n class=\"ss-step-panel\"\r\n [style.display]=\"i === currentSectionStep ? 'block' : 'none'\">\r\n <lib-form-section\r\n [config]=\"getSectionStepConfig(step)\"\r\n [controller]=\"controller\"\r\n [formGroup]=\"formGroup\">\r\n </lib-form-section>\r\n </div>\r\n </form>\r\n\r\n <!-- \u2500\u2500 Action bar: Prev / custom action buttons / Next \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"form-actions\"\r\n *ngIf=\"!readOnly && formSchema.showActions !== false && actionBarConfig?.buttons?.length\">\r\n\r\n <!-- LEFT GROUP: Previous button + left-aligned custom buttons -->\r\n <div class=\"action-group action-group--left\">\r\n <lib-button\r\n *ngIf=\"!isSectionStepFirst\"\r\n variant=\"outline\"\r\n (click)=\"navigateToPrevious()\">\r\n {{ previousLabel }}\r\n </lib-button>\r\n <ng-container *ngFor=\"let btn of getButtonsForAlignment('left')\">\r\n <lib-button\r\n *ngIf=\"!btn.showOnLastStepOnly || isSectionStepLast\"\r\n [variant]=\"$any(btn.variant) || 'outline'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- RIGHT GROUP: right-aligned custom buttons + Next button -->\r\n <div class=\"action-group action-group--right\">\r\n <ng-container *ngFor=\"let btn of getButtonsForAlignment('right')\">\r\n <lib-button\r\n *ngIf=\"!btn.showOnLastStepOnly || isSectionStepLast\"\r\n [variant]=\"$any(btn.variant) || 'primary'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </ng-container>\r\n <lib-button\r\n *ngIf=\"!isSectionStepLast\"\r\n variant=\"primary\"\r\n (click)=\"navigateToNext()\">\r\n {{ nextLabel }}\r\n </lib-button>\r\n </div>\r\n\r\n </div>\r\n\r\n </ng-container>\r\n\r\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 Regular card wrapper \u2014 used for SECTION and STEPPER form types.\r\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div class=\"smart-form-wrapper\" *ngIf=\"formSchema && isFormReady && !isSectionStepper\"\r\n [class.smart-form-wrapper--readonly]=\"readOnly\">\r\n\r\n <!-- Form Header -->\r\n <div class=\"form-header\" *ngIf=\"formSchema.showTitle !== false\">\r\n <h2 class=\"form-title\">{{ formSchema.label }}</h2>\r\n <p class=\"form-description\" *ngIf=\"formSchema.description\">{{ formSchema.description }}</p>\r\n </div>\r\n\r\n <!-- STEPPER type nav -->\r\n <div class=\"stepper-nav\" *ngIf=\"isStepper && formSchema.stepperConfig?.showStep !== false\">\r\n <div class=\"stepper-steps\" [class.horizontal]=\"formSchema.stepperConfig?.isHorizontal !== false\">\r\n <div *ngFor=\"let step of fieldList; let i = index\" class=\"stepper-step\" [class.active]=\"i === currentStep\"\r\n [class.completed]=\"i < currentStep\">\r\n <div class=\"step-number\">{{ i + 1 }}</div>\r\n <div class=\"step-label\">{{ step.sectionConfig?.label || 'Step ' + (i + 1) }}</div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Form Content -->\r\n <form [formGroup]=\"formGroup\" class=\"smart-form\">\r\n <!-- Regular SECTION form -->\r\n <div *ngIf=\"!isStepper && formSchema.sectionConfig && formSchema.sectionConfig.isEnabled !== false\" class=\"form-section\">\r\n <lib-form-section [config]=\"formSchema.sectionConfig\" [controller]=\"controller\" [formGroup]=\"formGroup\">\r\n </lib-form-section>\r\n </div>\r\n\r\n <!-- STEPPER type form -->\r\n <div *ngIf=\"isStepper && currentStepConfig && currentStepConfig.sectionConfig?.isEnabled !== false\" class=\"form-stepper\">\r\n <lib-form-section [config]=\"currentStepConfig.sectionConfig!\" [controller]=\"controller\" [formGroup]=\"formGroup\">\r\n </lib-form-section>\r\n </div>\r\n </form>\r\n\r\n <!-- \u2500\u2500 Form Actions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"form-actions\" *ngIf=\"formSchema.showActions !== false && !readOnly && actionBarConfig?.buttons?.length\">\r\n\r\n <!-- LEFT GROUP -->\r\n <div class=\"action-group action-group--left\">\r\n <lib-button\r\n *ngFor=\"let btn of getButtonsForAlignment('left')\"\r\n [variant]=\"$any(btn.variant) || 'outline'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </div>\r\n\r\n <!-- RIGHT GROUP -->\r\n <div class=\"action-group action-group--right\">\r\n <lib-button\r\n *ngFor=\"let btn of getButtonsForAlignment('right')\"\r\n [variant]=\"$any(btn.variant) || 'primary'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n</div>\r\n", styles: ["@keyframes skel-wave{0%{transform:translate(-100%)}to{transform:translate(200%)}}@keyframes skel-fade-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes form-reveal{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.smart-form-skeleton{background:var(--cc-sf-form-bg, #ffffff);border-radius:var(--cc-sf-form-border-radius, 12px);border:var(--cc-sf-form-border, none);box-shadow:var(--cc-sf-form-shadow, 0 1px 3px rgba(0, 0, 0, .06));padding:1.5rem;display:flex;flex-direction:column;gap:var(--cc-sf-form-section-gap, 24px);animation:skel-fade-in .3s ease both}.skel-block{position:relative;overflow:hidden;border-radius:6px;background:var(--cc-sf-input-disabled-bg, #F0F2F5)}.skel-block:after{content:\"\";position:absolute;inset:0;transform:translate(-100%);background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,.55) 45%,rgba(255,255,255,.75) 50%,rgba(255,255,255,.55) 55%,transparent 100%);animation:skel-wave 1.8s ease-in-out infinite;animation-delay:var(--sd, 0s)}.skel-header{display:flex;flex-direction:column;gap:10px}.skel-header .skel-title{height:26px;border-radius:8px}.skel-header .skel-desc{height:14px;border-radius:5px;opacity:.7}.skel-section{display:flex;flex-direction:column;gap:14px}.skel-section-label{height:16px;border-radius:0 5px 5px 0;border-left:var(--cc-sf-section-header-accent-width, 4px) solid var(--cc-sf-section-header-accent-color, #3B82F6);background:var(--cc-sf-input-disabled-bg, #F0F2F5);opacity:.85}.skel-fields{display:grid;gap:var(--cc-sf-grid-gap, 16px);align-items:start}.skel-fields--two{grid-template-columns:1fr 1fr}.skel-fields--three{grid-template-columns:1fr 1fr 1fr}@media(max-width:640px){.skel-fields--two,.skel-fields--three{grid-template-columns:1fr}}.skel-field{display:flex;flex-direction:column;gap:7px}.skel-field--full{grid-column:1/-1}.skel-label{height:12px;border-radius:4px;opacity:.75}.skel-input{height:42px;border-radius:8px}.skel-input--textarea{height:80px}.skel-chips{display:flex;flex-wrap:wrap;gap:8px}.skel-chip{height:32px;border-radius:20px;opacity:.8}.skel-actions{display:flex;justify-content:flex-end;align-items:center;gap:12px;padding-top:20px;border-top:var(--cc-sf-actions-border, 1px solid #E5E7EB)}.skel-btn{height:40px;border-radius:8px}.skel-btn--primary{background:color-mix(in srgb,var(--cc-sf-accent-color, #3B82F6) 18%,var(--cc-sf-input-disabled-bg, #F0F2F5))}.smart-form-container{width:100%;font-family:var(--cc-sf-font-family, \"Inter\", sans-serif)}.smart-form-wrapper{background:var(--cc-sf-form-bg, #ffffff);border-radius:var(--cc-sf-form-border-radius, 12px);border:var(--cc-sf-form-border, none);animation:form-reveal .3s ease both}.smart-form-wrapper .form-alert-feedback,.form-header{margin-bottom:1rem}.form-header .form-title{font-size:var(--cc-sf-form-title-size, 1.5rem);font-weight:var(--cc-sf-form-title-weight, 700);color:var(--cc-sf-form-title-color, #111827);margin:0 0 8px;line-height:1.25}.form-header .form-description{font-size:var(--cc-sf-form-desc-size, .875rem);color:var(--cc-sf-form-desc-color, #6B7280);margin:0}@keyframes ss-badge-pop{0%{transform:scale(.6);opacity:0}70%{transform:scale(1.12)}to{transform:scale(1);opacity:1}}@keyframes ss-pulse-ring{0%{box-shadow:0 0 #6366f173}70%{box-shadow:0 0 0 10px #6366f100}to{box-shadow:0 0 #6366f100}}@keyframes ss-panel-in{0%{opacity:0;transform:translateY(14px)}to{opacity:1;transform:translateY(0)}}@keyframes ss-connector-fill{0%{transform:scaleX(0)}to{transform:scaleX(1)}}.section-stepper-nav{position:relative;background:#fff;border-radius:16px;border:1px solid #E8EAF0;box-shadow:0 1px 3px #0000000d,0 4px 16px #6366f10f;padding:24px 28px 48px;margin-bottom:14px;overflow-x:clip;overflow-y:hidden}.section-stepper-nav:before{content:\"\";position:absolute;inset:0 0 auto;height:3px;border-radius:16px 16px 0 0;background:linear-gradient(90deg,#6366f1,#8b5cf6,#ec4899)}.section-stepper-track{display:flex;align-items:flex-start;justify-content:space-between;min-width:max-content;width:100%}.section-stepper-step{display:flex;flex-direction:column;align-items:center;position:relative;flex:1;cursor:pointer;padding:0 4px}.section-stepper-step:after{content:attr(data-tooltip);position:absolute;top:calc(100% + 8px);left:50%;transform:translate(-50%) translateY(4px);white-space:nowrap;background:#1e1b4b;color:#fff;font-size:.6875rem;font-weight:500;letter-spacing:.02em;padding:5px 11px;border-radius:6px;pointer-events:none;opacity:0;transition:opacity .2s ease,transform .2s ease;z-index:200;box-shadow:0 4px 14px #0003}.section-stepper-step:hover:after{opacity:1;transform:translate(-50%) translateY(0)}.section-stepper-step .ss-badge{width:38px;height:38px;min-width:38px;border-radius:50%;background:#f1f2f6;color:#9ca3af;border:2px solid #E5E7EB;display:flex;align-items:center;justify-content:center;font-size:.8125rem;font-weight:700;position:relative;z-index:1;transition:all .3s cubic-bezier(.34,1.56,.64,1)}.section-stepper-step .ss-badge span{line-height:1;letter-spacing:-.01em}.section-stepper-step .ss-badge svg{flex-shrink:0}.section-stepper-step .ss-badge .ss-warning-mark{font-size:1.25rem;font-weight:900;line-height:1;letter-spacing:0;color:#fff}.section-stepper-step .ss-label{margin-top:9px;font-size:.6875rem;font-weight:500;color:#b0b7c3;white-space:nowrap;text-align:center;transition:color .25s ease,font-weight .25s ease;max-width:88px;overflow:hidden;text-overflow:ellipsis;letter-spacing:.01em}.section-stepper-step .ss-connector{height:2px;background:#e8eaf0;position:absolute;top:19px;z-index:0;overflow:hidden;border-radius:2px}.section-stepper-step .ss-connector--left{left:0;right:calc(50% + 19px)}.section-stepper-step .ss-connector--right{left:calc(50% + 19px);right:0}.section-stepper-step .ss-connector:after{content:\"\";position:absolute;inset:0;background:linear-gradient(90deg,#6366f1,#8b5cf6);transform:scaleX(0);transform-origin:left;transition:transform .4s cubic-bezier(.4,0,.2,1)}.section-stepper-step.ss-active{cursor:default}.section-stepper-step.ss-active .ss-badge{background:linear-gradient(135deg,#6366f1,#8b5cf6);color:#fff;border-color:transparent;box-shadow:0 0 0 4px #6366f12e,0 4px 12px #6366f159;animation:ss-badge-pop .4s cubic-bezier(.34,1.56,.64,1) both,ss-pulse-ring 2s .4s ease-out infinite}.section-stepper-step.ss-active .ss-label{color:#4f46e5;font-weight:700}.section-stepper-step.ss-completed .ss-badge{background:linear-gradient(135deg,#10b981,#059669);color:#fff;border-color:transparent;box-shadow:0 2px 8px #10b9814d}.section-stepper-step.ss-completed .ss-label{color:#6b7280;font-weight:500}.section-stepper-step.ss-completed .ss-connector--left:after,.section-stepper-step.ss-completed .ss-connector--right:after{transform:scaleX(1)}.section-stepper-step.ss-warning .ss-badge{background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff;border-color:transparent;box-shadow:0 2px 8px #f59e0b59;animation:ss-badge-pop .35s cubic-bezier(.34,1.56,.64,1) both}.section-stepper-step.ss-warning .ss-label{color:#b45309;font-weight:600}.section-stepper-step.ss-warning .ss-connector--left:after{transform:scaleX(0)}.section-stepper-step.ss-warning .ss-connector--right:after{transform:scaleX(0)}.section-stepper-step:not(.ss-active):not(.ss-completed):not(.ss-warning):hover .ss-badge{background:#e8eaf0;color:#6366f1;border-color:#c7d2fe;transform:translateY(-2px)}.section-stepper-step:not(.ss-active):not(.ss-completed):not(.ss-warning):hover .ss-label{color:#6b7280}.ss-step-panel{animation:ss-panel-in .35s cubic-bezier(.4,0,.2,1) both}.ss-form{padding-top:0}@media(max-width:768px){.section-stepper-nav{padding:20px 16px 16px}.section-stepper-step .ss-label{font-size:.625rem;max-width:64px}.section-stepper-step .ss-badge{width:32px;height:32px;min-width:32px;font-size:.75rem}.section-stepper-step .ss-connector{top:16px}.section-stepper-step .ss-connector--left{right:calc(50% + 16px)}.section-stepper-step .ss-connector--right{left:calc(50% + 16px)}}.stepper-nav{margin-bottom:32px}.stepper-nav .stepper-steps{display:flex;gap:16px}.stepper-nav .stepper-steps.horizontal{flex-direction:row;justify-content:space-between}.stepper-nav .stepper-steps:not(.horizontal){flex-direction:column}.stepper-nav .stepper-step{display:flex;align-items:center;gap:12px;flex:1;position:relative}.stepper-nav .stepper-step:not(:last-child):after{content:\"\";position:absolute;top:calc(var(--cc-sf-step-number-size, 40px) / 2);left:calc(100% + 8px);width:calc(100% - 40px);height:2px;background:var(--cc-sf-step-connector-color, #E5E7EB);transition:background var(--cc-sf-btn-transition, .2s ease)}.stepper-nav .stepper-step.completed:after{background:var(--cc-sf-step-connector-done, #22C55E)}.stepper-nav .stepper-step .step-number{width:var(--cc-sf-step-number-size, 40px);height:var(--cc-sf-step-number-size, 40px);min-width:var(--cc-sf-step-number-size, 40px);border-radius:50%;background:var(--cc-sf-step-number-bg, #E5E7EB);color:var(--cc-sf-step-number-color, #6B7280);display:flex;align-items:center;justify-content:center;font-size:var(--cc-sf-step-number-font-size, .875rem);font-weight:var(--cc-sf-step-number-weight, 600);transition:var(--cc-sf-btn-transition, all .2s ease)}.stepper-nav .stepper-step .step-label{font-size:var(--cc-sf-step-label-size, .875rem);color:var(--cc-sf-step-label-color, #6B7280);font-weight:var(--cc-sf-step-label-weight, 500);transition:var(--cc-sf-btn-transition, all .2s ease)}.stepper-nav .stepper-step.active .step-number{background:var(--cc-sf-step-active-bg, #3B82F6);color:var(--cc-sf-step-active-color, #ffffff)}.stepper-nav .stepper-step.active .step-label{color:var(--cc-sf-step-active-label, #1D4ED8);font-weight:var(--cc-sf-step-active-label-weight, 700)}.stepper-nav .stepper-step.completed .step-number{background:var(--cc-sf-step-done-bg, #22C55E);color:var(--cc-sf-step-done-color, #ffffff)}.smart-form{display:flex;flex-direction:column;gap:var(--cc-sf-form-section-gap, 24px)}.smart-form>*{margin-bottom:0!important}.form-actions{display:flex;justify-content:space-between;align-items:center;gap:var(--cc-sf-actions-gap, 12px);padding:var(--cc-sf-actions-padding, 20px 10px 0);margin-top:8px}.form-actions .action-group{display:flex;align-items:center;gap:var(--cc-sf-actions-gap, 12px)}.form-actions .action-group--left{justify-content:flex-start}.form-actions .action-group--right{justify-content:flex-end;margin-left:auto}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: ButtonComponent, selector: "lib-button", inputs: ["variant", "type", "disabled", "width", "height", "borderRadius", "fontSize", "fontWeight", "backgroundColor", "color", "border", "icon", "labels"] }, { kind: "component", type: FormSectionComponent, selector: "lib-form-section", inputs: ["config", "controller", "formGroup"] }] });
|
|
5382
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SmartFormComponent, isStandalone: false, selector: "lib-smart-form", inputs: { formJson: "formJson", initialValues: "initialValues", enableDraftAutoSave: "enableDraftAutoSave", labels: "labels", mode: "mode", readOnly: "readOnly", selectedOptionsData: "selectedOptionsData" }, outputs: { submit: "submit", draftSave: "draftSave", actionClick: "actionClick", valueChange: "valueChange", fileAdded: "fileAdded", fileUploadFinished: "fileUploadFinished", fileRemoved: "fileRemoved", suffixActionClick: "suffixActionClick", stepChange: "stepChange" }, providers: [SmartFormController], viewQueries: [{ propertyName: "formFieldComponents", predicate: FormFieldComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"smart-form-container\">\r\n\r\n <!-- \u2550\u2550 Skeleton loader \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div class=\"smart-form-skeleton\" *ngIf=\"!isFormReady\" aria-hidden=\"true\">\r\n\r\n <!-- Title + description -->\r\n <div class=\"skel-header\">\r\n <div class=\"skel-block skel-title\" style=\"--sd: 0s\"></div>\r\n <div class=\"skel-block skel-desc\" style=\"--sd: 0.07s\"></div>\r\n </div>\r\n\r\n <!-- Section 1 \u2014 two full-width fields -->\r\n <div class=\"skel-section\">\r\n <div class=\"skel-block skel-section-label\" style=\"--sd: 0.1s; width: 32%\"></div>\r\n <div class=\"skel-fields skel-fields--two\">\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.13s; width: 55%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.15s\"></div>\r\n </div>\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.16s; width: 45%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.18s\"></div>\r\n </div>\r\n <div class=\"skel-field skel-field--full\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.2s; width: 38%\"></div>\r\n <div class=\"skel-block skel-input skel-input--textarea\" style=\"--sd: 0.22s\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Section 2 \u2014 three columns -->\r\n <div class=\"skel-section\">\r\n <div class=\"skel-block skel-section-label\" style=\"--sd: 0.25s; width: 24%\"></div>\r\n <div class=\"skel-fields skel-fields--three\">\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.28s; width: 60%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.30s\"></div>\r\n </div>\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.31s; width: 50%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.33s\"></div>\r\n </div>\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.34s; width: 65%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.36s\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Section 3 \u2014 two columns + chip row -->\r\n <div class=\"skel-section\">\r\n <div class=\"skel-block skel-section-label\" style=\"--sd: 0.38s; width: 28%\"></div>\r\n <div class=\"skel-fields skel-fields--two\">\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.40s; width: 48%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.42s\"></div>\r\n </div>\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.43s; width: 55%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.45s\"></div>\r\n </div>\r\n </div>\r\n <div class=\"skel-chips\">\r\n <div class=\"skel-block skel-chip\" style=\"--sd: 0.47s; width: 72px\"></div>\r\n <div class=\"skel-block skel-chip\" style=\"--sd: 0.49s; width: 96px\"></div>\r\n <div class=\"skel-block skel-chip\" style=\"--sd: 0.51s; width: 80px\"></div>\r\n <div class=\"skel-block skel-chip\" style=\"--sd: 0.53s; width: 64px\"></div>\r\n </div>\r\n </div>\r\n\r\n <!-- Action bar -->\r\n <div class=\"skel-actions\">\r\n <div class=\"skel-block skel-btn\" style=\"--sd: 0.55s; width: 88px\"></div>\r\n <div class=\"skel-block skel-btn skel-btn--primary\" style=\"--sd: 0.58s; width: 120px\"></div>\r\n </div>\r\n </div>\r\n\r\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 SECTION STEPPER layout \u2014 stepper nav card + per-step form panels.\r\n Rendered when formSchema.sectionStepper === true.\r\n The nav and each step section are separate cards (no outer wrapper card).\r\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <ng-container *ngIf=\"formSchema && isFormReady && isSectionStepper\">\r\n\r\n <!-- Horizontal stepper nav card \u2014 hidden for a single step (nothing to step between) -->\r\n <div class=\"section-stepper-nav\" *ngIf=\"sectionSteps.length > 1\">\r\n <div class=\"section-stepper-track\">\r\n <div\r\n *ngFor=\"let step of sectionSteps; let i = index\"\r\n class=\"section-stepper-step\"\r\n [class.ss-active]=\"i === currentSectionStep\"\r\n [class.ss-completed]=\"i < currentSectionStep && stepValidationStates[i] !== 'warning'\"\r\n [class.ss-warning]=\"i < currentSectionStep && stepValidationStates[i] === 'warning'\"\r\n [attr.data-tooltip]=\"step.sectionConfig?.label || 'Step ' + (i + 1)\"\r\n (click)=\"goToSectionStep(i)\">\r\n\r\n <!-- Connector line \u2014 left side (hidden for first step) -->\r\n <div class=\"ss-connector ss-connector--left\" *ngIf=\"i > 0\"></div>\r\n\r\n <!-- Step badge -->\r\n <div class=\"ss-badge\">\r\n <!-- Green checkmark: visited and valid -->\r\n <svg *ngIf=\"i < currentSectionStep && stepValidationStates[i] !== 'warning'\"\r\n width=\"13\" height=\"13\" viewBox=\"0 0 24 24\"\r\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\"\r\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <polyline points=\"20 6 9 17 4 12\"></polyline>\r\n </svg>\r\n <!-- Warning: bold ! is the most visible option inside a 38px circle badge -->\r\n <span *ngIf=\"i < currentSectionStep && stepValidationStates[i] === 'warning'\"\r\n class=\"ss-warning-mark\">!</span>\r\n <!-- Number: current or future step -->\r\n <span *ngIf=\"i >= currentSectionStep\">{{ i + 1 }}</span>\r\n </div>\r\n\r\n <!-- Step label (truncated; full text revealed by CSS tooltip) -->\r\n <div class=\"ss-label\" [title]=\"step.sectionConfig?.label || 'Step ' + (i + 1)\">\r\n {{ step.sectionConfig?.label || 'Step ' + (i + 1) }}\r\n </div>\r\n\r\n <!-- Connector line \u2014 right side (hidden for last step) -->\r\n <div class=\"ss-connector ss-connector--right\" *ngIf=\"i < sectionSteps.length - 1\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Step panels \u2014 all mounted, only active one is visible (preserves form data) -->\r\n <form [formGroup]=\"formGroup\" class=\"smart-form ss-form\">\r\n <div\r\n *ngFor=\"let step of sectionSteps; let i = index\"\r\n class=\"ss-step-panel\"\r\n [style.display]=\"i === currentSectionStep ? 'block' : 'none'\">\r\n <lib-form-section\r\n [config]=\"getSectionStepConfig(step)\"\r\n [controller]=\"controller\"\r\n [formGroup]=\"formGroup\">\r\n </lib-form-section>\r\n </div>\r\n </form>\r\n\r\n <!-- \u2500\u2500 Action bar: Prev / custom action buttons / Next \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"form-actions\"\r\n *ngIf=\"!readOnly && formSchema.showActions !== false && actionBarConfig?.buttons?.length\">\r\n\r\n <!-- LEFT GROUP: Previous button + left-aligned custom buttons -->\r\n <div class=\"action-group action-group--left\">\r\n <lib-button\r\n *ngIf=\"!isSectionStepFirst\"\r\n variant=\"outline\"\r\n (click)=\"navigateToPrevious()\">\r\n {{ previousLabel }}\r\n </lib-button>\r\n <ng-container *ngFor=\"let btn of getButtonsForAlignment('left')\">\r\n <lib-button\r\n *ngIf=\"!btn.showOnLastStepOnly || isSectionStepLast\"\r\n [variant]=\"$any(btn.variant) || 'outline'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- RIGHT GROUP: right-aligned custom buttons + Next button -->\r\n <div class=\"action-group action-group--right\">\r\n <ng-container *ngFor=\"let btn of getButtonsForAlignment('right')\">\r\n <lib-button\r\n *ngIf=\"!btn.showOnLastStepOnly || isSectionStepLast\"\r\n [variant]=\"$any(btn.variant) || 'primary'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </ng-container>\r\n <lib-button\r\n *ngIf=\"!isSectionStepLast\"\r\n variant=\"primary\"\r\n (click)=\"navigateToNext()\">\r\n {{ nextLabel }}\r\n </lib-button>\r\n </div>\r\n\r\n </div>\r\n\r\n </ng-container>\r\n\r\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 Regular card wrapper \u2014 used for SECTION and STEPPER form types.\r\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div class=\"smart-form-wrapper\" *ngIf=\"formSchema && isFormReady && !isSectionStepper\"\r\n [class.smart-form-wrapper--readonly]=\"readOnly\">\r\n\r\n <!-- Form Header -->\r\n <div class=\"form-header\" *ngIf=\"formSchema.showTitle !== false\">\r\n <h2 class=\"form-title\">{{ formSchema.label }}</h2>\r\n <p class=\"form-description\" *ngIf=\"formSchema.description\">{{ formSchema.description }}</p>\r\n </div>\r\n\r\n <!-- STEPPER type nav -->\r\n <div class=\"stepper-nav\" *ngIf=\"isStepper && formSchema.stepperConfig?.showStep !== false\">\r\n <div class=\"stepper-steps\" [class.horizontal]=\"formSchema.stepperConfig?.isHorizontal !== false\">\r\n <div *ngFor=\"let step of fieldList; let i = index\" class=\"stepper-step\" [class.active]=\"i === currentStep\"\r\n [class.completed]=\"i < currentStep\">\r\n <div class=\"step-number\">{{ i + 1 }}</div>\r\n <div class=\"step-label\">{{ step.sectionConfig?.label || 'Step ' + (i + 1) }}</div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Form Content -->\r\n <form [formGroup]=\"formGroup\" class=\"smart-form\">\r\n <!-- Regular SECTION form -->\r\n <div *ngIf=\"!isStepper && formSchema.sectionConfig && formSchema.sectionConfig.isEnabled !== false\" class=\"form-section\">\r\n <lib-form-section [config]=\"formSchema.sectionConfig\" [controller]=\"controller\" [formGroup]=\"formGroup\">\r\n </lib-form-section>\r\n </div>\r\n\r\n <!-- STEPPER type form -->\r\n <div *ngIf=\"isStepper && currentStepConfig && currentStepConfig.sectionConfig?.isEnabled !== false\" class=\"form-stepper\">\r\n <lib-form-section [config]=\"currentStepConfig.sectionConfig!\" [controller]=\"controller\" [formGroup]=\"formGroup\">\r\n </lib-form-section>\r\n </div>\r\n </form>\r\n\r\n <!-- \u2500\u2500 Form Actions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"form-actions\" *ngIf=\"formSchema.showActions !== false && !readOnly && actionBarConfig?.buttons?.length\">\r\n\r\n <!-- LEFT GROUP -->\r\n <div class=\"action-group action-group--left\">\r\n <lib-button\r\n *ngFor=\"let btn of getButtonsForAlignment('left')\"\r\n [variant]=\"$any(btn.variant) || 'outline'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </div>\r\n\r\n <!-- RIGHT GROUP -->\r\n <div class=\"action-group action-group--right\">\r\n <lib-button\r\n *ngFor=\"let btn of getButtonsForAlignment('right')\"\r\n [variant]=\"$any(btn.variant) || 'primary'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n</div>\r\n", styles: ["@keyframes skel-wave{0%{transform:translate(-100%)}to{transform:translate(200%)}}@keyframes skel-fade-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes form-reveal{0%{opacity:0;margin-top:8px}to{opacity:1;margin-top:0}}.smart-form-skeleton{background:var(--cc-sf-form-bg, #ffffff);border-radius:var(--cc-sf-form-border-radius, 12px);border:var(--cc-sf-form-border, none);box-shadow:var(--cc-sf-form-shadow, 0 1px 3px rgba(0, 0, 0, .06));padding:1.5rem;display:flex;flex-direction:column;gap:var(--cc-sf-form-section-gap, 24px);animation:skel-fade-in .3s ease both}.skel-block{position:relative;overflow:hidden;border-radius:6px;background:var(--cc-sf-input-disabled-bg, #F0F2F5)}.skel-block:after{content:\"\";position:absolute;inset:0;transform:translate(-100%);background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,.55) 45%,rgba(255,255,255,.75) 50%,rgba(255,255,255,.55) 55%,transparent 100%);animation:skel-wave 1.8s ease-in-out infinite;animation-delay:var(--sd, 0s)}.skel-header{display:flex;flex-direction:column;gap:10px}.skel-header .skel-title{height:26px;border-radius:8px}.skel-header .skel-desc{height:14px;border-radius:5px;opacity:.7}.skel-section{display:flex;flex-direction:column;gap:14px}.skel-section-label{height:16px;border-radius:0 5px 5px 0;border-left:var(--cc-sf-section-header-accent-width, 4px) solid var(--cc-sf-section-header-accent-color, #3B82F6);background:var(--cc-sf-input-disabled-bg, #F0F2F5);opacity:.85}.skel-fields{display:grid;gap:var(--cc-sf-grid-gap, 16px);align-items:start}.skel-fields--two{grid-template-columns:1fr 1fr}.skel-fields--three{grid-template-columns:1fr 1fr 1fr}@media(max-width:640px){.skel-fields--two,.skel-fields--three{grid-template-columns:1fr}}.skel-field{display:flex;flex-direction:column;gap:7px}.skel-field--full{grid-column:1/-1}.skel-label{height:12px;border-radius:4px;opacity:.75}.skel-input{height:42px;border-radius:8px}.skel-input--textarea{height:80px}.skel-chips{display:flex;flex-wrap:wrap;gap:8px}.skel-chip{height:32px;border-radius:20px;opacity:.8}.skel-actions{display:flex;justify-content:flex-end;align-items:center;gap:12px;padding-top:20px;border-top:var(--cc-sf-actions-border, 1px solid #E5E7EB)}.skel-btn{height:40px;border-radius:8px}.skel-btn--primary{background:color-mix(in srgb,var(--cc-sf-accent-color, #3B82F6) 18%,var(--cc-sf-input-disabled-bg, #F0F2F5))}.smart-form-container{width:100%;font-family:var(--cc-sf-font-family, \"Inter\", sans-serif)}.smart-form-wrapper{background:var(--cc-sf-form-bg, #ffffff);border-radius:var(--cc-sf-form-border-radius, 12px);border:var(--cc-sf-form-border, none);animation:form-reveal .3s ease both}.smart-form-wrapper .form-alert-feedback,.form-header{margin-bottom:1rem}.form-header .form-title{font-size:var(--cc-sf-form-title-size, 1.5rem);font-weight:var(--cc-sf-form-title-weight, 700);color:var(--cc-sf-form-title-color, #111827);margin:0 0 8px;line-height:1.25}.form-header .form-description{font-size:var(--cc-sf-form-desc-size, .875rem);color:var(--cc-sf-form-desc-color, #6B7280);margin:0}@keyframes ss-badge-pop{0%{transform:scale(.6);opacity:0}70%{transform:scale(1.12)}to{transform:scale(1);opacity:1}}@keyframes ss-pulse-ring{0%{box-shadow:0 0 #6366f173}70%{box-shadow:0 0 0 10px #6366f100}to{box-shadow:0 0 #6366f100}}@keyframes ss-panel-in{0%{opacity:0;transform:translateY(14px)}to{opacity:1;transform:translateY(0)}}@keyframes ss-connector-fill{0%{transform:scaleX(0)}to{transform:scaleX(1)}}.section-stepper-nav{position:relative;background:#fff;border-radius:16px;border:1px solid #E8EAF0;box-shadow:0 1px 3px #0000000d,0 4px 16px #6366f10f;padding:24px 28px 48px;margin-bottom:14px;overflow-x:clip;overflow-y:hidden}.section-stepper-nav:before{content:\"\";position:absolute;inset:0 0 auto;height:3px;border-radius:16px 16px 0 0;background:linear-gradient(90deg,#6366f1,#8b5cf6,#ec4899)}.section-stepper-track{display:flex;align-items:flex-start;justify-content:space-between;min-width:max-content;width:100%}.section-stepper-step{display:flex;flex-direction:column;align-items:center;position:relative;flex:1;cursor:pointer;padding:0 4px}.section-stepper-step:after{content:attr(data-tooltip);position:absolute;top:calc(100% + 8px);left:50%;transform:translate(-50%) translateY(4px);white-space:nowrap;background:#1e1b4b;color:#fff;font-size:.6875rem;font-weight:500;letter-spacing:.02em;padding:5px 11px;border-radius:6px;pointer-events:none;opacity:0;transition:opacity .2s ease,transform .2s ease;z-index:200;box-shadow:0 4px 14px #0003}.section-stepper-step:hover:after{opacity:1;transform:translate(-50%) translateY(0)}.section-stepper-step .ss-badge{width:38px;height:38px;min-width:38px;border-radius:50%;background:#f1f2f6;color:#9ca3af;border:2px solid #E5E7EB;display:flex;align-items:center;justify-content:center;font-size:.8125rem;font-weight:700;position:relative;z-index:1;transition:all .3s cubic-bezier(.34,1.56,.64,1)}.section-stepper-step .ss-badge span{line-height:1;letter-spacing:-.01em}.section-stepper-step .ss-badge svg{flex-shrink:0}.section-stepper-step .ss-badge .ss-warning-mark{font-size:1.25rem;font-weight:900;line-height:1;letter-spacing:0;color:#fff}.section-stepper-step .ss-label{margin-top:9px;font-size:.6875rem;font-weight:500;color:#b0b7c3;white-space:nowrap;text-align:center;transition:color .25s ease,font-weight .25s ease;max-width:88px;overflow:hidden;text-overflow:ellipsis;letter-spacing:.01em}.section-stepper-step .ss-connector{height:2px;background:#e8eaf0;position:absolute;top:19px;z-index:0;overflow:hidden;border-radius:2px}.section-stepper-step .ss-connector--left{left:0;right:calc(50% + 19px)}.section-stepper-step .ss-connector--right{left:calc(50% + 19px);right:0}.section-stepper-step .ss-connector:after{content:\"\";position:absolute;inset:0;background:linear-gradient(90deg,#6366f1,#8b5cf6);transform:scaleX(0);transform-origin:left;transition:transform .4s cubic-bezier(.4,0,.2,1)}.section-stepper-step.ss-active{cursor:default}.section-stepper-step.ss-active .ss-badge{background:linear-gradient(135deg,#6366f1,#8b5cf6);color:#fff;border-color:transparent;box-shadow:0 0 0 4px #6366f12e,0 4px 12px #6366f159;animation:ss-badge-pop .4s cubic-bezier(.34,1.56,.64,1) both,ss-pulse-ring 2s .4s ease-out infinite}.section-stepper-step.ss-active .ss-label{color:#4f46e5;font-weight:700}.section-stepper-step.ss-completed .ss-badge{background:linear-gradient(135deg,#10b981,#059669);color:#fff;border-color:transparent;box-shadow:0 2px 8px #10b9814d}.section-stepper-step.ss-completed .ss-label{color:#6b7280;font-weight:500}.section-stepper-step.ss-completed .ss-connector--left:after,.section-stepper-step.ss-completed .ss-connector--right:after{transform:scaleX(1)}.section-stepper-step.ss-warning .ss-badge{background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff;border-color:transparent;box-shadow:0 2px 8px #f59e0b59;animation:ss-badge-pop .35s cubic-bezier(.34,1.56,.64,1) both}.section-stepper-step.ss-warning .ss-label{color:#b45309;font-weight:600}.section-stepper-step.ss-warning .ss-connector--left:after{transform:scaleX(0)}.section-stepper-step.ss-warning .ss-connector--right:after{transform:scaleX(0)}.section-stepper-step:not(.ss-active):not(.ss-completed):not(.ss-warning):hover .ss-badge{background:#e8eaf0;color:#6366f1;border-color:#c7d2fe;transform:translateY(-2px)}.section-stepper-step:not(.ss-active):not(.ss-completed):not(.ss-warning):hover .ss-label{color:#6b7280}.ss-step-panel{animation:ss-panel-in .35s cubic-bezier(.4,0,.2,1) both}.ss-form{padding-top:0}@media(max-width:768px){.section-stepper-nav{padding:20px 16px 16px}.section-stepper-step .ss-label{font-size:.625rem;max-width:64px}.section-stepper-step .ss-badge{width:32px;height:32px;min-width:32px;font-size:.75rem}.section-stepper-step .ss-connector{top:16px}.section-stepper-step .ss-connector--left{right:calc(50% + 16px)}.section-stepper-step .ss-connector--right{left:calc(50% + 16px)}}.stepper-nav{margin-bottom:32px}.stepper-nav .stepper-steps{display:flex;gap:16px}.stepper-nav .stepper-steps.horizontal{flex-direction:row;justify-content:space-between}.stepper-nav .stepper-steps:not(.horizontal){flex-direction:column}.stepper-nav .stepper-step{display:flex;align-items:center;gap:12px;flex:1;position:relative}.stepper-nav .stepper-step:not(:last-child):after{content:\"\";position:absolute;top:calc(var(--cc-sf-step-number-size, 40px) / 2);left:calc(100% + 8px);width:calc(100% - 40px);height:2px;background:var(--cc-sf-step-connector-color, #E5E7EB);transition:background var(--cc-sf-btn-transition, .2s ease)}.stepper-nav .stepper-step.completed:after{background:var(--cc-sf-step-connector-done, #22C55E)}.stepper-nav .stepper-step .step-number{width:var(--cc-sf-step-number-size, 40px);height:var(--cc-sf-step-number-size, 40px);min-width:var(--cc-sf-step-number-size, 40px);border-radius:50%;background:var(--cc-sf-step-number-bg, #E5E7EB);color:var(--cc-sf-step-number-color, #6B7280);display:flex;align-items:center;justify-content:center;font-size:var(--cc-sf-step-number-font-size, .875rem);font-weight:var(--cc-sf-step-number-weight, 600);transition:var(--cc-sf-btn-transition, all .2s ease)}.stepper-nav .stepper-step .step-label{font-size:var(--cc-sf-step-label-size, .875rem);color:var(--cc-sf-step-label-color, #6B7280);font-weight:var(--cc-sf-step-label-weight, 500);transition:var(--cc-sf-btn-transition, all .2s ease)}.stepper-nav .stepper-step.active .step-number{background:var(--cc-sf-step-active-bg, #3B82F6);color:var(--cc-sf-step-active-color, #ffffff)}.stepper-nav .stepper-step.active .step-label{color:var(--cc-sf-step-active-label, #1D4ED8);font-weight:var(--cc-sf-step-active-label-weight, 700)}.stepper-nav .stepper-step.completed .step-number{background:var(--cc-sf-step-done-bg, #22C55E);color:var(--cc-sf-step-done-color, #ffffff)}.smart-form{display:flex;flex-direction:column;gap:var(--cc-sf-form-section-gap, 24px)}.smart-form>*{margin-bottom:0!important}.form-actions{display:flex;justify-content:space-between;align-items:center;gap:var(--cc-sf-actions-gap, 12px);padding:var(--cc-sf-actions-padding, 20px 10px 0);margin-top:8px}.form-actions .action-group{display:flex;align-items:center;gap:var(--cc-sf-actions-gap, 12px)}.form-actions .action-group--left{justify-content:flex-start}.form-actions .action-group--right{justify-content:flex-end;margin-left:auto}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: ButtonComponent, selector: "lib-button", inputs: ["variant", "type", "disabled", "width", "height", "borderRadius", "fontSize", "fontWeight", "backgroundColor", "color", "border", "icon", "labels"] }, { kind: "component", type: FormSectionComponent, selector: "lib-form-section", inputs: ["config", "controller", "formGroup"] }] });
|
|
4364
5383
|
}
|
|
4365
5384
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormComponent, decorators: [{
|
|
4366
5385
|
type: Component,
|
|
4367
|
-
args: [{ selector: 'lib-smart-form', providers: [SmartFormController], standalone: false, template: "<div class=\"smart-form-container\">\r\n\r\n <!-- \u2550\u2550 Skeleton loader \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div class=\"smart-form-skeleton\" *ngIf=\"!isFormReady\" aria-hidden=\"true\">\r\n\r\n <!-- Title + description -->\r\n <div class=\"skel-header\">\r\n <div class=\"skel-block skel-title\" style=\"--sd: 0s\"></div>\r\n <div class=\"skel-block skel-desc\" style=\"--sd: 0.07s\"></div>\r\n </div>\r\n\r\n <!-- Section 1 \u2014 two full-width fields -->\r\n <div class=\"skel-section\">\r\n <div class=\"skel-block skel-section-label\" style=\"--sd: 0.1s; width: 32%\"></div>\r\n <div class=\"skel-fields skel-fields--two\">\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.13s; width: 55%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.15s\"></div>\r\n </div>\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.16s; width: 45%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.18s\"></div>\r\n </div>\r\n <div class=\"skel-field skel-field--full\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.2s; width: 38%\"></div>\r\n <div class=\"skel-block skel-input skel-input--textarea\" style=\"--sd: 0.22s\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Section 2 \u2014 three columns -->\r\n <div class=\"skel-section\">\r\n <div class=\"skel-block skel-section-label\" style=\"--sd: 0.25s; width: 24%\"></div>\r\n <div class=\"skel-fields skel-fields--three\">\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.28s; width: 60%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.30s\"></div>\r\n </div>\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.31s; width: 50%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.33s\"></div>\r\n </div>\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.34s; width: 65%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.36s\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Section 3 \u2014 two columns + chip row -->\r\n <div class=\"skel-section\">\r\n <div class=\"skel-block skel-section-label\" style=\"--sd: 0.38s; width: 28%\"></div>\r\n <div class=\"skel-fields skel-fields--two\">\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.40s; width: 48%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.42s\"></div>\r\n </div>\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.43s; width: 55%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.45s\"></div>\r\n </div>\r\n </div>\r\n <div class=\"skel-chips\">\r\n <div class=\"skel-block skel-chip\" style=\"--sd: 0.47s; width: 72px\"></div>\r\n <div class=\"skel-block skel-chip\" style=\"--sd: 0.49s; width: 96px\"></div>\r\n <div class=\"skel-block skel-chip\" style=\"--sd: 0.51s; width: 80px\"></div>\r\n <div class=\"skel-block skel-chip\" style=\"--sd: 0.53s; width: 64px\"></div>\r\n </div>\r\n </div>\r\n\r\n <!-- Action bar -->\r\n <div class=\"skel-actions\">\r\n <div class=\"skel-block skel-btn\" style=\"--sd: 0.55s; width: 88px\"></div>\r\n <div class=\"skel-block skel-btn skel-btn--primary\" style=\"--sd: 0.58s; width: 120px\"></div>\r\n </div>\r\n </div>\r\n\r\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 SECTION STEPPER layout \u2014 stepper nav card + per-step form panels.\r\n Rendered when formSchema.sectionStepper === true.\r\n The nav and each step section are separate cards (no outer wrapper card).\r\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <ng-container *ngIf=\"formSchema && isFormReady && isSectionStepper\">\r\n\r\n <!-- Horizontal stepper nav card \u2014 hidden for a single step (nothing to step between) -->\r\n <div class=\"section-stepper-nav\" *ngIf=\"sectionSteps.length > 1\">\r\n <div class=\"section-stepper-track\">\r\n <div\r\n *ngFor=\"let step of sectionSteps; let i = index\"\r\n class=\"section-stepper-step\"\r\n [class.ss-active]=\"i === currentSectionStep\"\r\n [class.ss-completed]=\"i < currentSectionStep && stepValidationStates[i] !== 'warning'\"\r\n [class.ss-warning]=\"i < currentSectionStep && stepValidationStates[i] === 'warning'\"\r\n [attr.data-tooltip]=\"step.sectionConfig?.label || 'Step ' + (i + 1)\"\r\n (click)=\"goToSectionStep(i)\">\r\n\r\n <!-- Connector line \u2014 left side (hidden for first step) -->\r\n <div class=\"ss-connector ss-connector--left\" *ngIf=\"i > 0\"></div>\r\n\r\n <!-- Step badge -->\r\n <div class=\"ss-badge\">\r\n <!-- Green checkmark: visited and valid -->\r\n <svg *ngIf=\"i < currentSectionStep && stepValidationStates[i] !== 'warning'\"\r\n width=\"13\" height=\"13\" viewBox=\"0 0 24 24\"\r\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\"\r\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <polyline points=\"20 6 9 17 4 12\"></polyline>\r\n </svg>\r\n <!-- Warning: bold ! is the most visible option inside a 38px circle badge -->\r\n <span *ngIf=\"i < currentSectionStep && stepValidationStates[i] === 'warning'\"\r\n class=\"ss-warning-mark\">!</span>\r\n <!-- Number: current or future step -->\r\n <span *ngIf=\"i >= currentSectionStep\">{{ i + 1 }}</span>\r\n </div>\r\n\r\n <!-- Step label (truncated; full text revealed by CSS tooltip) -->\r\n <div class=\"ss-label\" [title]=\"step.sectionConfig?.label || 'Step ' + (i + 1)\">\r\n {{ step.sectionConfig?.label || 'Step ' + (i + 1) }}\r\n </div>\r\n\r\n <!-- Connector line \u2014 right side (hidden for last step) -->\r\n <div class=\"ss-connector ss-connector--right\" *ngIf=\"i < sectionSteps.length - 1\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Step panels \u2014 all mounted, only active one is visible (preserves form data) -->\r\n <form [formGroup]=\"formGroup\" class=\"smart-form ss-form\">\r\n <div\r\n *ngFor=\"let step of sectionSteps; let i = index\"\r\n class=\"ss-step-panel\"\r\n [style.display]=\"i === currentSectionStep ? 'block' : 'none'\">\r\n <lib-form-section\r\n [config]=\"getSectionStepConfig(step)\"\r\n [controller]=\"controller\"\r\n [formGroup]=\"formGroup\">\r\n </lib-form-section>\r\n </div>\r\n </form>\r\n\r\n <!-- \u2500\u2500 Action bar: Prev / custom action buttons / Next \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"form-actions\"\r\n *ngIf=\"!readOnly && formSchema.showActions !== false && actionBarConfig?.buttons?.length\">\r\n\r\n <!-- LEFT GROUP: Previous button + left-aligned custom buttons -->\r\n <div class=\"action-group action-group--left\">\r\n <lib-button\r\n *ngIf=\"!isSectionStepFirst\"\r\n variant=\"outline\"\r\n (click)=\"navigateToPrevious()\">\r\n {{ previousLabel }}\r\n </lib-button>\r\n <ng-container *ngFor=\"let btn of getButtonsForAlignment('left')\">\r\n <lib-button\r\n *ngIf=\"!btn.showOnLastStepOnly || isSectionStepLast\"\r\n [variant]=\"$any(btn.variant) || 'outline'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- RIGHT GROUP: right-aligned custom buttons + Next button -->\r\n <div class=\"action-group action-group--right\">\r\n <ng-container *ngFor=\"let btn of getButtonsForAlignment('right')\">\r\n <lib-button\r\n *ngIf=\"!btn.showOnLastStepOnly || isSectionStepLast\"\r\n [variant]=\"$any(btn.variant) || 'primary'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </ng-container>\r\n <lib-button\r\n *ngIf=\"!isSectionStepLast\"\r\n variant=\"primary\"\r\n (click)=\"navigateToNext()\">\r\n {{ nextLabel }}\r\n </lib-button>\r\n </div>\r\n\r\n </div>\r\n\r\n </ng-container>\r\n\r\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 Regular card wrapper \u2014 used for SECTION and STEPPER form types.\r\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div class=\"smart-form-wrapper\" *ngIf=\"formSchema && isFormReady && !isSectionStepper\"\r\n [class.smart-form-wrapper--readonly]=\"readOnly\">\r\n\r\n <!-- Form Header -->\r\n <div class=\"form-header\" *ngIf=\"formSchema.showTitle !== false\">\r\n <h2 class=\"form-title\">{{ formSchema.label }}</h2>\r\n <p class=\"form-description\" *ngIf=\"formSchema.description\">{{ formSchema.description }}</p>\r\n </div>\r\n\r\n <!-- STEPPER type nav -->\r\n <div class=\"stepper-nav\" *ngIf=\"isStepper && formSchema.stepperConfig?.showStep !== false\">\r\n <div class=\"stepper-steps\" [class.horizontal]=\"formSchema.stepperConfig?.isHorizontal !== false\">\r\n <div *ngFor=\"let step of fieldList; let i = index\" class=\"stepper-step\" [class.active]=\"i === currentStep\"\r\n [class.completed]=\"i < currentStep\">\r\n <div class=\"step-number\">{{ i + 1 }}</div>\r\n <div class=\"step-label\">{{ step.sectionConfig?.label || 'Step ' + (i + 1) }}</div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Form Content -->\r\n <form [formGroup]=\"formGroup\" class=\"smart-form\">\r\n <!-- Regular SECTION form -->\r\n <div *ngIf=\"!isStepper && formSchema.sectionConfig && formSchema.sectionConfig.isEnabled !== false\" class=\"form-section\">\r\n <lib-form-section [config]=\"formSchema.sectionConfig\" [controller]=\"controller\" [formGroup]=\"formGroup\">\r\n </lib-form-section>\r\n </div>\r\n\r\n <!-- STEPPER type form -->\r\n <div *ngIf=\"isStepper && currentStepConfig && currentStepConfig.sectionConfig?.isEnabled !== false\" class=\"form-stepper\">\r\n <lib-form-section [config]=\"currentStepConfig.sectionConfig!\" [controller]=\"controller\" [formGroup]=\"formGroup\">\r\n </lib-form-section>\r\n </div>\r\n </form>\r\n\r\n <!-- \u2500\u2500 Form Actions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"form-actions\" *ngIf=\"formSchema.showActions !== false && !readOnly && actionBarConfig?.buttons?.length\">\r\n\r\n <!-- LEFT GROUP -->\r\n <div class=\"action-group action-group--left\">\r\n <lib-button\r\n *ngFor=\"let btn of getButtonsForAlignment('left')\"\r\n [variant]=\"$any(btn.variant) || 'outline'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </div>\r\n\r\n <!-- RIGHT GROUP -->\r\n <div class=\"action-group action-group--right\">\r\n <lib-button\r\n *ngFor=\"let btn of getButtonsForAlignment('right')\"\r\n [variant]=\"$any(btn.variant) || 'primary'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n</div>\r\n", styles: ["@keyframes skel-wave{0%{transform:translate(-100%)}to{transform:translate(200%)}}@keyframes skel-fade-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes form-reveal{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.smart-form-skeleton{background:var(--cc-sf-form-bg, #ffffff);border-radius:var(--cc-sf-form-border-radius, 12px);border:var(--cc-sf-form-border, none);box-shadow:var(--cc-sf-form-shadow, 0 1px 3px rgba(0, 0, 0, .06));padding:1.5rem;display:flex;flex-direction:column;gap:var(--cc-sf-form-section-gap, 24px);animation:skel-fade-in .3s ease both}.skel-block{position:relative;overflow:hidden;border-radius:6px;background:var(--cc-sf-input-disabled-bg, #F0F2F5)}.skel-block:after{content:\"\";position:absolute;inset:0;transform:translate(-100%);background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,.55) 45%,rgba(255,255,255,.75) 50%,rgba(255,255,255,.55) 55%,transparent 100%);animation:skel-wave 1.8s ease-in-out infinite;animation-delay:var(--sd, 0s)}.skel-header{display:flex;flex-direction:column;gap:10px}.skel-header .skel-title{height:26px;border-radius:8px}.skel-header .skel-desc{height:14px;border-radius:5px;opacity:.7}.skel-section{display:flex;flex-direction:column;gap:14px}.skel-section-label{height:16px;border-radius:0 5px 5px 0;border-left:var(--cc-sf-section-header-accent-width, 4px) solid var(--cc-sf-section-header-accent-color, #3B82F6);background:var(--cc-sf-input-disabled-bg, #F0F2F5);opacity:.85}.skel-fields{display:grid;gap:var(--cc-sf-grid-gap, 16px);align-items:start}.skel-fields--two{grid-template-columns:1fr 1fr}.skel-fields--three{grid-template-columns:1fr 1fr 1fr}@media(max-width:640px){.skel-fields--two,.skel-fields--three{grid-template-columns:1fr}}.skel-field{display:flex;flex-direction:column;gap:7px}.skel-field--full{grid-column:1/-1}.skel-label{height:12px;border-radius:4px;opacity:.75}.skel-input{height:42px;border-radius:8px}.skel-input--textarea{height:80px}.skel-chips{display:flex;flex-wrap:wrap;gap:8px}.skel-chip{height:32px;border-radius:20px;opacity:.8}.skel-actions{display:flex;justify-content:flex-end;align-items:center;gap:12px;padding-top:20px;border-top:var(--cc-sf-actions-border, 1px solid #E5E7EB)}.skel-btn{height:40px;border-radius:8px}.skel-btn--primary{background:color-mix(in srgb,var(--cc-sf-accent-color, #3B82F6) 18%,var(--cc-sf-input-disabled-bg, #F0F2F5))}.smart-form-container{width:100%;font-family:var(--cc-sf-font-family, \"Inter\", sans-serif)}.smart-form-wrapper{background:var(--cc-sf-form-bg, #ffffff);border-radius:var(--cc-sf-form-border-radius, 12px);border:var(--cc-sf-form-border, none);animation:form-reveal .3s ease both}.smart-form-wrapper .form-alert-feedback,.form-header{margin-bottom:1rem}.form-header .form-title{font-size:var(--cc-sf-form-title-size, 1.5rem);font-weight:var(--cc-sf-form-title-weight, 700);color:var(--cc-sf-form-title-color, #111827);margin:0 0 8px;line-height:1.25}.form-header .form-description{font-size:var(--cc-sf-form-desc-size, .875rem);color:var(--cc-sf-form-desc-color, #6B7280);margin:0}@keyframes ss-badge-pop{0%{transform:scale(.6);opacity:0}70%{transform:scale(1.12)}to{transform:scale(1);opacity:1}}@keyframes ss-pulse-ring{0%{box-shadow:0 0 #6366f173}70%{box-shadow:0 0 0 10px #6366f100}to{box-shadow:0 0 #6366f100}}@keyframes ss-panel-in{0%{opacity:0;transform:translateY(14px)}to{opacity:1;transform:translateY(0)}}@keyframes ss-connector-fill{0%{transform:scaleX(0)}to{transform:scaleX(1)}}.section-stepper-nav{position:relative;background:#fff;border-radius:16px;border:1px solid #E8EAF0;box-shadow:0 1px 3px #0000000d,0 4px 16px #6366f10f;padding:24px 28px 48px;margin-bottom:14px;overflow-x:clip;overflow-y:hidden}.section-stepper-nav:before{content:\"\";position:absolute;inset:0 0 auto;height:3px;border-radius:16px 16px 0 0;background:linear-gradient(90deg,#6366f1,#8b5cf6,#ec4899)}.section-stepper-track{display:flex;align-items:flex-start;justify-content:space-between;min-width:max-content;width:100%}.section-stepper-step{display:flex;flex-direction:column;align-items:center;position:relative;flex:1;cursor:pointer;padding:0 4px}.section-stepper-step:after{content:attr(data-tooltip);position:absolute;top:calc(100% + 8px);left:50%;transform:translate(-50%) translateY(4px);white-space:nowrap;background:#1e1b4b;color:#fff;font-size:.6875rem;font-weight:500;letter-spacing:.02em;padding:5px 11px;border-radius:6px;pointer-events:none;opacity:0;transition:opacity .2s ease,transform .2s ease;z-index:200;box-shadow:0 4px 14px #0003}.section-stepper-step:hover:after{opacity:1;transform:translate(-50%) translateY(0)}.section-stepper-step .ss-badge{width:38px;height:38px;min-width:38px;border-radius:50%;background:#f1f2f6;color:#9ca3af;border:2px solid #E5E7EB;display:flex;align-items:center;justify-content:center;font-size:.8125rem;font-weight:700;position:relative;z-index:1;transition:all .3s cubic-bezier(.34,1.56,.64,1)}.section-stepper-step .ss-badge span{line-height:1;letter-spacing:-.01em}.section-stepper-step .ss-badge svg{flex-shrink:0}.section-stepper-step .ss-badge .ss-warning-mark{font-size:1.25rem;font-weight:900;line-height:1;letter-spacing:0;color:#fff}.section-stepper-step .ss-label{margin-top:9px;font-size:.6875rem;font-weight:500;color:#b0b7c3;white-space:nowrap;text-align:center;transition:color .25s ease,font-weight .25s ease;max-width:88px;overflow:hidden;text-overflow:ellipsis;letter-spacing:.01em}.section-stepper-step .ss-connector{height:2px;background:#e8eaf0;position:absolute;top:19px;z-index:0;overflow:hidden;border-radius:2px}.section-stepper-step .ss-connector--left{left:0;right:calc(50% + 19px)}.section-stepper-step .ss-connector--right{left:calc(50% + 19px);right:0}.section-stepper-step .ss-connector:after{content:\"\";position:absolute;inset:0;background:linear-gradient(90deg,#6366f1,#8b5cf6);transform:scaleX(0);transform-origin:left;transition:transform .4s cubic-bezier(.4,0,.2,1)}.section-stepper-step.ss-active{cursor:default}.section-stepper-step.ss-active .ss-badge{background:linear-gradient(135deg,#6366f1,#8b5cf6);color:#fff;border-color:transparent;box-shadow:0 0 0 4px #6366f12e,0 4px 12px #6366f159;animation:ss-badge-pop .4s cubic-bezier(.34,1.56,.64,1) both,ss-pulse-ring 2s .4s ease-out infinite}.section-stepper-step.ss-active .ss-label{color:#4f46e5;font-weight:700}.section-stepper-step.ss-completed .ss-badge{background:linear-gradient(135deg,#10b981,#059669);color:#fff;border-color:transparent;box-shadow:0 2px 8px #10b9814d}.section-stepper-step.ss-completed .ss-label{color:#6b7280;font-weight:500}.section-stepper-step.ss-completed .ss-connector--left:after,.section-stepper-step.ss-completed .ss-connector--right:after{transform:scaleX(1)}.section-stepper-step.ss-warning .ss-badge{background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff;border-color:transparent;box-shadow:0 2px 8px #f59e0b59;animation:ss-badge-pop .35s cubic-bezier(.34,1.56,.64,1) both}.section-stepper-step.ss-warning .ss-label{color:#b45309;font-weight:600}.section-stepper-step.ss-warning .ss-connector--left:after{transform:scaleX(0)}.section-stepper-step.ss-warning .ss-connector--right:after{transform:scaleX(0)}.section-stepper-step:not(.ss-active):not(.ss-completed):not(.ss-warning):hover .ss-badge{background:#e8eaf0;color:#6366f1;border-color:#c7d2fe;transform:translateY(-2px)}.section-stepper-step:not(.ss-active):not(.ss-completed):not(.ss-warning):hover .ss-label{color:#6b7280}.ss-step-panel{animation:ss-panel-in .35s cubic-bezier(.4,0,.2,1) both}.ss-form{padding-top:0}@media(max-width:768px){.section-stepper-nav{padding:20px 16px 16px}.section-stepper-step .ss-label{font-size:.625rem;max-width:64px}.section-stepper-step .ss-badge{width:32px;height:32px;min-width:32px;font-size:.75rem}.section-stepper-step .ss-connector{top:16px}.section-stepper-step .ss-connector--left{right:calc(50% + 16px)}.section-stepper-step .ss-connector--right{left:calc(50% + 16px)}}.stepper-nav{margin-bottom:32px}.stepper-nav .stepper-steps{display:flex;gap:16px}.stepper-nav .stepper-steps.horizontal{flex-direction:row;justify-content:space-between}.stepper-nav .stepper-steps:not(.horizontal){flex-direction:column}.stepper-nav .stepper-step{display:flex;align-items:center;gap:12px;flex:1;position:relative}.stepper-nav .stepper-step:not(:last-child):after{content:\"\";position:absolute;top:calc(var(--cc-sf-step-number-size, 40px) / 2);left:calc(100% + 8px);width:calc(100% - 40px);height:2px;background:var(--cc-sf-step-connector-color, #E5E7EB);transition:background var(--cc-sf-btn-transition, .2s ease)}.stepper-nav .stepper-step.completed:after{background:var(--cc-sf-step-connector-done, #22C55E)}.stepper-nav .stepper-step .step-number{width:var(--cc-sf-step-number-size, 40px);height:var(--cc-sf-step-number-size, 40px);min-width:var(--cc-sf-step-number-size, 40px);border-radius:50%;background:var(--cc-sf-step-number-bg, #E5E7EB);color:var(--cc-sf-step-number-color, #6B7280);display:flex;align-items:center;justify-content:center;font-size:var(--cc-sf-step-number-font-size, .875rem);font-weight:var(--cc-sf-step-number-weight, 600);transition:var(--cc-sf-btn-transition, all .2s ease)}.stepper-nav .stepper-step .step-label{font-size:var(--cc-sf-step-label-size, .875rem);color:var(--cc-sf-step-label-color, #6B7280);font-weight:var(--cc-sf-step-label-weight, 500);transition:var(--cc-sf-btn-transition, all .2s ease)}.stepper-nav .stepper-step.active .step-number{background:var(--cc-sf-step-active-bg, #3B82F6);color:var(--cc-sf-step-active-color, #ffffff)}.stepper-nav .stepper-step.active .step-label{color:var(--cc-sf-step-active-label, #1D4ED8);font-weight:var(--cc-sf-step-active-label-weight, 700)}.stepper-nav .stepper-step.completed .step-number{background:var(--cc-sf-step-done-bg, #22C55E);color:var(--cc-sf-step-done-color, #ffffff)}.smart-form{display:flex;flex-direction:column;gap:var(--cc-sf-form-section-gap, 24px)}.smart-form>*{margin-bottom:0!important}.form-actions{display:flex;justify-content:space-between;align-items:center;gap:var(--cc-sf-actions-gap, 12px);padding:var(--cc-sf-actions-padding, 20px 10px 0);margin-top:8px}.form-actions .action-group{display:flex;align-items:center;gap:var(--cc-sf-actions-gap, 12px)}.form-actions .action-group--left{justify-content:flex-start}.form-actions .action-group--right{justify-content:flex-end;margin-left:auto}\n"] }]
|
|
5386
|
+
args: [{ selector: 'lib-smart-form', providers: [SmartFormController], standalone: false, template: "<div class=\"smart-form-container\">\r\n\r\n <!-- \u2550\u2550 Skeleton loader \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div class=\"smart-form-skeleton\" *ngIf=\"!isFormReady\" aria-hidden=\"true\">\r\n\r\n <!-- Title + description -->\r\n <div class=\"skel-header\">\r\n <div class=\"skel-block skel-title\" style=\"--sd: 0s\"></div>\r\n <div class=\"skel-block skel-desc\" style=\"--sd: 0.07s\"></div>\r\n </div>\r\n\r\n <!-- Section 1 \u2014 two full-width fields -->\r\n <div class=\"skel-section\">\r\n <div class=\"skel-block skel-section-label\" style=\"--sd: 0.1s; width: 32%\"></div>\r\n <div class=\"skel-fields skel-fields--two\">\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.13s; width: 55%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.15s\"></div>\r\n </div>\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.16s; width: 45%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.18s\"></div>\r\n </div>\r\n <div class=\"skel-field skel-field--full\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.2s; width: 38%\"></div>\r\n <div class=\"skel-block skel-input skel-input--textarea\" style=\"--sd: 0.22s\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Section 2 \u2014 three columns -->\r\n <div class=\"skel-section\">\r\n <div class=\"skel-block skel-section-label\" style=\"--sd: 0.25s; width: 24%\"></div>\r\n <div class=\"skel-fields skel-fields--three\">\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.28s; width: 60%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.30s\"></div>\r\n </div>\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.31s; width: 50%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.33s\"></div>\r\n </div>\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.34s; width: 65%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.36s\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Section 3 \u2014 two columns + chip row -->\r\n <div class=\"skel-section\">\r\n <div class=\"skel-block skel-section-label\" style=\"--sd: 0.38s; width: 28%\"></div>\r\n <div class=\"skel-fields skel-fields--two\">\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.40s; width: 48%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.42s\"></div>\r\n </div>\r\n <div class=\"skel-field\">\r\n <div class=\"skel-block skel-label\" style=\"--sd: 0.43s; width: 55%\"></div>\r\n <div class=\"skel-block skel-input\" style=\"--sd: 0.45s\"></div>\r\n </div>\r\n </div>\r\n <div class=\"skel-chips\">\r\n <div class=\"skel-block skel-chip\" style=\"--sd: 0.47s; width: 72px\"></div>\r\n <div class=\"skel-block skel-chip\" style=\"--sd: 0.49s; width: 96px\"></div>\r\n <div class=\"skel-block skel-chip\" style=\"--sd: 0.51s; width: 80px\"></div>\r\n <div class=\"skel-block skel-chip\" style=\"--sd: 0.53s; width: 64px\"></div>\r\n </div>\r\n </div>\r\n\r\n <!-- Action bar -->\r\n <div class=\"skel-actions\">\r\n <div class=\"skel-block skel-btn\" style=\"--sd: 0.55s; width: 88px\"></div>\r\n <div class=\"skel-block skel-btn skel-btn--primary\" style=\"--sd: 0.58s; width: 120px\"></div>\r\n </div>\r\n </div>\r\n\r\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 SECTION STEPPER layout \u2014 stepper nav card + per-step form panels.\r\n Rendered when formSchema.sectionStepper === true.\r\n The nav and each step section are separate cards (no outer wrapper card).\r\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <ng-container *ngIf=\"formSchema && isFormReady && isSectionStepper\">\r\n\r\n <!-- Horizontal stepper nav card \u2014 hidden for a single step (nothing to step between) -->\r\n <div class=\"section-stepper-nav\" *ngIf=\"sectionSteps.length > 1\">\r\n <div class=\"section-stepper-track\">\r\n <div\r\n *ngFor=\"let step of sectionSteps; let i = index\"\r\n class=\"section-stepper-step\"\r\n [class.ss-active]=\"i === currentSectionStep\"\r\n [class.ss-completed]=\"i < currentSectionStep && stepValidationStates[i] !== 'warning'\"\r\n [class.ss-warning]=\"i < currentSectionStep && stepValidationStates[i] === 'warning'\"\r\n [attr.data-tooltip]=\"step.sectionConfig?.label || 'Step ' + (i + 1)\"\r\n (click)=\"goToSectionStep(i)\">\r\n\r\n <!-- Connector line \u2014 left side (hidden for first step) -->\r\n <div class=\"ss-connector ss-connector--left\" *ngIf=\"i > 0\"></div>\r\n\r\n <!-- Step badge -->\r\n <div class=\"ss-badge\">\r\n <!-- Green checkmark: visited and valid -->\r\n <svg *ngIf=\"i < currentSectionStep && stepValidationStates[i] !== 'warning'\"\r\n width=\"13\" height=\"13\" viewBox=\"0 0 24 24\"\r\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\"\r\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <polyline points=\"20 6 9 17 4 12\"></polyline>\r\n </svg>\r\n <!-- Warning: bold ! is the most visible option inside a 38px circle badge -->\r\n <span *ngIf=\"i < currentSectionStep && stepValidationStates[i] === 'warning'\"\r\n class=\"ss-warning-mark\">!</span>\r\n <!-- Number: current or future step -->\r\n <span *ngIf=\"i >= currentSectionStep\">{{ i + 1 }}</span>\r\n </div>\r\n\r\n <!-- Step label (truncated; full text revealed by CSS tooltip) -->\r\n <div class=\"ss-label\" [title]=\"step.sectionConfig?.label || 'Step ' + (i + 1)\">\r\n {{ step.sectionConfig?.label || 'Step ' + (i + 1) }}\r\n </div>\r\n\r\n <!-- Connector line \u2014 right side (hidden for last step) -->\r\n <div class=\"ss-connector ss-connector--right\" *ngIf=\"i < sectionSteps.length - 1\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Step panels \u2014 all mounted, only active one is visible (preserves form data) -->\r\n <form [formGroup]=\"formGroup\" class=\"smart-form ss-form\">\r\n <div\r\n *ngFor=\"let step of sectionSteps; let i = index\"\r\n class=\"ss-step-panel\"\r\n [style.display]=\"i === currentSectionStep ? 'block' : 'none'\">\r\n <lib-form-section\r\n [config]=\"getSectionStepConfig(step)\"\r\n [controller]=\"controller\"\r\n [formGroup]=\"formGroup\">\r\n </lib-form-section>\r\n </div>\r\n </form>\r\n\r\n <!-- \u2500\u2500 Action bar: Prev / custom action buttons / Next \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"form-actions\"\r\n *ngIf=\"!readOnly && formSchema.showActions !== false && actionBarConfig?.buttons?.length\">\r\n\r\n <!-- LEFT GROUP: Previous button + left-aligned custom buttons -->\r\n <div class=\"action-group action-group--left\">\r\n <lib-button\r\n *ngIf=\"!isSectionStepFirst\"\r\n variant=\"outline\"\r\n (click)=\"navigateToPrevious()\">\r\n {{ previousLabel }}\r\n </lib-button>\r\n <ng-container *ngFor=\"let btn of getButtonsForAlignment('left')\">\r\n <lib-button\r\n *ngIf=\"!btn.showOnLastStepOnly || isSectionStepLast\"\r\n [variant]=\"$any(btn.variant) || 'outline'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- RIGHT GROUP: right-aligned custom buttons + Next button -->\r\n <div class=\"action-group action-group--right\">\r\n <ng-container *ngFor=\"let btn of getButtonsForAlignment('right')\">\r\n <lib-button\r\n *ngIf=\"!btn.showOnLastStepOnly || isSectionStepLast\"\r\n [variant]=\"$any(btn.variant) || 'primary'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </ng-container>\r\n <lib-button\r\n *ngIf=\"!isSectionStepLast\"\r\n variant=\"primary\"\r\n (click)=\"navigateToNext()\">\r\n {{ nextLabel }}\r\n </lib-button>\r\n </div>\r\n\r\n </div>\r\n\r\n </ng-container>\r\n\r\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 Regular card wrapper \u2014 used for SECTION and STEPPER form types.\r\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n <div class=\"smart-form-wrapper\" *ngIf=\"formSchema && isFormReady && !isSectionStepper\"\r\n [class.smart-form-wrapper--readonly]=\"readOnly\">\r\n\r\n <!-- Form Header -->\r\n <div class=\"form-header\" *ngIf=\"formSchema.showTitle !== false\">\r\n <h2 class=\"form-title\">{{ formSchema.label }}</h2>\r\n <p class=\"form-description\" *ngIf=\"formSchema.description\">{{ formSchema.description }}</p>\r\n </div>\r\n\r\n <!-- STEPPER type nav -->\r\n <div class=\"stepper-nav\" *ngIf=\"isStepper && formSchema.stepperConfig?.showStep !== false\">\r\n <div class=\"stepper-steps\" [class.horizontal]=\"formSchema.stepperConfig?.isHorizontal !== false\">\r\n <div *ngFor=\"let step of fieldList; let i = index\" class=\"stepper-step\" [class.active]=\"i === currentStep\"\r\n [class.completed]=\"i < currentStep\">\r\n <div class=\"step-number\">{{ i + 1 }}</div>\r\n <div class=\"step-label\">{{ step.sectionConfig?.label || 'Step ' + (i + 1) }}</div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Form Content -->\r\n <form [formGroup]=\"formGroup\" class=\"smart-form\">\r\n <!-- Regular SECTION form -->\r\n <div *ngIf=\"!isStepper && formSchema.sectionConfig && formSchema.sectionConfig.isEnabled !== false\" class=\"form-section\">\r\n <lib-form-section [config]=\"formSchema.sectionConfig\" [controller]=\"controller\" [formGroup]=\"formGroup\">\r\n </lib-form-section>\r\n </div>\r\n\r\n <!-- STEPPER type form -->\r\n <div *ngIf=\"isStepper && currentStepConfig && currentStepConfig.sectionConfig?.isEnabled !== false\" class=\"form-stepper\">\r\n <lib-form-section [config]=\"currentStepConfig.sectionConfig!\" [controller]=\"controller\" [formGroup]=\"formGroup\">\r\n </lib-form-section>\r\n </div>\r\n </form>\r\n\r\n <!-- \u2500\u2500 Form Actions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"form-actions\" *ngIf=\"formSchema.showActions !== false && !readOnly && actionBarConfig?.buttons?.length\">\r\n\r\n <!-- LEFT GROUP -->\r\n <div class=\"action-group action-group--left\">\r\n <lib-button\r\n *ngFor=\"let btn of getButtonsForAlignment('left')\"\r\n [variant]=\"$any(btn.variant) || 'outline'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </div>\r\n\r\n <!-- RIGHT GROUP -->\r\n <div class=\"action-group action-group--right\">\r\n <lib-button\r\n *ngFor=\"let btn of getButtonsForAlignment('right')\"\r\n [variant]=\"$any(btn.variant) || 'primary'\"\r\n [disabled]=\"isButtonDisabled(btn)\"\r\n (click)=\"handleButtonClick(btn)\">\r\n {{ getButtonLabel(btn) }}\r\n </lib-button>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n</div>\r\n", styles: ["@keyframes skel-wave{0%{transform:translate(-100%)}to{transform:translate(200%)}}@keyframes skel-fade-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes form-reveal{0%{opacity:0;margin-top:8px}to{opacity:1;margin-top:0}}.smart-form-skeleton{background:var(--cc-sf-form-bg, #ffffff);border-radius:var(--cc-sf-form-border-radius, 12px);border:var(--cc-sf-form-border, none);box-shadow:var(--cc-sf-form-shadow, 0 1px 3px rgba(0, 0, 0, .06));padding:1.5rem;display:flex;flex-direction:column;gap:var(--cc-sf-form-section-gap, 24px);animation:skel-fade-in .3s ease both}.skel-block{position:relative;overflow:hidden;border-radius:6px;background:var(--cc-sf-input-disabled-bg, #F0F2F5)}.skel-block:after{content:\"\";position:absolute;inset:0;transform:translate(-100%);background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,.55) 45%,rgba(255,255,255,.75) 50%,rgba(255,255,255,.55) 55%,transparent 100%);animation:skel-wave 1.8s ease-in-out infinite;animation-delay:var(--sd, 0s)}.skel-header{display:flex;flex-direction:column;gap:10px}.skel-header .skel-title{height:26px;border-radius:8px}.skel-header .skel-desc{height:14px;border-radius:5px;opacity:.7}.skel-section{display:flex;flex-direction:column;gap:14px}.skel-section-label{height:16px;border-radius:0 5px 5px 0;border-left:var(--cc-sf-section-header-accent-width, 4px) solid var(--cc-sf-section-header-accent-color, #3B82F6);background:var(--cc-sf-input-disabled-bg, #F0F2F5);opacity:.85}.skel-fields{display:grid;gap:var(--cc-sf-grid-gap, 16px);align-items:start}.skel-fields--two{grid-template-columns:1fr 1fr}.skel-fields--three{grid-template-columns:1fr 1fr 1fr}@media(max-width:640px){.skel-fields--two,.skel-fields--three{grid-template-columns:1fr}}.skel-field{display:flex;flex-direction:column;gap:7px}.skel-field--full{grid-column:1/-1}.skel-label{height:12px;border-radius:4px;opacity:.75}.skel-input{height:42px;border-radius:8px}.skel-input--textarea{height:80px}.skel-chips{display:flex;flex-wrap:wrap;gap:8px}.skel-chip{height:32px;border-radius:20px;opacity:.8}.skel-actions{display:flex;justify-content:flex-end;align-items:center;gap:12px;padding-top:20px;border-top:var(--cc-sf-actions-border, 1px solid #E5E7EB)}.skel-btn{height:40px;border-radius:8px}.skel-btn--primary{background:color-mix(in srgb,var(--cc-sf-accent-color, #3B82F6) 18%,var(--cc-sf-input-disabled-bg, #F0F2F5))}.smart-form-container{width:100%;font-family:var(--cc-sf-font-family, \"Inter\", sans-serif)}.smart-form-wrapper{background:var(--cc-sf-form-bg, #ffffff);border-radius:var(--cc-sf-form-border-radius, 12px);border:var(--cc-sf-form-border, none);animation:form-reveal .3s ease both}.smart-form-wrapper .form-alert-feedback,.form-header{margin-bottom:1rem}.form-header .form-title{font-size:var(--cc-sf-form-title-size, 1.5rem);font-weight:var(--cc-sf-form-title-weight, 700);color:var(--cc-sf-form-title-color, #111827);margin:0 0 8px;line-height:1.25}.form-header .form-description{font-size:var(--cc-sf-form-desc-size, .875rem);color:var(--cc-sf-form-desc-color, #6B7280);margin:0}@keyframes ss-badge-pop{0%{transform:scale(.6);opacity:0}70%{transform:scale(1.12)}to{transform:scale(1);opacity:1}}@keyframes ss-pulse-ring{0%{box-shadow:0 0 #6366f173}70%{box-shadow:0 0 0 10px #6366f100}to{box-shadow:0 0 #6366f100}}@keyframes ss-panel-in{0%{opacity:0;transform:translateY(14px)}to{opacity:1;transform:translateY(0)}}@keyframes ss-connector-fill{0%{transform:scaleX(0)}to{transform:scaleX(1)}}.section-stepper-nav{position:relative;background:#fff;border-radius:16px;border:1px solid #E8EAF0;box-shadow:0 1px 3px #0000000d,0 4px 16px #6366f10f;padding:24px 28px 48px;margin-bottom:14px;overflow-x:clip;overflow-y:hidden}.section-stepper-nav:before{content:\"\";position:absolute;inset:0 0 auto;height:3px;border-radius:16px 16px 0 0;background:linear-gradient(90deg,#6366f1,#8b5cf6,#ec4899)}.section-stepper-track{display:flex;align-items:flex-start;justify-content:space-between;min-width:max-content;width:100%}.section-stepper-step{display:flex;flex-direction:column;align-items:center;position:relative;flex:1;cursor:pointer;padding:0 4px}.section-stepper-step:after{content:attr(data-tooltip);position:absolute;top:calc(100% + 8px);left:50%;transform:translate(-50%) translateY(4px);white-space:nowrap;background:#1e1b4b;color:#fff;font-size:.6875rem;font-weight:500;letter-spacing:.02em;padding:5px 11px;border-radius:6px;pointer-events:none;opacity:0;transition:opacity .2s ease,transform .2s ease;z-index:200;box-shadow:0 4px 14px #0003}.section-stepper-step:hover:after{opacity:1;transform:translate(-50%) translateY(0)}.section-stepper-step .ss-badge{width:38px;height:38px;min-width:38px;border-radius:50%;background:#f1f2f6;color:#9ca3af;border:2px solid #E5E7EB;display:flex;align-items:center;justify-content:center;font-size:.8125rem;font-weight:700;position:relative;z-index:1;transition:all .3s cubic-bezier(.34,1.56,.64,1)}.section-stepper-step .ss-badge span{line-height:1;letter-spacing:-.01em}.section-stepper-step .ss-badge svg{flex-shrink:0}.section-stepper-step .ss-badge .ss-warning-mark{font-size:1.25rem;font-weight:900;line-height:1;letter-spacing:0;color:#fff}.section-stepper-step .ss-label{margin-top:9px;font-size:.6875rem;font-weight:500;color:#b0b7c3;white-space:nowrap;text-align:center;transition:color .25s ease,font-weight .25s ease;max-width:88px;overflow:hidden;text-overflow:ellipsis;letter-spacing:.01em}.section-stepper-step .ss-connector{height:2px;background:#e8eaf0;position:absolute;top:19px;z-index:0;overflow:hidden;border-radius:2px}.section-stepper-step .ss-connector--left{left:0;right:calc(50% + 19px)}.section-stepper-step .ss-connector--right{left:calc(50% + 19px);right:0}.section-stepper-step .ss-connector:after{content:\"\";position:absolute;inset:0;background:linear-gradient(90deg,#6366f1,#8b5cf6);transform:scaleX(0);transform-origin:left;transition:transform .4s cubic-bezier(.4,0,.2,1)}.section-stepper-step.ss-active{cursor:default}.section-stepper-step.ss-active .ss-badge{background:linear-gradient(135deg,#6366f1,#8b5cf6);color:#fff;border-color:transparent;box-shadow:0 0 0 4px #6366f12e,0 4px 12px #6366f159;animation:ss-badge-pop .4s cubic-bezier(.34,1.56,.64,1) both,ss-pulse-ring 2s .4s ease-out infinite}.section-stepper-step.ss-active .ss-label{color:#4f46e5;font-weight:700}.section-stepper-step.ss-completed .ss-badge{background:linear-gradient(135deg,#10b981,#059669);color:#fff;border-color:transparent;box-shadow:0 2px 8px #10b9814d}.section-stepper-step.ss-completed .ss-label{color:#6b7280;font-weight:500}.section-stepper-step.ss-completed .ss-connector--left:after,.section-stepper-step.ss-completed .ss-connector--right:after{transform:scaleX(1)}.section-stepper-step.ss-warning .ss-badge{background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff;border-color:transparent;box-shadow:0 2px 8px #f59e0b59;animation:ss-badge-pop .35s cubic-bezier(.34,1.56,.64,1) both}.section-stepper-step.ss-warning .ss-label{color:#b45309;font-weight:600}.section-stepper-step.ss-warning .ss-connector--left:after{transform:scaleX(0)}.section-stepper-step.ss-warning .ss-connector--right:after{transform:scaleX(0)}.section-stepper-step:not(.ss-active):not(.ss-completed):not(.ss-warning):hover .ss-badge{background:#e8eaf0;color:#6366f1;border-color:#c7d2fe;transform:translateY(-2px)}.section-stepper-step:not(.ss-active):not(.ss-completed):not(.ss-warning):hover .ss-label{color:#6b7280}.ss-step-panel{animation:ss-panel-in .35s cubic-bezier(.4,0,.2,1) both}.ss-form{padding-top:0}@media(max-width:768px){.section-stepper-nav{padding:20px 16px 16px}.section-stepper-step .ss-label{font-size:.625rem;max-width:64px}.section-stepper-step .ss-badge{width:32px;height:32px;min-width:32px;font-size:.75rem}.section-stepper-step .ss-connector{top:16px}.section-stepper-step .ss-connector--left{right:calc(50% + 16px)}.section-stepper-step .ss-connector--right{left:calc(50% + 16px)}}.stepper-nav{margin-bottom:32px}.stepper-nav .stepper-steps{display:flex;gap:16px}.stepper-nav .stepper-steps.horizontal{flex-direction:row;justify-content:space-between}.stepper-nav .stepper-steps:not(.horizontal){flex-direction:column}.stepper-nav .stepper-step{display:flex;align-items:center;gap:12px;flex:1;position:relative}.stepper-nav .stepper-step:not(:last-child):after{content:\"\";position:absolute;top:calc(var(--cc-sf-step-number-size, 40px) / 2);left:calc(100% + 8px);width:calc(100% - 40px);height:2px;background:var(--cc-sf-step-connector-color, #E5E7EB);transition:background var(--cc-sf-btn-transition, .2s ease)}.stepper-nav .stepper-step.completed:after{background:var(--cc-sf-step-connector-done, #22C55E)}.stepper-nav .stepper-step .step-number{width:var(--cc-sf-step-number-size, 40px);height:var(--cc-sf-step-number-size, 40px);min-width:var(--cc-sf-step-number-size, 40px);border-radius:50%;background:var(--cc-sf-step-number-bg, #E5E7EB);color:var(--cc-sf-step-number-color, #6B7280);display:flex;align-items:center;justify-content:center;font-size:var(--cc-sf-step-number-font-size, .875rem);font-weight:var(--cc-sf-step-number-weight, 600);transition:var(--cc-sf-btn-transition, all .2s ease)}.stepper-nav .stepper-step .step-label{font-size:var(--cc-sf-step-label-size, .875rem);color:var(--cc-sf-step-label-color, #6B7280);font-weight:var(--cc-sf-step-label-weight, 500);transition:var(--cc-sf-btn-transition, all .2s ease)}.stepper-nav .stepper-step.active .step-number{background:var(--cc-sf-step-active-bg, #3B82F6);color:var(--cc-sf-step-active-color, #ffffff)}.stepper-nav .stepper-step.active .step-label{color:var(--cc-sf-step-active-label, #1D4ED8);font-weight:var(--cc-sf-step-active-label-weight, 700)}.stepper-nav .stepper-step.completed .step-number{background:var(--cc-sf-step-done-bg, #22C55E);color:var(--cc-sf-step-done-color, #ffffff)}.smart-form{display:flex;flex-direction:column;gap:var(--cc-sf-form-section-gap, 24px)}.smart-form>*{margin-bottom:0!important}.form-actions{display:flex;justify-content:space-between;align-items:center;gap:var(--cc-sf-actions-gap, 12px);padding:var(--cc-sf-actions-padding, 20px 10px 0);margin-top:8px}.form-actions .action-group{display:flex;align-items:center;gap:var(--cc-sf-actions-gap, 12px)}.form-actions .action-group--left{justify-content:flex-start}.form-actions .action-group--right{justify-content:flex-end;margin-left:auto}\n"] }]
|
|
4368
5387
|
}], ctorParameters: () => [{ type: i1$2.FormBuilder }, { type: SmartFormController }, { type: ExpressionService }, { type: i3.HttpClient }, { type: SnackbarService }, { type: i1$4.Router }, { type: i0.ChangeDetectorRef }], propDecorators: { formFieldComponents: [{
|
|
4369
5388
|
type: ViewChildren,
|
|
4370
5389
|
args: [FormFieldComponent]
|
|
@@ -4551,54 +5570,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
4551
5570
|
}]
|
|
4552
5571
|
}] });
|
|
4553
5572
|
|
|
4554
|
-
class SmartFormModule {
|
|
4555
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
4556
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: SmartFormModule, declarations: [SmartFormComponent,
|
|
4557
|
-
FormSectionComponent,
|
|
4558
|
-
FormFieldComponent,
|
|
4559
|
-
TrustedUrlPipe], imports: [CommonModule,
|
|
4560
|
-
ReactiveFormsModule,
|
|
4561
|
-
FormsModule,
|
|
4562
|
-
MaterialModule,
|
|
4563
|
-
ButtonModule,
|
|
4564
|
-
AlertModule, i11.QuillModule], exports: [SmartFormComponent] });
|
|
4565
|
-
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormModule, providers: [
|
|
4566
|
-
ExpressionService
|
|
4567
|
-
], imports: [CommonModule,
|
|
4568
|
-
ReactiveFormsModule,
|
|
4569
|
-
FormsModule,
|
|
4570
|
-
MaterialModule,
|
|
4571
|
-
ButtonModule,
|
|
4572
|
-
AlertModule,
|
|
4573
|
-
QuillModule.forRoot()] });
|
|
4574
|
-
}
|
|
4575
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormModule, decorators: [{
|
|
4576
|
-
type: NgModule,
|
|
4577
|
-
args: [{
|
|
4578
|
-
declarations: [
|
|
4579
|
-
SmartFormComponent,
|
|
4580
|
-
FormSectionComponent,
|
|
4581
|
-
FormFieldComponent,
|
|
4582
|
-
TrustedUrlPipe
|
|
4583
|
-
],
|
|
4584
|
-
imports: [
|
|
4585
|
-
CommonModule,
|
|
4586
|
-
ReactiveFormsModule,
|
|
4587
|
-
FormsModule,
|
|
4588
|
-
MaterialModule,
|
|
4589
|
-
ButtonModule,
|
|
4590
|
-
AlertModule,
|
|
4591
|
-
QuillModule.forRoot()
|
|
4592
|
-
],
|
|
4593
|
-
exports: [
|
|
4594
|
-
SmartFormComponent
|
|
4595
|
-
],
|
|
4596
|
-
providers: [
|
|
4597
|
-
ExpressionService
|
|
4598
|
-
]
|
|
4599
|
-
}]
|
|
4600
|
-
}] });
|
|
4601
|
-
|
|
4602
5573
|
class InputComponent {
|
|
4603
5574
|
config;
|
|
4604
5575
|
labels;
|
|
@@ -6630,7 +7601,8 @@ class FormComponentsModule {
|
|
|
6630
7601
|
RadioComponent,
|
|
6631
7602
|
ToggleComponent,
|
|
6632
7603
|
DatepickerComponent,
|
|
6633
|
-
SearchComponent
|
|
7604
|
+
SearchComponent,
|
|
7605
|
+
ClickOutsideDirective] });
|
|
6634
7606
|
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FormComponentsModule, imports: [CommonModule,
|
|
6635
7607
|
ReactiveFormsModule,
|
|
6636
7608
|
FormsModule,
|
|
@@ -6662,7 +7634,88 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
6662
7634
|
RadioComponent,
|
|
6663
7635
|
ToggleComponent,
|
|
6664
7636
|
DatepickerComponent,
|
|
6665
|
-
SearchComponent
|
|
7637
|
+
SearchComponent,
|
|
7638
|
+
ClickOutsideDirective
|
|
7639
|
+
]
|
|
7640
|
+
}]
|
|
7641
|
+
}] });
|
|
7642
|
+
|
|
7643
|
+
class TimePickerModule {
|
|
7644
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TimePickerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
7645
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: TimePickerModule, declarations: [TimePickerComponent,
|
|
7646
|
+
TimeWheelPanelComponent], imports: [CommonModule,
|
|
7647
|
+
FormsModule,
|
|
7648
|
+
FormComponentsModule], exports: [TimePickerComponent,
|
|
7649
|
+
TimeWheelPanelComponent] });
|
|
7650
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TimePickerModule, imports: [CommonModule,
|
|
7651
|
+
FormsModule,
|
|
7652
|
+
FormComponentsModule] });
|
|
7653
|
+
}
|
|
7654
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TimePickerModule, decorators: [{
|
|
7655
|
+
type: NgModule,
|
|
7656
|
+
args: [{
|
|
7657
|
+
declarations: [
|
|
7658
|
+
TimePickerComponent,
|
|
7659
|
+
TimeWheelPanelComponent
|
|
7660
|
+
],
|
|
7661
|
+
imports: [
|
|
7662
|
+
CommonModule,
|
|
7663
|
+
FormsModule,
|
|
7664
|
+
FormComponentsModule
|
|
7665
|
+
],
|
|
7666
|
+
exports: [
|
|
7667
|
+
TimePickerComponent,
|
|
7668
|
+
TimeWheelPanelComponent
|
|
7669
|
+
]
|
|
7670
|
+
}]
|
|
7671
|
+
}] });
|
|
7672
|
+
|
|
7673
|
+
class SmartFormModule {
|
|
7674
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
7675
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: SmartFormModule, declarations: [SmartFormComponent,
|
|
7676
|
+
FormSectionComponent,
|
|
7677
|
+
FormFieldComponent,
|
|
7678
|
+
TrustedUrlPipe], imports: [CommonModule,
|
|
7679
|
+
ReactiveFormsModule,
|
|
7680
|
+
FormsModule,
|
|
7681
|
+
MaterialModule,
|
|
7682
|
+
ButtonModule,
|
|
7683
|
+
AlertModule, i11.QuillModule, TimePickerModule], exports: [SmartFormComponent] });
|
|
7684
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormModule, providers: [
|
|
7685
|
+
ExpressionService
|
|
7686
|
+
], imports: [CommonModule,
|
|
7687
|
+
ReactiveFormsModule,
|
|
7688
|
+
FormsModule,
|
|
7689
|
+
MaterialModule,
|
|
7690
|
+
ButtonModule,
|
|
7691
|
+
AlertModule,
|
|
7692
|
+
QuillModule.forRoot(),
|
|
7693
|
+
TimePickerModule] });
|
|
7694
|
+
}
|
|
7695
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormModule, decorators: [{
|
|
7696
|
+
type: NgModule,
|
|
7697
|
+
args: [{
|
|
7698
|
+
declarations: [
|
|
7699
|
+
SmartFormComponent,
|
|
7700
|
+
FormSectionComponent,
|
|
7701
|
+
FormFieldComponent,
|
|
7702
|
+
TrustedUrlPipe
|
|
7703
|
+
],
|
|
7704
|
+
imports: [
|
|
7705
|
+
CommonModule,
|
|
7706
|
+
ReactiveFormsModule,
|
|
7707
|
+
FormsModule,
|
|
7708
|
+
MaterialModule,
|
|
7709
|
+
ButtonModule,
|
|
7710
|
+
AlertModule,
|
|
7711
|
+
QuillModule.forRoot(),
|
|
7712
|
+
TimePickerModule
|
|
7713
|
+
],
|
|
7714
|
+
exports: [
|
|
7715
|
+
SmartFormComponent
|
|
7716
|
+
],
|
|
7717
|
+
providers: [
|
|
7718
|
+
ExpressionService
|
|
6666
7719
|
]
|
|
6667
7720
|
}]
|
|
6668
7721
|
}] });
|
|
@@ -8947,13 +10000,22 @@ class ConfigSchemaTreeService {
|
|
|
8947
10000
|
fields.push(this._buildField(child));
|
|
8948
10001
|
}
|
|
8949
10002
|
}
|
|
8950
|
-
|
|
10003
|
+
// A section is locked (toggle replaced by a lock, can never be hidden) when its
|
|
10004
|
+
// own field is locked OR any descendant field/subsection is locked — hiding the
|
|
10005
|
+
// section would otherwise hide a field that must always stay visible. Subsection
|
|
10006
|
+
// locks are already transitive, so checking direct children is enough.
|
|
10007
|
+
const hasLockedDescendant = fields.some(f => f.lockVisibility) || subsections.some(s => s.lockVisibility);
|
|
10008
|
+
const lockVisibility = field.lockVisibility === true || hasLockedDescendant;
|
|
10009
|
+
// Locked sections are always visible; otherwise honour the schema's flags.
|
|
10010
|
+
const visible = lockVisibility
|
|
10011
|
+
? true
|
|
10012
|
+
: (field.isEnabled ?? field.visible ?? true) && field.disabled !== true;
|
|
8951
10013
|
return {
|
|
8952
10014
|
label: field.sectionConfig?.label || field.label || field.name || 'Section',
|
|
8953
10015
|
fieldConfig: field,
|
|
8954
10016
|
sectionConfig: field.sectionConfig ?? null,
|
|
8955
10017
|
visible,
|
|
8956
|
-
lockVisibility
|
|
10018
|
+
lockVisibility,
|
|
8957
10019
|
expanded: true,
|
|
8958
10020
|
fields,
|
|
8959
10021
|
subsections,
|
|
@@ -13007,6 +14069,8 @@ class SharedUiModule {
|
|
|
13007
14069
|
SmartFormModule,
|
|
13008
14070
|
SideNavModule,
|
|
13009
14071
|
FormBuilderModule,
|
|
14072
|
+
FilterTableSelectorModule,
|
|
14073
|
+
TimePickerModule,
|
|
13010
14074
|
FormFieldConfigurationModule,
|
|
13011
14075
|
TableColumnConfigurationModule,
|
|
13012
14076
|
FilterTableSelectorModule], exports: [MaterialModule,
|
|
@@ -13017,6 +14081,8 @@ class SharedUiModule {
|
|
|
13017
14081
|
SmartFormModule,
|
|
13018
14082
|
SideNavModule,
|
|
13019
14083
|
FormBuilderModule,
|
|
14084
|
+
FilterTableSelectorModule,
|
|
14085
|
+
TimePickerModule,
|
|
13020
14086
|
FormFieldConfigurationModule,
|
|
13021
14087
|
TableColumnConfigurationModule,
|
|
13022
14088
|
FilterTableSelectorModule] });
|
|
@@ -13029,6 +14095,8 @@ class SharedUiModule {
|
|
|
13029
14095
|
SmartFormModule,
|
|
13030
14096
|
SideNavModule,
|
|
13031
14097
|
FormBuilderModule,
|
|
14098
|
+
FilterTableSelectorModule,
|
|
14099
|
+
TimePickerModule,
|
|
13032
14100
|
FormFieldConfigurationModule,
|
|
13033
14101
|
TableColumnConfigurationModule,
|
|
13034
14102
|
FilterTableSelectorModule, MaterialModule,
|
|
@@ -13039,6 +14107,8 @@ class SharedUiModule {
|
|
|
13039
14107
|
SmartFormModule,
|
|
13040
14108
|
SideNavModule,
|
|
13041
14109
|
FormBuilderModule,
|
|
14110
|
+
FilterTableSelectorModule,
|
|
14111
|
+
TimePickerModule,
|
|
13042
14112
|
FormFieldConfigurationModule,
|
|
13043
14113
|
TableColumnConfigurationModule,
|
|
13044
14114
|
FilterTableSelectorModule] });
|
|
@@ -13057,6 +14127,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
13057
14127
|
SmartFormModule,
|
|
13058
14128
|
SideNavModule,
|
|
13059
14129
|
FormBuilderModule,
|
|
14130
|
+
FilterTableSelectorModule,
|
|
14131
|
+
TimePickerModule,
|
|
13060
14132
|
FormFieldConfigurationModule,
|
|
13061
14133
|
TableColumnConfigurationModule,
|
|
13062
14134
|
FilterTableSelectorModule
|
|
@@ -13070,6 +14142,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
13070
14142
|
SmartFormModule,
|
|
13071
14143
|
SideNavModule,
|
|
13072
14144
|
FormBuilderModule,
|
|
14145
|
+
FilterTableSelectorModule,
|
|
14146
|
+
TimePickerModule,
|
|
13073
14147
|
FormFieldConfigurationModule,
|
|
13074
14148
|
TableColumnConfigurationModule,
|
|
13075
14149
|
FilterTableSelectorModule
|
|
@@ -14819,5 +15893,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
14819
15893
|
* Generated bundle index. Do not edit.
|
|
14820
15894
|
*/
|
|
14821
15895
|
|
|
14822
|
-
export { AlertComponent, AlertModule, ButtonComponent, ButtonDropdownComponent, ButtonDropdownModule, ButtonModule, CheckboxComponent, ConfigColumnNodeComponent, ConfigFieldNodeComponent, ConfigSchemaTreeService, ConfigSectionNodeComponent, ConfigurableFormComponent, configurableForm_examples as ConfigurableFormExamples, ConfigurableFormModule, ConfirmationModalComponent, ConfirmationModalModule, DEFAULT_ITEMS_PER_PAGE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_SIDE_NAV_TOOLTIP_POSITION, DatepickerComponent, DropdownComponent, ExpressionService, FieldConfigurationService, FieldConfiguratorComponent, FieldSelectionComponent, FilterComponent, FilterModule, FilterSidebarComponent, FilterSidebarModule, FilterTableSelectorComponent, FilterTableSelectorModule, FormBuilderModule, FormComponentsModule, FormFieldConfigurationComponent, FormFieldConfigurationModule, InputComponent, MaterialModule, NAV_ORIENTATION_DEFAULT, NAV_VARIANT_DEFAULT, NavComponent, NavModule, PAGINATION_THEME_DARK, PAGINATION_THEME_DEFAULT, PaginationComponent, PaginationModule, RadioComponent, SearchComponent, SharedUiModule, SideNavComponent, SideNavModule, SmartFormComponent, SmartFormController, smartForm_examples as SmartFormExamples, SmartFormModule, SmartTableComponent, SmartTableModule, SnackbarComponent, SnackbarModule, SnackbarService, StringUtils, SummaryCardComponent, SummaryCardModule, TableColumnConfigurationComponent, TableColumnConfigurationModule, TableColumnConfigurationService, ToggleComponent, ValidationUtils, appendBaseUrlRecursively, clearLocalStorage, clearSessionStorage, getLocalStorageItem, getSessionStorageItem, removeLocalStorageItem, removeSessionStorageItem, setLocalStorageItem, setSessionStorageItem, translateConfig };
|
|
15896
|
+
export { AlertComponent, AlertModule, ButtonComponent, ButtonDropdownComponent, ButtonDropdownModule, ButtonModule, CheckboxComponent, ClickOutsideDirective, ConfigColumnNodeComponent, ConfigFieldNodeComponent, ConfigSchemaTreeService, ConfigSectionNodeComponent, ConfigurableFormComponent, configurableForm_examples as ConfigurableFormExamples, ConfigurableFormModule, ConfirmationModalComponent, ConfirmationModalModule, DEFAULT_ITEMS_PER_PAGE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_SIDE_NAV_TOOLTIP_POSITION, DatepickerComponent, DropdownComponent, ExpressionService, FieldConfigurationService, FieldConfiguratorComponent, FieldSelectionComponent, FilterComponent, FilterModule, FilterSidebarComponent, FilterSidebarModule, FilterTableSelectorComponent, FilterTableSelectorModule, FormBuilderModule, FormComponentsModule, FormFieldConfigurationComponent, FormFieldConfigurationModule, InputComponent, MaterialModule, NAV_ORIENTATION_DEFAULT, NAV_VARIANT_DEFAULT, NavComponent, NavModule, PAGINATION_THEME_DARK, PAGINATION_THEME_DEFAULT, PaginationComponent, PaginationModule, RadioComponent, SearchComponent, SharedUiModule, SideNavComponent, SideNavModule, SmartFormComponent, SmartFormController, smartForm_examples as SmartFormExamples, SmartFormModule, SmartTableComponent, SmartTableModule, SnackbarComponent, SnackbarModule, SnackbarService, StringUtils, SummaryCardComponent, SummaryCardModule, TableColumnConfigurationComponent, TableColumnConfigurationModule, TableColumnConfigurationService, TimePickerComponent, TimePickerModule, TimeWheelPanelComponent, ToggleComponent, ValidationUtils, appendBaseUrlRecursively, clearLocalStorage, clearSessionStorage, getLocalStorageItem, getSessionStorageItem, removeLocalStorageItem, removeSessionStorageItem, setLocalStorageItem, setSessionStorageItem, translateConfig };
|
|
14823
15897
|
//# sourceMappingURL=commons-shared-web-ui.mjs.map
|