@praxisui/list 8.0.0-beta.31 → 8.0.0-beta.33
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/praxisui-list.mjs +115 -119
- package/package.json +15 -14
- package/{index.d.ts → types/praxisui-list.d.ts} +24 -23
|
@@ -17,7 +17,7 @@ import * as i5$1 from '@angular/material/menu';
|
|
|
17
17
|
import { MatMenuModule } from '@angular/material/menu';
|
|
18
18
|
import * as i8$1 from '@angular/material/progress-spinner';
|
|
19
19
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
|
20
|
-
import * as i2
|
|
20
|
+
import * as i2 from '@angular/material/form-field';
|
|
21
21
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
22
22
|
import * as i10$1 from '@angular/material/paginator';
|
|
23
23
|
import { MatPaginatorIntl, MatPaginatorModule } from '@angular/material/paginator';
|
|
@@ -28,7 +28,7 @@ import * as i3$1 from '@angular/material/input';
|
|
|
28
28
|
import { MatInputModule } from '@angular/material/input';
|
|
29
29
|
import * as i12 from '@angular/material/tooltip';
|
|
30
30
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
31
|
-
import * as
|
|
31
|
+
import * as i1$1 from '@angular/forms';
|
|
32
32
|
import { FormsModule, FormControl, ReactiveFormsModule, FormGroup } from '@angular/forms';
|
|
33
33
|
import { BehaviorSubject, combineLatest, of, Subject, debounceTime, takeUntil, firstValueFrom, distinctUntilChanged as distinctUntilChanged$1, Subscription } from 'rxjs';
|
|
34
34
|
import { auditTime, switchMap, map, catchError, finalize, shareReplay, debounceTime as debounceTime$1, distinctUntilChanged, tap, take, takeUntil as takeUntil$1 } from 'rxjs/operators';
|
|
@@ -627,10 +627,10 @@ class ListDataService {
|
|
|
627
627
|
data,
|
|
628
628
|
};
|
|
629
629
|
}
|
|
630
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
631
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
630
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ListDataService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
631
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ListDataService });
|
|
632
632
|
}
|
|
633
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
633
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ListDataService, decorators: [{
|
|
634
634
|
type: Injectable
|
|
635
635
|
}] });
|
|
636
636
|
function unwrapApiEnvelope(page) {
|
|
@@ -724,10 +724,10 @@ class ListSkinService {
|
|
|
724
724
|
return `${base}\n${extra}`;
|
|
725
725
|
return base || extra;
|
|
726
726
|
}
|
|
727
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
728
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
727
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ListSkinService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
728
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ListSkinService, providedIn: 'root' });
|
|
729
729
|
}
|
|
730
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
730
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ListSkinService, decorators: [{
|
|
731
731
|
type: Injectable,
|
|
732
732
|
args: [{ providedIn: 'root' }]
|
|
733
733
|
}] });
|
|
@@ -1001,8 +1001,8 @@ class ExecutiveBadgeComponent {
|
|
|
1001
1001
|
get displayLabel() {
|
|
1002
1002
|
return String(this.label || '').trim();
|
|
1003
1003
|
}
|
|
1004
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1005
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
1004
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ExecutiveBadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1005
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.14", type: ExecutiveBadgeComponent, isStandalone: true, selector: "praxis-executive-badge", inputs: { ariaLabel: "ariaLabel", label: "label", variant: "variant" }, ngImport: i0, template: `
|
|
1006
1006
|
<span
|
|
1007
1007
|
class="badge"
|
|
1008
1008
|
[class]="variantClass"
|
|
@@ -1016,7 +1016,7 @@ class ExecutiveBadgeComponent {
|
|
|
1016
1016
|
</span>
|
|
1017
1017
|
`, isInline: true, styles: [".badge{display:inline-flex;align-items:center;padding:0 11px;border-radius:999px;font-size:.69rem;font-weight:700;min-height:21px}.badge__content{display:inline-flex;align-items:center;min-width:0}.badge-enterprise{background:#eaf4f7;color:#1d769b;border:1px solid #d6e8f0}.badge-corporate{background:#f4efe7;color:#9f8a58;border:1px solid #e7dbc4}.badge-middle{background:#ece8e1;color:#635745;border:1px solid #ddd5ca}\n"], dependencies: [{ kind: "component", type: PraxisRichContent, selector: "praxis-rich-content", inputs: ["document", "nodes", "context", "hostCapabilities", "layout", "rootClassName"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1018
1018
|
}
|
|
1019
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1019
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ExecutiveBadgeComponent, decorators: [{
|
|
1020
1020
|
type: Component,
|
|
1021
1021
|
args: [{ selector: 'praxis-executive-badge', standalone: true, imports: [PraxisRichContent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1022
1022
|
<span
|
|
@@ -1155,8 +1155,8 @@ class ExecutiveAlertsComponent {
|
|
|
1155
1155
|
},
|
|
1156
1156
|
];
|
|
1157
1157
|
}
|
|
1158
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1159
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
1158
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ExecutiveAlertsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1159
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: ExecutiveAlertsComponent, isStandalone: true, selector: "praxis-executive-alerts", inputs: { alerts: "alerts" }, usesOnChanges: true, ngImport: i0, template: `
|
|
1160
1160
|
@if (validAlerts.length > 0) {
|
|
1161
1161
|
<div
|
|
1162
1162
|
class="alerts-container"
|
|
@@ -1183,11 +1183,11 @@ class ExecutiveAlertsComponent {
|
|
|
1183
1183
|
}
|
|
1184
1184
|
</div>
|
|
1185
1185
|
}
|
|
1186
|
-
`, isInline: true, styles: [".alerts-container{display:inline-flex;gap:6px}.alert{display:inline-flex;align-items:center;gap:4px;padding:0 7px;border-radius:999px;font-size:.72rem;font-weight:700;min-width:28px;height:20px;justify-content:center;cursor:help;transition:all .2s ease}.alert:hover,.alert:focus{transform:scale(1.05);box-shadow:0 2px 4px #0000001a;outline:2px solid currentColor;outline-offset:2px}.alert-warning{background:#fff7e8;color:#bf8422;border:1px solid #f3ddaf}.alert-critical{background:#fff1f1;color:#c65857;border:1px solid #f3c9c7}.alert-icon,.alert-count{line-height:1}.alert-content,.alert-content__nodes{display:inline-flex;align-items:center;gap:4px}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "
|
|
1186
|
+
`, isInline: true, styles: [".alerts-container{display:inline-flex;gap:6px}.alert{display:inline-flex;align-items:center;gap:4px;padding:0 7px;border-radius:999px;font-size:.72rem;font-weight:700;min-width:28px;height:20px;justify-content:center;cursor:help;transition:all .2s ease}.alert:hover,.alert:focus{transform:scale(1.05);box-shadow:0 2px 4px #0000001a;outline:2px solid currentColor;outline-offset:2px}.alert-warning{background:#fff7e8;color:#bf8422;border:1px solid #f3ddaf}.alert-critical{background:#fff1f1;color:#c65857;border:1px solid #f3c9c7}.alert-icon,.alert-count{line-height:1}.alert-content,.alert-content__nodes{display:inline-flex;align-items:center;gap:4px}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "component", type: PraxisRichContent, selector: "praxis-rich-content", inputs: ["document", "nodes", "context", "hostCapabilities", "layout", "rootClassName"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1187
1187
|
}
|
|
1188
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1188
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ExecutiveAlertsComponent, decorators: [{
|
|
1189
1189
|
type: Component,
|
|
1190
|
-
args: [{ selector: 'praxis-executive-alerts', standalone: true, imports: [
|
|
1190
|
+
args: [{ selector: 'praxis-executive-alerts', standalone: true, imports: [PraxisRichContent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1191
1191
|
@if (validAlerts.length > 0) {
|
|
1192
1192
|
<div
|
|
1193
1193
|
class="alerts-container"
|
|
@@ -1292,8 +1292,8 @@ class ExecutiveOwnerComponent {
|
|
|
1292
1292
|
},
|
|
1293
1293
|
];
|
|
1294
1294
|
}
|
|
1295
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1296
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
1295
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ExecutiveOwnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1296
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.14", type: ExecutiveOwnerComponent, isStandalone: true, selector: "praxis-executive-owner", inputs: { name: "name" }, ngImport: i0, template: `
|
|
1297
1297
|
<div class="owner">
|
|
1298
1298
|
<praxis-rich-content
|
|
1299
1299
|
rootClassName="owner__content"
|
|
@@ -1302,7 +1302,7 @@ class ExecutiveOwnerComponent {
|
|
|
1302
1302
|
</div>
|
|
1303
1303
|
`, isInline: true, styles: [".owner{display:inline-flex;align-items:center;font-size:.82rem;color:#5f7289;font-weight:500}.owner__content{display:inline-flex;align-items:center;gap:8px}.owner__avatar{flex-shrink:0}.owner__name{white-space:nowrap}\n"], dependencies: [{ kind: "component", type: PraxisRichContent, selector: "praxis-rich-content", inputs: ["document", "nodes", "context", "hostCapabilities", "layout", "rootClassName"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1304
1304
|
}
|
|
1305
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1305
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ExecutiveOwnerComponent, decorators: [{
|
|
1306
1306
|
type: Component,
|
|
1307
1307
|
args: [{ selector: 'praxis-executive-owner', standalone: true, imports: [PraxisRichContent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1308
1308
|
<div class="owner">
|
|
@@ -1647,10 +1647,10 @@ class PraxisListSkinPreviewComponent {
|
|
|
1647
1647
|
}
|
|
1648
1648
|
// trackBy for preview lists
|
|
1649
1649
|
trackByIndex = (i) => i;
|
|
1650
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1651
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisListSkinPreviewComponent, isStandalone: true, selector: "praxis-list-skin-preview", inputs: { config: "config", items: "items", theme: "theme" }, ngImport: i0, template: "<div class=\"preview-root\" [ngClass]=\"skinClasses\" [attr.data-skin-scope]=\"skinScopeId\">\n @if (inlineCss) { <style [attr.nonce]=\"cspNonce || null\" [textContent]=\"inlineCss\"></style> }\n\n @if (isList()) {\n <mat-list>\n @for (item of items; track trackByIndex($index)) {\n <mat-list-item>\n <div class=\"list-item-content\">\n @if (leading(item); as lead) {\n @if (previewRichContentNodes(lead, { imageAlt: config.templating?.leading?.imageAlt }); as leadNodes) {\n <praxis-rich-content [nodes]=\"leadNodes\"></praxis-rich-content>\n } @else {\n @switch (lead.type) {\n @case ('icon') { <mat-icon [praxisIcon]=\"lead.value\" [ngClass]=\"lead.class\" [color]=\"isThemeColor(lead.color) ? lead.color : undefined\" [style.cssText]=\"(lead.style ? lead.style + ';' : '') + iconStyle(lead.color)\"></mat-icon> }\n @case ('image') {\n <div class=\"lead-image\" [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">\n <img [src]=\"lead.value\" [alt]=\"config.templating?.leading?.imageAlt || ''\" />\n @if (lead.badge?.value) { <mat-chip class=\"lead-badge\"\n [color]=\"isThemeColor(lead.badge?.color) ? lead.badge?.color : undefined\"\n [ngClass]=\"(((lead.badge?.variant || 'filled') === 'outlined') ? 'chip-outlined' : '')\"\n [style.cssText]=\"chipStyle(lead.badge?.color, lead.badge?.variant)\">{{ lead.badge?.value }}</mat-chip> }\n </div>\n }\n @case ('text') { <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</span> }\n @case ('chip') { <mat-chip [color]=\"isThemeColor(lead.color) ? lead.color : undefined\" [ngClass]=\"((lead.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(lead.color, lead.variant)\">{{ lead.value }}</mat-chip> }\n @case ('rating') { @for (_ of ratingRange(lead); track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, lead.value)\" [color]=\"ratingThemeColor(lead)\" [style.cssText]=\"ratingIconStyle(lead)\"></mat-icon> } }\n @case ('html') { <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\" [innerHTML]=\"lead.value\"></span> }\n @default { <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</span> }\n }\n }\n }\n <div class=\"list-item-text\">\n <div class=\"primary\" [ngClass]=\"primary(item)?.class\" [style.cssText]=\"primary(item)?.style\">\n @if (primary(item)?.type === 'html') {\n <span [innerHTML]=\"primary(item)?.value\"></span>\n } @else if (previewRichContentNodes(primary(item)); as primaryNodes) {\n <praxis-rich-content [nodes]=\"primaryNodes\"></praxis-rich-content>\n } @else {\n {{ primary(item)?.value }}\n }\n </div>\n @if ((config.layout?.lines || 1) > 1) {\n <div class=\"secondary\" [ngClass]=\"secondary(item)?.class\" [style.cssText]=\"secondary(item)?.style\">\n @if (secondary(item)?.type === 'html') {\n <span [innerHTML]=\"secondary(item)?.value\"></span>\n } @else if (previewRichContentNodes(secondary(item)); as secondaryNodes) {\n <praxis-rich-content [nodes]=\"secondaryNodes\"></praxis-rich-content>\n } @else {\n {{ secondary(item)?.value }}\n }\n </div>\n }\n @if (featuresVisible()) {\n @for (f of (config.templating?.features || []); track $index; let fi = $index) {\n <span class=\"feature\">\n @if (previewFeatureRichContentNodes(item, f); as featureNodes) {\n <praxis-rich-content [nodes]=\"featureNodes\"></praxis-rich-content>\n } @else {\n @if (f.icon && featuresMode() !== 'labels-only') { <mat-icon [praxisIcon]=\"f.icon\"></mat-icon> }\n @if (featuresMode() !== 'icons-only') { <span [ngClass]=\"f.class\" [style.cssText]=\"f.style\">{{ featureLabel(item, f.expr) }}</span> }\n }\n </span>\n }\n }\n </div>\n @if (meta(item); as m) {\n <div class=\"meta\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @if (metaPrefixIcon(); as mpi) { <mat-icon [praxisIcon]=\"mpi\"></mat-icon> }\n @if (previewRichContentNodes(m, { slot: 'meta' }); as metaNodes) {\n <praxis-rich-content [nodes]=\"metaNodes\"></praxis-rich-content>\n } @else {\n @switch (m.type) {\n @case ('chip') { <mat-chip [color]=\"isThemeColor(m.color) ? m.color : undefined\" [ngClass]=\"((m.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(m.color, m.variant)\">{{ m.value }}</mat-chip> }\n @case ('rating') { @for (_ of ratingRange(m); track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, m.value)\" [color]=\"ratingThemeColor(m)\" [style.cssText]=\"ratingIconStyle(m)\"></mat-icon> } }\n @case ('icon') { <mat-icon [praxisIcon]=\"m.value\" [color]=\"isThemeColor(m.color) ? m.color : undefined\" [style.cssText]=\"iconStyle(m.color)\"></mat-icon> }\n @case ('image') { <span class=\"inline-image\"><img [src]=\"m.value\" [alt]=\"m.imageAlt || ''\" /></span> }\n @case ('html') { <span [innerHTML]=\"m.value\"></span> }\n @default { <span>{{ m.value }}</span> }\n }\n }\n </div>\n }\n @if (trailing(item); as tr) {\n <div class=\"trailing\" [ngClass]=\"tr.class\" [style.cssText]=\"tr.style\">\n @if (previewRichContentNodes(tr); as trailingNodes) {\n <praxis-rich-content [nodes]=\"trailingNodes\"></praxis-rich-content>\n } @else {\n @switch (tr.type) {\n @case ('chip') { <mat-chip [color]=\"isThemeColor(tr.color) ? tr.color : undefined\" [ngClass]=\"((tr.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(tr.color, tr.variant)\">{{ tr.value }}</mat-chip> }\n @case ('icon') { <mat-icon [praxisIcon]=\"tr.value\" [color]=\"isThemeColor(tr.color) ? tr.color : undefined\" [style.cssText]=\"iconStyle(tr.color)\"></mat-icon> }\n @case ('image') { <span class=\"inline-image\"><img [src]=\"tr.value\" [alt]=\"tr.imageAlt || ''\" /></span> }\n @case ('rating') { @for (_ of ratingRange(tr); track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, tr.value)\" [color]=\"ratingThemeColor(tr)\" [style.cssText]=\"ratingIconStyle(tr)\"></mat-icon> } }\n @case ('html') { <span [innerHTML]=\"tr.value\"></span> }\n @default { <span>{{ tr.value }}</span> }\n }\n }\n </div>\n }\n </div>\n </mat-list-item>\n }\n </mat-list>\n } @else if (isTiles()) {\n <div class=\"tiles-grid\">\n @for (it of items; track trackByIndex($index)) {\n <div class=\"item-tile\">\n <div class=\"tile-media\">\n @if (leading(it); as lead) {\n @if (previewRichContentNodes(lead, { imageAlt: config.templating?.leading?.imageAlt }); as leadNodes) {\n <praxis-rich-content [nodes]=\"leadNodes\"></praxis-rich-content>\n } @else {\n @switch (lead.type) {\n @case ('image') {\n <img [src]=\"lead.value\" [alt]=\"config.templating?.leading?.imageAlt || ''\" />\n }\n @case ('icon') { <mat-icon [praxisIcon]=\"lead.value\" [ngClass]=\"lead.class\" [color]=\"isThemeColor(lead.color) ? lead.color : undefined\" [style.cssText]=\"(lead.style ? lead.style + ';' : '') + iconStyle(lead.color)\"></mat-icon> }\n @case ('text') { <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</span> }\n @case ('chip') { <mat-chip [color]=\"isThemeColor(lead.color) ? lead.color : undefined\" [ngClass]=\"((lead.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(lead.color, lead.variant)\">{{ lead.value }}</mat-chip> }\n @case ('rating') { @for (_ of ratingRange(lead); track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, lead.value)\" [color]=\"ratingThemeColor(lead)\" [style.cssText]=\"ratingIconStyle(lead)\"></mat-icon> } }\n @case ('html') { <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\" [innerHTML]=\"lead.value\"></span> }\n @default { <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</span> }\n }\n }\n }\n </div>\n @if (trailing(it); as tr) {\n @if (statusPosition() === 'top-right' && (tr?.type === 'chip' || tr?.type === 'icon')) {\n <div class=\"tile-status\">\n @if (previewRichContentNodes(tr); as trailingNodes) {\n <praxis-rich-content [nodes]=\"trailingNodes\"></praxis-rich-content>\n } @else {\n @if (tr?.type === 'chip') { <mat-chip [color]=\"isThemeColor(tr.color) ? tr.color : undefined\" [ngClass]=\"((tr.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(tr.color, tr.variant)\">{{ tr.value }}</mat-chip> }\n @if (tr?.type === 'icon') { <mat-icon [praxisIcon]=\"tr.value\" [color]=\"isThemeColor(tr.color) ? tr.color : undefined\" [style.cssText]=\"iconStyle(tr.color)\"></mat-icon> }\n }\n </div>\n }\n }\n <div class=\"tile-body\">\n <div class=\"primary\" [ngClass]=\"primary(it)?.class\" [style.cssText]=\"primary(it)?.style\">\n @if (primary(it)?.type === 'html') {\n <span [innerHTML]=\"primary(it)?.value\"></span>\n } @else if (previewRichContentNodes(primary(it)); as primaryNodes) {\n <praxis-rich-content [nodes]=\"primaryNodes\"></praxis-rich-content>\n } @else {\n {{ primary(it)?.value }}\n }\n </div>\n @if ((config.layout?.lines || 1) > 1) {\n <div class=\"secondary\" [ngClass]=\"secondary(it)?.class\" [style.cssText]=\"secondary(it)?.style\">\n @if (secondary(it)?.type === 'html') {\n <span [innerHTML]=\"secondary(it)?.value\"></span>\n } @else if (previewRichContentNodes(secondary(it)); as secondaryNodes) {\n <praxis-rich-content [nodes]=\"secondaryNodes\"></praxis-rich-content>\n } @else {\n {{ secondary(it)?.value }}\n }\n </div>\n }\n @if (meta(it); as m) {\n <div class=\"meta\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @if (metaPrefixIcon(); as mpi) { <mat-icon [praxisIcon]=\"mpi\"></mat-icon> }\n @if (previewRichContentNodes(m, { slot: 'meta' }); as metaNodes) {\n <praxis-rich-content [nodes]=\"metaNodes\"></praxis-rich-content>\n } @else {\n @switch (m.type) {\n @case ('chip') { <mat-chip [color]=\"isThemeColor(m.color) ? m.color : undefined\" [ngClass]=\"((m.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(m.color, m.variant)\">{{ m.value }}</mat-chip> }\n @case ('rating') { @for (_ of ratingRange(m); track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, m.value)\" [color]=\"ratingThemeColor(m)\" [style.cssText]=\"ratingIconStyle(m)\"></mat-icon> } }\n @case ('icon') { <mat-icon [praxisIcon]=\"m.value\" [color]=\"isThemeColor(m.color) ? m.color : undefined\" [style.cssText]=\"iconStyle(m.color)\"></mat-icon> }\n @case ('image') { <span class=\"inline-image\"><img [src]=\"m.value\" [alt]=\"m.imageAlt || ''\" /></span> }\n @case ('html') { <span [innerHTML]=\"m.value\"></span> }\n @default { <span>{{ m.value }}</span> }\n }\n }\n </div>\n }\n @if (featuresVisible()) {\n <div class=\"features\">\n @for (f of (config.templating?.features || []); track $index; let fi = $index) {\n <span class=\"feature\">\n @if (previewFeatureRichContentNodes(it, f); as featureNodes) {\n <praxis-rich-content [nodes]=\"featureNodes\"></praxis-rich-content>\n } @else {\n @if (f.icon && featuresMode() !== 'labels-only') { <mat-icon [praxisIcon]=\"f.icon\"></mat-icon> }\n @if (featuresMode() !== 'icons-only') { <span [ngClass]=\"f.class\" [style.cssText]=\"f.style\">{{ featureLabel(it, f.expr) }}</span> }\n }\n </span>\n }\n </div>\n }\n @if (trailing(it); as tr2) {\n @if (!(statusPosition() === 'top-right' && (tr2?.type === 'chip' || tr2?.type === 'icon'))) {\n <div class=\"meta\" [ngClass]=\"tr2.class\" [style.cssText]=\"tr2.style\">\n @if (previewRichContentNodes(tr2); as trailingNodes) {\n <praxis-rich-content [nodes]=\"trailingNodes\"></praxis-rich-content>\n } @else {\n @switch (tr2.type) {\n @case ('chip') { <mat-chip [color]=\"isThemeColor(tr2.color) ? tr2.color : undefined\" [ngClass]=\"((tr2.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(tr2.color, tr2.variant)\">{{ tr2.value }}</mat-chip> }\n @case ('rating') { @for (_ of ratingRange(tr2); track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, tr2.value)\" [color]=\"ratingThemeColor(tr2)\" [style.cssText]=\"ratingIconStyle(tr2)\"></mat-icon> } }\n @case ('image') { <span class=\"inline-image\"><img [src]=\"tr2.value\" [alt]=\"tr2.imageAlt || ''\" /></span> }\n @case ('html') { <span [innerHTML]=\"tr2.value\"></span> }\n @default { <span>{{ tr2.value }}</span> }\n }\n }\n </div>\n }\n }\n </div>\n </div>\n }\n </div>\n } @else {\n <div class=\"cards-grid\">\n @for (it of items; track trackByIndex($index)) {\n <div class=\"item-card\">\n <div class=\"list-item-content\">\n @if (leading(it); as lead) {\n @if (previewRichContentNodes(lead, { imageAlt: config.templating?.leading?.imageAlt }); as leadNodes) {\n <praxis-rich-content [nodes]=\"leadNodes\"></praxis-rich-content>\n } @else {\n @switch (lead.type) {\n @case ('icon') { <mat-icon [praxisIcon]=\"lead.value\" [ngClass]=\"lead.class\" [color]=\"isThemeColor(lead.color) ? lead.color : undefined\" [style.cssText]=\"(lead.style ? lead.style + ';' : '') + iconStyle(lead.color)\"></mat-icon> }\n @case ('image') {\n <div class=\"lead-image\" [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">\n <img [src]=\"lead.value\" [alt]=\"config.templating?.leading?.imageAlt || ''\" />\n @if (lead.badge?.value) { <mat-chip class=\"lead-badge\"\n [color]=\"isThemeColor(lead.badge?.color) ? lead.badge?.color : undefined\"\n [ngClass]=\"(((lead.badge?.variant || 'filled') === 'outlined') ? 'chip-outlined' : '')\"\n [style.cssText]=\"chipStyle(lead.badge?.color, lead.badge?.variant)\">{{ lead.badge?.value }}</mat-chip> }\n </div>\n }\n @case ('text') { <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</span> }\n @case ('chip') { <mat-chip [color]=\"isThemeColor(lead.color) ? lead.color : undefined\" [ngClass]=\"((lead.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(lead.color, lead.variant)\">{{ lead.value }}</mat-chip> }\n @case ('rating') { @for (_ of ratingRange(lead); track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, lead.value)\" [color]=\"ratingThemeColor(lead)\" [style.cssText]=\"ratingIconStyle(lead)\"></mat-icon> } }\n @case ('html') { <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\" [innerHTML]=\"lead.value\"></span> }\n @default { <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</span> }\n }\n }\n }\n <div class=\"list-item-text\">\n <div class=\"primary\" [ngClass]=\"primary(it)?.class\" [style.cssText]=\"primary(it)?.style\">\n @if (primary(it)?.type === 'html') {\n <span [innerHTML]=\"primary(it)?.value\"></span>\n } @else if (previewRichContentNodes(primary(it)); as primaryNodes) {\n <praxis-rich-content [nodes]=\"primaryNodes\"></praxis-rich-content>\n } @else {\n {{ primary(it)?.value }}\n }\n </div>\n @if ((config.layout?.lines || 1) > 1) {\n <div class=\"secondary\" [ngClass]=\"secondary(it)?.class\" [style.cssText]=\"secondary(it)?.style\">\n @if (secondary(it)?.type === 'html') {\n <span [innerHTML]=\"secondary(it)?.value\"></span>\n } @else if (previewRichContentNodes(secondary(it)); as secondaryNodes) {\n <praxis-rich-content [nodes]=\"secondaryNodes\"></praxis-rich-content>\n } @else {\n {{ secondary(it)?.value }}\n }\n </div>\n }\n @if (featuresVisible()) {\n @for (f of (config.templating?.features || []); track $index; let fi = $index) {\n <span class=\"feature\">\n @if (previewFeatureRichContentNodes(it, f); as featureNodes) {\n <praxis-rich-content [nodes]=\"featureNodes\"></praxis-rich-content>\n } @else {\n @if (f.icon && featuresMode() !== 'labels-only') { <mat-icon [praxisIcon]=\"f.icon\"></mat-icon> }\n @if (featuresMode() !== 'icons-only') { <span [ngClass]=\"f.class\" [style.cssText]=\"f.style\">{{ featureLabel(it, f.expr) }}</span> }\n }\n </span>\n }\n }\n </div>\n @if (meta(it); as m) {\n <div class=\"meta\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @if (metaPrefixIcon(); as mpi) { <mat-icon [praxisIcon]=\"mpi\"></mat-icon> }\n @if (previewRichContentNodes(m, { slot: 'meta' }); as metaNodes) {\n <praxis-rich-content [nodes]=\"metaNodes\"></praxis-rich-content>\n } @else {\n @switch (m.type) {\n @case ('chip') { <mat-chip [color]=\"isThemeColor(m.color) ? m.color : undefined\" [ngClass]=\"((m.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(m.color, m.variant)\">{{ m.value }}</mat-chip> }\n @case ('rating') { @for (_ of ratingRange(m); track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, m.value)\" [color]=\"ratingThemeColor(m)\" [style.cssText]=\"ratingIconStyle(m)\"></mat-icon> } }\n @case ('icon') { <mat-icon [praxisIcon]=\"m.value\" [color]=\"isThemeColor(m.color) ? m.color : undefined\" [style.cssText]=\"iconStyle(m.color)\"></mat-icon> }\n @case ('image') { <span class=\"inline-image\"><img [src]=\"m.value\" [alt]=\"m.imageAlt || ''\" /></span> }\n @case ('html') { <span [innerHTML]=\"m.value\"></span> }\n @default { <span>{{ m.value }}</span> }\n }\n }\n </div>\n }\n @if (trailing(it); as tr) {\n <div class=\"trailing\" [ngClass]=\"tr.class\" [style.cssText]=\"tr.style\">\n @if (previewRichContentNodes(tr); as trailingNodes) {\n <praxis-rich-content [nodes]=\"trailingNodes\"></praxis-rich-content>\n } @else {\n @switch (tr.type) {\n @case ('chip') { <mat-chip [color]=\"isThemeColor(tr.color) ? tr.color : undefined\" [ngClass]=\"((tr.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(tr.color, tr.variant)\">{{ tr.value }}</mat-chip> }\n @case ('icon') { <mat-icon [praxisIcon]=\"tr.value\" [color]=\"isThemeColor(tr.color) ? tr.color : undefined\" [style.cssText]=\"iconStyle(tr.color)\"></mat-icon> }\n @case ('image') { <span class=\"inline-image\"><img [src]=\"tr.value\" [alt]=\"tr.imageAlt || ''\" /></span> }\n @case ('rating') { @for (_ of ratingRange(tr); track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, tr.value)\" [color]=\"ratingThemeColor(tr)\" [style.cssText]=\"ratingIconStyle(tr)\"></mat-icon> } }\n @case ('html') { <span [innerHTML]=\"tr.value\"></span> }\n @default { <span>{{ tr.value }}</span> }\n }\n }\n </div>\n }\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [".preview-root{--p-list-radius: var(--md-sys-shape-corner-medium, 16px);--p-list-shadow: var(--md-sys-elevation-level2);--p-list-border: 1px solid var(--md-sys-color-outline-variant);--p-list-blur: 10px;--p-list-foreground: var(--md-sys-color-on-surface);--p-list-foreground-muted: var(--md-sys-color-on-surface-variant);--p-list-grad-foreground: var( --md-sys-color-on-primary-container, var(--md-sys-color-on-surface) );--p-list-grad-foreground-muted: color-mix( in srgb, var(--p-list-grad-foreground) 76%, transparent );--p-list-surface: var(--md-sys-color-surface-container);--p-list-accent: var(--md-sys-color-primary);--p-list-item-border: 1px solid var(--md-sys-color-outline-variant);--p-list-grad-from: var(--md-sys-color-primary-container);--p-list-grad-to: var(--md-sys-color-tertiary-container);--p-list-grad-angle: 135deg;--p-list-tile-minW: 240px;--p-list-tile-gap: 16px;--p-list-tile-padding: 16px;--p-list-tile-radius: 16px;--p-list-tile-media-radius: 12px;--p-list-tile-media-ratio: 1 / 1;padding:12px;border-radius:12px;overflow:hidden}.preview-light{background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface))}.preview-dark{background:var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface))}.preview-grid{background-color:var(--md-sys-color-surface-variant, var(--md-sys-color-surface));background-image:linear-gradient(var(--md-sys-color-outline-variant, var(--md-sys-color-outline)) 1px,transparent 1px),linear-gradient(90deg,var(--md-sys-color-outline-variant, var(--md-sys-color-outline)) 1px,transparent 1px);background-size:16px 16px}.skin-elevated,.skin-outline,.skin-flat,.skin-neumorphism{--p-list-item-surface: var(--mdc-elevated-card-container-color, var(--p-list-surface));--p-list-item-border: var(--p-list-border)}.skin-elevated .item-card,.skin-outline .item-card,.skin-flat .item-card,.skin-neumorphism .item-card{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border-radius:var(--p-list-radius);box-shadow:var(--p-list-shadow);border:var(--p-list-border)}.skin-elevated .item-tile,.skin-outline .item-tile,.skin-flat .item-tile,.skin-neumorphism .item-tile{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));box-shadow:var(--p-list-shadow);border:var(--p-list-border)}.skin-elevated .mat-mdc-list-item .list-item-content,.skin-outline .mat-mdc-list-item .list-item-content,.skin-flat .mat-mdc-list-item .list-item-content,.skin-neumorphism .mat-mdc-list-item .list-item-content{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border-radius:calc(var(--p-list-radius) * .75);box-shadow:var(--p-list-shadow);border:var(--p-list-item-border);color:var(--p-list-foreground)}.skin-outline{--p-list-shadow: none;--p-list-border: 1px solid var(--md-sys-color-outline)}.skin-flat{--p-list-shadow: none;--p-list-border: 1px solid var(--md-sys-color-outline-variant)}.skin-neumorphism{--p-list-shadow: var(--md-sys-elevation-level2);--p-list-border: 1px solid var(--md-sys-color-outline-variant);--p-list-radius: 1.25rem}.skin-pill-soft .item-card,.skin-pill-soft .item-tile{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border-radius:calc(var(--p-list-radius) * 1.3);box-shadow:var(--md-sys-elevation-level1);border:var(--p-list-border)}.skin-pill-soft .mat-mdc-list-item .list-item-content{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border-radius:calc(var(--p-list-radius) * 1.3);box-shadow:var(--md-sys-elevation-level1);border:var(--p-list-border);color:var(--p-list-foreground)}.lead-image{position:relative;width:96px;height:72px;border-radius:12px;overflow:hidden}.lead-image img{width:100%;height:100%;object-fit:cover;display:block}.lead-image .lead-badge{position:absolute;left:8px;bottom:8px}.features{display:flex;flex-wrap:wrap;gap:8px;margin-top:2px;color:var(--p-list-foreground-muted);min-width:0}.feature{display:inline-flex;align-items:center;gap:4px;min-width:0;max-width:100%;font-size:.75rem;line-height:1.25}.feature mat-icon{flex:0 0 auto;width:16px;height:16px;font-size:16px}.model-media .lead-image{width:136px;height:96px;border-radius:16px}.model-hotel .lead-image{width:160px;height:110px;border-radius:18px}.skin-gradient-tile{--p-list-foreground: var(--p-list-grad-foreground);--p-list-foreground-muted: var(--p-list-grad-foreground-muted)}.skin-gradient-tile .item-card,.skin-gradient-tile .item-tile{background:linear-gradient(var(--p-list-grad-angle),var(--p-list-grad-from),var(--p-list-grad-to));border-radius:var(--p-list-radius);color:var(--p-list-foreground)}.skin-gradient-tile .tile-media{background:color-mix(in srgb,var(--md-sys-color-surface) 74%,transparent);border-color:color-mix(in srgb,var(--p-list-foreground) 22%,transparent)}.skin-gradient-tile .tile-media mat-icon,.skin-gradient-tile .tile-meta,.skin-gradient-tile .tile-trailing,.skin-gradient-tile .feature,.skin-gradient-tile .secondary,.skin-gradient-tile .meta{color:var(--p-list-foreground-muted)}.skin-gradient-tile .tile-status,.skin-gradient-tile [data-rich-node-type=badge] .prx-rich-badge{color:var(--md-sys-color-on-secondary-container, var(--p-list-foreground));background:var(--md-sys-color-secondary-container, rgba(255, 255, 255, .72))}.skin-gradient-tile .mat-mdc-list-item .list-item-content{background:linear-gradient(var(--p-list-grad-angle),var(--p-list-grad-from),var(--p-list-grad-to));border-radius:var(--p-list-radius);color:var(--p-list-foreground)}.skin-glass .item-card,.skin-glass .item-tile{background:var(--md-sys-color-surface-container-low);border-radius:var(--p-list-radius);border:1px solid var(--md-sys-color-outline-variant);backdrop-filter:blur(var(--p-list-blur));-webkit-backdrop-filter:blur(var(--p-list-blur))}.skin-glass .mat-mdc-list-item .list-item-content{background:var(--md-sys-color-surface-container-low);border-radius:var(--p-list-radius);border:1px solid var(--md-sys-color-outline-variant);backdrop-filter:blur(var(--p-list-blur));-webkit-backdrop-filter:blur(var(--p-list-blur));color:var(--p-list-foreground)}.list-item-content{display:grid;grid-template-columns:auto minmax(0,1fr) auto auto;align-items:center;gap:12px;padding:12px 16px;min-width:0}.primary{font-weight:600;color:var(--p-list-foreground);min-width:0;overflow:hidden;text-overflow:ellipsis}.secondary{color:var(--p-list-foreground-muted);min-width:0;overflow:hidden}.meta{color:var(--p-list-foreground-muted);min-width:0}.trailing{color:var(--p-list-foreground-muted);text-align:end;min-width:0}.cards-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(var(--p-list-tile-minW),1fr));gap:var(--p-list-tile-gap)}.item-card{position:relative;display:grid;min-height:236px;padding:10px;overflow:hidden;color:var(--p-list-foreground)}.item-card .list-item-content{display:block;grid-template-columns:minmax(0,1fr);align-items:stretch;gap:8px;padding:0;min-width:0;overflow:hidden}.item-card .list-item-text{display:grid;gap:8px;min-width:0;width:100%}.item-card .lead-image{width:100%;height:auto;aspect-ratio:16/9;max-height:104px;border-radius:10px}.item-card .lead-image .lead-badge{max-width:calc(100% - 16px)}.item-card .primary{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-height:1.28;min-width:0;overflow-wrap:anywhere}.item-card .secondary{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;font-size:.78rem;line-height:1.3;color:color-mix(in srgb,var(--p-list-foreground) 72%,transparent);min-width:0;overflow-wrap:anywhere}.item-card .meta{display:inline-flex;align-items:center;gap:2px;max-width:100%;overflow:hidden;white-space:nowrap;font-size:.76rem;line-height:1.2;color:color-mix(in srgb,var(--p-list-foreground) 72%,transparent)}.item-card .meta mat-icon{flex:0 0 auto;width:16px;height:16px;font-size:16px}.item-card .trailing{justify-self:start;max-width:100%;text-align:start;color:inherit}.item-card .features{color:color-mix(in srgb,var(--p-list-foreground) 72%,transparent);min-width:0}.item-card .feature{min-width:0;max-width:100%;overflow-wrap:anywhere}.item-card .mat-mdc-chip{max-width:100%}.preview-root ::ng-deep praxis-rich-content .prx-rich-node[data-rich-node-type=badge]{background:transparent;color:inherit}.preview-root ::ng-deep praxis-rich-content .prx-rich-node[data-rich-node-type=badge] .prx-rich-badge{min-height:22px;padding:1px 9px;border:1px solid var(--p-list-chip-border-color, transparent);background:var(--p-list-chip-background, var(--md-sys-color-primary-container, #e8def8));color:var(--p-list-chip-color, var(--md-sys-color-on-primary-container, #21005d));line-height:18px}.item-card .mdc-evolution-chip__text-label,.item-card [data-rich-node-type=badge]{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tiles-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(var(--p-list-tile-minW),1fr));gap:var(--p-list-tile-gap)}.item-tile{padding:var(--p-list-tile-padding);border-radius:var(--p-list-tile-radius);border:var(--p-list-border);background:var(--p-list-surface);display:grid;gap:8px}.tile-media{width:100%;aspect-ratio:var(--p-list-tile-media-ratio);border-radius:var(--p-list-tile-media-radius);background:var(--md-sys-color-surface-container-low, var(--p-list-surface));display:grid;place-items:center;overflow:hidden}.tile-media img{width:100%;height:100%;object-fit:cover}.tile-body{display:grid;gap:4px}.chip-outlined.mat-mdc-chip{background:transparent!important;border:1px solid currentColor}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i3.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i3.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i5.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisRichContent, selector: "praxis-rich-content", inputs: ["document", "nodes", "context", "hostCapabilities", "layout", "rootClassName"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1650
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisListSkinPreviewComponent, deps: [{ token: ListSkinService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1651
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisListSkinPreviewComponent, isStandalone: true, selector: "praxis-list-skin-preview", inputs: { config: "config", items: "items", theme: "theme" }, ngImport: i0, template: "<div class=\"preview-root\" [ngClass]=\"skinClasses\" [attr.data-skin-scope]=\"skinScopeId\">\n @if (inlineCss) { <style [attr.nonce]=\"cspNonce || null\" [textContent]=\"inlineCss\"></style> }\n\n @if (isList()) {\n <mat-list>\n @for (item of items; track trackByIndex($index)) {\n <mat-list-item>\n <div class=\"list-item-content\">\n @if (leading(item); as lead) {\n @if (previewRichContentNodes(lead, { imageAlt: config.templating?.leading?.imageAlt }); as leadNodes) {\n <praxis-rich-content [nodes]=\"leadNodes\"></praxis-rich-content>\n } @else {\n @switch (lead.type) {\n @case ('icon') { <mat-icon [praxisIcon]=\"lead.value\" [ngClass]=\"lead.class\" [color]=\"isThemeColor(lead.color) ? lead.color : undefined\" [style.cssText]=\"(lead.style ? lead.style + ';' : '') + iconStyle(lead.color)\"></mat-icon> }\n @case ('image') {\n <div class=\"lead-image\" [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">\n <img [src]=\"lead.value\" [alt]=\"config.templating?.leading?.imageAlt || ''\" />\n @if (lead.badge?.value) { <mat-chip class=\"lead-badge\"\n [color]=\"isThemeColor(lead.badge?.color) ? lead.badge?.color : undefined\"\n [ngClass]=\"(((lead.badge?.variant || 'filled') === 'outlined') ? 'chip-outlined' : '')\"\n [style.cssText]=\"chipStyle(lead.badge?.color, lead.badge?.variant)\">{{ lead.badge?.value }}</mat-chip> }\n </div>\n }\n @case ('text') { <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</span> }\n @case ('chip') { <mat-chip [color]=\"isThemeColor(lead.color) ? lead.color : undefined\" [ngClass]=\"((lead.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(lead.color, lead.variant)\">{{ lead.value }}</mat-chip> }\n @case ('rating') { @for (_ of ratingRange(lead); track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, lead.value)\" [color]=\"ratingThemeColor(lead)\" [style.cssText]=\"ratingIconStyle(lead)\"></mat-icon> } }\n @case ('html') { <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\" [innerHTML]=\"lead.value\"></span> }\n @default { <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</span> }\n }\n }\n }\n <div class=\"list-item-text\">\n <div class=\"primary\" [ngClass]=\"primary(item)?.class\" [style.cssText]=\"primary(item)?.style\">\n @if (primary(item)?.type === 'html') {\n <span [innerHTML]=\"primary(item)?.value\"></span>\n } @else if (previewRichContentNodes(primary(item)); as primaryNodes) {\n <praxis-rich-content [nodes]=\"primaryNodes\"></praxis-rich-content>\n } @else {\n {{ primary(item)?.value }}\n }\n </div>\n @if ((config.layout?.lines || 1) > 1) {\n <div class=\"secondary\" [ngClass]=\"secondary(item)?.class\" [style.cssText]=\"secondary(item)?.style\">\n @if (secondary(item)?.type === 'html') {\n <span [innerHTML]=\"secondary(item)?.value\"></span>\n } @else if (previewRichContentNodes(secondary(item)); as secondaryNodes) {\n <praxis-rich-content [nodes]=\"secondaryNodes\"></praxis-rich-content>\n } @else {\n {{ secondary(item)?.value }}\n }\n </div>\n }\n @if (featuresVisible()) {\n @for (f of (config.templating?.features || []); track $index; let fi = $index) {\n <span class=\"feature\">\n @if (previewFeatureRichContentNodes(item, f); as featureNodes) {\n <praxis-rich-content [nodes]=\"featureNodes\"></praxis-rich-content>\n } @else {\n @if (f.icon && featuresMode() !== 'labels-only') { <mat-icon [praxisIcon]=\"f.icon\"></mat-icon> }\n @if (featuresMode() !== 'icons-only') { <span [ngClass]=\"f.class\" [style.cssText]=\"f.style\">{{ featureLabel(item, f.expr) }}</span> }\n }\n </span>\n }\n }\n </div>\n @if (meta(item); as m) {\n <div class=\"meta\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @if (metaPrefixIcon(); as mpi) { <mat-icon [praxisIcon]=\"mpi\"></mat-icon> }\n @if (previewRichContentNodes(m, { slot: 'meta' }); as metaNodes) {\n <praxis-rich-content [nodes]=\"metaNodes\"></praxis-rich-content>\n } @else {\n @switch (m.type) {\n @case ('chip') { <mat-chip [color]=\"isThemeColor(m.color) ? m.color : undefined\" [ngClass]=\"((m.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(m.color, m.variant)\">{{ m.value }}</mat-chip> }\n @case ('rating') { @for (_ of ratingRange(m); track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, m.value)\" [color]=\"ratingThemeColor(m)\" [style.cssText]=\"ratingIconStyle(m)\"></mat-icon> } }\n @case ('icon') { <mat-icon [praxisIcon]=\"m.value\" [color]=\"isThemeColor(m.color) ? m.color : undefined\" [style.cssText]=\"iconStyle(m.color)\"></mat-icon> }\n @case ('image') { <span class=\"inline-image\"><img [src]=\"m.value\" [alt]=\"m.imageAlt || ''\" /></span> }\n @case ('html') { <span [innerHTML]=\"m.value\"></span> }\n @default { <span>{{ m.value }}</span> }\n }\n }\n </div>\n }\n @if (trailing(item); as tr) {\n <div class=\"trailing\" [ngClass]=\"tr.class\" [style.cssText]=\"tr.style\">\n @if (previewRichContentNodes(tr); as trailingNodes) {\n <praxis-rich-content [nodes]=\"trailingNodes\"></praxis-rich-content>\n } @else {\n @switch (tr.type) {\n @case ('chip') { <mat-chip [color]=\"isThemeColor(tr.color) ? tr.color : undefined\" [ngClass]=\"((tr.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(tr.color, tr.variant)\">{{ tr.value }}</mat-chip> }\n @case ('icon') { <mat-icon [praxisIcon]=\"tr.value\" [color]=\"isThemeColor(tr.color) ? tr.color : undefined\" [style.cssText]=\"iconStyle(tr.color)\"></mat-icon> }\n @case ('image') { <span class=\"inline-image\"><img [src]=\"tr.value\" [alt]=\"tr.imageAlt || ''\" /></span> }\n @case ('rating') { @for (_ of ratingRange(tr); track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, tr.value)\" [color]=\"ratingThemeColor(tr)\" [style.cssText]=\"ratingIconStyle(tr)\"></mat-icon> } }\n @case ('html') { <span [innerHTML]=\"tr.value\"></span> }\n @default { <span>{{ tr.value }}</span> }\n }\n }\n </div>\n }\n </div>\n </mat-list-item>\n }\n </mat-list>\n } @else if (isTiles()) {\n <div class=\"tiles-grid\">\n @for (it of items; track trackByIndex($index)) {\n <div class=\"item-tile\">\n <div class=\"tile-media\">\n @if (leading(it); as lead) {\n @if (previewRichContentNodes(lead, { imageAlt: config.templating?.leading?.imageAlt }); as leadNodes) {\n <praxis-rich-content [nodes]=\"leadNodes\"></praxis-rich-content>\n } @else {\n @switch (lead.type) {\n @case ('image') {\n <img [src]=\"lead.value\" [alt]=\"config.templating?.leading?.imageAlt || ''\" />\n }\n @case ('icon') { <mat-icon [praxisIcon]=\"lead.value\" [ngClass]=\"lead.class\" [color]=\"isThemeColor(lead.color) ? lead.color : undefined\" [style.cssText]=\"(lead.style ? lead.style + ';' : '') + iconStyle(lead.color)\"></mat-icon> }\n @case ('text') { <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</span> }\n @case ('chip') { <mat-chip [color]=\"isThemeColor(lead.color) ? lead.color : undefined\" [ngClass]=\"((lead.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(lead.color, lead.variant)\">{{ lead.value }}</mat-chip> }\n @case ('rating') { @for (_ of ratingRange(lead); track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, lead.value)\" [color]=\"ratingThemeColor(lead)\" [style.cssText]=\"ratingIconStyle(lead)\"></mat-icon> } }\n @case ('html') { <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\" [innerHTML]=\"lead.value\"></span> }\n @default { <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</span> }\n }\n }\n }\n </div>\n @if (trailing(it); as tr) {\n @if (statusPosition() === 'top-right' && (tr?.type === 'chip' || tr?.type === 'icon')) {\n <div class=\"tile-status\">\n @if (previewRichContentNodes(tr); as trailingNodes) {\n <praxis-rich-content [nodes]=\"trailingNodes\"></praxis-rich-content>\n } @else {\n @if (tr?.type === 'chip') { <mat-chip [color]=\"isThemeColor(tr.color) ? tr.color : undefined\" [ngClass]=\"((tr.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(tr.color, tr.variant)\">{{ tr.value }}</mat-chip> }\n @if (tr?.type === 'icon') { <mat-icon [praxisIcon]=\"tr.value\" [color]=\"isThemeColor(tr.color) ? tr.color : undefined\" [style.cssText]=\"iconStyle(tr.color)\"></mat-icon> }\n }\n </div>\n }\n }\n <div class=\"tile-body\">\n <div class=\"primary\" [ngClass]=\"primary(it)?.class\" [style.cssText]=\"primary(it)?.style\">\n @if (primary(it)?.type === 'html') {\n <span [innerHTML]=\"primary(it)?.value\"></span>\n } @else if (previewRichContentNodes(primary(it)); as primaryNodes) {\n <praxis-rich-content [nodes]=\"primaryNodes\"></praxis-rich-content>\n } @else {\n {{ primary(it)?.value }}\n }\n </div>\n @if ((config.layout?.lines || 1) > 1) {\n <div class=\"secondary\" [ngClass]=\"secondary(it)?.class\" [style.cssText]=\"secondary(it)?.style\">\n @if (secondary(it)?.type === 'html') {\n <span [innerHTML]=\"secondary(it)?.value\"></span>\n } @else if (previewRichContentNodes(secondary(it)); as secondaryNodes) {\n <praxis-rich-content [nodes]=\"secondaryNodes\"></praxis-rich-content>\n } @else {\n {{ secondary(it)?.value }}\n }\n </div>\n }\n @if (meta(it); as m) {\n <div class=\"meta\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @if (metaPrefixIcon(); as mpi) { <mat-icon [praxisIcon]=\"mpi\"></mat-icon> }\n @if (previewRichContentNodes(m, { slot: 'meta' }); as metaNodes) {\n <praxis-rich-content [nodes]=\"metaNodes\"></praxis-rich-content>\n } @else {\n @switch (m.type) {\n @case ('chip') { <mat-chip [color]=\"isThemeColor(m.color) ? m.color : undefined\" [ngClass]=\"((m.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(m.color, m.variant)\">{{ m.value }}</mat-chip> }\n @case ('rating') { @for (_ of ratingRange(m); track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, m.value)\" [color]=\"ratingThemeColor(m)\" [style.cssText]=\"ratingIconStyle(m)\"></mat-icon> } }\n @case ('icon') { <mat-icon [praxisIcon]=\"m.value\" [color]=\"isThemeColor(m.color) ? m.color : undefined\" [style.cssText]=\"iconStyle(m.color)\"></mat-icon> }\n @case ('image') { <span class=\"inline-image\"><img [src]=\"m.value\" [alt]=\"m.imageAlt || ''\" /></span> }\n @case ('html') { <span [innerHTML]=\"m.value\"></span> }\n @default { <span>{{ m.value }}</span> }\n }\n }\n </div>\n }\n @if (featuresVisible()) {\n <div class=\"features\">\n @for (f of (config.templating?.features || []); track $index; let fi = $index) {\n <span class=\"feature\">\n @if (previewFeatureRichContentNodes(it, f); as featureNodes) {\n <praxis-rich-content [nodes]=\"featureNodes\"></praxis-rich-content>\n } @else {\n @if (f.icon && featuresMode() !== 'labels-only') { <mat-icon [praxisIcon]=\"f.icon\"></mat-icon> }\n @if (featuresMode() !== 'icons-only') { <span [ngClass]=\"f.class\" [style.cssText]=\"f.style\">{{ featureLabel(it, f.expr) }}</span> }\n }\n </span>\n }\n </div>\n }\n @if (trailing(it); as tr2) {\n @if (!(statusPosition() === 'top-right' && (tr2?.type === 'chip' || tr2?.type === 'icon'))) {\n <div class=\"meta\" [ngClass]=\"tr2.class\" [style.cssText]=\"tr2.style\">\n @if (previewRichContentNodes(tr2); as trailingNodes) {\n <praxis-rich-content [nodes]=\"trailingNodes\"></praxis-rich-content>\n } @else {\n @switch (tr2.type) {\n @case ('chip') { <mat-chip [color]=\"isThemeColor(tr2.color) ? tr2.color : undefined\" [ngClass]=\"((tr2.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(tr2.color, tr2.variant)\">{{ tr2.value }}</mat-chip> }\n @case ('rating') { @for (_ of ratingRange(tr2); track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, tr2.value)\" [color]=\"ratingThemeColor(tr2)\" [style.cssText]=\"ratingIconStyle(tr2)\"></mat-icon> } }\n @case ('image') { <span class=\"inline-image\"><img [src]=\"tr2.value\" [alt]=\"tr2.imageAlt || ''\" /></span> }\n @case ('html') { <span [innerHTML]=\"tr2.value\"></span> }\n @default { <span>{{ tr2.value }}</span> }\n }\n }\n </div>\n }\n }\n </div>\n </div>\n }\n </div>\n } @else {\n <div class=\"cards-grid\">\n @for (it of items; track trackByIndex($index)) {\n <div class=\"item-card\">\n <div class=\"list-item-content\">\n @if (leading(it); as lead) {\n @if (previewRichContentNodes(lead, { imageAlt: config.templating?.leading?.imageAlt }); as leadNodes) {\n <praxis-rich-content [nodes]=\"leadNodes\"></praxis-rich-content>\n } @else {\n @switch (lead.type) {\n @case ('icon') { <mat-icon [praxisIcon]=\"lead.value\" [ngClass]=\"lead.class\" [color]=\"isThemeColor(lead.color) ? lead.color : undefined\" [style.cssText]=\"(lead.style ? lead.style + ';' : '') + iconStyle(lead.color)\"></mat-icon> }\n @case ('image') {\n <div class=\"lead-image\" [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">\n <img [src]=\"lead.value\" [alt]=\"config.templating?.leading?.imageAlt || ''\" />\n @if (lead.badge?.value) { <mat-chip class=\"lead-badge\"\n [color]=\"isThemeColor(lead.badge?.color) ? lead.badge?.color : undefined\"\n [ngClass]=\"(((lead.badge?.variant || 'filled') === 'outlined') ? 'chip-outlined' : '')\"\n [style.cssText]=\"chipStyle(lead.badge?.color, lead.badge?.variant)\">{{ lead.badge?.value }}</mat-chip> }\n </div>\n }\n @case ('text') { <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</span> }\n @case ('chip') { <mat-chip [color]=\"isThemeColor(lead.color) ? lead.color : undefined\" [ngClass]=\"((lead.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(lead.color, lead.variant)\">{{ lead.value }}</mat-chip> }\n @case ('rating') { @for (_ of ratingRange(lead); track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, lead.value)\" [color]=\"ratingThemeColor(lead)\" [style.cssText]=\"ratingIconStyle(lead)\"></mat-icon> } }\n @case ('html') { <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\" [innerHTML]=\"lead.value\"></span> }\n @default { <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</span> }\n }\n }\n }\n <div class=\"list-item-text\">\n <div class=\"primary\" [ngClass]=\"primary(it)?.class\" [style.cssText]=\"primary(it)?.style\">\n @if (primary(it)?.type === 'html') {\n <span [innerHTML]=\"primary(it)?.value\"></span>\n } @else if (previewRichContentNodes(primary(it)); as primaryNodes) {\n <praxis-rich-content [nodes]=\"primaryNodes\"></praxis-rich-content>\n } @else {\n {{ primary(it)?.value }}\n }\n </div>\n @if ((config.layout?.lines || 1) > 1) {\n <div class=\"secondary\" [ngClass]=\"secondary(it)?.class\" [style.cssText]=\"secondary(it)?.style\">\n @if (secondary(it)?.type === 'html') {\n <span [innerHTML]=\"secondary(it)?.value\"></span>\n } @else if (previewRichContentNodes(secondary(it)); as secondaryNodes) {\n <praxis-rich-content [nodes]=\"secondaryNodes\"></praxis-rich-content>\n } @else {\n {{ secondary(it)?.value }}\n }\n </div>\n }\n @if (featuresVisible()) {\n @for (f of (config.templating?.features || []); track $index; let fi = $index) {\n <span class=\"feature\">\n @if (previewFeatureRichContentNodes(it, f); as featureNodes) {\n <praxis-rich-content [nodes]=\"featureNodes\"></praxis-rich-content>\n } @else {\n @if (f.icon && featuresMode() !== 'labels-only') { <mat-icon [praxisIcon]=\"f.icon\"></mat-icon> }\n @if (featuresMode() !== 'icons-only') { <span [ngClass]=\"f.class\" [style.cssText]=\"f.style\">{{ featureLabel(it, f.expr) }}</span> }\n }\n </span>\n }\n }\n </div>\n @if (meta(it); as m) {\n <div class=\"meta\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @if (metaPrefixIcon(); as mpi) { <mat-icon [praxisIcon]=\"mpi\"></mat-icon> }\n @if (previewRichContentNodes(m, { slot: 'meta' }); as metaNodes) {\n <praxis-rich-content [nodes]=\"metaNodes\"></praxis-rich-content>\n } @else {\n @switch (m.type) {\n @case ('chip') { <mat-chip [color]=\"isThemeColor(m.color) ? m.color : undefined\" [ngClass]=\"((m.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(m.color, m.variant)\">{{ m.value }}</mat-chip> }\n @case ('rating') { @for (_ of ratingRange(m); track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, m.value)\" [color]=\"ratingThemeColor(m)\" [style.cssText]=\"ratingIconStyle(m)\"></mat-icon> } }\n @case ('icon') { <mat-icon [praxisIcon]=\"m.value\" [color]=\"isThemeColor(m.color) ? m.color : undefined\" [style.cssText]=\"iconStyle(m.color)\"></mat-icon> }\n @case ('image') { <span class=\"inline-image\"><img [src]=\"m.value\" [alt]=\"m.imageAlt || ''\" /></span> }\n @case ('html') { <span [innerHTML]=\"m.value\"></span> }\n @default { <span>{{ m.value }}</span> }\n }\n }\n </div>\n }\n @if (trailing(it); as tr) {\n <div class=\"trailing\" [ngClass]=\"tr.class\" [style.cssText]=\"tr.style\">\n @if (previewRichContentNodes(tr); as trailingNodes) {\n <praxis-rich-content [nodes]=\"trailingNodes\"></praxis-rich-content>\n } @else {\n @switch (tr.type) {\n @case ('chip') { <mat-chip [color]=\"isThemeColor(tr.color) ? tr.color : undefined\" [ngClass]=\"((tr.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\" [style.cssText]=\"chipStyle(tr.color, tr.variant)\">{{ tr.value }}</mat-chip> }\n @case ('icon') { <mat-icon [praxisIcon]=\"tr.value\" [color]=\"isThemeColor(tr.color) ? tr.color : undefined\" [style.cssText]=\"iconStyle(tr.color)\"></mat-icon> }\n @case ('image') { <span class=\"inline-image\"><img [src]=\"tr.value\" [alt]=\"tr.imageAlt || ''\" /></span> }\n @case ('rating') { @for (_ of ratingRange(tr); track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, tr.value)\" [color]=\"ratingThemeColor(tr)\" [style.cssText]=\"ratingIconStyle(tr)\"></mat-icon> } }\n @case ('html') { <span [innerHTML]=\"tr.value\"></span> }\n @default { <span>{{ tr.value }}</span> }\n }\n }\n </div>\n }\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [".preview-root{--p-list-radius: var(--md-sys-shape-corner-medium, 16px);--p-list-shadow: var(--md-sys-elevation-level2);--p-list-border: 1px solid var(--md-sys-color-outline-variant);--p-list-blur: 10px;--p-list-foreground: var(--md-sys-color-on-surface);--p-list-foreground-muted: var(--md-sys-color-on-surface-variant);--p-list-grad-foreground: var( --md-sys-color-on-primary-container, var(--md-sys-color-on-surface) );--p-list-grad-foreground-muted: color-mix( in srgb, var(--p-list-grad-foreground) 76%, transparent );--p-list-surface: var(--md-sys-color-surface-container);--p-list-accent: var(--md-sys-color-primary);--p-list-item-border: 1px solid var(--md-sys-color-outline-variant);--p-list-grad-from: var(--md-sys-color-primary-container);--p-list-grad-to: var(--md-sys-color-tertiary-container);--p-list-grad-angle: 135deg;--p-list-tile-minW: 240px;--p-list-tile-gap: 16px;--p-list-tile-padding: 16px;--p-list-tile-radius: 16px;--p-list-tile-media-radius: 12px;--p-list-tile-media-ratio: 1 / 1;padding:12px;border-radius:12px;overflow:hidden}.preview-light{background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface))}.preview-dark{background:var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface))}.preview-grid{background-color:var(--md-sys-color-surface-variant, var(--md-sys-color-surface));background-image:linear-gradient(var(--md-sys-color-outline-variant, var(--md-sys-color-outline)) 1px,transparent 1px),linear-gradient(90deg,var(--md-sys-color-outline-variant, var(--md-sys-color-outline)) 1px,transparent 1px);background-size:16px 16px}.skin-elevated,.skin-outline,.skin-flat,.skin-neumorphism{--p-list-item-surface: var(--mdc-elevated-card-container-color, var(--p-list-surface));--p-list-item-border: var(--p-list-border)}.skin-elevated .item-card,.skin-outline .item-card,.skin-flat .item-card,.skin-neumorphism .item-card{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border-radius:var(--p-list-radius);box-shadow:var(--p-list-shadow);border:var(--p-list-border)}.skin-elevated .item-tile,.skin-outline .item-tile,.skin-flat .item-tile,.skin-neumorphism .item-tile{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));box-shadow:var(--p-list-shadow);border:var(--p-list-border)}.skin-elevated .mat-mdc-list-item .list-item-content,.skin-outline .mat-mdc-list-item .list-item-content,.skin-flat .mat-mdc-list-item .list-item-content,.skin-neumorphism .mat-mdc-list-item .list-item-content{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border-radius:calc(var(--p-list-radius) * .75);box-shadow:var(--p-list-shadow);border:var(--p-list-item-border);color:var(--p-list-foreground)}.skin-outline{--p-list-shadow: none;--p-list-border: 1px solid var(--md-sys-color-outline)}.skin-flat{--p-list-shadow: none;--p-list-border: 1px solid var(--md-sys-color-outline-variant)}.skin-neumorphism{--p-list-shadow: var(--md-sys-elevation-level2);--p-list-border: 1px solid var(--md-sys-color-outline-variant);--p-list-radius: 1.25rem}.skin-pill-soft .item-card,.skin-pill-soft .item-tile{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border-radius:calc(var(--p-list-radius) * 1.3);box-shadow:var(--md-sys-elevation-level1);border:var(--p-list-border)}.skin-pill-soft .mat-mdc-list-item .list-item-content{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border-radius:calc(var(--p-list-radius) * 1.3);box-shadow:var(--md-sys-elevation-level1);border:var(--p-list-border);color:var(--p-list-foreground)}.lead-image{position:relative;width:96px;height:72px;border-radius:12px;overflow:hidden}.lead-image img{width:100%;height:100%;object-fit:cover;display:block}.lead-image .lead-badge{position:absolute;left:8px;bottom:8px}.features{display:flex;flex-wrap:wrap;gap:8px;margin-top:2px;color:var(--p-list-foreground-muted);min-width:0}.feature{display:inline-flex;align-items:center;gap:4px;min-width:0;max-width:100%;font-size:.75rem;line-height:1.25}.feature mat-icon{flex:0 0 auto;width:16px;height:16px;font-size:16px}.model-media .lead-image{width:136px;height:96px;border-radius:16px}.model-hotel .lead-image{width:160px;height:110px;border-radius:18px}.skin-gradient-tile{--p-list-foreground: var(--p-list-grad-foreground);--p-list-foreground-muted: var(--p-list-grad-foreground-muted)}.skin-gradient-tile .item-card,.skin-gradient-tile .item-tile{background:linear-gradient(var(--p-list-grad-angle),var(--p-list-grad-from),var(--p-list-grad-to));border-radius:var(--p-list-radius);color:var(--p-list-foreground)}.skin-gradient-tile .tile-media{background:color-mix(in srgb,var(--md-sys-color-surface) 74%,transparent);border-color:color-mix(in srgb,var(--p-list-foreground) 22%,transparent)}.skin-gradient-tile .tile-media mat-icon,.skin-gradient-tile .tile-meta,.skin-gradient-tile .tile-trailing,.skin-gradient-tile .feature,.skin-gradient-tile .secondary,.skin-gradient-tile .meta{color:var(--p-list-foreground-muted)}.skin-gradient-tile .tile-status,.skin-gradient-tile [data-rich-node-type=badge] .prx-rich-badge{color:var(--md-sys-color-on-secondary-container, var(--p-list-foreground));background:var(--md-sys-color-secondary-container, rgba(255, 255, 255, .72))}.skin-gradient-tile .mat-mdc-list-item .list-item-content{background:linear-gradient(var(--p-list-grad-angle),var(--p-list-grad-from),var(--p-list-grad-to));border-radius:var(--p-list-radius);color:var(--p-list-foreground)}.skin-glass .item-card,.skin-glass .item-tile{background:var(--md-sys-color-surface-container-low);border-radius:var(--p-list-radius);border:1px solid var(--md-sys-color-outline-variant);backdrop-filter:blur(var(--p-list-blur));-webkit-backdrop-filter:blur(var(--p-list-blur))}.skin-glass .mat-mdc-list-item .list-item-content{background:var(--md-sys-color-surface-container-low);border-radius:var(--p-list-radius);border:1px solid var(--md-sys-color-outline-variant);backdrop-filter:blur(var(--p-list-blur));-webkit-backdrop-filter:blur(var(--p-list-blur));color:var(--p-list-foreground)}.list-item-content{display:grid;grid-template-columns:auto minmax(0,1fr) auto auto;align-items:center;gap:12px;padding:12px 16px;min-width:0}.primary{font-weight:600;color:var(--p-list-foreground);min-width:0;overflow:hidden;text-overflow:ellipsis}.secondary{color:var(--p-list-foreground-muted);min-width:0;overflow:hidden}.meta{color:var(--p-list-foreground-muted);min-width:0}.trailing{color:var(--p-list-foreground-muted);text-align:end;min-width:0}.cards-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(var(--p-list-tile-minW),1fr));gap:var(--p-list-tile-gap)}.item-card{position:relative;display:grid;min-height:236px;padding:10px;overflow:hidden;color:var(--p-list-foreground)}.item-card .list-item-content{display:block;grid-template-columns:minmax(0,1fr);align-items:stretch;gap:8px;padding:0;min-width:0;overflow:hidden}.item-card .list-item-text{display:grid;gap:8px;min-width:0;width:100%}.item-card .lead-image{width:100%;height:auto;aspect-ratio:16/9;max-height:104px;border-radius:10px}.item-card .lead-image .lead-badge{max-width:calc(100% - 16px)}.item-card .primary{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-height:1.28;min-width:0;overflow-wrap:anywhere}.item-card .secondary{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;font-size:.78rem;line-height:1.3;color:color-mix(in srgb,var(--p-list-foreground) 72%,transparent);min-width:0;overflow-wrap:anywhere}.item-card .meta{display:inline-flex;align-items:center;gap:2px;max-width:100%;overflow:hidden;white-space:nowrap;font-size:.76rem;line-height:1.2;color:color-mix(in srgb,var(--p-list-foreground) 72%,transparent)}.item-card .meta mat-icon{flex:0 0 auto;width:16px;height:16px;font-size:16px}.item-card .trailing{justify-self:start;max-width:100%;text-align:start;color:inherit}.item-card .features{color:color-mix(in srgb,var(--p-list-foreground) 72%,transparent);min-width:0}.item-card .feature{min-width:0;max-width:100%;overflow-wrap:anywhere}.item-card .mat-mdc-chip{max-width:100%}.preview-root ::ng-deep praxis-rich-content .prx-rich-node[data-rich-node-type=badge]{background:transparent;color:inherit}.preview-root ::ng-deep praxis-rich-content .prx-rich-node[data-rich-node-type=badge] .prx-rich-badge{min-height:22px;padding:1px 9px;border:1px solid var(--p-list-chip-border-color, transparent);background:var(--p-list-chip-background, var(--md-sys-color-primary-container, #e8def8));color:var(--p-list-chip-color, var(--md-sys-color-on-primary-container, #21005d));line-height:18px}.item-card .mdc-evolution-chip__text-label,.item-card [data-rich-node-type=badge]{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tiles-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(var(--p-list-tile-minW),1fr));gap:var(--p-list-tile-gap)}.item-tile{padding:var(--p-list-tile-padding);border-radius:var(--p-list-tile-radius);border:var(--p-list-border);background:var(--p-list-surface);display:grid;gap:8px}.tile-media{width:100%;aspect-ratio:var(--p-list-tile-media-ratio);border-radius:var(--p-list-tile-media-radius);background:var(--md-sys-color-surface-container-low, var(--p-list-surface));display:grid;place-items:center;overflow:hidden}.tile-media img{width:100%;height:100%;object-fit:cover}.tile-body{display:grid;gap:4px}.chip-outlined.mat-mdc-chip{background:transparent!important;border:1px solid currentColor}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i3.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i3.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i5.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisRichContent, selector: "praxis-rich-content", inputs: ["document", "nodes", "context", "hostCapabilities", "layout", "rootClassName"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1652
1652
|
}
|
|
1653
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1653
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisListSkinPreviewComponent, decorators: [{
|
|
1654
1654
|
type: Component,
|
|
1655
1655
|
args: [{ selector: 'praxis-list-skin-preview', standalone: true, imports: [
|
|
1656
1656
|
CommonModule,
|
|
@@ -1678,12 +1678,12 @@ class PraxisMetaEditorTextComponent {
|
|
|
1678
1678
|
this.setPipe?.(this.model, pipe);
|
|
1679
1679
|
this.change.emit();
|
|
1680
1680
|
}
|
|
1681
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1682
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
1681
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisMetaEditorTextComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1682
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.14", type: PraxisMetaEditorTextComponent, isStandalone: true, selector: "praxis-meta-editor-text", inputs: { model: "model", setPipe: "setPipe" }, outputs: { change: "change" }, ngImport: i0, template: "<div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Pipes / Formato</mat-label>\n <input matInput [(ngModel)]=\"model.extraPipe\" (ngModelChange)=\"change.emit()\" />\n <button mat-icon-button matSuffix [matMenuTriggerFor]=\"pipeMenu\"><mat-icon>build</mat-icon></button>\n </mat-form-field>\n <mat-menu #pipeMenu=\"matMenu\">\n <button mat-menu-item (click)=\"onPipe('map:KEY=VALUE')\"><mat-icon>tune</mat-icon>Map</button>\n </mat-menu>\n</div>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.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: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i5$1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i5$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i5$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }] });
|
|
1683
1683
|
}
|
|
1684
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1684
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisMetaEditorTextComponent, decorators: [{
|
|
1685
1685
|
type: Component,
|
|
1686
|
-
args: [{ selector: 'praxis-meta-editor-text', standalone: true, imports: [
|
|
1686
|
+
args: [{ selector: 'praxis-meta-editor-text', standalone: true, imports: [FormsModule, MatFormFieldModule, MatInputModule, MatIconModule, MatMenuModule], template: "<div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Pipes / Formato</mat-label>\n <input matInput [(ngModel)]=\"model.extraPipe\" (ngModelChange)=\"change.emit()\" />\n <button mat-icon-button matSuffix [matMenuTriggerFor]=\"pipeMenu\"><mat-icon>build</mat-icon></button>\n </mat-form-field>\n <mat-menu #pipeMenu=\"matMenu\">\n <button mat-menu-item (click)=\"onPipe('map:KEY=VALUE')\"><mat-icon>tune</mat-icon>Map</button>\n </mat-menu>\n</div>\n" }]
|
|
1687
1687
|
}], propDecorators: { model: [{
|
|
1688
1688
|
type: Input
|
|
1689
1689
|
}], setPipe: [{
|
|
@@ -1699,12 +1699,12 @@ class PraxisMetaEditorChipComponent {
|
|
|
1699
1699
|
isCustomColor;
|
|
1700
1700
|
enableCustomColor;
|
|
1701
1701
|
change = new EventEmitter();
|
|
1702
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1703
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
1702
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisMetaEditorChipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1703
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisMetaEditorChipComponent, isStandalone: true, selector: "praxis-meta-editor-chip", inputs: { model: "model", paletteOptions: "paletteOptions", colorDotBackground: "colorDotBackground", isCustomColor: "isCustomColor", enableCustomColor: "enableCustomColor" }, outputs: { change: "change" }, ngImport: i0, template: "<div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor</mat-label>\n <mat-select [(ngModel)]=\"model.chipColor\" (ngModelChange)=\"change.emit()\">\n @for (c of paletteOptions; track c) {\n <mat-option [value]=\"c.value\">\n <span class=\"color-dot\" [style.background]=\"colorDotBackground?.(c.value)\"></span>{{ c.label }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Variante</mat-label>\n <mat-select [(ngModel)]=\"model.chipVariant\" (ngModelChange)=\"change.emit()\">\n <mat-option value=\"filled\">Filled</mat-option>\n <mat-option value=\"outlined\">Outlined</mat-option>\n </mat-select>\n </mat-form-field>\n</div>\n<div class=\"g gap-8\">\n @if (isCustomColor?.(model.chipColor)) {\n <div>\n <pdx-color-picker label=\"Cor personalizada\" [format]=\"'hex'\" [(ngModel)]=\"model.chipColor\"\n (ngModelChange)=\"change.emit()\"></pdx-color-picker>\n </div>\n } @else {\n <button mat-stroked-button type=\"button\" (click)=\"enableCustomColor?.(model, 'chipColor'); change.emit()\">\n Usar cor personalizada\n </button>\n }\n</div>\n<div class=\"g row-flow gap-8 mt-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button mat-stroked-button type=\"button\" (click)=\"model.chipVariant='filled'; model.chipColor='primary'; change.emit()\">Status</button>\n <button mat-stroked-button type=\"button\" (click)=\"model.chipVariant='outlined'; model.chipColor='primary'; change.emit()\">Outlined</button>\n <button mat-stroked-button type=\"button\" (click)=\"model.chipVariant='filled'; model.chipColor='warn'; change.emit()\">Cr\u00EDtico</button>\n</div>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: PdxColorPickerComponent, selector: "pdx-color-picker", inputs: ["actionsLayout", "activeView", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "clearButton", "disabledMode", "readonlyMode", "visible", "presentationMode", "fillMode", "format", "gradientSettings", "icon", "iconClass", "svgIcon", "paletteSettings", "popupSettings", "preview", "rounded", "size", "tabindex", "views", "maxRecent", "showRecent"], outputs: ["valueChange", "open", "close", "cancel", "activeViewChange", "activeColorClick", "focusEvent", "blurEvent"] }] });
|
|
1704
1704
|
}
|
|
1705
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1705
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisMetaEditorChipComponent, decorators: [{
|
|
1706
1706
|
type: Component,
|
|
1707
|
-
args: [{ selector: 'praxis-meta-editor-chip', standalone: true, imports: [
|
|
1707
|
+
args: [{ selector: 'praxis-meta-editor-chip', standalone: true, imports: [FormsModule, MatFormFieldModule, MatSelectModule, MatButtonModule, PdxColorPickerComponent], template: "<div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor</mat-label>\n <mat-select [(ngModel)]=\"model.chipColor\" (ngModelChange)=\"change.emit()\">\n @for (c of paletteOptions; track c) {\n <mat-option [value]=\"c.value\">\n <span class=\"color-dot\" [style.background]=\"colorDotBackground?.(c.value)\"></span>{{ c.label }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Variante</mat-label>\n <mat-select [(ngModel)]=\"model.chipVariant\" (ngModelChange)=\"change.emit()\">\n <mat-option value=\"filled\">Filled</mat-option>\n <mat-option value=\"outlined\">Outlined</mat-option>\n </mat-select>\n </mat-form-field>\n</div>\n<div class=\"g gap-8\">\n @if (isCustomColor?.(model.chipColor)) {\n <div>\n <pdx-color-picker label=\"Cor personalizada\" [format]=\"'hex'\" [(ngModel)]=\"model.chipColor\"\n (ngModelChange)=\"change.emit()\"></pdx-color-picker>\n </div>\n } @else {\n <button mat-stroked-button type=\"button\" (click)=\"enableCustomColor?.(model, 'chipColor'); change.emit()\">\n Usar cor personalizada\n </button>\n }\n</div>\n<div class=\"g row-flow gap-8 mt-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button mat-stroked-button type=\"button\" (click)=\"model.chipVariant='filled'; model.chipColor='primary'; change.emit()\">Status</button>\n <button mat-stroked-button type=\"button\" (click)=\"model.chipVariant='outlined'; model.chipColor='primary'; change.emit()\">Outlined</button>\n <button mat-stroked-button type=\"button\" (click)=\"model.chipVariant='filled'; model.chipColor='warn'; change.emit()\">Cr\u00EDtico</button>\n</div>\n" }]
|
|
1708
1708
|
}], propDecorators: { model: [{
|
|
1709
1709
|
type: Input
|
|
1710
1710
|
}], paletteOptions: [{
|
|
@@ -1726,12 +1726,12 @@ class PraxisMetaEditorRatingComponent {
|
|
|
1726
1726
|
isCustomColor;
|
|
1727
1727
|
enableCustomColor;
|
|
1728
1728
|
change = new EventEmitter();
|
|
1729
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1730
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
1729
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisMetaEditorRatingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1730
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisMetaEditorRatingComponent, isStandalone: true, selector: "praxis-meta-editor-rating", inputs: { model: "model", paletteOptions: "paletteOptions", colorDotBackground: "colorDotBackground", isCustomColor: "isCustomColor", enableCustomColor: "enableCustomColor" }, outputs: { change: "change" }, ngImport: i0, template: "<div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor</mat-label>\n <mat-select [(ngModel)]=\"model.ratingColor\" (ngModelChange)=\"change.emit()\">\n @for (c of paletteOptions; track c) {\n <mat-option [value]=\"c.value\">\n <span class=\"color-dot\" [style.background]=\"colorDotBackground?.(c.value)\"></span>{{ c.label }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tamanho (px)</mat-label>\n <input matInput type=\"number\" min=\"10\" max=\"32\" [(ngModel)]=\"model.ratingSize\" (ngModelChange)=\"change.emit()\" />\n @if (model.ratingSize!=null && (model.ratingSize < 10 || model.ratingSize > 32)) {\n <mat-error>Tamanho 10\u201332</mat-error>\n }\n </mat-form-field>\n</div>\n<div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>M\u00E1ximo</mat-label>\n <input matInput type=\"number\" min=\"1\" max=\"10\" [(ngModel)]=\"model.ratingMax\" (ngModelChange)=\"change.emit()\" />\n @if (model.ratingMax!=null && (model.ratingMax < 1 || model.ratingMax > 10)) {\n <mat-error>M\u00E1ximo 1\u201310</mat-error>\n }\n </mat-form-field>\n <div class=\"muted text-caption\">Padr\u00E3o: 5</div>\n</div>\n<div class=\"g gap-8\">\n @if (isCustomColor?.(model.ratingColor)) {\n <div>\n <pdx-color-picker label=\"Cor personalizada\" [format]=\"'hex'\" [(ngModel)]=\"model.ratingColor\"\n (ngModelChange)=\"change.emit()\"></pdx-color-picker>\n </div>\n } @else {\n <button mat-stroked-button type=\"button\" (click)=\"enableCustomColor?.(model, 'ratingColor'); change.emit()\">\n Usar cor personalizada\n </button>\n }\n</div>\n<div class=\"g row-flow gap-8 mt-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button mat-stroked-button type=\"button\" (click)=\"model.ratingSize=12; model.ratingMax=5; model.ratingColor='primary'; change.emit()\">Compacto</button>\n <button mat-stroked-button type=\"button\" (click)=\"model.ratingSize=16; model.ratingMax=5; model.ratingColor='primary'; change.emit()\">Padr\u00E3o</button>\n <button mat-stroked-button type=\"button\" (click)=\"model.ratingSize=20; model.ratingMax=5; model.ratingColor='accent'; change.emit()\">Destaque</button>\n</div>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.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$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.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: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: PdxColorPickerComponent, selector: "pdx-color-picker", inputs: ["actionsLayout", "activeView", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "clearButton", "disabledMode", "readonlyMode", "visible", "presentationMode", "fillMode", "format", "gradientSettings", "icon", "iconClass", "svgIcon", "paletteSettings", "popupSettings", "preview", "rounded", "size", "tabindex", "views", "maxRecent", "showRecent"], outputs: ["valueChange", "open", "close", "cancel", "activeViewChange", "activeColorClick", "focusEvent", "blurEvent"] }] });
|
|
1731
1731
|
}
|
|
1732
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1732
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisMetaEditorRatingComponent, decorators: [{
|
|
1733
1733
|
type: Component,
|
|
1734
|
-
args: [{ selector: 'praxis-meta-editor-rating', standalone: true, imports: [
|
|
1734
|
+
args: [{ selector: 'praxis-meta-editor-rating', standalone: true, imports: [FormsModule, MatFormFieldModule, MatInputModule, MatSelectModule, MatButtonModule, PdxColorPickerComponent], template: "<div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor</mat-label>\n <mat-select [(ngModel)]=\"model.ratingColor\" (ngModelChange)=\"change.emit()\">\n @for (c of paletteOptions; track c) {\n <mat-option [value]=\"c.value\">\n <span class=\"color-dot\" [style.background]=\"colorDotBackground?.(c.value)\"></span>{{ c.label }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tamanho (px)</mat-label>\n <input matInput type=\"number\" min=\"10\" max=\"32\" [(ngModel)]=\"model.ratingSize\" (ngModelChange)=\"change.emit()\" />\n @if (model.ratingSize!=null && (model.ratingSize < 10 || model.ratingSize > 32)) {\n <mat-error>Tamanho 10\u201332</mat-error>\n }\n </mat-form-field>\n</div>\n<div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>M\u00E1ximo</mat-label>\n <input matInput type=\"number\" min=\"1\" max=\"10\" [(ngModel)]=\"model.ratingMax\" (ngModelChange)=\"change.emit()\" />\n @if (model.ratingMax!=null && (model.ratingMax < 1 || model.ratingMax > 10)) {\n <mat-error>M\u00E1ximo 1\u201310</mat-error>\n }\n </mat-form-field>\n <div class=\"muted text-caption\">Padr\u00E3o: 5</div>\n</div>\n<div class=\"g gap-8\">\n @if (isCustomColor?.(model.ratingColor)) {\n <div>\n <pdx-color-picker label=\"Cor personalizada\" [format]=\"'hex'\" [(ngModel)]=\"model.ratingColor\"\n (ngModelChange)=\"change.emit()\"></pdx-color-picker>\n </div>\n } @else {\n <button mat-stroked-button type=\"button\" (click)=\"enableCustomColor?.(model, 'ratingColor'); change.emit()\">\n Usar cor personalizada\n </button>\n }\n</div>\n<div class=\"g row-flow gap-8 mt-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button mat-stroked-button type=\"button\" (click)=\"model.ratingSize=12; model.ratingMax=5; model.ratingColor='primary'; change.emit()\">Compacto</button>\n <button mat-stroked-button type=\"button\" (click)=\"model.ratingSize=16; model.ratingMax=5; model.ratingColor='primary'; change.emit()\">Padr\u00E3o</button>\n <button mat-stroked-button type=\"button\" (click)=\"model.ratingSize=20; model.ratingMax=5; model.ratingColor='accent'; change.emit()\">Destaque</button>\n</div>\n" }]
|
|
1735
1735
|
}], propDecorators: { model: [{
|
|
1736
1736
|
type: Input
|
|
1737
1737
|
}], paletteOptions: [{
|
|
@@ -1749,12 +1749,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1749
1749
|
class PraxisMetaEditorCurrencyComponent {
|
|
1750
1750
|
model;
|
|
1751
1751
|
change = new EventEmitter();
|
|
1752
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1753
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
1752
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisMetaEditorCurrencyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1753
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.14", type: PraxisMetaEditorCurrencyComponent, isStandalone: true, selector: "praxis-meta-editor-currency", inputs: { model: "model" }, outputs: { change: "change" }, ngImport: i0, template: "<div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Moeda</mat-label>\n <input matInput [(ngModel)]=\"model.currencyCode\" (ngModelChange)=\"change.emit()\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Locale</mat-label>\n <input matInput [(ngModel)]=\"model.locale\" (ngModelChange)=\"change.emit()\" />\n </mat-form-field>\n</div>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.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"] }] });
|
|
1754
1754
|
}
|
|
1755
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1755
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisMetaEditorCurrencyComponent, decorators: [{
|
|
1756
1756
|
type: Component,
|
|
1757
|
-
args: [{ selector: 'praxis-meta-editor-currency', standalone: true, imports: [
|
|
1757
|
+
args: [{ selector: 'praxis-meta-editor-currency', standalone: true, imports: [FormsModule, MatFormFieldModule, MatInputModule], template: "<div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Moeda</mat-label>\n <input matInput [(ngModel)]=\"model.currencyCode\" (ngModelChange)=\"change.emit()\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Locale</mat-label>\n <input matInput [(ngModel)]=\"model.locale\" (ngModelChange)=\"change.emit()\" />\n </mat-form-field>\n</div>\n" }]
|
|
1758
1758
|
}], propDecorators: { model: [{
|
|
1759
1759
|
type: Input
|
|
1760
1760
|
}], change: [{
|
|
@@ -1764,12 +1764,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1764
1764
|
class PraxisMetaEditorDateComponent {
|
|
1765
1765
|
model;
|
|
1766
1766
|
change = new EventEmitter();
|
|
1767
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1768
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
1767
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisMetaEditorDateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1768
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.14", type: PraxisMetaEditorDateComponent, isStandalone: true, selector: "praxis-meta-editor-date", inputs: { model: "model" }, outputs: { change: "change" }, ngImport: i0, template: "<div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Formato</mat-label>\n <mat-select [(ngModel)]=\"model.dateStyle\" (ngModelChange)=\"change.emit()\">\n <mat-option value=\"short\">Curto</mat-option>\n <mat-option value=\"medium\">M\u00E9dio</mat-option>\n <mat-option value=\"long\">Longo</mat-option>\n <mat-option value=\"full\">Completo</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Locale</mat-label>\n <input matInput [(ngModel)]=\"model.locale\" (ngModelChange)=\"change.emit()\" />\n </mat-form-field>\n</div>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.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: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }] });
|
|
1769
1769
|
}
|
|
1770
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1770
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisMetaEditorDateComponent, decorators: [{
|
|
1771
1771
|
type: Component,
|
|
1772
|
-
args: [{ selector: 'praxis-meta-editor-date', standalone: true, imports: [
|
|
1772
|
+
args: [{ selector: 'praxis-meta-editor-date', standalone: true, imports: [FormsModule, MatFormFieldModule, MatInputModule, MatSelectModule], template: "<div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Formato</mat-label>\n <mat-select [(ngModel)]=\"model.dateStyle\" (ngModelChange)=\"change.emit()\">\n <mat-option value=\"short\">Curto</mat-option>\n <mat-option value=\"medium\">M\u00E9dio</mat-option>\n <mat-option value=\"long\">Longo</mat-option>\n <mat-option value=\"full\">Completo</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Locale</mat-label>\n <input matInput [(ngModel)]=\"model.locale\" (ngModelChange)=\"change.emit()\" />\n </mat-form-field>\n</div>\n" }]
|
|
1773
1773
|
}], propDecorators: { model: [{
|
|
1774
1774
|
type: Input
|
|
1775
1775
|
}], change: [{
|
|
@@ -1783,12 +1783,12 @@ class PraxisMetaEditorIconComponent {
|
|
|
1783
1783
|
isCustomColor;
|
|
1784
1784
|
enableCustomColor;
|
|
1785
1785
|
change = new EventEmitter();
|
|
1786
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1787
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
1786
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisMetaEditorIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1787
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisMetaEditorIconComponent, isStandalone: true, selector: "praxis-meta-editor-icon", inputs: { model: "model", paletteOptions: "paletteOptions", colorDotBackground: "colorDotBackground", isCustomColor: "isCustomColor", enableCustomColor: "enableCustomColor" }, outputs: { change: "change" }, ngImport: i0, template: "<div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor</mat-label>\n <mat-select [(ngModel)]=\"model.iconColor\" (ngModelChange)=\"change.emit()\">\n @for (c of paletteOptions; track c) {\n <mat-option [value]=\"c.value\">\n <span class=\"color-dot\" [style.background]=\"colorDotBackground?.(c.value)\"></span>{{ c.label }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n</div>\n<div class=\"g gap-8\">\n @if (isCustomColor?.(model.iconColor)) {\n <div>\n <pdx-color-picker label=\"Cor personalizada\" [format]=\"'hex'\" [(ngModel)]=\"model.iconColor\"\n (ngModelChange)=\"change.emit()\"></pdx-color-picker>\n </div>\n } @else {\n <button mat-stroked-button type=\"button\" (click)=\"enableCustomColor?.(model, 'iconColor'); change.emit()\">\n Usar cor personalizada\n </button>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: PdxColorPickerComponent, selector: "pdx-color-picker", inputs: ["actionsLayout", "activeView", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "clearButton", "disabledMode", "readonlyMode", "visible", "presentationMode", "fillMode", "format", "gradientSettings", "icon", "iconClass", "svgIcon", "paletteSettings", "popupSettings", "preview", "rounded", "size", "tabindex", "views", "maxRecent", "showRecent"], outputs: ["valueChange", "open", "close", "cancel", "activeViewChange", "activeColorClick", "focusEvent", "blurEvent"] }] });
|
|
1788
1788
|
}
|
|
1789
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1789
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisMetaEditorIconComponent, decorators: [{
|
|
1790
1790
|
type: Component,
|
|
1791
|
-
args: [{ selector: 'praxis-meta-editor-icon', standalone: true, imports: [
|
|
1791
|
+
args: [{ selector: 'praxis-meta-editor-icon', standalone: true, imports: [FormsModule, MatFormFieldModule, MatSelectModule, MatButtonModule, PdxColorPickerComponent], template: "<div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor</mat-label>\n <mat-select [(ngModel)]=\"model.iconColor\" (ngModelChange)=\"change.emit()\">\n @for (c of paletteOptions; track c) {\n <mat-option [value]=\"c.value\">\n <span class=\"color-dot\" [style.background]=\"colorDotBackground?.(c.value)\"></span>{{ c.label }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n</div>\n<div class=\"g gap-8\">\n @if (isCustomColor?.(model.iconColor)) {\n <div>\n <pdx-color-picker label=\"Cor personalizada\" [format]=\"'hex'\" [(ngModel)]=\"model.iconColor\"\n (ngModelChange)=\"change.emit()\"></pdx-color-picker>\n </div>\n } @else {\n <button mat-stroked-button type=\"button\" (click)=\"enableCustomColor?.(model, 'iconColor'); change.emit()\">\n Usar cor personalizada\n </button>\n }\n</div>\n" }]
|
|
1792
1792
|
}], propDecorators: { model: [{
|
|
1793
1793
|
type: Input
|
|
1794
1794
|
}], paletteOptions: [{
|
|
@@ -1806,12 +1806,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1806
1806
|
class PraxisMetaEditorImageComponent {
|
|
1807
1807
|
model;
|
|
1808
1808
|
change = new EventEmitter();
|
|
1809
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1810
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
1809
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisMetaEditorImageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1810
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.14", type: PraxisMetaEditorImageComponent, isStandalone: true, selector: "praxis-meta-editor-image", inputs: { model: "model" }, outputs: { change: "change" }, ngImport: i0, template: "<div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\" class=\"col-span-2\">\n <mat-label>Alt da imagem</mat-label>\n <input matInput [(ngModel)]=\"model.imageAlt\" (ngModelChange)=\"change.emit()\" />\n </mat-form-field>\n</div>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.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"] }] });
|
|
1811
1811
|
}
|
|
1812
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1812
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisMetaEditorImageComponent, decorators: [{
|
|
1813
1813
|
type: Component,
|
|
1814
|
-
args: [{ selector: 'praxis-meta-editor-image', standalone: true, imports: [
|
|
1814
|
+
args: [{ selector: 'praxis-meta-editor-image', standalone: true, imports: [FormsModule, MatFormFieldModule, MatInputModule], template: "<div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\" class=\"col-span-2\">\n <mat-label>Alt da imagem</mat-label>\n <input matInput [(ngModel)]=\"model.imageAlt\" (ngModelChange)=\"change.emit()\" />\n </mat-form-field>\n</div>\n" }]
|
|
1815
1815
|
}], propDecorators: { model: [{
|
|
1816
1816
|
type: Input
|
|
1817
1817
|
}], change: [{
|
|
@@ -3277,8 +3277,8 @@ class PraxisListJsonConfigEditorComponent {
|
|
|
3277
3277
|
hasUnsavedLocalChanges() {
|
|
3278
3278
|
return this.jsonText.trim() !== this.lastSyncedJsonText.trim();
|
|
3279
3279
|
}
|
|
3280
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
3281
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
3280
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisListJsonConfigEditorComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
3281
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisListJsonConfigEditorComponent, isStandalone: true, selector: "praxis-list-json-config-editor", inputs: { document: "document" }, outputs: { documentChange: "documentChange", validationChange: "validationChange", editorEvent: "editorEvent" }, usesOnChanges: true, ngImport: i0, template: `
|
|
3282
3282
|
<div class="json-config-editor">
|
|
3283
3283
|
<mat-card class="educational-card">
|
|
3284
3284
|
<mat-card-header>
|
|
@@ -3338,18 +3338,17 @@ class PraxisListJsonConfigEditorComponent {
|
|
|
3338
3338
|
</mat-form-field>
|
|
3339
3339
|
</div>
|
|
3340
3340
|
</div>
|
|
3341
|
-
`, isInline: true, styles: [".json-config-editor{display:flex;flex-direction:column;gap:16px}.educational-card{background-color:var(--md-sys-color-surface-container-low);border-left:4px solid var(--md-sys-color-primary)}.card-icon{background-color:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container);font-size:20px;width:40px;height:40px;display:flex;align-items:center;justify-content:center}.json-editor-section{display:flex;flex-direction:column;gap:16px}.json-editor-toolbar{display:flex;gap:12px;padding:12px;background-color:var(--md-sys-color-surface-container-low);border-radius:8px;border:1px solid var(--md-sys-color-outline-variant)}.json-textarea-field{width:100%}.pending-update-note{margin:0;color:var(--md-sys-color-secondary);font-size:.875rem}.json-textarea{font-family:Monaco,Menlo,Ubuntu Mono,Consolas,monospace!important;font-size:13px!important;line-height:1.4!important;min-height:320px!important;white-space:pre!important;overflow-wrap:normal!important;overflow-x:auto!important;resize:none!important}.valid-hint{color:var(--md-sys-color-primary)!important}.warning-hint{color:var(--md-sys-color-secondary)!important}\n"], dependencies: [{ kind: "ngmodule", type:
|
|
3341
|
+
`, isInline: true, styles: [".json-config-editor{display:flex;flex-direction:column;gap:16px}.educational-card{background-color:var(--md-sys-color-surface-container-low);border-left:4px solid var(--md-sys-color-primary)}.card-icon{background-color:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container);font-size:20px;width:40px;height:40px;display:flex;align-items:center;justify-content:center}.json-editor-section{display:flex;flex-direction:column;gap:16px}.json-editor-toolbar{display:flex;gap:12px;padding:12px;background-color:var(--md-sys-color-surface-container-low);border-radius:8px;border:1px solid var(--md-sys-color-outline-variant)}.json-textarea-field{width:100%}.pending-update-note{margin:0;color:var(--md-sys-color-secondary);font-size:.875rem}.json-textarea{font-family:Monaco,Menlo,Ubuntu Mono,Consolas,monospace!important;font-size:13px!important;line-height:1.4!important;min-height:320px!important;white-space:pre!important;overflow-wrap:normal!important;overflow-x:auto!important;resize:none!important}.valid-hint{color:var(--md-sys-color-primary)!important}.warning-hint{color:var(--md-sys-color-secondary)!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i3$2.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i3$2.MatCardAvatar, selector: "[mat-card-avatar], [matCardAvatar]" }, { kind: "directive", type: i3$2.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i3$2.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i3$2.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i2.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3342
3342
|
}
|
|
3343
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
3343
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisListJsonConfigEditorComponent, decorators: [{
|
|
3344
3344
|
type: Component,
|
|
3345
3345
|
args: [{ selector: 'praxis-list-json-config-editor', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
3346
|
-
CommonModule,
|
|
3347
3346
|
FormsModule,
|
|
3348
3347
|
MatButtonModule,
|
|
3349
3348
|
MatCardModule,
|
|
3350
3349
|
MatFormFieldModule,
|
|
3351
3350
|
MatIconModule,
|
|
3352
|
-
MatInputModule
|
|
3351
|
+
MatInputModule
|
|
3353
3352
|
], template: `
|
|
3354
3353
|
<div class="json-config-editor">
|
|
3355
3354
|
<mat-card class="educational-card">
|
|
@@ -6034,8 +6033,8 @@ class PraxisListConfigEditor {
|
|
|
6034
6033
|
return 'horizontal_rule';
|
|
6035
6034
|
}
|
|
6036
6035
|
}
|
|
6037
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
6038
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
6036
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisListConfigEditor, deps: [{ token: SETTINGS_PANEL_DATA, optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
6037
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisListConfigEditor, isStandalone: true, selector: "praxis-list-config-editor", inputs: { config: "config", listId: "listId" }, providers: [
|
|
6039
6038
|
{
|
|
6040
6039
|
provide: PRAXIS_I18N_CONFIG,
|
|
6041
6040
|
multi: true,
|
|
@@ -6047,9 +6046,9 @@ class PraxisListConfigEditor {
|
|
|
6047
6046
|
},
|
|
6048
6047
|
providePraxisI18nConfig(SURFACE_OPEN_I18N_CONFIG),
|
|
6049
6048
|
providePraxisListI18n(),
|
|
6050
|
-
], ngImport: i0, template: "<mat-tab-group class=\"list-editor-tabs\">\n <mat-tab [label]=\"tx('Data')\">\n <ng-template matTabContent>\n <div class=\"editor-content\">\n <div class=\"g g-1-auto gap-8 ai-center\">\n <div class=\"muted\">\n {{ tx('Assistant-applied adjustments replace the entire configuration object.') }}\n </div>\n <button\n mat-icon-button\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('applyConfigFromAdapter does not perform a deep merge. Make sure the adapter sends the full config.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </div>\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>{{ tx('Resource (API)') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.dataSource.resourcePath\"\n (ngModelChange)=\"onResourcePathChange($event)\"\n [placeholder]=\"tx('e.g.: users')\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Resource endpoint (resourcePath).')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>{{ tx('Query (JSON)') }}</mat-label>\n <textarea\n matInput\n rows=\"3\"\n [(ngModel)]=\"queryJson\"\n (ngModelChange)=\"onQueryChanged($event)\"\n [placeholder]=\"queryPlaceholder()\"\n ></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Optional. Use valid JSON for initial filters.')\"\n *ngIf=\"!queryError\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error *ngIf=\"queryError\">{{ queryError }}</mat-error>\n </mat-form-field>\n <div class=\"g g-auto-220 gap-12 ai-end mt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Sort by') }}</mat-label>\n <mat-select\n [(ngModel)]=\"sortField\"\n (ngModelChange)=\"updateSortConfig()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Resource base field.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Direction') }}</mat-label>\n <mat-select\n [(ngModel)]=\"sortDir\"\n (ngModelChange)=\"updateSortConfig()\"\n >\n <mat-option value=\"asc\">{{ tx('Ascending') }}</mat-option>\n <mat-option value=\"desc\">{{ tx('Descending') }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('JSON')\">\n <ng-template matTabContent>\n <div class=\"editor-content\">\n <praxis-list-json-config-editor\n [document]=\"document\"\n (documentChange)=\"onJsonConfigChange($event)\"\n (validationChange)=\"onJsonValidationChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n >\n </praxis-list-json-config-editor>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Actions')\">\n <ng-template matTabContent>\n <div class=\"editor-content g gap-12\">\n <div class=\"g g-1-auto gap-8 ai-center\">\n <div class=\"muted\">\n {{ tx('Configure per-item action buttons (icon, label, color, visibility)') }}\n </div>\n <button mat-flat-button color=\"primary\" (click)=\"addAction()\">\n {{ tx('Add action') }}\n </button>\n </div>\n <div class=\"g g-1-auto gap-8 ai-center\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Global action (Praxis)') }}</mat-label>\n <mat-select\n [(ngModel)]=\"selectedGlobalActionId\"\n (ngModelChange)=\"onGlobalActionSelected($event)\"\n >\n <mat-option [value]=\"undefined\">{{ tx('-- Select --') }}</mat-option>\n <mat-option *ngFor=\"let ga of globalActionCatalog\" [value]=\"ga.id\">\n <mat-icon class=\"option-icon\">{{ ga.icon || \"bolt\" }}</mat-icon>\n {{ ga.label }}\n </mat-option>\n </mat-select>\n <mat-hint\n *ngIf=\"!globalActionCatalog.length\"\n class=\"text-caption muted\"\n >{{ tx('No global action registered.') }}</mat-hint\n >\n </mat-form-field>\n <div class=\"muted text-caption\">\n {{ tx('Select to add with a structured global action.') }}\n </div>\n </div>\n <div\n *ngFor=\"let a of working.actions || []; let i = index\"\n class=\"g g-auto-200 gap-12 ai-end\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('ID') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.id\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Action type') }}</mat-label>\n <mat-select [(ngModel)]=\"a.kind\" (ngModelChange)=\"onActionsChanged()\">\n <mat-option value=\"icon\">{{ tx('Icon') }}</mat-option>\n <mat-option value=\"button\">{{ tx('Button') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Icon') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.icon\"\n (ngModelChange)=\"onActionsChanged()\"\n [placeholder]=\"tx('e.g.: edit, delete')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Global action') }}</mat-label>\n <mat-select\n [ngModel]=\"getGlobalActionId(a)\"\n (ngModelChange)=\"onActionGlobalActionIdChange(a, $event)\"\n >\n <mat-option value=\"\">{{ tx('None') }}</mat-option>\n <mat-option *ngFor=\"let action of globalActionCatalog\" [value]=\"action.id\">\n {{ action.label || action.id }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Label') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.label\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n </mat-form-field>\n <ng-container *ngIf=\"a.kind === 'button'\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Variant') }}</mat-label>\n <mat-select\n [(ngModel)]=\"a.buttonVariant\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option value=\"stroked\">{{ tx('Outlined') }}</mat-option>\n <mat-option value=\"raised\">{{ tx('Raised') }}</mat-option>\n <mat-option value=\"flat\">{{ tx('Filled') }}</mat-option>\n </mat-select>\n </mat-form-field>\n </ng-container>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Action color') }}</mat-label>\n <mat-select\n [(ngModel)]=\"a.color\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option *ngFor=\"let c of paletteOptions\" [value]=\"c.value\">\n <span\n class=\"color-dot\"\n [style.background]=\"colorDotBackground(c.value)\"\n ></span\n >{{ tx(c.label) }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <div\n class=\"g gap-8\"\n *ngIf=\"isCustomColor(a.color); else actionCustomBtn\"\n >\n <pdx-color-picker\n [label]=\"tx('Custom color')\"\n [format]=\"'hex'\"\n [(ngModel)]=\"a.color\"\n (ngModelChange)=\"onActionsChanged()\"\n ></pdx-color-picker>\n </div>\n <ng-template #actionCustomBtn>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"enableCustomActionColor(a)\"\n >\n {{ tx('Use custom color') }}\n </button>\n </ng-template>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Action payload') }}</mat-label>\n <mat-select\n [(ngModel)]=\"a.emitPayload\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option [value]=\"undefined\">{{ tx('Default') }}</mat-option>\n <mat-option value=\"item\">{{ tx('Item') }}</mat-option>\n <mat-option value=\"id\">{{ tx('ID') }}</mat-option>\n <mat-option value=\"value\">{{ tx('Value') }}</mat-option>\n </mat-select>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Payload emitted by the action.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"col-span-2\">\n <mat-label>{{ actionVisibilityLabel() }}</mat-label>\n <textarea\n matInput\n rows=\"3\"\n [ngModel]=\"actionShowIfModel(i)\"\n (ngModelChange)=\"onActionShowIfChanged(i, a, $event)\"\n ></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"actionConditionTooltip()\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"g row-flow gap-8 ai-center\">\n <button\n *ngIf=\"(a.kind || 'icon') === 'icon'\"\n mat-icon-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n >\n <mat-icon\n [praxisIcon]=\"a.icon || 'bolt'\"\n [style.cssText]=\"iconStyle(a.color)\"\n ></mat-icon>\n </button>\n <ng-container *ngIf=\"a.kind === 'button'\">\n <button\n *ngIf=\"a.buttonVariant === 'stroked'\"\n mat-stroked-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'stroked')\"\n >\n {{ a.label || a.id || tx('Action') }}\n </button>\n <button\n *ngIf=\"a.buttonVariant === 'raised'\"\n mat-raised-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'raised')\"\n >\n {{ a.label || a.id || tx('Action') }}\n </button>\n <button\n *ngIf=\"!a.buttonVariant || a.buttonVariant === 'flat'\"\n mat-flat-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'flat')\"\n >\n {{ a.label || a.id || tx('Action') }}\n </button>\n </ng-container>\n <span class=\"muted\">{{ tx('Preview') }}</span>\n </div>\n <div class=\"flex-end\">\n <button mat-button color=\"warn\" (click)=\"removeAction(i)\">\n {{ tx('Remove') }}\n </button>\n </div>\n <div class=\"g gap-8 col-span-2\" *ngIf=\"a.globalAction?.actionId\">\n <mat-slide-toggle\n [(ngModel)]=\"a.showLoading\"\n (ngModelChange)=\"onActionsChanged()\"\n >{{ tx('Show loading') }}</mat-slide-toggle\n >\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >{{ tx('Confirmation') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <div class=\"g row-flow gap-8 ai-center\">\n <span class=\"text-caption muted\">{{ tx('Type') }}</span>\n <mat-button-toggle-group\n [value]=\"a.confirmation?.type || ''\"\n (change)=\"applyConfirmationPreset(a, $event.value)\"\n >\n <mat-button-toggle value=\"\">{{ tx('Default') }}</mat-button-toggle>\n <mat-button-toggle value=\"danger\">{{ tx('Danger') }}</mat-button-toggle>\n <mat-button-toggle value=\"warning\">{{ tx('Warning') }}</mat-button-toggle>\n <mat-button-toggle value=\"info\">{{ tx('Info') }}</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Title') }}</mat-label>\n <input\n matInput\n [ngModel]=\"a.confirmation?.title\"\n (ngModelChange)=\"setConfirmationField(a, 'title', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Message') }}</mat-label>\n <input\n matInput\n [ngModel]=\"a.confirmation?.message\"\n (ngModelChange)=\"setConfirmationField(a, 'message', $event)\"\n />\n </mat-form-field>\n <div class=\"g gap-6\">\n <div class=\"text-caption muted\">{{ tx('Preview') }}</div>\n <div class=\"text-caption\">\n <strong>{{\n a.confirmation?.title || tx('Confirm action')\n }}</strong>\n </div>\n <div class=\"text-caption muted\">\n {{\n a.confirmation?.message ||\n tx('Are you sure you want to continue?')\n }}\n </div>\n <div class=\"text-caption\">\n <span\n class=\"confirm-type\"\n [ngClass]=\"a.confirmation?.type || 'default'\"\n >{{ tx('Type') }}:\n {{ a.confirmation?.type || tx('Default').toLowerCase() }}</span\n >\n </div>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!a.confirmation?.title && !a.confirmation?.message\"\n >\n {{ tx('Set a title or message for the confirmation.') }}\n </div>\n </div>\n </div>\n </mat-expansion-panel>\n <ng-container *ngIf=\"isSurfaceOpenCommand(a); else defaultGlobalActionPayloadEditor\">\n <div class=\"col-span-2\">\n <praxis-surface-open-action-editor\n [value]=\"getSurfaceOpenGlobalActionPayload(a)\"\n hostKind=\"list\"\n (valueChange)=\"onSurfaceOpenGlobalActionPayloadChange(a, $event)\"\n ></praxis-surface-open-action-editor>\n </div>\n </ng-container>\n <ng-template #defaultGlobalActionPayloadEditor>\n <mat-form-field appearance=\"outline\" class=\"col-span-2\">\n <mat-label>{{ tx('Payload (JSON/Template)') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"getGlobalActionPayloadText(a)\"\n (ngModelChange)=\"onGlobalActionPayloadTextChange(a, $event)\"\n placeholder='{\"message\":\"${item.name} favoritado\"}'\n ></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"globalActionPayloadSchemaTooltip(a)\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error *ngIf=\"isGlobalActionPayloadInvalid(a)\"\n >{{ tx('Invalid JSON') }}</mat-error\n >\n </mat-form-field>\n <div class=\"g row-flow gap-8 ai-center\">\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"applyGlobalActionPayloadExample(a)\"\n >\n {{ tx('Insert example') }}\n </button>\n <span class=\"muted text-caption\">{{\n globalActionPayloadExampleHint(a)\n }}</span>\n </div>\n </ng-template>\n <mat-slide-toggle\n [(ngModel)]=\"a.emitLocal\"\n (ngModelChange)=\"onActionsChanged()\"\n >{{ tx('Emit local event too') }}</mat-slide-toggle\n >\n </div>\n </div>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Layout')\">\n <ng-template matTabContent>\n <div class=\"editor-content grid gap-3\">\n <div class=\"preset-row g row-flow gap-8\">\n <button mat-stroked-button (click)=\"applyLayoutPreset('tiles-modern')\">\n {{ tx('Modern tiles preset') }}\n </button>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Variant') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.variant\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"list\">{{ tx('List') }}</mat-option>\n <mat-option value=\"cards\">{{ tx('Cards') }}</mat-option>\n <mat-option value=\"tiles\">{{ tx('Tiles') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Model') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.model\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <ng-container\n *ngIf=\"working.layout.variant === 'list'; else cardModels\"\n >\n <mat-option value=\"standard\">{{ tx('Standard') }}</mat-option>\n <mat-option value=\"media\">{{ tx('Media on the left') }}</mat-option>\n <mat-option value=\"hotel\">{{ tx('Hotel (large media)') }}</mat-option>\n </ng-container>\n <ng-template #cardModels>\n <ng-container\n *ngIf=\"working.layout.variant === 'tiles'; else cardsOnly\"\n >\n <mat-option value=\"standard\">{{ tx('Standard tile') }}</mat-option>\n <mat-option value=\"media\">{{ tx('Tile with media') }}</mat-option>\n <mat-option value=\"hotel\">{{ tx('Hotel tile') }}</mat-option>\n </ng-container>\n <ng-template #cardsOnly>\n <mat-option value=\"standard\">{{ tx('Standard') }}</mat-option>\n <mat-option value=\"media\">{{ tx('Card with media') }}</mat-option>\n <mat-option value=\"hotel\">{{ tx('Hotel') }}</mat-option>\n </ng-template>\n </ng-template>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Lines') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.lines\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option [value]=\"1\">1</mat-option>\n <mat-option [value]=\"2\">2</mat-option>\n <mat-option [value]=\"3\">3</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Items per page') }}</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"1\"\n [(ngModel)]=\"working.layout.pageSize\"\n (ngModelChange)=\"onPageSizeChange($event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Density') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.density\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"default\">{{ tx('Default') }}</mat-option>\n <mat-option value=\"comfortable\">{{ tx('Comfortable') }}</mat-option>\n <mat-option value=\"compact\">{{ tx('Compact') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Spacing between items') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.itemSpacing\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"none\">{{ tx('No extra space') }}</mat-option>\n <mat-option value=\"tight\">{{ tx('Tight') }}</mat-option>\n <mat-option value=\"default\">{{ tx('Standard') }}</mat-option>\n <mat-option value=\"relaxed\">{{ tx('Relaxed') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"working.layout.variant !== 'tiles'\"\n >\n <mat-label>{{ tx('Dividers') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.dividers\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"none\">{{ tx('None') }}</mat-option>\n <mat-option value=\"between\">{{ tx('Between groups') }}</mat-option>\n <mat-option value=\"all\">{{ tx('All') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <ng-container *ngIf=\"fields.length > 0; else groupByText\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Group by') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.groupBy\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option [value]=\"\">{{ tx('None') }}</mat-option>\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n </ng-container>\n <ng-template #groupByText>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Group by') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.layout.groupBy\"\n (ngModelChange)=\"onLayoutChanged()\"\n [placeholder]=\"tx('e.g.: department')\"\n />\n </mat-form-field>\n </ng-template>\n <mat-slide-toggle\n [(ngModel)]=\"working.layout.stickySectionHeader\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n {{ tx('Sticky section header') }}\n </mat-slide-toggle>\n <mat-slide-toggle\n [(ngModel)]=\"working.layout.virtualScroll\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n {{ tx('Virtual scroll') }}\n </mat-slide-toggle>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">{{ tx('List tools') }}</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showSearch\"\n (ngModelChange)=\"onUiChanged()\"\n >{{ tx('Show search') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showSort\"\n (ngModelChange)=\"onUiChanged()\"\n >{{ tx('Show sorting') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showRange\"\n (ngModelChange)=\"onUiChanged()\"\n >{{ tx('Show total X-Y range') }}</mat-slide-toggle\n >\n </div>\n <div\n class=\"g g-auto-220 gap-12 ai-end mt-12\"\n *ngIf=\"working.ui?.showSearch\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field to search') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.ui.searchField\"\n (ngModelChange)=\"onUiChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Search placeholder') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.ui.searchPlaceholder\"\n (ngModelChange)=\"onUiChanged()\"\n [placeholder]=\"tx('e.g.: Search by title')\"\n />\n </mat-form-field>\n </div>\n <div class=\"mt-12\" *ngIf=\"working.ui?.showSort\">\n <div class=\"g g-1-auto ai-center gap-8\">\n <div class=\"muted\">\n {{ tx('Sorting options (label \u2192 field+direction)') }}\n </div>\n <button mat-flat-button color=\"primary\" (click)=\"addUiSortRow()\">\n {{ tx('Add option') }}\n </button>\n </div>\n <div\n class=\"g g-auto-220 gap-12 ai-end mt-12\"\n *ngFor=\"let r of uiSortRows; let i = index\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Label') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"r.label\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n [placeholder]=\"tx('e.g.: Most recent')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"r.field\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Direction') }}</mat-label>\n <mat-select\n [(ngModel)]=\"r.dir\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n >\n <mat-option value=\"desc\">{{ tx('Descending') }}</mat-option>\n <mat-option value=\"asc\">{{ tx('Ascending') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"error\" *ngIf=\"isUiSortRowDuplicate(i)\">\n {{ tx('Duplicate option (field+direction)') }}\n </div>\n <div class=\"flex-end\">\n <button mat-button color=\"warn\" (click)=\"removeUiSortRow(i)\">\n {{ tx('Remove') }}\n </button>\n </div>\n </div>\n </div>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Content')\">\n <ng-template matTabContent>\n <div class=\"editor-content\">\n <div class=\"editor-main\">\n <mat-accordion multi>\n <!-- Primary -->\n <mat-expansion-panel [expanded]=\"true\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingPrimary.type) }}</mat-icon>\n <span>{{ tx('Primary (Title)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingPrimary.field || tx('Not mapped')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'name';\n onMappingChanged()\n \"\n >\n {{ tx('Name') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'title';\n onMappingChanged()\n \"\n >\n {{ tx('Title') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'name';\n mappingSecondary.type = 'text';\n mappingSecondary.field = 'role';\n onMappingChanged()\n \"\n >\n {{ tx('Name + role') }}\n </button>\n </div>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingPrimary.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingPrimary.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of primaryTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n @switch (mappingPrimary.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingPrimary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingPrimary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingPrimary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingPrimary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n }\n\n <!-- Advanced -->\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header>\n <mat-panel-title>{{ tx('Formatting and style') }}</mat-panel-title>\n </mat-expansion-panel-header>\n <div class=\"g gap-12 pt-12\">\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"\n mappingPrimary.type === 'text' ||\n mappingPrimary.type === 'html'\n \"\n >\n <mat-label>{{ tx('CSS class') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"\n mappingPrimary.type === 'text' ||\n mappingPrimary.type === 'html'\n \"\n >\n <mat-label>{{ tx('Inline style') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('CSS class') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Inline style') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Secondary -->\n <mat-expansion-panel [expanded]=\"!!mappingSecondary.field\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingSecondary.type) }}</mat-icon>\n <span>{{ tx('Secondary (Summary)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingSecondary.field || tx('Not mapped')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'text';\n mappingSecondary.field = 'subtitle';\n onMappingChanged()\n \"\n >\n {{ tx('Subtitle') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'date';\n mappingSecondary.field = 'hireDate';\n mappingSecondary.dateStyle = 'short';\n onMappingChanged()\n \"\n >\n {{ tx('Short date') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"applySalaryPreset()\"\n >\n {{ tx('Salary') }}\n </button>\n </div>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSecondary.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSecondary.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of secondaryTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n @switch (mappingSecondary.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingSecondary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingSecondary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingSecondary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingSecondary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header>\n <mat-panel-title>{{ tx('Formatting and style') }}</mat-panel-title>\n </mat-expansion-panel-header>\n <div class=\"g gap-12 pt-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('CSS class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSecondary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Inline style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSecondary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <mat-expansion-panel\n [expanded]=\"!!mappingMeta.field || mappingMetaFields.length > 0\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingMeta.type || \"text\")\n }}</mat-icon>\n <span>{{ tx('Meta (Detail/Side)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>\n {{\n mappingMetaFields.length\n ? tx('Composed field ({{count}})', { count: mappingMetaFields.length })\n : mappingMeta.field || tx('Not mapped')\n }}\n </mat-panel-description>\n </mat-expansion-panel-header>\n\n <div class=\"g gap-12\">\n <!-- Composition Mode Toggle -->\n <div class=\"g g-1-1 gap-12 p-12 bg-subtle rounded\">\n <div class=\"text-caption muted\">{{ tx('Composition mode') }}</div>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Fields to compose (multi-select)') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMetaFields\"\n multiple\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <div\n class=\"g g-1-1 ai-center gap-12\"\n *ngIf=\"mappingMetaFields.length\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Separator') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMetaSeparator\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-slide-toggle\n [(ngModel)]=\"mappingMetaWrapSecondInParens\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n {{ tx('(Second) in parentheses') }}\n </mat-slide-toggle>\n </div>\n </div>\n\n <!-- Single Field Mode (if no composition) -->\n <div class=\"g g-1-1 gap-12\" *ngIf=\"!mappingMetaFields.length\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Single field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option [value]=\"undefined\">{{ tx('-- None --') }}</mat-option>\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of metaTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- Type configuration (pluggable editors) -->\n @switch (mappingMeta.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingMeta\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingMeta\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <praxis-meta-editor-image\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <!-- Advanced -->\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Advanced options') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Position') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.placement\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option value=\"side\">{{ tx('Side (right)') }}</mat-option>\n <mat-option value=\"line\">{{ tx('Inline (below)') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('CSS class') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMeta.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Style') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMeta.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n <!-- Trailing -->\n <mat-expansion-panel [expanded]=\"!!mappingTrailing.field\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingTrailing.type || \"text\")\n }}</mat-icon>\n <span>{{ tx('Trailing (right)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingTrailing.field || tx('Not mapped')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingTrailing.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option [value]=\"undefined\">{{ tx('-- None --') }}</mat-option>\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingTrailing.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of trailingTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'chip';\n mappingTrailing.chipColor = 'primary';\n mappingTrailing.chipVariant = 'filled';\n mappingTrailing.field = 'status';\n onMappingChanged()\n \"\n >\n {{ tx('Status chip') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'icon';\n mappingTrailing.field = 'status';\n mappingTrailing.iconColor = 'primary';\n onMappingChanged()\n \"\n >\n {{ tx('Status icon') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"applyPricePreset()\"\n >\n {{ tx('Price') }}\n </button>\n </div>\n\n @switch (mappingTrailing.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingTrailing\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingTrailing\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('URL / Expr') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingTrailing.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n [placeholder]=\"tx('https://... or ${item.imageUrl}')\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Use an absolute/relative URL or a ${item.field} expression.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error\n *ngIf=\"\n isImageUrlRequiredInvalid(mappingTrailing.imageUrl)\n \"\n >{{ tx('URL/expr is required') }}</mat-error\n >\n </mat-form-field>\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!mappingTrailing.imageUrl\"\n >\n {{ tx('Set the URL/expr to render the image.') }}\n </div>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Style') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingTrailing.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingTrailing.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Leading -->\n <mat-expansion-panel\n [expanded]=\"\n !!mappingLeading.field ||\n (mappingLeading.type === 'icon' && !!mappingLeading.icon) ||\n (mappingLeading.type === 'image' && !!mappingLeading.imageUrl)\n \"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingLeading.type) }}</mat-icon>\n <span>{{ tx('Leading (left)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>\n {{\n mappingLeading.type === \"icon\"\n ? mappingLeading.icon || tx('Static icon')\n : mappingLeading.field ||\n (mappingLeading.imageUrl\n ? tx('Static image')\n : tx('Not mapped'))\n }}\n </mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingLeading.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of leadingTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <!-- Field (only if not static icon/image, though user might want dynamic) -->\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"\n mappingLeading.type !== 'icon' &&\n mappingLeading.type !== 'image'\n \"\n >\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingLeading.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'icon';\n mappingLeading.icon = 'person';\n mappingLeading.iconColor = 'primary';\n onMappingChanged()\n \"\n >\n {{ tx('Avatar icon') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'image';\n mappingLeading.imageUrl = 'https://placehold.co/64x64';\n mappingLeading.imageAlt = 'Avatar';\n mappingLeading.badgeText = '${item.status}';\n onMappingChanged()\n \"\n >\n {{ tx('Avatar image + badge') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'chip';\n mappingLeading.field = 'tag';\n mappingLeading.chipColor = 'accent';\n mappingLeading.chipVariant = 'filled';\n onMappingChanged()\n \"\n >\n {{ tx('Tag chip') }}\n </button>\n </div>\n\n <!-- Icon Specific -->\n <div\n class=\"g g-1-auto gap-12 ai-center\"\n *ngIf=\"mappingLeading.type === 'icon'\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Icon') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.icon\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <button mat-icon-button matSuffix (click)=\"pickLeadingIcon()\">\n <mat-icon>search</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"text-caption muted\">\n {{ tx('Use the `|iconMap` pipe in the extra pipe for dynamic rendering.') }}\n </div>\n </div>\n <div *ngIf=\"mappingLeading.type === 'icon'\">\n <praxis-meta-editor-icon\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n </div>\n\n <!-- Image Specific -->\n <div\n class=\"g g-1-1 gap-12\"\n *ngIf=\"mappingLeading.type === 'image'\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Image URL') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n [placeholder]=\"tx('https://... or ${item.imageUrl}')\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Use an absolute/relative URL or a ${item.field} expression.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error\n *ngIf=\"isImageUrlRequiredInvalid(mappingLeading.imageUrl)\"\n >{{ tx('URL/expr is required') }}</mat-error\n >\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Alt text') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.imageAlt\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Badge text') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.badgeText\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n\n @switch (mappingLeading.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingLeading\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingLeading\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Style') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingLeading.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingLeading.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Features -->\n <mat-expansion-panel\n [expanded]=\"featuresVisible && features.length > 0\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>view_list</mat-icon>\n <span>{{ tx('Features') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description\n >{{ features.length }} item(s)</mat-panel-description\n >\n </mat-expansion-panel-header>\n\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-12 ai-center\">\n <mat-slide-toggle\n [(ngModel)]=\"featuresVisible\"\n (ngModelChange)=\"onFeaturesChanged()\"\n >{{ tx('Enable features') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"featuresSyncWithMeta\"\n (ngModelChange)=\"onMappingChanged()\"\n >{{ tx('Sync with Meta') }}</mat-slide-toggle\n >\n <span class=\"flex-1\"></span>\n <mat-button-toggle-group\n [(ngModel)]=\"featuresMode\"\n (change)=\"onFeaturesChanged()\"\n appearance=\"legacy\"\n >\n <mat-button-toggle value=\"icons+labels\"\n ><mat-icon>view_list</mat-icon></mat-button-toggle\n >\n <mat-button-toggle value=\"icons-only\"\n ><mat-icon>more_horiz</mat-icon></mat-button-toggle\n >\n </mat-button-toggle-group>\n </div>\n\n <div\n *ngFor=\"let f of features; let i = index\"\n class=\"g g-auto-1 gap-8 ai-center p-8 border rounded mb-2\"\n >\n <button mat-icon-button (click)=\"pickFeatureIcon(i)\">\n <mat-icon>{{ f.icon || \"search\" }}</mat-icon>\n </button>\n <mat-form-field\n appearance=\"outline\"\n class=\"dense-form-field no-sub\"\n >\n <input\n matInput\n [(ngModel)]=\"f.expr\"\n (ngModelChange)=\"onFeaturesChanged()\"\n [placeholder]=\"tx('Expression/Text')\"\n />\n </mat-form-field>\n <button mat-icon-button color=\"warn\" (click)=\"removeFeature(i)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n <button mat-button color=\"primary\" (click)=\"addFeature()\">\n <mat-icon>add</mat-icon> {{ tx('Add feature') }}\n </button>\n </div>\n </mat-expansion-panel>\n <!-- Section Header -->\n <mat-expansion-panel [expanded]=\"!!mappingSectionHeader.expr\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingSectionHeader.type)\n }}</mat-icon>\n <span>{{ tx('Section header') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingSectionHeader.expr || tx('Not configured')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSectionHeader.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of sectionHeaderTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Expression (item.key)') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingSectionHeader.expr\"\n (ngModelChange)=\"onMappingChanged()\"\n placeholder=\"item.key\"\n />\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSectionHeader.type = 'text';\n mappingSectionHeader.expr = '${item.key}';\n onMappingChanged()\n \"\n >\n {{ tx('Default text') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSectionHeader.type = 'chip';\n mappingSectionHeader.chipColor = 'primary';\n mappingSectionHeader.chipVariant = 'filled';\n mappingSectionHeader.expr = '${item.key}';\n onMappingChanged()\n \"\n >\n {{ tx('Default chip') }}\n </button>\n </div>\n\n @switch (mappingSectionHeader.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingSectionHeader\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingSectionHeader\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Image URL') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingSectionHeader.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <mat-error\n *ngIf=\"\n isImageUrlRequiredInvalid(\n mappingSectionHeader.imageUrl\n )\n \"\n >{{ tx('URL/expr is required') }}</mat-error\n >\n </mat-form-field>\n </div>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!mappingSectionHeader.imageUrl\"\n >\n {{ tx('Set the URL/expr to render the image.') }}\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingSectionHeader\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Style') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSectionHeader.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSectionHeader.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Empty State -->\n <mat-expansion-panel [expanded]=\"!!mappingEmptyState.expr\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>inbox</mat-icon>\n <span>{{ tx('Empty state') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingEmptyState.expr || tx('Default')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingEmptyState.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of emptyStateTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Message / Expr') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingEmptyState.expr\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingEmptyState.type = 'text';\n mappingEmptyState.expr = tx('No items available');\n onMappingChanged()\n \"\n >\n {{ tx('Default message') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingEmptyState.type = 'image';\n mappingEmptyState.imageUrl = '/list-empty-state.svg';\n mappingEmptyState.imageAlt = tx('No results');\n onMappingChanged()\n \"\n >\n {{ tx('Default image') }}\n </button>\n </div>\n\n @switch (mappingEmptyState.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingEmptyState\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingEmptyState\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Image URL') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <mat-error\n *ngIf=\"\n isImageUrlRequiredInvalid(mappingEmptyState.imageUrl)\n \"\n >{{ tx('URL/expr is required') }}</mat-error\n >\n </mat-form-field>\n </div>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!mappingEmptyState.imageUrl\"\n >\n {{ tx('Set the URL/expr to render the image.') }}\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingEmptyState\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Style') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n </mat-accordion>\n\n <button mat-flat-button color=\"primary\" (click)=\"applyTemplate()\">\n {{ tx('Apply mapping') }}\n </button>\n <button\n mat-button\n (click)=\"inferFromFields()\"\n [disabled]=\"!fields.length\"\n >\n {{ tx('Infer from schema') }}\n </button>\n <div class=\"g g-auto-220 gap-12 ai-end mt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Skeleton count') }}</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"0\"\n [(ngModel)]=\"skeletonCountInput\"\n (ngModelChange)=\"onSkeletonChanged($event)\"\n />\n </mat-form-field>\n </div>\n\n <div class=\"g gap-12 mt-12\">\n <div class=\"g row-flow gap-8 ai-center\">\n <span class=\"section-title mat-subtitle-1\">{{ tx('Theme preview') }}</span>\n <mat-button-toggle-group\n [(ngModel)]=\"skinPreviewTheme\"\n (change)=\"onSkinChanged()\"\n appearance=\"legacy\"\n >\n <mat-button-toggle [value]=\"'light'\">{{ tx('Light') }}</mat-button-toggle>\n <mat-button-toggle [value]=\"'dark'\">{{ tx('Dark') }}</mat-button-toggle>\n <mat-button-toggle [value]=\"'grid'\">{{ tx('Grid') }}</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <div class=\"skin-preview-wrap\">\n <praxis-list-skin-preview\n [config]=\"working\"\n [items]=\"previewData\"\n [theme]=\"skinPreviewTheme\"\n ></praxis-list-skin-preview>\n </div>\n </div>\n </div>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('i18n/A11y')\">\n <ng-template matTabContent>\n <div\n class=\"editor-content grid gap-3\"\n *ngIf=\"working?.a11y && working?.events\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Default locale') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.i18n.locale\"\n (ngModelChange)=\"markDirty()\"\n [placeholder]=\"tx('e.g.: en-US')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Default currency') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.i18n.currency\"\n (ngModelChange)=\"markDirty()\"\n [placeholder]=\"currencyPlaceholder()\"\n />\n </mat-form-field>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">{{ tx('Accessibility') }}</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('aria-label') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.a11y!.ariaLabel\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('aria-labelledby') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.a11y!.ariaLabelledBy\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n </div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-slide-toggle\n [(ngModel)]=\"working!.a11y!.highContrast\"\n (ngModelChange)=\"markDirty()\"\n >{{ tx('High contrast') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working!.a11y!.reduceMotion\"\n (ngModelChange)=\"markDirty()\"\n >{{ tx('Reduce motion') }}</mat-slide-toggle\n >\n </div>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">{{ tx('Events') }}</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('itemClick') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.itemClick\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>actionClick</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.actionClick\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>selectionChange</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.selectionChange\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>loaded</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.loaded\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n </div>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Selection')\">\n <ng-template matTabContent>\n <div class=\"editor-content grid gap-3\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Mode') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.selection.mode\"\n (ngModelChange)=\"onSelectionChanged()\"\n >\n <mat-option value=\"none\">{{ tx('No selection') }}</mat-option>\n <mat-option value=\"single\">{{ tx('Single') }}</mat-option>\n <mat-option value=\"multiple\">{{ tx('Multiple') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Form name') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.formControlName\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"formControlName\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Form path') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.formControlPath\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"formControlPath\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Compare by (field)') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.compareBy\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Unique item key.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Return') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.selection.return\"\n (ngModelChange)=\"onSelectionChanged()\"\n >\n <mat-option value=\"value\">{{ tx('Value') }}</mat-option>\n <mat-option value=\"item\">{{ tx('Item') }}</mat-option>\n <mat-option value=\"id\">{{ tx('ID') }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Appearance')\">\n <ng-template matTabContent>\n <div class=\"editor-content grid gap-3\">\n <div class=\"preset-row g row-flow gap-8\">\n <button mat-button (click)=\"applySkinPreset('pill-soft')\">\n {{ tx('Pill Soft') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('gradient-tile')\">\n {{ tx('Gradient Tile') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('glass')\">\n {{ tx('Glass') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('elevated')\">\n {{ tx('Elevated') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('outline')\">\n {{ tx('Outline') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('flat')\">\n {{ tx('Flat') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('neumorphism')\">\n {{ tx('Neumorphism') }}\n </button>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Style') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.skin.type\"\n (ngModelChange)=\"onSkinTypeChanged($event)\"\n >\n <mat-option value=\"pill-soft\">{{ tx('Pill Soft') }}</mat-option>\n <mat-option value=\"gradient-tile\">{{ tx('Gradient Tile') }}</mat-option>\n <mat-option value=\"glass\">{{ tx('Glass') }}</mat-option>\n <mat-option value=\"elevated\">{{ tx('Elevated') }}</mat-option>\n <mat-option value=\"outline\">{{ tx('Outline') }}</mat-option>\n <mat-option value=\"flat\">{{ tx('Flat') }}</mat-option>\n <mat-option value=\"neumorphism\">{{ tx('Neumorphism') }}</mat-option>\n <mat-option value=\"custom\">{{ tx('Custom') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Radius') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.radius\"\n (ngModelChange)=\"onSkinChanged()\"\n [placeholder]=\"tx('e.g.: 1.25rem')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Shadow') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.shadow\"\n (ngModelChange)=\"onSkinChanged()\"\n [placeholder]=\"tx('e.g.: var(--md-sys-elevation-level2)')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Border') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.border\"\n (ngModelChange)=\"onSkinChanged()\"\n />\n </mat-form-field>\n <mat-form-field\n *ngIf=\"working.skin.type === 'glass'\"\n appearance=\"outline\"\n >\n <mat-label>{{ tx('Blur') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.backdropBlur\"\n (ngModelChange)=\"onSkinChanged()\"\n [placeholder]=\"tx('e.g.: 8px')\"\n />\n </mat-form-field>\n <div *ngIf=\"working.skin.type === 'gradient-tile'\" class=\"g gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Gradient from') }}</mat-label>\n <input\n matInput\n [ngModel]=\"working.skin.gradient.from || ''\"\n (ngModelChange)=\"onSkinGradientChanged('from', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Gradient to') }}</mat-label>\n <input\n matInput\n [ngModel]=\"working.skin.gradient.to || ''\"\n (ngModelChange)=\"onSkinGradientChanged('to', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Angle') }}</mat-label>\n <input\n matInput\n type=\"number\"\n [ngModel]=\"working.skin.gradient.angle ?? 135\"\n (ngModelChange)=\"onSkinGradientChanged('angle', $event)\"\n />\n </mat-form-field>\n </div>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Extra CSS class (skin.class)') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.class\"\n (ngModelChange)=\"onSkinChanged()\"\n [placeholder]=\"tx('e.g.: my-list-skin')\"\n />\n </mat-form-field>\n\n <div\n *ngIf=\"working.skin.type === 'custom'\"\n class=\"g g-auto-220 gap-12 ai-end\"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>{{ tx('Inline style (skin.inlineStyle)') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [(ngModel)]=\"working.skin.inlineStyle\"\n (ngModelChange)=\"onSkinChanged()\"\n [attr.placeholder]=\"':host{--p-list-radius: 1rem}'\"\n ></textarea>\n </mat-form-field>\n <div class=\"text-caption\">\n {{ tx('CSS class example (add this to your global styles):') }}\n <pre class=\"code-block\">\n.my-list-skin .item-card {\n border-radius: 14px;\n border: 1px solid var(--md-sys-color-outline-variant);\n box-shadow: var(--md-sys-elevation-level2);\n}\n.my-list-skin .mat-mdc-list-item .list-item-content {\n backdrop-filter: blur(6px);\n}</pre\n >\n </div>\n </div>\n </div>\n </ng-template>\n </mat-tab>\n</mat-tab-group>\n", styles: [".confirm-type{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;line-height:16px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant)}.confirm-type.danger{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.confirm-type.warning{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.confirm-type.info{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}:host{display:block;color:var(--md-sys-color-on-surface)}.list-editor-tabs{--editor-surface: var(--md-sys-color-surface-container-lowest);--editor-border: 1px solid var(--md-sys-color-outline-variant);--editor-radius: var(--md-sys-shape-corner-large, 16px);--editor-muted: var(--md-sys-color-on-surface-variant);--editor-accent: var(--md-sys-color-primary)}.editor-content{padding:16px;background:var(--editor-surface);border:var(--editor-border);border-radius:var(--editor-radius);display:grid;gap:12px}.editor-content .mat-mdc-form-field{width:100%;max-width:none;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var( --md-sys-color-outline-variant );--mdc-outlined-text-field-hover-outline-color: var( --md-sys-color-outline );--mdc-outlined-text-field-focus-outline-color: var( --md-sys-color-primary );--mdc-outlined-text-field-error-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-error-focus-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-error-hover-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-label-text-color: var( --md-sys-color-on-surface-variant );--mdc-outlined-text-field-input-text-color: var( --md-sys-color-on-surface );--mdc-outlined-text-field-supporting-text-color: var( --md-sys-color-on-surface-variant )}.editor-content .mat-mdc-form-field.w-full{max-width:none}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}.editor-split{grid-template-columns:minmax(0,1fr);align-items:start}.editor-main,.editor-aside{display:grid;gap:12px}.skin-preview-wrap{border-radius:calc(var(--editor-radius) - 4px);border:var(--editor-border);background:var(--md-sys-color-surface-container);padding:12px}.g{display:grid}.g-auto-220{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.g-auto-200{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}.g-1-auto{grid-template-columns:1fr auto}.row-flow{grid-auto-flow:column}.gap-6{gap:6px}.gap-8{gap:8px}.gap-12{gap:12px}.ai-center{align-items:center}.ai-end{align-items:end}.mt-12{margin-top:12px}.mb-8{margin-bottom:8px}.mb-6{margin-bottom:6px}.my-8{margin:8px 0}.subtitle{margin:8px 0 4px;color:var(--editor-muted);font-weight:500}.section-title{color:var(--editor-muted);font-weight:600}.chips-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.error{color:var(--md-sys-color-error);font-size:.85rem}.muted{color:var(--editor-muted)}.text-caption{color:var(--editor-muted);font-size:.8rem}:host ::ng-deep .mat-mdc-select-panel .option-icon{font-size:18px;margin-right:6px;vertical-align:middle}:host ::ng-deep .mat-mdc-select-panel .color-dot{width:10px;height:10px;border-radius:999px;display:inline-block;margin-right:6px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-outline)}:host ::ng-deep .mat-mdc-select-panel .color-primary{background:var(--md-sys-color-primary)}:host ::ng-deep .mat-mdc-select-panel .color-accent{background:var(--md-sys-color-tertiary)}:host ::ng-deep .mat-mdc-select-panel .color-warn{background:var(--md-sys-color-error)}:host ::ng-deep .mat-mdc-select-panel .color-default{background:var(--md-sys-color-outline)}@media(max-width:1024px){.editor-split{grid-template-columns:minmax(0,1fr)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.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: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i3$3.MatTabContent, selector: "[matTabContent]" }, { kind: "component", type: i3$3.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$3.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$1.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i2$1.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i2$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.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: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i8.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i10.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i10.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i10.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i10.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i10.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatButtonToggleModule }, { kind: "directive", type: i11.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i11.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i12.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i3.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "ngmodule", type: MatMenuModule }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisListSkinPreviewComponent, selector: "praxis-list-skin-preview", inputs: ["config", "items", "theme"] }, { kind: "component", type: PraxisMetaEditorTextComponent, selector: "praxis-meta-editor-text", inputs: ["model", "setPipe"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorChipComponent, selector: "praxis-meta-editor-chip", inputs: ["model", "paletteOptions", "colorDotBackground", "isCustomColor", "enableCustomColor"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorRatingComponent, selector: "praxis-meta-editor-rating", inputs: ["model", "paletteOptions", "colorDotBackground", "isCustomColor", "enableCustomColor"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorCurrencyComponent, selector: "praxis-meta-editor-currency", inputs: ["model"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorDateComponent, selector: "praxis-meta-editor-date", inputs: ["model"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorIconComponent, selector: "praxis-meta-editor-icon", inputs: ["model", "paletteOptions", "colorDotBackground", "isCustomColor", "enableCustomColor"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorImageComponent, selector: "praxis-meta-editor-image", inputs: ["model"], outputs: ["change"] }, { kind: "component", type: PraxisListJsonConfigEditorComponent, selector: "praxis-list-json-config-editor", inputs: ["document"], outputs: ["documentChange", "validationChange", "editorEvent"] }, { kind: "component", type: PdxColorPickerComponent, selector: "pdx-color-picker", inputs: ["actionsLayout", "activeView", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "clearButton", "disabledMode", "readonlyMode", "visible", "presentationMode", "fillMode", "format", "gradientSettings", "icon", "iconClass", "svgIcon", "paletteSettings", "popupSettings", "preview", "rounded", "size", "tabindex", "views", "maxRecent", "showRecent"], outputs: ["valueChange", "open", "close", "cancel", "activeViewChange", "activeColorClick", "focusEvent", "blurEvent"] }, { kind: "component", type: SurfaceOpenActionEditorComponent, selector: "praxis-surface-open-action-editor", inputs: ["value", "hostKind"], outputs: ["valueChange"] }] });
|
|
6049
|
+
], ngImport: i0, template: "<mat-tab-group class=\"list-editor-tabs\">\n <mat-tab [label]=\"tx('Data')\">\n <ng-template matTabContent>\n <div class=\"editor-content\">\n <div class=\"g g-1-auto gap-8 ai-center\">\n <div class=\"muted\">\n {{ tx('Assistant-applied adjustments replace the entire configuration object.') }}\n </div>\n <button\n mat-icon-button\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('applyConfigFromAdapter does not perform a deep merge. Make sure the adapter sends the full config.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </div>\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>{{ tx('Resource (API)') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.dataSource.resourcePath\"\n (ngModelChange)=\"onResourcePathChange($event)\"\n [placeholder]=\"tx('e.g.: users')\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Resource endpoint (resourcePath).')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>{{ tx('Query (JSON)') }}</mat-label>\n <textarea\n matInput\n rows=\"3\"\n [(ngModel)]=\"queryJson\"\n (ngModelChange)=\"onQueryChanged($event)\"\n [placeholder]=\"queryPlaceholder()\"\n ></textarea>\n @if (!queryError) {\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Optional. Use valid JSON for initial filters.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n }\n @if (queryError) {\n <mat-error>{{ queryError }}</mat-error>\n }\n </mat-form-field>\n <div class=\"g g-auto-220 gap-12 ai-end mt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Sort by') }}</mat-label>\n <mat-select\n [(ngModel)]=\"sortField\"\n (ngModelChange)=\"updateSortConfig()\"\n >\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Resource base field.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Direction') }}</mat-label>\n <mat-select\n [(ngModel)]=\"sortDir\"\n (ngModelChange)=\"updateSortConfig()\"\n >\n <mat-option value=\"asc\">{{ tx('Ascending') }}</mat-option>\n <mat-option value=\"desc\">{{ tx('Descending') }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('JSON')\">\n <ng-template matTabContent>\n <div class=\"editor-content\">\n <praxis-list-json-config-editor\n [document]=\"document\"\n (documentChange)=\"onJsonConfigChange($event)\"\n (validationChange)=\"onJsonValidationChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n >\n </praxis-list-json-config-editor>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Actions')\">\n <ng-template matTabContent>\n <div class=\"editor-content g gap-12\">\n <div class=\"g g-1-auto gap-8 ai-center\">\n <div class=\"muted\">\n {{ tx('Configure per-item action buttons (icon, label, color, visibility)') }}\n </div>\n <button mat-flat-button color=\"primary\" (click)=\"addAction()\">\n {{ tx('Add action') }}\n </button>\n </div>\n <div class=\"g g-1-auto gap-8 ai-center\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Global action (Praxis)') }}</mat-label>\n <mat-select\n [(ngModel)]=\"selectedGlobalActionId\"\n (ngModelChange)=\"onGlobalActionSelected($event)\"\n >\n <mat-option [value]=\"undefined\">{{ tx('-- Select --') }}</mat-option>\n @for (ga of globalActionCatalog; track ga) {\n <mat-option [value]=\"ga.id\">\n <mat-icon class=\"option-icon\">{{ ga.icon || \"bolt\" }}</mat-icon>\n {{ ga.label }}\n </mat-option>\n }\n </mat-select>\n @if (!globalActionCatalog.length) {\n <mat-hint\n class=\"text-caption muted\"\n >{{ tx('No global action registered.') }}</mat-hint\n >\n }\n </mat-form-field>\n <div class=\"muted text-caption\">\n {{ tx('Select to add with a structured global action.') }}\n </div>\n </div>\n @for (a of working.actions || []; track a; let i = $index) {\n <div\n class=\"g g-auto-200 gap-12 ai-end\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('ID') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.id\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Action type') }}</mat-label>\n <mat-select [(ngModel)]=\"a.kind\" (ngModelChange)=\"onActionsChanged()\">\n <mat-option value=\"icon\">{{ tx('Icon') }}</mat-option>\n <mat-option value=\"button\">{{ tx('Button') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Icon') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.icon\"\n (ngModelChange)=\"onActionsChanged()\"\n [placeholder]=\"tx('e.g.: edit, delete')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Global action') }}</mat-label>\n <mat-select\n [ngModel]=\"getGlobalActionId(a)\"\n (ngModelChange)=\"onActionGlobalActionIdChange(a, $event)\"\n >\n <mat-option value=\"\">{{ tx('None') }}</mat-option>\n @for (action of globalActionCatalog; track action) {\n <mat-option [value]=\"action.id\">\n {{ action.label || action.id }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Label') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.label\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n </mat-form-field>\n @if (a.kind === 'button') {\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Variant') }}</mat-label>\n <mat-select\n [(ngModel)]=\"a.buttonVariant\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option value=\"stroked\">{{ tx('Outlined') }}</mat-option>\n <mat-option value=\"raised\">{{ tx('Raised') }}</mat-option>\n <mat-option value=\"flat\">{{ tx('Filled') }}</mat-option>\n </mat-select>\n </mat-form-field>\n }\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Action color') }}</mat-label>\n <mat-select\n [(ngModel)]=\"a.color\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n @for (c of paletteOptions; track c) {\n <mat-option [value]=\"c.value\">\n <span\n class=\"color-dot\"\n [style.background]=\"colorDotBackground(c.value)\"\n ></span\n >{{ tx(c.label) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (isCustomColor(a.color)) {\n <div\n class=\"g gap-8\"\n >\n <pdx-color-picker\n [label]=\"tx('Custom color')\"\n [format]=\"'hex'\"\n [(ngModel)]=\"a.color\"\n (ngModelChange)=\"onActionsChanged()\"\n ></pdx-color-picker>\n </div>\n } @else {\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"enableCustomActionColor(a)\"\n >\n {{ tx('Use custom color') }}\n </button>\n }\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Action payload') }}</mat-label>\n <mat-select\n [(ngModel)]=\"a.emitPayload\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option [value]=\"undefined\">{{ tx('Default') }}</mat-option>\n <mat-option value=\"item\">{{ tx('Item') }}</mat-option>\n <mat-option value=\"id\">{{ tx('ID') }}</mat-option>\n <mat-option value=\"value\">{{ tx('Value') }}</mat-option>\n </mat-select>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Payload emitted by the action.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"col-span-2\">\n <mat-label>{{ actionVisibilityLabel() }}</mat-label>\n <textarea\n matInput\n rows=\"3\"\n [ngModel]=\"actionShowIfModel(i)\"\n (ngModelChange)=\"onActionShowIfChanged(i, a, $event)\"\n ></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"actionConditionTooltip()\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"g row-flow gap-8 ai-center\">\n @if ((a.kind || 'icon') === 'icon') {\n <button\n mat-icon-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n >\n <mat-icon\n [praxisIcon]=\"a.icon || 'bolt'\"\n [style.cssText]=\"iconStyle(a.color)\"\n ></mat-icon>\n </button>\n }\n @if (a.kind === 'button') {\n @if (a.buttonVariant === 'stroked') {\n <button\n mat-stroked-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'stroked')\"\n >\n {{ a.label || a.id || tx('Action') }}\n </button>\n }\n @if (a.buttonVariant === 'raised') {\n <button\n mat-raised-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'raised')\"\n >\n {{ a.label || a.id || tx('Action') }}\n </button>\n }\n @if (!a.buttonVariant || a.buttonVariant === 'flat') {\n <button\n mat-flat-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'flat')\"\n >\n {{ a.label || a.id || tx('Action') }}\n </button>\n }\n }\n <span class=\"muted\">{{ tx('Preview') }}</span>\n </div>\n <div class=\"flex-end\">\n <button mat-button color=\"warn\" (click)=\"removeAction(i)\">\n {{ tx('Remove') }}\n </button>\n </div>\n @if (a.globalAction?.actionId) {\n <div class=\"g gap-8 col-span-2\">\n <mat-slide-toggle\n [(ngModel)]=\"a.showLoading\"\n (ngModelChange)=\"onActionsChanged()\"\n >{{ tx('Show loading') }}</mat-slide-toggle\n >\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >{{ tx('Confirmation') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <div class=\"g row-flow gap-8 ai-center\">\n <span class=\"text-caption muted\">{{ tx('Type') }}</span>\n <mat-button-toggle-group\n [value]=\"a.confirmation?.type || ''\"\n (change)=\"applyConfirmationPreset(a, $event.value)\"\n >\n <mat-button-toggle value=\"\">{{ tx('Default') }}</mat-button-toggle>\n <mat-button-toggle value=\"danger\">{{ tx('Danger') }}</mat-button-toggle>\n <mat-button-toggle value=\"warning\">{{ tx('Warning') }}</mat-button-toggle>\n <mat-button-toggle value=\"info\">{{ tx('Info') }}</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Title') }}</mat-label>\n <input\n matInput\n [ngModel]=\"a.confirmation?.title\"\n (ngModelChange)=\"setConfirmationField(a, 'title', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Message') }}</mat-label>\n <input\n matInput\n [ngModel]=\"a.confirmation?.message\"\n (ngModelChange)=\"setConfirmationField(a, 'message', $event)\"\n />\n </mat-form-field>\n <div class=\"g gap-6\">\n <div class=\"text-caption muted\">{{ tx('Preview') }}</div>\n <div class=\"text-caption\">\n <strong>{{\n a.confirmation?.title || tx('Confirm action')\n }}</strong>\n </div>\n <div class=\"text-caption muted\">\n {{\n a.confirmation?.message ||\n tx('Are you sure you want to continue?')\n }}\n </div>\n <div class=\"text-caption\">\n <span\n class=\"confirm-type\"\n [ngClass]=\"a.confirmation?.type || 'default'\"\n >{{ tx('Type') }}:\n {{ a.confirmation?.type || tx('Default').toLowerCase() }}</span\n >\n </div>\n @if (!a.confirmation?.title && !a.confirmation?.message) {\n <div\n class=\"text-caption muted\"\n >\n {{ tx('Set a title or message for the confirmation.') }}\n </div>\n }\n </div>\n </div>\n </mat-expansion-panel>\n @if (isSurfaceOpenCommand(a)) {\n <div class=\"col-span-2\">\n <praxis-surface-open-action-editor\n [value]=\"getSurfaceOpenGlobalActionPayload(a)\"\n hostKind=\"list\"\n (valueChange)=\"onSurfaceOpenGlobalActionPayloadChange(a, $event)\"\n ></praxis-surface-open-action-editor>\n </div>\n } @else {\n <mat-form-field appearance=\"outline\" class=\"col-span-2\">\n <mat-label>{{ tx('Payload (JSON/Template)') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"getGlobalActionPayloadText(a)\"\n (ngModelChange)=\"onGlobalActionPayloadTextChange(a, $event)\"\n placeholder='{\"message\":\"${item.name} favoritado\"}'\n ></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"globalActionPayloadSchemaTooltip(a)\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n @if (isGlobalActionPayloadInvalid(a)) {\n <mat-error\n >{{ tx('Invalid JSON') }}</mat-error\n >\n }\n </mat-form-field>\n <div class=\"g row-flow gap-8 ai-center\">\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"applyGlobalActionPayloadExample(a)\"\n >\n {{ tx('Insert example') }}\n </button>\n <span class=\"muted text-caption\">{{\n globalActionPayloadExampleHint(a)\n }}</span>\n </div>\n }\n <mat-slide-toggle\n [(ngModel)]=\"a.emitLocal\"\n (ngModelChange)=\"onActionsChanged()\"\n >{{ tx('Emit local event too') }}</mat-slide-toggle\n >\n </div>\n }\n </div>\n }\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Layout')\">\n <ng-template matTabContent>\n <div class=\"editor-content grid gap-3\">\n <div class=\"preset-row g row-flow gap-8\">\n <button mat-stroked-button (click)=\"applyLayoutPreset('tiles-modern')\">\n {{ tx('Modern tiles preset') }}\n </button>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Variant') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.variant\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"list\">{{ tx('List') }}</mat-option>\n <mat-option value=\"cards\">{{ tx('Cards') }}</mat-option>\n <mat-option value=\"tiles\">{{ tx('Tiles') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Model') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.model\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n @if (working.layout.variant === 'list') {\n <mat-option value=\"standard\">{{ tx('Standard') }}</mat-option>\n <mat-option value=\"media\">{{ tx('Media on the left') }}</mat-option>\n <mat-option value=\"hotel\">{{ tx('Hotel (large media)') }}</mat-option>\n } @else {\n @if (working.layout.variant === 'tiles') {\n <mat-option value=\"standard\">{{ tx('Standard tile') }}</mat-option>\n <mat-option value=\"media\">{{ tx('Tile with media') }}</mat-option>\n <mat-option value=\"hotel\">{{ tx('Hotel tile') }}</mat-option>\n } @else {\n <mat-option value=\"standard\">{{ tx('Standard') }}</mat-option>\n <mat-option value=\"media\">{{ tx('Card with media') }}</mat-option>\n <mat-option value=\"hotel\">{{ tx('Hotel') }}</mat-option>\n }\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Lines') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.lines\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option [value]=\"1\">1</mat-option>\n <mat-option [value]=\"2\">2</mat-option>\n <mat-option [value]=\"3\">3</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Items per page') }}</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"1\"\n [(ngModel)]=\"working.layout.pageSize\"\n (ngModelChange)=\"onPageSizeChange($event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Density') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.density\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"default\">{{ tx('Default') }}</mat-option>\n <mat-option value=\"comfortable\">{{ tx('Comfortable') }}</mat-option>\n <mat-option value=\"compact\">{{ tx('Compact') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Spacing between items') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.itemSpacing\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"none\">{{ tx('No extra space') }}</mat-option>\n <mat-option value=\"tight\">{{ tx('Tight') }}</mat-option>\n <mat-option value=\"default\">{{ tx('Standard') }}</mat-option>\n <mat-option value=\"relaxed\">{{ tx('Relaxed') }}</mat-option>\n </mat-select>\n </mat-form-field>\n @if (working.layout.variant !== 'tiles') {\n <mat-form-field\n appearance=\"outline\"\n >\n <mat-label>{{ tx('Dividers') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.dividers\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"none\">{{ tx('None') }}</mat-option>\n <mat-option value=\"between\">{{ tx('Between groups') }}</mat-option>\n <mat-option value=\"all\">{{ tx('All') }}</mat-option>\n </mat-select>\n </mat-form-field>\n }\n @if (fields.length > 0) {\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Group by') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.groupBy\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option [value]=\"\">{{ tx('None') }}</mat-option>\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Group by') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.layout.groupBy\"\n (ngModelChange)=\"onLayoutChanged()\"\n [placeholder]=\"tx('e.g.: department')\"\n />\n </mat-form-field>\n }\n <mat-slide-toggle\n [(ngModel)]=\"working.layout.stickySectionHeader\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n {{ tx('Sticky section header') }}\n </mat-slide-toggle>\n <mat-slide-toggle\n [(ngModel)]=\"working.layout.virtualScroll\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n {{ tx('Virtual scroll') }}\n </mat-slide-toggle>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">{{ tx('List tools') }}</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showSearch\"\n (ngModelChange)=\"onUiChanged()\"\n >{{ tx('Show search') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showSort\"\n (ngModelChange)=\"onUiChanged()\"\n >{{ tx('Show sorting') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showRange\"\n (ngModelChange)=\"onUiChanged()\"\n >{{ tx('Show total X-Y range') }}</mat-slide-toggle\n >\n </div>\n @if (working.ui?.showSearch) {\n <div\n class=\"g g-auto-220 gap-12 ai-end mt-12\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field to search') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.ui.searchField\"\n (ngModelChange)=\"onUiChanged()\"\n >\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Search placeholder') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.ui.searchPlaceholder\"\n (ngModelChange)=\"onUiChanged()\"\n [placeholder]=\"tx('e.g.: Search by title')\"\n />\n </mat-form-field>\n </div>\n }\n @if (working.ui?.showSort) {\n <div class=\"mt-12\">\n <div class=\"g g-1-auto ai-center gap-8\">\n <div class=\"muted\">\n {{ tx('Sorting options (label \u2192 field+direction)') }}\n </div>\n <button mat-flat-button color=\"primary\" (click)=\"addUiSortRow()\">\n {{ tx('Add option') }}\n </button>\n </div>\n @for (r of uiSortRows; track r; let i = $index) {\n <div\n class=\"g g-auto-220 gap-12 ai-end mt-12\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Label') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"r.label\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n [placeholder]=\"tx('e.g.: Most recent')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"r.field\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n >\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Direction') }}</mat-label>\n <mat-select\n [(ngModel)]=\"r.dir\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n >\n <mat-option value=\"desc\">{{ tx('Descending') }}</mat-option>\n <mat-option value=\"asc\">{{ tx('Ascending') }}</mat-option>\n </mat-select>\n </mat-form-field>\n @if (isUiSortRowDuplicate(i)) {\n <div class=\"error\">\n {{ tx('Duplicate option (field+direction)') }}\n </div>\n }\n <div class=\"flex-end\">\n <button mat-button color=\"warn\" (click)=\"removeUiSortRow(i)\">\n {{ tx('Remove') }}\n </button>\n </div>\n </div>\n }\n </div>\n }\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Content')\">\n <ng-template matTabContent>\n <div class=\"editor-content\">\n <div class=\"editor-main\">\n <mat-accordion multi>\n <!-- Primary -->\n <mat-expansion-panel [expanded]=\"true\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingPrimary.type) }}</mat-icon>\n <span>{{ tx('Primary (Title)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingPrimary.field || tx('Not mapped')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'name';\n onMappingChanged()\n \"\n >\n {{ tx('Name') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'title';\n onMappingChanged()\n \"\n >\n {{ tx('Title') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'name';\n mappingSecondary.type = 'text';\n mappingSecondary.field = 'role';\n onMappingChanged()\n \"\n >\n {{ tx('Name + role') }}\n </button>\n </div>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingPrimary.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingPrimary.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (mt of primaryTypeConfigs; track mt) {\n <mat-option\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n @switch (mappingPrimary.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingPrimary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingPrimary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingPrimary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingPrimary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n }\n\n <!-- Advanced -->\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header>\n <mat-panel-title>{{ tx('Formatting and style') }}</mat-panel-title>\n </mat-expansion-panel-header>\n <div class=\"g gap-12 pt-12\">\n @if (\n mappingPrimary.type === 'text' ||\n mappingPrimary.type === 'html'\n ) {\n <mat-form-field\n appearance=\"outline\"\n >\n <mat-label>{{ tx('CSS class') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n }\n @if (\n mappingPrimary.type === 'text' ||\n mappingPrimary.type === 'html'\n ) {\n <mat-form-field\n appearance=\"outline\"\n >\n <mat-label>{{ tx('Inline style') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n }\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('CSS class') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Inline style') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Secondary -->\n <mat-expansion-panel [expanded]=\"!!mappingSecondary.field\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingSecondary.type) }}</mat-icon>\n <span>{{ tx('Secondary (Summary)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingSecondary.field || tx('Not mapped')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'text';\n mappingSecondary.field = 'subtitle';\n onMappingChanged()\n \"\n >\n {{ tx('Subtitle') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'date';\n mappingSecondary.field = 'hireDate';\n mappingSecondary.dateStyle = 'short';\n onMappingChanged()\n \"\n >\n {{ tx('Short date') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"applySalaryPreset()\"\n >\n {{ tx('Salary') }}\n </button>\n </div>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSecondary.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSecondary.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (mt of secondaryTypeConfigs; track mt) {\n <mat-option\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n @switch (mappingSecondary.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingSecondary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingSecondary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingSecondary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingSecondary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header>\n <mat-panel-title>{{ tx('Formatting and style') }}</mat-panel-title>\n </mat-expansion-panel-header>\n <div class=\"g gap-12 pt-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('CSS class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSecondary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Inline style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSecondary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <mat-expansion-panel\n [expanded]=\"!!mappingMeta.field || mappingMetaFields.length > 0\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingMeta.type || \"text\")\n }}</mat-icon>\n <span>{{ tx('Meta (Detail/Side)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>\n {{\n mappingMetaFields.length\n ? tx('Composed field ({{count}})', { count: mappingMetaFields.length })\n : mappingMeta.field || tx('Not mapped')\n }}\n </mat-panel-description>\n </mat-expansion-panel-header>\n\n <div class=\"g gap-12\">\n <!-- Composition Mode Toggle -->\n <div class=\"g g-1-1 gap-12 p-12 bg-subtle rounded\">\n <div class=\"text-caption muted\">{{ tx('Composition mode') }}</div>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Fields to compose (multi-select)') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMetaFields\"\n multiple\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (mappingMetaFields.length) {\n <div\n class=\"g g-1-1 ai-center gap-12\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Separator') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMetaSeparator\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-slide-toggle\n [(ngModel)]=\"mappingMetaWrapSecondInParens\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n {{ tx('(Second) in parentheses') }}\n </mat-slide-toggle>\n </div>\n }\n </div>\n\n <!-- Single Field Mode (if no composition) -->\n @if (!mappingMetaFields.length) {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Single field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option [value]=\"undefined\">{{ tx('-- None --') }}</mat-option>\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (mt of metaTypeConfigs; track mt) {\n <mat-option\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n\n <!-- Type configuration (pluggable editors) -->\n @switch (mappingMeta.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingMeta\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingMeta\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <praxis-meta-editor-image\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <!-- Advanced -->\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Advanced options') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Position') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.placement\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option value=\"side\">{{ tx('Side (right)') }}</mat-option>\n <mat-option value=\"line\">{{ tx('Inline (below)') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('CSS class') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMeta.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Style') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMeta.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n <!-- Trailing -->\n <mat-expansion-panel [expanded]=\"!!mappingTrailing.field\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingTrailing.type || \"text\")\n }}</mat-icon>\n <span>{{ tx('Trailing (right)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingTrailing.field || tx('Not mapped')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingTrailing.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option [value]=\"undefined\">{{ tx('-- None --') }}</mat-option>\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingTrailing.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (mt of trailingTypeConfigs; track mt) {\n <mat-option\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'chip';\n mappingTrailing.chipColor = 'primary';\n mappingTrailing.chipVariant = 'filled';\n mappingTrailing.field = 'status';\n onMappingChanged()\n \"\n >\n {{ tx('Status chip') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'icon';\n mappingTrailing.field = 'status';\n mappingTrailing.iconColor = 'primary';\n onMappingChanged()\n \"\n >\n {{ tx('Status icon') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"applyPricePreset()\"\n >\n {{ tx('Price') }}\n </button>\n </div>\n\n @switch (mappingTrailing.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingTrailing\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingTrailing\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('URL / Expr') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingTrailing.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n [placeholder]=\"tx('https://... or ${item.imageUrl}')\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Use an absolute/relative URL or a ${item.field} expression.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n @if (\n isImageUrlRequiredInvalid(mappingTrailing.imageUrl)\n ) {\n <mat-error\n >{{ tx('URL/expr is required') }}</mat-error\n >\n }\n </mat-form-field>\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n @if (!mappingTrailing.imageUrl) {\n <div\n class=\"text-caption muted\"\n >\n {{ tx('Set the URL/expr to render the image.') }}\n </div>\n }\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Style') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingTrailing.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingTrailing.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Leading -->\n <mat-expansion-panel\n [expanded]=\"\n !!mappingLeading.field ||\n (mappingLeading.type === 'icon' && !!mappingLeading.icon) ||\n (mappingLeading.type === 'image' && !!mappingLeading.imageUrl)\n \"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingLeading.type) }}</mat-icon>\n <span>{{ tx('Leading (left)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>\n {{\n mappingLeading.type === \"icon\"\n ? mappingLeading.icon || tx('Static icon')\n : mappingLeading.field ||\n (mappingLeading.imageUrl\n ? tx('Static image')\n : tx('Not mapped'))\n }}\n </mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingLeading.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (mt of leadingTypeConfigs; track mt) {\n <mat-option\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <!-- Field (only if not static icon/image, though user might want dynamic) -->\n @if (\n mappingLeading.type !== 'icon' &&\n mappingLeading.type !== 'image'\n ) {\n <mat-form-field\n appearance=\"outline\"\n >\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingLeading.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'icon';\n mappingLeading.icon = 'person';\n mappingLeading.iconColor = 'primary';\n onMappingChanged()\n \"\n >\n {{ tx('Avatar icon') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'image';\n mappingLeading.imageUrl = 'https://placehold.co/64x64';\n mappingLeading.imageAlt = 'Avatar';\n mappingLeading.badgeText = '${item.status}';\n onMappingChanged()\n \"\n >\n {{ tx('Avatar image + badge') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'chip';\n mappingLeading.field = 'tag';\n mappingLeading.chipColor = 'accent';\n mappingLeading.chipVariant = 'filled';\n onMappingChanged()\n \"\n >\n {{ tx('Tag chip') }}\n </button>\n </div>\n\n <!-- Icon Specific -->\n @if (mappingLeading.type === 'icon') {\n <div\n class=\"g g-1-auto gap-12 ai-center\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Icon') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.icon\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <button mat-icon-button matSuffix (click)=\"pickLeadingIcon()\">\n <mat-icon>search</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"text-caption muted\">\n {{ tx('Use the `|iconMap` pipe in the extra pipe for dynamic rendering.') }}\n </div>\n </div>\n }\n @if (mappingLeading.type === 'icon') {\n <div>\n <praxis-meta-editor-icon\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n </div>\n }\n\n <!-- Image Specific -->\n @if (mappingLeading.type === 'image') {\n <div\n class=\"g g-1-1 gap-12\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Image URL') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n [placeholder]=\"tx('https://... or ${item.imageUrl}')\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Use an absolute/relative URL or a ${item.field} expression.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n @if (isImageUrlRequiredInvalid(mappingLeading.imageUrl)) {\n <mat-error\n >{{ tx('URL/expr is required') }}</mat-error\n >\n }\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Alt text') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.imageAlt\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Badge text') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.badgeText\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n }\n\n @switch (mappingLeading.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingLeading\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingLeading\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Style') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingLeading.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingLeading.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Features -->\n <mat-expansion-panel\n [expanded]=\"featuresVisible && features.length > 0\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>view_list</mat-icon>\n <span>{{ tx('Features') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description\n >{{ features.length }} item(s)</mat-panel-description\n >\n </mat-expansion-panel-header>\n\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-12 ai-center\">\n <mat-slide-toggle\n [(ngModel)]=\"featuresVisible\"\n (ngModelChange)=\"onFeaturesChanged()\"\n >{{ tx('Enable features') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"featuresSyncWithMeta\"\n (ngModelChange)=\"onMappingChanged()\"\n >{{ tx('Sync with Meta') }}</mat-slide-toggle\n >\n <span class=\"flex-1\"></span>\n <mat-button-toggle-group\n [(ngModel)]=\"featuresMode\"\n (change)=\"onFeaturesChanged()\"\n appearance=\"legacy\"\n >\n <mat-button-toggle value=\"icons+labels\"\n ><mat-icon>view_list</mat-icon></mat-button-toggle\n >\n <mat-button-toggle value=\"icons-only\"\n ><mat-icon>more_horiz</mat-icon></mat-button-toggle\n >\n </mat-button-toggle-group>\n </div>\n\n @for (f of features; track f; let i = $index) {\n <div\n class=\"g g-auto-1 gap-8 ai-center p-8 border rounded mb-2\"\n >\n <button mat-icon-button (click)=\"pickFeatureIcon(i)\">\n <mat-icon>{{ f.icon || \"search\" }}</mat-icon>\n </button>\n <mat-form-field\n appearance=\"outline\"\n class=\"dense-form-field no-sub\"\n >\n <input\n matInput\n [(ngModel)]=\"f.expr\"\n (ngModelChange)=\"onFeaturesChanged()\"\n [placeholder]=\"tx('Expression/Text')\"\n />\n </mat-form-field>\n <button mat-icon-button color=\"warn\" (click)=\"removeFeature(i)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n <button mat-button color=\"primary\" (click)=\"addFeature()\">\n <mat-icon>add</mat-icon> {{ tx('Add feature') }}\n </button>\n </div>\n </mat-expansion-panel>\n <!-- Section Header -->\n <mat-expansion-panel [expanded]=\"!!mappingSectionHeader.expr\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingSectionHeader.type)\n }}</mat-icon>\n <span>{{ tx('Section header') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingSectionHeader.expr || tx('Not configured')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSectionHeader.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (mt of sectionHeaderTypeConfigs; track mt) {\n <mat-option\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Expression (item.key)') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingSectionHeader.expr\"\n (ngModelChange)=\"onMappingChanged()\"\n placeholder=\"item.key\"\n />\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSectionHeader.type = 'text';\n mappingSectionHeader.expr = '${item.key}';\n onMappingChanged()\n \"\n >\n {{ tx('Default text') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSectionHeader.type = 'chip';\n mappingSectionHeader.chipColor = 'primary';\n mappingSectionHeader.chipVariant = 'filled';\n mappingSectionHeader.expr = '${item.key}';\n onMappingChanged()\n \"\n >\n {{ tx('Default chip') }}\n </button>\n </div>\n\n @switch (mappingSectionHeader.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingSectionHeader\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingSectionHeader\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Image URL') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingSectionHeader.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n @if (\n isImageUrlRequiredInvalid(\n mappingSectionHeader.imageUrl\n )\n ) {\n <mat-error\n >{{ tx('URL/expr is required') }}</mat-error\n >\n }\n </mat-form-field>\n </div>\n @if (!mappingSectionHeader.imageUrl) {\n <div\n class=\"text-caption muted\"\n >\n {{ tx('Set the URL/expr to render the image.') }}\n </div>\n }\n <praxis-meta-editor-image\n [model]=\"mappingSectionHeader\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Style') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSectionHeader.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSectionHeader.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Empty State -->\n <mat-expansion-panel [expanded]=\"!!mappingEmptyState.expr\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>inbox</mat-icon>\n <span>{{ tx('Empty state') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingEmptyState.expr || tx('Default')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingEmptyState.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (mt of emptyStateTypeConfigs; track mt) {\n <mat-option\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Message / Expr') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingEmptyState.expr\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingEmptyState.type = 'text';\n mappingEmptyState.expr = tx('No items available');\n onMappingChanged()\n \"\n >\n {{ tx('Default message') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingEmptyState.type = 'image';\n mappingEmptyState.imageUrl = '/list-empty-state.svg';\n mappingEmptyState.imageAlt = tx('No results');\n onMappingChanged()\n \"\n >\n {{ tx('Default image') }}\n </button>\n </div>\n\n @switch (mappingEmptyState.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingEmptyState\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingEmptyState\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Image URL') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n @if (\n isImageUrlRequiredInvalid(mappingEmptyState.imageUrl)\n ) {\n <mat-error\n >{{ tx('URL/expr is required') }}</mat-error\n >\n }\n </mat-form-field>\n </div>\n @if (!mappingEmptyState.imageUrl) {\n <div\n class=\"text-caption muted\"\n >\n {{ tx('Set the URL/expr to render the image.') }}\n </div>\n }\n <praxis-meta-editor-image\n [model]=\"mappingEmptyState\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Style') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n </mat-accordion>\n\n <button mat-flat-button color=\"primary\" (click)=\"applyTemplate()\">\n {{ tx('Apply mapping') }}\n </button>\n <button\n mat-button\n (click)=\"inferFromFields()\"\n [disabled]=\"!fields.length\"\n >\n {{ tx('Infer from schema') }}\n </button>\n <div class=\"g g-auto-220 gap-12 ai-end mt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Skeleton count') }}</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"0\"\n [(ngModel)]=\"skeletonCountInput\"\n (ngModelChange)=\"onSkeletonChanged($event)\"\n />\n </mat-form-field>\n </div>\n\n <div class=\"g gap-12 mt-12\">\n <div class=\"g row-flow gap-8 ai-center\">\n <span class=\"section-title mat-subtitle-1\">{{ tx('Theme preview') }}</span>\n <mat-button-toggle-group\n [(ngModel)]=\"skinPreviewTheme\"\n (change)=\"onSkinChanged()\"\n appearance=\"legacy\"\n >\n <mat-button-toggle [value]=\"'light'\">{{ tx('Light') }}</mat-button-toggle>\n <mat-button-toggle [value]=\"'dark'\">{{ tx('Dark') }}</mat-button-toggle>\n <mat-button-toggle [value]=\"'grid'\">{{ tx('Grid') }}</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <div class=\"skin-preview-wrap\">\n <praxis-list-skin-preview\n [config]=\"working\"\n [items]=\"previewData\"\n [theme]=\"skinPreviewTheme\"\n ></praxis-list-skin-preview>\n </div>\n </div>\n </div>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('i18n/A11y')\">\n <ng-template matTabContent>\n @if (working?.a11y && working?.events) {\n <div\n class=\"editor-content grid gap-3\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Default locale') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.i18n.locale\"\n (ngModelChange)=\"markDirty()\"\n [placeholder]=\"tx('e.g.: en-US')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Default currency') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.i18n.currency\"\n (ngModelChange)=\"markDirty()\"\n [placeholder]=\"currencyPlaceholder()\"\n />\n </mat-form-field>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">{{ tx('Accessibility') }}</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('aria-label') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.a11y!.ariaLabel\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('aria-labelledby') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.a11y!.ariaLabelledBy\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n </div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-slide-toggle\n [(ngModel)]=\"working!.a11y!.highContrast\"\n (ngModelChange)=\"markDirty()\"\n >{{ tx('High contrast') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working!.a11y!.reduceMotion\"\n (ngModelChange)=\"markDirty()\"\n >{{ tx('Reduce motion') }}</mat-slide-toggle\n >\n </div>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">{{ tx('Events') }}</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('itemClick') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.itemClick\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>actionClick</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.actionClick\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>selectionChange</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.selectionChange\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>loaded</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.loaded\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n </div>\n </div>\n }\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Selection')\">\n <ng-template matTabContent>\n <div class=\"editor-content grid gap-3\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Mode') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.selection.mode\"\n (ngModelChange)=\"onSelectionChanged()\"\n >\n <mat-option value=\"none\">{{ tx('No selection') }}</mat-option>\n <mat-option value=\"single\">{{ tx('Single') }}</mat-option>\n <mat-option value=\"multiple\">{{ tx('Multiple') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Form name') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.formControlName\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"formControlName\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Form path') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.formControlPath\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"formControlPath\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Compare by (field)') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.compareBy\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Unique item key.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Return') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.selection.return\"\n (ngModelChange)=\"onSelectionChanged()\"\n >\n <mat-option value=\"value\">{{ tx('Value') }}</mat-option>\n <mat-option value=\"item\">{{ tx('Item') }}</mat-option>\n <mat-option value=\"id\">{{ tx('ID') }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Appearance')\">\n <ng-template matTabContent>\n <div class=\"editor-content grid gap-3\">\n <div class=\"preset-row g row-flow gap-8\">\n <button mat-button (click)=\"applySkinPreset('pill-soft')\">\n {{ tx('Pill Soft') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('gradient-tile')\">\n {{ tx('Gradient Tile') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('glass')\">\n {{ tx('Glass') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('elevated')\">\n {{ tx('Elevated') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('outline')\">\n {{ tx('Outline') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('flat')\">\n {{ tx('Flat') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('neumorphism')\">\n {{ tx('Neumorphism') }}\n </button>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Style') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.skin.type\"\n (ngModelChange)=\"onSkinTypeChanged($event)\"\n >\n <mat-option value=\"pill-soft\">{{ tx('Pill Soft') }}</mat-option>\n <mat-option value=\"gradient-tile\">{{ tx('Gradient Tile') }}</mat-option>\n <mat-option value=\"glass\">{{ tx('Glass') }}</mat-option>\n <mat-option value=\"elevated\">{{ tx('Elevated') }}</mat-option>\n <mat-option value=\"outline\">{{ tx('Outline') }}</mat-option>\n <mat-option value=\"flat\">{{ tx('Flat') }}</mat-option>\n <mat-option value=\"neumorphism\">{{ tx('Neumorphism') }}</mat-option>\n <mat-option value=\"custom\">{{ tx('Custom') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Radius') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.radius\"\n (ngModelChange)=\"onSkinChanged()\"\n [placeholder]=\"tx('e.g.: 1.25rem')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Shadow') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.shadow\"\n (ngModelChange)=\"onSkinChanged()\"\n [placeholder]=\"tx('e.g.: var(--md-sys-elevation-level2)')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Border') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.border\"\n (ngModelChange)=\"onSkinChanged()\"\n />\n </mat-form-field>\n @if (working.skin.type === 'glass') {\n <mat-form-field\n appearance=\"outline\"\n >\n <mat-label>{{ tx('Blur') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.backdropBlur\"\n (ngModelChange)=\"onSkinChanged()\"\n [placeholder]=\"tx('e.g.: 8px')\"\n />\n </mat-form-field>\n }\n @if (working.skin.type === 'gradient-tile') {\n <div class=\"g gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Gradient from') }}</mat-label>\n <input\n matInput\n [ngModel]=\"working.skin.gradient.from || ''\"\n (ngModelChange)=\"onSkinGradientChanged('from', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Gradient to') }}</mat-label>\n <input\n matInput\n [ngModel]=\"working.skin.gradient.to || ''\"\n (ngModelChange)=\"onSkinGradientChanged('to', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Angle') }}</mat-label>\n <input\n matInput\n type=\"number\"\n [ngModel]=\"working.skin.gradient.angle ?? 135\"\n (ngModelChange)=\"onSkinGradientChanged('angle', $event)\"\n />\n </mat-form-field>\n </div>\n }\n\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Extra CSS class (skin.class)') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.class\"\n (ngModelChange)=\"onSkinChanged()\"\n [placeholder]=\"tx('e.g.: my-list-skin')\"\n />\n </mat-form-field>\n\n @if (working.skin.type === 'custom') {\n <div\n class=\"g g-auto-220 gap-12 ai-end\"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>{{ tx('Inline style (skin.inlineStyle)') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [(ngModel)]=\"working.skin.inlineStyle\"\n (ngModelChange)=\"onSkinChanged()\"\n [attr.placeholder]=\"':host{--p-list-radius: 1rem}'\"\n ></textarea>\n </mat-form-field>\n <div class=\"text-caption\">\n {{ tx('CSS class example (add this to your global styles):') }}\n <pre class=\"code-block\">\n .my-list-skin .item-card {\n border-radius: 14px;\n border: 1px solid var(--md-sys-color-outline-variant);\n box-shadow: var(--md-sys-elevation-level2);\n }\n .my-list-skin .mat-mdc-list-item .list-item-content {\n backdrop-filter: blur(6px);\n }</pre\n >\n </div>\n </div>\n }\n </div>\n </ng-template>\n </mat-tab>\n </mat-tab-group>\n", styles: [".confirm-type{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;line-height:16px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant)}.confirm-type.danger{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.confirm-type.warning{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.confirm-type.info{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}:host{display:block;color:var(--md-sys-color-on-surface)}.list-editor-tabs{--editor-surface: var(--md-sys-color-surface-container-lowest);--editor-border: 1px solid var(--md-sys-color-outline-variant);--editor-radius: var(--md-sys-shape-corner-large, 16px);--editor-muted: var(--md-sys-color-on-surface-variant);--editor-accent: var(--md-sys-color-primary)}.editor-content{padding:16px;background:var(--editor-surface);border:var(--editor-border);border-radius:var(--editor-radius);display:grid;gap:12px}.editor-content .mat-mdc-form-field{width:100%;max-width:none;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var( --md-sys-color-outline-variant );--mdc-outlined-text-field-hover-outline-color: var( --md-sys-color-outline );--mdc-outlined-text-field-focus-outline-color: var( --md-sys-color-primary );--mdc-outlined-text-field-error-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-error-focus-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-error-hover-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-label-text-color: var( --md-sys-color-on-surface-variant );--mdc-outlined-text-field-input-text-color: var( --md-sys-color-on-surface );--mdc-outlined-text-field-supporting-text-color: var( --md-sys-color-on-surface-variant )}.editor-content .mat-mdc-form-field.w-full{max-width:none}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}.editor-split{grid-template-columns:minmax(0,1fr);align-items:start}.editor-main,.editor-aside{display:grid;gap:12px}.skin-preview-wrap{border-radius:calc(var(--editor-radius) - 4px);border:var(--editor-border);background:var(--md-sys-color-surface-container);padding:12px}.g{display:grid}.g-auto-220{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.g-auto-200{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}.g-1-auto{grid-template-columns:1fr auto}.row-flow{grid-auto-flow:column}.gap-6{gap:6px}.gap-8{gap:8px}.gap-12{gap:12px}.ai-center{align-items:center}.ai-end{align-items:end}.mt-12{margin-top:12px}.mb-8{margin-bottom:8px}.mb-6{margin-bottom:6px}.my-8{margin:8px 0}.subtitle{margin:8px 0 4px;color:var(--editor-muted);font-weight:500}.section-title{color:var(--editor-muted);font-weight:600}.chips-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.error{color:var(--md-sys-color-error);font-size:.85rem}.muted{color:var(--editor-muted)}.text-caption{color:var(--editor-muted);font-size:.8rem}:host ::ng-deep .mat-mdc-select-panel .option-icon{font-size:18px;margin-right:6px;vertical-align:middle}:host ::ng-deep .mat-mdc-select-panel .color-dot{width:10px;height:10px;border-radius:999px;display:inline-block;margin-right:6px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-outline)}:host ::ng-deep .mat-mdc-select-panel .color-primary{background:var(--md-sys-color-primary)}:host ::ng-deep .mat-mdc-select-panel .color-accent{background:var(--md-sys-color-tertiary)}:host ::ng-deep .mat-mdc-select-panel .color-warn{background:var(--md-sys-color-error)}:host ::ng-deep .mat-mdc-select-panel .color-default{background:var(--md-sys-color-outline)}@media(max-width:1024px){.editor-split{grid-template-columns:minmax(0,1fr)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.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$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i3$3.MatTabContent, selector: "[matTabContent]" }, { kind: "component", type: i3$3.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$3.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i2.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.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: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i8.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i10.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i10.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i10.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i10.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i10.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatButtonToggleModule }, { kind: "directive", type: i11.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i11.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i12.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i3.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "ngmodule", type: MatMenuModule }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisListSkinPreviewComponent, selector: "praxis-list-skin-preview", inputs: ["config", "items", "theme"] }, { kind: "component", type: PraxisMetaEditorTextComponent, selector: "praxis-meta-editor-text", inputs: ["model", "setPipe"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorChipComponent, selector: "praxis-meta-editor-chip", inputs: ["model", "paletteOptions", "colorDotBackground", "isCustomColor", "enableCustomColor"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorRatingComponent, selector: "praxis-meta-editor-rating", inputs: ["model", "paletteOptions", "colorDotBackground", "isCustomColor", "enableCustomColor"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorCurrencyComponent, selector: "praxis-meta-editor-currency", inputs: ["model"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorDateComponent, selector: "praxis-meta-editor-date", inputs: ["model"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorIconComponent, selector: "praxis-meta-editor-icon", inputs: ["model", "paletteOptions", "colorDotBackground", "isCustomColor", "enableCustomColor"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorImageComponent, selector: "praxis-meta-editor-image", inputs: ["model"], outputs: ["change"] }, { kind: "component", type: PraxisListJsonConfigEditorComponent, selector: "praxis-list-json-config-editor", inputs: ["document"], outputs: ["documentChange", "validationChange", "editorEvent"] }, { kind: "component", type: PdxColorPickerComponent, selector: "pdx-color-picker", inputs: ["actionsLayout", "activeView", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "clearButton", "disabledMode", "readonlyMode", "visible", "presentationMode", "fillMode", "format", "gradientSettings", "icon", "iconClass", "svgIcon", "paletteSettings", "popupSettings", "preview", "rounded", "size", "tabindex", "views", "maxRecent", "showRecent"], outputs: ["valueChange", "open", "close", "cancel", "activeViewChange", "activeColorClick", "focusEvent", "blurEvent"] }, { kind: "component", type: SurfaceOpenActionEditorComponent, selector: "praxis-surface-open-action-editor", inputs: ["value", "hostKind"], outputs: ["valueChange"] }] });
|
|
6051
6050
|
}
|
|
6052
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
6051
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisListConfigEditor, decorators: [{
|
|
6053
6052
|
type: Component,
|
|
6054
6053
|
args: [{ selector: 'praxis-list-config-editor', standalone: true, providers: [
|
|
6055
6054
|
{
|
|
@@ -6091,7 +6090,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
6091
6090
|
PraxisListJsonConfigEditorComponent,
|
|
6092
6091
|
PdxColorPickerComponent,
|
|
6093
6092
|
SurfaceOpenActionEditorComponent,
|
|
6094
|
-
], template: "<mat-tab-group class=\"list-editor-tabs\">\n <mat-tab [label]=\"tx('Data')\">\n <ng-template matTabContent>\n <div class=\"editor-content\">\n <div class=\"g g-1-auto gap-8 ai-center\">\n <div class=\"muted\">\n {{ tx('Assistant-applied adjustments replace the entire configuration object.') }}\n </div>\n <button\n mat-icon-button\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('applyConfigFromAdapter does not perform a deep merge. Make sure the adapter sends the full config.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </div>\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>{{ tx('Resource (API)') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.dataSource.resourcePath\"\n (ngModelChange)=\"onResourcePathChange($event)\"\n [placeholder]=\"tx('e.g.: users')\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Resource endpoint (resourcePath).')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>{{ tx('Query (JSON)') }}</mat-label>\n <textarea\n matInput\n rows=\"3\"\n [(ngModel)]=\"queryJson\"\n (ngModelChange)=\"onQueryChanged($event)\"\n [placeholder]=\"queryPlaceholder()\"\n ></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Optional. Use valid JSON for initial filters.')\"\n *ngIf=\"!queryError\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error *ngIf=\"queryError\">{{ queryError }}</mat-error>\n </mat-form-field>\n <div class=\"g g-auto-220 gap-12 ai-end mt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Sort by') }}</mat-label>\n <mat-select\n [(ngModel)]=\"sortField\"\n (ngModelChange)=\"updateSortConfig()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Resource base field.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Direction') }}</mat-label>\n <mat-select\n [(ngModel)]=\"sortDir\"\n (ngModelChange)=\"updateSortConfig()\"\n >\n <mat-option value=\"asc\">{{ tx('Ascending') }}</mat-option>\n <mat-option value=\"desc\">{{ tx('Descending') }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('JSON')\">\n <ng-template matTabContent>\n <div class=\"editor-content\">\n <praxis-list-json-config-editor\n [document]=\"document\"\n (documentChange)=\"onJsonConfigChange($event)\"\n (validationChange)=\"onJsonValidationChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n >\n </praxis-list-json-config-editor>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Actions')\">\n <ng-template matTabContent>\n <div class=\"editor-content g gap-12\">\n <div class=\"g g-1-auto gap-8 ai-center\">\n <div class=\"muted\">\n {{ tx('Configure per-item action buttons (icon, label, color, visibility)') }}\n </div>\n <button mat-flat-button color=\"primary\" (click)=\"addAction()\">\n {{ tx('Add action') }}\n </button>\n </div>\n <div class=\"g g-1-auto gap-8 ai-center\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Global action (Praxis)') }}</mat-label>\n <mat-select\n [(ngModel)]=\"selectedGlobalActionId\"\n (ngModelChange)=\"onGlobalActionSelected($event)\"\n >\n <mat-option [value]=\"undefined\">{{ tx('-- Select --') }}</mat-option>\n <mat-option *ngFor=\"let ga of globalActionCatalog\" [value]=\"ga.id\">\n <mat-icon class=\"option-icon\">{{ ga.icon || \"bolt\" }}</mat-icon>\n {{ ga.label }}\n </mat-option>\n </mat-select>\n <mat-hint\n *ngIf=\"!globalActionCatalog.length\"\n class=\"text-caption muted\"\n >{{ tx('No global action registered.') }}</mat-hint\n >\n </mat-form-field>\n <div class=\"muted text-caption\">\n {{ tx('Select to add with a structured global action.') }}\n </div>\n </div>\n <div\n *ngFor=\"let a of working.actions || []; let i = index\"\n class=\"g g-auto-200 gap-12 ai-end\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('ID') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.id\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Action type') }}</mat-label>\n <mat-select [(ngModel)]=\"a.kind\" (ngModelChange)=\"onActionsChanged()\">\n <mat-option value=\"icon\">{{ tx('Icon') }}</mat-option>\n <mat-option value=\"button\">{{ tx('Button') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Icon') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.icon\"\n (ngModelChange)=\"onActionsChanged()\"\n [placeholder]=\"tx('e.g.: edit, delete')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Global action') }}</mat-label>\n <mat-select\n [ngModel]=\"getGlobalActionId(a)\"\n (ngModelChange)=\"onActionGlobalActionIdChange(a, $event)\"\n >\n <mat-option value=\"\">{{ tx('None') }}</mat-option>\n <mat-option *ngFor=\"let action of globalActionCatalog\" [value]=\"action.id\">\n {{ action.label || action.id }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Label') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.label\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n </mat-form-field>\n <ng-container *ngIf=\"a.kind === 'button'\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Variant') }}</mat-label>\n <mat-select\n [(ngModel)]=\"a.buttonVariant\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option value=\"stroked\">{{ tx('Outlined') }}</mat-option>\n <mat-option value=\"raised\">{{ tx('Raised') }}</mat-option>\n <mat-option value=\"flat\">{{ tx('Filled') }}</mat-option>\n </mat-select>\n </mat-form-field>\n </ng-container>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Action color') }}</mat-label>\n <mat-select\n [(ngModel)]=\"a.color\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option *ngFor=\"let c of paletteOptions\" [value]=\"c.value\">\n <span\n class=\"color-dot\"\n [style.background]=\"colorDotBackground(c.value)\"\n ></span\n >{{ tx(c.label) }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <div\n class=\"g gap-8\"\n *ngIf=\"isCustomColor(a.color); else actionCustomBtn\"\n >\n <pdx-color-picker\n [label]=\"tx('Custom color')\"\n [format]=\"'hex'\"\n [(ngModel)]=\"a.color\"\n (ngModelChange)=\"onActionsChanged()\"\n ></pdx-color-picker>\n </div>\n <ng-template #actionCustomBtn>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"enableCustomActionColor(a)\"\n >\n {{ tx('Use custom color') }}\n </button>\n </ng-template>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Action payload') }}</mat-label>\n <mat-select\n [(ngModel)]=\"a.emitPayload\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option [value]=\"undefined\">{{ tx('Default') }}</mat-option>\n <mat-option value=\"item\">{{ tx('Item') }}</mat-option>\n <mat-option value=\"id\">{{ tx('ID') }}</mat-option>\n <mat-option value=\"value\">{{ tx('Value') }}</mat-option>\n </mat-select>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Payload emitted by the action.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"col-span-2\">\n <mat-label>{{ actionVisibilityLabel() }}</mat-label>\n <textarea\n matInput\n rows=\"3\"\n [ngModel]=\"actionShowIfModel(i)\"\n (ngModelChange)=\"onActionShowIfChanged(i, a, $event)\"\n ></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"actionConditionTooltip()\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"g row-flow gap-8 ai-center\">\n <button\n *ngIf=\"(a.kind || 'icon') === 'icon'\"\n mat-icon-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n >\n <mat-icon\n [praxisIcon]=\"a.icon || 'bolt'\"\n [style.cssText]=\"iconStyle(a.color)\"\n ></mat-icon>\n </button>\n <ng-container *ngIf=\"a.kind === 'button'\">\n <button\n *ngIf=\"a.buttonVariant === 'stroked'\"\n mat-stroked-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'stroked')\"\n >\n {{ a.label || a.id || tx('Action') }}\n </button>\n <button\n *ngIf=\"a.buttonVariant === 'raised'\"\n mat-raised-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'raised')\"\n >\n {{ a.label || a.id || tx('Action') }}\n </button>\n <button\n *ngIf=\"!a.buttonVariant || a.buttonVariant === 'flat'\"\n mat-flat-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'flat')\"\n >\n {{ a.label || a.id || tx('Action') }}\n </button>\n </ng-container>\n <span class=\"muted\">{{ tx('Preview') }}</span>\n </div>\n <div class=\"flex-end\">\n <button mat-button color=\"warn\" (click)=\"removeAction(i)\">\n {{ tx('Remove') }}\n </button>\n </div>\n <div class=\"g gap-8 col-span-2\" *ngIf=\"a.globalAction?.actionId\">\n <mat-slide-toggle\n [(ngModel)]=\"a.showLoading\"\n (ngModelChange)=\"onActionsChanged()\"\n >{{ tx('Show loading') }}</mat-slide-toggle\n >\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >{{ tx('Confirmation') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <div class=\"g row-flow gap-8 ai-center\">\n <span class=\"text-caption muted\">{{ tx('Type') }}</span>\n <mat-button-toggle-group\n [value]=\"a.confirmation?.type || ''\"\n (change)=\"applyConfirmationPreset(a, $event.value)\"\n >\n <mat-button-toggle value=\"\">{{ tx('Default') }}</mat-button-toggle>\n <mat-button-toggle value=\"danger\">{{ tx('Danger') }}</mat-button-toggle>\n <mat-button-toggle value=\"warning\">{{ tx('Warning') }}</mat-button-toggle>\n <mat-button-toggle value=\"info\">{{ tx('Info') }}</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Title') }}</mat-label>\n <input\n matInput\n [ngModel]=\"a.confirmation?.title\"\n (ngModelChange)=\"setConfirmationField(a, 'title', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Message') }}</mat-label>\n <input\n matInput\n [ngModel]=\"a.confirmation?.message\"\n (ngModelChange)=\"setConfirmationField(a, 'message', $event)\"\n />\n </mat-form-field>\n <div class=\"g gap-6\">\n <div class=\"text-caption muted\">{{ tx('Preview') }}</div>\n <div class=\"text-caption\">\n <strong>{{\n a.confirmation?.title || tx('Confirm action')\n }}</strong>\n </div>\n <div class=\"text-caption muted\">\n {{\n a.confirmation?.message ||\n tx('Are you sure you want to continue?')\n }}\n </div>\n <div class=\"text-caption\">\n <span\n class=\"confirm-type\"\n [ngClass]=\"a.confirmation?.type || 'default'\"\n >{{ tx('Type') }}:\n {{ a.confirmation?.type || tx('Default').toLowerCase() }}</span\n >\n </div>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!a.confirmation?.title && !a.confirmation?.message\"\n >\n {{ tx('Set a title or message for the confirmation.') }}\n </div>\n </div>\n </div>\n </mat-expansion-panel>\n <ng-container *ngIf=\"isSurfaceOpenCommand(a); else defaultGlobalActionPayloadEditor\">\n <div class=\"col-span-2\">\n <praxis-surface-open-action-editor\n [value]=\"getSurfaceOpenGlobalActionPayload(a)\"\n hostKind=\"list\"\n (valueChange)=\"onSurfaceOpenGlobalActionPayloadChange(a, $event)\"\n ></praxis-surface-open-action-editor>\n </div>\n </ng-container>\n <ng-template #defaultGlobalActionPayloadEditor>\n <mat-form-field appearance=\"outline\" class=\"col-span-2\">\n <mat-label>{{ tx('Payload (JSON/Template)') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"getGlobalActionPayloadText(a)\"\n (ngModelChange)=\"onGlobalActionPayloadTextChange(a, $event)\"\n placeholder='{\"message\":\"${item.name} favoritado\"}'\n ></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"globalActionPayloadSchemaTooltip(a)\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error *ngIf=\"isGlobalActionPayloadInvalid(a)\"\n >{{ tx('Invalid JSON') }}</mat-error\n >\n </mat-form-field>\n <div class=\"g row-flow gap-8 ai-center\">\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"applyGlobalActionPayloadExample(a)\"\n >\n {{ tx('Insert example') }}\n </button>\n <span class=\"muted text-caption\">{{\n globalActionPayloadExampleHint(a)\n }}</span>\n </div>\n </ng-template>\n <mat-slide-toggle\n [(ngModel)]=\"a.emitLocal\"\n (ngModelChange)=\"onActionsChanged()\"\n >{{ tx('Emit local event too') }}</mat-slide-toggle\n >\n </div>\n </div>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Layout')\">\n <ng-template matTabContent>\n <div class=\"editor-content grid gap-3\">\n <div class=\"preset-row g row-flow gap-8\">\n <button mat-stroked-button (click)=\"applyLayoutPreset('tiles-modern')\">\n {{ tx('Modern tiles preset') }}\n </button>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Variant') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.variant\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"list\">{{ tx('List') }}</mat-option>\n <mat-option value=\"cards\">{{ tx('Cards') }}</mat-option>\n <mat-option value=\"tiles\">{{ tx('Tiles') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Model') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.model\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <ng-container\n *ngIf=\"working.layout.variant === 'list'; else cardModels\"\n >\n <mat-option value=\"standard\">{{ tx('Standard') }}</mat-option>\n <mat-option value=\"media\">{{ tx('Media on the left') }}</mat-option>\n <mat-option value=\"hotel\">{{ tx('Hotel (large media)') }}</mat-option>\n </ng-container>\n <ng-template #cardModels>\n <ng-container\n *ngIf=\"working.layout.variant === 'tiles'; else cardsOnly\"\n >\n <mat-option value=\"standard\">{{ tx('Standard tile') }}</mat-option>\n <mat-option value=\"media\">{{ tx('Tile with media') }}</mat-option>\n <mat-option value=\"hotel\">{{ tx('Hotel tile') }}</mat-option>\n </ng-container>\n <ng-template #cardsOnly>\n <mat-option value=\"standard\">{{ tx('Standard') }}</mat-option>\n <mat-option value=\"media\">{{ tx('Card with media') }}</mat-option>\n <mat-option value=\"hotel\">{{ tx('Hotel') }}</mat-option>\n </ng-template>\n </ng-template>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Lines') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.lines\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option [value]=\"1\">1</mat-option>\n <mat-option [value]=\"2\">2</mat-option>\n <mat-option [value]=\"3\">3</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Items per page') }}</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"1\"\n [(ngModel)]=\"working.layout.pageSize\"\n (ngModelChange)=\"onPageSizeChange($event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Density') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.density\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"default\">{{ tx('Default') }}</mat-option>\n <mat-option value=\"comfortable\">{{ tx('Comfortable') }}</mat-option>\n <mat-option value=\"compact\">{{ tx('Compact') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Spacing between items') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.itemSpacing\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"none\">{{ tx('No extra space') }}</mat-option>\n <mat-option value=\"tight\">{{ tx('Tight') }}</mat-option>\n <mat-option value=\"default\">{{ tx('Standard') }}</mat-option>\n <mat-option value=\"relaxed\">{{ tx('Relaxed') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"working.layout.variant !== 'tiles'\"\n >\n <mat-label>{{ tx('Dividers') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.dividers\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"none\">{{ tx('None') }}</mat-option>\n <mat-option value=\"between\">{{ tx('Between groups') }}</mat-option>\n <mat-option value=\"all\">{{ tx('All') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <ng-container *ngIf=\"fields.length > 0; else groupByText\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Group by') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.groupBy\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option [value]=\"\">{{ tx('None') }}</mat-option>\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n </ng-container>\n <ng-template #groupByText>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Group by') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.layout.groupBy\"\n (ngModelChange)=\"onLayoutChanged()\"\n [placeholder]=\"tx('e.g.: department')\"\n />\n </mat-form-field>\n </ng-template>\n <mat-slide-toggle\n [(ngModel)]=\"working.layout.stickySectionHeader\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n {{ tx('Sticky section header') }}\n </mat-slide-toggle>\n <mat-slide-toggle\n [(ngModel)]=\"working.layout.virtualScroll\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n {{ tx('Virtual scroll') }}\n </mat-slide-toggle>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">{{ tx('List tools') }}</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showSearch\"\n (ngModelChange)=\"onUiChanged()\"\n >{{ tx('Show search') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showSort\"\n (ngModelChange)=\"onUiChanged()\"\n >{{ tx('Show sorting') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showRange\"\n (ngModelChange)=\"onUiChanged()\"\n >{{ tx('Show total X-Y range') }}</mat-slide-toggle\n >\n </div>\n <div\n class=\"g g-auto-220 gap-12 ai-end mt-12\"\n *ngIf=\"working.ui?.showSearch\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field to search') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.ui.searchField\"\n (ngModelChange)=\"onUiChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Search placeholder') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.ui.searchPlaceholder\"\n (ngModelChange)=\"onUiChanged()\"\n [placeholder]=\"tx('e.g.: Search by title')\"\n />\n </mat-form-field>\n </div>\n <div class=\"mt-12\" *ngIf=\"working.ui?.showSort\">\n <div class=\"g g-1-auto ai-center gap-8\">\n <div class=\"muted\">\n {{ tx('Sorting options (label \u2192 field+direction)') }}\n </div>\n <button mat-flat-button color=\"primary\" (click)=\"addUiSortRow()\">\n {{ tx('Add option') }}\n </button>\n </div>\n <div\n class=\"g g-auto-220 gap-12 ai-end mt-12\"\n *ngFor=\"let r of uiSortRows; let i = index\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Label') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"r.label\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n [placeholder]=\"tx('e.g.: Most recent')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"r.field\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Direction') }}</mat-label>\n <mat-select\n [(ngModel)]=\"r.dir\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n >\n <mat-option value=\"desc\">{{ tx('Descending') }}</mat-option>\n <mat-option value=\"asc\">{{ tx('Ascending') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"error\" *ngIf=\"isUiSortRowDuplicate(i)\">\n {{ tx('Duplicate option (field+direction)') }}\n </div>\n <div class=\"flex-end\">\n <button mat-button color=\"warn\" (click)=\"removeUiSortRow(i)\">\n {{ tx('Remove') }}\n </button>\n </div>\n </div>\n </div>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Content')\">\n <ng-template matTabContent>\n <div class=\"editor-content\">\n <div class=\"editor-main\">\n <mat-accordion multi>\n <!-- Primary -->\n <mat-expansion-panel [expanded]=\"true\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingPrimary.type) }}</mat-icon>\n <span>{{ tx('Primary (Title)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingPrimary.field || tx('Not mapped')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'name';\n onMappingChanged()\n \"\n >\n {{ tx('Name') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'title';\n onMappingChanged()\n \"\n >\n {{ tx('Title') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'name';\n mappingSecondary.type = 'text';\n mappingSecondary.field = 'role';\n onMappingChanged()\n \"\n >\n {{ tx('Name + role') }}\n </button>\n </div>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingPrimary.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingPrimary.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of primaryTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n @switch (mappingPrimary.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingPrimary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingPrimary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingPrimary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingPrimary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n }\n\n <!-- Advanced -->\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header>\n <mat-panel-title>{{ tx('Formatting and style') }}</mat-panel-title>\n </mat-expansion-panel-header>\n <div class=\"g gap-12 pt-12\">\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"\n mappingPrimary.type === 'text' ||\n mappingPrimary.type === 'html'\n \"\n >\n <mat-label>{{ tx('CSS class') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"\n mappingPrimary.type === 'text' ||\n mappingPrimary.type === 'html'\n \"\n >\n <mat-label>{{ tx('Inline style') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('CSS class') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Inline style') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Secondary -->\n <mat-expansion-panel [expanded]=\"!!mappingSecondary.field\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingSecondary.type) }}</mat-icon>\n <span>{{ tx('Secondary (Summary)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingSecondary.field || tx('Not mapped')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'text';\n mappingSecondary.field = 'subtitle';\n onMappingChanged()\n \"\n >\n {{ tx('Subtitle') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'date';\n mappingSecondary.field = 'hireDate';\n mappingSecondary.dateStyle = 'short';\n onMappingChanged()\n \"\n >\n {{ tx('Short date') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"applySalaryPreset()\"\n >\n {{ tx('Salary') }}\n </button>\n </div>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSecondary.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSecondary.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of secondaryTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n @switch (mappingSecondary.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingSecondary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingSecondary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingSecondary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingSecondary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header>\n <mat-panel-title>{{ tx('Formatting and style') }}</mat-panel-title>\n </mat-expansion-panel-header>\n <div class=\"g gap-12 pt-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('CSS class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSecondary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Inline style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSecondary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <mat-expansion-panel\n [expanded]=\"!!mappingMeta.field || mappingMetaFields.length > 0\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingMeta.type || \"text\")\n }}</mat-icon>\n <span>{{ tx('Meta (Detail/Side)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>\n {{\n mappingMetaFields.length\n ? tx('Composed field ({{count}})', { count: mappingMetaFields.length })\n : mappingMeta.field || tx('Not mapped')\n }}\n </mat-panel-description>\n </mat-expansion-panel-header>\n\n <div class=\"g gap-12\">\n <!-- Composition Mode Toggle -->\n <div class=\"g g-1-1 gap-12 p-12 bg-subtle rounded\">\n <div class=\"text-caption muted\">{{ tx('Composition mode') }}</div>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Fields to compose (multi-select)') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMetaFields\"\n multiple\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <div\n class=\"g g-1-1 ai-center gap-12\"\n *ngIf=\"mappingMetaFields.length\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Separator') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMetaSeparator\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-slide-toggle\n [(ngModel)]=\"mappingMetaWrapSecondInParens\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n {{ tx('(Second) in parentheses') }}\n </mat-slide-toggle>\n </div>\n </div>\n\n <!-- Single Field Mode (if no composition) -->\n <div class=\"g g-1-1 gap-12\" *ngIf=\"!mappingMetaFields.length\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Single field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option [value]=\"undefined\">{{ tx('-- None --') }}</mat-option>\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of metaTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- Type configuration (pluggable editors) -->\n @switch (mappingMeta.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingMeta\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingMeta\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <praxis-meta-editor-image\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <!-- Advanced -->\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Advanced options') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Position') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.placement\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option value=\"side\">{{ tx('Side (right)') }}</mat-option>\n <mat-option value=\"line\">{{ tx('Inline (below)') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('CSS class') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMeta.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Style') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMeta.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n <!-- Trailing -->\n <mat-expansion-panel [expanded]=\"!!mappingTrailing.field\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingTrailing.type || \"text\")\n }}</mat-icon>\n <span>{{ tx('Trailing (right)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingTrailing.field || tx('Not mapped')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingTrailing.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option [value]=\"undefined\">{{ tx('-- None --') }}</mat-option>\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingTrailing.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of trailingTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'chip';\n mappingTrailing.chipColor = 'primary';\n mappingTrailing.chipVariant = 'filled';\n mappingTrailing.field = 'status';\n onMappingChanged()\n \"\n >\n {{ tx('Status chip') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'icon';\n mappingTrailing.field = 'status';\n mappingTrailing.iconColor = 'primary';\n onMappingChanged()\n \"\n >\n {{ tx('Status icon') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"applyPricePreset()\"\n >\n {{ tx('Price') }}\n </button>\n </div>\n\n @switch (mappingTrailing.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingTrailing\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingTrailing\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('URL / Expr') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingTrailing.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n [placeholder]=\"tx('https://... or ${item.imageUrl}')\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Use an absolute/relative URL or a ${item.field} expression.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error\n *ngIf=\"\n isImageUrlRequiredInvalid(mappingTrailing.imageUrl)\n \"\n >{{ tx('URL/expr is required') }}</mat-error\n >\n </mat-form-field>\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!mappingTrailing.imageUrl\"\n >\n {{ tx('Set the URL/expr to render the image.') }}\n </div>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Style') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingTrailing.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingTrailing.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Leading -->\n <mat-expansion-panel\n [expanded]=\"\n !!mappingLeading.field ||\n (mappingLeading.type === 'icon' && !!mappingLeading.icon) ||\n (mappingLeading.type === 'image' && !!mappingLeading.imageUrl)\n \"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingLeading.type) }}</mat-icon>\n <span>{{ tx('Leading (left)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>\n {{\n mappingLeading.type === \"icon\"\n ? mappingLeading.icon || tx('Static icon')\n : mappingLeading.field ||\n (mappingLeading.imageUrl\n ? tx('Static image')\n : tx('Not mapped'))\n }}\n </mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingLeading.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of leadingTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <!-- Field (only if not static icon/image, though user might want dynamic) -->\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"\n mappingLeading.type !== 'icon' &&\n mappingLeading.type !== 'image'\n \"\n >\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingLeading.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'icon';\n mappingLeading.icon = 'person';\n mappingLeading.iconColor = 'primary';\n onMappingChanged()\n \"\n >\n {{ tx('Avatar icon') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'image';\n mappingLeading.imageUrl = 'https://placehold.co/64x64';\n mappingLeading.imageAlt = 'Avatar';\n mappingLeading.badgeText = '${item.status}';\n onMappingChanged()\n \"\n >\n {{ tx('Avatar image + badge') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'chip';\n mappingLeading.field = 'tag';\n mappingLeading.chipColor = 'accent';\n mappingLeading.chipVariant = 'filled';\n onMappingChanged()\n \"\n >\n {{ tx('Tag chip') }}\n </button>\n </div>\n\n <!-- Icon Specific -->\n <div\n class=\"g g-1-auto gap-12 ai-center\"\n *ngIf=\"mappingLeading.type === 'icon'\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Icon') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.icon\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <button mat-icon-button matSuffix (click)=\"pickLeadingIcon()\">\n <mat-icon>search</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"text-caption muted\">\n {{ tx('Use the `|iconMap` pipe in the extra pipe for dynamic rendering.') }}\n </div>\n </div>\n <div *ngIf=\"mappingLeading.type === 'icon'\">\n <praxis-meta-editor-icon\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n </div>\n\n <!-- Image Specific -->\n <div\n class=\"g g-1-1 gap-12\"\n *ngIf=\"mappingLeading.type === 'image'\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Image URL') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n [placeholder]=\"tx('https://... or ${item.imageUrl}')\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Use an absolute/relative URL or a ${item.field} expression.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error\n *ngIf=\"isImageUrlRequiredInvalid(mappingLeading.imageUrl)\"\n >{{ tx('URL/expr is required') }}</mat-error\n >\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Alt text') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.imageAlt\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Badge text') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.badgeText\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n\n @switch (mappingLeading.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingLeading\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingLeading\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Style') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingLeading.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingLeading.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Features -->\n <mat-expansion-panel\n [expanded]=\"featuresVisible && features.length > 0\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>view_list</mat-icon>\n <span>{{ tx('Features') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description\n >{{ features.length }} item(s)</mat-panel-description\n >\n </mat-expansion-panel-header>\n\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-12 ai-center\">\n <mat-slide-toggle\n [(ngModel)]=\"featuresVisible\"\n (ngModelChange)=\"onFeaturesChanged()\"\n >{{ tx('Enable features') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"featuresSyncWithMeta\"\n (ngModelChange)=\"onMappingChanged()\"\n >{{ tx('Sync with Meta') }}</mat-slide-toggle\n >\n <span class=\"flex-1\"></span>\n <mat-button-toggle-group\n [(ngModel)]=\"featuresMode\"\n (change)=\"onFeaturesChanged()\"\n appearance=\"legacy\"\n >\n <mat-button-toggle value=\"icons+labels\"\n ><mat-icon>view_list</mat-icon></mat-button-toggle\n >\n <mat-button-toggle value=\"icons-only\"\n ><mat-icon>more_horiz</mat-icon></mat-button-toggle\n >\n </mat-button-toggle-group>\n </div>\n\n <div\n *ngFor=\"let f of features; let i = index\"\n class=\"g g-auto-1 gap-8 ai-center p-8 border rounded mb-2\"\n >\n <button mat-icon-button (click)=\"pickFeatureIcon(i)\">\n <mat-icon>{{ f.icon || \"search\" }}</mat-icon>\n </button>\n <mat-form-field\n appearance=\"outline\"\n class=\"dense-form-field no-sub\"\n >\n <input\n matInput\n [(ngModel)]=\"f.expr\"\n (ngModelChange)=\"onFeaturesChanged()\"\n [placeholder]=\"tx('Expression/Text')\"\n />\n </mat-form-field>\n <button mat-icon-button color=\"warn\" (click)=\"removeFeature(i)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n <button mat-button color=\"primary\" (click)=\"addFeature()\">\n <mat-icon>add</mat-icon> {{ tx('Add feature') }}\n </button>\n </div>\n </mat-expansion-panel>\n <!-- Section Header -->\n <mat-expansion-panel [expanded]=\"!!mappingSectionHeader.expr\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingSectionHeader.type)\n }}</mat-icon>\n <span>{{ tx('Section header') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingSectionHeader.expr || tx('Not configured')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSectionHeader.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of sectionHeaderTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Expression (item.key)') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingSectionHeader.expr\"\n (ngModelChange)=\"onMappingChanged()\"\n placeholder=\"item.key\"\n />\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSectionHeader.type = 'text';\n mappingSectionHeader.expr = '${item.key}';\n onMappingChanged()\n \"\n >\n {{ tx('Default text') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSectionHeader.type = 'chip';\n mappingSectionHeader.chipColor = 'primary';\n mappingSectionHeader.chipVariant = 'filled';\n mappingSectionHeader.expr = '${item.key}';\n onMappingChanged()\n \"\n >\n {{ tx('Default chip') }}\n </button>\n </div>\n\n @switch (mappingSectionHeader.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingSectionHeader\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingSectionHeader\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Image URL') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingSectionHeader.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <mat-error\n *ngIf=\"\n isImageUrlRequiredInvalid(\n mappingSectionHeader.imageUrl\n )\n \"\n >{{ tx('URL/expr is required') }}</mat-error\n >\n </mat-form-field>\n </div>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!mappingSectionHeader.imageUrl\"\n >\n {{ tx('Set the URL/expr to render the image.') }}\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingSectionHeader\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Style') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSectionHeader.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSectionHeader.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Empty State -->\n <mat-expansion-panel [expanded]=\"!!mappingEmptyState.expr\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>inbox</mat-icon>\n <span>{{ tx('Empty state') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingEmptyState.expr || tx('Default')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingEmptyState.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of emptyStateTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Message / Expr') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingEmptyState.expr\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingEmptyState.type = 'text';\n mappingEmptyState.expr = tx('No items available');\n onMappingChanged()\n \"\n >\n {{ tx('Default message') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingEmptyState.type = 'image';\n mappingEmptyState.imageUrl = '/list-empty-state.svg';\n mappingEmptyState.imageAlt = tx('No results');\n onMappingChanged()\n \"\n >\n {{ tx('Default image') }}\n </button>\n </div>\n\n @switch (mappingEmptyState.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingEmptyState\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingEmptyState\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Image URL') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <mat-error\n *ngIf=\"\n isImageUrlRequiredInvalid(mappingEmptyState.imageUrl)\n \"\n >{{ tx('URL/expr is required') }}</mat-error\n >\n </mat-form-field>\n </div>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!mappingEmptyState.imageUrl\"\n >\n {{ tx('Set the URL/expr to render the image.') }}\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingEmptyState\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Style') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n </mat-accordion>\n\n <button mat-flat-button color=\"primary\" (click)=\"applyTemplate()\">\n {{ tx('Apply mapping') }}\n </button>\n <button\n mat-button\n (click)=\"inferFromFields()\"\n [disabled]=\"!fields.length\"\n >\n {{ tx('Infer from schema') }}\n </button>\n <div class=\"g g-auto-220 gap-12 ai-end mt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Skeleton count') }}</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"0\"\n [(ngModel)]=\"skeletonCountInput\"\n (ngModelChange)=\"onSkeletonChanged($event)\"\n />\n </mat-form-field>\n </div>\n\n <div class=\"g gap-12 mt-12\">\n <div class=\"g row-flow gap-8 ai-center\">\n <span class=\"section-title mat-subtitle-1\">{{ tx('Theme preview') }}</span>\n <mat-button-toggle-group\n [(ngModel)]=\"skinPreviewTheme\"\n (change)=\"onSkinChanged()\"\n appearance=\"legacy\"\n >\n <mat-button-toggle [value]=\"'light'\">{{ tx('Light') }}</mat-button-toggle>\n <mat-button-toggle [value]=\"'dark'\">{{ tx('Dark') }}</mat-button-toggle>\n <mat-button-toggle [value]=\"'grid'\">{{ tx('Grid') }}</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <div class=\"skin-preview-wrap\">\n <praxis-list-skin-preview\n [config]=\"working\"\n [items]=\"previewData\"\n [theme]=\"skinPreviewTheme\"\n ></praxis-list-skin-preview>\n </div>\n </div>\n </div>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('i18n/A11y')\">\n <ng-template matTabContent>\n <div\n class=\"editor-content grid gap-3\"\n *ngIf=\"working?.a11y && working?.events\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Default locale') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.i18n.locale\"\n (ngModelChange)=\"markDirty()\"\n [placeholder]=\"tx('e.g.: en-US')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Default currency') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.i18n.currency\"\n (ngModelChange)=\"markDirty()\"\n [placeholder]=\"currencyPlaceholder()\"\n />\n </mat-form-field>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">{{ tx('Accessibility') }}</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('aria-label') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.a11y!.ariaLabel\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('aria-labelledby') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.a11y!.ariaLabelledBy\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n </div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-slide-toggle\n [(ngModel)]=\"working!.a11y!.highContrast\"\n (ngModelChange)=\"markDirty()\"\n >{{ tx('High contrast') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working!.a11y!.reduceMotion\"\n (ngModelChange)=\"markDirty()\"\n >{{ tx('Reduce motion') }}</mat-slide-toggle\n >\n </div>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">{{ tx('Events') }}</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('itemClick') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.itemClick\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>actionClick</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.actionClick\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>selectionChange</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.selectionChange\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>loaded</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.loaded\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n </div>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Selection')\">\n <ng-template matTabContent>\n <div class=\"editor-content grid gap-3\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Mode') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.selection.mode\"\n (ngModelChange)=\"onSelectionChanged()\"\n >\n <mat-option value=\"none\">{{ tx('No selection') }}</mat-option>\n <mat-option value=\"single\">{{ tx('Single') }}</mat-option>\n <mat-option value=\"multiple\">{{ tx('Multiple') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Form name') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.formControlName\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"formControlName\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Form path') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.formControlPath\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"formControlPath\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Compare by (field)') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.compareBy\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Unique item key.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Return') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.selection.return\"\n (ngModelChange)=\"onSelectionChanged()\"\n >\n <mat-option value=\"value\">{{ tx('Value') }}</mat-option>\n <mat-option value=\"item\">{{ tx('Item') }}</mat-option>\n <mat-option value=\"id\">{{ tx('ID') }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Appearance')\">\n <ng-template matTabContent>\n <div class=\"editor-content grid gap-3\">\n <div class=\"preset-row g row-flow gap-8\">\n <button mat-button (click)=\"applySkinPreset('pill-soft')\">\n {{ tx('Pill Soft') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('gradient-tile')\">\n {{ tx('Gradient Tile') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('glass')\">\n {{ tx('Glass') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('elevated')\">\n {{ tx('Elevated') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('outline')\">\n {{ tx('Outline') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('flat')\">\n {{ tx('Flat') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('neumorphism')\">\n {{ tx('Neumorphism') }}\n </button>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Style') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.skin.type\"\n (ngModelChange)=\"onSkinTypeChanged($event)\"\n >\n <mat-option value=\"pill-soft\">{{ tx('Pill Soft') }}</mat-option>\n <mat-option value=\"gradient-tile\">{{ tx('Gradient Tile') }}</mat-option>\n <mat-option value=\"glass\">{{ tx('Glass') }}</mat-option>\n <mat-option value=\"elevated\">{{ tx('Elevated') }}</mat-option>\n <mat-option value=\"outline\">{{ tx('Outline') }}</mat-option>\n <mat-option value=\"flat\">{{ tx('Flat') }}</mat-option>\n <mat-option value=\"neumorphism\">{{ tx('Neumorphism') }}</mat-option>\n <mat-option value=\"custom\">{{ tx('Custom') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Radius') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.radius\"\n (ngModelChange)=\"onSkinChanged()\"\n [placeholder]=\"tx('e.g.: 1.25rem')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Shadow') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.shadow\"\n (ngModelChange)=\"onSkinChanged()\"\n [placeholder]=\"tx('e.g.: var(--md-sys-elevation-level2)')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Border') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.border\"\n (ngModelChange)=\"onSkinChanged()\"\n />\n </mat-form-field>\n <mat-form-field\n *ngIf=\"working.skin.type === 'glass'\"\n appearance=\"outline\"\n >\n <mat-label>{{ tx('Blur') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.backdropBlur\"\n (ngModelChange)=\"onSkinChanged()\"\n [placeholder]=\"tx('e.g.: 8px')\"\n />\n </mat-form-field>\n <div *ngIf=\"working.skin.type === 'gradient-tile'\" class=\"g gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Gradient from') }}</mat-label>\n <input\n matInput\n [ngModel]=\"working.skin.gradient.from || ''\"\n (ngModelChange)=\"onSkinGradientChanged('from', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Gradient to') }}</mat-label>\n <input\n matInput\n [ngModel]=\"working.skin.gradient.to || ''\"\n (ngModelChange)=\"onSkinGradientChanged('to', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Angle') }}</mat-label>\n <input\n matInput\n type=\"number\"\n [ngModel]=\"working.skin.gradient.angle ?? 135\"\n (ngModelChange)=\"onSkinGradientChanged('angle', $event)\"\n />\n </mat-form-field>\n </div>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Extra CSS class (skin.class)') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.class\"\n (ngModelChange)=\"onSkinChanged()\"\n [placeholder]=\"tx('e.g.: my-list-skin')\"\n />\n </mat-form-field>\n\n <div\n *ngIf=\"working.skin.type === 'custom'\"\n class=\"g g-auto-220 gap-12 ai-end\"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>{{ tx('Inline style (skin.inlineStyle)') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [(ngModel)]=\"working.skin.inlineStyle\"\n (ngModelChange)=\"onSkinChanged()\"\n [attr.placeholder]=\"':host{--p-list-radius: 1rem}'\"\n ></textarea>\n </mat-form-field>\n <div class=\"text-caption\">\n {{ tx('CSS class example (add this to your global styles):') }}\n <pre class=\"code-block\">\n.my-list-skin .item-card {\n border-radius: 14px;\n border: 1px solid var(--md-sys-color-outline-variant);\n box-shadow: var(--md-sys-elevation-level2);\n}\n.my-list-skin .mat-mdc-list-item .list-item-content {\n backdrop-filter: blur(6px);\n}</pre\n >\n </div>\n </div>\n </div>\n </ng-template>\n </mat-tab>\n</mat-tab-group>\n", styles: [".confirm-type{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;line-height:16px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant)}.confirm-type.danger{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.confirm-type.warning{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.confirm-type.info{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}:host{display:block;color:var(--md-sys-color-on-surface)}.list-editor-tabs{--editor-surface: var(--md-sys-color-surface-container-lowest);--editor-border: 1px solid var(--md-sys-color-outline-variant);--editor-radius: var(--md-sys-shape-corner-large, 16px);--editor-muted: var(--md-sys-color-on-surface-variant);--editor-accent: var(--md-sys-color-primary)}.editor-content{padding:16px;background:var(--editor-surface);border:var(--editor-border);border-radius:var(--editor-radius);display:grid;gap:12px}.editor-content .mat-mdc-form-field{width:100%;max-width:none;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var( --md-sys-color-outline-variant );--mdc-outlined-text-field-hover-outline-color: var( --md-sys-color-outline );--mdc-outlined-text-field-focus-outline-color: var( --md-sys-color-primary );--mdc-outlined-text-field-error-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-error-focus-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-error-hover-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-label-text-color: var( --md-sys-color-on-surface-variant );--mdc-outlined-text-field-input-text-color: var( --md-sys-color-on-surface );--mdc-outlined-text-field-supporting-text-color: var( --md-sys-color-on-surface-variant )}.editor-content .mat-mdc-form-field.w-full{max-width:none}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}.editor-split{grid-template-columns:minmax(0,1fr);align-items:start}.editor-main,.editor-aside{display:grid;gap:12px}.skin-preview-wrap{border-radius:calc(var(--editor-radius) - 4px);border:var(--editor-border);background:var(--md-sys-color-surface-container);padding:12px}.g{display:grid}.g-auto-220{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.g-auto-200{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}.g-1-auto{grid-template-columns:1fr auto}.row-flow{grid-auto-flow:column}.gap-6{gap:6px}.gap-8{gap:8px}.gap-12{gap:12px}.ai-center{align-items:center}.ai-end{align-items:end}.mt-12{margin-top:12px}.mb-8{margin-bottom:8px}.mb-6{margin-bottom:6px}.my-8{margin:8px 0}.subtitle{margin:8px 0 4px;color:var(--editor-muted);font-weight:500}.section-title{color:var(--editor-muted);font-weight:600}.chips-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.error{color:var(--md-sys-color-error);font-size:.85rem}.muted{color:var(--editor-muted)}.text-caption{color:var(--editor-muted);font-size:.8rem}:host ::ng-deep .mat-mdc-select-panel .option-icon{font-size:18px;margin-right:6px;vertical-align:middle}:host ::ng-deep .mat-mdc-select-panel .color-dot{width:10px;height:10px;border-radius:999px;display:inline-block;margin-right:6px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-outline)}:host ::ng-deep .mat-mdc-select-panel .color-primary{background:var(--md-sys-color-primary)}:host ::ng-deep .mat-mdc-select-panel .color-accent{background:var(--md-sys-color-tertiary)}:host ::ng-deep .mat-mdc-select-panel .color-warn{background:var(--md-sys-color-error)}:host ::ng-deep .mat-mdc-select-panel .color-default{background:var(--md-sys-color-outline)}@media(max-width:1024px){.editor-split{grid-template-columns:minmax(0,1fr)}}\n"] }]
|
|
6093
|
+
], template: "<mat-tab-group class=\"list-editor-tabs\">\n <mat-tab [label]=\"tx('Data')\">\n <ng-template matTabContent>\n <div class=\"editor-content\">\n <div class=\"g g-1-auto gap-8 ai-center\">\n <div class=\"muted\">\n {{ tx('Assistant-applied adjustments replace the entire configuration object.') }}\n </div>\n <button\n mat-icon-button\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('applyConfigFromAdapter does not perform a deep merge. Make sure the adapter sends the full config.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </div>\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>{{ tx('Resource (API)') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.dataSource.resourcePath\"\n (ngModelChange)=\"onResourcePathChange($event)\"\n [placeholder]=\"tx('e.g.: users')\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Resource endpoint (resourcePath).')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>{{ tx('Query (JSON)') }}</mat-label>\n <textarea\n matInput\n rows=\"3\"\n [(ngModel)]=\"queryJson\"\n (ngModelChange)=\"onQueryChanged($event)\"\n [placeholder]=\"queryPlaceholder()\"\n ></textarea>\n @if (!queryError) {\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Optional. Use valid JSON for initial filters.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n }\n @if (queryError) {\n <mat-error>{{ queryError }}</mat-error>\n }\n </mat-form-field>\n <div class=\"g g-auto-220 gap-12 ai-end mt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Sort by') }}</mat-label>\n <mat-select\n [(ngModel)]=\"sortField\"\n (ngModelChange)=\"updateSortConfig()\"\n >\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Resource base field.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Direction') }}</mat-label>\n <mat-select\n [(ngModel)]=\"sortDir\"\n (ngModelChange)=\"updateSortConfig()\"\n >\n <mat-option value=\"asc\">{{ tx('Ascending') }}</mat-option>\n <mat-option value=\"desc\">{{ tx('Descending') }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('JSON')\">\n <ng-template matTabContent>\n <div class=\"editor-content\">\n <praxis-list-json-config-editor\n [document]=\"document\"\n (documentChange)=\"onJsonConfigChange($event)\"\n (validationChange)=\"onJsonValidationChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n >\n </praxis-list-json-config-editor>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Actions')\">\n <ng-template matTabContent>\n <div class=\"editor-content g gap-12\">\n <div class=\"g g-1-auto gap-8 ai-center\">\n <div class=\"muted\">\n {{ tx('Configure per-item action buttons (icon, label, color, visibility)') }}\n </div>\n <button mat-flat-button color=\"primary\" (click)=\"addAction()\">\n {{ tx('Add action') }}\n </button>\n </div>\n <div class=\"g g-1-auto gap-8 ai-center\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Global action (Praxis)') }}</mat-label>\n <mat-select\n [(ngModel)]=\"selectedGlobalActionId\"\n (ngModelChange)=\"onGlobalActionSelected($event)\"\n >\n <mat-option [value]=\"undefined\">{{ tx('-- Select --') }}</mat-option>\n @for (ga of globalActionCatalog; track ga) {\n <mat-option [value]=\"ga.id\">\n <mat-icon class=\"option-icon\">{{ ga.icon || \"bolt\" }}</mat-icon>\n {{ ga.label }}\n </mat-option>\n }\n </mat-select>\n @if (!globalActionCatalog.length) {\n <mat-hint\n class=\"text-caption muted\"\n >{{ tx('No global action registered.') }}</mat-hint\n >\n }\n </mat-form-field>\n <div class=\"muted text-caption\">\n {{ tx('Select to add with a structured global action.') }}\n </div>\n </div>\n @for (a of working.actions || []; track a; let i = $index) {\n <div\n class=\"g g-auto-200 gap-12 ai-end\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('ID') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.id\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Action type') }}</mat-label>\n <mat-select [(ngModel)]=\"a.kind\" (ngModelChange)=\"onActionsChanged()\">\n <mat-option value=\"icon\">{{ tx('Icon') }}</mat-option>\n <mat-option value=\"button\">{{ tx('Button') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Icon') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.icon\"\n (ngModelChange)=\"onActionsChanged()\"\n [placeholder]=\"tx('e.g.: edit, delete')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Global action') }}</mat-label>\n <mat-select\n [ngModel]=\"getGlobalActionId(a)\"\n (ngModelChange)=\"onActionGlobalActionIdChange(a, $event)\"\n >\n <mat-option value=\"\">{{ tx('None') }}</mat-option>\n @for (action of globalActionCatalog; track action) {\n <mat-option [value]=\"action.id\">\n {{ action.label || action.id }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Label') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.label\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n </mat-form-field>\n @if (a.kind === 'button') {\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Variant') }}</mat-label>\n <mat-select\n [(ngModel)]=\"a.buttonVariant\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option value=\"stroked\">{{ tx('Outlined') }}</mat-option>\n <mat-option value=\"raised\">{{ tx('Raised') }}</mat-option>\n <mat-option value=\"flat\">{{ tx('Filled') }}</mat-option>\n </mat-select>\n </mat-form-field>\n }\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Action color') }}</mat-label>\n <mat-select\n [(ngModel)]=\"a.color\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n @for (c of paletteOptions; track c) {\n <mat-option [value]=\"c.value\">\n <span\n class=\"color-dot\"\n [style.background]=\"colorDotBackground(c.value)\"\n ></span\n >{{ tx(c.label) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (isCustomColor(a.color)) {\n <div\n class=\"g gap-8\"\n >\n <pdx-color-picker\n [label]=\"tx('Custom color')\"\n [format]=\"'hex'\"\n [(ngModel)]=\"a.color\"\n (ngModelChange)=\"onActionsChanged()\"\n ></pdx-color-picker>\n </div>\n } @else {\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"enableCustomActionColor(a)\"\n >\n {{ tx('Use custom color') }}\n </button>\n }\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Action payload') }}</mat-label>\n <mat-select\n [(ngModel)]=\"a.emitPayload\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option [value]=\"undefined\">{{ tx('Default') }}</mat-option>\n <mat-option value=\"item\">{{ tx('Item') }}</mat-option>\n <mat-option value=\"id\">{{ tx('ID') }}</mat-option>\n <mat-option value=\"value\">{{ tx('Value') }}</mat-option>\n </mat-select>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Payload emitted by the action.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"col-span-2\">\n <mat-label>{{ actionVisibilityLabel() }}</mat-label>\n <textarea\n matInput\n rows=\"3\"\n [ngModel]=\"actionShowIfModel(i)\"\n (ngModelChange)=\"onActionShowIfChanged(i, a, $event)\"\n ></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"actionConditionTooltip()\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"g row-flow gap-8 ai-center\">\n @if ((a.kind || 'icon') === 'icon') {\n <button\n mat-icon-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n >\n <mat-icon\n [praxisIcon]=\"a.icon || 'bolt'\"\n [style.cssText]=\"iconStyle(a.color)\"\n ></mat-icon>\n </button>\n }\n @if (a.kind === 'button') {\n @if (a.buttonVariant === 'stroked') {\n <button\n mat-stroked-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'stroked')\"\n >\n {{ a.label || a.id || tx('Action') }}\n </button>\n }\n @if (a.buttonVariant === 'raised') {\n <button\n mat-raised-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'raised')\"\n >\n {{ a.label || a.id || tx('Action') }}\n </button>\n }\n @if (!a.buttonVariant || a.buttonVariant === 'flat') {\n <button\n mat-flat-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'flat')\"\n >\n {{ a.label || a.id || tx('Action') }}\n </button>\n }\n }\n <span class=\"muted\">{{ tx('Preview') }}</span>\n </div>\n <div class=\"flex-end\">\n <button mat-button color=\"warn\" (click)=\"removeAction(i)\">\n {{ tx('Remove') }}\n </button>\n </div>\n @if (a.globalAction?.actionId) {\n <div class=\"g gap-8 col-span-2\">\n <mat-slide-toggle\n [(ngModel)]=\"a.showLoading\"\n (ngModelChange)=\"onActionsChanged()\"\n >{{ tx('Show loading') }}</mat-slide-toggle\n >\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >{{ tx('Confirmation') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <div class=\"g row-flow gap-8 ai-center\">\n <span class=\"text-caption muted\">{{ tx('Type') }}</span>\n <mat-button-toggle-group\n [value]=\"a.confirmation?.type || ''\"\n (change)=\"applyConfirmationPreset(a, $event.value)\"\n >\n <mat-button-toggle value=\"\">{{ tx('Default') }}</mat-button-toggle>\n <mat-button-toggle value=\"danger\">{{ tx('Danger') }}</mat-button-toggle>\n <mat-button-toggle value=\"warning\">{{ tx('Warning') }}</mat-button-toggle>\n <mat-button-toggle value=\"info\">{{ tx('Info') }}</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Title') }}</mat-label>\n <input\n matInput\n [ngModel]=\"a.confirmation?.title\"\n (ngModelChange)=\"setConfirmationField(a, 'title', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Message') }}</mat-label>\n <input\n matInput\n [ngModel]=\"a.confirmation?.message\"\n (ngModelChange)=\"setConfirmationField(a, 'message', $event)\"\n />\n </mat-form-field>\n <div class=\"g gap-6\">\n <div class=\"text-caption muted\">{{ tx('Preview') }}</div>\n <div class=\"text-caption\">\n <strong>{{\n a.confirmation?.title || tx('Confirm action')\n }}</strong>\n </div>\n <div class=\"text-caption muted\">\n {{\n a.confirmation?.message ||\n tx('Are you sure you want to continue?')\n }}\n </div>\n <div class=\"text-caption\">\n <span\n class=\"confirm-type\"\n [ngClass]=\"a.confirmation?.type || 'default'\"\n >{{ tx('Type') }}:\n {{ a.confirmation?.type || tx('Default').toLowerCase() }}</span\n >\n </div>\n @if (!a.confirmation?.title && !a.confirmation?.message) {\n <div\n class=\"text-caption muted\"\n >\n {{ tx('Set a title or message for the confirmation.') }}\n </div>\n }\n </div>\n </div>\n </mat-expansion-panel>\n @if (isSurfaceOpenCommand(a)) {\n <div class=\"col-span-2\">\n <praxis-surface-open-action-editor\n [value]=\"getSurfaceOpenGlobalActionPayload(a)\"\n hostKind=\"list\"\n (valueChange)=\"onSurfaceOpenGlobalActionPayloadChange(a, $event)\"\n ></praxis-surface-open-action-editor>\n </div>\n } @else {\n <mat-form-field appearance=\"outline\" class=\"col-span-2\">\n <mat-label>{{ tx('Payload (JSON/Template)') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"getGlobalActionPayloadText(a)\"\n (ngModelChange)=\"onGlobalActionPayloadTextChange(a, $event)\"\n placeholder='{\"message\":\"${item.name} favoritado\"}'\n ></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"globalActionPayloadSchemaTooltip(a)\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n @if (isGlobalActionPayloadInvalid(a)) {\n <mat-error\n >{{ tx('Invalid JSON') }}</mat-error\n >\n }\n </mat-form-field>\n <div class=\"g row-flow gap-8 ai-center\">\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"applyGlobalActionPayloadExample(a)\"\n >\n {{ tx('Insert example') }}\n </button>\n <span class=\"muted text-caption\">{{\n globalActionPayloadExampleHint(a)\n }}</span>\n </div>\n }\n <mat-slide-toggle\n [(ngModel)]=\"a.emitLocal\"\n (ngModelChange)=\"onActionsChanged()\"\n >{{ tx('Emit local event too') }}</mat-slide-toggle\n >\n </div>\n }\n </div>\n }\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Layout')\">\n <ng-template matTabContent>\n <div class=\"editor-content grid gap-3\">\n <div class=\"preset-row g row-flow gap-8\">\n <button mat-stroked-button (click)=\"applyLayoutPreset('tiles-modern')\">\n {{ tx('Modern tiles preset') }}\n </button>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Variant') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.variant\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"list\">{{ tx('List') }}</mat-option>\n <mat-option value=\"cards\">{{ tx('Cards') }}</mat-option>\n <mat-option value=\"tiles\">{{ tx('Tiles') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Model') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.model\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n @if (working.layout.variant === 'list') {\n <mat-option value=\"standard\">{{ tx('Standard') }}</mat-option>\n <mat-option value=\"media\">{{ tx('Media on the left') }}</mat-option>\n <mat-option value=\"hotel\">{{ tx('Hotel (large media)') }}</mat-option>\n } @else {\n @if (working.layout.variant === 'tiles') {\n <mat-option value=\"standard\">{{ tx('Standard tile') }}</mat-option>\n <mat-option value=\"media\">{{ tx('Tile with media') }}</mat-option>\n <mat-option value=\"hotel\">{{ tx('Hotel tile') }}</mat-option>\n } @else {\n <mat-option value=\"standard\">{{ tx('Standard') }}</mat-option>\n <mat-option value=\"media\">{{ tx('Card with media') }}</mat-option>\n <mat-option value=\"hotel\">{{ tx('Hotel') }}</mat-option>\n }\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Lines') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.lines\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option [value]=\"1\">1</mat-option>\n <mat-option [value]=\"2\">2</mat-option>\n <mat-option [value]=\"3\">3</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Items per page') }}</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"1\"\n [(ngModel)]=\"working.layout.pageSize\"\n (ngModelChange)=\"onPageSizeChange($event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Density') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.density\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"default\">{{ tx('Default') }}</mat-option>\n <mat-option value=\"comfortable\">{{ tx('Comfortable') }}</mat-option>\n <mat-option value=\"compact\">{{ tx('Compact') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Spacing between items') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.itemSpacing\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"none\">{{ tx('No extra space') }}</mat-option>\n <mat-option value=\"tight\">{{ tx('Tight') }}</mat-option>\n <mat-option value=\"default\">{{ tx('Standard') }}</mat-option>\n <mat-option value=\"relaxed\">{{ tx('Relaxed') }}</mat-option>\n </mat-select>\n </mat-form-field>\n @if (working.layout.variant !== 'tiles') {\n <mat-form-field\n appearance=\"outline\"\n >\n <mat-label>{{ tx('Dividers') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.dividers\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"none\">{{ tx('None') }}</mat-option>\n <mat-option value=\"between\">{{ tx('Between groups') }}</mat-option>\n <mat-option value=\"all\">{{ tx('All') }}</mat-option>\n </mat-select>\n </mat-form-field>\n }\n @if (fields.length > 0) {\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Group by') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.groupBy\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option [value]=\"\">{{ tx('None') }}</mat-option>\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Group by') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.layout.groupBy\"\n (ngModelChange)=\"onLayoutChanged()\"\n [placeholder]=\"tx('e.g.: department')\"\n />\n </mat-form-field>\n }\n <mat-slide-toggle\n [(ngModel)]=\"working.layout.stickySectionHeader\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n {{ tx('Sticky section header') }}\n </mat-slide-toggle>\n <mat-slide-toggle\n [(ngModel)]=\"working.layout.virtualScroll\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n {{ tx('Virtual scroll') }}\n </mat-slide-toggle>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">{{ tx('List tools') }}</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showSearch\"\n (ngModelChange)=\"onUiChanged()\"\n >{{ tx('Show search') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showSort\"\n (ngModelChange)=\"onUiChanged()\"\n >{{ tx('Show sorting') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showRange\"\n (ngModelChange)=\"onUiChanged()\"\n >{{ tx('Show total X-Y range') }}</mat-slide-toggle\n >\n </div>\n @if (working.ui?.showSearch) {\n <div\n class=\"g g-auto-220 gap-12 ai-end mt-12\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field to search') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.ui.searchField\"\n (ngModelChange)=\"onUiChanged()\"\n >\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Search placeholder') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.ui.searchPlaceholder\"\n (ngModelChange)=\"onUiChanged()\"\n [placeholder]=\"tx('e.g.: Search by title')\"\n />\n </mat-form-field>\n </div>\n }\n @if (working.ui?.showSort) {\n <div class=\"mt-12\">\n <div class=\"g g-1-auto ai-center gap-8\">\n <div class=\"muted\">\n {{ tx('Sorting options (label \u2192 field+direction)') }}\n </div>\n <button mat-flat-button color=\"primary\" (click)=\"addUiSortRow()\">\n {{ tx('Add option') }}\n </button>\n </div>\n @for (r of uiSortRows; track r; let i = $index) {\n <div\n class=\"g g-auto-220 gap-12 ai-end mt-12\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Label') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"r.label\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n [placeholder]=\"tx('e.g.: Most recent')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"r.field\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n >\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Direction') }}</mat-label>\n <mat-select\n [(ngModel)]=\"r.dir\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n >\n <mat-option value=\"desc\">{{ tx('Descending') }}</mat-option>\n <mat-option value=\"asc\">{{ tx('Ascending') }}</mat-option>\n </mat-select>\n </mat-form-field>\n @if (isUiSortRowDuplicate(i)) {\n <div class=\"error\">\n {{ tx('Duplicate option (field+direction)') }}\n </div>\n }\n <div class=\"flex-end\">\n <button mat-button color=\"warn\" (click)=\"removeUiSortRow(i)\">\n {{ tx('Remove') }}\n </button>\n </div>\n </div>\n }\n </div>\n }\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Content')\">\n <ng-template matTabContent>\n <div class=\"editor-content\">\n <div class=\"editor-main\">\n <mat-accordion multi>\n <!-- Primary -->\n <mat-expansion-panel [expanded]=\"true\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingPrimary.type) }}</mat-icon>\n <span>{{ tx('Primary (Title)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingPrimary.field || tx('Not mapped')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'name';\n onMappingChanged()\n \"\n >\n {{ tx('Name') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'title';\n onMappingChanged()\n \"\n >\n {{ tx('Title') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'name';\n mappingSecondary.type = 'text';\n mappingSecondary.field = 'role';\n onMappingChanged()\n \"\n >\n {{ tx('Name + role') }}\n </button>\n </div>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingPrimary.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingPrimary.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (mt of primaryTypeConfigs; track mt) {\n <mat-option\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n @switch (mappingPrimary.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingPrimary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingPrimary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingPrimary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingPrimary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n }\n\n <!-- Advanced -->\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header>\n <mat-panel-title>{{ tx('Formatting and style') }}</mat-panel-title>\n </mat-expansion-panel-header>\n <div class=\"g gap-12 pt-12\">\n @if (\n mappingPrimary.type === 'text' ||\n mappingPrimary.type === 'html'\n ) {\n <mat-form-field\n appearance=\"outline\"\n >\n <mat-label>{{ tx('CSS class') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n }\n @if (\n mappingPrimary.type === 'text' ||\n mappingPrimary.type === 'html'\n ) {\n <mat-form-field\n appearance=\"outline\"\n >\n <mat-label>{{ tx('Inline style') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n }\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('CSS class') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Inline style') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Secondary -->\n <mat-expansion-panel [expanded]=\"!!mappingSecondary.field\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingSecondary.type) }}</mat-icon>\n <span>{{ tx('Secondary (Summary)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingSecondary.field || tx('Not mapped')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'text';\n mappingSecondary.field = 'subtitle';\n onMappingChanged()\n \"\n >\n {{ tx('Subtitle') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'date';\n mappingSecondary.field = 'hireDate';\n mappingSecondary.dateStyle = 'short';\n onMappingChanged()\n \"\n >\n {{ tx('Short date') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"applySalaryPreset()\"\n >\n {{ tx('Salary') }}\n </button>\n </div>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSecondary.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSecondary.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (mt of secondaryTypeConfigs; track mt) {\n <mat-option\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n @switch (mappingSecondary.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingSecondary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingSecondary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingSecondary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingSecondary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header>\n <mat-panel-title>{{ tx('Formatting and style') }}</mat-panel-title>\n </mat-expansion-panel-header>\n <div class=\"g gap-12 pt-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('CSS class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSecondary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Inline style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSecondary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <mat-expansion-panel\n [expanded]=\"!!mappingMeta.field || mappingMetaFields.length > 0\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingMeta.type || \"text\")\n }}</mat-icon>\n <span>{{ tx('Meta (Detail/Side)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>\n {{\n mappingMetaFields.length\n ? tx('Composed field ({{count}})', { count: mappingMetaFields.length })\n : mappingMeta.field || tx('Not mapped')\n }}\n </mat-panel-description>\n </mat-expansion-panel-header>\n\n <div class=\"g gap-12\">\n <!-- Composition Mode Toggle -->\n <div class=\"g g-1-1 gap-12 p-12 bg-subtle rounded\">\n <div class=\"text-caption muted\">{{ tx('Composition mode') }}</div>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Fields to compose (multi-select)') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMetaFields\"\n multiple\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (mappingMetaFields.length) {\n <div\n class=\"g g-1-1 ai-center gap-12\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Separator') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMetaSeparator\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-slide-toggle\n [(ngModel)]=\"mappingMetaWrapSecondInParens\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n {{ tx('(Second) in parentheses') }}\n </mat-slide-toggle>\n </div>\n }\n </div>\n\n <!-- Single Field Mode (if no composition) -->\n @if (!mappingMetaFields.length) {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Single field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option [value]=\"undefined\">{{ tx('-- None --') }}</mat-option>\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (mt of metaTypeConfigs; track mt) {\n <mat-option\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n\n <!-- Type configuration (pluggable editors) -->\n @switch (mappingMeta.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingMeta\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingMeta\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <praxis-meta-editor-image\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <!-- Advanced -->\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Advanced options') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Position') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.placement\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option value=\"side\">{{ tx('Side (right)') }}</mat-option>\n <mat-option value=\"line\">{{ tx('Inline (below)') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('CSS class') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMeta.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Style') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMeta.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n <!-- Trailing -->\n <mat-expansion-panel [expanded]=\"!!mappingTrailing.field\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingTrailing.type || \"text\")\n }}</mat-icon>\n <span>{{ tx('Trailing (right)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingTrailing.field || tx('Not mapped')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingTrailing.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option [value]=\"undefined\">{{ tx('-- None --') }}</mat-option>\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingTrailing.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (mt of trailingTypeConfigs; track mt) {\n <mat-option\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'chip';\n mappingTrailing.chipColor = 'primary';\n mappingTrailing.chipVariant = 'filled';\n mappingTrailing.field = 'status';\n onMappingChanged()\n \"\n >\n {{ tx('Status chip') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'icon';\n mappingTrailing.field = 'status';\n mappingTrailing.iconColor = 'primary';\n onMappingChanged()\n \"\n >\n {{ tx('Status icon') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"applyPricePreset()\"\n >\n {{ tx('Price') }}\n </button>\n </div>\n\n @switch (mappingTrailing.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingTrailing\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingTrailing\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('URL / Expr') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingTrailing.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n [placeholder]=\"tx('https://... or ${item.imageUrl}')\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Use an absolute/relative URL or a ${item.field} expression.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n @if (\n isImageUrlRequiredInvalid(mappingTrailing.imageUrl)\n ) {\n <mat-error\n >{{ tx('URL/expr is required') }}</mat-error\n >\n }\n </mat-form-field>\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n @if (!mappingTrailing.imageUrl) {\n <div\n class=\"text-caption muted\"\n >\n {{ tx('Set the URL/expr to render the image.') }}\n </div>\n }\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Style') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingTrailing.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingTrailing.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Leading -->\n <mat-expansion-panel\n [expanded]=\"\n !!mappingLeading.field ||\n (mappingLeading.type === 'icon' && !!mappingLeading.icon) ||\n (mappingLeading.type === 'image' && !!mappingLeading.imageUrl)\n \"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingLeading.type) }}</mat-icon>\n <span>{{ tx('Leading (left)') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>\n {{\n mappingLeading.type === \"icon\"\n ? mappingLeading.icon || tx('Static icon')\n : mappingLeading.field ||\n (mappingLeading.imageUrl\n ? tx('Static image')\n : tx('Not mapped'))\n }}\n </mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingLeading.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (mt of leadingTypeConfigs; track mt) {\n <mat-option\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <!-- Field (only if not static icon/image, though user might want dynamic) -->\n @if (\n mappingLeading.type !== 'icon' &&\n mappingLeading.type !== 'image'\n ) {\n <mat-form-field\n appearance=\"outline\"\n >\n <mat-label>{{ tx('Field') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingLeading.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (f of fields; track f) {\n <mat-option [value]=\"f\">{{\n f\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'icon';\n mappingLeading.icon = 'person';\n mappingLeading.iconColor = 'primary';\n onMappingChanged()\n \"\n >\n {{ tx('Avatar icon') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'image';\n mappingLeading.imageUrl = 'https://placehold.co/64x64';\n mappingLeading.imageAlt = 'Avatar';\n mappingLeading.badgeText = '${item.status}';\n onMappingChanged()\n \"\n >\n {{ tx('Avatar image + badge') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'chip';\n mappingLeading.field = 'tag';\n mappingLeading.chipColor = 'accent';\n mappingLeading.chipVariant = 'filled';\n onMappingChanged()\n \"\n >\n {{ tx('Tag chip') }}\n </button>\n </div>\n\n <!-- Icon Specific -->\n @if (mappingLeading.type === 'icon') {\n <div\n class=\"g g-1-auto gap-12 ai-center\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Icon') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.icon\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <button mat-icon-button matSuffix (click)=\"pickLeadingIcon()\">\n <mat-icon>search</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"text-caption muted\">\n {{ tx('Use the `|iconMap` pipe in the extra pipe for dynamic rendering.') }}\n </div>\n </div>\n }\n @if (mappingLeading.type === 'icon') {\n <div>\n <praxis-meta-editor-icon\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n </div>\n }\n\n <!-- Image Specific -->\n @if (mappingLeading.type === 'image') {\n <div\n class=\"g g-1-1 gap-12\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Image URL') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n [placeholder]=\"tx('https://... or ${item.imageUrl}')\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Use an absolute/relative URL or a ${item.field} expression.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n @if (isImageUrlRequiredInvalid(mappingLeading.imageUrl)) {\n <mat-error\n >{{ tx('URL/expr is required') }}</mat-error\n >\n }\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Alt text') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.imageAlt\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Badge text') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.badgeText\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n }\n\n @switch (mappingLeading.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingLeading\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingLeading\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Style') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingLeading.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingLeading.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Features -->\n <mat-expansion-panel\n [expanded]=\"featuresVisible && features.length > 0\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>view_list</mat-icon>\n <span>{{ tx('Features') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description\n >{{ features.length }} item(s)</mat-panel-description\n >\n </mat-expansion-panel-header>\n\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-12 ai-center\">\n <mat-slide-toggle\n [(ngModel)]=\"featuresVisible\"\n (ngModelChange)=\"onFeaturesChanged()\"\n >{{ tx('Enable features') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"featuresSyncWithMeta\"\n (ngModelChange)=\"onMappingChanged()\"\n >{{ tx('Sync with Meta') }}</mat-slide-toggle\n >\n <span class=\"flex-1\"></span>\n <mat-button-toggle-group\n [(ngModel)]=\"featuresMode\"\n (change)=\"onFeaturesChanged()\"\n appearance=\"legacy\"\n >\n <mat-button-toggle value=\"icons+labels\"\n ><mat-icon>view_list</mat-icon></mat-button-toggle\n >\n <mat-button-toggle value=\"icons-only\"\n ><mat-icon>more_horiz</mat-icon></mat-button-toggle\n >\n </mat-button-toggle-group>\n </div>\n\n @for (f of features; track f; let i = $index) {\n <div\n class=\"g g-auto-1 gap-8 ai-center p-8 border rounded mb-2\"\n >\n <button mat-icon-button (click)=\"pickFeatureIcon(i)\">\n <mat-icon>{{ f.icon || \"search\" }}</mat-icon>\n </button>\n <mat-form-field\n appearance=\"outline\"\n class=\"dense-form-field no-sub\"\n >\n <input\n matInput\n [(ngModel)]=\"f.expr\"\n (ngModelChange)=\"onFeaturesChanged()\"\n [placeholder]=\"tx('Expression/Text')\"\n />\n </mat-form-field>\n <button mat-icon-button color=\"warn\" (click)=\"removeFeature(i)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n <button mat-button color=\"primary\" (click)=\"addFeature()\">\n <mat-icon>add</mat-icon> {{ tx('Add feature') }}\n </button>\n </div>\n </mat-expansion-panel>\n <!-- Section Header -->\n <mat-expansion-panel [expanded]=\"!!mappingSectionHeader.expr\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingSectionHeader.type)\n }}</mat-icon>\n <span>{{ tx('Section header') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingSectionHeader.expr || tx('Not configured')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSectionHeader.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (mt of sectionHeaderTypeConfigs; track mt) {\n <mat-option\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Expression (item.key)') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingSectionHeader.expr\"\n (ngModelChange)=\"onMappingChanged()\"\n placeholder=\"item.key\"\n />\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSectionHeader.type = 'text';\n mappingSectionHeader.expr = '${item.key}';\n onMappingChanged()\n \"\n >\n {{ tx('Default text') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSectionHeader.type = 'chip';\n mappingSectionHeader.chipColor = 'primary';\n mappingSectionHeader.chipVariant = 'filled';\n mappingSectionHeader.expr = '${item.key}';\n onMappingChanged()\n \"\n >\n {{ tx('Default chip') }}\n </button>\n </div>\n\n @switch (mappingSectionHeader.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingSectionHeader\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingSectionHeader\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Image URL') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingSectionHeader.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n @if (\n isImageUrlRequiredInvalid(\n mappingSectionHeader.imageUrl\n )\n ) {\n <mat-error\n >{{ tx('URL/expr is required') }}</mat-error\n >\n }\n </mat-form-field>\n </div>\n @if (!mappingSectionHeader.imageUrl) {\n <div\n class=\"text-caption muted\"\n >\n {{ tx('Set the URL/expr to render the image.') }}\n </div>\n }\n <praxis-meta-editor-image\n [model]=\"mappingSectionHeader\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Style') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSectionHeader.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSectionHeader.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Empty State -->\n <mat-expansion-panel [expanded]=\"!!mappingEmptyState.expr\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>inbox</mat-icon>\n <span>{{ tx('Empty state') }}</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingEmptyState.expr || tx('Default')\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Type') }}</mat-label>\n <mat-select\n [(ngModel)]=\"mappingEmptyState.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n @for (mt of emptyStateTypeConfigs; track mt) {\n <mat-option\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ tx(mt.label) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Message / Expr') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingEmptyState.expr\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">{{ tx('Presets') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingEmptyState.type = 'text';\n mappingEmptyState.expr = tx('No items available');\n onMappingChanged()\n \"\n >\n {{ tx('Default message') }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingEmptyState.type = 'image';\n mappingEmptyState.imageUrl = '/list-empty-state.svg';\n mappingEmptyState.imageAlt = tx('No results');\n onMappingChanged()\n \"\n >\n {{ tx('Default image') }}\n </button>\n </div>\n\n @switch (mappingEmptyState.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingEmptyState\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingEmptyState\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground.bind(this)\"\n [isCustomColor]=\"isCustomColor.bind(this)\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Image URL') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n @if (\n isImageUrlRequiredInvalid(mappingEmptyState.imageUrl)\n ) {\n <mat-error\n >{{ tx('URL/expr is required') }}</mat-error\n >\n }\n </mat-form-field>\n </div>\n @if (!mappingEmptyState.imageUrl) {\n <div\n class=\"text-caption muted\"\n >\n {{ tx('Set the URL/expr to render the image.') }}\n </div>\n }\n <praxis-meta-editor-image\n [model]=\"mappingEmptyState\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title>{{ tx('Style') }}</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Class') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>{{ tx('Style') }}</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n </mat-accordion>\n\n <button mat-flat-button color=\"primary\" (click)=\"applyTemplate()\">\n {{ tx('Apply mapping') }}\n </button>\n <button\n mat-button\n (click)=\"inferFromFields()\"\n [disabled]=\"!fields.length\"\n >\n {{ tx('Infer from schema') }}\n </button>\n <div class=\"g g-auto-220 gap-12 ai-end mt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Skeleton count') }}</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"0\"\n [(ngModel)]=\"skeletonCountInput\"\n (ngModelChange)=\"onSkeletonChanged($event)\"\n />\n </mat-form-field>\n </div>\n\n <div class=\"g gap-12 mt-12\">\n <div class=\"g row-flow gap-8 ai-center\">\n <span class=\"section-title mat-subtitle-1\">{{ tx('Theme preview') }}</span>\n <mat-button-toggle-group\n [(ngModel)]=\"skinPreviewTheme\"\n (change)=\"onSkinChanged()\"\n appearance=\"legacy\"\n >\n <mat-button-toggle [value]=\"'light'\">{{ tx('Light') }}</mat-button-toggle>\n <mat-button-toggle [value]=\"'dark'\">{{ tx('Dark') }}</mat-button-toggle>\n <mat-button-toggle [value]=\"'grid'\">{{ tx('Grid') }}</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <div class=\"skin-preview-wrap\">\n <praxis-list-skin-preview\n [config]=\"working\"\n [items]=\"previewData\"\n [theme]=\"skinPreviewTheme\"\n ></praxis-list-skin-preview>\n </div>\n </div>\n </div>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('i18n/A11y')\">\n <ng-template matTabContent>\n @if (working?.a11y && working?.events) {\n <div\n class=\"editor-content grid gap-3\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Default locale') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.i18n.locale\"\n (ngModelChange)=\"markDirty()\"\n [placeholder]=\"tx('e.g.: en-US')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Default currency') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.i18n.currency\"\n (ngModelChange)=\"markDirty()\"\n [placeholder]=\"currencyPlaceholder()\"\n />\n </mat-form-field>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">{{ tx('Accessibility') }}</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('aria-label') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.a11y!.ariaLabel\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('aria-labelledby') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.a11y!.ariaLabelledBy\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n </div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-slide-toggle\n [(ngModel)]=\"working!.a11y!.highContrast\"\n (ngModelChange)=\"markDirty()\"\n >{{ tx('High contrast') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working!.a11y!.reduceMotion\"\n (ngModelChange)=\"markDirty()\"\n >{{ tx('Reduce motion') }}</mat-slide-toggle\n >\n </div>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">{{ tx('Events') }}</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('itemClick') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.itemClick\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>actionClick</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.actionClick\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>selectionChange</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.selectionChange\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>loaded</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.loaded\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n </div>\n </div>\n }\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Selection')\">\n <ng-template matTabContent>\n <div class=\"editor-content grid gap-3\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Mode') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.selection.mode\"\n (ngModelChange)=\"onSelectionChanged()\"\n >\n <mat-option value=\"none\">{{ tx('No selection') }}</mat-option>\n <mat-option value=\"single\">{{ tx('Single') }}</mat-option>\n <mat-option value=\"multiple\">{{ tx('Multiple') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Form name') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.formControlName\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"formControlName\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Form path') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.formControlPath\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"formControlPath\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Compare by (field)') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.compareBy\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"tx('Unique item key.')\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Return') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.selection.return\"\n (ngModelChange)=\"onSelectionChanged()\"\n >\n <mat-option value=\"value\">{{ tx('Value') }}</mat-option>\n <mat-option value=\"item\">{{ tx('Item') }}</mat-option>\n <mat-option value=\"id\">{{ tx('ID') }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </ng-template>\n </mat-tab>\n <mat-tab [label]=\"tx('Appearance')\">\n <ng-template matTabContent>\n <div class=\"editor-content grid gap-3\">\n <div class=\"preset-row g row-flow gap-8\">\n <button mat-button (click)=\"applySkinPreset('pill-soft')\">\n {{ tx('Pill Soft') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('gradient-tile')\">\n {{ tx('Gradient Tile') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('glass')\">\n {{ tx('Glass') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('elevated')\">\n {{ tx('Elevated') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('outline')\">\n {{ tx('Outline') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('flat')\">\n {{ tx('Flat') }}\n </button>\n <button mat-button (click)=\"applySkinPreset('neumorphism')\">\n {{ tx('Neumorphism') }}\n </button>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Style') }}</mat-label>\n <mat-select\n [(ngModel)]=\"working.skin.type\"\n (ngModelChange)=\"onSkinTypeChanged($event)\"\n >\n <mat-option value=\"pill-soft\">{{ tx('Pill Soft') }}</mat-option>\n <mat-option value=\"gradient-tile\">{{ tx('Gradient Tile') }}</mat-option>\n <mat-option value=\"glass\">{{ tx('Glass') }}</mat-option>\n <mat-option value=\"elevated\">{{ tx('Elevated') }}</mat-option>\n <mat-option value=\"outline\">{{ tx('Outline') }}</mat-option>\n <mat-option value=\"flat\">{{ tx('Flat') }}</mat-option>\n <mat-option value=\"neumorphism\">{{ tx('Neumorphism') }}</mat-option>\n <mat-option value=\"custom\">{{ tx('Custom') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Radius') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.radius\"\n (ngModelChange)=\"onSkinChanged()\"\n [placeholder]=\"tx('e.g.: 1.25rem')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Shadow') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.shadow\"\n (ngModelChange)=\"onSkinChanged()\"\n [placeholder]=\"tx('e.g.: var(--md-sys-elevation-level2)')\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Border') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.border\"\n (ngModelChange)=\"onSkinChanged()\"\n />\n </mat-form-field>\n @if (working.skin.type === 'glass') {\n <mat-form-field\n appearance=\"outline\"\n >\n <mat-label>{{ tx('Blur') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.backdropBlur\"\n (ngModelChange)=\"onSkinChanged()\"\n [placeholder]=\"tx('e.g.: 8px')\"\n />\n </mat-form-field>\n }\n @if (working.skin.type === 'gradient-tile') {\n <div class=\"g gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Gradient from') }}</mat-label>\n <input\n matInput\n [ngModel]=\"working.skin.gradient.from || ''\"\n (ngModelChange)=\"onSkinGradientChanged('from', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Gradient to') }}</mat-label>\n <input\n matInput\n [ngModel]=\"working.skin.gradient.to || ''\"\n (ngModelChange)=\"onSkinGradientChanged('to', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Angle') }}</mat-label>\n <input\n matInput\n type=\"number\"\n [ngModel]=\"working.skin.gradient.angle ?? 135\"\n (ngModelChange)=\"onSkinGradientChanged('angle', $event)\"\n />\n </mat-form-field>\n </div>\n }\n\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('Extra CSS class (skin.class)') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.class\"\n (ngModelChange)=\"onSkinChanged()\"\n [placeholder]=\"tx('e.g.: my-list-skin')\"\n />\n </mat-form-field>\n\n @if (working.skin.type === 'custom') {\n <div\n class=\"g g-auto-220 gap-12 ai-end\"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>{{ tx('Inline style (skin.inlineStyle)') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [(ngModel)]=\"working.skin.inlineStyle\"\n (ngModelChange)=\"onSkinChanged()\"\n [attr.placeholder]=\"':host{--p-list-radius: 1rem}'\"\n ></textarea>\n </mat-form-field>\n <div class=\"text-caption\">\n {{ tx('CSS class example (add this to your global styles):') }}\n <pre class=\"code-block\">\n .my-list-skin .item-card {\n border-radius: 14px;\n border: 1px solid var(--md-sys-color-outline-variant);\n box-shadow: var(--md-sys-elevation-level2);\n }\n .my-list-skin .mat-mdc-list-item .list-item-content {\n backdrop-filter: blur(6px);\n }</pre\n >\n </div>\n </div>\n }\n </div>\n </ng-template>\n </mat-tab>\n </mat-tab-group>\n", styles: [".confirm-type{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;line-height:16px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant)}.confirm-type.danger{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.confirm-type.warning{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.confirm-type.info{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}:host{display:block;color:var(--md-sys-color-on-surface)}.list-editor-tabs{--editor-surface: var(--md-sys-color-surface-container-lowest);--editor-border: 1px solid var(--md-sys-color-outline-variant);--editor-radius: var(--md-sys-shape-corner-large, 16px);--editor-muted: var(--md-sys-color-on-surface-variant);--editor-accent: var(--md-sys-color-primary)}.editor-content{padding:16px;background:var(--editor-surface);border:var(--editor-border);border-radius:var(--editor-radius);display:grid;gap:12px}.editor-content .mat-mdc-form-field{width:100%;max-width:none;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var( --md-sys-color-outline-variant );--mdc-outlined-text-field-hover-outline-color: var( --md-sys-color-outline );--mdc-outlined-text-field-focus-outline-color: var( --md-sys-color-primary );--mdc-outlined-text-field-error-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-error-focus-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-error-hover-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-label-text-color: var( --md-sys-color-on-surface-variant );--mdc-outlined-text-field-input-text-color: var( --md-sys-color-on-surface );--mdc-outlined-text-field-supporting-text-color: var( --md-sys-color-on-surface-variant )}.editor-content .mat-mdc-form-field.w-full{max-width:none}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}.editor-split{grid-template-columns:minmax(0,1fr);align-items:start}.editor-main,.editor-aside{display:grid;gap:12px}.skin-preview-wrap{border-radius:calc(var(--editor-radius) - 4px);border:var(--editor-border);background:var(--md-sys-color-surface-container);padding:12px}.g{display:grid}.g-auto-220{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.g-auto-200{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}.g-1-auto{grid-template-columns:1fr auto}.row-flow{grid-auto-flow:column}.gap-6{gap:6px}.gap-8{gap:8px}.gap-12{gap:12px}.ai-center{align-items:center}.ai-end{align-items:end}.mt-12{margin-top:12px}.mb-8{margin-bottom:8px}.mb-6{margin-bottom:6px}.my-8{margin:8px 0}.subtitle{margin:8px 0 4px;color:var(--editor-muted);font-weight:500}.section-title{color:var(--editor-muted);font-weight:600}.chips-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.error{color:var(--md-sys-color-error);font-size:.85rem}.muted{color:var(--editor-muted)}.text-caption{color:var(--editor-muted);font-size:.8rem}:host ::ng-deep .mat-mdc-select-panel .option-icon{font-size:18px;margin-right:6px;vertical-align:middle}:host ::ng-deep .mat-mdc-select-panel .color-dot{width:10px;height:10px;border-radius:999px;display:inline-block;margin-right:6px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-outline)}:host ::ng-deep .mat-mdc-select-panel .color-primary{background:var(--md-sys-color-primary)}:host ::ng-deep .mat-mdc-select-panel .color-accent{background:var(--md-sys-color-tertiary)}:host ::ng-deep .mat-mdc-select-panel .color-warn{background:var(--md-sys-color-error)}:host ::ng-deep .mat-mdc-select-panel .color-default{background:var(--md-sys-color-outline)}@media(max-width:1024px){.editor-split{grid-template-columns:minmax(0,1fr)}}\n"] }]
|
|
6095
6094
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
6096
6095
|
type: Optional
|
|
6097
6096
|
}, {
|
|
@@ -10093,8 +10092,8 @@ class PraxisListMetricComponent {
|
|
|
10093
10092
|
.filter((entry) => !!entry);
|
|
10094
10093
|
return entries.length ? Object.fromEntries(entries) : undefined;
|
|
10095
10094
|
}
|
|
10096
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
10097
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
10095
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisListMetricComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10096
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisListMetricComponent, isStandalone: true, selector: "praxis-list-metric", inputs: { metric: "metric" }, ngImport: i0, template: `
|
|
10098
10097
|
<div
|
|
10099
10098
|
class="p-list-metric"
|
|
10100
10099
|
[ngClass]="metricClasses()"
|
|
@@ -10177,7 +10176,7 @@ class PraxisListMetricComponent {
|
|
|
10177
10176
|
</div>
|
|
10178
10177
|
`, isInline: true, styles: [":host{display:block;min-width:0}.p-list-metric{--p-list-metric-tone: currentColor;--p-list-metric-progress-fill: var( --p-list-metric-tone, var(--p-list-accent) );--p-list-metric-progress-track: color-mix( in srgb, var(--p-list-metric-progress-fill) 18%, transparent );display:grid;gap:4px;min-width:0;color:var(--p-list-metric-tone, inherit)}.p-list-metric--align-start{justify-items:start;text-align:start}.p-list-metric--align-center{justify-items:center;text-align:center}.p-list-metric--align-end{justify-items:end;text-align:end}.p-list-metric--tone-positive{--p-list-metric-tone: #2f7d32}.p-list-metric--tone-warning{--p-list-metric-tone: #b26a00}.p-list-metric--tone-negative{--p-list-metric-tone: #b3261e}.p-list-metric--tone-neutral{--p-list-metric-tone: var(--p-list-foreground-muted)}.p-list-metric__label{font-size:.7rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--p-list-foreground-muted)}.p-list-metric__main{display:grid;align-items:center;gap:8px;min-width:0}.p-list-metric--layout-icon-value-caption .p-list-metric__main{grid-template-columns:auto minmax(0,1fr)}.p-list-metric--layout-stacked-center .p-list-metric__main{justify-items:center;text-align:center}.p-list-metric--icon-right .p-list-metric__main{grid-template-columns:minmax(0,1fr) auto}.p-list-metric__icon{display:inline-flex;align-items:center;justify-content:center;color:var(--p-list-metric-tone, inherit)}.p-list-metric__icon--right{order:2}.p-list-metric__icon mat-icon{width:1rem;height:1rem;font-size:1rem}.p-list-metric__content{display:grid;gap:2px;min-width:0}.p-list-metric__value-row{display:flex;align-items:baseline;gap:6px;min-width:0;flex-wrap:wrap;--prx-rich-content-inline-align-items: baseline;--prx-rich-content-inline-flex-wrap: wrap;--prx-rich-content-inline-gap: 6px}.p-list-metric--layout-stacked-center .p-list-metric__value-row{justify-content:center}.p-list-metric__value{font-size:.95rem;font-weight:700;line-height:1.1;min-width:0}.p-list-metric__caption,.p-list-metric__subcaption{font-size:.72rem;line-height:1.2;color:var(--p-list-foreground-muted)}.p-list-metric__caption--inline{display:inline-flex;align-items:center}.p-list-metric__progress{width:100%;height:4px;border-radius:999px;overflow:hidden;background:var(--p-list-metric-progress-track)}.p-list-metric__progress-value{display:block;height:100%;border-radius:inherit;background:var(--p-list-metric-progress-fill)}.p-list-metric__progress--indeterminate{position:relative}.p-list-metric__progress--indeterminate .p-list-metric__progress-value{width:40%!important;animation:p-list-metric-progress 1.2s ease-in-out infinite}@keyframes p-list-metric-progress{0%{transform:translate(-100%)}to{transform:translate(250%)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: PraxisRichContent, selector: "praxis-rich-content", inputs: ["document", "nodes", "context", "hostCapabilities", "layout", "rootClassName"] }] });
|
|
10179
10178
|
}
|
|
10180
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
10179
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisListMetricComponent, decorators: [{
|
|
10181
10180
|
type: Component,
|
|
10182
10181
|
args: [{ selector: 'praxis-list-metric', standalone: true, imports: [CommonModule, PraxisRichContent], template: `
|
|
10183
10182
|
<div
|
|
@@ -10318,8 +10317,8 @@ class ExecutiveAccountIdentityComponent {
|
|
|
10318
10317
|
}
|
|
10319
10318
|
return nodes;
|
|
10320
10319
|
}
|
|
10321
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
10322
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
10320
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ExecutiveAccountIdentityComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10321
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: ExecutiveAccountIdentityComponent, isStandalone: true, selector: "praxis-executive-account-identity", inputs: { title: "title", markerIcon: "markerIcon", segmentVariant: "segmentVariant", segmentLabel: "segmentLabel", accountType: "accountType", since: "since" }, ngImport: i0, template: `
|
|
10323
10322
|
<span class="account-identity">
|
|
10324
10323
|
<span class="account-identity__title-row">
|
|
10325
10324
|
<praxis-rich-content
|
|
@@ -10348,14 +10347,13 @@ class ExecutiveAccountIdentityComponent {
|
|
|
10348
10347
|
</span>
|
|
10349
10348
|
}
|
|
10350
10349
|
</span>
|
|
10351
|
-
`, isInline: true, styles: [":host{display:inline-flex;min-width:0}.account-identity{display:inline-flex;flex-direction:column;gap:4px;min-width:0}.account-identity__title-row{display:inline-flex;align-items:center;min-width:0}.account-identity__title-content{display:inline-flex;align-items:center;gap:8px;min-width:0}.account-identity__title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:1.05rem;font-weight:800;letter-spacing:-.016em;color:#183046;line-height:1.16}.account-identity__marker{color:#b89536;font-size:15px!important;width:15px!important;height:15px!important;flex-shrink:0;transform:translateY(1px)}.account-identity__subtitle-row{display:inline-flex;align-items:center;flex-wrap:wrap;gap:7px;min-width:0}.account-identity__meta{display:inline-flex;align-items:center;color:#7f90a4;white-space:nowrap;font-size:.76rem;line-height:1.18}.account-identity__separator{color:#a5b0bc;font-size:.74rem;line-height:1}\n"], dependencies: [{ kind: "
|
|
10350
|
+
`, isInline: true, styles: [":host{display:inline-flex;min-width:0}.account-identity{display:inline-flex;flex-direction:column;gap:4px;min-width:0}.account-identity__title-row{display:inline-flex;align-items:center;min-width:0}.account-identity__title-content{display:inline-flex;align-items:center;gap:8px;min-width:0}.account-identity__title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:1.05rem;font-weight:800;letter-spacing:-.016em;color:#183046;line-height:1.16}.account-identity__marker{color:#b89536;font-size:15px!important;width:15px!important;height:15px!important;flex-shrink:0;transform:translateY(1px)}.account-identity__subtitle-row{display:inline-flex;align-items:center;flex-wrap:wrap;gap:7px;min-width:0}.account-identity__meta{display:inline-flex;align-items:center;color:#7f90a4;white-space:nowrap;font-size:.76rem;line-height:1.18}.account-identity__separator{color:#a5b0bc;font-size:.74rem;line-height:1}\n"], dependencies: [{ kind: "component", type: PraxisRichContent, selector: "praxis-rich-content", inputs: ["document", "nodes", "context", "hostCapabilities", "layout", "rootClassName"] }, { kind: "component", type: ExecutiveBadgeComponent, selector: "praxis-executive-badge", inputs: ["ariaLabel", "label", "variant"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
10352
10351
|
}
|
|
10353
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
10352
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ExecutiveAccountIdentityComponent, decorators: [{
|
|
10354
10353
|
type: Component,
|
|
10355
10354
|
args: [{ selector: 'praxis-executive-account-identity', standalone: true, imports: [
|
|
10356
|
-
CommonModule,
|
|
10357
10355
|
PraxisRichContent,
|
|
10358
|
-
ExecutiveBadgeComponent
|
|
10356
|
+
ExecutiveBadgeComponent
|
|
10359
10357
|
], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
10360
10358
|
<span class="account-identity">
|
|
10361
10359
|
<span class="account-identity__title-row">
|
|
@@ -10402,18 +10400,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
10402
10400
|
|
|
10403
10401
|
class ExecutiveAlertSummaryComponent {
|
|
10404
10402
|
alerts = [];
|
|
10405
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
10406
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
10403
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ExecutiveAlertSummaryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10404
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.14", type: ExecutiveAlertSummaryComponent, isStandalone: true, selector: "praxis-executive-alert-summary", inputs: { alerts: "alerts" }, ngImport: i0, template: `
|
|
10407
10405
|
<span class="alert-summary">
|
|
10408
10406
|
<praxis-executive-alerts
|
|
10409
10407
|
[alerts]="alerts || []"
|
|
10410
10408
|
></praxis-executive-alerts>
|
|
10411
10409
|
</span>
|
|
10412
|
-
`, isInline: true, styles: [":host{display:inline-flex;min-width:0}.alert-summary{display:inline-flex;min-width:0}\n"], dependencies: [{ kind: "
|
|
10410
|
+
`, isInline: true, styles: [":host{display:inline-flex;min-width:0}.alert-summary{display:inline-flex;min-width:0}\n"], dependencies: [{ kind: "component", type: ExecutiveAlertsComponent, selector: "praxis-executive-alerts", inputs: ["alerts"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
10413
10411
|
}
|
|
10414
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
10412
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ExecutiveAlertSummaryComponent, decorators: [{
|
|
10415
10413
|
type: Component,
|
|
10416
|
-
args: [{ selector: 'praxis-executive-alert-summary', standalone: true, imports: [
|
|
10414
|
+
args: [{ selector: 'praxis-executive-alert-summary', standalone: true, imports: [ExecutiveAlertsComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
10417
10415
|
<span class="alert-summary">
|
|
10418
10416
|
<praxis-executive-alerts
|
|
10419
10417
|
[alerts]="alerts || []"
|
|
@@ -10453,8 +10451,8 @@ class ExecutiveOwnerSummaryComponent {
|
|
|
10453
10451
|
}
|
|
10454
10452
|
return nodes;
|
|
10455
10453
|
}
|
|
10456
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
10457
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
10454
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ExecutiveOwnerSummaryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10455
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: ExecutiveOwnerSummaryComponent, isStandalone: true, selector: "praxis-executive-owner-summary", inputs: { name: "name", icon: "icon" }, ngImport: i0, template: `
|
|
10458
10456
|
@if (displayIcon) {
|
|
10459
10457
|
<praxis-rich-content
|
|
10460
10458
|
class="owner-summary owner-summary--icon"
|
|
@@ -10464,14 +10462,13 @@ class ExecutiveOwnerSummaryComponent {
|
|
|
10464
10462
|
} @else if (hasName) {
|
|
10465
10463
|
<praxis-executive-owner [name]="displayName"></praxis-executive-owner>
|
|
10466
10464
|
}
|
|
10467
|
-
`, isInline: true, styles: [":host{display:inline-flex;min-width:0}.owner-summary{display:inline-flex;align-items:center;min-width:0;font-size:.82rem;color:#5b6f84;font-weight:500}.owner-summary__content{display:inline-flex;align-items:center;gap:9px;min-width:0}.owner-summary__icon{width:20px!important;height:20px!important;font-size:20px!important;color:#8ea2b6;flex-shrink:0}.owner-summary__name{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.15}\n"], dependencies: [{ kind: "
|
|
10465
|
+
`, isInline: true, styles: [":host{display:inline-flex;min-width:0}.owner-summary{display:inline-flex;align-items:center;min-width:0;font-size:.82rem;color:#5b6f84;font-weight:500}.owner-summary__content{display:inline-flex;align-items:center;gap:9px;min-width:0}.owner-summary__icon{width:20px!important;height:20px!important;font-size:20px!important;color:#8ea2b6;flex-shrink:0}.owner-summary__name{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.15}\n"], dependencies: [{ kind: "component", type: PraxisRichContent, selector: "praxis-rich-content", inputs: ["document", "nodes", "context", "hostCapabilities", "layout", "rootClassName"] }, { kind: "component", type: ExecutiveOwnerComponent, selector: "praxis-executive-owner", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
10468
10466
|
}
|
|
10469
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
10467
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ExecutiveOwnerSummaryComponent, decorators: [{
|
|
10470
10468
|
type: Component,
|
|
10471
10469
|
args: [{ selector: 'praxis-executive-owner-summary', standalone: true, imports: [
|
|
10472
|
-
CommonModule,
|
|
10473
10470
|
PraxisRichContent,
|
|
10474
|
-
ExecutiveOwnerComponent
|
|
10471
|
+
ExecutiveOwnerComponent
|
|
10475
10472
|
], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
10476
10473
|
@if (displayIcon) {
|
|
10477
10474
|
<praxis-rich-content
|
|
@@ -10518,8 +10515,8 @@ class PraxisListRuntimeComponentComponent {
|
|
|
10518
10515
|
badgeLabel() {
|
|
10519
10516
|
return this.stringInput('label', 'segmentLabel').trim();
|
|
10520
10517
|
}
|
|
10521
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
10522
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
10518
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisListRuntimeComponentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10519
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisListRuntimeComponentComponent, isStandalone: true, selector: "praxis-list-runtime-component", inputs: { component: "component" }, ngImport: i0, template: `
|
|
10523
10520
|
<span
|
|
10524
10521
|
class="p-list-runtime-component"
|
|
10525
10522
|
[ngClass]="component?.class"
|
|
@@ -10603,7 +10600,7 @@ class PraxisListRuntimeComponentComponent {
|
|
|
10603
10600
|
</span>
|
|
10604
10601
|
`, isInline: true, styles: [":host{display:inline-flex;min-width:0}.p-list-runtime-component{display:inline-flex;min-width:0}.p-list-runtime-component__unsupported{display:inline-flex;align-items:center;justify-content:center;width:.75rem;height:.75rem;min-width:.75rem;border:1px dashed var(--md-sys-color-outline-variant);border-radius:999px;overflow:hidden;color:transparent;font-size:0;line-height:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ExecutiveAccountIdentityComponent, selector: "praxis-executive-account-identity", inputs: ["title", "markerIcon", "segmentVariant", "segmentLabel", "accountType", "since"] }, { kind: "component", type: ExecutiveAlertSummaryComponent, selector: "praxis-executive-alert-summary", inputs: ["alerts"] }, { kind: "component", type: ExecutiveAlertsComponent, selector: "praxis-executive-alerts", inputs: ["alerts"] }, { kind: "component", type: ExecutiveBadgeComponent, selector: "praxis-executive-badge", inputs: ["ariaLabel", "label", "variant"] }, { kind: "component", type: ExecutiveOwnerComponent, selector: "praxis-executive-owner", inputs: ["name"] }, { kind: "component", type: ExecutiveOwnerSummaryComponent, selector: "praxis-executive-owner-summary", inputs: ["name", "icon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
10605
10602
|
}
|
|
10606
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
10603
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisListRuntimeComponentComponent, decorators: [{
|
|
10607
10604
|
type: Component,
|
|
10608
10605
|
args: [{ selector: 'praxis-list-runtime-component', standalone: true, imports: [
|
|
10609
10606
|
CommonModule,
|
|
@@ -10874,8 +10871,8 @@ class PraxisListComposeComponent {
|
|
|
10874
10871
|
: '';
|
|
10875
10872
|
return `font-size:${size}px;width:${size}px;height:${size}px;${color}`;
|
|
10876
10873
|
}
|
|
10877
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
10878
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
10874
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisListComposeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10875
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisListComposeComponent, isStandalone: true, selector: "praxis-list-compose", inputs: { compose: "compose" }, ngImport: i0, template: `
|
|
10879
10876
|
<ng-container
|
|
10880
10877
|
*ngTemplateOutlet="renderCompose; context: { $implicit: compose }"
|
|
10881
10878
|
></ng-container>
|
|
@@ -11011,7 +11008,7 @@ class PraxisListComposeComponent {
|
|
|
11011
11008
|
</ng-template>
|
|
11012
11009
|
`, isInline: true, styles: [":host{display:block;min-width:0}.p-list-compose{display:flex;min-width:0}.p-list-compose--direction-row{flex-direction:row}.p-list-compose--direction-column{flex-direction:column}.p-list-compose--align-start{align-items:flex-start}.p-list-compose--align-center{align-items:center}.p-list-compose--align-end{align-items:flex-end}.p-list-compose--wrap{flex-wrap:wrap}.p-list-compose--nowrap{flex-wrap:nowrap}.p-list-compose__separator{color:var(--p-list-foreground-muted);white-space:pre-wrap}.p-list-compose__unsupported{display:inline-flex;align-items:center;justify-content:center;width:.75rem;height:.75rem;min-width:.75rem;border:1px dashed var(--md-sys-color-outline-variant);border-radius:999px;overflow:hidden;color:transparent;font-size:0;line-height:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i5.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisRichContent, selector: "praxis-rich-content", inputs: ["document", "nodes", "context", "hostCapabilities", "layout", "rootClassName"] }, { kind: "component", type: PraxisListMetricComponent, selector: "praxis-list-metric", inputs: ["metric"] }, { kind: "component", type: PraxisListRuntimeComponentComponent, selector: "praxis-list-runtime-component", inputs: ["component"] }] });
|
|
11013
11010
|
}
|
|
11014
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
11011
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisListComposeComponent, decorators: [{
|
|
11015
11012
|
type: Component,
|
|
11016
11013
|
args: [{ selector: 'praxis-list-compose', standalone: true, imports: [
|
|
11017
11014
|
CommonModule,
|
|
@@ -11264,7 +11261,7 @@ class PraxisList {
|
|
|
11264
11261
|
if (!this.aiAssistantOpen) {
|
|
11265
11262
|
this.openAiAssistantFromSession(session);
|
|
11266
11263
|
}
|
|
11267
|
-
}, ...(ngDevMode ? [{ debugName: "aiAssistantSessionEffect" }] : []));
|
|
11264
|
+
}, ...(ngDevMode ? [{ debugName: "aiAssistantSessionEffect" }] : /* istanbul ignore next */ []));
|
|
11268
11265
|
paginatorSelectConfig = {
|
|
11269
11266
|
panelClass: 'praxis-list-paginator-select-panel',
|
|
11270
11267
|
};
|
|
@@ -13874,15 +13871,15 @@ class PraxisList {
|
|
|
13874
13871
|
}
|
|
13875
13872
|
return [{ key: '', value: String(value) }];
|
|
13876
13873
|
}
|
|
13877
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
13878
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
13874
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisList, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13875
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisList, isStandalone: true, selector: "praxis-list", inputs: { config: "config", listId: "listId", componentInstanceId: "componentInstanceId", configPersistenceStrategy: "configPersistenceStrategy", queryContext: "queryContext", form: "form", enableCustomization: ["enableCustomization", "enableCustomization", booleanAttribute] }, outputs: { itemClick: "itemClick", actionClick: "actionClick", selectionChange: "selectionChange", exportAction: "exportAction" }, providers: [
|
|
13879
13876
|
GenericCrudService,
|
|
13880
13877
|
ListDataService,
|
|
13881
13878
|
MatPaginatorIntl,
|
|
13882
13879
|
providePraxisListI18n(),
|
|
13883
|
-
], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"praxis-list-root\"\n [ngClass]=\"skinClasses\"\n [attr.data-skin-scope]=\"skinScopeId\"\n [attr.aria-label]=\"config.a11y?.ariaLabel || null\"\n [attr.aria-labelledby]=\"config.a11y?.ariaLabelledBy || null\"\n>\n @if (inlineCss) {\n <style [attr.nonce]=\"cspNonce || null\" [textContent]=\"inlineCss\"></style>\n }\n\n @if (enableCustomization) {\n <div class=\"list-assistant\">\n <button\n mat-mini-fab\n type=\"button\"\n color=\"primary\"\n (click)=\"openConfigEditor()\"\n [matTooltip]=\"configEditorLabel()\"\n [attr.aria-label]=\"configEditorLabel()\"\n data-testid=\"praxis-list-open-config-editor\"\n >\n <mat-icon [praxisIcon]=\"'edit'\"></mat-icon>\n </button>\n <button\n mat-mini-fab\n type=\"button\"\n color=\"primary\"\n (click)=\"openAiAssistant()\"\n matTooltip=\"Copiloto semantico Praxis\"\n aria-label=\"Abrir copiloto semantico Praxis da lista\"\n data-testid=\"praxis-list-ai-assistant-trigger\"\n >\n <mat-icon [praxisIcon]=\"'auto_awesome'\"></mat-icon>\n </button>\n </div>\n }\n\n @if (aiAssistantOpen && aiAssistantViewState) {\n <praxis-ai-assistant-shell\n [labels]=\"aiAssistantLabels\"\n [mode]=\"aiAssistantViewState.mode\"\n [state]=\"aiAssistantViewState.state\"\n [contextItems]=\"aiAssistantViewState.contextItems\"\n [attachments]=\"aiAssistantViewState.attachments\"\n [messages]=\"aiAssistantViewState.messages\"\n [quickReplies]=\"aiAssistantViewState.quickReplies\"\n [prompt]=\"aiAssistantPrompt\"\n [statusText]=\"aiAssistantViewState.statusText\"\n [errorText]=\"aiAssistantViewState.errorText\"\n [busy]=\"aiAssistantViewState.state === 'processing' || aiAssistantViewState.state === 'applying'\"\n [canApply]=\"aiAssistantViewState.canApply\"\n [layout]=\"aiAssistantLayout\"\n testIdPrefix=\"praxis-list-ai-assistant\"\n panelTestId=\"praxis-list-ai-assistant-panel\"\n submitTestId=\"praxis-list-ai-assistant-submit\"\n applyTestId=\"praxis-list-ai-assistant-apply\"\n (promptChange)=\"onAiAssistantPromptChange($event)\"\n (submitPrompt)=\"onAiAssistantSubmit($event)\"\n (apply)=\"onAiAssistantApply()\"\n (retryTurn)=\"onAiAssistantRetry()\"\n (cancelTurn)=\"onAiAssistantCancel()\"\n (quickReply)=\"onAiAssistantQuickReply($event)\"\n (editMessage)=\"onAiAssistantEditMessage($event)\"\n (resendMessage)=\"onAiAssistantResendMessage($event)\"\n (layoutChange)=\"onAiAssistantLayoutChange($event)\"\n (close)=\"closeAiAssistant()\"\n ></praxis-ai-assistant-shell>\n }\n\n <!-- Skeleton while loading -->\n @if ((loading$ | async) && hasSkeleton()) {\n @if (isListVariant()) {\n <mat-list>\n @for (_ of skeletonItems(); track $index; let i = $index) {\n <mat-list-item>\n <div class=\"list-item-content\">\n <div class=\"list-item-leading\">\n <div class=\"skeleton skeleton-avatar\"></div>\n </div>\n <div class=\"list-item-text\">\n <div class=\"skeleton skeleton-line w-60\"></div>\n @if (layoutLines > 1) {\n <div class=\"skeleton skeleton-line w-40\"></div>\n }\n </div>\n <div class=\"list-item-trailing\">\n <div class=\"skeleton skeleton-chip\"></div>\n </div>\n </div>\n </mat-list-item>\n }\n </mat-list>\n } @else {\n <div class=\"cards-grid\">\n @for (_ of skeletonItems(); track $index; let i = $index) {\n <div class=\"item-card\">\n <div class=\"list-item-content\">\n <div class=\"list-item-leading\">\n <div class=\"skeleton skeleton-avatar\"></div>\n </div>\n <div class=\"list-item-text\">\n <div class=\"skeleton skeleton-line w-60\"></div>\n @if (layoutLines > 1) {\n <div class=\"skeleton skeleton-line w-40\"></div>\n }\n </div>\n <div class=\"list-item-trailing\">\n <div class=\"skeleton skeleton-chip\"></div>\n </div>\n </div>\n </div>\n }\n </div>\n }\n } @else {\n <ng-container *ngTemplateOutlet=\"notLoading\"></ng-container>\n }\n\n <ng-template #notLoading>\n @if (config.export?.enabled) {\n <div class=\"list-export-tools\">\n <button\n mat-stroked-button\n type=\"button\"\n [matMenuTriggerFor]=\"listExportMenu\"\n [attr.aria-label]=\"t('export.button', 'Exportar')\"\n [disabled]=\"exportBusy\"\n [attr.aria-busy]=\"exportBusy\"\n >\n <mat-icon [praxisIcon]=\"'download'\"></mat-icon>\n {{ t(\"export.button\", \"Exportar\") }}\n </button>\n <span class=\"cdk-visually-hidden\" aria-live=\"polite\">\n {{ exportStatusMessage }}\n </span>\n <mat-menu #listExportMenu=\"matMenu\">\n @for (format of listExportFormats(); track format) {\n <button\n mat-menu-item\n type=\"button\"\n (click)=\"onExportAction(format)\"\n >\n <mat-icon [praxisIcon]=\"exportIcon(format)\"></mat-icon>\n {{ format.toUpperCase() }}\n </button>\n }\n </mat-menu>\n </div>\n }\n\n <!-- Empty state -->\n @if (items$ | async; as all) {\n @if (all.length === 0) {\n <div class=\"section-header\">\n @if (emptyStateTemplate(); as empty) {\n @if (\n simpleRichContentNodes(empty, {\n imageAlt: config.templating?.emptyState?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (empty.type) {\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"empty.value\"\n [ngClass]=\"empty.class\"\n [color]=\"isThemeColor(empty.color) ? empty.color : undefined\"\n [style.cssText]=\"\n (empty.style ? empty.style + ';' : '') +\n iconStyle(empty.color)\n \"\n ></mat-icon>\n }\n @case (\"image\") {\n <span\n class=\"inline-image\"\n [ngClass]=\"empty.class\"\n [style.cssText]=\"empty.style\"\n >\n <img\n [src]=\"empty.value\"\n [alt]=\"config.templating?.emptyState?.imageAlt || ''\"\n />\n @if (empty.badge?.value) {\n <mat-chip\n class=\"inline-badge\"\n [color]=\"\n isThemeColor(empty.badge?.color)\n ? empty.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (empty.badge?.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(empty.badge?.color, empty.badge?.variant)\n \"\n >{{ empty.badge?.value }}</mat-chip\n >\n }\n </span>\n }\n @case (\"chip\") {\n <mat-chip\n [color]=\"isThemeColor(empty.color) ? empty.color : undefined\"\n [ngClass]=\"[\n empty.class || '',\n (empty.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : '',\n ]\"\n [style.cssText]=\"\n chipStyle(empty.color, empty.variant) +\n (empty.style ? ';' + empty.style : '')\n \"\n >{{ empty.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n <span [ngClass]=\"empty.class\" [style.cssText]=\"empty.style\">\n @for (\n _ of ratingRange(empty);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, empty.value)\"\n [color]=\"ratingThemeColor(empty)\"\n [style.cssText]=\"ratingIconStyle(empty)\"\n ></mat-icon>\n }\n </span>\n }\n @case (\"html\") {\n <span\n [ngClass]=\"empty.class\"\n [style.cssText]=\"empty.style\"\n [innerHTML]=\"empty.value\"\n ></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"empty\"></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose [compose]=\"empty\"></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"empty\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span [ngClass]=\"empty.class\" [style.cssText]=\"empty.style\">{{\n empty.value\n }}</span>\n }\n }\n }\n }\n </div>\n }\n }\n\n <ng-template\n #rowLayoutNode\n let-node\n let-slot=\"slot\"\n let-imageAlt=\"imageAlt\"\n >\n @if (simpleRichContentNodes(node, { slot: slot, imageAlt: imageAlt }); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (node?.type) {\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"node?.value\"\n [ngClass]=\"node?.class\"\n [color]=\"isThemeColor(node?.color) ? node?.color : undefined\"\n [style.cssText]=\"\n ((node?.style || '') ? node.style + ';' : '') +\n iconStyle(node?.color)\n \"\n ></mat-icon>\n }\n @case (\"image\") {\n <span\n class=\"inline-image\"\n [ngClass]=\"node?.class\"\n [style.cssText]=\"node?.style\"\n >\n <img [src]=\"node?.value\" [alt]=\"node?.imageAlt || imageAlt || ''\" />\n @if (node?.badge?.value) {\n <mat-chip\n class=\"inline-badge\"\n [color]=\"\n isThemeColor(node?.badge?.color)\n ? node?.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (node?.badge?.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(node?.badge?.color, node?.badge?.variant)\n \"\n >\n {{ node?.badge?.value }}\n </mat-chip>\n }\n </span>\n }\n @case (\"chip\") {\n <mat-chip\n [color]=\"isThemeColor(node?.color) ? node?.color : undefined\"\n [ngClass]=\"[\n node?.class || '',\n (node?.variant || 'filled') === 'outlined' ? 'chip-outlined' : '',\n ]\"\n [style.cssText]=\"\n chipStyle(node?.color, node?.variant) +\n (node?.style ? ';' + node.style : '')\n \"\n >\n {{ node?.value }}\n </mat-chip>\n }\n @case (\"rating\") {\n <span [ngClass]=\"node?.class\" [style.cssText]=\"node?.style\">\n @for (_ of ratingRange(node); track $index; let idx = $index) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, node?.value)\"\n [color]=\"ratingThemeColor(node)\"\n [style.cssText]=\"ratingIconStyle(node)\"\n ></mat-icon>\n }\n </span>\n }\n @case (\"html\") {\n <span\n [ngClass]=\"node?.class\"\n [style.cssText]=\"node?.style\"\n [innerHTML]=\"node?.value\"\n ></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"node\"></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose [compose]=\"node\"></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"node\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span [ngClass]=\"node?.class\" [style.cssText]=\"node?.style\">\n @if (slot === \"meta\" && config.templating?.metaPrefixIcon; as mpi) {\n <mat-icon [praxisIcon]=\"mpi\"></mat-icon>\n }\n {{ node?.value }}\n </span>\n }\n }\n }\n </ng-template>\n\n <ng-template\n #listRowLayout\n let-item\n let-index=\"index\"\n let-sectionKey=\"sectionKey\"\n let-clickable=\"clickable\"\n >\n <div\n class=\"list-item-content\"\n [ngClass]=\"rowLayoutItemClass(item)\"\n [attr.style]=\"rowLayoutItemStyle(item)\"\n [attr.role]=\"clickable ? 'button' : null\"\n [attr.tabindex]=\"clickable ? '0' : null\"\n [attr.aria-label]=\"clickable ? itemAriaLabel(item) : null\"\n [attr.aria-expanded]=\"\n clickable && expansionOwnedByRow() ? isExpanded(item, index) : null\n \"\n [attr.aria-controls]=\"\n clickable && expansionOwnedByRow()\n ? expandRegionId(item, index)\n : null\n \"\n (click)=\"clickable ? onRowActivate(item, index, sectionKey) : null\"\n (keydown.enter)=\"clickable ? onRowActivate(item, index, sectionKey) : null\"\n (keydown.space)=\"\n onRowSpaceActivate($event, clickable, item, index, sectionKey)\n \"\n >\n @for (column of rowLayoutColumns(); track rowLayoutTrackColumn($index, column)) {\n <div\n [attr.data-row-slot]=\"column.slot\"\n [ngClass]=\"rowLayoutColumnClass(column)\"\n [attr.style]=\"rowLayoutColumnStyle(column)\"\n >\n @if (column.slot === \"leading\") {\n @if (rowLayoutSlot(item, column.slot); as lead) {\n <ng-container\n *ngTemplateOutlet=\"\n rowLayoutNode;\n context: {\n $implicit: lead,\n slot: column.slot,\n imageAlt: rowLayoutImageAlt(column.slot),\n }\n \"\n ></ng-container>\n } @else {\n <span class=\"leading-placeholder\"></span>\n }\n } @else if (column.slot === \"trailing\") {\n @if (rowLayoutSlot(item, column.slot); as trailingNode) {\n <ng-container\n *ngTemplateOutlet=\"\n rowLayoutNode;\n context: {\n $implicit: trailingNode,\n slot: column.slot,\n imageAlt: rowLayoutImageAlt(column.slot),\n }\n \"\n ></ng-container>\n }\n @if (showExpandIcon(\"trailing\")) {\n <button\n mat-icon-button\n type=\"button\"\n class=\"expand-toggle\"\n [attr.aria-label]=\"expandToggleAriaLabel(item, index)\"\n [attr.aria-expanded]=\"\n expansionOwnedByIcon() ? isExpanded(item, index) : null\n \"\n [attr.aria-controls]=\"\n expansionOwnedByIcon()\n ? expandRegionId(item, index)\n : null\n \"\n (click)=\"onExpandToggle($event, item, index)\"\n >\n <mat-icon\n [praxisIcon]=\"\n isExpanded(item, index) ? 'expand_less' : 'expand_more'\n \"\n ></mat-icon>\n </button>\n }\n @if ((visibleActions(item, \"trailing\") || []).length) {\n <div\n class=\"list-item-actions\"\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"onActionKeydown($event)\"\n (keydown.space)=\"onActionKeydown($event)\"\n >\n @for (\n action of visibleActions(item, \"trailing\");\n let aidx = $index;\n track action?.id ?? $index\n ) {\n @if ((action.kind || \"icon\") === \"icon\") {\n <button\n mat-icon-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n (click)=\"onActionClick($event, action.id, item, index)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, index)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n index\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, index)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n <mat-icon\n [praxisIcon]=\"action.icon\"\n [style.cssText]=\"iconStyle(action.color)\"\n ></mat-icon>\n }\n </button>\n } @else {\n @if (action.buttonVariant === \"stroked\") {\n <button\n mat-stroked-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'stroked')\"\n (click)=\"onActionClick($event, action.id, item, index)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, index)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n index\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, index)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (action.buttonVariant === \"raised\") {\n <button\n mat-raised-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'raised')\"\n (click)=\"onActionClick($event, action.id, item, index)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, index)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n index\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, index)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (!action.buttonVariant || action.buttonVariant === \"flat\") {\n <button\n mat-flat-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'flat')\"\n (click)=\"onActionClick($event, action.id, item, index)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, index)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n index\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, index)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n }\n }\n </div>\n }\n } @else if (column.slot === \"actions\") {\n @if ((visibleActions(item, \"actions\") || []).length) {\n <div\n class=\"list-item-actions\"\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"onActionKeydown($event)\"\n (keydown.space)=\"onActionKeydown($event)\"\n >\n @for (\n action of visibleActions(item, \"actions\");\n let aidx = $index;\n track action?.id ?? $index\n ) {\n @if ((action.kind || \"icon\") === \"icon\") {\n <button\n mat-icon-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n (click)=\"onActionClick($event, action.id, item, index)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, index)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n index\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, index)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n <mat-icon\n [praxisIcon]=\"action.icon\"\n [style.cssText]=\"iconStyle(action.color)\"\n ></mat-icon>\n }\n </button>\n } @else {\n @if (action.buttonVariant === \"stroked\") {\n <button\n mat-stroked-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'stroked')\"\n (click)=\"onActionClick($event, action.id, item, index)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, index)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n index\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, index)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (action.buttonVariant === \"raised\") {\n <button\n mat-raised-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'raised')\"\n (click)=\"onActionClick($event, action.id, item, index)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, index)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n index\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, index)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (!action.buttonVariant || action.buttonVariant === \"flat\") {\n <button\n mat-flat-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'flat')\"\n (click)=\"onActionClick($event, action.id, item, index)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, index)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n index\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, index)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n }\n }\n </div>\n }\n } @else if (column.slot === \"expand\") {\n @if (showExpandIcon(\"expand\")) {\n <button\n mat-icon-button\n type=\"button\"\n class=\"expand-toggle\"\n [attr.aria-label]=\"expandToggleAriaLabel(item, index)\"\n [attr.aria-expanded]=\"\n expansionOwnedByIcon() ? isExpanded(item, index) : null\n \"\n [attr.aria-controls]=\"\n expansionOwnedByIcon()\n ? expandRegionId(item, index)\n : null\n \"\n (click)=\"onExpandToggle($event, item, index)\"\n >\n <mat-icon\n [praxisIcon]=\"\n isExpanded(item, index) ? 'expand_less' : 'expand_more'\n \"\n ></mat-icon>\n </button>\n }\n } @else if (rowLayoutSlot(item, column.slot); as slotNode) {\n <ng-container\n *ngTemplateOutlet=\"\n rowLayoutNode;\n context: {\n $implicit: slotNode,\n slot: column.slot,\n imageAlt: rowLayoutImageAlt(column.slot),\n }\n \"\n ></ng-container>\n }\n </div>\n }\n </div>\n </ng-template>\n\n <!-- List variant -->\n @if (isListVariant()) {\n <!-- Selection list -->\n @if (isSelectionEnabled()) {\n <mat-selection-list\n [multiple]=\"config.selection?.mode === 'multiple'\"\n [formControl]=\"boundControl\"\n (selectionChange)=\"onSelectionChange($event)\"\n >\n @for (\n section of sections$ | async;\n track trackBySection($index, section)\n ) {\n @if (section.key) {\n <div class=\"section-header mat-subheader\">\n @if (sectionHeaderTemplate(section.key); as sh) {\n @if (\n simpleRichContentNodes(sh, {\n imageAlt: config.templating?.sectionHeader?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (sh.type) {\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"sh.value\"\n [ngClass]=\"sh.class\"\n [color]=\"isThemeColor(sh.color) ? sh.color : undefined\"\n [style.cssText]=\"\n (sh.style ? sh.style + ';' : '') + iconStyle(sh.color)\n \"\n ></mat-icon>\n }\n @case (\"image\") {\n <span\n class=\"inline-image\"\n [ngClass]=\"sh.class\"\n [style.cssText]=\"sh.style\"\n >\n <img\n [src]=\"sh.value\"\n [alt]=\"\n config.templating?.sectionHeader?.imageAlt || ''\n \"\n />\n @if (sh.badge?.value) {\n <mat-chip\n class=\"inline-badge\"\n [color]=\"\n isThemeColor(sh.badge?.color)\n ? sh.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (sh.badge?.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(sh.badge?.color, sh.badge?.variant)\n \"\n >{{ sh.badge?.value }}</mat-chip\n >\n }\n </span>\n }\n @case (\"chip\") {\n <mat-chip\n [color]=\"isThemeColor(sh.color) ? sh.color : undefined\"\n [ngClass]=\"[\n sh.class || '',\n (sh.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : '',\n ]\"\n [style.cssText]=\"\n chipStyle(sh.color, sh.variant) +\n (sh.style ? ';' + sh.style : '')\n \"\n >{{ sh.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n <span [ngClass]=\"sh.class\" [style.cssText]=\"sh.style\">\n @for (\n _ of ratingRange(sh);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, sh.value)\"\n [color]=\"ratingThemeColor(sh)\"\n [style.cssText]=\"ratingIconStyle(sh)\"\n ></mat-icon>\n }\n </span>\n }\n @case (\"html\") {\n <span\n [ngClass]=\"sh.class\"\n [style.cssText]=\"sh.style\"\n [innerHTML]=\"sh.value\"\n ></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"sh\"></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose [compose]=\"sh\"></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"sh\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span [ngClass]=\"sh.class\" [style.cssText]=\"sh.style\">{{\n sh.value\n }}</span>\n }\n }\n }\n }\n </div>\n }\n @for (\n item of section.items;\n track trackByItem($index, item);\n let i = $index\n ) {\n <mat-list-option\n [value]=\"item\"\n [attr.aria-label]=\"itemAriaLabel(item)\"\n >\n @if (hasRowLayoutGrid()) {\n <ng-container\n *ngTemplateOutlet=\"\n listRowLayout;\n context: {\n $implicit: item,\n index: i,\n sectionKey: section.key || undefined,\n clickable: false,\n }\n \"\n ></ng-container>\n } @else {\n <div\n class=\"list-item-content\"\n [ngClass]=\"itemRuleClass(item)\"\n [attr.style]=\"itemRuleStyle(item)\"\n >\n <div class=\"list-item-leading\">\n @if (leading(item); as lead) {\n @if (\n simpleRichContentNodes(lead, {\n imageAlt: config.templating?.leading?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (lead.type) {\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"lead.value\"\n [ngClass]=\"lead.class\"\n [color]=\"\n isThemeColor(lead.color) ? lead.color : undefined\n \"\n [style.cssText]=\"\n (lead.style ? lead.style + ';' : '') +\n iconStyle(lead.color)\n \"\n ></mat-icon>\n }\n @case (\"image\") {\n <div\n class=\"lead-image\"\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >\n <img\n [src]=\"lead.value\"\n [alt]=\"config.templating?.leading?.imageAlt || ''\"\n />\n @if (lead.badge?.value) {\n <mat-chip\n class=\"lead-badge\"\n [color]=\"\n isThemeColor(lead.badge?.color)\n ? lead.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (lead.badge?.variant || 'filled') ===\n 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(\n lead.badge?.color,\n lead.badge?.variant\n )\n \"\n >{{ lead.badge?.value }}</mat-chip\n >\n }\n </div>\n }\n @case (\"text\") {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >{{ lead.value }}</span\n >\n }\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(lead.color) ? lead.color : undefined\n \"\n [ngClass]=\"\n (lead.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(lead.color, lead.variant)\n \"\n >{{ lead.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >\n @for (\n _ of ratingRange(lead);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, lead.value)\"\n [color]=\"ratingThemeColor(lead)\"\n [style.cssText]=\"ratingIconStyle(lead)\"\n ></mat-icon>\n }\n </span>\n }\n @case (\"html\") {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n [innerHTML]=\"lead.value\"\n ></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"lead\"></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose\n [compose]=\"lead\"\n ></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"lead\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >{{ lead.value }}</span\n >\n }\n }\n }\n } @else {\n <span class=\"leading-placeholder\"></span>\n }\n </div>\n <div class=\"list-item-text\">\n <div\n class=\"primary\"\n [ngClass]=\"primary(item)?.class\"\n [style.cssText]=\"primary(item)?.style\"\n >\n @if (simpleRichContentNodes(primary(item)); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else if (primary(item)?.type === \"metric\") {\n <praxis-list-metric\n [metric]=\"primary(item)\"\n ></praxis-list-metric>\n } @else if (primary(item)?.type === \"compose\") {\n <praxis-list-compose\n [compose]=\"primary(item)\"\n ></praxis-list-compose>\n } @else if (primary(item)?.type === \"component\") {\n <praxis-list-runtime-component\n [component]=\"primary(item)\"\n ></praxis-list-runtime-component>\n } @else {\n {{ primary(item)?.value }}\n }\n </div>\n @if (layoutLines > 1) {\n <div\n class=\"secondary\"\n [ngClass]=\"secondary(item)?.class\"\n [style.cssText]=\"secondary(item)?.style\"\n >\n @if (simpleRichContentNodes(secondary(item)); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else if (secondary(item)?.type === \"metric\") {\n <praxis-list-metric\n [metric]=\"secondary(item)\"\n ></praxis-list-metric>\n } @else if (secondary(item)?.type === \"compose\") {\n <praxis-list-compose\n [compose]=\"secondary(item)\"\n ></praxis-list-compose>\n } @else if (secondary(item)?.type === \"component\") {\n <praxis-list-runtime-component\n [component]=\"secondary(item)\"\n ></praxis-list-runtime-component>\n } @else {\n {{ secondary(item)?.value }}\n }\n </div>\n }\n @if (meta(item); as m) {\n @if (\n (config.templating?.metaPlacement === \"line\" &&\n (m?.type === \"text\" ||\n m?.type === \"date\" ||\n m?.type === \"currency\")) ||\n (layoutLines > 2 &&\n (m?.type === \"text\" ||\n m?.type === \"date\" ||\n m?.type === \"currency\"))\n ) {\n <div\n class=\"tertiary\"\n [ngClass]=\"m.class\"\n [style.cssText]=\"m.style\"\n >\n @if (config.templating?.metaPrefixIcon; as mpi) {\n <mat-icon [praxisIcon]=\"mpi\"></mat-icon>\n }\n {{ m.value }}\n </div>\n }\n }\n @if (featuresVisible()) {\n <div class=\"features\">\n @for (\n f of config.templating?.features || [];\n track $index;\n let fi = $index\n ) {\n <span\n class=\"feature\"\n [ngClass]=\"\n featureSemanticClass(item, f.expr, f.class)\n \"\n >\n @if (featureRichContentNodes(item, f); as featureNodes) {\n <praxis-rich-content [nodes]=\"featureNodes\"></praxis-rich-content>\n } @else {\n @if (f.icon && featuresMode() !== \"labels-only\") {\n <mat-icon [praxisIcon]=\"f.icon\"></mat-icon>\n }\n @if (featuresMode() !== \"icons-only\") {\n <span\n [ngClass]=\"f.class\"\n [style.cssText]=\"f.style\"\n >\n @for (\n line of featureLabelLines(item, f.expr);\n track $index\n ) {\n <span class=\"feature-line\">{{ line }}</span>\n }\n </span>\n }\n }\n @if (\n featureProgressPercent(item, f.expr, f.class);\n as progress\n ) {\n <span class=\"feature-progress\" aria-hidden=\"true\">\n <span\n class=\"feature-progress__value\"\n [style.width.%]=\"progress\"\n ></span>\n </span>\n }\n </span>\n }\n </div>\n }\n </div>\n <div class=\"list-item-trailing\">\n @if (meta(item); as m) {\n @if (\n !(\n (config.templating?.metaPlacement === \"line\" ||\n layoutLines > 2) &&\n (m?.type === \"text\" ||\n m?.type === \"date\" ||\n m?.type === \"currency\")\n )\n ) {\n <div\n class=\"meta\"\n [ngClass]=\"m.class\"\n [style.cssText]=\"m.style\"\n >\n @if (simpleRichContentNodes(m, { slot: 'meta' }); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (m.type) {\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(m.color) ? m.color : undefined\n \"\n [ngClass]=\"\n (m.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(m.color, m.variant)\"\n >{{ m.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(m);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, m.value)\"\n [color]=\"ratingThemeColor(m)\"\n [style.cssText]=\"ratingIconStyle(m)\"\n ></mat-icon>\n }\n }\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"m.value\"\n [color]=\"\n isThemeColor(m.color) ? m.color : undefined\n \"\n [style.cssText]=\"iconStyle(m.color)\"\n ></mat-icon>\n }\n @case (\"image\") {\n <span class=\"inline-image\"\n ><img [src]=\"m.value\" [alt]=\"m.imageAlt || ''\"\n /></span>\n }\n @case (\"html\") {\n <span [innerHTML]=\"m.value\"></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"m\"></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose\n [compose]=\"m\"\n ></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"m\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span>\n @if (\n config.templating?.metaPrefixIcon;\n as mpi2\n ) {\n <mat-icon [praxisIcon]=\"mpi2\"></mat-icon>\n }\n {{ m.value }}</span\n >\n }\n }\n }\n </div>\n }\n }\n @if (trailing(item); as tr) {\n <div\n class=\"trailing\"\n [ngClass]=\"tr.class\"\n [style.cssText]=\"tr.style\"\n >\n @if (\n simpleRichContentNodes(tr, {\n imageAlt: config.templating?.trailing?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (tr.type) {\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(tr.color) ? tr.color : undefined\n \"\n [ngClass]=\"\n (tr.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(tr.color, tr.variant)\"\n >{{ tr.value }}</mat-chip\n >\n }\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"tr.value\"\n [color]=\"\n isThemeColor(tr.color) ? tr.color : undefined\n \"\n [style.cssText]=\"iconStyle(tr.color)\"\n ></mat-icon>\n }\n @case (\"image\") {\n <div\n class=\"lead-image\"\n [ngClass]=\"tr.class\"\n [style.cssText]=\"tr.style\"\n >\n <img\n [src]=\"tr.value\"\n [alt]=\"\n config.templating?.trailing?.imageAlt || ''\n \"\n />\n @if (tr.badge?.value) {\n <mat-chip\n class=\"lead-badge\"\n [color]=\"\n isThemeColor(tr.badge?.color)\n ? tr.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (tr.badge?.variant || 'filled') ===\n 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(\n tr.badge?.color,\n tr.badge?.variant\n )\n \"\n >{{ tr.badge?.value }}</mat-chip\n >\n }\n </div>\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(tr);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, tr.value)\"\n [color]=\"ratingThemeColor(tr)\"\n [style.cssText]=\"ratingIconStyle(tr)\"\n ></mat-icon>\n }\n }\n @case (\"html\") {\n @if (item.alertsData !== undefined && item.ownerName) {\n <!-- Component-based rendering for executive trailing -->\n <span class=\"exec-trailing-shell\">\n <praxis-executive-alerts [alerts]=\"item.alertsData\"></praxis-executive-alerts>\n <praxis-executive-owner [name]=\"item.ownerName\"></praxis-executive-owner>\n </span>\n } @else {\n <span [innerHTML]=\"tr.value\"></span>\n }\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"tr\"></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose\n [compose]=\"tr\"\n ></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"tr\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span>{{ tr.value }}</span>\n }\n }\n }\n </div>\n }\n @if (showExpandIcon(\"trailing\")) {\n <button\n mat-icon-button\n type=\"button\"\n class=\"expand-toggle\"\n [attr.aria-label]=\"expandToggleAriaLabel(item, i)\"\n [attr.aria-expanded]=\"\n expansionOwnedByIcon() ? isExpanded(item, i) : null\n \"\n [attr.aria-controls]=\"\n expansionOwnedByIcon()\n ? expandRegionId(item, i)\n : null\n \"\n (click)=\"onExpandToggle($event, item, i)\"\n >\n <mat-icon\n [praxisIcon]=\"\n isExpanded(item, i) ? 'expand_less' : 'expand_more'\n \"\n ></mat-icon>\n </button>\n }\n @if ((visibleActions(item, \"trailing\") || []).length) {\n <div\n class=\"list-item-actions\"\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"onActionKeydown($event)\"\n (keydown.space)=\"onActionKeydown($event)\"\n >\n @for (\n action of visibleActions(item, \"trailing\");\n let aidx = $index;\n track action?.id ?? $index\n ) {\n @if ((action.kind || \"icon\") === \"icon\") {\n <button\n mat-icon-button\n [color]=\"\n isThemeColor(action.color)\n ? action.color\n : undefined\n \"\n (click)=\"\n onActionClick($event, action.id, item, i)\n \"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n i\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n <mat-icon\n [praxisIcon]=\"action.icon\"\n [style.cssText]=\"iconStyle(action.color)\"\n ></mat-icon>\n }\n </button>\n } @else {\n @if (action.buttonVariant === \"stroked\") {\n <button\n mat-stroked-button\n [color]=\"\n isThemeColor(action.color)\n ? action.color\n : undefined\n \"\n [style.cssText]=\"\n buttonStyle(action.color, 'stroked')\n \"\n (click)=\"\n onActionClick($event, action.id, item, i)\n \"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n i\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (action.buttonVariant === \"raised\") {\n <button\n mat-raised-button\n [color]=\"\n isThemeColor(action.color)\n ? action.color\n : undefined\n \"\n [style.cssText]=\"\n buttonStyle(action.color, 'raised')\n \"\n (click)=\"\n onActionClick($event, action.id, item, i)\n \"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n i\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (\n !action.buttonVariant ||\n action.buttonVariant === \"flat\"\n ) {\n <button\n mat-flat-button\n [color]=\"\n isThemeColor(action.color)\n ? action.color\n : undefined\n \"\n [style.cssText]=\"\n buttonStyle(action.color, 'flat')\n \"\n (click)=\"\n onActionClick($event, action.id, item, i)\n \"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n i\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n }\n }\n </div>\n }\n </div>\n </div>\n }\n @if (isExpandable() && isExpanded(item, i)) {\n <div\n class=\"item-expansion\"\n [id]=\"expandRegionId(item, i)\"\n role=\"region\"\n [attr.aria-label]=\"expansionRegionAriaLabel(item)\"\n >\n <div class=\"item-expansion-grid\">\n @for (\n expansionSection of expansionSections(item);\n track expansionSection.id\n ) {\n <section\n class=\"expansion-section\"\n [attr.data-section-type]=\"expansionSection.type\"\n >\n @if (expansionSection.title) {\n <div class=\"expansion-section-title\">\n {{ expansionSection.title }}\n </div>\n }\n @if (\n expansionSectionHasContent(\n item,\n expansionSection,\n i\n )\n ) {\n @switch (expansionSection.type) {\n @case (\"chip-list\") {\n <div class=\"expansion-chip-list\">\n @for (\n expansionEntry of expansionSectionItems(\n item,\n expansionSection,\n i\n );\n track $index\n ) {\n <mat-chip class=\"expansion-chip\">{{\n expansionItemLabel(expansionEntry)\n }}</mat-chip>\n }\n </div>\n }\n @case (\"timeline\") {\n <div class=\"expansion-timeline\">\n @for (\n expansionEntry of expansionSectionItems(\n item,\n expansionSection,\n i\n );\n track $index\n ) {\n <div class=\"expansion-timeline-item\">\n <div class=\"expansion-timeline-title\">\n {{\n expansionTimelineTitle(expansionEntry)\n }}\n </div>\n @if (\n expansionTimelineMeta(expansionEntry)\n ) {\n <div class=\"expansion-timeline-meta\">\n {{\n expansionTimelineMeta(\n expansionEntry\n )\n }}\n </div>\n }\n @if (\n expansionTimelineDescription(\n expansionEntry\n )\n ) {\n <div\n class=\"expansion-timeline-description\"\n >\n {{\n expansionTimelineDescription(\n expansionEntry\n )\n }}\n </div>\n }\n </div>\n }\n </div>\n }\n @case (\"key-value\") {\n <dl class=\"expansion-key-value\">\n @for (\n expansionEntry of expansionSectionItems(\n item,\n expansionSection,\n i\n );\n track $index\n ) {\n <div class=\"expansion-key-value-row\">\n @if (\n expansionKeyValueKey(expansionEntry)\n ) {\n <dt>\n {{\n expansionKeyValueKey(expansionEntry)\n }}\n </dt>\n }\n <dd>\n {{\n expansionKeyValueValue(expansionEntry)\n }}\n </dd>\n </div>\n }\n </dl>\n }\n @default {\n <div class=\"expansion-info-list\">\n @for (\n expansionEntry of expansionSectionItems(\n item,\n expansionSection,\n i\n );\n track $index\n ) {\n <div class=\"expansion-info-item\">\n <div class=\"expansion-info-title\">\n {{ expansionInfoTitle(expansionEntry) }}\n </div>\n @if (expansionInfoValue(expansionEntry)) {\n <div class=\"expansion-info-value\">\n {{\n expansionInfoValue(expansionEntry)\n }}\n </div>\n }\n </div>\n }\n </div>\n }\n }\n } @else {\n <div class=\"expansion-empty\">\n {{\n expansionSection.emptyLabel ||\n \"Sem dados dispon\u00EDveis\"\n }}\n </div>\n }\n </section>\n }\n </div>\n </div>\n }\n </mat-list-option>\n @if (\n (config.layout?.dividers === \"all\" ||\n config.layout?.dividers === \"between\") &&\n i < section.items.length - 1\n ) {\n <mat-divider></mat-divider>\n }\n }\n }\n </mat-selection-list>\n } @else {\n <ng-container *ngTemplateOutlet=\"readList\"></ng-container>\n }\n\n <!-- Read-only list -->\n <ng-template #readList>\n <mat-list>\n @for (\n section of sections$ | async;\n track trackBySection($index, section);\n let sidx = $index\n ) {\n @if (section.key) {\n <div class=\"section-header mat-subheader\">\n @if (sectionHeaderTemplate(section.key); as sh) {\n @if (\n simpleRichContentNodes(sh, {\n imageAlt: config.templating?.sectionHeader?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (sh.type) {\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"sh.value\"\n [ngClass]=\"sh.class\"\n [color]=\"isThemeColor(sh.color) ? sh.color : undefined\"\n [style.cssText]=\"\n (sh.style ? sh.style + ';' : '') + iconStyle(sh.color)\n \"\n ></mat-icon>\n }\n @case (\"image\") {\n <span\n class=\"inline-image\"\n [ngClass]=\"sh.class\"\n [style.cssText]=\"sh.style\"\n >\n <img\n [src]=\"sh.value\"\n [alt]=\"\n config.templating?.sectionHeader?.imageAlt || ''\n \"\n />\n @if (sh.badge?.value) {\n <mat-chip\n class=\"inline-badge\"\n [color]=\"\n isThemeColor(sh.badge?.color)\n ? sh.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (sh.badge?.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(sh.badge?.color, sh.badge?.variant)\n \"\n >{{ sh.badge?.value }}</mat-chip\n >\n }\n </span>\n }\n @case (\"chip\") {\n <mat-chip\n [color]=\"isThemeColor(sh.color) ? sh.color : undefined\"\n [ngClass]=\"[\n sh.class || '',\n (sh.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : '',\n ]\"\n [style.cssText]=\"\n chipStyle(sh.color, sh.variant) +\n (sh.style ? ';' + sh.style : '')\n \"\n >{{ sh.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n <span [ngClass]=\"sh.class\" [style.cssText]=\"sh.style\">\n @for (\n _ of ratingRange(sh);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, sh.value)\"\n [color]=\"ratingThemeColor(sh)\"\n [style.cssText]=\"ratingIconStyle(sh)\"\n ></mat-icon>\n }\n </span>\n }\n @case (\"html\") {\n <span\n [ngClass]=\"sh.class\"\n [style.cssText]=\"sh.style\"\n [innerHTML]=\"sh.value\"\n ></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"sh\"></praxis-list-metric>\n }\n @default {\n <span [ngClass]=\"sh.class\" [style.cssText]=\"sh.style\">{{\n sh.value\n }}</span>\n }\n }\n }\n }\n </div>\n }\n @for (\n item of section.items;\n track trackByItem($index, item);\n let i = $index\n ) {\n <mat-list-item [attr.data-item-status]=\"item?.status || null\">\n @if (hasRowLayoutGrid()) {\n <ng-container\n *ngTemplateOutlet=\"\n listRowLayout;\n context: {\n $implicit: item,\n index: i,\n sectionKey: section.key || undefined,\n clickable: true,\n }\n \"\n ></ng-container>\n } @else {\n <div\n class=\"list-item-content\"\n [ngClass]=\"itemRuleClass(item)\"\n [attr.style]=\"itemRuleStyle(item)\"\n >\n <button\n type=\"button\"\n class=\"list-item-main-action\"\n [attr.aria-label]=\"itemAriaLabel(item)\"\n [attr.aria-expanded]=\"\n expansionOwnedByRow() ? isExpanded(item, i) : null\n \"\n [attr.aria-controls]=\"\n expansionOwnedByRow() ? expandRegionId(item, i) : null\n \"\n (click)=\"onRowActivate(item, i, section.key || undefined)\"\n >\n <div class=\"list-item-leading\">\n @if (leading(item); as lead) {\n @if (\n simpleRichContentNodes(lead, {\n imageAlt: config.templating?.leading?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (lead.type) {\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"lead.value\"\n [ngClass]=\"lead.class\"\n [color]=\"\n isThemeColor(lead.color)\n ? lead.color\n : undefined\n \"\n [style.cssText]=\"\n (lead.style ? lead.style + ';' : '') +\n iconStyle(lead.color)\n \"\n ></mat-icon>\n }\n @case (\"image\") {\n <div\n class=\"lead-image\"\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >\n <img\n [src]=\"lead.value\"\n [alt]=\"\n config.templating?.leading?.imageAlt || ''\n \"\n />\n @if (lead.badge?.value) {\n <mat-chip\n class=\"lead-badge\"\n [color]=\"\n isThemeColor(lead.badge?.color)\n ? lead.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (lead.badge?.variant || 'filled') ===\n 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(\n lead.badge?.color,\n lead.badge?.variant\n )\n \"\n >{{ lead.badge?.value }}</mat-chip\n >\n }\n </div>\n }\n @case (\"text\") {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >{{ lead.value }}</span\n >\n }\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(lead.color)\n ? lead.color\n : undefined\n \"\n [ngClass]=\"\n (lead.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(lead.color, lead.variant)\n \"\n >{{ lead.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(lead);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, lead.value)\"\n [color]=\"ratingThemeColor(lead)\"\n [style.cssText]=\"ratingIconStyle(lead)\"\n ></mat-icon>\n }\n }\n @case (\"html\") {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n [innerHTML]=\"lead.value\"\n ></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"lead\"></praxis-list-metric>\n }\n @default {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >{{ lead.value }}</span\n >\n }\n }\n }\n } @else {\n <span class=\"leading-placeholder\"></span>\n }\n </div>\n <div class=\"list-item-text\">\n <div\n class=\"primary\"\n [ngClass]=\"primary(item)?.class\"\n [style.cssText]=\"primary(item)?.style\"\n >\n @if (simpleRichContentNodes(primary(item)); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else if (primary(item)?.type === \"metric\") {\n <praxis-list-metric\n [metric]=\"primary(item)\"\n ></praxis-list-metric>\n } @else if (primary(item)?.type === \"html\") {\n <span [innerHTML]=\"primary(item)?.value\"></span>\n } @else {\n {{ primary(item)?.value }}\n }\n </div>\n @if (layoutLines > 1) {\n <div\n class=\"secondary\"\n [ngClass]=\"secondary(item)?.class\"\n [style.cssText]=\"secondary(item)?.style\"\n >\n @if (simpleRichContentNodes(secondary(item)); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else if (item.segmentVariant && item.segmentLabel && item.accountType && item.since) {\n <!-- Component-based rendering for executive items -->\n <span class=\"exec-subline\">\n <praxis-executive-badge\n [variant]=\"item.segmentVariant\"\n [label]=\"item.segmentLabel\"\n ></praxis-executive-badge>\n <span class=\"exec-subcopy\">{{ item.accountType }}</span>\n <span class=\"exec-subseparator\">\u00B7</span>\n <span class=\"exec-subcopy\">Desde {{ item.since }}</span>\n </span>\n } @else if (secondary(item)?.type === \"html\") {\n <span [innerHTML]=\"secondary(item)?.value\"></span>\n } @else if (secondary(item)?.type === \"metric\") {\n <praxis-list-metric\n [metric]=\"secondary(item)\"\n ></praxis-list-metric>\n } @else {\n {{ secondary(item)?.value }}\n }\n </div>\n }\n @if (meta(item); as m) {\n @if (\n (config.templating?.metaPlacement === \"line\" &&\n (m?.type === \"text\" ||\n m?.type === \"date\" ||\n m?.type === \"currency\")) ||\n (layoutLines > 2 &&\n (m?.type === \"text\" ||\n m?.type === \"date\" ||\n m?.type === \"currency\"))\n ) {\n <div\n class=\"tertiary\"\n [ngClass]=\"m.class\"\n [style.cssText]=\"m.style\"\n >\n @if (config.templating?.metaPrefixIcon; as mpi) {\n <mat-icon [praxisIcon]=\"mpi\"></mat-icon>\n }\n {{ m.value }}\n </div>\n }\n }\n @if (featuresVisible()) {\n <div class=\"features\">\n @for (\n f of config.templating?.features || [];\n track $index;\n let fi = $index\n ) {\n <span\n class=\"feature\"\n [ngClass]=\"\n featureSemanticClass(item, f.expr, f.class)\n \"\n >\n @if (featureRichContentNodes(item, f); as featureNodes) {\n <praxis-rich-content [nodes]=\"featureNodes\"></praxis-rich-content>\n } @else {\n @if (f.icon && featuresMode() !== \"labels-only\") {\n <mat-icon [praxisIcon]=\"f.icon\"></mat-icon>\n }\n @if (featuresMode() !== \"icons-only\") {\n <span\n [ngClass]=\"f.class\"\n [style.cssText]=\"f.style\"\n >\n @for (\n line of featureLabelLines(item, f.expr);\n track $index\n ) {\n <span class=\"feature-line\">{{ line }}</span>\n }\n </span>\n }\n }\n @if (\n featureProgressPercent(item, f.expr, f.class);\n as progress\n ) {\n <span\n class=\"feature-progress\"\n aria-hidden=\"true\"\n >\n <span\n class=\"feature-progress__value\"\n [style.width.%]=\"progress\"\n ></span>\n </span>\n }\n </span>\n }\n </div>\n }\n </div>\n </button>\n <div class=\"list-item-trailing\">\n @if (meta(item); as m) {\n @if (\n !(\n (config.templating?.metaPlacement === \"line\" ||\n layoutLines > 2) &&\n (m?.type === \"text\" ||\n m?.type === \"date\" ||\n m?.type === \"currency\")\n )\n ) {\n <div\n class=\"meta\"\n [ngClass]=\"m.class\"\n [style.cssText]=\"m.style\"\n >\n @if (simpleRichContentNodes(m, { slot: 'meta' }); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (m.type) {\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(m.color) ? m.color : undefined\n \"\n [ngClass]=\"\n (m.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(m.color, m.variant)\"\n >{{ m.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(m);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, m.value)\"\n [color]=\"ratingThemeColor(m)\"\n [style.cssText]=\"ratingIconStyle(m)\"\n ></mat-icon>\n }\n }\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"m.value\"\n [color]=\"\n isThemeColor(m.color) ? m.color : undefined\n \"\n [style.cssText]=\"iconStyle(m.color)\"\n ></mat-icon>\n }\n @case (\"image\") {\n <span class=\"inline-image\"\n ><img [src]=\"m.value\" [alt]=\"m.imageAlt || ''\"\n /></span>\n }\n @case (\"html\") {\n <span [innerHTML]=\"m.value\"></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"m\"></praxis-list-metric>\n }\n @default {\n <span>\n @if (\n config.templating?.metaPrefixIcon;\n as mpi2\n ) {\n <mat-icon [praxisIcon]=\"mpi2\"></mat-icon>\n }\n {{ m.value }}</span\n >\n }\n }\n }\n </div>\n }\n }\n @if (trailing(item); as tr) {\n <div\n class=\"trailing\"\n [ngClass]=\"tr.class\"\n [style.cssText]=\"tr.style\"\n >\n @if (\n simpleRichContentNodes(tr, {\n imageAlt: config.templating?.trailing?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (tr.type) {\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(tr.color) ? tr.color : undefined\n \"\n [ngClass]=\"\n (tr.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(tr.color, tr.variant)\"\n >{{ tr.value }}</mat-chip\n >\n }\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"tr.value\"\n [color]=\"\n isThemeColor(tr.color) ? tr.color : undefined\n \"\n [style.cssText]=\"iconStyle(tr.color)\"\n ></mat-icon>\n }\n @case (\"image\") {\n <div\n class=\"lead-image\"\n [ngClass]=\"tr.class\"\n [style.cssText]=\"tr.style\"\n >\n <img\n [src]=\"tr.value\"\n [alt]=\"\n config.templating?.trailing?.imageAlt || ''\n \"\n />\n @if (tr.badge?.value) {\n <mat-chip\n class=\"lead-badge\"\n [color]=\"\n isThemeColor(tr.badge?.color)\n ? tr.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (tr.badge?.variant || 'filled') ===\n 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(\n tr.badge?.color,\n tr.badge?.variant\n )\n \"\n >{{ tr.badge?.value }}</mat-chip\n >\n }\n </div>\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(tr);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, tr.value)\"\n [color]=\"ratingThemeColor(tr)\"\n [style.cssText]=\"ratingIconStyle(tr)\"\n ></mat-icon>\n }\n }\n @case (\"html\") {\n @if (item.alertsData !== undefined && item.ownerName) {\n <!-- Component-based rendering for executive trailing -->\n <span class=\"exec-trailing-shell\">\n <praxis-executive-alerts [alerts]=\"item.alertsData\"></praxis-executive-alerts>\n <praxis-executive-owner [name]=\"item.ownerName\"></praxis-executive-owner>\n </span>\n } @else {\n <span [innerHTML]=\"tr.value\"></span>\n }\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"tr\"></praxis-list-metric>\n }\n @default {\n <span>{{ tr.value }}</span>\n }\n }\n }\n </div>\n }\n @if (showExpandIcon(\"trailing\")) {\n <button\n mat-icon-button\n type=\"button\"\n class=\"expand-toggle\"\n [attr.aria-label]=\"expandToggleAriaLabel(item, i)\"\n [attr.aria-expanded]=\"\n expansionOwnedByIcon() ? isExpanded(item, i) : null\n \"\n [attr.aria-controls]=\"\n expansionOwnedByIcon()\n ? expandRegionId(item, i)\n : null\n \"\n (click)=\"onExpandToggle($event, item, i)\"\n >\n <mat-icon\n [praxisIcon]=\"\n isExpanded(item, i) ? 'expand_less' : 'expand_more'\n \"\n ></mat-icon>\n </button>\n }\n @if ((visibleActions(item, \"trailing\") || []).length) {\n <div\n class=\"list-item-actions\"\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"onActionKeydown($event)\"\n (keydown.space)=\"onActionKeydown($event)\"\n >\n @for (\n action of visibleActions(item, \"trailing\");\n let aidx = $index;\n track action?.id ?? $index\n ) {\n @if ((action.kind || \"icon\") === \"icon\") {\n <button\n mat-icon-button\n [color]=\"\n isThemeColor(action.color)\n ? action.color\n : undefined\n \"\n (click)=\"\n onActionClick($event, action.id, item, i)\n \"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n i\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n <mat-icon\n [praxisIcon]=\"action.icon\"\n [style.cssText]=\"iconStyle(action.color)\"\n ></mat-icon>\n }\n </button>\n } @else {\n @if (action.buttonVariant === \"stroked\") {\n <button\n mat-stroked-button\n [color]=\"\n isThemeColor(action.color)\n ? action.color\n : undefined\n \"\n [style.cssText]=\"\n buttonStyle(action.color, 'stroked')\n \"\n (click)=\"\n onActionClick($event, action.id, item, i)\n \"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n i\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (action.buttonVariant === \"raised\") {\n <button\n mat-raised-button\n [color]=\"\n isThemeColor(action.color)\n ? action.color\n : undefined\n \"\n [style.cssText]=\"\n buttonStyle(action.color, 'raised')\n \"\n (click)=\"\n onActionClick($event, action.id, item, i)\n \"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n i\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (\n !action.buttonVariant ||\n action.buttonVariant === \"flat\"\n ) {\n <button\n mat-flat-button\n [color]=\"\n isThemeColor(action.color)\n ? action.color\n : undefined\n \"\n [style.cssText]=\"\n buttonStyle(action.color, 'flat')\n \"\n (click)=\"\n onActionClick($event, action.id, item, i)\n \"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n i\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n }\n }\n </div>\n }\n </div>\n </div>\n }\n @if (isExpandable() && isExpanded(item, i)) {\n <div\n class=\"item-expansion\"\n [id]=\"expandRegionId(item, i)\"\n role=\"region\"\n [attr.aria-label]=\"expansionRegionAriaLabel(item)\"\n >\n <div class=\"item-expansion-grid\">\n @for (\n expansionSection of expansionSections(item);\n track expansionSection.id\n ) {\n <section\n class=\"expansion-section\"\n [attr.data-section-type]=\"expansionSection.type\"\n >\n @if (expansionSection.title) {\n <div class=\"expansion-section-title\">\n {{ expansionSection.title }}\n </div>\n }\n @if (\n expansionSectionHasContent(\n item,\n expansionSection,\n i\n )\n ) {\n @switch (expansionSection.type) {\n @case (\"chip-list\") {\n <div class=\"expansion-chip-list\">\n @for (\n expansionEntry of expansionSectionItems(\n item,\n expansionSection,\n i\n );\n track $index\n ) {\n <mat-chip class=\"expansion-chip\">{{\n expansionItemLabel(expansionEntry)\n }}</mat-chip>\n }\n </div>\n }\n @case (\"timeline\") {\n <div class=\"expansion-timeline\">\n @for (\n expansionEntry of expansionSectionItems(\n item,\n expansionSection,\n i\n );\n track $index\n ) {\n <div class=\"expansion-timeline-item\">\n <div class=\"expansion-timeline-title\">\n {{\n expansionTimelineTitle(expansionEntry)\n }}\n </div>\n @if (\n expansionTimelineMeta(expansionEntry)\n ) {\n <div class=\"expansion-timeline-meta\">\n {{\n expansionTimelineMeta(\n expansionEntry\n )\n }}\n </div>\n }\n @if (\n expansionTimelineDescription(\n expansionEntry\n )\n ) {\n <div\n class=\"expansion-timeline-description\"\n >\n {{\n expansionTimelineDescription(\n expansionEntry\n )\n }}\n </div>\n }\n </div>\n }\n </div>\n }\n @case (\"key-value\") {\n <dl class=\"expansion-key-value\">\n @for (\n expansionEntry of expansionSectionItems(\n item,\n expansionSection,\n i\n );\n track $index\n ) {\n <div class=\"expansion-key-value-row\">\n @if (\n expansionKeyValueKey(expansionEntry)\n ) {\n <dt>\n {{\n expansionKeyValueKey(expansionEntry)\n }}\n </dt>\n }\n <dd>\n {{\n expansionKeyValueValue(expansionEntry)\n }}\n </dd>\n </div>\n }\n </dl>\n }\n @default {\n <div class=\"expansion-info-list\">\n @for (\n expansionEntry of expansionSectionItems(\n item,\n expansionSection,\n i\n );\n track $index\n ) {\n <div class=\"expansion-info-item\">\n <div class=\"expansion-info-title\">\n {{ expansionInfoTitle(expansionEntry) }}\n </div>\n @if (expansionInfoValue(expansionEntry)) {\n <div class=\"expansion-info-value\">\n {{\n expansionInfoValue(expansionEntry)\n }}\n </div>\n }\n </div>\n }\n </div>\n }\n }\n } @else {\n <div class=\"expansion-empty\">\n {{\n expansionSection.emptyLabel ||\n \"Sem dados dispon\u00EDveis\"\n }}\n </div>\n }\n </section>\n }\n </div>\n </div>\n }\n </mat-list-item>\n @if (\n (config.layout?.dividers === \"all\" ||\n config.layout?.dividers === \"between\") &&\n i < section.items.length - 1\n ) {\n <mat-divider></mat-divider>\n }\n }\n }\n </mat-list>\n </ng-template>\n } @else if (isTilesVariant()) {\n <ng-container *ngTemplateOutlet=\"tilesVariant\"></ng-container>\n } @else {\n <ng-container *ngTemplateOutlet=\"cardsVariant\"></ng-container>\n }\n\n <!-- Cards variant -->\n <ng-template #cardsVariant>\n <div class=\"cards-grid\">\n @for (it of (items$ | async) ?? []; track $index; let i = $index) {\n <div\n class=\"item-card\"\n [ngClass]=\"itemRuleClass(it)\"\n [attr.style]=\"itemRuleStyle(it)\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"itemAriaLabel(it)\"\n (click)=\"onItemClick(it, i)\"\n (keydown.enter)=\"onItemClick(it, i)\"\n (keydown.space)=\"$event.preventDefault(); onItemClick(it, i)\"\n >\n <div class=\"list-item-content\">\n <div class=\"list-item-leading\">\n @if (leading(it); as lead) {\n @if (\n simpleRichContentNodes(lead, {\n imageAlt: config.templating?.leading?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (lead.type) {\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"lead.value\"\n [ngClass]=\"lead.class\"\n [color]=\"\n isThemeColor(lead.color) ? lead.color : undefined\n \"\n [style.cssText]=\"\n (lead.style ? lead.style + ';' : '') +\n iconStyle(lead.color)\n \"\n ></mat-icon>\n }\n @case (\"image\") {\n <div\n class=\"lead-image\"\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >\n <img\n [src]=\"lead.value\"\n [alt]=\"config.templating?.leading?.imageAlt || ''\"\n />\n @if (lead.badge?.value) {\n <mat-chip\n class=\"lead-badge\"\n [color]=\"\n isThemeColor(lead.badge?.color)\n ? lead.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (lead.badge?.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(\n lead.badge?.color,\n lead.badge?.variant\n )\n \"\n >{{ lead.badge?.value }}</mat-chip\n >\n }\n </div>\n }\n @case (\"text\") {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >{{ lead.value }}</span\n >\n }\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(lead.color) ? lead.color : undefined\n \"\n [ngClass]=\"\n (lead.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(lead.color, lead.variant)\"\n >{{ lead.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(lead);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, lead.value)\"\n [color]=\"ratingThemeColor(lead)\"\n [style.cssText]=\"ratingIconStyle(lead)\"\n ></mat-icon>\n }\n }\n @case (\"html\") {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n [innerHTML]=\"lead.value\"\n ></span>\n }\n @case (\"metric\") {\n <praxis-list-metric\n [metric]=\"lead\"\n ></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose\n [compose]=\"lead\"\n ></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"lead\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >{{ lead.value }}</span\n >\n }\n }\n }\n } @else {\n <span class=\"leading-placeholder\"></span>\n }\n </div>\n <div class=\"list-item-text\">\n <div\n class=\"primary\"\n [ngClass]=\"primary(it)?.class\"\n [style.cssText]=\"primary(it)?.style\"\n >\n @if (simpleRichContentNodes(primary(it)); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else if (primary(it)?.type === \"metric\") {\n <praxis-list-metric [metric]=\"primary(it)\"></praxis-list-metric>\n } @else if (primary(it)?.type === \"compose\") {\n <praxis-list-compose\n [compose]=\"primary(it)\"\n ></praxis-list-compose>\n } @else if (primary(it)?.type === \"component\") {\n <praxis-list-runtime-component\n [component]=\"primary(it)\"\n ></praxis-list-runtime-component>\n } @else {\n {{ primary(it)?.value }}\n }\n </div>\n @if (layoutLines > 1) {\n <div\n class=\"secondary\"\n [ngClass]=\"secondary(it)?.class\"\n [style.cssText]=\"secondary(it)?.style\"\n >\n @if (simpleRichContentNodes(secondary(it)); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else if (secondary(it)?.type === \"metric\") {\n <praxis-list-metric\n [metric]=\"secondary(it)\"\n ></praxis-list-metric>\n } @else if (secondary(it)?.type === \"compose\") {\n <praxis-list-compose\n [compose]=\"secondary(it)\"\n ></praxis-list-compose>\n } @else if (secondary(it)?.type === \"component\") {\n <praxis-list-runtime-component\n [component]=\"secondary(it)\"\n ></praxis-list-runtime-component>\n } @else {\n {{ secondary(it)?.value }}\n }\n </div>\n }\n @if (meta(it); as m) {\n @if (\n (config.templating?.metaPlacement === \"line\" &&\n (m?.type === \"text\" ||\n m?.type === \"date\" ||\n m?.type === \"currency\")) ||\n (layoutLines > 2 &&\n (m?.type === \"text\" ||\n m?.type === \"date\" ||\n m?.type === \"currency\"))\n ) {\n <div\n class=\"tertiary\"\n [ngClass]=\"m.class\"\n [style.cssText]=\"m.style\"\n >\n @if (config.templating?.metaPrefixIcon; as mpi) {\n <mat-icon [praxisIcon]=\"mpi\"></mat-icon>\n }\n {{ m.value }}\n </div>\n }\n }\n @if (featuresVisible()) {\n <div class=\"features\">\n @for (\n f of config.templating?.features || [];\n track $index;\n let fi = $index\n ) {\n <span\n class=\"feature\"\n [ngClass]=\"featureSemanticClass(it, f.expr, f.class)\"\n >\n @if (featureRichContentNodes(it, f); as featureNodes) {\n <praxis-rich-content [nodes]=\"featureNodes\"></praxis-rich-content>\n } @else {\n @if (f.icon && featuresMode() !== \"labels-only\") {\n <mat-icon [praxisIcon]=\"f.icon\"></mat-icon>\n }\n @if (featuresMode() !== \"icons-only\") {\n <span [ngClass]=\"f.class\" [style.cssText]=\"f.style\">\n @for (\n line of featureLabelLines(it, f.expr);\n track $index\n ) {\n <span class=\"feature-line\">{{ line }}</span>\n }\n </span>\n }\n }\n @if (\n featureProgressPercent(it, f.expr, f.class);\n as progress\n ) {\n <span class=\"feature-progress\" aria-hidden=\"true\">\n <span\n class=\"feature-progress__value\"\n [style.width.%]=\"progress\"\n ></span>\n </span>\n }\n </span>\n }\n </div>\n }\n @if (cardDetailSlots(it); as detailSlots) {\n @if (detailSlots.length) {\n <div class=\"card-detail-strip\">\n @for (\n detail of detailSlots;\n track detail.slot\n ) {\n <div\n class=\"card-detail\"\n [ngClass]=\"'card-detail--' + detail.slot\"\n >\n <span class=\"card-detail__label\">{{\n detail.label\n }}</span>\n <span class=\"card-detail__value\">\n <ng-container\n *ngTemplateOutlet=\"\n rowLayoutNode;\n context: {\n $implicit: detail.node,\n slot: detail.slot,\n imageAlt: rowLayoutImageAlt(detail.slot),\n }\n \"\n ></ng-container>\n </span>\n </div>\n }\n </div>\n }\n }\n </div>\n <div class=\"list-item-trailing\">\n @if (meta(it); as m) {\n @if (\n !(\n (config.templating?.metaPlacement === \"line\" ||\n layoutLines > 2) &&\n (m?.type === \"text\" ||\n m?.type === \"date\" ||\n m?.type === \"currency\")\n )\n ) {\n <div\n class=\"meta\"\n [ngClass]=\"m.class\"\n [style.cssText]=\"m.style\"\n >\n @if (\n simpleRichContentNodes(m, {\n slot: \"meta\",\n imageAlt: m.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (m.type) {\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(m.color) ? m.color : undefined\n \"\n [ngClass]=\"\n (m.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(m.color, m.variant)\"\n >{{ m.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(m);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, m.value)\"\n [color]=\"ratingThemeColor(m)\"\n [style.cssText]=\"ratingIconStyle(m)\"\n ></mat-icon>\n }\n }\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"m.value\"\n [color]=\"\n isThemeColor(m.color) ? m.color : undefined\n \"\n [style.cssText]=\"iconStyle(m.color)\"\n ></mat-icon>\n }\n @case (\"image\") {\n <span class=\"inline-image\"\n ><img [src]=\"m.value\" [alt]=\"m.imageAlt || ''\"\n /></span>\n }\n @case (\"html\") {\n <span [innerHTML]=\"m.value\"></span>\n }\n @case (\"metric\") {\n <praxis-list-metric\n [metric]=\"m\"\n ></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose\n [compose]=\"m\"\n ></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"m\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span>\n @if (config.templating?.metaPrefixIcon; as mpi2) {\n <mat-icon [praxisIcon]=\"mpi2\"></mat-icon>\n }\n {{ m.value }}</span\n >\n }\n }\n }\n </div>\n }\n }\n @if (trailing(it); as tr) {\n @if (\n (config.templating?.statusPosition || \"inline\") ===\n \"top-right\" &&\n (tr?.type === \"chip\" || tr?.type === \"icon\")\n ) {\n <div class=\"status-overlay\">\n @if (simpleRichContentNodes(tr); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @if (tr?.type === \"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(tr.color) ? tr.color : undefined\n \"\n [ngClass]=\"\n (tr.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(tr.color, tr.variant)\"\n >{{ tr.value }}</mat-chip\n >\n }\n @if (tr?.type === \"icon\") {\n <mat-icon\n [praxisIcon]=\"tr.value\"\n [color]=\"\n isThemeColor(tr.color) ? tr.color : undefined\n \"\n [style.cssText]=\"iconStyle(tr.color)\"\n ></mat-icon>\n }\n }\n </div>\n } @else {\n <div\n class=\"trailing\"\n [ngClass]=\"tr.class\"\n [style.cssText]=\"tr.style\"\n >\n @if (\n simpleRichContentNodes(tr, {\n imageAlt: config.templating?.trailing?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (tr.type) {\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(tr.color) ? tr.color : undefined\n \"\n [ngClass]=\"\n (tr.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(tr.color, tr.variant)\"\n >{{ tr.value }}</mat-chip\n >\n }\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"tr.value\"\n [color]=\"\n isThemeColor(tr.color) ? tr.color : undefined\n \"\n [style.cssText]=\"iconStyle(tr.color)\"\n ></mat-icon>\n }\n @case (\"image\") {\n <div\n class=\"lead-image\"\n [ngClass]=\"tr.class\"\n [style.cssText]=\"tr.style\"\n >\n <img\n [src]=\"tr.value\"\n [alt]=\"\n config.templating?.trailing?.imageAlt || ''\n \"\n />\n @if (tr.badge?.value) {\n <mat-chip\n class=\"lead-badge\"\n [color]=\"\n isThemeColor(tr.badge?.color)\n ? tr.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (tr.badge?.variant || 'filled') ===\n 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(\n tr.badge?.color,\n tr.badge?.variant\n )\n \"\n >{{ tr.badge?.value }}</mat-chip\n >\n }\n </div>\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(tr);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, tr.value)\"\n [color]=\"ratingThemeColor(tr)\"\n [style.cssText]=\"ratingIconStyle(tr)\"\n ></mat-icon>\n }\n }\n @case (\"html\") {\n <span [innerHTML]=\"tr.value\"></span>\n }\n @case (\"metric\") {\n <praxis-list-metric\n [metric]=\"tr\"\n ></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose\n [compose]=\"tr\"\n ></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"tr\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span>{{ tr.value }}</span>\n }\n }\n }\n </div>\n }\n }\n </div>\n </div>\n <div\n class=\"card-actions\"\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"onActionKeydown($event)\"\n (keydown.space)=\"onActionKeydown($event)\"\n >\n @for (\n action of visibleActions(it, \"trailing\");\n let aidx = $index;\n track action?.id ?? $index\n ) {\n @if ((action.kind || \"icon\") === \"icon\") {\n <button\n mat-icon-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n (click)=\"onActionClick($event, action.id, it, i)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, it, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(action.id, it, i),\n }\"\n >\n @if (isActionLoading(action.id, it, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n <mat-icon\n [praxisIcon]=\"action.icon\"\n [style.cssText]=\"iconStyle(action.color)\"\n ></mat-icon>\n }\n </button>\n } @else {\n @if (action.buttonVariant === \"stroked\") {\n <button\n mat-stroked-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'stroked')\"\n (click)=\"onActionClick($event, action.id, it, i)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, it, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(action.id, it, i),\n }\"\n >\n @if (isActionLoading(action.id, it, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (action.buttonVariant === \"raised\") {\n <button\n mat-raised-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'raised')\"\n (click)=\"onActionClick($event, action.id, it, i)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, it, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(action.id, it, i),\n }\"\n >\n @if (isActionLoading(action.id, it, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (\n !action.buttonVariant || action.buttonVariant === \"flat\"\n ) {\n <button\n mat-flat-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'flat')\"\n (click)=\"onActionClick($event, action.id, it, i)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, it, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(action.id, it, i),\n }\"\n >\n @if (isActionLoading(action.id, it, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n }\n }\n </div>\n </div>\n }\n </div>\n </ng-template>\n\n <!-- Tiles variant -->\n <ng-template #tilesVariant>\n <div class=\"tiles-grid\">\n @for (it of (items$ | async) ?? []; track $index; let i = $index) {\n <div\n class=\"item-tile\"\n [ngClass]=\"itemRuleClass(it)\"\n [attr.style]=\"itemRuleStyle(it)\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"itemAriaLabel(it)\"\n (click)=\"onItemClick(it, i)\"\n (keydown.enter)=\"onItemClick(it, i)\"\n (keydown.space)=\"$event.preventDefault(); onItemClick(it, i)\"\n >\n <div class=\"tile-media\">\n @if (leading(it); as lead) {\n @if (\n simpleRichContentNodes(lead, {\n imageAlt: config.templating?.leading?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (lead.type) {\n @case (\"image\") {\n <img\n [src]=\"lead.value\"\n [alt]=\"config.templating?.leading?.imageAlt || ''\"\n />\n }\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"lead.value\"\n [ngClass]=\"lead.class\"\n [color]=\"\n isThemeColor(lead.color) ? lead.color : undefined\n \"\n [style.cssText]=\"\n (lead.style ? lead.style + ';' : '') +\n iconStyle(lead.color)\n \"\n ></mat-icon>\n }\n @case (\"text\") {\n <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{\n lead.value\n }}</span>\n }\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(lead.color) ? lead.color : undefined\n \"\n [ngClass]=\"\n (lead.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(lead.color, lead.variant)\"\n >{{ lead.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">\n @for (\n _ of ratingRange(lead);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, lead.value)\"\n [color]=\"ratingThemeColor(lead)\"\n [style.cssText]=\"ratingIconStyle(lead)\"\n ></mat-icon>\n }\n </span>\n }\n @case (\"html\") {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n [innerHTML]=\"lead.value\"\n ></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"lead\"></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose\n [compose]=\"lead\"\n ></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"lead\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{\n lead.value\n }}</span>\n }\n }\n }\n }\n </div>\n\n @if (trailing(it); as tr) {\n @if (\n (config.templating?.statusPosition || \"inline\") ===\n \"top-right\" &&\n (tr?.type === \"chip\" || tr?.type === \"icon\")\n ) {\n <div class=\"tile-status\">\n @if (simpleRichContentNodes(tr); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @if (tr?.type === \"chip\") {\n <mat-chip\n [color]=\"isThemeColor(tr.color) ? tr.color : undefined\"\n [ngClass]=\"\n (tr.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(tr.color, tr.variant)\"\n >{{ tr.value }}</mat-chip\n >\n }\n @if (tr?.type === \"icon\") {\n <mat-icon\n [praxisIcon]=\"tr.value\"\n [color]=\"isThemeColor(tr.color) ? tr.color : undefined\"\n [style.cssText]=\"iconStyle(tr.color)\"\n ></mat-icon>\n }\n }\n </div>\n }\n }\n\n <div class=\"tile-body\">\n <div\n class=\"primary\"\n [ngClass]=\"primary(it)?.class\"\n [style.cssText]=\"primary(it)?.style\"\n >\n @if (simpleRichContentNodes(primary(it)); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else if (primary(it)?.type === \"metric\") {\n <praxis-list-metric [metric]=\"primary(it)\"></praxis-list-metric>\n } @else if (primary(it)?.type === \"compose\") {\n <praxis-list-compose\n [compose]=\"primary(it)\"\n ></praxis-list-compose>\n } @else if (primary(it)?.type === \"component\") {\n <praxis-list-runtime-component\n [component]=\"primary(it)\"\n ></praxis-list-runtime-component>\n } @else {\n {{ primary(it)?.value }}\n }\n </div>\n @if (layoutLines > 1) {\n <div\n class=\"secondary\"\n [ngClass]=\"secondary(it)?.class\"\n [style.cssText]=\"secondary(it)?.style\"\n >\n @if (simpleRichContentNodes(secondary(it)); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else if (secondary(it)?.type === \"metric\") {\n <praxis-list-metric\n [metric]=\"secondary(it)\"\n ></praxis-list-metric>\n } @else if (secondary(it)?.type === \"compose\") {\n <praxis-list-compose\n [compose]=\"secondary(it)\"\n ></praxis-list-compose>\n } @else if (secondary(it)?.type === \"component\") {\n <praxis-list-runtime-component\n [component]=\"secondary(it)\"\n ></praxis-list-runtime-component>\n } @else {\n {{ secondary(it)?.value }}\n }\n </div>\n }\n\n @if (meta(it); as m) {\n <div\n class=\"tile-meta\"\n [ngClass]=\"m.class\"\n [style.cssText]=\"m.style\"\n >\n @if (config.templating?.metaPrefixIcon; as mpi) {\n <mat-icon [praxisIcon]=\"mpi\"></mat-icon>\n }\n @if (\n simpleRichContentNodes(m, {\n slot: \"meta\",\n imageAlt: m.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (m.type) {\n @case (\"chip\") {\n <mat-chip\n [color]=\"isThemeColor(m.color) ? m.color : undefined\"\n [ngClass]=\"\n (m.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(m.color, m.variant)\"\n >{{ m.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(m);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, m.value)\"\n [color]=\"ratingThemeColor(m)\"\n [style.cssText]=\"ratingIconStyle(m)\"\n ></mat-icon>\n }\n }\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"m.value\"\n [color]=\"isThemeColor(m.color) ? m.color : undefined\"\n [style.cssText]=\"iconStyle(m.color)\"\n ></mat-icon>\n }\n @case (\"image\") {\n <span class=\"inline-image\"\n ><img [src]=\"m.value\" [alt]=\"m.imageAlt || ''\"\n /></span>\n }\n @case (\"html\") {\n <span [innerHTML]=\"m.value\"></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"m\"></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose\n [compose]=\"m\"\n ></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"m\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span>{{ m.value }}</span>\n }\n }\n }\n </div>\n }\n\n @if (featuresVisible()) {\n <div class=\"tiles-features\">\n @for (\n f of config.templating?.features || [];\n track $index;\n let fi = $index\n ) {\n <span\n class=\"feature\"\n [ngClass]=\"featureSemanticClass(it, f.expr, f.class)\"\n >\n @if (featureRichContentNodes(it, f); as featureNodes) {\n <praxis-rich-content [nodes]=\"featureNodes\"></praxis-rich-content>\n } @else {\n @if (f.icon && featuresMode() !== \"labels-only\") {\n <mat-icon [praxisIcon]=\"f.icon\"></mat-icon>\n }\n @if (featuresMode() !== \"icons-only\") {\n <span [ngClass]=\"f.class\" [style.cssText]=\"f.style\">\n @for (\n line of featureLabelLines(it, f.expr);\n track $index\n ) {\n <span class=\"feature-line\">{{ line }}</span>\n }\n </span>\n }\n }\n @if (\n featureProgressPercent(it, f.expr, f.class);\n as progress\n ) {\n <span class=\"feature-progress\" aria-hidden=\"true\">\n <span\n class=\"feature-progress__value\"\n [style.width.%]=\"progress\"\n ></span>\n </span>\n }\n </span>\n }\n </div>\n }\n\n @if (trailing(it); as tr2) {\n @if (\n !(\n (config.templating?.statusPosition || \"inline\") ===\n \"top-right\" &&\n (tr2?.type === \"chip\" || tr2?.type === \"icon\")\n )\n ) {\n <div\n class=\"tile-trailing\"\n [ngClass]=\"tr2.class\"\n [style.cssText]=\"tr2.style\"\n >\n @if (\n simpleRichContentNodes(tr2, {\n imageAlt: tr2.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (tr2.type) {\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(tr2.color) ? tr2.color : undefined\n \"\n [ngClass]=\"\n (tr2.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(tr2.color, tr2.variant)\"\n >{{ tr2.value }}</mat-chip\n >\n }\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"tr2.value\"\n [color]=\"\n isThemeColor(tr2.color) ? tr2.color : undefined\n \"\n [style.cssText]=\"iconStyle(tr2.color)\"\n ></mat-icon>\n }\n @case (\"image\") {\n <span class=\"inline-image\"\n ><img [src]=\"tr2.value\" [alt]=\"tr2.imageAlt || ''\"\n /></span>\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(tr2);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, tr2.value)\"\n [color]=\"ratingThemeColor(tr2)\"\n [style.cssText]=\"ratingIconStyle(tr2)\"\n ></mat-icon>\n }\n }\n @case (\"html\") {\n <span [innerHTML]=\"tr2.value\"></span>\n }\n @case (\"metric\") {\n <praxis-list-metric\n [metric]=\"tr2\"\n ></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose\n [compose]=\"tr2\"\n ></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"tr2\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span>{{ tr2.value }}</span>\n }\n }\n }\n </div>\n }\n }\n </div>\n\n @if ((visibleActions(it, \"trailing\") || []).length) {\n <div\n class=\"tile-actions\"\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"onActionKeydown($event)\"\n (keydown.space)=\"onActionKeydown($event)\"\n >\n @for (\n action of visibleActions(it, \"trailing\");\n let aidx = $index;\n track action?.id ?? $index\n ) {\n @if ((action.kind || \"icon\") === \"icon\") {\n <button\n mat-icon-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n (click)=\"onActionClick($event, action.id, it, i)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, it, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(action.id, it, i),\n }\"\n >\n @if (isActionLoading(action.id, it, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n <mat-icon\n [praxisIcon]=\"action.icon\"\n [style.cssText]=\"iconStyle(action.color)\"\n ></mat-icon>\n }\n </button>\n } @else {\n @if (action.buttonVariant === \"stroked\") {\n <button\n mat-stroked-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'stroked')\"\n (click)=\"onActionClick($event, action.id, it, i)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, it, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(action.id, it, i),\n }\"\n >\n @if (isActionLoading(action.id, it, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (action.buttonVariant === \"raised\") {\n <button\n mat-raised-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'raised')\"\n (click)=\"onActionClick($event, action.id, it, i)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, it, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(action.id, it, i),\n }\"\n >\n @if (isActionLoading(action.id, it, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (\n !action.buttonVariant || action.buttonVariant === \"flat\"\n ) {\n <button\n mat-flat-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'flat')\"\n (click)=\"onActionClick($event, action.id, it, i)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, it, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(action.id, it, i),\n }\"\n >\n @if (isActionLoading(action.id, it, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n }\n }\n </div>\n }\n </div>\n }\n </div>\n </ng-template>\n\n <!-- Remote data controls -->\n @if (config.dataSource?.resourcePath) {\n @if (config.ui?.showSort || (config.ui?.showSearch && config.ui?.searchField)) {\n <div class=\"list-remote-tools\">\n @if (config.ui?.showSort) {\n <mat-form-field class=\"paginator-sort\" appearance=\"outline\">\n <mat-label>Ordenar</mat-label>\n <mat-select (selectionChange)=\"onSortChange($event.value)\">\n @for (op of config.ui?.sortOptions || []; track $index) {\n <mat-option [value]=\"sortOptionValue(op)\">{{\n sortOptionLabel(op)\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n @if (config.ui?.showSearch && config.ui?.searchField) {\n <mat-form-field class=\"paginator-search\" appearance=\"outline\">\n <mat-label>{{\n config.ui?.searchPlaceholder || \"Buscar\"\n }}</mat-label>\n <input\n matInput\n type=\"search\"\n (input)=\"onSearchInput($any($event.target).value)\"\n />\n </mat-form-field>\n }\n </div>\n }\n @if (page$ | async; as ps) {\n @if (total$ | async; as total) {\n @if (config.ui?.showRange === false) {\n <div class=\"list-paginator-total\" aria-live=\"polite\">\n {{ remoteTotalLabel(total) }}\n </div>\n }\n <mat-paginator\n [length]=\"total || 0\"\n [pageIndex]=\"ps.pageNumber || 0\"\n [pageSize]=\"ps.pageSize || config.layout?.pageSize || 10\"\n [pageSizeOptions]=\"listPageSizeOptions()\"\n [selectConfig]=\"paginatorSelectConfig\"\n [showFirstLastButtons]=\"true\"\n [class.hide-range]=\"config.ui?.showRange === false\"\n (page)=\"onPageChange($event)\"\n >\n </mat-paginator>\n }\n }\n }\n </ng-template>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.praxis-list-root{--p-list-radius: var(--md-sys-shape-corner-medium, 16px);--p-list-shadow: var(--md-sys-elevation-level2);--p-list-border: 1px solid var(--md-sys-color-outline-variant);--p-list-blur: 10px;--p-list-surface: var(--md-sys-color-surface-container);--p-list-surface-low: var(--md-sys-color-surface);--p-list-surface-high: var( --md-sys-color-surface-container-high, var(--md-sys-color-surface-container) );--p-list-foreground: var(--md-sys-color-on-surface);--p-list-foreground-muted: var(--md-sys-color-on-surface-variant);--p-list-accent: var(--md-sys-color-primary);--p-list-accent-weak: var(--md-sys-color-primary-container);--p-list-item-surface: var( --md-sys-color-surface-container-low, var(--md-sys-color-surface) );--p-list-item-border: 1px solid var(--md-sys-color-outline-variant);--p-list-item-hover-surface: var(--md-sys-color-surface-container-low);--p-list-item-active-surface: var(--md-sys-color-surface-container);--p-list-item-selected-surface: var(--md-sys-color-surface-container);--p-list-grad-from: var(--md-sys-color-primary-container);--p-list-grad-to: var(--md-sys-color-tertiary-container);--p-list-grad-angle: 135deg;--p-list-grad-foreground: var( --md-sys-color-on-primary-container, var(--md-sys-color-on-surface) );--p-list-grad-foreground-muted: color-mix( in srgb, var(--p-list-grad-foreground) 76%, transparent );--p-list-leading-width: 36px;--p-list-trailing-min-width: 140px;--p-list-trailing-max-width: 320px;--p-list-selection-end-gutter: 56px;--p-list-item-gap: 10px;--p-list-item-padding-x: 14px;--p-list-item-padding-y: 10px;--p-list-item-stack-gap: 0px;--p-list-meta-size: .875rem;--p-list-meta-weight: 500;--p-list-chip-height: 22px;--p-list-chip-font-size: 12px;--p-list-trailing-padding-right: 8px;--p-list-action-button-height: 32px;--p-list-action-icon-button-size: 32px;--p-list-action-icon-size: 18px;--p-list-tile-minW: 240px;--p-list-tile-gap: 12px;--p-list-tile-padding: 12px;--p-list-tile-radius: 12px;--p-list-tile-media-radius: 12px;--p-list-tile-media-ratio: 1 / 1;--p-list-tile-hover-overlay: .04;--p-list-tile-press-overlay: .08;--p-list-tile-press-scale: .99}.list-assistant{display:flex;justify-content:flex-end;gap:8px;padding:4px 4px 8px}.action-loading{opacity:.65}.action-spinner{width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center}.action-loading .mat-mdc-button-touch-target,.action-loading .mdc-button__label{opacity:.7}.skin-elevated,.skin-outline,.skin-flat,.skin-neumorphism{--p-list-item-surface: var( --mdc-elevated-card-container-color, var(--p-list-surface) );--p-list-item-border: var(--p-list-border)}.skin-elevated .item-card,.skin-outline .item-card,.skin-flat .item-card,.skin-neumorphism .item-card{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border-radius:var(--p-list-radius);box-shadow:var(--p-list-shadow);border:var(--p-list-border)}.skin-elevated .item-tile,.skin-outline .item-tile,.skin-flat .item-tile,.skin-neumorphism .item-tile{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));box-shadow:var(--p-list-shadow);border:var(--p-list-border)}.skin-elevated .mat-mdc-list-item .list-item-content,.skin-outline .mat-mdc-list-item .list-item-content,.skin-flat .mat-mdc-list-item .list-item-content,.skin-neumorphism .mat-mdc-list-item .list-item-content{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border-radius:calc(var(--p-list-radius) * .75);box-shadow:var(--p-list-shadow);border:var(--p-list-item-border);color:var(--p-list-foreground)}.skin-outline{--p-list-shadow: none;--p-list-border: 1px solid var(--md-sys-color-outline)}.skin-flat{--p-list-shadow: none;--p-list-border: 1px solid var(--md-sys-color-outline-variant)}.skin-neumorphism{--p-list-shadow: var(--md-sys-elevation-level2);--p-list-border: 1px solid var(--md-sys-color-outline-variant);--p-list-radius: 1.25rem}.skin-pill-soft .item-card,.skin-pill-soft .item-tile{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border-radius:calc(var(--p-list-radius) * 1.3);box-shadow:var(--md-sys-elevation-level1);border:var(--p-list-border)}.skin-pill-soft .mat-mdc-list-item .list-item-content{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border-radius:calc(var(--p-list-radius) * 1.3);box-shadow:var(--md-sys-elevation-level1);border:var(--p-list-border);color:var(--p-list-foreground)}.skin-gradient-tile{--p-list-foreground: var(--p-list-grad-foreground);--p-list-foreground-muted: var(--p-list-grad-foreground-muted);--p-list-item-hover-surface: linear-gradient( var(--p-list-grad-angle), var(--p-list-grad-from), var(--p-list-grad-to) );--p-list-item-active-surface: linear-gradient( var(--p-list-grad-angle), var(--p-list-grad-from), var(--p-list-grad-to) );--p-list-item-selected-surface: linear-gradient( var(--p-list-grad-angle), var(--p-list-grad-from), var(--p-list-grad-to) )}.skin-gradient-tile .item-card,.skin-gradient-tile .item-tile{background:linear-gradient(var(--p-list-grad-angle),var(--p-list-grad-from),var(--p-list-grad-to));border-radius:var(--p-list-radius);color:var(--p-list-foreground)}.skin-gradient-tile .tile-media{background:color-mix(in srgb,var(--md-sys-color-surface) 74%,transparent);border-color:color-mix(in srgb,var(--p-list-foreground) 22%,transparent)}.skin-gradient-tile .tile-media mat-icon,.skin-gradient-tile .tile-meta,.skin-gradient-tile .tile-trailing,.skin-gradient-tile .feature,.skin-gradient-tile .secondary,.skin-gradient-tile .meta{color:var(--p-list-foreground-muted)}.skin-gradient-tile .tile-status,.skin-gradient-tile [data-rich-node-type=badge] .prx-rich-badge{color:var(--md-sys-color-on-secondary-container, var(--p-list-foreground));background:var(--md-sys-color-secondary-container, rgba(255, 255, 255, .72))}.skin-gradient-tile .mat-mdc-list-item .list-item-content{background:linear-gradient(var(--p-list-grad-angle),var(--p-list-grad-from),var(--p-list-grad-to));border-radius:var(--p-list-radius);color:var(--p-list-foreground)}.skin-glass .item-card,.skin-glass .item-tile{background:var(--md-sys-color-surface-container-low);border-radius:var(--p-list-radius);border:1px solid var(--md-sys-color-outline-variant);backdrop-filter:blur(var(--p-list-blur));-webkit-backdrop-filter:blur(var(--p-list-blur))}.skin-glass .mat-mdc-list-item .list-item-content{background:var(--md-sys-color-surface-container-low);border-radius:var(--p-list-radius);border:1px solid var(--md-sys-color-outline-variant);backdrop-filter:blur(var(--p-list-blur));-webkit-backdrop-filter:blur(var(--p-list-blur));color:var(--p-list-foreground)}.density-compact .mat-mdc-list-item,.density-compact .item-card{--p-list-item-padding-y: 6px;--p-list-item-padding-x: 10px;--p-list-item-gap: 8px}.density-comfortable .mat-mdc-list-item,.density-comfortable .item-card{--p-list-item-padding-y: 8px;--p-list-item-padding-x: 12px;--p-list-item-gap: 10px}.praxis-list-root.lines-3 .mat-mdc-list-item,.praxis-list-root.lines-3 .mat-mdc-list-option{min-height:68px}.density-compact{--p-list-tile-gap: 10px;--p-list-tile-padding: 10px;--p-list-tile-minW: 188px}.density-comfortable{--p-list-tile-gap: 12px;--p-list-tile-padding: 12px}.item-spacing-none{--p-list-item-stack-gap: 0px}.item-spacing-tight{--p-list-item-stack-gap: 4px;--p-list-tile-gap: 10px}.item-spacing-default{--p-list-item-stack-gap: 6px;--p-list-tile-gap: 12px}.item-spacing-relaxed{--p-list-item-stack-gap: 14px;--p-list-tile-gap: 20px}.list-item-content{box-sizing:border-box;display:grid;grid-template-columns:var(--p-list-leading-width) minmax(0,1fr) minmax(var(--p-list-trailing-min-width),min(34vw,var(--p-list-trailing-max-width)));align-items:center;gap:var(--p-list-item-gap);padding:var(--p-list-item-padding-y) var(--p-list-item-padding-x);width:100%;min-width:0;border-radius:calc(var(--p-list-radius) * .75);overflow:visible}.list-item-content--row-layout{--p-list-row-template-columns: var(--p-list-leading-width) minmax(0, 1fr) minmax(var(--p-list-trailing-min-width), max-content);--p-list-row-gap: var(--p-list-item-gap);--p-list-row-align-items: center;grid-template-columns:var(--p-list-row-template-columns);gap:var(--p-list-row-gap);align-items:var(--p-list-row-align-items)}.list-item-content--row-layout[role=button]{cursor:pointer}.list-item-content--row-layout[role=button]:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.list-row-slot{min-width:0;height:100%;align-self:stretch;display:flex;align-items:var(--p-list-row-slot-align-items, center);gap:8px}.list-item-content--row-layout .list-row-slot>praxis-rich-content,.list-item-content--row-layout .list-row-slot>praxis-list-metric,.list-item-content--row-layout .list-row-slot>praxis-list-compose,.list-item-content--row-layout .list-row-slot>praxis-list-runtime-component{min-width:0}.list-row-slot--leading{justify-content:center}.list-row-slot--primary,.list-row-slot--secondary{min-width:0}.list-row-slot--meta{color:var(--p-list-foreground);font-variant-numeric:tabular-nums}.list-row-slot--trailing{flex-wrap:wrap;justify-content:flex-end}.list-row-slot .primary,.list-row-slot .secondary,.list-row-slot .meta,.list-row-slot .trailing{min-width:0}.list-item-main-action{grid-column:1/3;display:grid;grid-template-columns:var(--p-list-leading-width) minmax(0,1fr);align-items:center;gap:var(--p-list-item-gap);min-width:0;padding:0;border:0;background:transparent;color:inherit;text-align:start;font:inherit;cursor:pointer}.list-item-main-action:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px;border-radius:12px}.list-item-leading{display:flex;align-items:center;justify-content:center;min-height:100%}.leading-placeholder{width:24px;height:24px;display:inline-block}.list-item-text{min-width:0;display:grid;align-content:center;gap:2px}.list-item-trailing{box-sizing:border-box;min-width:0;width:100%;max-width:var(--p-list-trailing-max-width);display:grid;align-self:center;align-items:center;justify-content:end;justify-items:end;align-content:center;gap:4px;text-align:end;padding-right:var(--p-list-trailing-padding-right)}.list-item-trailing>*{align-self:center}.list-item-content--row-layout .list-item-actions{justify-content:flex-end}.list-item-actions{width:max-content;justify-self:end;align-self:center;display:flex;align-items:center;justify-content:flex-end;gap:4px;flex-wrap:wrap;max-width:100%;min-height:var(--p-list-action-button-height)}.list-item-actions .mat-mdc-button-base{min-height:var(--p-list-action-button-height);height:var(--p-list-action-button-height);display:inline-flex;align-items:center}.list-item-actions .mat-mdc-icon-button{width:var(--p-list-action-icon-button-size);height:var(--p-list-action-icon-button-size);min-width:var(--p-list-action-icon-button-size);padding:calc((var(--p-list-action-icon-button-size) - var(--p-list-action-icon-size)) / 2);--mdc-icon-button-state-layer-size: var(--p-list-action-icon-button-size)}.list-item-actions .mat-mdc-icon-button mat-icon{width:var(--p-list-action-icon-size);height:var(--p-list-action-icon-size);font-size:var(--p-list-action-icon-size);line-height:var(--p-list-action-icon-size)}.expand-toggle{color:var(--p-list-foreground-muted)}.mat-mdc-list-item .list-item-content{background:var(--p-list-item-surface);border:var(--p-list-item-border);transition:background .15s ease,box-shadow .15s ease,transform .15s ease}.mat-mdc-list-item,.mat-mdc-list-option{height:auto;align-items:stretch;overflow:visible}.mat-mdc-list-option .mdc-list-item__end{align-self:center;margin-top:auto;margin-bottom:auto}.mat-mdc-list-option .list-item-content{padding-right:calc(var(--p-list-item-padding-x) + var(--p-list-selection-end-gutter))}.mat-mdc-list-item.mdc-list-item--with-one-line,.mat-mdc-list-item.mdc-list-item--with-two-lines,.mat-mdc-list-item.mdc-list-item--with-three-lines,.mat-mdc-list-item-single-line,.mat-mdc-list-item-two-line,.mat-mdc-list-item-three-line,.mat-mdc-list-option.mdc-list-item--with-one-line,.mat-mdc-list-option.mdc-list-item--with-two-lines,.mat-mdc-list-option.mdc-list-item--with-three-lines{height:auto!important}.mdc-list-item{overflow:visible}.density-compact .mat-mdc-list-item,.density-compact .mat-mdc-list-option{min-height:40px}.density-comfortable .mat-mdc-list-item,.density-comfortable .mat-mdc-list-option{min-height:46px}.praxis-list-root:not(.density-compact):not(.density-comfortable) .mat-mdc-list-item,.praxis-list-root:not(.density-compact):not(.density-comfortable) .mat-mdc-list-option{min-height:48px}.mat-mdc-list-item .mdc-list-item__content,.mat-mdc-list-option .mdc-list-item__content,.mat-mdc-list-item .mdc-list-item__primary-text,.mat-mdc-list-option .mdc-list-item__primary-text{display:block!important;width:100%!important;height:auto!important;padding:0!important;margin:0!important;overflow:visible!important;box-sizing:border-box}.mat-mdc-list-item,.mat-mdc-list-option{padding:0!important;box-sizing:border-box}.praxis-list-root mat-list>mat-list-item:not(:last-child),.praxis-list-root mat-selection-list>mat-list-option:not(:last-child){margin-bottom:var(--p-list-item-stack-gap)}.mat-mdc-list-item:hover .list-item-content,.mat-mdc-list-option:hover .list-item-content{background:var(--p-list-item-hover-surface)}.mat-mdc-list-item:active .list-item-content,.mat-mdc-list-option:active .list-item-content{background:var(--p-list-item-active-surface)}.mat-mdc-list-option.mdc-list-item--selected .list-item-content{background:var(--p-list-item-selected-surface);border-color:var(--md-sys-color-outline-variant)}.mat-mdc-list-option.cdk-keyboard-focused .list-item-content,.mat-mdc-list-option.cdk-focused .list-item-content{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.mat-mdc-list-item:focus-visible .list-item-content{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.praxis-list-root mat-selection-list>.mat-mdc-list-option{border-radius:calc(var(--p-list-radius) * .75);background:var(--p-list-item-surface);border:var(--p-list-item-border);color:var(--p-list-foreground);overflow:hidden;transition:background .15s ease,box-shadow .15s ease,border-color .15s ease}.skin-elevated mat-selection-list>.mat-mdc-list-option,.skin-outline mat-selection-list>.mat-mdc-list-option,.skin-flat mat-selection-list>.mat-mdc-list-option,.skin-neumorphism mat-selection-list>.mat-mdc-list-option{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border:var(--p-list-item-border);box-shadow:var(--p-list-shadow)}.skin-pill-soft mat-selection-list>.mat-mdc-list-option{border-radius:calc(var(--p-list-radius) * 1.3);background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border:var(--p-list-border);box-shadow:var(--md-sys-elevation-level1)}.skin-gradient-tile mat-selection-list>.mat-mdc-list-option{border-radius:var(--p-list-radius);background:linear-gradient(var(--p-list-grad-angle),var(--p-list-grad-from),var(--p-list-grad-to));color:var(--p-list-foreground)}.skin-glass mat-selection-list>.mat-mdc-list-option{border-radius:var(--p-list-radius);background:var(--md-sys-color-surface-container-low);border:1px solid var(--md-sys-color-outline-variant);backdrop-filter:blur(var(--p-list-blur));-webkit-backdrop-filter:blur(var(--p-list-blur))}.praxis-list-root mat-selection-list>.mat-mdc-list-option:hover{background:var(--p-list-item-hover-surface)}.praxis-list-root mat-selection-list>.mat-mdc-list-option:active{background:var(--p-list-item-active-surface)}.praxis-list-root mat-selection-list>.mat-mdc-list-option.mdc-list-item--selected{background:var(--p-list-item-selected-surface);border-color:var(--md-sys-color-outline-variant)}.praxis-list-root mat-selection-list>.mat-mdc-list-option .list-item-content{background:transparent;border:0;box-shadow:none;border-radius:0}.praxis-list-root mat-selection-list>.mat-mdc-list-option.cdk-keyboard-focused,.praxis-list-root mat-selection-list>.mat-mdc-list-option.cdk-focused{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.executive-inline-skin{--p-list-leading-width: 56px;--p-list-trailing-min-width: 232px;--p-list-item-padding-x: 14px;--p-list-item-padding-y: 8px;--p-list-item-gap: 10px;--p-list-surface: #f8fafb;--p-list-surface-low: #ffffff;--p-list-item-surface: #ffffff;--p-list-item-hover-surface: #f7fafc;--p-list-item-active-surface: #eef4f8;--p-list-item-selected-surface: #eef4f8;--p-list-foreground: #17324d;--p-list-foreground-muted: #72849a}.executive-inline-skin .list-item-content--row-layout.exec-row-layout{min-height:72px;padding:12px 20px;background:linear-gradient(180deg,#fff,#fbfcfd)!important;border:1px solid #dce5ec!important;border-radius:18px;box-shadow:0 1px 2px #0f223a08,0 4px 14px #0f223a08!important;color:#17324d!important}.executive-inline-skin .mat-mdc-list-item:hover .list-item-content--row-layout.exec-row-layout{background:linear-gradient(180deg,#fff,#f7fafc)!important}.executive-inline-skin .list-item-content--row-layout.exec-row-layout+.item-expansion{margin-left:10px;margin-right:10px}.executive-inline-skin .list-item-content--row-layout.exec-row-layout .list-row-slot{min-height:34px}.executive-inline-skin .list-row-slot--leading{justify-content:center}.executive-inline-skin .list-row-slot--identity{align-items:center;padding-right:4px}.executive-inline-skin .list-row-slot--balance,.executive-inline-skin .list-row-slot--limit,.executive-inline-skin .list-row-slot--risk,.executive-inline-skin .list-row-slot--alerts{justify-content:center;padding-inline:2px}.executive-inline-skin .list-row-slot--owner{justify-content:flex-start;padding-left:0}.executive-inline-skin .list-row-slot--actions{justify-content:flex-end;padding-left:0}.executive-inline-skin .list-row-slot--expand{justify-content:center}.executive-inline-skin .docs-expansion-leading{width:44px;height:44px;display:inline-flex;align-items:center;justify-content:center;border-radius:15px;background:linear-gradient(180deg,#edf4f9,#e6eef5);color:#1b5f86;font-size:1rem;font-weight:800;letter-spacing:.02em;box-shadow:0 1px 3px #1b5f861f;border:1px solid #dbe7f0}.executive-inline-skin .exec-balance-metric,.executive-inline-skin .exec-limit-metric,.executive-inline-skin .exec-risk-metric{min-width:0}.executive-inline-skin .exec-balance-metric .p-list-metric,.executive-inline-skin .exec-limit-metric .p-list-metric,.executive-inline-skin .exec-risk-metric .p-list-metric{gap:5px}.executive-inline-skin .exec-balance-metric .p-list-metric__label,.executive-inline-skin .exec-limit-metric .p-list-metric__label,.executive-inline-skin .exec-risk-metric .p-list-metric__label{font-size:.62rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#8090a1}.executive-inline-skin .exec-balance-metric .p-list-metric__value,.executive-inline-skin .exec-limit-metric .p-list-metric__value{font-size:.96rem;font-weight:800;letter-spacing:-.02em;color:#0d1f34;line-height:1.05}.executive-inline-skin .exec-limit-metric .p-list-metric__caption{font-size:.66rem;font-weight:700;color:#8090a1}.executive-inline-skin .exec-limit-metric .p-list-metric__progress{width:68px;justify-self:center;height:3px}.executive-inline-skin .exec-risk-metric{min-width:84px}.executive-inline-skin .exec-risk-metric .p-list-metric{justify-items:center}.executive-inline-skin .exec-risk-metric .p-list-metric__main{gap:6px}.executive-inline-skin .exec-risk-metric .p-list-metric__value{font-size:1.02rem;font-weight:800;letter-spacing:-.024em;line-height:1}.executive-inline-skin .exec-risk-metric .p-list-metric__progress{width:56px;justify-self:center;height:3px}.executive-inline-skin .exec-risk-metric .p-list-metric__caption{font-size:.7rem;font-weight:700;line-height:1.1}.executive-inline-skin .exec-risk-metric .p-list-metric__caption--below-progress{justify-self:center;margin-top:-1px}.executive-inline-skin .exec-risk-metric .p-list-metric__icon mat-icon{width:15px;height:15px;font-size:15px}.executive-inline-skin .list-item-actions{gap:6px;flex-wrap:nowrap}.executive-inline-skin .list-row-slot--actions .list-item-actions{justify-content:flex-end}.executive-inline-skin .list-item-actions button,.executive-inline-skin .expand-toggle{width:30px;height:30px;min-width:30px;border-radius:12px;color:#6d7f93!important}.executive-inline-skin .list-item-actions button mat-icon,.executive-inline-skin .expand-toggle mat-icon{width:18px;height:18px;font-size:18px}.executive-inline-skin .item-expansion{position:relative;margin-top:-10px;padding:22px 26px;background:linear-gradient(180deg,#fcfdfe,#f5f8fb);border-color:#e4eaf0;border-radius:0 0 22px 22px;box-shadow:inset 0 1px #ffffffdb}.lead-image{position:relative;width:96px;height:72px;border-radius:12px;overflow:hidden}.lead-image img{width:100%;height:100%;object-fit:cover;display:block}.lead-image .lead-badge{position:absolute;left:8px;bottom:8px}.inline-image{position:relative;width:20px;height:20px;border-radius:4px;overflow:hidden;display:inline-block;vertical-align:middle}.inline-image img{width:100%;height:100%;object-fit:cover;display:block}.inline-image .inline-badge{position:absolute;left:2px;bottom:2px}.model-media .lead-image{width:136px;height:96px;border-radius:16px}.model-media .list-item-content{gap:16px}.model-media .item-card{--p-list-leading-width: 136px}.model-hotel .lead-image{width:160px;height:110px;border-radius:18px}.model-hotel .item-card{--p-list-leading-width: 160px}.primary,.secondary{display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}.primary{-webkit-line-clamp:1;font-weight:600;color:var(--p-list-foreground)}.secondary{-webkit-line-clamp:1;color:var(--p-list-foreground-muted)}.lines-3 .tertiary{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.item-tile .primary{-webkit-line-clamp:2;font-size:.95rem;line-height:1.25rem}.item-tile .secondary{-webkit-line-clamp:2;font-size:.8rem;line-height:1.1rem}.tertiary{color:var(--p-list-foreground-muted)}.features{display:flex;flex-wrap:wrap;gap:12px;margin-top:6px;color:inherit}.feature{display:inline-flex;align-items:center;gap:6px}.feature-line{display:block}.feature-progress{display:block;width:88px;height:4px;margin-top:6px;border-radius:999px;background:#dbe5eb;overflow:hidden}.feature-progress__value{display:block;height:100%;border-radius:inherit;background:#2f9b69}.meta{color:var(--p-list-foreground);font-size:var(--p-list-meta-size);font-weight:var(--p-list-meta-weight);font-variant-numeric:tabular-nums;max-width:100%;min-width:0;display:inline-flex;align-items:center;justify-content:flex-end;gap:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.meta>span,.meta praxis-rich-content{min-width:0;display:inline-flex;align-items:center;justify-content:flex-end;gap:4px;line-height:1.15;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.meta .mat-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;line-height:1;vertical-align:middle}.trailing{color:var(--p-list-foreground-muted);margin-left:0;max-width:100%;min-width:0;display:inline-flex;justify-content:flex-end;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.trailing praxis-rich-content{min-width:0}:host ::ng-deep .meta praxis-rich-content .prx-rich-node[data-rich-node-type=badge],:host ::ng-deep .trailing praxis-rich-content .prx-rich-node[data-rich-node-type=badge],:host ::ng-deep .list-row-slot praxis-rich-content .prx-rich-node[data-rich-node-type=badge]{background:transparent;color:inherit}:host ::ng-deep .meta praxis-rich-content .prx-rich-node[data-rich-node-type=badge] .prx-rich-badge,:host ::ng-deep .trailing praxis-rich-content .prx-rich-node[data-rich-node-type=badge] .prx-rich-badge,:host ::ng-deep .list-row-slot praxis-rich-content .prx-rich-node[data-rich-node-type=badge] .prx-rich-badge{min-height:22px;padding:1px 9px;border:1px solid var(--p-list-chip-border-color, transparent);background:var(--p-list-chip-background, var(--md-sys-color-primary-container, #e8def8));color:var(--p-list-chip-color, var(--md-sys-color-on-primary-container, #21005d));line-height:18px}.section-header{font-size:.85rem;color:var(--p-list-foreground-muted);padding:8px 12px}.praxis-list-root mat-list,.praxis-list-root mat-selection-list{display:block;padding-bottom:12px}.praxis-list-root .mat-divider{margin-left:var(--p-list-item-padding-x);margin-right:var(--p-list-item-padding-x)}.item-expansion{margin:0 var(--p-list-item-padding-x) var(--p-list-item-stack-gap);padding:14px 16px;background:color-mix(in srgb,var(--p-list-surface) 88%,white 12%);border:1px solid var(--md-sys-color-outline-variant);border-radius:calc(var(--p-list-radius) * .75)}.item-expansion-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px}.expansion-section{min-width:0;display:grid;gap:10px;align-content:start}.expansion-section-title{font-size:.78rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--p-list-foreground-muted)}.expansion-info-list,.expansion-timeline,.expansion-key-value{display:grid;gap:10px}.expansion-info-item,.expansion-timeline-item{display:grid;gap:4px;padding:10px 12px;border-radius:14px;background:var(--p-list-item-surface);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent 28%)}.expansion-info-title,.expansion-timeline-title{font-size:.92rem;font-weight:600;color:var(--p-list-foreground)}.expansion-info-value,.expansion-timeline-meta,.expansion-timeline-description,.expansion-empty{font-size:.82rem;line-height:1.45;color:var(--p-list-foreground-muted)}.expansion-chip-list{display:flex;flex-wrap:wrap;gap:8px}.expansion-chip{background:var(--p-list-item-surface);border:1px solid var(--md-sys-color-outline-variant)}.expansion-key-value{margin:0}.expansion-key-value-row{display:grid;gap:4px;padding:10px 12px;border-radius:14px;background:var(--p-list-item-surface);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent 28%)}.expansion-key-value-row dt,.expansion-key-value-row dd{margin:0}.expansion-key-value-row dt{font-size:.78rem;font-weight:600;color:var(--p-list-foreground-muted)}.expansion-key-value-row dd{font-size:.9rem;color:var(--p-list-foreground)}.cards-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,max(340px,var(--p-list-tile-minW))),1fr));gap:max(var(--p-list-tile-gap),16px)}.tiles-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(var(--p-list-tile-minW),1fr));gap:var(--p-list-tile-gap)}.item-tile{position:relative;display:flex;flex-direction:column;gap:10px;padding:var(--p-list-tile-padding);border-radius:var(--p-list-tile-radius);background:var(--p-list-surface);border:var(--p-list-border);color:var(--p-list-foreground);cursor:pointer;min-height:160px;transition:box-shadow .16s ease-out,border-color .16s ease-out,transform .12s ease-out,background .16s ease-out}.item-tile:hover{box-shadow:var(--md-sys-elevation-level2);border-color:var(--md-sys-color-outline-variant);background:var(--p-list-item-hover-surface)}.item-tile:active{transform:scale(var(--p-list-tile-press-scale));background:var(--p-list-item-active-surface)}.item-tile:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.tile-media{width:100%;aspect-ratio:var(--p-list-tile-media-ratio);border-radius:var(--p-list-tile-media-radius);background:var(--p-list-item-surface);border:1px solid var(--md-sys-color-outline-variant);display:grid;place-items:center;overflow:hidden}.tile-media img{width:100%;height:100%;object-fit:cover;display:block}.tile-media mat-icon{font-size:28px;height:28px;width:28px;color:var(--p-list-foreground-muted)}.tile-body{display:grid;gap:6px;min-width:0}.tile-meta{display:inline-flex;align-items:center;gap:6px;font-size:.75rem;color:var(--p-list-foreground-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tile-trailing{font-size:.75rem;color:var(--p-list-foreground-muted);white-space:nowrap}.tiles-features{display:flex;flex-wrap:wrap;gap:8px;color:inherit}.tile-status{position:absolute;top:10px;right:10px}.tile-actions{position:absolute;top:8px;left:8px;display:flex;gap:4px;opacity:0;pointer-events:none;transform:translateY(-2px);transition:opacity .16s ease-out,transform .16s ease-out}.item-tile:hover .tile-actions,.item-tile:focus-visible .tile-actions,.item-tile:focus-within .tile-actions{opacity:1;pointer-events:auto;transform:translateY(0)}@media(hover:none){.tile-actions{opacity:1;pointer-events:auto;transform:none}}@media(max-width:600px){.tiles-grid{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}}@media(prefers-reduced-motion:reduce){.item-tile,.tile-actions{transition:none}.item-tile:active{transform:none}.item-card{transition:none}.item-card:active{transform:none}}.item-card{padding:max(var(--p-list-tile-padding),16px);display:flex;flex-direction:column;min-height:220px;position:relative;cursor:pointer;background:var(--p-list-surface);border:var(--p-list-border);border-radius:var(--p-list-tile-radius);box-shadow:var(--p-list-shadow);transition:box-shadow .16s ease-out,border-color .16s ease-out,transform .12s ease-out,background .16s ease-out;--p-list-leading-width: 96px;--p-list-trailing-min-width: 0px}.item-card .list-item-content{display:block;padding:0;min-width:0;overflow:hidden}.item-card .list-item-leading{position:absolute;top:18px;left:18px;min-height:0;z-index:1}.item-card .leading-placeholder{width:36px;height:36px}.item-card .leading-icon.mat-icon,.item-card .list-item-leading>mat-icon{width:36px;height:36px;line-height:36px;font-size:20px;display:inline-flex;align-items:center;justify-content:center;border-radius:14px;background:linear-gradient(145deg,color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),color-mix(in srgb,var(--md-sys-color-primary-container) 34%,transparent));color:var(--md-sys-color-primary)}.item-card .list-item-text{display:grid;gap:9px;padding-left:50px;width:100%;min-width:0;box-sizing:border-box}.item-card .primary{padding-right:min(50%,160px);font-size:1rem;font-weight:800;letter-spacing:-.01em;line-height:1.18;min-width:0;overflow-wrap:anywhere}.item-card .secondary{color:var(--p-list-foreground-muted);font-weight:600;line-height:1.35;min-width:0;overflow-wrap:anywhere}.item-card .features{display:flex;flex-direction:column;gap:8px;margin-top:2px}.item-card .feature{display:inline-flex;align-items:start;gap:8px;min-width:0;max-width:100%;overflow-wrap:anywhere}.item-card .feature mat-icon{margin-top:1px;color:var(--p-list-foreground-muted);opacity:.86}.item-card .list-item-trailing{min-width:var(--p-list-trailing-min-width);grid-column:1/-1;align-items:stretch;padding-right:0}.card-detail-strip{display:grid;grid-template-columns:repeat(auto-fit,minmax(148px,1fr));gap:10px;margin-top:16px}.card-detail{min-width:0;padding:11px 12px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 82%,transparent);border-radius:14px;background:linear-gradient(145deg,color-mix(in srgb,var(--md-sys-color-primary-container) 22%,transparent),color-mix(in srgb,var(--p-list-surface) 92%,transparent));box-shadow:inset 0 1px #ffffff14}.card-detail__label{display:block;margin-bottom:3px;color:var(--p-list-foreground-muted);font-size:.68rem;font-weight:700;letter-spacing:.08em;line-height:1.1;text-transform:uppercase}.card-detail__value{display:block;min-width:0;color:var(--p-list-foreground);font-size:.94rem;font-weight:800;line-height:1.2;overflow-wrap:normal;word-break:normal}.card-detail__value,.card-detail__value *{white-space:nowrap}.card-detail--balance .card-detail__value{color:var(--md-sys-color-primary)}.card-detail--limit .card-detail__value{color:var(--md-sys-color-error)}.card-detail--risk .card-detail__value{color:var(--md-sys-color-tertiary, var(--p-list-foreground))}.item-card:hover{box-shadow:var(--md-sys-elevation-level3);border-color:var(--md-sys-color-outline-variant);background:var(--p-list-item-hover-surface)}.item-card:active{transform:scale(var(--p-list-tile-press-scale));background:var(--p-list-item-active-surface)}.item-card:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.card-actions{display:flex;align-items:center;justify-content:flex-end;gap:4px;padding:6px 8px 4px;border-top:1px solid var(--md-sys-color-outline-variant);margin-top:auto}.status-overlay{position:absolute;top:14px;right:14px;max-width:min(42%,148px);overflow:hidden;pointer-events:none}.status-overlay .mat-mdc-chip{pointer-events:auto;width:100%;min-width:0;max-width:100%}.status-overlay .mdc-evolution-chip__text-label,.status-overlay .mat-mdc-chip-action-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.leading-icon.mat-icon{width:36px;height:36px;line-height:36px;font-size:20px;display:inline-flex;align-items:center;justify-content:center;border-radius:999px;background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.mat-mdc-chip{--mdc-chip-container-height: var(--p-list-chip-height);font-size:var(--p-list-chip-font-size)}.meta .mat-icon{font-size:18px;height:18px;width:18px}.features{overflow:hidden}@keyframes shimmer{0%{background-position:-468px 0}to{background-position:468px 0}}.skeleton{animation-duration:1.2s;animation-fill-mode:forwards;animation-iteration-count:infinite;animation-name:shimmer;animation-timing-function:linear;background:linear-gradient(to right,var(--md-sys-color-surface-container-low) 8%,var(--md-sys-color-surface-container) 18%,var(--md-sys-color-surface-container-low) 33%);background-size:800px 104px;position:relative}.skeleton-avatar{width:32px;height:32px;border-radius:50%}.skeleton-line{height:10px;margin:6px 0;border-radius:6px}.skeleton-chip{width:48px;height:18px;border-radius:999px}.chip-outlined.mat-mdc-chip{background:transparent!important;border:1px solid currentColor}.w-60{width:60%}.w-40{width:40%}.list-remote-tools{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-top:14px;padding:14px 4px 8px;border-top:1px solid var(--md-sys-color-outline-variant);position:relative;z-index:1;background:var(--p-list-surface-low);border-radius:calc(var(--p-list-radius) * .75)}.list-export-tools{display:flex;justify-content:flex-end;margin:0 0 12px}.list-export-tools button{border-radius:8px}.list-export-tools .mat-icon{margin-right:6px}.paginator-sort{width:180px}.paginator-search{min-width:220px}.mat-mdc-paginator{margin-top:14px;border-top:1px solid var(--md-sys-color-outline-variant);background:var(--p-list-surface-low);color:var(--p-list-foreground)}:host ::ng-deep .mat-mdc-paginator .mat-mdc-paginator-container{min-height:40px;padding:4px 8px;gap:6px}:host ::ng-deep .mat-mdc-paginator .mat-mdc-paginator-page-size-select{margin-left:8px;width:92px}:host ::ng-deep .mat-mdc-paginator .mat-mdc-paginator-page-size-select .mat-mdc-text-field-wrapper{--mdc-outlined-text-field-container-color: color-mix(in srgb, var(--p-list-surface-low) 92%, var(--md-sys-color-on-surface) 8%);--mdc-outlined-text-field-input-text-color: var(--p-list-foreground);min-height:40px;padding-inline:12px 8px;background:color-mix(in srgb,var(--p-list-surface-low) 92%,var(--md-sys-color-on-surface) 8%)!important;background-color:color-mix(in srgb,var(--p-list-surface-low) 92%,var(--md-sys-color-on-surface) 8%)!important;color:var(--p-list-foreground)!important}:host ::ng-deep .mat-mdc-paginator .mat-mdc-paginator-page-size-select .mat-mdc-form-field-infix{min-height:40px;padding-block:8px;width:auto}:host ::ng-deep .mat-mdc-paginator .mat-mdc-paginator-page-size-select .mat-mdc-select-trigger{min-height:24px;padding-right:2px;color:var(--p-list-foreground)}:host ::ng-deep .mat-mdc-paginator .mat-mdc-paginator-range-label,:host ::ng-deep .mat-mdc-paginator .mat-mdc-select-value-text{font-size:12px;color:var(--p-list-foreground-muted)}.list-paginator-total{margin-top:14px;padding:4px 8px 0;font-size:12px;color:var(--p-list-foreground-muted)}:host ::ng-deep .mat-mdc-paginator.hide-range .mat-mdc-paginator-range-label{display:none}::ng-deep .praxis-list-paginator-select-panel.mat-mdc-select-panel{--mat-select-panel-background-color: var(--md-sys-color-surface-container, #fff);background:var(--md-sys-color-surface-container, #fff)!important;background-color:var(--md-sys-color-surface-container, #fff)!important;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #d7dbe5) 70%,transparent);box-shadow:0 12px 28px color-mix(in srgb,var(--md-sys-color-shadow, #000) 28%,transparent)}::ng-deep .praxis-list-paginator-select-panel .mat-mdc-option{--mat-option-label-text-color: var(--md-sys-color-on-surface, #111827);color:var(--md-sys-color-on-surface, #111827)!important}::ng-deep .praxis-list-paginator-select-panel .mat-mdc-option:hover:not(.mdc-list-item--disabled),::ng-deep .praxis-list-paginator-select-panel .mat-mdc-option.mat-mdc-option-active{--mat-option-hover-state-layer-color: color-mix(in srgb, var(--md-sys-color-primary, #2f7cff) 14%, transparent);background:color-mix(in srgb,var(--md-sys-color-primary, #2f7cff) 14%,transparent)!important;color:var(--md-sys-color-on-surface, #111827)!important}.muted{color:var(--p-list-foreground-muted)}@media(max-width:720px){.list-item-content{grid-template-columns:var(--p-list-leading-width) minmax(0,1fr)}.list-item-trailing{grid-column:1/-1;padding-right:0;align-items:flex-start;text-align:start}.item-expansion{margin-left:0;margin-right:0}.list-remote-tools{align-items:stretch}.paginator-sort,.paginator-search{width:100%;min-width:0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i3.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i3.MatSelectionList, selector: "mat-selection-list", inputs: ["color", "compareWith", "multiple", "hideSingleSelectionIndicator", "disabled"], outputs: ["selectionChange"], exportAs: ["matSelectionList"] }, { kind: "component", type: i3.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "component", type: i3.MatListOption, selector: "mat-list-option", inputs: ["togglePosition", "checkboxPosition", "color", "value", "selected"], outputs: ["selectedChange"], exportAs: ["matListOption"] }, { kind: "component", type: i3.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i5.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i5$1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i5$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i5$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i8$1.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i10$1.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "ngmodule", type: MatSnackBarModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.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: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i12.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: PraxisAiAssistantShellComponent, selector: "praxis-ai-assistant-shell", inputs: ["labels", "mode", "state", "contextItems", "attachments", "messages", "quickReplies", "prompt", "statusText", "errorText", "testIdPrefix", "panelTestId", "submitTestId", "applyTestId", "primaryAction", "secondaryActions", "governanceActions", "busy", "canSubmit", "canApply", "submitOnEnter", "showAttachAction", "enablePastedAttachments", "enableFileAttachments", "attachmentAccept", "attachmentMultiple", "draggable", "resizable", "minWidth", "minHeight", "margin", "layout"], outputs: ["promptChange", "submitPrompt", "apply", "retryTurn", "cancelTurn", "shellAction", "close", "attach", "attachmentsPasted", "attachmentsSelected", "removeAttachment", "messageAction", "editMessage", "resendMessage", "quickReply", "layoutChange"] }, { kind: "component", type: ExecutiveBadgeComponent, selector: "praxis-executive-badge", inputs: ["ariaLabel", "label", "variant"] }, { kind: "component", type: ExecutiveAlertsComponent, selector: "praxis-executive-alerts", inputs: ["alerts"] }, { kind: "component", type: ExecutiveOwnerComponent, selector: "praxis-executive-owner", inputs: ["name"] }, { kind: "component", type: PraxisRichContent, selector: "praxis-rich-content", inputs: ["document", "nodes", "context", "hostCapabilities", "layout", "rootClassName"] }, { kind: "component", type: PraxisListMetricComponent, selector: "praxis-list-metric", inputs: ["metric"] }, { kind: "component", type: PraxisListComposeComponent, selector: "praxis-list-compose", inputs: ["compose"] }, { kind: "component", type: PraxisListRuntimeComponentComponent, selector: "praxis-list-runtime-component", inputs: ["component"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13880
|
+
], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"praxis-list-root\"\n [ngClass]=\"skinClasses\"\n [attr.data-skin-scope]=\"skinScopeId\"\n [attr.aria-label]=\"config.a11y?.ariaLabel || null\"\n [attr.aria-labelledby]=\"config.a11y?.ariaLabelledBy || null\"\n>\n @if (inlineCss) {\n <style [attr.nonce]=\"cspNonce || null\" [textContent]=\"inlineCss\"></style>\n }\n\n @if (enableCustomization) {\n <div class=\"list-assistant\">\n <button\n mat-mini-fab\n type=\"button\"\n color=\"primary\"\n (click)=\"openConfigEditor()\"\n [matTooltip]=\"configEditorLabel()\"\n [attr.aria-label]=\"configEditorLabel()\"\n data-testid=\"praxis-list-open-config-editor\"\n >\n <mat-icon [praxisIcon]=\"'edit'\"></mat-icon>\n </button>\n <button\n mat-mini-fab\n type=\"button\"\n color=\"primary\"\n (click)=\"openAiAssistant()\"\n matTooltip=\"Copiloto semantico Praxis\"\n aria-label=\"Abrir copiloto semantico Praxis da lista\"\n data-testid=\"praxis-list-ai-assistant-trigger\"\n >\n <mat-icon [praxisIcon]=\"'auto_awesome'\"></mat-icon>\n </button>\n </div>\n }\n\n @if (aiAssistantOpen && aiAssistantViewState) {\n <praxis-ai-assistant-shell\n [labels]=\"aiAssistantLabels\"\n [mode]=\"aiAssistantViewState.mode\"\n [state]=\"aiAssistantViewState.state\"\n [contextItems]=\"aiAssistantViewState.contextItems\"\n [attachments]=\"aiAssistantViewState.attachments\"\n [messages]=\"aiAssistantViewState.messages\"\n [quickReplies]=\"aiAssistantViewState.quickReplies\"\n [prompt]=\"aiAssistantPrompt\"\n [statusText]=\"aiAssistantViewState.statusText\"\n [errorText]=\"aiAssistantViewState.errorText\"\n [busy]=\"aiAssistantViewState.state === 'processing' || aiAssistantViewState.state === 'applying'\"\n [canApply]=\"aiAssistantViewState.canApply\"\n [layout]=\"aiAssistantLayout\"\n testIdPrefix=\"praxis-list-ai-assistant\"\n panelTestId=\"praxis-list-ai-assistant-panel\"\n submitTestId=\"praxis-list-ai-assistant-submit\"\n applyTestId=\"praxis-list-ai-assistant-apply\"\n (promptChange)=\"onAiAssistantPromptChange($event)\"\n (submitPrompt)=\"onAiAssistantSubmit($event)\"\n (apply)=\"onAiAssistantApply()\"\n (retryTurn)=\"onAiAssistantRetry()\"\n (cancelTurn)=\"onAiAssistantCancel()\"\n (quickReply)=\"onAiAssistantQuickReply($event)\"\n (editMessage)=\"onAiAssistantEditMessage($event)\"\n (resendMessage)=\"onAiAssistantResendMessage($event)\"\n (layoutChange)=\"onAiAssistantLayoutChange($event)\"\n (close)=\"closeAiAssistant()\"\n ></praxis-ai-assistant-shell>\n }\n\n <!-- Skeleton while loading -->\n @if ((loading$ | async) && hasSkeleton()) {\n @if (isListVariant()) {\n <mat-list>\n @for (_ of skeletonItems(); track $index; let i = $index) {\n <mat-list-item>\n <div class=\"list-item-content\">\n <div class=\"list-item-leading\">\n <div class=\"skeleton skeleton-avatar\"></div>\n </div>\n <div class=\"list-item-text\">\n <div class=\"skeleton skeleton-line w-60\"></div>\n @if (layoutLines > 1) {\n <div class=\"skeleton skeleton-line w-40\"></div>\n }\n </div>\n <div class=\"list-item-trailing\">\n <div class=\"skeleton skeleton-chip\"></div>\n </div>\n </div>\n </mat-list-item>\n }\n </mat-list>\n } @else {\n <div class=\"cards-grid\">\n @for (_ of skeletonItems(); track $index; let i = $index) {\n <div class=\"item-card\">\n <div class=\"list-item-content\">\n <div class=\"list-item-leading\">\n <div class=\"skeleton skeleton-avatar\"></div>\n </div>\n <div class=\"list-item-text\">\n <div class=\"skeleton skeleton-line w-60\"></div>\n @if (layoutLines > 1) {\n <div class=\"skeleton skeleton-line w-40\"></div>\n }\n </div>\n <div class=\"list-item-trailing\">\n <div class=\"skeleton skeleton-chip\"></div>\n </div>\n </div>\n </div>\n }\n </div>\n }\n } @else {\n <ng-container *ngTemplateOutlet=\"notLoading\"></ng-container>\n }\n\n <ng-template #notLoading>\n @if (config.export?.enabled) {\n <div class=\"list-export-tools\">\n <button\n mat-stroked-button\n type=\"button\"\n [matMenuTriggerFor]=\"listExportMenu\"\n [attr.aria-label]=\"t('export.button', 'Exportar')\"\n [disabled]=\"exportBusy\"\n [attr.aria-busy]=\"exportBusy\"\n >\n <mat-icon [praxisIcon]=\"'download'\"></mat-icon>\n {{ t(\"export.button\", \"Exportar\") }}\n </button>\n <span class=\"cdk-visually-hidden\" aria-live=\"polite\">\n {{ exportStatusMessage }}\n </span>\n <mat-menu #listExportMenu=\"matMenu\">\n @for (format of listExportFormats(); track format) {\n <button\n mat-menu-item\n type=\"button\"\n (click)=\"onExportAction(format)\"\n >\n <mat-icon [praxisIcon]=\"exportIcon(format)\"></mat-icon>\n {{ format.toUpperCase() }}\n </button>\n }\n </mat-menu>\n </div>\n }\n\n <!-- Empty state -->\n @if (items$ | async; as all) {\n @if (all.length === 0) {\n <div class=\"section-header\">\n @if (emptyStateTemplate(); as empty) {\n @if (\n simpleRichContentNodes(empty, {\n imageAlt: config.templating?.emptyState?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (empty.type) {\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"empty.value\"\n [ngClass]=\"empty.class\"\n [color]=\"isThemeColor(empty.color) ? empty.color : undefined\"\n [style.cssText]=\"\n (empty.style ? empty.style + ';' : '') +\n iconStyle(empty.color)\n \"\n ></mat-icon>\n }\n @case (\"image\") {\n <span\n class=\"inline-image\"\n [ngClass]=\"empty.class\"\n [style.cssText]=\"empty.style\"\n >\n <img\n [src]=\"empty.value\"\n [alt]=\"config.templating?.emptyState?.imageAlt || ''\"\n />\n @if (empty.badge?.value) {\n <mat-chip\n class=\"inline-badge\"\n [color]=\"\n isThemeColor(empty.badge?.color)\n ? empty.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (empty.badge?.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(empty.badge?.color, empty.badge?.variant)\n \"\n >{{ empty.badge?.value }}</mat-chip\n >\n }\n </span>\n }\n @case (\"chip\") {\n <mat-chip\n [color]=\"isThemeColor(empty.color) ? empty.color : undefined\"\n [ngClass]=\"[\n empty.class || '',\n (empty.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : '',\n ]\"\n [style.cssText]=\"\n chipStyle(empty.color, empty.variant) +\n (empty.style ? ';' + empty.style : '')\n \"\n >{{ empty.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n <span [ngClass]=\"empty.class\" [style.cssText]=\"empty.style\">\n @for (\n _ of ratingRange(empty);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, empty.value)\"\n [color]=\"ratingThemeColor(empty)\"\n [style.cssText]=\"ratingIconStyle(empty)\"\n ></mat-icon>\n }\n </span>\n }\n @case (\"html\") {\n <span\n [ngClass]=\"empty.class\"\n [style.cssText]=\"empty.style\"\n [innerHTML]=\"empty.value\"\n ></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"empty\"></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose [compose]=\"empty\"></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"empty\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span [ngClass]=\"empty.class\" [style.cssText]=\"empty.style\">{{\n empty.value\n }}</span>\n }\n }\n }\n }\n </div>\n }\n }\n\n <ng-template\n #rowLayoutNode\n let-node\n let-slot=\"slot\"\n let-imageAlt=\"imageAlt\"\n >\n @if (simpleRichContentNodes(node, { slot: slot, imageAlt: imageAlt }); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (node?.type) {\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"node?.value\"\n [ngClass]=\"node?.class\"\n [color]=\"isThemeColor(node?.color) ? node?.color : undefined\"\n [style.cssText]=\"\n ((node?.style || '') ? node.style + ';' : '') +\n iconStyle(node?.color)\n \"\n ></mat-icon>\n }\n @case (\"image\") {\n <span\n class=\"inline-image\"\n [ngClass]=\"node?.class\"\n [style.cssText]=\"node?.style\"\n >\n <img [src]=\"node?.value\" [alt]=\"node?.imageAlt || imageAlt || ''\" />\n @if (node?.badge?.value) {\n <mat-chip\n class=\"inline-badge\"\n [color]=\"\n isThemeColor(node?.badge?.color)\n ? node?.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (node?.badge?.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(node?.badge?.color, node?.badge?.variant)\n \"\n >\n {{ node?.badge?.value }}\n </mat-chip>\n }\n </span>\n }\n @case (\"chip\") {\n <mat-chip\n [color]=\"isThemeColor(node?.color) ? node?.color : undefined\"\n [ngClass]=\"[\n node?.class || '',\n (node?.variant || 'filled') === 'outlined' ? 'chip-outlined' : '',\n ]\"\n [style.cssText]=\"\n chipStyle(node?.color, node?.variant) +\n (node?.style ? ';' + node.style : '')\n \"\n >\n {{ node?.value }}\n </mat-chip>\n }\n @case (\"rating\") {\n <span [ngClass]=\"node?.class\" [style.cssText]=\"node?.style\">\n @for (_ of ratingRange(node); track $index; let idx = $index) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, node?.value)\"\n [color]=\"ratingThemeColor(node)\"\n [style.cssText]=\"ratingIconStyle(node)\"\n ></mat-icon>\n }\n </span>\n }\n @case (\"html\") {\n <span\n [ngClass]=\"node?.class\"\n [style.cssText]=\"node?.style\"\n [innerHTML]=\"node?.value\"\n ></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"node\"></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose [compose]=\"node\"></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"node\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span [ngClass]=\"node?.class\" [style.cssText]=\"node?.style\">\n @if (slot === \"meta\" && config.templating?.metaPrefixIcon; as mpi) {\n <mat-icon [praxisIcon]=\"mpi\"></mat-icon>\n }\n {{ node?.value }}\n </span>\n }\n }\n }\n </ng-template>\n\n <ng-template\n #listRowLayout\n let-item\n let-index=\"index\"\n let-sectionKey=\"sectionKey\"\n let-clickable=\"clickable\"\n >\n <div\n class=\"list-item-content\"\n [ngClass]=\"rowLayoutItemClass(item)\"\n [attr.style]=\"rowLayoutItemStyle(item)\"\n [attr.role]=\"clickable ? 'button' : null\"\n [attr.tabindex]=\"clickable ? '0' : null\"\n [attr.aria-label]=\"clickable ? itemAriaLabel(item) : null\"\n [attr.aria-expanded]=\"\n clickable && expansionOwnedByRow() ? isExpanded(item, index) : null\n \"\n [attr.aria-controls]=\"\n clickable && expansionOwnedByRow()\n ? expandRegionId(item, index)\n : null\n \"\n (click)=\"clickable ? onRowActivate(item, index, sectionKey) : null\"\n (keydown.enter)=\"clickable ? onRowActivate(item, index, sectionKey) : null\"\n (keydown.space)=\"\n onRowSpaceActivate($event, clickable, item, index, sectionKey)\n \"\n >\n @for (column of rowLayoutColumns(); track rowLayoutTrackColumn($index, column)) {\n <div\n [attr.data-row-slot]=\"column.slot\"\n [ngClass]=\"rowLayoutColumnClass(column)\"\n [attr.style]=\"rowLayoutColumnStyle(column)\"\n >\n @if (column.slot === \"leading\") {\n @if (rowLayoutSlot(item, column.slot); as lead) {\n <ng-container\n *ngTemplateOutlet=\"\n rowLayoutNode;\n context: {\n $implicit: lead,\n slot: column.slot,\n imageAlt: rowLayoutImageAlt(column.slot),\n }\n \"\n ></ng-container>\n } @else {\n <span class=\"leading-placeholder\"></span>\n }\n } @else if (column.slot === \"trailing\") {\n @if (rowLayoutSlot(item, column.slot); as trailingNode) {\n <ng-container\n *ngTemplateOutlet=\"\n rowLayoutNode;\n context: {\n $implicit: trailingNode,\n slot: column.slot,\n imageAlt: rowLayoutImageAlt(column.slot),\n }\n \"\n ></ng-container>\n }\n @if (showExpandIcon(\"trailing\")) {\n <button\n mat-icon-button\n type=\"button\"\n class=\"expand-toggle\"\n [attr.aria-label]=\"expandToggleAriaLabel(item, index)\"\n [attr.aria-expanded]=\"\n expansionOwnedByIcon() ? isExpanded(item, index) : null\n \"\n [attr.aria-controls]=\"\n expansionOwnedByIcon()\n ? expandRegionId(item, index)\n : null\n \"\n (click)=\"onExpandToggle($event, item, index)\"\n >\n <mat-icon\n [praxisIcon]=\"\n isExpanded(item, index) ? 'expand_less' : 'expand_more'\n \"\n ></mat-icon>\n </button>\n }\n @if ((visibleActions(item, \"trailing\") || []).length) {\n <div\n class=\"list-item-actions\"\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"onActionKeydown($event)\"\n (keydown.space)=\"onActionKeydown($event)\"\n >\n @for (\n action of visibleActions(item, \"trailing\");\n let aidx = $index;\n track action?.id ?? $index\n ) {\n @if ((action.kind || \"icon\") === \"icon\") {\n <button\n mat-icon-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n (click)=\"onActionClick($event, action.id, item, index)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, index)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n index\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, index)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n <mat-icon\n [praxisIcon]=\"action.icon\"\n [style.cssText]=\"iconStyle(action.color)\"\n ></mat-icon>\n }\n </button>\n } @else {\n @if (action.buttonVariant === \"stroked\") {\n <button\n mat-stroked-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'stroked')\"\n (click)=\"onActionClick($event, action.id, item, index)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, index)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n index\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, index)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (action.buttonVariant === \"raised\") {\n <button\n mat-raised-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'raised')\"\n (click)=\"onActionClick($event, action.id, item, index)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, index)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n index\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, index)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (!action.buttonVariant || action.buttonVariant === \"flat\") {\n <button\n mat-flat-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'flat')\"\n (click)=\"onActionClick($event, action.id, item, index)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, index)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n index\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, index)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n }\n }\n </div>\n }\n } @else if (column.slot === \"actions\") {\n @if ((visibleActions(item, \"actions\") || []).length) {\n <div\n class=\"list-item-actions\"\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"onActionKeydown($event)\"\n (keydown.space)=\"onActionKeydown($event)\"\n >\n @for (\n action of visibleActions(item, \"actions\");\n let aidx = $index;\n track action?.id ?? $index\n ) {\n @if ((action.kind || \"icon\") === \"icon\") {\n <button\n mat-icon-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n (click)=\"onActionClick($event, action.id, item, index)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, index)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n index\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, index)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n <mat-icon\n [praxisIcon]=\"action.icon\"\n [style.cssText]=\"iconStyle(action.color)\"\n ></mat-icon>\n }\n </button>\n } @else {\n @if (action.buttonVariant === \"stroked\") {\n <button\n mat-stroked-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'stroked')\"\n (click)=\"onActionClick($event, action.id, item, index)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, index)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n index\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, index)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (action.buttonVariant === \"raised\") {\n <button\n mat-raised-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'raised')\"\n (click)=\"onActionClick($event, action.id, item, index)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, index)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n index\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, index)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (!action.buttonVariant || action.buttonVariant === \"flat\") {\n <button\n mat-flat-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'flat')\"\n (click)=\"onActionClick($event, action.id, item, index)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, index)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n index\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, index)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n }\n }\n </div>\n }\n } @else if (column.slot === \"expand\") {\n @if (showExpandIcon(\"expand\")) {\n <button\n mat-icon-button\n type=\"button\"\n class=\"expand-toggle\"\n [attr.aria-label]=\"expandToggleAriaLabel(item, index)\"\n [attr.aria-expanded]=\"\n expansionOwnedByIcon() ? isExpanded(item, index) : null\n \"\n [attr.aria-controls]=\"\n expansionOwnedByIcon()\n ? expandRegionId(item, index)\n : null\n \"\n (click)=\"onExpandToggle($event, item, index)\"\n >\n <mat-icon\n [praxisIcon]=\"\n isExpanded(item, index) ? 'expand_less' : 'expand_more'\n \"\n ></mat-icon>\n </button>\n }\n } @else if (rowLayoutSlot(item, column.slot); as slotNode) {\n <ng-container\n *ngTemplateOutlet=\"\n rowLayoutNode;\n context: {\n $implicit: slotNode,\n slot: column.slot,\n imageAlt: rowLayoutImageAlt(column.slot),\n }\n \"\n ></ng-container>\n }\n </div>\n }\n </div>\n </ng-template>\n\n <!-- List variant -->\n @if (isListVariant()) {\n <!-- Selection list -->\n @if (isSelectionEnabled()) {\n <mat-selection-list\n [multiple]=\"config.selection?.mode === 'multiple'\"\n [formControl]=\"boundControl\"\n (selectionChange)=\"onSelectionChange($event)\"\n >\n @for (\n section of sections$ | async;\n track trackBySection($index, section)\n ) {\n @if (section.key) {\n <div class=\"section-header mat-subheader\">\n @if (sectionHeaderTemplate(section.key); as sh) {\n @if (\n simpleRichContentNodes(sh, {\n imageAlt: config.templating?.sectionHeader?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (sh.type) {\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"sh.value\"\n [ngClass]=\"sh.class\"\n [color]=\"isThemeColor(sh.color) ? sh.color : undefined\"\n [style.cssText]=\"\n (sh.style ? sh.style + ';' : '') + iconStyle(sh.color)\n \"\n ></mat-icon>\n }\n @case (\"image\") {\n <span\n class=\"inline-image\"\n [ngClass]=\"sh.class\"\n [style.cssText]=\"sh.style\"\n >\n <img\n [src]=\"sh.value\"\n [alt]=\"\n config.templating?.sectionHeader?.imageAlt || ''\n \"\n />\n @if (sh.badge?.value) {\n <mat-chip\n class=\"inline-badge\"\n [color]=\"\n isThemeColor(sh.badge?.color)\n ? sh.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (sh.badge?.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(sh.badge?.color, sh.badge?.variant)\n \"\n >{{ sh.badge?.value }}</mat-chip\n >\n }\n </span>\n }\n @case (\"chip\") {\n <mat-chip\n [color]=\"isThemeColor(sh.color) ? sh.color : undefined\"\n [ngClass]=\"[\n sh.class || '',\n (sh.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : '',\n ]\"\n [style.cssText]=\"\n chipStyle(sh.color, sh.variant) +\n (sh.style ? ';' + sh.style : '')\n \"\n >{{ sh.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n <span [ngClass]=\"sh.class\" [style.cssText]=\"sh.style\">\n @for (\n _ of ratingRange(sh);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, sh.value)\"\n [color]=\"ratingThemeColor(sh)\"\n [style.cssText]=\"ratingIconStyle(sh)\"\n ></mat-icon>\n }\n </span>\n }\n @case (\"html\") {\n <span\n [ngClass]=\"sh.class\"\n [style.cssText]=\"sh.style\"\n [innerHTML]=\"sh.value\"\n ></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"sh\"></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose [compose]=\"sh\"></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"sh\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span [ngClass]=\"sh.class\" [style.cssText]=\"sh.style\">{{\n sh.value\n }}</span>\n }\n }\n }\n }\n </div>\n }\n @for (\n item of section.items;\n track trackByItem($index, item);\n let i = $index\n ) {\n <mat-list-option\n [value]=\"item\"\n [attr.aria-label]=\"itemAriaLabel(item)\"\n >\n @if (hasRowLayoutGrid()) {\n <ng-container\n *ngTemplateOutlet=\"\n listRowLayout;\n context: {\n $implicit: item,\n index: i,\n sectionKey: section.key || undefined,\n clickable: false,\n }\n \"\n ></ng-container>\n } @else {\n <div\n class=\"list-item-content\"\n [ngClass]=\"itemRuleClass(item)\"\n [attr.style]=\"itemRuleStyle(item)\"\n >\n <div class=\"list-item-leading\">\n @if (leading(item); as lead) {\n @if (\n simpleRichContentNodes(lead, {\n imageAlt: config.templating?.leading?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (lead.type) {\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"lead.value\"\n [ngClass]=\"lead.class\"\n [color]=\"\n isThemeColor(lead.color) ? lead.color : undefined\n \"\n [style.cssText]=\"\n (lead.style ? lead.style + ';' : '') +\n iconStyle(lead.color)\n \"\n ></mat-icon>\n }\n @case (\"image\") {\n <div\n class=\"lead-image\"\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >\n <img\n [src]=\"lead.value\"\n [alt]=\"config.templating?.leading?.imageAlt || ''\"\n />\n @if (lead.badge?.value) {\n <mat-chip\n class=\"lead-badge\"\n [color]=\"\n isThemeColor(lead.badge?.color)\n ? lead.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (lead.badge?.variant || 'filled') ===\n 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(\n lead.badge?.color,\n lead.badge?.variant\n )\n \"\n >{{ lead.badge?.value }}</mat-chip\n >\n }\n </div>\n }\n @case (\"text\") {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >{{ lead.value }}</span\n >\n }\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(lead.color) ? lead.color : undefined\n \"\n [ngClass]=\"\n (lead.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(lead.color, lead.variant)\n \"\n >{{ lead.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >\n @for (\n _ of ratingRange(lead);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, lead.value)\"\n [color]=\"ratingThemeColor(lead)\"\n [style.cssText]=\"ratingIconStyle(lead)\"\n ></mat-icon>\n }\n </span>\n }\n @case (\"html\") {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n [innerHTML]=\"lead.value\"\n ></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"lead\"></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose\n [compose]=\"lead\"\n ></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"lead\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >{{ lead.value }}</span\n >\n }\n }\n }\n } @else {\n <span class=\"leading-placeholder\"></span>\n }\n </div>\n <div class=\"list-item-text\">\n <div\n class=\"primary\"\n [ngClass]=\"primary(item)?.class\"\n [style.cssText]=\"primary(item)?.style\"\n >\n @if (simpleRichContentNodes(primary(item)); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else if (primary(item)?.type === \"metric\") {\n <praxis-list-metric\n [metric]=\"primary(item)\"\n ></praxis-list-metric>\n } @else if (primary(item)?.type === \"compose\") {\n <praxis-list-compose\n [compose]=\"primary(item)\"\n ></praxis-list-compose>\n } @else if (primary(item)?.type === \"component\") {\n <praxis-list-runtime-component\n [component]=\"primary(item)\"\n ></praxis-list-runtime-component>\n } @else {\n {{ primary(item)?.value }}\n }\n </div>\n @if (layoutLines > 1) {\n <div\n class=\"secondary\"\n [ngClass]=\"secondary(item)?.class\"\n [style.cssText]=\"secondary(item)?.style\"\n >\n @if (simpleRichContentNodes(secondary(item)); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else if (secondary(item)?.type === \"metric\") {\n <praxis-list-metric\n [metric]=\"secondary(item)\"\n ></praxis-list-metric>\n } @else if (secondary(item)?.type === \"compose\") {\n <praxis-list-compose\n [compose]=\"secondary(item)\"\n ></praxis-list-compose>\n } @else if (secondary(item)?.type === \"component\") {\n <praxis-list-runtime-component\n [component]=\"secondary(item)\"\n ></praxis-list-runtime-component>\n } @else {\n {{ secondary(item)?.value }}\n }\n </div>\n }\n @if (meta(item); as m) {\n @if (\n (config.templating?.metaPlacement === \"line\" &&\n (m?.type === \"text\" ||\n m?.type === \"date\" ||\n m?.type === \"currency\")) ||\n (layoutLines > 2 &&\n (m?.type === \"text\" ||\n m?.type === \"date\" ||\n m?.type === \"currency\"))\n ) {\n <div\n class=\"tertiary\"\n [ngClass]=\"m.class\"\n [style.cssText]=\"m.style\"\n >\n @if (config.templating?.metaPrefixIcon; as mpi) {\n <mat-icon [praxisIcon]=\"mpi\"></mat-icon>\n }\n {{ m.value }}\n </div>\n }\n }\n @if (featuresVisible()) {\n <div class=\"features\">\n @for (\n f of config.templating?.features || [];\n track $index;\n let fi = $index\n ) {\n <span\n class=\"feature\"\n [ngClass]=\"\n featureSemanticClass(item, f.expr, f.class)\n \"\n >\n @if (featureRichContentNodes(item, f); as featureNodes) {\n <praxis-rich-content [nodes]=\"featureNodes\"></praxis-rich-content>\n } @else {\n @if (f.icon && featuresMode() !== \"labels-only\") {\n <mat-icon [praxisIcon]=\"f.icon\"></mat-icon>\n }\n @if (featuresMode() !== \"icons-only\") {\n <span\n [ngClass]=\"f.class\"\n [style.cssText]=\"f.style\"\n >\n @for (\n line of featureLabelLines(item, f.expr);\n track $index\n ) {\n <span class=\"feature-line\">{{ line }}</span>\n }\n </span>\n }\n }\n @if (\n featureProgressPercent(item, f.expr, f.class);\n as progress\n ) {\n <span class=\"feature-progress\" aria-hidden=\"true\">\n <span\n class=\"feature-progress__value\"\n [style.width.%]=\"progress\"\n ></span>\n </span>\n }\n </span>\n }\n </div>\n }\n </div>\n <div class=\"list-item-trailing\">\n @if (meta(item); as m) {\n @if (\n !(\n (config.templating?.metaPlacement === \"line\" ||\n layoutLines > 2) &&\n (m?.type === \"text\" ||\n m?.type === \"date\" ||\n m?.type === \"currency\")\n )\n ) {\n <div\n class=\"meta\"\n [ngClass]=\"m.class\"\n [style.cssText]=\"m.style\"\n >\n @if (simpleRichContentNodes(m, { slot: 'meta' }); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (m.type) {\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(m.color) ? m.color : undefined\n \"\n [ngClass]=\"\n (m.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(m.color, m.variant)\"\n >{{ m.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(m);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, m.value)\"\n [color]=\"ratingThemeColor(m)\"\n [style.cssText]=\"ratingIconStyle(m)\"\n ></mat-icon>\n }\n }\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"m.value\"\n [color]=\"\n isThemeColor(m.color) ? m.color : undefined\n \"\n [style.cssText]=\"iconStyle(m.color)\"\n ></mat-icon>\n }\n @case (\"image\") {\n <span class=\"inline-image\"\n ><img [src]=\"m.value\" [alt]=\"m.imageAlt || ''\"\n /></span>\n }\n @case (\"html\") {\n <span [innerHTML]=\"m.value\"></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"m\"></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose\n [compose]=\"m\"\n ></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"m\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span>\n @if (\n config.templating?.metaPrefixIcon;\n as mpi2\n ) {\n <mat-icon [praxisIcon]=\"mpi2\"></mat-icon>\n }\n {{ m.value }}</span\n >\n }\n }\n }\n </div>\n }\n }\n @if (trailing(item); as tr) {\n <div\n class=\"trailing\"\n [ngClass]=\"tr.class\"\n [style.cssText]=\"tr.style\"\n >\n @if (\n simpleRichContentNodes(tr, {\n imageAlt: config.templating?.trailing?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (tr.type) {\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(tr.color) ? tr.color : undefined\n \"\n [ngClass]=\"\n (tr.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(tr.color, tr.variant)\"\n >{{ tr.value }}</mat-chip\n >\n }\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"tr.value\"\n [color]=\"\n isThemeColor(tr.color) ? tr.color : undefined\n \"\n [style.cssText]=\"iconStyle(tr.color)\"\n ></mat-icon>\n }\n @case (\"image\") {\n <div\n class=\"lead-image\"\n [ngClass]=\"tr.class\"\n [style.cssText]=\"tr.style\"\n >\n <img\n [src]=\"tr.value\"\n [alt]=\"\n config.templating?.trailing?.imageAlt || ''\n \"\n />\n @if (tr.badge?.value) {\n <mat-chip\n class=\"lead-badge\"\n [color]=\"\n isThemeColor(tr.badge?.color)\n ? tr.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (tr.badge?.variant || 'filled') ===\n 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(\n tr.badge?.color,\n tr.badge?.variant\n )\n \"\n >{{ tr.badge?.value }}</mat-chip\n >\n }\n </div>\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(tr);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, tr.value)\"\n [color]=\"ratingThemeColor(tr)\"\n [style.cssText]=\"ratingIconStyle(tr)\"\n ></mat-icon>\n }\n }\n @case (\"html\") {\n @if (item.alertsData !== undefined && item.ownerName) {\n <!-- Component-based rendering for executive trailing -->\n <span class=\"exec-trailing-shell\">\n <praxis-executive-alerts [alerts]=\"item.alertsData\"></praxis-executive-alerts>\n <praxis-executive-owner [name]=\"item.ownerName\"></praxis-executive-owner>\n </span>\n } @else {\n <span [innerHTML]=\"tr.value\"></span>\n }\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"tr\"></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose\n [compose]=\"tr\"\n ></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"tr\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span>{{ tr.value }}</span>\n }\n }\n }\n </div>\n }\n @if (showExpandIcon(\"trailing\")) {\n <button\n mat-icon-button\n type=\"button\"\n class=\"expand-toggle\"\n [attr.aria-label]=\"expandToggleAriaLabel(item, i)\"\n [attr.aria-expanded]=\"\n expansionOwnedByIcon() ? isExpanded(item, i) : null\n \"\n [attr.aria-controls]=\"\n expansionOwnedByIcon()\n ? expandRegionId(item, i)\n : null\n \"\n (click)=\"onExpandToggle($event, item, i)\"\n >\n <mat-icon\n [praxisIcon]=\"\n isExpanded(item, i) ? 'expand_less' : 'expand_more'\n \"\n ></mat-icon>\n </button>\n }\n @if ((visibleActions(item, \"trailing\") || []).length) {\n <div\n class=\"list-item-actions\"\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"onActionKeydown($event)\"\n (keydown.space)=\"onActionKeydown($event)\"\n >\n @for (\n action of visibleActions(item, \"trailing\");\n let aidx = $index;\n track action?.id ?? $index\n ) {\n @if ((action.kind || \"icon\") === \"icon\") {\n <button\n mat-icon-button\n [color]=\"\n isThemeColor(action.color)\n ? action.color\n : undefined\n \"\n (click)=\"\n onActionClick($event, action.id, item, i)\n \"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n i\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n <mat-icon\n [praxisIcon]=\"action.icon\"\n [style.cssText]=\"iconStyle(action.color)\"\n ></mat-icon>\n }\n </button>\n } @else {\n @if (action.buttonVariant === \"stroked\") {\n <button\n mat-stroked-button\n [color]=\"\n isThemeColor(action.color)\n ? action.color\n : undefined\n \"\n [style.cssText]=\"\n buttonStyle(action.color, 'stroked')\n \"\n (click)=\"\n onActionClick($event, action.id, item, i)\n \"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n i\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (action.buttonVariant === \"raised\") {\n <button\n mat-raised-button\n [color]=\"\n isThemeColor(action.color)\n ? action.color\n : undefined\n \"\n [style.cssText]=\"\n buttonStyle(action.color, 'raised')\n \"\n (click)=\"\n onActionClick($event, action.id, item, i)\n \"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n i\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (\n !action.buttonVariant ||\n action.buttonVariant === \"flat\"\n ) {\n <button\n mat-flat-button\n [color]=\"\n isThemeColor(action.color)\n ? action.color\n : undefined\n \"\n [style.cssText]=\"\n buttonStyle(action.color, 'flat')\n \"\n (click)=\"\n onActionClick($event, action.id, item, i)\n \"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n i\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n }\n }\n </div>\n }\n </div>\n </div>\n }\n @if (isExpandable() && isExpanded(item, i)) {\n <div\n class=\"item-expansion\"\n [id]=\"expandRegionId(item, i)\"\n role=\"region\"\n [attr.aria-label]=\"expansionRegionAriaLabel(item)\"\n >\n <div class=\"item-expansion-grid\">\n @for (\n expansionSection of expansionSections(item);\n track expansionSection.id\n ) {\n <section\n class=\"expansion-section\"\n [attr.data-section-type]=\"expansionSection.type\"\n >\n @if (expansionSection.title) {\n <div class=\"expansion-section-title\">\n {{ expansionSection.title }}\n </div>\n }\n @if (\n expansionSectionHasContent(\n item,\n expansionSection,\n i\n )\n ) {\n @switch (expansionSection.type) {\n @case (\"chip-list\") {\n <div class=\"expansion-chip-list\">\n @for (\n expansionEntry of expansionSectionItems(\n item,\n expansionSection,\n i\n );\n track $index\n ) {\n <mat-chip class=\"expansion-chip\">{{\n expansionItemLabel(expansionEntry)\n }}</mat-chip>\n }\n </div>\n }\n @case (\"timeline\") {\n <div class=\"expansion-timeline\">\n @for (\n expansionEntry of expansionSectionItems(\n item,\n expansionSection,\n i\n );\n track $index\n ) {\n <div class=\"expansion-timeline-item\">\n <div class=\"expansion-timeline-title\">\n {{\n expansionTimelineTitle(expansionEntry)\n }}\n </div>\n @if (\n expansionTimelineMeta(expansionEntry)\n ) {\n <div class=\"expansion-timeline-meta\">\n {{\n expansionTimelineMeta(\n expansionEntry\n )\n }}\n </div>\n }\n @if (\n expansionTimelineDescription(\n expansionEntry\n )\n ) {\n <div\n class=\"expansion-timeline-description\"\n >\n {{\n expansionTimelineDescription(\n expansionEntry\n )\n }}\n </div>\n }\n </div>\n }\n </div>\n }\n @case (\"key-value\") {\n <dl class=\"expansion-key-value\">\n @for (\n expansionEntry of expansionSectionItems(\n item,\n expansionSection,\n i\n );\n track $index\n ) {\n <div class=\"expansion-key-value-row\">\n @if (\n expansionKeyValueKey(expansionEntry)\n ) {\n <dt>\n {{\n expansionKeyValueKey(expansionEntry)\n }}\n </dt>\n }\n <dd>\n {{\n expansionKeyValueValue(expansionEntry)\n }}\n </dd>\n </div>\n }\n </dl>\n }\n @default {\n <div class=\"expansion-info-list\">\n @for (\n expansionEntry of expansionSectionItems(\n item,\n expansionSection,\n i\n );\n track $index\n ) {\n <div class=\"expansion-info-item\">\n <div class=\"expansion-info-title\">\n {{ expansionInfoTitle(expansionEntry) }}\n </div>\n @if (expansionInfoValue(expansionEntry)) {\n <div class=\"expansion-info-value\">\n {{\n expansionInfoValue(expansionEntry)\n }}\n </div>\n }\n </div>\n }\n </div>\n }\n }\n } @else {\n <div class=\"expansion-empty\">\n {{\n expansionSection.emptyLabel ||\n \"Sem dados dispon\u00EDveis\"\n }}\n </div>\n }\n </section>\n }\n </div>\n </div>\n }\n </mat-list-option>\n @if (\n (config.layout?.dividers === \"all\" ||\n config.layout?.dividers === \"between\") &&\n i < section.items.length - 1\n ) {\n <mat-divider></mat-divider>\n }\n }\n }\n </mat-selection-list>\n } @else {\n <ng-container *ngTemplateOutlet=\"readList\"></ng-container>\n }\n\n <!-- Read-only list -->\n <ng-template #readList>\n <mat-list>\n @for (\n section of sections$ | async;\n track trackBySection($index, section);\n let sidx = $index\n ) {\n @if (section.key) {\n <div class=\"section-header mat-subheader\">\n @if (sectionHeaderTemplate(section.key); as sh) {\n @if (\n simpleRichContentNodes(sh, {\n imageAlt: config.templating?.sectionHeader?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (sh.type) {\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"sh.value\"\n [ngClass]=\"sh.class\"\n [color]=\"isThemeColor(sh.color) ? sh.color : undefined\"\n [style.cssText]=\"\n (sh.style ? sh.style + ';' : '') + iconStyle(sh.color)\n \"\n ></mat-icon>\n }\n @case (\"image\") {\n <span\n class=\"inline-image\"\n [ngClass]=\"sh.class\"\n [style.cssText]=\"sh.style\"\n >\n <img\n [src]=\"sh.value\"\n [alt]=\"\n config.templating?.sectionHeader?.imageAlt || ''\n \"\n />\n @if (sh.badge?.value) {\n <mat-chip\n class=\"inline-badge\"\n [color]=\"\n isThemeColor(sh.badge?.color)\n ? sh.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (sh.badge?.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(sh.badge?.color, sh.badge?.variant)\n \"\n >{{ sh.badge?.value }}</mat-chip\n >\n }\n </span>\n }\n @case (\"chip\") {\n <mat-chip\n [color]=\"isThemeColor(sh.color) ? sh.color : undefined\"\n [ngClass]=\"[\n sh.class || '',\n (sh.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : '',\n ]\"\n [style.cssText]=\"\n chipStyle(sh.color, sh.variant) +\n (sh.style ? ';' + sh.style : '')\n \"\n >{{ sh.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n <span [ngClass]=\"sh.class\" [style.cssText]=\"sh.style\">\n @for (\n _ of ratingRange(sh);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, sh.value)\"\n [color]=\"ratingThemeColor(sh)\"\n [style.cssText]=\"ratingIconStyle(sh)\"\n ></mat-icon>\n }\n </span>\n }\n @case (\"html\") {\n <span\n [ngClass]=\"sh.class\"\n [style.cssText]=\"sh.style\"\n [innerHTML]=\"sh.value\"\n ></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"sh\"></praxis-list-metric>\n }\n @default {\n <span [ngClass]=\"sh.class\" [style.cssText]=\"sh.style\">{{\n sh.value\n }}</span>\n }\n }\n }\n }\n </div>\n }\n @for (\n item of section.items;\n track trackByItem($index, item);\n let i = $index\n ) {\n <mat-list-item [attr.data-item-status]=\"item?.status || null\">\n @if (hasRowLayoutGrid()) {\n <ng-container\n *ngTemplateOutlet=\"\n listRowLayout;\n context: {\n $implicit: item,\n index: i,\n sectionKey: section.key || undefined,\n clickable: true,\n }\n \"\n ></ng-container>\n } @else {\n <div\n class=\"list-item-content\"\n [ngClass]=\"itemRuleClass(item)\"\n [attr.style]=\"itemRuleStyle(item)\"\n >\n <button\n type=\"button\"\n class=\"list-item-main-action\"\n [attr.aria-label]=\"itemAriaLabel(item)\"\n [attr.aria-expanded]=\"\n expansionOwnedByRow() ? isExpanded(item, i) : null\n \"\n [attr.aria-controls]=\"\n expansionOwnedByRow() ? expandRegionId(item, i) : null\n \"\n (click)=\"onRowActivate(item, i, section.key || undefined)\"\n >\n <div class=\"list-item-leading\">\n @if (leading(item); as lead) {\n @if (\n simpleRichContentNodes(lead, {\n imageAlt: config.templating?.leading?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (lead.type) {\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"lead.value\"\n [ngClass]=\"lead.class\"\n [color]=\"\n isThemeColor(lead.color)\n ? lead.color\n : undefined\n \"\n [style.cssText]=\"\n (lead.style ? lead.style + ';' : '') +\n iconStyle(lead.color)\n \"\n ></mat-icon>\n }\n @case (\"image\") {\n <div\n class=\"lead-image\"\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >\n <img\n [src]=\"lead.value\"\n [alt]=\"\n config.templating?.leading?.imageAlt || ''\n \"\n />\n @if (lead.badge?.value) {\n <mat-chip\n class=\"lead-badge\"\n [color]=\"\n isThemeColor(lead.badge?.color)\n ? lead.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (lead.badge?.variant || 'filled') ===\n 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(\n lead.badge?.color,\n lead.badge?.variant\n )\n \"\n >{{ lead.badge?.value }}</mat-chip\n >\n }\n </div>\n }\n @case (\"text\") {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >{{ lead.value }}</span\n >\n }\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(lead.color)\n ? lead.color\n : undefined\n \"\n [ngClass]=\"\n (lead.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(lead.color, lead.variant)\n \"\n >{{ lead.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(lead);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, lead.value)\"\n [color]=\"ratingThemeColor(lead)\"\n [style.cssText]=\"ratingIconStyle(lead)\"\n ></mat-icon>\n }\n }\n @case (\"html\") {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n [innerHTML]=\"lead.value\"\n ></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"lead\"></praxis-list-metric>\n }\n @default {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >{{ lead.value }}</span\n >\n }\n }\n }\n } @else {\n <span class=\"leading-placeholder\"></span>\n }\n </div>\n <div class=\"list-item-text\">\n <div\n class=\"primary\"\n [ngClass]=\"primary(item)?.class\"\n [style.cssText]=\"primary(item)?.style\"\n >\n @if (simpleRichContentNodes(primary(item)); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else if (primary(item)?.type === \"metric\") {\n <praxis-list-metric\n [metric]=\"primary(item)\"\n ></praxis-list-metric>\n } @else if (primary(item)?.type === \"html\") {\n <span [innerHTML]=\"primary(item)?.value\"></span>\n } @else {\n {{ primary(item)?.value }}\n }\n </div>\n @if (layoutLines > 1) {\n <div\n class=\"secondary\"\n [ngClass]=\"secondary(item)?.class\"\n [style.cssText]=\"secondary(item)?.style\"\n >\n @if (simpleRichContentNodes(secondary(item)); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else if (item.segmentVariant && item.segmentLabel && item.accountType && item.since) {\n <!-- Component-based rendering for executive items -->\n <span class=\"exec-subline\">\n <praxis-executive-badge\n [variant]=\"item.segmentVariant\"\n [label]=\"item.segmentLabel\"\n ></praxis-executive-badge>\n <span class=\"exec-subcopy\">{{ item.accountType }}</span>\n <span class=\"exec-subseparator\">\u00B7</span>\n <span class=\"exec-subcopy\">Desde {{ item.since }}</span>\n </span>\n } @else if (secondary(item)?.type === \"html\") {\n <span [innerHTML]=\"secondary(item)?.value\"></span>\n } @else if (secondary(item)?.type === \"metric\") {\n <praxis-list-metric\n [metric]=\"secondary(item)\"\n ></praxis-list-metric>\n } @else {\n {{ secondary(item)?.value }}\n }\n </div>\n }\n @if (meta(item); as m) {\n @if (\n (config.templating?.metaPlacement === \"line\" &&\n (m?.type === \"text\" ||\n m?.type === \"date\" ||\n m?.type === \"currency\")) ||\n (layoutLines > 2 &&\n (m?.type === \"text\" ||\n m?.type === \"date\" ||\n m?.type === \"currency\"))\n ) {\n <div\n class=\"tertiary\"\n [ngClass]=\"m.class\"\n [style.cssText]=\"m.style\"\n >\n @if (config.templating?.metaPrefixIcon; as mpi) {\n <mat-icon [praxisIcon]=\"mpi\"></mat-icon>\n }\n {{ m.value }}\n </div>\n }\n }\n @if (featuresVisible()) {\n <div class=\"features\">\n @for (\n f of config.templating?.features || [];\n track $index;\n let fi = $index\n ) {\n <span\n class=\"feature\"\n [ngClass]=\"\n featureSemanticClass(item, f.expr, f.class)\n \"\n >\n @if (featureRichContentNodes(item, f); as featureNodes) {\n <praxis-rich-content [nodes]=\"featureNodes\"></praxis-rich-content>\n } @else {\n @if (f.icon && featuresMode() !== \"labels-only\") {\n <mat-icon [praxisIcon]=\"f.icon\"></mat-icon>\n }\n @if (featuresMode() !== \"icons-only\") {\n <span\n [ngClass]=\"f.class\"\n [style.cssText]=\"f.style\"\n >\n @for (\n line of featureLabelLines(item, f.expr);\n track $index\n ) {\n <span class=\"feature-line\">{{ line }}</span>\n }\n </span>\n }\n }\n @if (\n featureProgressPercent(item, f.expr, f.class);\n as progress\n ) {\n <span\n class=\"feature-progress\"\n aria-hidden=\"true\"\n >\n <span\n class=\"feature-progress__value\"\n [style.width.%]=\"progress\"\n ></span>\n </span>\n }\n </span>\n }\n </div>\n }\n </div>\n </button>\n <div class=\"list-item-trailing\">\n @if (meta(item); as m) {\n @if (\n !(\n (config.templating?.metaPlacement === \"line\" ||\n layoutLines > 2) &&\n (m?.type === \"text\" ||\n m?.type === \"date\" ||\n m?.type === \"currency\")\n )\n ) {\n <div\n class=\"meta\"\n [ngClass]=\"m.class\"\n [style.cssText]=\"m.style\"\n >\n @if (simpleRichContentNodes(m, { slot: 'meta' }); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (m.type) {\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(m.color) ? m.color : undefined\n \"\n [ngClass]=\"\n (m.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(m.color, m.variant)\"\n >{{ m.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(m);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, m.value)\"\n [color]=\"ratingThemeColor(m)\"\n [style.cssText]=\"ratingIconStyle(m)\"\n ></mat-icon>\n }\n }\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"m.value\"\n [color]=\"\n isThemeColor(m.color) ? m.color : undefined\n \"\n [style.cssText]=\"iconStyle(m.color)\"\n ></mat-icon>\n }\n @case (\"image\") {\n <span class=\"inline-image\"\n ><img [src]=\"m.value\" [alt]=\"m.imageAlt || ''\"\n /></span>\n }\n @case (\"html\") {\n <span [innerHTML]=\"m.value\"></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"m\"></praxis-list-metric>\n }\n @default {\n <span>\n @if (\n config.templating?.metaPrefixIcon;\n as mpi2\n ) {\n <mat-icon [praxisIcon]=\"mpi2\"></mat-icon>\n }\n {{ m.value }}</span\n >\n }\n }\n }\n </div>\n }\n }\n @if (trailing(item); as tr) {\n <div\n class=\"trailing\"\n [ngClass]=\"tr.class\"\n [style.cssText]=\"tr.style\"\n >\n @if (\n simpleRichContentNodes(tr, {\n imageAlt: config.templating?.trailing?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (tr.type) {\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(tr.color) ? tr.color : undefined\n \"\n [ngClass]=\"\n (tr.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(tr.color, tr.variant)\"\n >{{ tr.value }}</mat-chip\n >\n }\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"tr.value\"\n [color]=\"\n isThemeColor(tr.color) ? tr.color : undefined\n \"\n [style.cssText]=\"iconStyle(tr.color)\"\n ></mat-icon>\n }\n @case (\"image\") {\n <div\n class=\"lead-image\"\n [ngClass]=\"tr.class\"\n [style.cssText]=\"tr.style\"\n >\n <img\n [src]=\"tr.value\"\n [alt]=\"\n config.templating?.trailing?.imageAlt || ''\n \"\n />\n @if (tr.badge?.value) {\n <mat-chip\n class=\"lead-badge\"\n [color]=\"\n isThemeColor(tr.badge?.color)\n ? tr.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (tr.badge?.variant || 'filled') ===\n 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(\n tr.badge?.color,\n tr.badge?.variant\n )\n \"\n >{{ tr.badge?.value }}</mat-chip\n >\n }\n </div>\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(tr);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, tr.value)\"\n [color]=\"ratingThemeColor(tr)\"\n [style.cssText]=\"ratingIconStyle(tr)\"\n ></mat-icon>\n }\n }\n @case (\"html\") {\n @if (item.alertsData !== undefined && item.ownerName) {\n <!-- Component-based rendering for executive trailing -->\n <span class=\"exec-trailing-shell\">\n <praxis-executive-alerts [alerts]=\"item.alertsData\"></praxis-executive-alerts>\n <praxis-executive-owner [name]=\"item.ownerName\"></praxis-executive-owner>\n </span>\n } @else {\n <span [innerHTML]=\"tr.value\"></span>\n }\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"tr\"></praxis-list-metric>\n }\n @default {\n <span>{{ tr.value }}</span>\n }\n }\n }\n </div>\n }\n @if (showExpandIcon(\"trailing\")) {\n <button\n mat-icon-button\n type=\"button\"\n class=\"expand-toggle\"\n [attr.aria-label]=\"expandToggleAriaLabel(item, i)\"\n [attr.aria-expanded]=\"\n expansionOwnedByIcon() ? isExpanded(item, i) : null\n \"\n [attr.aria-controls]=\"\n expansionOwnedByIcon()\n ? expandRegionId(item, i)\n : null\n \"\n (click)=\"onExpandToggle($event, item, i)\"\n >\n <mat-icon\n [praxisIcon]=\"\n isExpanded(item, i) ? 'expand_less' : 'expand_more'\n \"\n ></mat-icon>\n </button>\n }\n @if ((visibleActions(item, \"trailing\") || []).length) {\n <div\n class=\"list-item-actions\"\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"onActionKeydown($event)\"\n (keydown.space)=\"onActionKeydown($event)\"\n >\n @for (\n action of visibleActions(item, \"trailing\");\n let aidx = $index;\n track action?.id ?? $index\n ) {\n @if ((action.kind || \"icon\") === \"icon\") {\n <button\n mat-icon-button\n [color]=\"\n isThemeColor(action.color)\n ? action.color\n : undefined\n \"\n (click)=\"\n onActionClick($event, action.id, item, i)\n \"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n i\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n <mat-icon\n [praxisIcon]=\"action.icon\"\n [style.cssText]=\"iconStyle(action.color)\"\n ></mat-icon>\n }\n </button>\n } @else {\n @if (action.buttonVariant === \"stroked\") {\n <button\n mat-stroked-button\n [color]=\"\n isThemeColor(action.color)\n ? action.color\n : undefined\n \"\n [style.cssText]=\"\n buttonStyle(action.color, 'stroked')\n \"\n (click)=\"\n onActionClick($event, action.id, item, i)\n \"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n i\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (action.buttonVariant === \"raised\") {\n <button\n mat-raised-button\n [color]=\"\n isThemeColor(action.color)\n ? action.color\n : undefined\n \"\n [style.cssText]=\"\n buttonStyle(action.color, 'raised')\n \"\n (click)=\"\n onActionClick($event, action.id, item, i)\n \"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n i\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (\n !action.buttonVariant ||\n action.buttonVariant === \"flat\"\n ) {\n <button\n mat-flat-button\n [color]=\"\n isThemeColor(action.color)\n ? action.color\n : undefined\n \"\n [style.cssText]=\"\n buttonStyle(action.color, 'flat')\n \"\n (click)=\"\n onActionClick($event, action.id, item, i)\n \"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, item, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(\n action.id,\n item,\n i\n ),\n }\"\n >\n @if (isActionLoading(action.id, item, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n }\n }\n </div>\n }\n </div>\n </div>\n }\n @if (isExpandable() && isExpanded(item, i)) {\n <div\n class=\"item-expansion\"\n [id]=\"expandRegionId(item, i)\"\n role=\"region\"\n [attr.aria-label]=\"expansionRegionAriaLabel(item)\"\n >\n <div class=\"item-expansion-grid\">\n @for (\n expansionSection of expansionSections(item);\n track expansionSection.id\n ) {\n <section\n class=\"expansion-section\"\n [attr.data-section-type]=\"expansionSection.type\"\n >\n @if (expansionSection.title) {\n <div class=\"expansion-section-title\">\n {{ expansionSection.title }}\n </div>\n }\n @if (\n expansionSectionHasContent(\n item,\n expansionSection,\n i\n )\n ) {\n @switch (expansionSection.type) {\n @case (\"chip-list\") {\n <div class=\"expansion-chip-list\">\n @for (\n expansionEntry of expansionSectionItems(\n item,\n expansionSection,\n i\n );\n track $index\n ) {\n <mat-chip class=\"expansion-chip\">{{\n expansionItemLabel(expansionEntry)\n }}</mat-chip>\n }\n </div>\n }\n @case (\"timeline\") {\n <div class=\"expansion-timeline\">\n @for (\n expansionEntry of expansionSectionItems(\n item,\n expansionSection,\n i\n );\n track $index\n ) {\n <div class=\"expansion-timeline-item\">\n <div class=\"expansion-timeline-title\">\n {{\n expansionTimelineTitle(expansionEntry)\n }}\n </div>\n @if (\n expansionTimelineMeta(expansionEntry)\n ) {\n <div class=\"expansion-timeline-meta\">\n {{\n expansionTimelineMeta(\n expansionEntry\n )\n }}\n </div>\n }\n @if (\n expansionTimelineDescription(\n expansionEntry\n )\n ) {\n <div\n class=\"expansion-timeline-description\"\n >\n {{\n expansionTimelineDescription(\n expansionEntry\n )\n }}\n </div>\n }\n </div>\n }\n </div>\n }\n @case (\"key-value\") {\n <dl class=\"expansion-key-value\">\n @for (\n expansionEntry of expansionSectionItems(\n item,\n expansionSection,\n i\n );\n track $index\n ) {\n <div class=\"expansion-key-value-row\">\n @if (\n expansionKeyValueKey(expansionEntry)\n ) {\n <dt>\n {{\n expansionKeyValueKey(expansionEntry)\n }}\n </dt>\n }\n <dd>\n {{\n expansionKeyValueValue(expansionEntry)\n }}\n </dd>\n </div>\n }\n </dl>\n }\n @default {\n <div class=\"expansion-info-list\">\n @for (\n expansionEntry of expansionSectionItems(\n item,\n expansionSection,\n i\n );\n track $index\n ) {\n <div class=\"expansion-info-item\">\n <div class=\"expansion-info-title\">\n {{ expansionInfoTitle(expansionEntry) }}\n </div>\n @if (expansionInfoValue(expansionEntry)) {\n <div class=\"expansion-info-value\">\n {{\n expansionInfoValue(expansionEntry)\n }}\n </div>\n }\n </div>\n }\n </div>\n }\n }\n } @else {\n <div class=\"expansion-empty\">\n {{\n expansionSection.emptyLabel ||\n \"Sem dados dispon\u00EDveis\"\n }}\n </div>\n }\n </section>\n }\n </div>\n </div>\n }\n </mat-list-item>\n @if (\n (config.layout?.dividers === \"all\" ||\n config.layout?.dividers === \"between\") &&\n i < section.items.length - 1\n ) {\n <mat-divider></mat-divider>\n }\n }\n }\n </mat-list>\n </ng-template>\n } @else if (isTilesVariant()) {\n <ng-container *ngTemplateOutlet=\"tilesVariant\"></ng-container>\n } @else {\n <ng-container *ngTemplateOutlet=\"cardsVariant\"></ng-container>\n }\n\n <!-- Cards variant -->\n <ng-template #cardsVariant>\n <div class=\"cards-grid\">\n @for (it of (items$ | async) ?? []; track $index; let i = $index) {\n <div\n class=\"item-card\"\n [ngClass]=\"itemRuleClass(it)\"\n [attr.style]=\"itemRuleStyle(it)\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"itemAriaLabel(it)\"\n (click)=\"onItemClick(it, i)\"\n (keydown.enter)=\"onItemClick(it, i)\"\n (keydown.space)=\"$event.preventDefault(); onItemClick(it, i)\"\n >\n <div class=\"list-item-content\">\n <div class=\"list-item-leading\">\n @if (leading(it); as lead) {\n @if (\n simpleRichContentNodes(lead, {\n imageAlt: config.templating?.leading?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (lead.type) {\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"lead.value\"\n [ngClass]=\"lead.class\"\n [color]=\"\n isThemeColor(lead.color) ? lead.color : undefined\n \"\n [style.cssText]=\"\n (lead.style ? lead.style + ';' : '') +\n iconStyle(lead.color)\n \"\n ></mat-icon>\n }\n @case (\"image\") {\n <div\n class=\"lead-image\"\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >\n <img\n [src]=\"lead.value\"\n [alt]=\"config.templating?.leading?.imageAlt || ''\"\n />\n @if (lead.badge?.value) {\n <mat-chip\n class=\"lead-badge\"\n [color]=\"\n isThemeColor(lead.badge?.color)\n ? lead.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (lead.badge?.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(\n lead.badge?.color,\n lead.badge?.variant\n )\n \"\n >{{ lead.badge?.value }}</mat-chip\n >\n }\n </div>\n }\n @case (\"text\") {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >{{ lead.value }}</span\n >\n }\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(lead.color) ? lead.color : undefined\n \"\n [ngClass]=\"\n (lead.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(lead.color, lead.variant)\"\n >{{ lead.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(lead);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, lead.value)\"\n [color]=\"ratingThemeColor(lead)\"\n [style.cssText]=\"ratingIconStyle(lead)\"\n ></mat-icon>\n }\n }\n @case (\"html\") {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n [innerHTML]=\"lead.value\"\n ></span>\n }\n @case (\"metric\") {\n <praxis-list-metric\n [metric]=\"lead\"\n ></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose\n [compose]=\"lead\"\n ></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"lead\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n >{{ lead.value }}</span\n >\n }\n }\n }\n } @else {\n <span class=\"leading-placeholder\"></span>\n }\n </div>\n <div class=\"list-item-text\">\n <div\n class=\"primary\"\n [ngClass]=\"primary(it)?.class\"\n [style.cssText]=\"primary(it)?.style\"\n >\n @if (simpleRichContentNodes(primary(it)); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else if (primary(it)?.type === \"metric\") {\n <praxis-list-metric [metric]=\"primary(it)\"></praxis-list-metric>\n } @else if (primary(it)?.type === \"compose\") {\n <praxis-list-compose\n [compose]=\"primary(it)\"\n ></praxis-list-compose>\n } @else if (primary(it)?.type === \"component\") {\n <praxis-list-runtime-component\n [component]=\"primary(it)\"\n ></praxis-list-runtime-component>\n } @else {\n {{ primary(it)?.value }}\n }\n </div>\n @if (layoutLines > 1) {\n <div\n class=\"secondary\"\n [ngClass]=\"secondary(it)?.class\"\n [style.cssText]=\"secondary(it)?.style\"\n >\n @if (simpleRichContentNodes(secondary(it)); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else if (secondary(it)?.type === \"metric\") {\n <praxis-list-metric\n [metric]=\"secondary(it)\"\n ></praxis-list-metric>\n } @else if (secondary(it)?.type === \"compose\") {\n <praxis-list-compose\n [compose]=\"secondary(it)\"\n ></praxis-list-compose>\n } @else if (secondary(it)?.type === \"component\") {\n <praxis-list-runtime-component\n [component]=\"secondary(it)\"\n ></praxis-list-runtime-component>\n } @else {\n {{ secondary(it)?.value }}\n }\n </div>\n }\n @if (meta(it); as m) {\n @if (\n (config.templating?.metaPlacement === \"line\" &&\n (m?.type === \"text\" ||\n m?.type === \"date\" ||\n m?.type === \"currency\")) ||\n (layoutLines > 2 &&\n (m?.type === \"text\" ||\n m?.type === \"date\" ||\n m?.type === \"currency\"))\n ) {\n <div\n class=\"tertiary\"\n [ngClass]=\"m.class\"\n [style.cssText]=\"m.style\"\n >\n @if (config.templating?.metaPrefixIcon; as mpi) {\n <mat-icon [praxisIcon]=\"mpi\"></mat-icon>\n }\n {{ m.value }}\n </div>\n }\n }\n @if (featuresVisible()) {\n <div class=\"features\">\n @for (\n f of config.templating?.features || [];\n track $index;\n let fi = $index\n ) {\n <span\n class=\"feature\"\n [ngClass]=\"featureSemanticClass(it, f.expr, f.class)\"\n >\n @if (featureRichContentNodes(it, f); as featureNodes) {\n <praxis-rich-content [nodes]=\"featureNodes\"></praxis-rich-content>\n } @else {\n @if (f.icon && featuresMode() !== \"labels-only\") {\n <mat-icon [praxisIcon]=\"f.icon\"></mat-icon>\n }\n @if (featuresMode() !== \"icons-only\") {\n <span [ngClass]=\"f.class\" [style.cssText]=\"f.style\">\n @for (\n line of featureLabelLines(it, f.expr);\n track $index\n ) {\n <span class=\"feature-line\">{{ line }}</span>\n }\n </span>\n }\n }\n @if (\n featureProgressPercent(it, f.expr, f.class);\n as progress\n ) {\n <span class=\"feature-progress\" aria-hidden=\"true\">\n <span\n class=\"feature-progress__value\"\n [style.width.%]=\"progress\"\n ></span>\n </span>\n }\n </span>\n }\n </div>\n }\n @if (cardDetailSlots(it); as detailSlots) {\n @if (detailSlots.length) {\n <div class=\"card-detail-strip\">\n @for (\n detail of detailSlots;\n track detail.slot\n ) {\n <div\n class=\"card-detail\"\n [ngClass]=\"'card-detail--' + detail.slot\"\n >\n <span class=\"card-detail__label\">{{\n detail.label\n }}</span>\n <span class=\"card-detail__value\">\n <ng-container\n *ngTemplateOutlet=\"\n rowLayoutNode;\n context: {\n $implicit: detail.node,\n slot: detail.slot,\n imageAlt: rowLayoutImageAlt(detail.slot),\n }\n \"\n ></ng-container>\n </span>\n </div>\n }\n </div>\n }\n }\n </div>\n <div class=\"list-item-trailing\">\n @if (meta(it); as m) {\n @if (\n !(\n (config.templating?.metaPlacement === \"line\" ||\n layoutLines > 2) &&\n (m?.type === \"text\" ||\n m?.type === \"date\" ||\n m?.type === \"currency\")\n )\n ) {\n <div\n class=\"meta\"\n [ngClass]=\"m.class\"\n [style.cssText]=\"m.style\"\n >\n @if (\n simpleRichContentNodes(m, {\n slot: \"meta\",\n imageAlt: m.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (m.type) {\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(m.color) ? m.color : undefined\n \"\n [ngClass]=\"\n (m.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(m.color, m.variant)\"\n >{{ m.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(m);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, m.value)\"\n [color]=\"ratingThemeColor(m)\"\n [style.cssText]=\"ratingIconStyle(m)\"\n ></mat-icon>\n }\n }\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"m.value\"\n [color]=\"\n isThemeColor(m.color) ? m.color : undefined\n \"\n [style.cssText]=\"iconStyle(m.color)\"\n ></mat-icon>\n }\n @case (\"image\") {\n <span class=\"inline-image\"\n ><img [src]=\"m.value\" [alt]=\"m.imageAlt || ''\"\n /></span>\n }\n @case (\"html\") {\n <span [innerHTML]=\"m.value\"></span>\n }\n @case (\"metric\") {\n <praxis-list-metric\n [metric]=\"m\"\n ></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose\n [compose]=\"m\"\n ></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"m\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span>\n @if (config.templating?.metaPrefixIcon; as mpi2) {\n <mat-icon [praxisIcon]=\"mpi2\"></mat-icon>\n }\n {{ m.value }}</span\n >\n }\n }\n }\n </div>\n }\n }\n @if (trailing(it); as tr) {\n @if (\n (config.templating?.statusPosition || \"inline\") ===\n \"top-right\" &&\n (tr?.type === \"chip\" || tr?.type === \"icon\")\n ) {\n <div class=\"status-overlay\">\n @if (simpleRichContentNodes(tr); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @if (tr?.type === \"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(tr.color) ? tr.color : undefined\n \"\n [ngClass]=\"\n (tr.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(tr.color, tr.variant)\"\n >{{ tr.value }}</mat-chip\n >\n }\n @if (tr?.type === \"icon\") {\n <mat-icon\n [praxisIcon]=\"tr.value\"\n [color]=\"\n isThemeColor(tr.color) ? tr.color : undefined\n \"\n [style.cssText]=\"iconStyle(tr.color)\"\n ></mat-icon>\n }\n }\n </div>\n } @else {\n <div\n class=\"trailing\"\n [ngClass]=\"tr.class\"\n [style.cssText]=\"tr.style\"\n >\n @if (\n simpleRichContentNodes(tr, {\n imageAlt: config.templating?.trailing?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (tr.type) {\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(tr.color) ? tr.color : undefined\n \"\n [ngClass]=\"\n (tr.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(tr.color, tr.variant)\"\n >{{ tr.value }}</mat-chip\n >\n }\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"tr.value\"\n [color]=\"\n isThemeColor(tr.color) ? tr.color : undefined\n \"\n [style.cssText]=\"iconStyle(tr.color)\"\n ></mat-icon>\n }\n @case (\"image\") {\n <div\n class=\"lead-image\"\n [ngClass]=\"tr.class\"\n [style.cssText]=\"tr.style\"\n >\n <img\n [src]=\"tr.value\"\n [alt]=\"\n config.templating?.trailing?.imageAlt || ''\n \"\n />\n @if (tr.badge?.value) {\n <mat-chip\n class=\"lead-badge\"\n [color]=\"\n isThemeColor(tr.badge?.color)\n ? tr.badge?.color\n : undefined\n \"\n [ngClass]=\"\n (tr.badge?.variant || 'filled') ===\n 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"\n chipStyle(\n tr.badge?.color,\n tr.badge?.variant\n )\n \"\n >{{ tr.badge?.value }}</mat-chip\n >\n }\n </div>\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(tr);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, tr.value)\"\n [color]=\"ratingThemeColor(tr)\"\n [style.cssText]=\"ratingIconStyle(tr)\"\n ></mat-icon>\n }\n }\n @case (\"html\") {\n <span [innerHTML]=\"tr.value\"></span>\n }\n @case (\"metric\") {\n <praxis-list-metric\n [metric]=\"tr\"\n ></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose\n [compose]=\"tr\"\n ></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"tr\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span>{{ tr.value }}</span>\n }\n }\n }\n </div>\n }\n }\n </div>\n </div>\n <div\n class=\"card-actions\"\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"onActionKeydown($event)\"\n (keydown.space)=\"onActionKeydown($event)\"\n >\n @for (\n action of visibleActions(it, \"trailing\");\n let aidx = $index;\n track action?.id ?? $index\n ) {\n @if ((action.kind || \"icon\") === \"icon\") {\n <button\n mat-icon-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n (click)=\"onActionClick($event, action.id, it, i)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, it, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(action.id, it, i),\n }\"\n >\n @if (isActionLoading(action.id, it, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n <mat-icon\n [praxisIcon]=\"action.icon\"\n [style.cssText]=\"iconStyle(action.color)\"\n ></mat-icon>\n }\n </button>\n } @else {\n @if (action.buttonVariant === \"stroked\") {\n <button\n mat-stroked-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'stroked')\"\n (click)=\"onActionClick($event, action.id, it, i)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, it, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(action.id, it, i),\n }\"\n >\n @if (isActionLoading(action.id, it, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (action.buttonVariant === \"raised\") {\n <button\n mat-raised-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'raised')\"\n (click)=\"onActionClick($event, action.id, it, i)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, it, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(action.id, it, i),\n }\"\n >\n @if (isActionLoading(action.id, it, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (\n !action.buttonVariant || action.buttonVariant === \"flat\"\n ) {\n <button\n mat-flat-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'flat')\"\n (click)=\"onActionClick($event, action.id, it, i)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, it, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(action.id, it, i),\n }\"\n >\n @if (isActionLoading(action.id, it, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n }\n }\n </div>\n </div>\n }\n </div>\n </ng-template>\n\n <!-- Tiles variant -->\n <ng-template #tilesVariant>\n <div class=\"tiles-grid\">\n @for (it of (items$ | async) ?? []; track $index; let i = $index) {\n <div\n class=\"item-tile\"\n [ngClass]=\"itemRuleClass(it)\"\n [attr.style]=\"itemRuleStyle(it)\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"itemAriaLabel(it)\"\n (click)=\"onItemClick(it, i)\"\n (keydown.enter)=\"onItemClick(it, i)\"\n (keydown.space)=\"$event.preventDefault(); onItemClick(it, i)\"\n >\n <div class=\"tile-media\">\n @if (leading(it); as lead) {\n @if (\n simpleRichContentNodes(lead, {\n imageAlt: config.templating?.leading?.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (lead.type) {\n @case (\"image\") {\n <img\n [src]=\"lead.value\"\n [alt]=\"config.templating?.leading?.imageAlt || ''\"\n />\n }\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"lead.value\"\n [ngClass]=\"lead.class\"\n [color]=\"\n isThemeColor(lead.color) ? lead.color : undefined\n \"\n [style.cssText]=\"\n (lead.style ? lead.style + ';' : '') +\n iconStyle(lead.color)\n \"\n ></mat-icon>\n }\n @case (\"text\") {\n <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{\n lead.value\n }}</span>\n }\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(lead.color) ? lead.color : undefined\n \"\n [ngClass]=\"\n (lead.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(lead.color, lead.variant)\"\n >{{ lead.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">\n @for (\n _ of ratingRange(lead);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, lead.value)\"\n [color]=\"ratingThemeColor(lead)\"\n [style.cssText]=\"ratingIconStyle(lead)\"\n ></mat-icon>\n }\n </span>\n }\n @case (\"html\") {\n <span\n [ngClass]=\"lead.class\"\n [style.cssText]=\"lead.style\"\n [innerHTML]=\"lead.value\"\n ></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"lead\"></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose\n [compose]=\"lead\"\n ></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"lead\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{\n lead.value\n }}</span>\n }\n }\n }\n }\n </div>\n\n @if (trailing(it); as tr) {\n @if (\n (config.templating?.statusPosition || \"inline\") ===\n \"top-right\" &&\n (tr?.type === \"chip\" || tr?.type === \"icon\")\n ) {\n <div class=\"tile-status\">\n @if (simpleRichContentNodes(tr); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @if (tr?.type === \"chip\") {\n <mat-chip\n [color]=\"isThemeColor(tr.color) ? tr.color : undefined\"\n [ngClass]=\"\n (tr.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(tr.color, tr.variant)\"\n >{{ tr.value }}</mat-chip\n >\n }\n @if (tr?.type === \"icon\") {\n <mat-icon\n [praxisIcon]=\"tr.value\"\n [color]=\"isThemeColor(tr.color) ? tr.color : undefined\"\n [style.cssText]=\"iconStyle(tr.color)\"\n ></mat-icon>\n }\n }\n </div>\n }\n }\n\n <div class=\"tile-body\">\n <div\n class=\"primary\"\n [ngClass]=\"primary(it)?.class\"\n [style.cssText]=\"primary(it)?.style\"\n >\n @if (simpleRichContentNodes(primary(it)); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else if (primary(it)?.type === \"metric\") {\n <praxis-list-metric [metric]=\"primary(it)\"></praxis-list-metric>\n } @else if (primary(it)?.type === \"compose\") {\n <praxis-list-compose\n [compose]=\"primary(it)\"\n ></praxis-list-compose>\n } @else if (primary(it)?.type === \"component\") {\n <praxis-list-runtime-component\n [component]=\"primary(it)\"\n ></praxis-list-runtime-component>\n } @else {\n {{ primary(it)?.value }}\n }\n </div>\n @if (layoutLines > 1) {\n <div\n class=\"secondary\"\n [ngClass]=\"secondary(it)?.class\"\n [style.cssText]=\"secondary(it)?.style\"\n >\n @if (simpleRichContentNodes(secondary(it)); as richNodes) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else if (secondary(it)?.type === \"metric\") {\n <praxis-list-metric\n [metric]=\"secondary(it)\"\n ></praxis-list-metric>\n } @else if (secondary(it)?.type === \"compose\") {\n <praxis-list-compose\n [compose]=\"secondary(it)\"\n ></praxis-list-compose>\n } @else if (secondary(it)?.type === \"component\") {\n <praxis-list-runtime-component\n [component]=\"secondary(it)\"\n ></praxis-list-runtime-component>\n } @else {\n {{ secondary(it)?.value }}\n }\n </div>\n }\n\n @if (meta(it); as m) {\n <div\n class=\"tile-meta\"\n [ngClass]=\"m.class\"\n [style.cssText]=\"m.style\"\n >\n @if (config.templating?.metaPrefixIcon; as mpi) {\n <mat-icon [praxisIcon]=\"mpi\"></mat-icon>\n }\n @if (\n simpleRichContentNodes(m, {\n slot: \"meta\",\n imageAlt: m.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (m.type) {\n @case (\"chip\") {\n <mat-chip\n [color]=\"isThemeColor(m.color) ? m.color : undefined\"\n [ngClass]=\"\n (m.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(m.color, m.variant)\"\n >{{ m.value }}</mat-chip\n >\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(m);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, m.value)\"\n [color]=\"ratingThemeColor(m)\"\n [style.cssText]=\"ratingIconStyle(m)\"\n ></mat-icon>\n }\n }\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"m.value\"\n [color]=\"isThemeColor(m.color) ? m.color : undefined\"\n [style.cssText]=\"iconStyle(m.color)\"\n ></mat-icon>\n }\n @case (\"image\") {\n <span class=\"inline-image\"\n ><img [src]=\"m.value\" [alt]=\"m.imageAlt || ''\"\n /></span>\n }\n @case (\"html\") {\n <span [innerHTML]=\"m.value\"></span>\n }\n @case (\"metric\") {\n <praxis-list-metric [metric]=\"m\"></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose\n [compose]=\"m\"\n ></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"m\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span>{{ m.value }}</span>\n }\n }\n }\n </div>\n }\n\n @if (featuresVisible()) {\n <div class=\"tiles-features\">\n @for (\n f of config.templating?.features || [];\n track $index;\n let fi = $index\n ) {\n <span\n class=\"feature\"\n [ngClass]=\"featureSemanticClass(it, f.expr, f.class)\"\n >\n @if (featureRichContentNodes(it, f); as featureNodes) {\n <praxis-rich-content [nodes]=\"featureNodes\"></praxis-rich-content>\n } @else {\n @if (f.icon && featuresMode() !== \"labels-only\") {\n <mat-icon [praxisIcon]=\"f.icon\"></mat-icon>\n }\n @if (featuresMode() !== \"icons-only\") {\n <span [ngClass]=\"f.class\" [style.cssText]=\"f.style\">\n @for (\n line of featureLabelLines(it, f.expr);\n track $index\n ) {\n <span class=\"feature-line\">{{ line }}</span>\n }\n </span>\n }\n }\n @if (\n featureProgressPercent(it, f.expr, f.class);\n as progress\n ) {\n <span class=\"feature-progress\" aria-hidden=\"true\">\n <span\n class=\"feature-progress__value\"\n [style.width.%]=\"progress\"\n ></span>\n </span>\n }\n </span>\n }\n </div>\n }\n\n @if (trailing(it); as tr2) {\n @if (\n !(\n (config.templating?.statusPosition || \"inline\") ===\n \"top-right\" &&\n (tr2?.type === \"chip\" || tr2?.type === \"icon\")\n )\n ) {\n <div\n class=\"tile-trailing\"\n [ngClass]=\"tr2.class\"\n [style.cssText]=\"tr2.style\"\n >\n @if (\n simpleRichContentNodes(tr2, {\n imageAlt: tr2.imageAlt || \"\",\n });\n as richNodes\n ) {\n <praxis-rich-content [nodes]=\"richNodes\"></praxis-rich-content>\n } @else {\n @switch (tr2.type) {\n @case (\"chip\") {\n <mat-chip\n [color]=\"\n isThemeColor(tr2.color) ? tr2.color : undefined\n \"\n [ngClass]=\"\n (tr2.variant || 'filled') === 'outlined'\n ? 'chip-outlined'\n : ''\n \"\n [style.cssText]=\"chipStyle(tr2.color, tr2.variant)\"\n >{{ tr2.value }}</mat-chip\n >\n }\n @case (\"icon\") {\n <mat-icon\n [praxisIcon]=\"tr2.value\"\n [color]=\"\n isThemeColor(tr2.color) ? tr2.color : undefined\n \"\n [style.cssText]=\"iconStyle(tr2.color)\"\n ></mat-icon>\n }\n @case (\"image\") {\n <span class=\"inline-image\"\n ><img [src]=\"tr2.value\" [alt]=\"tr2.imageAlt || ''\"\n /></span>\n }\n @case (\"rating\") {\n @for (\n _ of ratingRange(tr2);\n track $index;\n let idx = $index\n ) {\n <mat-icon\n [praxisIcon]=\"starIcon(idx, tr2.value)\"\n [color]=\"ratingThemeColor(tr2)\"\n [style.cssText]=\"ratingIconStyle(tr2)\"\n ></mat-icon>\n }\n }\n @case (\"html\") {\n <span [innerHTML]=\"tr2.value\"></span>\n }\n @case (\"metric\") {\n <praxis-list-metric\n [metric]=\"tr2\"\n ></praxis-list-metric>\n }\n @case (\"compose\") {\n <praxis-list-compose\n [compose]=\"tr2\"\n ></praxis-list-compose>\n }\n @case (\"component\") {\n <praxis-list-runtime-component\n [component]=\"tr2\"\n ></praxis-list-runtime-component>\n }\n @default {\n <span>{{ tr2.value }}</span>\n }\n }\n }\n </div>\n }\n }\n </div>\n\n @if ((visibleActions(it, \"trailing\") || []).length) {\n <div\n class=\"tile-actions\"\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"onActionKeydown($event)\"\n (keydown.space)=\"onActionKeydown($event)\"\n >\n @for (\n action of visibleActions(it, \"trailing\");\n let aidx = $index;\n track action?.id ?? $index\n ) {\n @if ((action.kind || \"icon\") === \"icon\") {\n <button\n mat-icon-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n (click)=\"onActionClick($event, action.id, it, i)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, it, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(action.id, it, i),\n }\"\n >\n @if (isActionLoading(action.id, it, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n <mat-icon\n [praxisIcon]=\"action.icon\"\n [style.cssText]=\"iconStyle(action.color)\"\n ></mat-icon>\n }\n </button>\n } @else {\n @if (action.buttonVariant === \"stroked\") {\n <button\n mat-stroked-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'stroked')\"\n (click)=\"onActionClick($event, action.id, it, i)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, it, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(action.id, it, i),\n }\"\n >\n @if (isActionLoading(action.id, it, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (action.buttonVariant === \"raised\") {\n <button\n mat-raised-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'raised')\"\n (click)=\"onActionClick($event, action.id, it, i)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, it, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(action.id, it, i),\n }\"\n >\n @if (isActionLoading(action.id, it, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n @if (\n !action.buttonVariant || action.buttonVariant === \"flat\"\n ) {\n <button\n mat-flat-button\n [color]=\"\n isThemeColor(action.color) ? action.color : undefined\n \"\n [style.cssText]=\"buttonStyle(action.color, 'flat')\"\n (click)=\"onActionClick($event, action.id, it, i)\"\n [attr.aria-label]=\"action.label || action.id\"\n [disabled]=\"isActionLoading(action.id, it, i)\"\n [ngClass]=\"{\n 'action-loading': isActionLoading(action.id, it, i),\n }\"\n >\n @if (isActionLoading(action.id, it, i)) {\n <mat-progress-spinner\n class=\"action-spinner\"\n mode=\"indeterminate\"\n diameter=\"16\"\n strokeWidth=\"3\"\n ></mat-progress-spinner>\n } @else {\n {{ action.label || action.id }}\n }\n </button>\n }\n }\n }\n </div>\n }\n </div>\n }\n </div>\n </ng-template>\n\n <!-- Remote data controls -->\n @if (config.dataSource?.resourcePath) {\n @if (config.ui?.showSort || (config.ui?.showSearch && config.ui?.searchField)) {\n <div class=\"list-remote-tools\">\n @if (config.ui?.showSort) {\n <mat-form-field class=\"paginator-sort\" appearance=\"outline\">\n <mat-label>Ordenar</mat-label>\n <mat-select (selectionChange)=\"onSortChange($event.value)\">\n @for (op of config.ui?.sortOptions || []; track $index) {\n <mat-option [value]=\"sortOptionValue(op)\">{{\n sortOptionLabel(op)\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n @if (config.ui?.showSearch && config.ui?.searchField) {\n <mat-form-field class=\"paginator-search\" appearance=\"outline\">\n <mat-label>{{\n config.ui?.searchPlaceholder || \"Buscar\"\n }}</mat-label>\n <input\n matInput\n type=\"search\"\n (input)=\"onSearchInput($any($event.target).value)\"\n />\n </mat-form-field>\n }\n </div>\n }\n @if (page$ | async; as ps) {\n @if (total$ | async; as total) {\n @if (config.ui?.showRange === false) {\n <div class=\"list-paginator-total\" aria-live=\"polite\">\n {{ remoteTotalLabel(total) }}\n </div>\n }\n <mat-paginator\n [length]=\"total || 0\"\n [pageIndex]=\"ps.pageNumber || 0\"\n [pageSize]=\"ps.pageSize || config.layout?.pageSize || 10\"\n [pageSizeOptions]=\"listPageSizeOptions()\"\n [selectConfig]=\"paginatorSelectConfig\"\n [showFirstLastButtons]=\"true\"\n [class.hide-range]=\"config.ui?.showRange === false\"\n (page)=\"onPageChange($event)\"\n >\n </mat-paginator>\n }\n }\n }\n </ng-template>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.praxis-list-root{--p-list-radius: var(--md-sys-shape-corner-medium, 16px);--p-list-shadow: var(--md-sys-elevation-level2);--p-list-border: 1px solid var(--md-sys-color-outline-variant);--p-list-blur: 10px;--p-list-surface: var(--md-sys-color-surface-container);--p-list-surface-low: var(--md-sys-color-surface);--p-list-surface-high: var( --md-sys-color-surface-container-high, var(--md-sys-color-surface-container) );--p-list-foreground: var(--md-sys-color-on-surface);--p-list-foreground-muted: var(--md-sys-color-on-surface-variant);--p-list-accent: var(--md-sys-color-primary);--p-list-accent-weak: var(--md-sys-color-primary-container);--p-list-item-surface: var( --md-sys-color-surface-container-low, var(--md-sys-color-surface) );--p-list-item-border: 1px solid var(--md-sys-color-outline-variant);--p-list-item-hover-surface: var(--md-sys-color-surface-container-low);--p-list-item-active-surface: var(--md-sys-color-surface-container);--p-list-item-selected-surface: var(--md-sys-color-surface-container);--p-list-grad-from: var(--md-sys-color-primary-container);--p-list-grad-to: var(--md-sys-color-tertiary-container);--p-list-grad-angle: 135deg;--p-list-grad-foreground: var( --md-sys-color-on-primary-container, var(--md-sys-color-on-surface) );--p-list-grad-foreground-muted: color-mix( in srgb, var(--p-list-grad-foreground) 76%, transparent );--p-list-leading-width: 36px;--p-list-trailing-min-width: 140px;--p-list-trailing-max-width: 320px;--p-list-selection-end-gutter: 56px;--p-list-item-gap: 10px;--p-list-item-padding-x: 14px;--p-list-item-padding-y: 10px;--p-list-item-stack-gap: 0px;--p-list-meta-size: .875rem;--p-list-meta-weight: 500;--p-list-chip-height: 22px;--p-list-chip-font-size: 12px;--p-list-trailing-padding-right: 8px;--p-list-action-button-height: 32px;--p-list-action-icon-button-size: 32px;--p-list-action-icon-size: 18px;--p-list-tile-minW: 240px;--p-list-tile-gap: 12px;--p-list-tile-padding: 12px;--p-list-tile-radius: 12px;--p-list-tile-media-radius: 12px;--p-list-tile-media-ratio: 1 / 1;--p-list-tile-hover-overlay: .04;--p-list-tile-press-overlay: .08;--p-list-tile-press-scale: .99}.list-assistant{display:flex;justify-content:flex-end;gap:8px;padding:4px 4px 8px}.action-loading{opacity:.65}.action-spinner{width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center}.action-loading .mat-mdc-button-touch-target,.action-loading .mdc-button__label{opacity:.7}.skin-elevated,.skin-outline,.skin-flat,.skin-neumorphism{--p-list-item-surface: var( --mdc-elevated-card-container-color, var(--p-list-surface) );--p-list-item-border: var(--p-list-border)}.skin-elevated .item-card,.skin-outline .item-card,.skin-flat .item-card,.skin-neumorphism .item-card{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border-radius:var(--p-list-radius);box-shadow:var(--p-list-shadow);border:var(--p-list-border)}.skin-elevated .item-tile,.skin-outline .item-tile,.skin-flat .item-tile,.skin-neumorphism .item-tile{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));box-shadow:var(--p-list-shadow);border:var(--p-list-border)}.skin-elevated .mat-mdc-list-item .list-item-content,.skin-outline .mat-mdc-list-item .list-item-content,.skin-flat .mat-mdc-list-item .list-item-content,.skin-neumorphism .mat-mdc-list-item .list-item-content{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border-radius:calc(var(--p-list-radius) * .75);box-shadow:var(--p-list-shadow);border:var(--p-list-item-border);color:var(--p-list-foreground)}.skin-outline{--p-list-shadow: none;--p-list-border: 1px solid var(--md-sys-color-outline)}.skin-flat{--p-list-shadow: none;--p-list-border: 1px solid var(--md-sys-color-outline-variant)}.skin-neumorphism{--p-list-shadow: var(--md-sys-elevation-level2);--p-list-border: 1px solid var(--md-sys-color-outline-variant);--p-list-radius: 1.25rem}.skin-pill-soft .item-card,.skin-pill-soft .item-tile{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border-radius:calc(var(--p-list-radius) * 1.3);box-shadow:var(--md-sys-elevation-level1);border:var(--p-list-border)}.skin-pill-soft .mat-mdc-list-item .list-item-content{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border-radius:calc(var(--p-list-radius) * 1.3);box-shadow:var(--md-sys-elevation-level1);border:var(--p-list-border);color:var(--p-list-foreground)}.skin-gradient-tile{--p-list-foreground: var(--p-list-grad-foreground);--p-list-foreground-muted: var(--p-list-grad-foreground-muted);--p-list-item-hover-surface: linear-gradient( var(--p-list-grad-angle), var(--p-list-grad-from), var(--p-list-grad-to) );--p-list-item-active-surface: linear-gradient( var(--p-list-grad-angle), var(--p-list-grad-from), var(--p-list-grad-to) );--p-list-item-selected-surface: linear-gradient( var(--p-list-grad-angle), var(--p-list-grad-from), var(--p-list-grad-to) )}.skin-gradient-tile .item-card,.skin-gradient-tile .item-tile{background:linear-gradient(var(--p-list-grad-angle),var(--p-list-grad-from),var(--p-list-grad-to));border-radius:var(--p-list-radius);color:var(--p-list-foreground)}.skin-gradient-tile .tile-media{background:color-mix(in srgb,var(--md-sys-color-surface) 74%,transparent);border-color:color-mix(in srgb,var(--p-list-foreground) 22%,transparent)}.skin-gradient-tile .tile-media mat-icon,.skin-gradient-tile .tile-meta,.skin-gradient-tile .tile-trailing,.skin-gradient-tile .feature,.skin-gradient-tile .secondary,.skin-gradient-tile .meta{color:var(--p-list-foreground-muted)}.skin-gradient-tile .tile-status,.skin-gradient-tile [data-rich-node-type=badge] .prx-rich-badge{color:var(--md-sys-color-on-secondary-container, var(--p-list-foreground));background:var(--md-sys-color-secondary-container, rgba(255, 255, 255, .72))}.skin-gradient-tile .mat-mdc-list-item .list-item-content{background:linear-gradient(var(--p-list-grad-angle),var(--p-list-grad-from),var(--p-list-grad-to));border-radius:var(--p-list-radius);color:var(--p-list-foreground)}.skin-glass .item-card,.skin-glass .item-tile{background:var(--md-sys-color-surface-container-low);border-radius:var(--p-list-radius);border:1px solid var(--md-sys-color-outline-variant);backdrop-filter:blur(var(--p-list-blur));-webkit-backdrop-filter:blur(var(--p-list-blur))}.skin-glass .mat-mdc-list-item .list-item-content{background:var(--md-sys-color-surface-container-low);border-radius:var(--p-list-radius);border:1px solid var(--md-sys-color-outline-variant);backdrop-filter:blur(var(--p-list-blur));-webkit-backdrop-filter:blur(var(--p-list-blur));color:var(--p-list-foreground)}.density-compact .mat-mdc-list-item,.density-compact .item-card{--p-list-item-padding-y: 6px;--p-list-item-padding-x: 10px;--p-list-item-gap: 8px}.density-comfortable .mat-mdc-list-item,.density-comfortable .item-card{--p-list-item-padding-y: 8px;--p-list-item-padding-x: 12px;--p-list-item-gap: 10px}.praxis-list-root.lines-3 .mat-mdc-list-item,.praxis-list-root.lines-3 .mat-mdc-list-option{min-height:68px}.density-compact{--p-list-tile-gap: 10px;--p-list-tile-padding: 10px;--p-list-tile-minW: 188px}.density-comfortable{--p-list-tile-gap: 12px;--p-list-tile-padding: 12px}.item-spacing-none{--p-list-item-stack-gap: 0px}.item-spacing-tight{--p-list-item-stack-gap: 4px;--p-list-tile-gap: 10px}.item-spacing-default{--p-list-item-stack-gap: 6px;--p-list-tile-gap: 12px}.item-spacing-relaxed{--p-list-item-stack-gap: 14px;--p-list-tile-gap: 20px}.list-item-content{box-sizing:border-box;display:grid;grid-template-columns:var(--p-list-leading-width) minmax(0,1fr) minmax(var(--p-list-trailing-min-width),min(34vw,var(--p-list-trailing-max-width)));align-items:center;gap:var(--p-list-item-gap);padding:var(--p-list-item-padding-y) var(--p-list-item-padding-x);width:100%;min-width:0;border-radius:calc(var(--p-list-radius) * .75);overflow:visible}.list-item-content--row-layout{--p-list-row-template-columns: var(--p-list-leading-width) minmax(0, 1fr) minmax(var(--p-list-trailing-min-width), max-content);--p-list-row-gap: var(--p-list-item-gap);--p-list-row-align-items: center;grid-template-columns:var(--p-list-row-template-columns);gap:var(--p-list-row-gap);align-items:var(--p-list-row-align-items)}.list-item-content--row-layout[role=button]{cursor:pointer}.list-item-content--row-layout[role=button]:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.list-row-slot{min-width:0;height:100%;align-self:stretch;display:flex;align-items:var(--p-list-row-slot-align-items, center);gap:8px}.list-item-content--row-layout .list-row-slot>praxis-rich-content,.list-item-content--row-layout .list-row-slot>praxis-list-metric,.list-item-content--row-layout .list-row-slot>praxis-list-compose,.list-item-content--row-layout .list-row-slot>praxis-list-runtime-component{min-width:0}.list-row-slot--leading{justify-content:center}.list-row-slot--primary,.list-row-slot--secondary{min-width:0}.list-row-slot--meta{color:var(--p-list-foreground);font-variant-numeric:tabular-nums}.list-row-slot--trailing{flex-wrap:wrap;justify-content:flex-end}.list-row-slot .primary,.list-row-slot .secondary,.list-row-slot .meta,.list-row-slot .trailing{min-width:0}.list-item-main-action{grid-column:1/3;display:grid;grid-template-columns:var(--p-list-leading-width) minmax(0,1fr);align-items:center;gap:var(--p-list-item-gap);min-width:0;padding:0;border:0;background:transparent;color:inherit;text-align:start;font:inherit;cursor:pointer}.list-item-main-action:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px;border-radius:12px}.list-item-leading{display:flex;align-items:center;justify-content:center;min-height:100%}.leading-placeholder{width:24px;height:24px;display:inline-block}.list-item-text{min-width:0;display:grid;align-content:center;gap:2px}.list-item-trailing{box-sizing:border-box;min-width:0;width:100%;max-width:var(--p-list-trailing-max-width);display:grid;align-self:center;align-items:center;justify-content:end;justify-items:end;align-content:center;gap:4px;text-align:end;padding-right:var(--p-list-trailing-padding-right)}.list-item-trailing>*{align-self:center}.list-item-content--row-layout .list-item-actions{justify-content:flex-end}.list-item-actions{width:max-content;justify-self:end;align-self:center;display:flex;align-items:center;justify-content:flex-end;gap:4px;flex-wrap:wrap;max-width:100%;min-height:var(--p-list-action-button-height)}.list-item-actions .mat-mdc-button-base{min-height:var(--p-list-action-button-height);height:var(--p-list-action-button-height);display:inline-flex;align-items:center}.list-item-actions .mat-mdc-icon-button{width:var(--p-list-action-icon-button-size);height:var(--p-list-action-icon-button-size);min-width:var(--p-list-action-icon-button-size);padding:calc((var(--p-list-action-icon-button-size) - var(--p-list-action-icon-size)) / 2);--mdc-icon-button-state-layer-size: var(--p-list-action-icon-button-size)}.list-item-actions .mat-mdc-icon-button mat-icon{width:var(--p-list-action-icon-size);height:var(--p-list-action-icon-size);font-size:var(--p-list-action-icon-size);line-height:var(--p-list-action-icon-size)}.expand-toggle{color:var(--p-list-foreground-muted)}.mat-mdc-list-item .list-item-content{background:var(--p-list-item-surface);border:var(--p-list-item-border);transition:background .15s ease,box-shadow .15s ease,transform .15s ease}.mat-mdc-list-item,.mat-mdc-list-option{height:auto;align-items:stretch;overflow:visible}.mat-mdc-list-option .mdc-list-item__end{align-self:center;margin-top:auto;margin-bottom:auto}.mat-mdc-list-option .list-item-content{padding-right:calc(var(--p-list-item-padding-x) + var(--p-list-selection-end-gutter))}.mat-mdc-list-item.mdc-list-item--with-one-line,.mat-mdc-list-item.mdc-list-item--with-two-lines,.mat-mdc-list-item.mdc-list-item--with-three-lines,.mat-mdc-list-item-single-line,.mat-mdc-list-item-two-line,.mat-mdc-list-item-three-line,.mat-mdc-list-option.mdc-list-item--with-one-line,.mat-mdc-list-option.mdc-list-item--with-two-lines,.mat-mdc-list-option.mdc-list-item--with-three-lines{height:auto!important}.mdc-list-item{overflow:visible}.density-compact .mat-mdc-list-item,.density-compact .mat-mdc-list-option{min-height:40px}.density-comfortable .mat-mdc-list-item,.density-comfortable .mat-mdc-list-option{min-height:46px}.praxis-list-root:not(.density-compact):not(.density-comfortable) .mat-mdc-list-item,.praxis-list-root:not(.density-compact):not(.density-comfortable) .mat-mdc-list-option{min-height:48px}.mat-mdc-list-item .mdc-list-item__content,.mat-mdc-list-option .mdc-list-item__content,.mat-mdc-list-item .mdc-list-item__primary-text,.mat-mdc-list-option .mdc-list-item__primary-text{display:block!important;width:100%!important;height:auto!important;padding:0!important;margin:0!important;overflow:visible!important;box-sizing:border-box}.mat-mdc-list-item,.mat-mdc-list-option{padding:0!important;box-sizing:border-box}.praxis-list-root mat-list>mat-list-item:not(:last-child),.praxis-list-root mat-selection-list>mat-list-option:not(:last-child){margin-bottom:var(--p-list-item-stack-gap)}.mat-mdc-list-item:hover .list-item-content,.mat-mdc-list-option:hover .list-item-content{background:var(--p-list-item-hover-surface)}.mat-mdc-list-item:active .list-item-content,.mat-mdc-list-option:active .list-item-content{background:var(--p-list-item-active-surface)}.mat-mdc-list-option.mdc-list-item--selected .list-item-content{background:var(--p-list-item-selected-surface);border-color:var(--md-sys-color-outline-variant)}.mat-mdc-list-option.cdk-keyboard-focused .list-item-content,.mat-mdc-list-option.cdk-focused .list-item-content{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.mat-mdc-list-item:focus-visible .list-item-content{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.praxis-list-root mat-selection-list>.mat-mdc-list-option{border-radius:calc(var(--p-list-radius) * .75);background:var(--p-list-item-surface);border:var(--p-list-item-border);color:var(--p-list-foreground);overflow:hidden;transition:background .15s ease,box-shadow .15s ease,border-color .15s ease}.skin-elevated mat-selection-list>.mat-mdc-list-option,.skin-outline mat-selection-list>.mat-mdc-list-option,.skin-flat mat-selection-list>.mat-mdc-list-option,.skin-neumorphism mat-selection-list>.mat-mdc-list-option{background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border:var(--p-list-item-border);box-shadow:var(--p-list-shadow)}.skin-pill-soft mat-selection-list>.mat-mdc-list-option{border-radius:calc(var(--p-list-radius) * 1.3);background:var(--mdc-elevated-card-container-color, var(--p-list-surface));border:var(--p-list-border);box-shadow:var(--md-sys-elevation-level1)}.skin-gradient-tile mat-selection-list>.mat-mdc-list-option{border-radius:var(--p-list-radius);background:linear-gradient(var(--p-list-grad-angle),var(--p-list-grad-from),var(--p-list-grad-to));color:var(--p-list-foreground)}.skin-glass mat-selection-list>.mat-mdc-list-option{border-radius:var(--p-list-radius);background:var(--md-sys-color-surface-container-low);border:1px solid var(--md-sys-color-outline-variant);backdrop-filter:blur(var(--p-list-blur));-webkit-backdrop-filter:blur(var(--p-list-blur))}.praxis-list-root mat-selection-list>.mat-mdc-list-option:hover{background:var(--p-list-item-hover-surface)}.praxis-list-root mat-selection-list>.mat-mdc-list-option:active{background:var(--p-list-item-active-surface)}.praxis-list-root mat-selection-list>.mat-mdc-list-option.mdc-list-item--selected{background:var(--p-list-item-selected-surface);border-color:var(--md-sys-color-outline-variant)}.praxis-list-root mat-selection-list>.mat-mdc-list-option .list-item-content{background:transparent;border:0;box-shadow:none;border-radius:0}.praxis-list-root mat-selection-list>.mat-mdc-list-option.cdk-keyboard-focused,.praxis-list-root mat-selection-list>.mat-mdc-list-option.cdk-focused{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.executive-inline-skin{--p-list-leading-width: 56px;--p-list-trailing-min-width: 232px;--p-list-item-padding-x: 14px;--p-list-item-padding-y: 8px;--p-list-item-gap: 10px;--p-list-surface: #f8fafb;--p-list-surface-low: #ffffff;--p-list-item-surface: #ffffff;--p-list-item-hover-surface: #f7fafc;--p-list-item-active-surface: #eef4f8;--p-list-item-selected-surface: #eef4f8;--p-list-foreground: #17324d;--p-list-foreground-muted: #72849a}.executive-inline-skin .list-item-content--row-layout.exec-row-layout{min-height:72px;padding:12px 20px;background:linear-gradient(180deg,#fff,#fbfcfd)!important;border:1px solid #dce5ec!important;border-radius:18px;box-shadow:0 1px 2px #0f223a08,0 4px 14px #0f223a08!important;color:#17324d!important}.executive-inline-skin .mat-mdc-list-item:hover .list-item-content--row-layout.exec-row-layout{background:linear-gradient(180deg,#fff,#f7fafc)!important}.executive-inline-skin .list-item-content--row-layout.exec-row-layout+.item-expansion{margin-left:10px;margin-right:10px}.executive-inline-skin .list-item-content--row-layout.exec-row-layout .list-row-slot{min-height:34px}.executive-inline-skin .list-row-slot--leading{justify-content:center}.executive-inline-skin .list-row-slot--identity{align-items:center;padding-right:4px}.executive-inline-skin .list-row-slot--balance,.executive-inline-skin .list-row-slot--limit,.executive-inline-skin .list-row-slot--risk,.executive-inline-skin .list-row-slot--alerts{justify-content:center;padding-inline:2px}.executive-inline-skin .list-row-slot--owner{justify-content:flex-start;padding-left:0}.executive-inline-skin .list-row-slot--actions{justify-content:flex-end;padding-left:0}.executive-inline-skin .list-row-slot--expand{justify-content:center}.executive-inline-skin .docs-expansion-leading{width:44px;height:44px;display:inline-flex;align-items:center;justify-content:center;border-radius:15px;background:linear-gradient(180deg,#edf4f9,#e6eef5);color:#1b5f86;font-size:1rem;font-weight:800;letter-spacing:.02em;box-shadow:0 1px 3px #1b5f861f;border:1px solid #dbe7f0}.executive-inline-skin .exec-balance-metric,.executive-inline-skin .exec-limit-metric,.executive-inline-skin .exec-risk-metric{min-width:0}.executive-inline-skin .exec-balance-metric .p-list-metric,.executive-inline-skin .exec-limit-metric .p-list-metric,.executive-inline-skin .exec-risk-metric .p-list-metric{gap:5px}.executive-inline-skin .exec-balance-metric .p-list-metric__label,.executive-inline-skin .exec-limit-metric .p-list-metric__label,.executive-inline-skin .exec-risk-metric .p-list-metric__label{font-size:.62rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#8090a1}.executive-inline-skin .exec-balance-metric .p-list-metric__value,.executive-inline-skin .exec-limit-metric .p-list-metric__value{font-size:.96rem;font-weight:800;letter-spacing:-.02em;color:#0d1f34;line-height:1.05}.executive-inline-skin .exec-limit-metric .p-list-metric__caption{font-size:.66rem;font-weight:700;color:#8090a1}.executive-inline-skin .exec-limit-metric .p-list-metric__progress{width:68px;justify-self:center;height:3px}.executive-inline-skin .exec-risk-metric{min-width:84px}.executive-inline-skin .exec-risk-metric .p-list-metric{justify-items:center}.executive-inline-skin .exec-risk-metric .p-list-metric__main{gap:6px}.executive-inline-skin .exec-risk-metric .p-list-metric__value{font-size:1.02rem;font-weight:800;letter-spacing:-.024em;line-height:1}.executive-inline-skin .exec-risk-metric .p-list-metric__progress{width:56px;justify-self:center;height:3px}.executive-inline-skin .exec-risk-metric .p-list-metric__caption{font-size:.7rem;font-weight:700;line-height:1.1}.executive-inline-skin .exec-risk-metric .p-list-metric__caption--below-progress{justify-self:center;margin-top:-1px}.executive-inline-skin .exec-risk-metric .p-list-metric__icon mat-icon{width:15px;height:15px;font-size:15px}.executive-inline-skin .list-item-actions{gap:6px;flex-wrap:nowrap}.executive-inline-skin .list-row-slot--actions .list-item-actions{justify-content:flex-end}.executive-inline-skin .list-item-actions button,.executive-inline-skin .expand-toggle{width:30px;height:30px;min-width:30px;border-radius:12px;color:#6d7f93!important}.executive-inline-skin .list-item-actions button mat-icon,.executive-inline-skin .expand-toggle mat-icon{width:18px;height:18px;font-size:18px}.executive-inline-skin .item-expansion{position:relative;margin-top:-10px;padding:22px 26px;background:linear-gradient(180deg,#fcfdfe,#f5f8fb);border-color:#e4eaf0;border-radius:0 0 22px 22px;box-shadow:inset 0 1px #ffffffdb}.lead-image{position:relative;width:96px;height:72px;border-radius:12px;overflow:hidden}.lead-image img{width:100%;height:100%;object-fit:cover;display:block}.lead-image .lead-badge{position:absolute;left:8px;bottom:8px}.inline-image{position:relative;width:20px;height:20px;border-radius:4px;overflow:hidden;display:inline-block;vertical-align:middle}.inline-image img{width:100%;height:100%;object-fit:cover;display:block}.inline-image .inline-badge{position:absolute;left:2px;bottom:2px}.model-media .lead-image{width:136px;height:96px;border-radius:16px}.model-media .list-item-content{gap:16px}.model-media .item-card{--p-list-leading-width: 136px}.model-hotel .lead-image{width:160px;height:110px;border-radius:18px}.model-hotel .item-card{--p-list-leading-width: 160px}.primary,.secondary{display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}.primary{-webkit-line-clamp:1;font-weight:600;color:var(--p-list-foreground)}.secondary{-webkit-line-clamp:1;color:var(--p-list-foreground-muted)}.lines-3 .tertiary{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.item-tile .primary{-webkit-line-clamp:2;font-size:.95rem;line-height:1.25rem}.item-tile .secondary{-webkit-line-clamp:2;font-size:.8rem;line-height:1.1rem}.tertiary{color:var(--p-list-foreground-muted)}.features{display:flex;flex-wrap:wrap;gap:12px;margin-top:6px;color:inherit}.feature{display:inline-flex;align-items:center;gap:6px}.feature-line{display:block}.feature-progress{display:block;width:88px;height:4px;margin-top:6px;border-radius:999px;background:#dbe5eb;overflow:hidden}.feature-progress__value{display:block;height:100%;border-radius:inherit;background:#2f9b69}.meta{color:var(--p-list-foreground);font-size:var(--p-list-meta-size);font-weight:var(--p-list-meta-weight);font-variant-numeric:tabular-nums;max-width:100%;min-width:0;display:inline-flex;align-items:center;justify-content:flex-end;gap:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.meta>span,.meta praxis-rich-content{min-width:0;display:inline-flex;align-items:center;justify-content:flex-end;gap:4px;line-height:1.15;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.meta .mat-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;line-height:1;vertical-align:middle}.trailing{color:var(--p-list-foreground-muted);margin-left:0;max-width:100%;min-width:0;display:inline-flex;justify-content:flex-end;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.trailing praxis-rich-content{min-width:0}:host ::ng-deep .meta praxis-rich-content .prx-rich-node[data-rich-node-type=badge],:host ::ng-deep .trailing praxis-rich-content .prx-rich-node[data-rich-node-type=badge],:host ::ng-deep .list-row-slot praxis-rich-content .prx-rich-node[data-rich-node-type=badge]{background:transparent;color:inherit}:host ::ng-deep .meta praxis-rich-content .prx-rich-node[data-rich-node-type=badge] .prx-rich-badge,:host ::ng-deep .trailing praxis-rich-content .prx-rich-node[data-rich-node-type=badge] .prx-rich-badge,:host ::ng-deep .list-row-slot praxis-rich-content .prx-rich-node[data-rich-node-type=badge] .prx-rich-badge{min-height:22px;padding:1px 9px;border:1px solid var(--p-list-chip-border-color, transparent);background:var(--p-list-chip-background, var(--md-sys-color-primary-container, #e8def8));color:var(--p-list-chip-color, var(--md-sys-color-on-primary-container, #21005d));line-height:18px}.section-header{font-size:.85rem;color:var(--p-list-foreground-muted);padding:8px 12px}.praxis-list-root mat-list,.praxis-list-root mat-selection-list{display:block;padding-bottom:12px}.praxis-list-root .mat-divider{margin-left:var(--p-list-item-padding-x);margin-right:var(--p-list-item-padding-x)}.item-expansion{margin:0 var(--p-list-item-padding-x) var(--p-list-item-stack-gap);padding:14px 16px;background:color-mix(in srgb,var(--p-list-surface) 88%,white 12%);border:1px solid var(--md-sys-color-outline-variant);border-radius:calc(var(--p-list-radius) * .75)}.item-expansion-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px}.expansion-section{min-width:0;display:grid;gap:10px;align-content:start}.expansion-section-title{font-size:.78rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--p-list-foreground-muted)}.expansion-info-list,.expansion-timeline,.expansion-key-value{display:grid;gap:10px}.expansion-info-item,.expansion-timeline-item{display:grid;gap:4px;padding:10px 12px;border-radius:14px;background:var(--p-list-item-surface);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent 28%)}.expansion-info-title,.expansion-timeline-title{font-size:.92rem;font-weight:600;color:var(--p-list-foreground)}.expansion-info-value,.expansion-timeline-meta,.expansion-timeline-description,.expansion-empty{font-size:.82rem;line-height:1.45;color:var(--p-list-foreground-muted)}.expansion-chip-list{display:flex;flex-wrap:wrap;gap:8px}.expansion-chip{background:var(--p-list-item-surface);border:1px solid var(--md-sys-color-outline-variant)}.expansion-key-value{margin:0}.expansion-key-value-row{display:grid;gap:4px;padding:10px 12px;border-radius:14px;background:var(--p-list-item-surface);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent 28%)}.expansion-key-value-row dt,.expansion-key-value-row dd{margin:0}.expansion-key-value-row dt{font-size:.78rem;font-weight:600;color:var(--p-list-foreground-muted)}.expansion-key-value-row dd{font-size:.9rem;color:var(--p-list-foreground)}.cards-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,max(340px,var(--p-list-tile-minW))),1fr));gap:max(var(--p-list-tile-gap),16px)}.tiles-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(var(--p-list-tile-minW),1fr));gap:var(--p-list-tile-gap)}.item-tile{position:relative;display:flex;flex-direction:column;gap:10px;padding:var(--p-list-tile-padding);border-radius:var(--p-list-tile-radius);background:var(--p-list-surface);border:var(--p-list-border);color:var(--p-list-foreground);cursor:pointer;min-height:160px;transition:box-shadow .16s ease-out,border-color .16s ease-out,transform .12s ease-out,background .16s ease-out}.item-tile:hover{box-shadow:var(--md-sys-elevation-level2);border-color:var(--md-sys-color-outline-variant);background:var(--p-list-item-hover-surface)}.item-tile:active{transform:scale(var(--p-list-tile-press-scale));background:var(--p-list-item-active-surface)}.item-tile:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.tile-media{width:100%;aspect-ratio:var(--p-list-tile-media-ratio);border-radius:var(--p-list-tile-media-radius);background:var(--p-list-item-surface);border:1px solid var(--md-sys-color-outline-variant);display:grid;place-items:center;overflow:hidden}.tile-media img{width:100%;height:100%;object-fit:cover;display:block}.tile-media mat-icon{font-size:28px;height:28px;width:28px;color:var(--p-list-foreground-muted)}.tile-body{display:grid;gap:6px;min-width:0}.tile-meta{display:inline-flex;align-items:center;gap:6px;font-size:.75rem;color:var(--p-list-foreground-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tile-trailing{font-size:.75rem;color:var(--p-list-foreground-muted);white-space:nowrap}.tiles-features{display:flex;flex-wrap:wrap;gap:8px;color:inherit}.tile-status{position:absolute;top:10px;right:10px}.tile-actions{position:absolute;top:8px;left:8px;display:flex;gap:4px;opacity:0;pointer-events:none;transform:translateY(-2px);transition:opacity .16s ease-out,transform .16s ease-out}.item-tile:hover .tile-actions,.item-tile:focus-visible .tile-actions,.item-tile:focus-within .tile-actions{opacity:1;pointer-events:auto;transform:translateY(0)}@media(hover:none){.tile-actions{opacity:1;pointer-events:auto;transform:none}}@media(max-width:600px){.tiles-grid{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}}@media(prefers-reduced-motion:reduce){.item-tile,.tile-actions{transition:none}.item-tile:active{transform:none}.item-card{transition:none}.item-card:active{transform:none}}.item-card{padding:max(var(--p-list-tile-padding),16px);display:flex;flex-direction:column;min-height:220px;position:relative;cursor:pointer;background:var(--p-list-surface);border:var(--p-list-border);border-radius:var(--p-list-tile-radius);box-shadow:var(--p-list-shadow);transition:box-shadow .16s ease-out,border-color .16s ease-out,transform .12s ease-out,background .16s ease-out;--p-list-leading-width: 96px;--p-list-trailing-min-width: 0px}.item-card .list-item-content{display:block;padding:0;min-width:0;overflow:hidden}.item-card .list-item-leading{position:absolute;top:18px;left:18px;min-height:0;z-index:1}.item-card .leading-placeholder{width:36px;height:36px}.item-card .leading-icon.mat-icon,.item-card .list-item-leading>mat-icon{width:36px;height:36px;line-height:36px;font-size:20px;display:inline-flex;align-items:center;justify-content:center;border-radius:14px;background:linear-gradient(145deg,color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),color-mix(in srgb,var(--md-sys-color-primary-container) 34%,transparent));color:var(--md-sys-color-primary)}.item-card .list-item-text{display:grid;gap:9px;padding-left:50px;width:100%;min-width:0;box-sizing:border-box}.item-card .primary{padding-right:min(50%,160px);font-size:1rem;font-weight:800;letter-spacing:-.01em;line-height:1.18;min-width:0;overflow-wrap:anywhere}.item-card .secondary{color:var(--p-list-foreground-muted);font-weight:600;line-height:1.35;min-width:0;overflow-wrap:anywhere}.item-card .features{display:flex;flex-direction:column;gap:8px;margin-top:2px}.item-card .feature{display:inline-flex;align-items:start;gap:8px;min-width:0;max-width:100%;overflow-wrap:anywhere}.item-card .feature mat-icon{margin-top:1px;color:var(--p-list-foreground-muted);opacity:.86}.item-card .list-item-trailing{min-width:var(--p-list-trailing-min-width);grid-column:1/-1;align-items:stretch;padding-right:0}.card-detail-strip{display:grid;grid-template-columns:repeat(auto-fit,minmax(148px,1fr));gap:10px;margin-top:16px}.card-detail{min-width:0;padding:11px 12px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 82%,transparent);border-radius:14px;background:linear-gradient(145deg,color-mix(in srgb,var(--md-sys-color-primary-container) 22%,transparent),color-mix(in srgb,var(--p-list-surface) 92%,transparent));box-shadow:inset 0 1px #ffffff14}.card-detail__label{display:block;margin-bottom:3px;color:var(--p-list-foreground-muted);font-size:.68rem;font-weight:700;letter-spacing:.08em;line-height:1.1;text-transform:uppercase}.card-detail__value{display:block;min-width:0;color:var(--p-list-foreground);font-size:.94rem;font-weight:800;line-height:1.2;overflow-wrap:normal;word-break:normal}.card-detail__value,.card-detail__value *{white-space:nowrap}.card-detail--balance .card-detail__value{color:var(--md-sys-color-primary)}.card-detail--limit .card-detail__value{color:var(--md-sys-color-error)}.card-detail--risk .card-detail__value{color:var(--md-sys-color-tertiary, var(--p-list-foreground))}.item-card:hover{box-shadow:var(--md-sys-elevation-level3);border-color:var(--md-sys-color-outline-variant);background:var(--p-list-item-hover-surface)}.item-card:active{transform:scale(var(--p-list-tile-press-scale));background:var(--p-list-item-active-surface)}.item-card:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.card-actions{display:flex;align-items:center;justify-content:flex-end;gap:4px;padding:6px 8px 4px;border-top:1px solid var(--md-sys-color-outline-variant);margin-top:auto}.status-overlay{position:absolute;top:14px;right:14px;max-width:min(42%,148px);overflow:hidden;pointer-events:none}.status-overlay .mat-mdc-chip{pointer-events:auto;width:100%;min-width:0;max-width:100%}.status-overlay .mdc-evolution-chip__text-label,.status-overlay .mat-mdc-chip-action-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.leading-icon.mat-icon{width:36px;height:36px;line-height:36px;font-size:20px;display:inline-flex;align-items:center;justify-content:center;border-radius:999px;background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.mat-mdc-chip{--mdc-chip-container-height: var(--p-list-chip-height);font-size:var(--p-list-chip-font-size)}.meta .mat-icon{font-size:18px;height:18px;width:18px}.features{overflow:hidden}@keyframes shimmer{0%{background-position:-468px 0}to{background-position:468px 0}}.skeleton{animation-duration:1.2s;animation-fill-mode:forwards;animation-iteration-count:infinite;animation-name:shimmer;animation-timing-function:linear;background:linear-gradient(to right,var(--md-sys-color-surface-container-low) 8%,var(--md-sys-color-surface-container) 18%,var(--md-sys-color-surface-container-low) 33%);background-size:800px 104px;position:relative}.skeleton-avatar{width:32px;height:32px;border-radius:50%}.skeleton-line{height:10px;margin:6px 0;border-radius:6px}.skeleton-chip{width:48px;height:18px;border-radius:999px}.chip-outlined.mat-mdc-chip{background:transparent!important;border:1px solid currentColor}.w-60{width:60%}.w-40{width:40%}.list-remote-tools{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-top:14px;padding:14px 4px 8px;border-top:1px solid var(--md-sys-color-outline-variant);position:relative;z-index:1;background:var(--p-list-surface-low);border-radius:calc(var(--p-list-radius) * .75)}.list-export-tools{display:flex;justify-content:flex-end;margin:0 0 12px}.list-export-tools button{border-radius:8px}.list-export-tools .mat-icon{margin-right:6px}.paginator-sort{width:180px}.paginator-search{min-width:220px}.mat-mdc-paginator{margin-top:14px;border-top:1px solid var(--md-sys-color-outline-variant);background:var(--p-list-surface-low);color:var(--p-list-foreground)}:host ::ng-deep .mat-mdc-paginator .mat-mdc-paginator-container{min-height:40px;padding:4px 8px;gap:6px}:host ::ng-deep .mat-mdc-paginator .mat-mdc-paginator-page-size-select{margin-left:8px;width:92px}:host ::ng-deep .mat-mdc-paginator .mat-mdc-paginator-page-size-select .mat-mdc-text-field-wrapper{--mdc-outlined-text-field-container-color: color-mix(in srgb, var(--p-list-surface-low) 92%, var(--md-sys-color-on-surface) 8%);--mdc-outlined-text-field-input-text-color: var(--p-list-foreground);min-height:40px;padding-inline:12px 8px;background:color-mix(in srgb,var(--p-list-surface-low) 92%,var(--md-sys-color-on-surface) 8%)!important;background-color:color-mix(in srgb,var(--p-list-surface-low) 92%,var(--md-sys-color-on-surface) 8%)!important;color:var(--p-list-foreground)!important}:host ::ng-deep .mat-mdc-paginator .mat-mdc-paginator-page-size-select .mat-mdc-form-field-infix{min-height:40px;padding-block:8px;width:auto}:host ::ng-deep .mat-mdc-paginator .mat-mdc-paginator-page-size-select .mat-mdc-select-trigger{min-height:24px;padding-right:2px;color:var(--p-list-foreground)}:host ::ng-deep .mat-mdc-paginator .mat-mdc-paginator-range-label,:host ::ng-deep .mat-mdc-paginator .mat-mdc-select-value-text{font-size:12px;color:var(--p-list-foreground-muted)}.list-paginator-total{margin-top:14px;padding:4px 8px 0;font-size:12px;color:var(--p-list-foreground-muted)}:host ::ng-deep .mat-mdc-paginator.hide-range .mat-mdc-paginator-range-label{display:none}::ng-deep .praxis-list-paginator-select-panel.mat-mdc-select-panel{--mat-select-panel-background-color: var(--md-sys-color-surface-container, #fff);background:var(--md-sys-color-surface-container, #fff)!important;background-color:var(--md-sys-color-surface-container, #fff)!important;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #d7dbe5) 70%,transparent);box-shadow:0 12px 28px color-mix(in srgb,var(--md-sys-color-shadow, #000) 28%,transparent)}::ng-deep .praxis-list-paginator-select-panel .mat-mdc-option{--mat-option-label-text-color: var(--md-sys-color-on-surface, #111827);color:var(--md-sys-color-on-surface, #111827)!important}::ng-deep .praxis-list-paginator-select-panel .mat-mdc-option:hover:not(.mdc-list-item--disabled),::ng-deep .praxis-list-paginator-select-panel .mat-mdc-option.mat-mdc-option-active{--mat-option-hover-state-layer-color: color-mix(in srgb, var(--md-sys-color-primary, #2f7cff) 14%, transparent);background:color-mix(in srgb,var(--md-sys-color-primary, #2f7cff) 14%,transparent)!important;color:var(--md-sys-color-on-surface, #111827)!important}.muted{color:var(--p-list-foreground-muted)}@media(max-width:720px){.list-item-content{grid-template-columns:var(--p-list-leading-width) minmax(0,1fr)}.list-item-trailing{grid-column:1/-1;padding-right:0;align-items:flex-start;text-align:start}.item-expansion{margin-left:0;margin-right:0}.list-remote-tools{align-items:stretch}.paginator-sort,.paginator-search{width:100%;min-width:0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i3.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i3.MatSelectionList, selector: "mat-selection-list", inputs: ["color", "compareWith", "multiple", "hideSingleSelectionIndicator", "disabled"], outputs: ["selectionChange"], exportAs: ["matSelectionList"] }, { kind: "component", type: i3.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "component", type: i3.MatListOption, selector: "mat-list-option", inputs: ["togglePosition", "checkboxPosition", "color", "value", "selected"], outputs: ["selectedChange"], exportAs: ["matListOption"] }, { kind: "component", type: i3.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i5.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i5$1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i5$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i5$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i8$1.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i10$1.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "ngmodule", type: MatSnackBarModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.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: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i12.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: PraxisAiAssistantShellComponent, selector: "praxis-ai-assistant-shell", inputs: ["labels", "mode", "state", "contextItems", "attachments", "messages", "quickReplies", "prompt", "statusText", "errorText", "testIdPrefix", "panelTestId", "submitTestId", "applyTestId", "primaryAction", "secondaryActions", "governanceActions", "busy", "canSubmit", "canApply", "submitOnEnter", "showAttachAction", "enablePastedAttachments", "enableFileAttachments", "attachmentAccept", "attachmentMultiple", "draggable", "resizable", "minWidth", "minHeight", "margin", "layout"], outputs: ["promptChange", "submitPrompt", "apply", "retryTurn", "cancelTurn", "shellAction", "close", "attach", "attachmentsPasted", "attachmentsSelected", "removeAttachment", "messageAction", "editMessage", "resendMessage", "quickReply", "layoutChange"] }, { kind: "component", type: ExecutiveBadgeComponent, selector: "praxis-executive-badge", inputs: ["ariaLabel", "label", "variant"] }, { kind: "component", type: ExecutiveAlertsComponent, selector: "praxis-executive-alerts", inputs: ["alerts"] }, { kind: "component", type: ExecutiveOwnerComponent, selector: "praxis-executive-owner", inputs: ["name"] }, { kind: "component", type: PraxisRichContent, selector: "praxis-rich-content", inputs: ["document", "nodes", "context", "hostCapabilities", "layout", "rootClassName"] }, { kind: "component", type: PraxisListMetricComponent, selector: "praxis-list-metric", inputs: ["metric"] }, { kind: "component", type: PraxisListComposeComponent, selector: "praxis-list-compose", inputs: ["compose"] }, { kind: "component", type: PraxisListRuntimeComponentComponent, selector: "praxis-list-runtime-component", inputs: ["component"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13884
13881
|
}
|
|
13885
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
13882
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisList, decorators: [{
|
|
13886
13883
|
type: Component,
|
|
13887
13884
|
args: [{ selector: 'praxis-list', standalone: true, imports: [
|
|
13888
13885
|
CommonModule,
|
|
@@ -13996,19 +13993,19 @@ class PraxisListWidgetConfigEditor {
|
|
|
13996
13993
|
},
|
|
13997
13994
|
};
|
|
13998
13995
|
}
|
|
13999
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
14000
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
13996
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisListWidgetConfigEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13997
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.14", type: PraxisListWidgetConfigEditor, isStandalone: true, selector: "praxis-list-widget-config-editor", inputs: { inputs: "inputs", widgetKey: "widgetKey" }, viewQueries: [{ propertyName: "listEditor", first: true, predicate: ["listEditor"], descendants: true }], ngImport: i0, template: `
|
|
14001
13998
|
<section data-testid="list-widget-config-editor">
|
|
14002
13999
|
<praxis-list-config-editor #listEditor [config]="config" [listId]="listId" />
|
|
14003
14000
|
</section>
|
|
14004
|
-
`, isInline: true, dependencies: [{ kind: "
|
|
14001
|
+
`, isInline: true, dependencies: [{ kind: "component", type: PraxisListConfigEditor, selector: "praxis-list-config-editor", inputs: ["config", "listId"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
14005
14002
|
}
|
|
14006
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
14003
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisListWidgetConfigEditor, decorators: [{
|
|
14007
14004
|
type: Component,
|
|
14008
14005
|
args: [{
|
|
14009
14006
|
selector: 'praxis-list-widget-config-editor',
|
|
14010
14007
|
standalone: true,
|
|
14011
|
-
imports: [
|
|
14008
|
+
imports: [PraxisListConfigEditor],
|
|
14012
14009
|
template: `
|
|
14013
14010
|
<section data-testid="list-widget-config-editor">
|
|
14014
14011
|
<praxis-list-config-editor #listEditor [config]="config" [listId]="listId" />
|
|
@@ -14738,16 +14735,16 @@ class PraxisListDocPageComponent {
|
|
|
14738
14735
|
form = new FormGroup({
|
|
14739
14736
|
selectedItems: new FormControl(null),
|
|
14740
14737
|
});
|
|
14741
|
-
scenario = signal('operations', ...(ngDevMode ? [{ debugName: "scenario" }] : []));
|
|
14742
|
-
variant = signal('list', ...(ngDevMode ? [{ debugName: "variant" }] : []));
|
|
14743
|
-
density = signal('comfortable', ...(ngDevMode ? [{ debugName: "density" }] : []));
|
|
14744
|
-
lines = signal(2, ...(ngDevMode ? [{ debugName: "lines" }] : []));
|
|
14745
|
-
skin = signal('elevated', ...(ngDevMode ? [{ debugName: "skin" }] : []));
|
|
14746
|
-
featuresMode = signal('icons+labels', ...(ngDevMode ? [{ debugName: "featuresMode" }] : []));
|
|
14747
|
-
selectionMode = signal('multiple', ...(ngDevMode ? [{ debugName: "selectionMode" }] : []));
|
|
14748
|
-
customizationEnabled = signal(false, ...(ngDevMode ? [{ debugName: "customizationEnabled" }] : []));
|
|
14749
|
-
activeTab = signal('template', ...(ngDevMode ? [{ debugName: "activeTab" }] : []));
|
|
14750
|
-
eventLog = signal([], ...(ngDevMode ? [{ debugName: "eventLog" }] : []));
|
|
14738
|
+
scenario = signal('operations', ...(ngDevMode ? [{ debugName: "scenario" }] : /* istanbul ignore next */ []));
|
|
14739
|
+
variant = signal('list', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
14740
|
+
density = signal('comfortable', ...(ngDevMode ? [{ debugName: "density" }] : /* istanbul ignore next */ []));
|
|
14741
|
+
lines = signal(2, ...(ngDevMode ? [{ debugName: "lines" }] : /* istanbul ignore next */ []));
|
|
14742
|
+
skin = signal('elevated', ...(ngDevMode ? [{ debugName: "skin" }] : /* istanbul ignore next */ []));
|
|
14743
|
+
featuresMode = signal('icons+labels', ...(ngDevMode ? [{ debugName: "featuresMode" }] : /* istanbul ignore next */ []));
|
|
14744
|
+
selectionMode = signal('multiple', ...(ngDevMode ? [{ debugName: "selectionMode" }] : /* istanbul ignore next */ []));
|
|
14745
|
+
customizationEnabled = signal(false, ...(ngDevMode ? [{ debugName: "customizationEnabled" }] : /* istanbul ignore next */ []));
|
|
14746
|
+
activeTab = signal('template', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
|
|
14747
|
+
eventLog = signal([], ...(ngDevMode ? [{ debugName: "eventLog" }] : /* istanbul ignore next */ []));
|
|
14751
14748
|
nextLogId = 1;
|
|
14752
14749
|
scenarioOptions = [
|
|
14753
14750
|
{
|
|
@@ -14856,9 +14853,9 @@ class PraxisListDocPageComponent {
|
|
|
14856
14853
|
return [];
|
|
14857
14854
|
}
|
|
14858
14855
|
return OPERATIONS_ITEMS;
|
|
14859
|
-
}, ...(ngDevMode ? [{ debugName: "liveItems" }] : []));
|
|
14856
|
+
}, ...(ngDevMode ? [{ debugName: "liveItems" }] : /* istanbul ignore next */ []));
|
|
14860
14857
|
activeScenarioMeta = computed(() => this.scenarioOptions.find((entry) => entry.id === this.scenario()) ||
|
|
14861
|
-
null, ...(ngDevMode ? [{ debugName: "activeScenarioMeta" }] : []));
|
|
14858
|
+
null, ...(ngDevMode ? [{ debugName: "activeScenarioMeta" }] : /* istanbul ignore next */ []));
|
|
14862
14859
|
liveConfig = computed(() => {
|
|
14863
14860
|
const scenario = this.scenario();
|
|
14864
14861
|
const isCatalog = scenario === 'catalog';
|
|
@@ -15067,7 +15064,7 @@ class PraxisListDocPageComponent {
|
|
|
15067
15064
|
ariaLabel: 'Documentacao viva do Praxis List',
|
|
15068
15065
|
},
|
|
15069
15066
|
};
|
|
15070
|
-
}, ...(ngDevMode ? [{ debugName: "liveConfig" }] : []));
|
|
15067
|
+
}, ...(ngDevMode ? [{ debugName: "liveConfig" }] : /* istanbul ignore next */ []));
|
|
15071
15068
|
skinPreviewItems = CATALOG_ITEMS.slice(0, 2);
|
|
15072
15069
|
skinPreviewConfigs = computed(() => [
|
|
15073
15070
|
'elevated',
|
|
@@ -15098,7 +15095,7 @@ class PraxisListDocPageComponent {
|
|
|
15098
15095
|
statusPosition: 'top-right',
|
|
15099
15096
|
},
|
|
15100
15097
|
},
|
|
15101
|
-
})), ...(ngDevMode ? [{ debugName: "skinPreviewConfigs" }] : []));
|
|
15098
|
+
})), ...(ngDevMode ? [{ debugName: "skinPreviewConfigs" }] : /* istanbul ignore next */ []));
|
|
15102
15099
|
configPreview = computed(() => {
|
|
15103
15100
|
const clone = JSON.parse(JSON.stringify(this.liveConfig()));
|
|
15104
15101
|
if (clone.dataSource?.data) {
|
|
@@ -15107,7 +15104,7 @@ class PraxisListDocPageComponent {
|
|
|
15107
15104
|
];
|
|
15108
15105
|
}
|
|
15109
15106
|
return JSON.stringify(clone, null, 2);
|
|
15110
|
-
}, ...(ngDevMode ? [{ debugName: "configPreview" }] : []));
|
|
15107
|
+
}, ...(ngDevMode ? [{ debugName: "configPreview" }] : /* istanbul ignore next */ []));
|
|
15111
15108
|
selectedSnippet = computed(() => {
|
|
15112
15109
|
switch (this.activeTab()) {
|
|
15113
15110
|
case 'template':
|
|
@@ -15121,7 +15118,7 @@ class PraxisListDocPageComponent {
|
|
|
15121
15118
|
default:
|
|
15122
15119
|
return this.templateSnippet;
|
|
15123
15120
|
}
|
|
15124
|
-
}, ...(ngDevMode ? [{ debugName: "selectedSnippet" }] : []));
|
|
15121
|
+
}, ...(ngDevMode ? [{ debugName: "selectedSnippet" }] : /* istanbul ignore next */ []));
|
|
15125
15122
|
templateSnippet = `<praxis-list-doc-page></praxis-list-doc-page>`;
|
|
15126
15123
|
tsSnippet = `import { Component } from '@angular/core';
|
|
15127
15124
|
import { PraxisListDocPageComponent } from '@praxisui/list';
|
|
@@ -15513,23 +15510,22 @@ export class ListDocHostComponent {}`;
|
|
|
15513
15510
|
return [entry, ...curr].slice(0, 14);
|
|
15514
15511
|
});
|
|
15515
15512
|
}
|
|
15516
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
15517
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
15513
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisListDocPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
15514
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisListDocPageComponent, isStandalone: true, selector: "praxis-list-doc-page", providers: [
|
|
15518
15515
|
{ provide: API_URL, useValue: { default: { baseUrl: '/api' } } },
|
|
15519
15516
|
{
|
|
15520
15517
|
provide: ASYNC_CONFIG_STORAGE,
|
|
15521
15518
|
useFactory: () => new LocalStorageAsyncAdapter(new LocalStorageConfigService()),
|
|
15522
15519
|
},
|
|
15523
15520
|
{ provide: SETTINGS_PANEL_DATA, useValue: {} },
|
|
15524
|
-
], ngImport: i0, template: "<section class=\"list-doc-page\">\n <section class=\"lab\">\n <aside class=\"controls\">\n <article class=\"panel panel--controls\">\n <h2>Playground</h2>\n <p class=\"muted\">\n Ajuste os controles e observe o comportamento da lista em tempo real.\n </p>\n\n <div class=\"control-block\">\n <p class=\"control-block__title\">Cenario</p>\n <div class=\"chip-row\">\n @for (option of scenarioOptions; track option.id) {\n <button\n type=\"button\"\n class=\"chip-button\"\n [attr.data-testid]=\"'scenario-' + option.id\"\n [class.is-active]=\"scenario() === option.id\"\n (click)=\"setScenario(option.id)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n @if (activeScenarioMeta(); as activeScenario) {\n <p class=\"control-hint\">{{ activeScenario.helper }}</p>\n }\n </div>\n\n <div class=\"control-block\">\n <p class=\"control-block__title\">Variant</p>\n <div class=\"chip-row\">\n @for (option of variantOptions; track option.id) {\n <button\n type=\"button\"\n class=\"chip-button\"\n [attr.data-testid]=\"'variant-' + option.id\"\n [class.is-active]=\"variant() === option.id\"\n [disabled]=\"\n isExecutiveExpansionScenario() && option.id !== 'list'\n \"\n (click)=\"setVariant(option.id)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n @if (isExecutiveExpansionScenario()) {\n <p class=\"control-hint\">\n Este cenario fixa <code>variant = list</code> para validar a\n detail row inline da V1.\n </p>\n }\n </div>\n\n <div class=\"control-grid\">\n <div class=\"control-block\">\n <p class=\"control-block__title\">Density</p>\n <div class=\"chip-row\">\n @for (option of densityOptions; track option.id) {\n <button\n type=\"button\"\n class=\"chip-button chip-button--small\"\n [attr.data-testid]=\"'density-' + option.id\"\n [class.is-active]=\"density() === option.id\"\n (click)=\"setDensity(option.id)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n </div>\n\n <div class=\"control-block\">\n <p class=\"control-block__title\">Lines</p>\n <div class=\"chip-row\">\n @for (count of [1, 2, 3]; track count) {\n <button\n type=\"button\"\n class=\"chip-button chip-button--small\"\n [attr.data-testid]=\"'lines-' + count\"\n [class.is-active]=\"lines() === count\"\n [disabled]=\"isExecutiveExpansionScenario()\"\n (click)=\"setLines($any(count))\"\n >\n {{ count }}\n </button>\n }\n </div>\n @if (isExecutiveExpansionScenario()) {\n <p class=\"control-hint\">\n Linhas travadas em 2 para manter aderencia a referencia\n executiva.\n </p>\n }\n </div>\n </div>\n\n <div class=\"control-block\">\n <p class=\"control-block__title\">Skin</p>\n <div class=\"chip-row\">\n @for (option of skinOptions; track option.id) {\n <button\n type=\"button\"\n class=\"chip-button chip-button--small\"\n [attr.data-testid]=\"'skin-' + option.id\"\n [class.is-active]=\"skin() === option.id\"\n (click)=\"setSkin(option.id)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n </div>\n\n <div class=\"control-block\">\n <p class=\"control-block__title\">Features mode</p>\n <div class=\"chip-row\">\n @for (option of featuresModeOptions; track option.id) {\n <button\n type=\"button\"\n class=\"chip-button chip-button--small\"\n [attr.data-testid]=\"'features-' + option.id\"\n [class.is-active]=\"featuresMode() === option.id\"\n (click)=\"setFeaturesMode(option.id)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n </div>\n\n <div class=\"control-block\">\n <p class=\"control-block__title\">Selection</p>\n <div class=\"chip-row\">\n @for (option of selectionOptions; track option.id) {\n <button\n type=\"button\"\n class=\"chip-button chip-button--small\"\n [attr.data-testid]=\"'selection-' + option.id\"\n [class.is-active]=\"selectionMode() === option.id\"\n [disabled]=\"isExecutiveExpansionScenario()\"\n (click)=\"setSelectionMode(option.id)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n @if (isExecutiveExpansionScenario()) {\n <p class=\"control-hint\">\n Selecao desabilitada neste exemplo para evitar conflito com\n expansao por linha.\n </p>\n }\n </div>\n\n <div class=\"control-block\">\n <p class=\"control-block__title\">Customization mode</p>\n <div class=\"chip-row\">\n <button\n type=\"button\"\n class=\"chip-button chip-button--small\"\n data-testid=\"customization-on\"\n [class.is-active]=\"customizationEnabled()\"\n (click)=\"setCustomizationMode(true)\"\n >\n On\n </button>\n <button\n type=\"button\"\n class=\"chip-button chip-button--small\"\n data-testid=\"customization-off\"\n [class.is-active]=\"!customizationEnabled()\"\n (click)=\"setCustomizationMode(false)\"\n >\n Off\n </button>\n </div>\n <p class=\"control-hint\">\n Ative para exibir editor interno + AI assistant do componente.\n </p>\n </div>\n </article>\n\n <article class=\"panel panel--events\">\n <div class=\"panel__head\">\n <h2>Eventos</h2>\n <button\n type=\"button\"\n class=\"ghost-btn\"\n data-testid=\"clear-log\"\n (click)=\"clearLog()\"\n >\n Limpar log\n </button>\n </div>\n @if (eventLog().length === 0) {\n <p class=\"muted\">\n Sem eventos ainda. Clique em itens, acoes ou selecao.\n </p>\n } @else {\n <ul class=\"event-log\">\n @for (entry of eventLog(); track entry.id) {\n <li>{{ entry.message }}</li>\n }\n </ul>\n }\n </article>\n </aside>\n\n <article class=\"panel panel--live\">\n <h2>Exemplo live</h2>\n <p class=\"muted\">\n Mostra list/cards/tiles com os mesmos dados e configura em tempo real.\n </p>\n @if (scenario() === \"executive-expansion\") {\n <p class=\"muted\">\n Cenario canonico da V1 para validar linha executiva com detail row\n inline e owner de expansao dedicado.\n </p>\n }\n <praxis-list\n listId=\"praxis-list-doc-live\"\n componentInstanceId=\"main-showcase\"\n [config]=\"liveConfig()\"\n [form]=\"form\"\n [enableCustomization]=\"customizationEnabled()\"\n (itemClick)=\"handleItemClick($event)\"\n (actionClick)=\"handleActionClick($event)\"\n (selectionChange)=\"handleSelectionChange($event)\"\n />\n </article>\n </section>\n\n <section class=\"skin-matrix\">\n <div class=\"section-head\">\n <h2>Matriz de skins</h2>\n <p>\n Preview rapido de todos os estilos visuais disponiveis no componente.\n </p>\n </div>\n <div class=\"skin-matrix__grid\">\n @for (preview of skinPreviewConfigs(); track preview.type) {\n <article class=\"panel skin-preview\">\n <h3>{{ preview.type }}</h3>\n <praxis-list-skin-preview\n [config]=\"preview.config\"\n [items]=\"skinPreviewItems\"\n />\n </article>\n }\n </div>\n </section>\n\n <section class=\"code-lab panel\">\n <div class=\"section-head\">\n <h2>Snippets de referencia</h2>\n <p>\n Use os blocos abaixo para replicar os exemplos no host ou no site de\n documentacao.\n </p>\n </div>\n\n <div class=\"code-tabs\">\n @for (tab of codeTabs; track tab.id) {\n <button\n type=\"button\"\n class=\"chip-button\"\n [attr.data-testid]=\"'tab-' + tab.id\"\n [class.is-active]=\"activeTab() === tab.id\"\n (click)=\"setCodeTab(tab.id)\"\n >\n {{ tab.label }}\n </button>\n }\n </div>\n\n <pre class=\"code-box\"><code>{{ selectedSnippet() }}</code></pre>\n </section>\n\n <section class=\"notes panel\">\n <h2>Notas de arquitetura</h2>\n <ul>\n <li>\n <strong>Persistencia:</strong> sempre definir <code>listId</code>; para\n multiplas instancias, use <code>componentInstanceId</code>.\n </li>\n <li>\n <strong>Local vs remoto:</strong> <code>dataSource.data</code> tem\n precedencia para preview local; <code>resourcePath</code> define o\n contrato remoto.\n </li>\n <li>\n <strong>Toolbar remota:</strong> controles de paginacao/sort/busca\n refletem o contrato; a semantica completa de filtro/paginacao depende do\n backend em <code>/filter</code>.\n </li>\n <li>\n <strong>Editor interno:</strong> habilite\n <code>enableCustomization</code> quando o host tiver os providers de\n Settings Panel e AI.\n </li>\n <li>\n <strong>A11y:</strong> configure <code>a11y.ariaLabel</code>/<code\n >ariaLabelledBy</code\n >\n e mantenha labels de acoes explicitas.\n </li>\n <li>\n <strong>Expansao inline V1:</strong> suportada apenas em\n <code>layout.variant = list</code>.\n </li>\n <li>\n <strong>Selecao + expansao:</strong> quando houver selecao, o trigger\n canonico e por icone para evitar ambiguidade operacional.\n </li>\n </ul>\n </section>\n</section>\n", styles: [":host{display:block}.list-doc-page{--doc-bg: linear-gradient(160deg, #eef5ff 0%, #f9fbff 48%, #e8f7ef 100%);--doc-surface: rgba(255, 255, 255, .86);--doc-surface-strong: #ffffff;--doc-border: #cfdae8;--doc-text: #10243a;--doc-muted: #4d6075;--doc-accent: #0b6dff;--doc-accent-2: #0f9d58;--doc-accent-soft: color-mix(in srgb, var(--doc-accent) 15%, #ffffff);display:grid;gap:20px;padding:18px;border-radius:22px;border:1px solid var(--doc-border);color:var(--doc-text);background:var(--doc-bg);font-family:Manrope,Segoe UI,sans-serif}.hero{display:grid;gap:16px;grid-template-columns:minmax(0,1.3fr) minmax(0,1fr)}.hero__copy{border-radius:18px;border:1px solid var(--doc-border);padding:18px;background:radial-gradient(120% 130% at 0% 0%,rgba(11,109,255,.2),transparent 56%),var(--doc-surface-strong);box-shadow:0 14px 24px #0a254c14}.hero__eyebrow{margin:0 0 8px;font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:#385f9c;font-weight:700}.hero h1{margin:0;font-size:clamp(1.24rem,2.8vw,1.86rem);line-height:1.25}.hero h1 code{background:color-mix(in srgb,#0b6dff 12%,#ffffff);border:1px solid color-mix(in srgb,#0b6dff 28%,#cad6ec);border-radius:8px;padding:2px 6px;font-size:.9em}.hero p{margin:10px 0 0;color:var(--doc-muted)}.hero__badges{margin-top:14px;display:flex;flex-wrap:wrap;gap:8px}.hero__badges span{border-radius:999px;border:1px solid color-mix(in srgb,#0f9d58 26%,#cad9d2);background:color-mix(in srgb,#0f9d58 12%,#ffffff);color:#0f5d39;padding:5px 10px;font-size:12px;font-weight:700}.hero__panel{border-radius:18px;border:1px solid var(--doc-border);padding:16px;background:var(--doc-surface);box-shadow:0 10px 20px #182e5414}.hero__panel-title{margin:0 0 10px;font-size:13px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:#345684}.hero__panel ul{margin:0;padding:0;list-style:none;display:grid;gap:8px}.hero__panel li{display:grid;gap:2px;padding:8px 10px;border-radius:10px;border:1px solid color-mix(in srgb,#274f82 14%,#d1d8e6);background:#fff}.hero__panel strong{font-size:12px}.hero__panel span{color:var(--doc-muted);font-size:12px}.lab{display:grid;gap:14px;grid-template-columns:minmax(260px,340px) minmax(0,1fr);align-items:start}.controls{display:grid;gap:12px}.panel{border-radius:16px;border:1px solid var(--doc-border);background:var(--doc-surface);box-shadow:0 8px 18px #1b2d4f12}.panel--controls,.panel--events{padding:14px}.panel--controls h2,.panel--live h2,.panel--events h2,.skin-preview h3,.code-lab h2,.notes h2{margin:0}.panel--live{padding:14px;min-width:0}.muted{margin:6px 0 0;color:var(--doc-muted);font-size:13px}.control-block{margin-top:12px}.control-grid{display:grid;gap:10px;grid-template-columns:repeat(2,minmax(0,1fr))}.control-block__title{margin:0 0 6px;font-size:12px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:#325d96}.control-hint{margin:8px 0 0;color:var(--doc-muted);font-size:12px}.chip-row{display:flex;gap:7px;flex-wrap:wrap}.chip-button{min-height:30px;border-radius:999px;border:1px solid color-mix(in srgb,#225ea9 30%,#c8d3e3);background:#f4f8ff;color:#1f3f69;padding:0 12px;font-size:12px;font-weight:700;cursor:pointer;transition:.14s ease;transition-property:border-color,background,color,transform}.chip-button--small{min-height:28px;padding-inline:10px;font-size:11px}.chip-button:hover{transform:translateY(-1px);border-color:color-mix(in srgb,var(--doc-accent) 44%,#a6bad7)}.chip-button.is-active{border-color:color-mix(in srgb,var(--doc-accent) 62%,#6f8aa9);background:linear-gradient(140deg,#1565d8,#0f9360);color:#fff}.chip-button:focus-visible,.ghost-btn:focus-visible{outline:2px solid color-mix(in srgb,var(--doc-accent) 65%,#416688);outline-offset:1px}.panel__head{display:flex;gap:10px;justify-content:space-between;align-items:center}.ghost-btn{min-height:30px;border-radius:8px;border:1px solid color-mix(in srgb,#173d6e 32%,#cdd7e4);background:#fff;color:#1f3d66;font-size:12px;font-weight:700;padding:0 10px;cursor:pointer}.event-log{margin:10px 0 0;padding:0;list-style:none;display:grid;gap:6px}.event-log li{border-radius:9px;border:1px solid color-mix(in srgb,#34598a 16%,#ccd7e6);background:#fff;padding:7px 9px;font-family:JetBrains Mono,Consolas,monospace;font-size:12px;color:#273e5f}.skin-matrix{display:grid;gap:10px}.section-head h2{margin:0}.section-head p{margin:4px 0 0;color:var(--doc-muted)}.skin-matrix__grid{display:grid;gap:12px;width:100%;max-width:1840px;margin-inline:auto;align-items:start;grid-template-columns:repeat(auto-fit,minmax(min(100%,420px),1fr))}.skin-preview{padding:12px;min-width:0}.skin-preview h3{margin-bottom:8px;text-transform:capitalize}.code-lab{padding:14px}.code-tabs{margin-top:12px;display:flex;gap:8px;flex-wrap:wrap}.code-box{margin:12px 0 0;border-radius:12px;border:1px solid color-mix(in srgb,#2d3f58 36%,#8194aa);background:#0d1420;color:#d9ecff;padding:12px;max-height:360px;overflow:auto;font-size:12px;line-height:1.55}.notes{padding:14px}.notes ul{margin:10px 0 0;padding-left:18px;display:grid;gap:8px}.notes li{color:#21354f}.notes code{background:color-mix(in srgb,#0b6dff 12%,#ffffff);border:1px solid color-mix(in srgb,#0b6dff 30%,#c8d5e7);border-radius:6px;padding:1px 5px}@media(max-width:1060px){.hero,.lab{grid-template-columns:1fr}}@media(max-width:700px){.list-doc-page{padding:12px}.control-grid{grid-template-columns:1fr}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: PraxisList, selector: "praxis-list", inputs: ["config", "listId", "componentInstanceId", "configPersistenceStrategy", "queryContext", "form", "enableCustomization"], outputs: ["itemClick", "actionClick", "selectionChange", "exportAction"] }, { kind: "component", type: PraxisListSkinPreviewComponent, selector: "praxis-list-skin-preview", inputs: ["config", "items", "theme"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
15521
|
+
], ngImport: i0, template: "<section class=\"list-doc-page\">\n <section class=\"lab\">\n <aside class=\"controls\">\n <article class=\"panel panel--controls\">\n <h2>Playground</h2>\n <p class=\"muted\">\n Ajuste os controles e observe o comportamento da lista em tempo real.\n </p>\n\n <div class=\"control-block\">\n <p class=\"control-block__title\">Cenario</p>\n <div class=\"chip-row\">\n @for (option of scenarioOptions; track option.id) {\n <button\n type=\"button\"\n class=\"chip-button\"\n [attr.data-testid]=\"'scenario-' + option.id\"\n [class.is-active]=\"scenario() === option.id\"\n (click)=\"setScenario(option.id)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n @if (activeScenarioMeta(); as activeScenario) {\n <p class=\"control-hint\">{{ activeScenario.helper }}</p>\n }\n </div>\n\n <div class=\"control-block\">\n <p class=\"control-block__title\">Variant</p>\n <div class=\"chip-row\">\n @for (option of variantOptions; track option.id) {\n <button\n type=\"button\"\n class=\"chip-button\"\n [attr.data-testid]=\"'variant-' + option.id\"\n [class.is-active]=\"variant() === option.id\"\n [disabled]=\"\n isExecutiveExpansionScenario() && option.id !== 'list'\n \"\n (click)=\"setVariant(option.id)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n @if (isExecutiveExpansionScenario()) {\n <p class=\"control-hint\">\n Este cenario fixa <code>variant = list</code> para validar a\n detail row inline da V1.\n </p>\n }\n </div>\n\n <div class=\"control-grid\">\n <div class=\"control-block\">\n <p class=\"control-block__title\">Density</p>\n <div class=\"chip-row\">\n @for (option of densityOptions; track option.id) {\n <button\n type=\"button\"\n class=\"chip-button chip-button--small\"\n [attr.data-testid]=\"'density-' + option.id\"\n [class.is-active]=\"density() === option.id\"\n (click)=\"setDensity(option.id)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n </div>\n\n <div class=\"control-block\">\n <p class=\"control-block__title\">Lines</p>\n <div class=\"chip-row\">\n @for (count of [1, 2, 3]; track count) {\n <button\n type=\"button\"\n class=\"chip-button chip-button--small\"\n [attr.data-testid]=\"'lines-' + count\"\n [class.is-active]=\"lines() === count\"\n [disabled]=\"isExecutiveExpansionScenario()\"\n (click)=\"setLines($any(count))\"\n >\n {{ count }}\n </button>\n }\n </div>\n @if (isExecutiveExpansionScenario()) {\n <p class=\"control-hint\">\n Linhas travadas em 2 para manter aderencia a referencia\n executiva.\n </p>\n }\n </div>\n </div>\n\n <div class=\"control-block\">\n <p class=\"control-block__title\">Skin</p>\n <div class=\"chip-row\">\n @for (option of skinOptions; track option.id) {\n <button\n type=\"button\"\n class=\"chip-button chip-button--small\"\n [attr.data-testid]=\"'skin-' + option.id\"\n [class.is-active]=\"skin() === option.id\"\n (click)=\"setSkin(option.id)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n </div>\n\n <div class=\"control-block\">\n <p class=\"control-block__title\">Features mode</p>\n <div class=\"chip-row\">\n @for (option of featuresModeOptions; track option.id) {\n <button\n type=\"button\"\n class=\"chip-button chip-button--small\"\n [attr.data-testid]=\"'features-' + option.id\"\n [class.is-active]=\"featuresMode() === option.id\"\n (click)=\"setFeaturesMode(option.id)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n </div>\n\n <div class=\"control-block\">\n <p class=\"control-block__title\">Selection</p>\n <div class=\"chip-row\">\n @for (option of selectionOptions; track option.id) {\n <button\n type=\"button\"\n class=\"chip-button chip-button--small\"\n [attr.data-testid]=\"'selection-' + option.id\"\n [class.is-active]=\"selectionMode() === option.id\"\n [disabled]=\"isExecutiveExpansionScenario()\"\n (click)=\"setSelectionMode(option.id)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n @if (isExecutiveExpansionScenario()) {\n <p class=\"control-hint\">\n Selecao desabilitada neste exemplo para evitar conflito com\n expansao por linha.\n </p>\n }\n </div>\n\n <div class=\"control-block\">\n <p class=\"control-block__title\">Customization mode</p>\n <div class=\"chip-row\">\n <button\n type=\"button\"\n class=\"chip-button chip-button--small\"\n data-testid=\"customization-on\"\n [class.is-active]=\"customizationEnabled()\"\n (click)=\"setCustomizationMode(true)\"\n >\n On\n </button>\n <button\n type=\"button\"\n class=\"chip-button chip-button--small\"\n data-testid=\"customization-off\"\n [class.is-active]=\"!customizationEnabled()\"\n (click)=\"setCustomizationMode(false)\"\n >\n Off\n </button>\n </div>\n <p class=\"control-hint\">\n Ative para exibir editor interno + AI assistant do componente.\n </p>\n </div>\n </article>\n\n <article class=\"panel panel--events\">\n <div class=\"panel__head\">\n <h2>Eventos</h2>\n <button\n type=\"button\"\n class=\"ghost-btn\"\n data-testid=\"clear-log\"\n (click)=\"clearLog()\"\n >\n Limpar log\n </button>\n </div>\n @if (eventLog().length === 0) {\n <p class=\"muted\">\n Sem eventos ainda. Clique em itens, acoes ou selecao.\n </p>\n } @else {\n <ul class=\"event-log\">\n @for (entry of eventLog(); track entry.id) {\n <li>{{ entry.message }}</li>\n }\n </ul>\n }\n </article>\n </aside>\n\n <article class=\"panel panel--live\">\n <h2>Exemplo live</h2>\n <p class=\"muted\">\n Mostra list/cards/tiles com os mesmos dados e configura em tempo real.\n </p>\n @if (scenario() === \"executive-expansion\") {\n <p class=\"muted\">\n Cenario canonico da V1 para validar linha executiva com detail row\n inline e owner de expansao dedicado.\n </p>\n }\n <praxis-list\n listId=\"praxis-list-doc-live\"\n componentInstanceId=\"main-showcase\"\n [config]=\"liveConfig()\"\n [form]=\"form\"\n [enableCustomization]=\"customizationEnabled()\"\n (itemClick)=\"handleItemClick($event)\"\n (actionClick)=\"handleActionClick($event)\"\n (selectionChange)=\"handleSelectionChange($event)\"\n />\n </article>\n </section>\n\n <section class=\"skin-matrix\">\n <div class=\"section-head\">\n <h2>Matriz de skins</h2>\n <p>\n Preview rapido de todos os estilos visuais disponiveis no componente.\n </p>\n </div>\n <div class=\"skin-matrix__grid\">\n @for (preview of skinPreviewConfigs(); track preview.type) {\n <article class=\"panel skin-preview\">\n <h3>{{ preview.type }}</h3>\n <praxis-list-skin-preview\n [config]=\"preview.config\"\n [items]=\"skinPreviewItems\"\n />\n </article>\n }\n </div>\n </section>\n\n <section class=\"code-lab panel\">\n <div class=\"section-head\">\n <h2>Snippets de referencia</h2>\n <p>\n Use os blocos abaixo para replicar os exemplos no host ou no site de\n documentacao.\n </p>\n </div>\n\n <div class=\"code-tabs\">\n @for (tab of codeTabs; track tab.id) {\n <button\n type=\"button\"\n class=\"chip-button\"\n [attr.data-testid]=\"'tab-' + tab.id\"\n [class.is-active]=\"activeTab() === tab.id\"\n (click)=\"setCodeTab(tab.id)\"\n >\n {{ tab.label }}\n </button>\n }\n </div>\n\n <pre class=\"code-box\"><code>{{ selectedSnippet() }}</code></pre>\n </section>\n\n <section class=\"notes panel\">\n <h2>Notas de arquitetura</h2>\n <ul>\n <li>\n <strong>Persistencia:</strong> sempre definir <code>listId</code>; para\n multiplas instancias, use <code>componentInstanceId</code>.\n </li>\n <li>\n <strong>Local vs remoto:</strong> <code>dataSource.data</code> tem\n precedencia para preview local; <code>resourcePath</code> define o\n contrato remoto.\n </li>\n <li>\n <strong>Toolbar remota:</strong> controles de paginacao/sort/busca\n refletem o contrato; a semantica completa de filtro/paginacao depende do\n backend em <code>/filter</code>.\n </li>\n <li>\n <strong>Editor interno:</strong> habilite\n <code>enableCustomization</code> quando o host tiver os providers de\n Settings Panel e AI.\n </li>\n <li>\n <strong>A11y:</strong> configure <code>a11y.ariaLabel</code>/<code\n >ariaLabelledBy</code\n >\n e mantenha labels de acoes explicitas.\n </li>\n <li>\n <strong>Expansao inline V1:</strong> suportada apenas em\n <code>layout.variant = list</code>.\n </li>\n <li>\n <strong>Selecao + expansao:</strong> quando houver selecao, o trigger\n canonico e por icone para evitar ambiguidade operacional.\n </li>\n </ul>\n </section>\n</section>\n", styles: [":host{display:block}.list-doc-page{--doc-bg: linear-gradient(160deg, #eef5ff 0%, #f9fbff 48%, #e8f7ef 100%);--doc-surface: rgba(255, 255, 255, .86);--doc-surface-strong: #ffffff;--doc-border: #cfdae8;--doc-text: #10243a;--doc-muted: #4d6075;--doc-accent: #0b6dff;--doc-accent-2: #0f9d58;--doc-accent-soft: color-mix(in srgb, var(--doc-accent) 15%, #ffffff);display:grid;gap:20px;padding:18px;border-radius:22px;border:1px solid var(--doc-border);color:var(--doc-text);background:var(--doc-bg);font-family:Manrope,Segoe UI,sans-serif}.hero{display:grid;gap:16px;grid-template-columns:minmax(0,1.3fr) minmax(0,1fr)}.hero__copy{border-radius:18px;border:1px solid var(--doc-border);padding:18px;background:radial-gradient(120% 130% at 0% 0%,rgba(11,109,255,.2),transparent 56%),var(--doc-surface-strong);box-shadow:0 14px 24px #0a254c14}.hero__eyebrow{margin:0 0 8px;font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:#385f9c;font-weight:700}.hero h1{margin:0;font-size:clamp(1.24rem,2.8vw,1.86rem);line-height:1.25}.hero h1 code{background:color-mix(in srgb,#0b6dff 12%,#ffffff);border:1px solid color-mix(in srgb,#0b6dff 28%,#cad6ec);border-radius:8px;padding:2px 6px;font-size:.9em}.hero p{margin:10px 0 0;color:var(--doc-muted)}.hero__badges{margin-top:14px;display:flex;flex-wrap:wrap;gap:8px}.hero__badges span{border-radius:999px;border:1px solid color-mix(in srgb,#0f9d58 26%,#cad9d2);background:color-mix(in srgb,#0f9d58 12%,#ffffff);color:#0f5d39;padding:5px 10px;font-size:12px;font-weight:700}.hero__panel{border-radius:18px;border:1px solid var(--doc-border);padding:16px;background:var(--doc-surface);box-shadow:0 10px 20px #182e5414}.hero__panel-title{margin:0 0 10px;font-size:13px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:#345684}.hero__panel ul{margin:0;padding:0;list-style:none;display:grid;gap:8px}.hero__panel li{display:grid;gap:2px;padding:8px 10px;border-radius:10px;border:1px solid color-mix(in srgb,#274f82 14%,#d1d8e6);background:#fff}.hero__panel strong{font-size:12px}.hero__panel span{color:var(--doc-muted);font-size:12px}.lab{display:grid;gap:14px;grid-template-columns:minmax(260px,340px) minmax(0,1fr);align-items:start}.controls{display:grid;gap:12px}.panel{border-radius:16px;border:1px solid var(--doc-border);background:var(--doc-surface);box-shadow:0 8px 18px #1b2d4f12}.panel--controls,.panel--events{padding:14px}.panel--controls h2,.panel--live h2,.panel--events h2,.skin-preview h3,.code-lab h2,.notes h2{margin:0}.panel--live{padding:14px;min-width:0}.muted{margin:6px 0 0;color:var(--doc-muted);font-size:13px}.control-block{margin-top:12px}.control-grid{display:grid;gap:10px;grid-template-columns:repeat(2,minmax(0,1fr))}.control-block__title{margin:0 0 6px;font-size:12px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:#325d96}.control-hint{margin:8px 0 0;color:var(--doc-muted);font-size:12px}.chip-row{display:flex;gap:7px;flex-wrap:wrap}.chip-button{min-height:30px;border-radius:999px;border:1px solid color-mix(in srgb,#225ea9 30%,#c8d3e3);background:#f4f8ff;color:#1f3f69;padding:0 12px;font-size:12px;font-weight:700;cursor:pointer;transition:.14s ease;transition-property:border-color,background,color,transform}.chip-button--small{min-height:28px;padding-inline:10px;font-size:11px}.chip-button:hover{transform:translateY(-1px);border-color:color-mix(in srgb,var(--doc-accent) 44%,#a6bad7)}.chip-button.is-active{border-color:color-mix(in srgb,var(--doc-accent) 62%,#6f8aa9);background:linear-gradient(140deg,#1565d8,#0f9360);color:#fff}.chip-button:focus-visible,.ghost-btn:focus-visible{outline:2px solid color-mix(in srgb,var(--doc-accent) 65%,#416688);outline-offset:1px}.panel__head{display:flex;gap:10px;justify-content:space-between;align-items:center}.ghost-btn{min-height:30px;border-radius:8px;border:1px solid color-mix(in srgb,#173d6e 32%,#cdd7e4);background:#fff;color:#1f3d66;font-size:12px;font-weight:700;padding:0 10px;cursor:pointer}.event-log{margin:10px 0 0;padding:0;list-style:none;display:grid;gap:6px}.event-log li{border-radius:9px;border:1px solid color-mix(in srgb,#34598a 16%,#ccd7e6);background:#fff;padding:7px 9px;font-family:JetBrains Mono,Consolas,monospace;font-size:12px;color:#273e5f}.skin-matrix{display:grid;gap:10px}.section-head h2{margin:0}.section-head p{margin:4px 0 0;color:var(--doc-muted)}.skin-matrix__grid{display:grid;gap:12px;width:100%;max-width:1840px;margin-inline:auto;align-items:start;grid-template-columns:repeat(auto-fit,minmax(min(100%,420px),1fr))}.skin-preview{padding:12px;min-width:0}.skin-preview h3{margin-bottom:8px;text-transform:capitalize}.code-lab{padding:14px}.code-tabs{margin-top:12px;display:flex;gap:8px;flex-wrap:wrap}.code-box{margin:12px 0 0;border-radius:12px;border:1px solid color-mix(in srgb,#2d3f58 36%,#8194aa);background:#0d1420;color:#d9ecff;padding:12px;max-height:360px;overflow:auto;font-size:12px;line-height:1.55}.notes{padding:14px}.notes ul{margin:10px 0 0;padding-left:18px;display:grid;gap:8px}.notes li{color:#21354f}.notes code{background:color-mix(in srgb,#0b6dff 12%,#ffffff);border:1px solid color-mix(in srgb,#0b6dff 30%,#c8d5e7);border-radius:6px;padding:1px 5px}@media(max-width:1060px){.hero,.lab{grid-template-columns:1fr}}@media(max-width:700px){.list-doc-page{padding:12px}.control-grid{grid-template-columns:1fr}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: PraxisList, selector: "praxis-list", inputs: ["config", "listId", "componentInstanceId", "configPersistenceStrategy", "queryContext", "form", "enableCustomization"], outputs: ["itemClick", "actionClick", "selectionChange", "exportAction"] }, { kind: "component", type: PraxisListSkinPreviewComponent, selector: "praxis-list-skin-preview", inputs: ["config", "items", "theme"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
15525
15522
|
}
|
|
15526
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
15523
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisListDocPageComponent, decorators: [{
|
|
15527
15524
|
type: Component,
|
|
15528
15525
|
args: [{ selector: 'praxis-list-doc-page', standalone: true, imports: [
|
|
15529
|
-
CommonModule,
|
|
15530
15526
|
ReactiveFormsModule,
|
|
15531
15527
|
PraxisList,
|
|
15532
|
-
PraxisListSkinPreviewComponent
|
|
15528
|
+
PraxisListSkinPreviewComponent
|
|
15533
15529
|
], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
15534
15530
|
{ provide: API_URL, useValue: { default: { baseUrl: '/api' } } },
|
|
15535
15531
|
{
|