@praxisui/list 1.0.0-beta.3 → 1.0.0-beta.30

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.
@@ -12,25 +12,27 @@ import { MatChipsModule } from '@angular/material/chips';
12
12
  import { MatDividerModule } from '@angular/material/divider';
13
13
  import * as i7 from '@angular/material/button';
14
14
  import { MatButtonModule } from '@angular/material/button';
15
+ import * as i4$1 from '@angular/material/form-field';
16
+ import { MatFormFieldModule } from '@angular/material/form-field';
17
+ import * as i6 from '@angular/material/select';
18
+ import { MatSelectModule } from '@angular/material/select';
19
+ import * as i5 from '@angular/material/input';
20
+ import { MatInputModule } from '@angular/material/input';
15
21
  import * as i2 from '@angular/forms';
16
22
  import { FormsModule, FormControl, ReactiveFormsModule } from '@angular/forms';
17
- import { BehaviorSubject, combineLatest, of, Subject } from 'rxjs';
23
+ import { BehaviorSubject, combineLatest, of, Subject, debounceTime as debounceTime$1, distinctUntilChanged as distinctUntilChanged$1 } from 'rxjs';
18
24
  import { auditTime, switchMap, map, catchError, finalize, shareReplay, debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
19
25
  import { SETTINGS_PANEL_DATA, SettingsPanelService } from '@praxisui/settings-panel';
20
26
  import * as i3$1 from '@angular/material/tabs';
21
27
  import { MatTabsModule } from '@angular/material/tabs';
22
- import * as i4$1 from '@angular/material/form-field';
23
- import { MatFormFieldModule } from '@angular/material/form-field';
24
- import * as i5 from '@angular/material/input';
25
- import { MatInputModule } from '@angular/material/input';
26
- import * as i6 from '@angular/material/select';
27
- import { MatSelectModule } from '@angular/material/select';
28
28
  import * as i8 from '@angular/material/slide-toggle';
29
29
  import { MatSlideToggleModule } from '@angular/material/slide-toggle';
30
30
  import * as i11 from '@angular/material/button-toggle';
31
31
  import { MatButtonToggleModule } from '@angular/material/button-toggle';
32
32
  import * as i12 from '@angular/material/tooltip';
33
33
  import { MatTooltipModule } from '@angular/material/tooltip';
34
+ import * as i15 from '@angular/material/menu';
35
+ import { MatMenuModule } from '@angular/material/menu';
34
36
  import * as i10 from '@angular/material/expansion';
35
37
  import { MatExpansionModule } from '@angular/material/expansion';
36
38
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@@ -43,13 +45,49 @@ import { produce } from 'immer';
43
45
  function evalExpr(expr, ctx) {
44
46
  if (!expr)
45
47
  return '';
46
- // Replace ${...} with values from context (item)
47
- return expr.replace(/\$\{([^}]+)\}/g, (_, path) => {
48
+ // Replace ${...} with values from context (item), supporting simple pipes inside placeholders
49
+ return expr.replace(/\$\{([^}]+)\}/g, (_, raw) => {
48
50
  try {
49
- const value = path
51
+ const { baseExpr, pipe } = splitFirstPipe(raw);
52
+ const value = baseExpr
50
53
  .split('.')
51
54
  .reduce((acc, k) => (acc == null ? undefined : acc[k]), ctx);
52
- return value == null ? '' : String(value);
55
+ let out = value == null ? '' : String(value);
56
+ if (pipe) {
57
+ const { name, args } = parsePipe(pipe);
58
+ if (name === 'bool') {
59
+ const [trueLabel, falseLabel] = parseTwoArgs(args);
60
+ const truthy = toBoolean(value);
61
+ out = truthy ? (trueLabel ?? 'Sim') : (falseLabel ?? 'Não');
62
+ }
63
+ if (name === 'date') {
64
+ const [localeArg, styleArg] = parseTwoArgs(args);
65
+ const dt = value ? new Date(value) : null;
66
+ if (dt && !isNaN(dt.getTime())) {
67
+ const fmt = new Intl.DateTimeFormat(localeArg || undefined, mapDateStyle(styleArg)).format(dt);
68
+ out = fmt;
69
+ }
70
+ else {
71
+ out = '';
72
+ }
73
+ }
74
+ if (name === 'map') {
75
+ const map = parseMap(args);
76
+ const key = String(value ?? '').trim();
77
+ out = map[key] ?? map[key.toLowerCase?.()] ?? map[key.toUpperCase?.()] ?? out;
78
+ }
79
+ if (name === 'number') {
80
+ const [localeArg, styleArg] = parseTwoArgs(args);
81
+ const num = Number(value);
82
+ if (isFinite(num)) {
83
+ const opt = {};
84
+ if ((styleArg || '').toLowerCase() === 'compact')
85
+ opt.notation = 'compact';
86
+ out = new Intl.NumberFormat(localeArg || undefined, opt).format(num);
87
+ }
88
+ }
89
+ }
90
+ return out;
53
91
  }
54
92
  catch {
55
93
  return '';
@@ -64,11 +102,9 @@ function evalExpr(expr, ctx) {
64
102
  function evaluateTemplate(def, item) {
65
103
  if (!def)
66
104
  return null;
67
- // Icons ignore pipes (usually a literal name)
68
- if (def.type === 'icon') {
69
- return { type: def.type, value: def.expr, class: def.class, style: def.style };
70
- }
71
- const { baseExpr, pipe } = splitFirstPipe(def.expr);
105
+ // Support a top-level pipe outside of template placeholders,
106
+ // e.g.: "${item.active}|bool:Ativo:Inativo".
107
+ const { baseExpr, pipe } = splitFirstTopLevelPipe(def.expr);
72
108
  let value = evalExpr(baseExpr, { item });
73
109
  if (pipe) {
74
110
  const { name, args } = parsePipe(pipe);
@@ -80,7 +116,7 @@ function evaluateTemplate(def, item) {
80
116
  }
81
117
  const out = { type: def.type, value, class: def.class, style: def.style, color: def.color, variant: def.variant };
82
118
  if (def.badge?.expr) {
83
- const { baseExpr: b2, pipe: p2 } = splitFirstPipe(def.badge.expr);
119
+ const { baseExpr: b2, pipe: p2 } = splitFirstTopLevelPipe(def.badge.expr);
84
120
  let badgeVal = evalExpr(b2, { item });
85
121
  if (p2) {
86
122
  const { name, args } = parsePipe(p2);
@@ -104,6 +140,26 @@ function splitFirstPipe(expr) {
104
140
  return { baseExpr: expr.trim() };
105
141
  return { baseExpr: expr.slice(0, idx).trim(), pipe: expr.slice(idx + 1).trim() };
106
142
  }
143
+ // Find first '|' that is NOT inside a "${...}" placeholder
144
+ function splitFirstTopLevelPipe(expr) {
145
+ let inTpl = 0;
146
+ for (let i = 0; i < expr.length; i++) {
147
+ const c = expr[i];
148
+ if (c === '$' && expr[i + 1] === '{') {
149
+ inTpl++;
150
+ i++;
151
+ continue;
152
+ }
153
+ if (c === '}' && inTpl > 0) {
154
+ inTpl--;
155
+ continue;
156
+ }
157
+ if (c === '|' && inTpl === 0) {
158
+ return { baseExpr: expr.slice(0, i).trim(), pipe: expr.slice(i + 1).trim() };
159
+ }
160
+ }
161
+ return { baseExpr: expr.trim() };
162
+ }
107
163
  function parsePipe(pipeSpec) {
108
164
  const idx = pipeSpec.indexOf(':');
109
165
  if (idx === -1)
@@ -124,6 +180,35 @@ function toBoolean(val) {
124
180
  const s = String(val).trim().toLowerCase();
125
181
  return s === 'true' || s === '1' || s === 'yes' || s === 'sim' || s === 'on';
126
182
  }
183
+ function mapDateStyle(style) {
184
+ switch ((style || '').toLowerCase()) {
185
+ case 'short':
186
+ return { dateStyle: 'short' };
187
+ case 'long':
188
+ return { dateStyle: 'long' };
189
+ case 'full':
190
+ return { dateStyle: 'full' };
191
+ case 'medium':
192
+ default:
193
+ return { dateStyle: 'medium' };
194
+ }
195
+ }
196
+ function parseMap(spec) {
197
+ const out = {};
198
+ if (!spec)
199
+ return out;
200
+ const entries = spec.split(/[,;|]/g).map(s => s.trim()).filter(Boolean);
201
+ for (const e of entries) {
202
+ const idx = e.indexOf('=');
203
+ if (idx === -1)
204
+ continue;
205
+ const k = e.slice(0, idx).trim();
206
+ const v = e.slice(idx + 1).trim();
207
+ if (k)
208
+ out[k] = v;
209
+ }
210
+ return out;
211
+ }
127
212
 
128
213
  function adaptSelection(config, items) {
129
214
  const mode = (config?.selection?.mode ?? 'none');
@@ -153,6 +238,7 @@ class ListDataService {
153
238
  query$ = new BehaviorSubject({});
154
239
  loading$ = new BehaviorSubject(false);
155
240
  total$ = new BehaviorSubject(0);
241
+ pageState$ = this.pageable$.asObservable();
156
242
  lastSig = '';
157
243
  crud = inject(GenericCrudService, {
158
244
  optional: true,
@@ -253,6 +339,9 @@ class ListDataService {
253
339
  }
254
340
  setQuery(q) {
255
341
  this.query$.next(q || {});
342
+ // Reset to first page when query changes
343
+ const p = this.pageable$.value;
344
+ this.pageable$.next({ ...p, pageNumber: 0 });
256
345
  }
257
346
  groupedStream() {
258
347
  return this.stream().pipe(switchMap((items) => this.config$.pipe(map((cfg) => cfg?.layout?.groupBy), map((groupBy) => {
@@ -454,6 +543,7 @@ class PraxisListConfigEditor {
454
543
  selection: { mode: 'none', return: 'value' },
455
544
  skin: { type: 'elevated', gradient: { from: '', to: '', angle: 135 } },
456
545
  i18n: { locale: 'en-US', currency: 'USD' },
546
+ ui: {},
457
547
  };
458
548
  isDirty$ = new BehaviorSubject(false);
459
549
  isValid$ = new BehaviorSubject(true);
@@ -472,6 +562,10 @@ class PraxisListConfigEditor {
472
562
  mappingLeading = { type: 'icon' };
473
563
  mapping = {};
474
564
  mappingDirty = false;
565
+ statusPosition = undefined;
566
+ iconColorMapEntries = [];
567
+ // UI (search/sort/range) editor state
568
+ uiSortRows = [];
475
569
  // Meta composition (multi-field)
476
570
  mappingMetaFields = [];
477
571
  mappingMetaSeparator = ' • ';
@@ -491,6 +585,7 @@ class PraxisListConfigEditor {
491
585
  this.working = this.normalize(structuredClone(cfg));
492
586
  // Initialize mapping UI from existing templating
493
587
  this.hydrateMappingFromTemplating(this.working.templating);
588
+ this.hydrateUiEditorFromConfig();
494
589
  }
495
590
  this.initialJson = JSON.stringify(this.working);
496
591
  this.lastJson = this.initialJson;
@@ -571,6 +666,7 @@ class PraxisListConfigEditor {
571
666
  class: cfg.skin?.class,
572
667
  },
573
668
  i18n: cfg.i18n || {},
669
+ ui: cfg.ui || {},
574
670
  };
575
671
  return out;
576
672
  }
@@ -746,6 +842,72 @@ class PraxisListConfigEditor {
746
842
  this.markDirty();
747
843
  this.verify();
748
844
  }
845
+ onUiChanged() {
846
+ this.markDirty();
847
+ this.verify();
848
+ }
849
+ addUiSortRow() {
850
+ this.uiSortRows = [...(this.uiSortRows || []), { label: '', field: '', dir: 'desc' }];
851
+ this.onUiSortRowsChanged();
852
+ }
853
+ removeUiSortRow(i) {
854
+ const arr = [...(this.uiSortRows || [])];
855
+ arr.splice(i, 1);
856
+ this.uiSortRows = arr;
857
+ this.onUiSortRowsChanged();
858
+ }
859
+ onUiSortRowsChanged() {
860
+ // Map rows to config.ui.sortOptions
861
+ const opts = (this.uiSortRows || [])
862
+ .filter(r => (r.field || '').trim())
863
+ .map(r => ({ label: (r.label || '').trim() || `${r.field},${r.dir || 'desc'}`, value: `${r.field},${r.dir || 'desc'}` }));
864
+ this.working = produce(this.working, (draft) => {
865
+ (draft.ui ||= {}).sortOptions = opts;
866
+ });
867
+ this.onUiChanged();
868
+ }
869
+ isUiSortRowDuplicate(index) {
870
+ const rows = this.uiSortRows || [];
871
+ const key = `${rows[index]?.field || ''},${rows[index]?.dir || 'desc'}`.trim();
872
+ if (!key || !rows[index]?.field)
873
+ return false;
874
+ let count = 0;
875
+ for (const r of rows) {
876
+ const k = `${r?.field || ''},${r?.dir || 'desc'}`.trim();
877
+ if (k === key)
878
+ count++;
879
+ }
880
+ return count > 1;
881
+ }
882
+ isIconColorDuplicate(index) {
883
+ const arr = this.iconColorMapEntries || [];
884
+ const key = (arr[index]?.key || '').trim();
885
+ if (!key)
886
+ return false;
887
+ let count = 0;
888
+ for (const e of arr) {
889
+ if ((e?.key || '').trim() === key)
890
+ count++;
891
+ }
892
+ return count > 1;
893
+ }
894
+ hydrateUiEditorFromConfig() {
895
+ const ui = (this.working.ui ||= {});
896
+ const arr = ui.sortOptions || [];
897
+ const rows = [];
898
+ for (const op of arr) {
899
+ if (typeof op === 'string') {
900
+ const [f, d] = op.split(',');
901
+ rows.push({ label: op, field: f, dir: d || 'asc' });
902
+ }
903
+ else if (op && typeof op === 'object') {
904
+ const v = String(op.value || '');
905
+ const [f, d] = v.split(',');
906
+ rows.push({ label: op.label, field: f, dir: d || 'asc' });
907
+ }
908
+ }
909
+ this.uiSortRows = rows;
910
+ }
749
911
  // Legacy method retained to avoid breaking references; no-op now.
750
912
  loadFieldsIfNeeded() { }
751
913
  updateSortConfig() {
@@ -767,8 +929,10 @@ class PraxisListConfigEditor {
767
929
  if (!slot?.field)
768
930
  return undefined;
769
931
  const base = '${item.' + slot.field + '}';
770
- if (slot.type === 'text')
771
- return { type: 'text', expr: base, class: slot.class, style: slot.style };
932
+ if (slot.type === 'text') {
933
+ const pipe = (slot.extraPipe || '').trim();
934
+ return { type: 'text', expr: pipe ? base + '|' + pipe : base, class: slot.class, style: slot.style };
935
+ }
772
936
  if (slot.type === 'chip')
773
937
  return { type: 'chip', expr: base, class: slot.class, style: slot.style, color: slot.chipColor, variant: slot.chipVariant };
774
938
  if (slot.type === 'rating')
@@ -791,6 +955,11 @@ class PraxisListConfigEditor {
791
955
  param = ':' + style;
792
956
  return { type: 'date', expr: param ? base + '|' + param : base, class: slot.class, style: slot.style };
793
957
  }
958
+ if (slot.type === 'icon') {
959
+ const pipe = (slot.extraPipe || '').trim();
960
+ const expr = pipe ? base + '|' + pipe : base;
961
+ return { type: 'icon', expr, class: slot.class, style: slot.style };
962
+ }
794
963
  return undefined;
795
964
  };
796
965
  const p = build(this.mappingPrimary);
@@ -839,6 +1008,22 @@ class PraxisListConfigEditor {
839
1008
  t.metaPlacement = this.mappingMeta.placement;
840
1009
  }
841
1010
  t.metaPrefixIcon = (this.mappingMetaPrefixIcon || '').trim() || undefined;
1011
+ // Status overlay and icon color map
1012
+ t.statusPosition = this.statusPosition || undefined;
1013
+ if (this.iconColorMapEntries && this.iconColorMapEntries.length) {
1014
+ const map = {};
1015
+ for (const e of this.iconColorMapEntries) {
1016
+ const k = (e.key || '').trim();
1017
+ if (!k)
1018
+ continue;
1019
+ if (e.color)
1020
+ map[k] = e.color;
1021
+ }
1022
+ t.iconColorMap = Object.keys(map).length ? map : undefined;
1023
+ }
1024
+ else {
1025
+ t.iconColorMap = undefined;
1026
+ }
842
1027
  // Apply features
843
1028
  if (this.features && this.features.length) {
844
1029
  t.features = this.features
@@ -884,6 +1069,37 @@ class PraxisListConfigEditor {
884
1069
  this.mappingDirty = true;
885
1070
  this.isDirty$.next(true);
886
1071
  }
1072
+ // Icon color map editor helpers
1073
+ addIconColorEntry() {
1074
+ this.iconColorMapEntries = [...(this.iconColorMapEntries || []), { key: '', color: undefined }];
1075
+ this.onIconColorMapChanged();
1076
+ }
1077
+ removeIconColorEntry(i) {
1078
+ const arr = [...(this.iconColorMapEntries || [])];
1079
+ arr.splice(i, 1);
1080
+ this.iconColorMapEntries = arr;
1081
+ this.onIconColorMapChanged();
1082
+ }
1083
+ onIconColorMapChanged() {
1084
+ // Normalize: last value wins, remove vazios
1085
+ const map = new Map();
1086
+ for (const e of (this.iconColorMapEntries || [])) {
1087
+ const k = (e?.key || '').trim();
1088
+ if (!k)
1089
+ continue;
1090
+ map.set(k, e?.color || undefined);
1091
+ }
1092
+ this.iconColorMapEntries = Array.from(map.entries()).map(([key, color]) => ({ key, color }));
1093
+ this.mappingDirty = true;
1094
+ this.isDirty$.next(true);
1095
+ }
1096
+ // Pipe assistant helper: sets extraPipe for a given mapping and marks dirty
1097
+ setPipe(target, spec) {
1098
+ if (!target)
1099
+ return;
1100
+ target.extraPipe = spec;
1101
+ this.onMappingChanged();
1102
+ }
887
1103
  // ===============
888
1104
  // Expr helpers (Features): click-to-insert fields and literals
889
1105
  // ===============
@@ -962,6 +1178,8 @@ class PraxisListConfigEditor {
962
1178
  Object.assign(base, parseCurrency(param));
963
1179
  if (templ.primary.type === 'date')
964
1180
  Object.assign(base, parseDate(param));
1181
+ if (templ.primary.type === 'text')
1182
+ base.extraPipe = param;
965
1183
  this.mappingPrimary = base;
966
1184
  }
967
1185
  if (templ.secondary) {
@@ -971,6 +1189,8 @@ class PraxisListConfigEditor {
971
1189
  Object.assign(base, parseCurrency(param));
972
1190
  if (templ.secondary.type === 'date')
973
1191
  Object.assign(base, parseDate(param));
1192
+ if (templ.secondary.type === 'text')
1193
+ base.extraPipe = param;
974
1194
  this.mappingSecondary = base;
975
1195
  }
976
1196
  if (templ.meta) {
@@ -980,6 +1200,8 @@ class PraxisListConfigEditor {
980
1200
  Object.assign(base, parseCurrency(param));
981
1201
  if (templ.meta.type === 'date')
982
1202
  Object.assign(base, parseDate(param));
1203
+ if (templ.meta.type === 'text')
1204
+ base.extraPipe = param;
983
1205
  this.mappingMeta = base;
984
1206
  if (templ.metaPlacement)
985
1207
  this.mappingMeta.placement = templ.metaPlacement;
@@ -1007,12 +1229,27 @@ class PraxisListConfigEditor {
1007
1229
  Object.assign(base, parseCurrency(param));
1008
1230
  if (templ.trailing.type === 'date')
1009
1231
  Object.assign(base, parseDate(param));
1232
+ if (templ.trailing.type === 'icon' || templ.trailing.type === 'text')
1233
+ base.extraPipe = param;
1010
1234
  this.mappingTrailing = base;
1011
1235
  if (templ.trailing?.color)
1012
1236
  this.mappingTrailing.chipColor = templ.trailing.color;
1013
1237
  if (templ.trailing?.variant)
1014
1238
  this.mappingTrailing.chipVariant = templ.trailing.variant;
1015
1239
  }
1240
+ // Overlay position and icon colors
1241
+ this.statusPosition = templ?.statusPosition;
1242
+ const icm = templ?.iconColorMap;
1243
+ this.iconColorMapEntries = Object.entries(icm || {}).map(([k, v]) => ({ key: k, color: v }));
1244
+ if ((this.mappingTrailing?.field || '').toLowerCase() === 'status' && (!this.iconColorMapEntries || this.iconColorMapEntries.length === 0)) {
1245
+ this.iconColorMapEntries = [
1246
+ { key: 'PLANEJADA', color: 'primary' },
1247
+ { key: 'EM_ANDAMENTO', color: 'accent' },
1248
+ { key: 'PAUSADA', color: 'accent' },
1249
+ { key: 'CONCLUIDA', color: 'primary' },
1250
+ { key: 'FALHOU', color: 'warn' },
1251
+ ];
1252
+ }
1016
1253
  if (templ.leading?.type === 'icon' && typeof templ.leading.expr === 'string') {
1017
1254
  this.mappingLeading = { type: 'icon', icon: templ.leading.expr };
1018
1255
  }
@@ -1056,8 +1293,27 @@ class PraxisListConfigEditor {
1056
1293
  const compareBy = (this.working?.selection?.compareBy || '').trim();
1057
1294
  if (compareBy && this.fields.length && !this.fields.includes(compareBy))
1058
1295
  valid = false;
1296
+ // Duplicatas em opções de ordenação (UI) tornam inválido
1297
+ if (valid && this.working?.ui?.showSort) {
1298
+ const rows = this.uiSortRows || [];
1299
+ const seen = new Map();
1300
+ for (const r of rows) {
1301
+ const k = `${(r?.field || '').trim()},${(r?.dir || 'desc').trim()}`;
1302
+ if (!r?.field)
1303
+ continue;
1304
+ seen.set(k, (seen.get(k) || 0) + 1);
1305
+ }
1306
+ for (const [, count] of seen) {
1307
+ if (count > 1) {
1308
+ valid = false;
1309
+ break;
1310
+ }
1311
+ }
1312
+ }
1059
1313
  this.isValid$.next(valid);
1060
1314
  }
1315
+ // UI editor hydration already called in ctor
1316
+ // Ensure UI.sortOptions are kept when saving via Settings Panel
1061
1317
  inferFromFields() {
1062
1318
  if (!this.fields?.length)
1063
1319
  return;
@@ -1155,6 +1411,52 @@ class PraxisListConfigEditor {
1155
1411
  </mat-select>
1156
1412
  </mat-form-field>
1157
1413
  </div>
1414
+ <mat-divider class="my-8"></mat-divider>
1415
+ <div class="subtitle">UI (Buscar / Ordenar / Rodapé)</div>
1416
+ <div class="g g-auto-220 gap-12 ai-end">
1417
+ <mat-slide-toggle [(ngModel)]="working.ui.showSearch" (ngModelChange)="onUiChanged()">Mostrar busca</mat-slide-toggle>
1418
+ <mat-slide-toggle [(ngModel)]="working.ui.showSort" (ngModelChange)="onUiChanged()">Mostrar ordenar</mat-slide-toggle>
1419
+ <mat-slide-toggle [(ngModel)]="working.ui.showRange" (ngModelChange)="onUiChanged()">Mostrar faixa X–Y de Total</mat-slide-toggle>
1420
+ </div>
1421
+ <div class="g g-auto-220 gap-12 ai-end mt-12" *ngIf="working.ui?.showSearch">
1422
+ <mat-form-field appearance="outline">
1423
+ <mat-label>Campo para buscar</mat-label>
1424
+ <mat-select [(ngModel)]="working.ui.searchField" (ngModelChange)="onUiChanged()">
1425
+ <mat-option *ngFor="let f of fields" [value]="f">{{ f }}</mat-option>
1426
+ </mat-select>
1427
+ </mat-form-field>
1428
+ <mat-form-field appearance="outline">
1429
+ <mat-label>Placeholder</mat-label>
1430
+ <input matInput [(ngModel)]="working.ui.searchPlaceholder" (ngModelChange)="onUiChanged()" placeholder="ex.: Buscar por título" />
1431
+ </mat-form-field>
1432
+ </div>
1433
+ <div class="mt-12" *ngIf="working.ui?.showSort">
1434
+ <div class="g g-1-auto ai-center gap-8">
1435
+ <div class="muted">Opções de ordenação (rótulo → campo+direção)</div>
1436
+ <button mat-flat-button color="primary" (click)="addUiSortRow()">Adicionar opção</button>
1437
+ </div>
1438
+ <div class="g g-auto-220 gap-12 ai-end mt-12" *ngFor="let r of uiSortRows; let i = index">
1439
+ <mat-form-field appearance="outline">
1440
+ <mat-label>Rótulo</mat-label>
1441
+ <input matInput [(ngModel)]="r.label" (ngModelChange)="onUiSortRowsChanged()" placeholder="ex.: Mais recentes" />
1442
+ </mat-form-field>
1443
+ <mat-form-field appearance="outline">
1444
+ <mat-label>Campo</mat-label>
1445
+ <mat-select [(ngModel)]="r.field" (ngModelChange)="onUiSortRowsChanged()">
1446
+ <mat-option *ngFor="let f of fields" [value]="f">{{ f }}</mat-option>
1447
+ </mat-select>
1448
+ </mat-form-field>
1449
+ <mat-form-field appearance="outline">
1450
+ <mat-label>Direção</mat-label>
1451
+ <mat-select [(ngModel)]="r.dir" (ngModelChange)="onUiSortRowsChanged()">
1452
+ <mat-option value="desc">Descendente</mat-option>
1453
+ <mat-option value="asc">Ascendente</mat-option>
1454
+ </mat-select>
1455
+ </mat-form-field>
1456
+ <div class="error" *ngIf="isUiSortRowDuplicate(i)">Opção duplicada (campo+direção)</div>
1457
+ <div class="flex-end"><button mat-button color="warn" (click)="removeUiSortRow(i)">Remover</button></div>
1458
+ </div>
1459
+ </div>
1158
1460
  </div>
1159
1461
  </mat-tab>
1160
1462
  <mat-tab label="Ações">
@@ -1320,6 +1622,18 @@ class PraxisListConfigEditor {
1320
1622
  </mat-select>
1321
1623
  </mat-form-field>
1322
1624
  </ng-container>
1625
+ <mat-form-field appearance="outline" *ngIf="mappingPrimary.type==='text'">
1626
+ <mat-label>Pipe extra (ex.: map:K=V)</mat-label>
1627
+ <input matInput [(ngModel)]="mappingPrimary.extraPipe" (ngModelChange)="onMappingChanged()" placeholder="ex.: map:PLANEJADA=Planejada" />
1628
+ <button mat-icon-button matSuffix [matMenuTriggerFor]="pipeMenuPrimary" aria-label="Assistente de pipes" matTooltip="Assistente de pipes"><mat-icon>build</mat-icon></button>
1629
+ </mat-form-field>
1630
+ <mat-menu #pipeMenuPrimary="matMenu">
1631
+ <button mat-menu-item (click)="setPipe(mappingPrimary, 'map:KEY=VALUE,KEY2=VALUE2')"><mat-icon>tune</mat-icon><span>Map: KEY=VALUE…</span></button>
1632
+ <button mat-menu-item (click)="setPipe(mappingPrimary, 'number:pt-BR')"><mat-icon>push_pin</mat-icon><span>Número: pt-BR</span></button>
1633
+ <button mat-menu-item (click)="setPipe(mappingPrimary, 'number:pt-BR:compact')"><mat-icon>format_list_numbered</mat-icon><span>Número: compacto</span></button>
1634
+ <button mat-menu-item (click)="setPipe(mappingPrimary, 'bool:Sim:Não')"><mat-icon>check_circle</mat-icon><span>Bool: Sim/Não</span></button>
1635
+ <button mat-menu-item (click)="setPipe(mappingPrimary, 'date:pt-BR:short')"><mat-icon>schedule</mat-icon><span>Data: curto</span></button>
1636
+ </mat-menu>
1323
1637
  <mat-form-field appearance="outline">
1324
1638
  <mat-label>Classe CSS</mat-label>
1325
1639
  <input matInput [(ngModel)]="mappingPrimary.class" (ngModelChange)="onMappingChanged()" />
@@ -1375,6 +1689,18 @@ class PraxisListConfigEditor {
1375
1689
  </mat-select>
1376
1690
  </mat-form-field>
1377
1691
  </ng-container>
1692
+ <mat-form-field appearance="outline" *ngIf="mappingSecondary.type==='text'">
1693
+ <mat-label>Pipe extra (ex.: number:pt-BR:compact)</mat-label>
1694
+ <input matInput [(ngModel)]="mappingSecondary.extraPipe" (ngModelChange)="onMappingChanged()" placeholder="ex.: number:pt-BR:compact" />
1695
+ <button mat-icon-button matSuffix [matMenuTriggerFor]="pipeMenuSecondary" aria-label="Assistente de pipes" matTooltip="Assistente de pipes"><mat-icon>build</mat-icon></button>
1696
+ </mat-form-field>
1697
+ <mat-menu #pipeMenuSecondary="matMenu">
1698
+ <button mat-menu-item (click)="setPipe(mappingSecondary, 'map:KEY=VALUE,KEY2=VALUE2')"><mat-icon>tune</mat-icon><span>Map: KEY=VALUE…</span></button>
1699
+ <button mat-menu-item (click)="setPipe(mappingSecondary, 'number:pt-BR')"><mat-icon>push_pin</mat-icon><span>Número: pt-BR</span></button>
1700
+ <button mat-menu-item (click)="setPipe(mappingSecondary, 'number:pt-BR:compact')"><mat-icon>format_list_numbered</mat-icon><span>Número: compacto</span></button>
1701
+ <button mat-menu-item (click)="setPipe(mappingSecondary, 'bool:Sim:Não')"><mat-icon>check_circle</mat-icon><span>Bool: Sim/Não</span></button>
1702
+ <button mat-menu-item (click)="setPipe(mappingSecondary, 'date:pt-BR:short')"><mat-icon>schedule</mat-icon><span>Data: curto</span></button>
1703
+ </mat-menu>
1378
1704
  <mat-form-field appearance="outline">
1379
1705
  <mat-label>Classe CSS</mat-label>
1380
1706
  <input matInput [(ngModel)]="mappingSecondary.class" (ngModelChange)="onMappingChanged()" />
@@ -1469,6 +1795,18 @@ class PraxisListConfigEditor {
1469
1795
  </mat-select>
1470
1796
  </mat-form-field>
1471
1797
  </ng-container>
1798
+ <mat-form-field appearance="outline" *ngIf="mappingMeta.type==='text'">
1799
+ <mat-label>Pipe extra (ex.: map:K=V)</mat-label>
1800
+ <input matInput [(ngModel)]="mappingMeta.extraPipe" (ngModelChange)="onMappingChanged()" placeholder="ex.: map:ABERTO=Aberto,FECHADO=Fechado" />
1801
+ <button mat-icon-button matSuffix [matMenuTriggerFor]="pipeMenuMeta" aria-label="Assistente de pipes" matTooltip="Assistente de pipes"><mat-icon>build</mat-icon></button>
1802
+ </mat-form-field>
1803
+ <mat-menu #pipeMenuMeta="matMenu">
1804
+ <button mat-menu-item (click)="setPipe(mappingMeta, 'map:KEY=VALUE,KEY2=VALUE2')"><mat-icon>tune</mat-icon><span>Map: KEY=VALUE…</span></button>
1805
+ <button mat-menu-item (click)="setPipe(mappingMeta, 'number:pt-BR')"><mat-icon>push_pin</mat-icon><span>Número: pt-BR</span></button>
1806
+ <button mat-menu-item (click)="setPipe(mappingMeta, 'number:pt-BR:compact')"><mat-icon>format_list_numbered</mat-icon><span>Número: compacto</span></button>
1807
+ <button mat-menu-item (click)="setPipe(mappingMeta, 'bool:Sim:Não')"><mat-icon>check_circle</mat-icon><span>Bool: Sim/Não</span></button>
1808
+ <button mat-menu-item (click)="setPipe(mappingMeta, 'date:pt-BR:short')"><mat-icon>schedule</mat-icon><span>Data: curto</span></button>
1809
+ </mat-menu>
1472
1810
  </div>
1473
1811
  </mat-expansion-panel>
1474
1812
  <mat-expansion-panel>
@@ -1489,8 +1827,19 @@ class PraxisListConfigEditor {
1489
1827
  <mat-option value="chip">Chip</mat-option>
1490
1828
  <mat-option value="currency">Moeda</mat-option>
1491
1829
  <mat-option value="date">Data</mat-option>
1830
+ <mat-option value="icon">Ícone</mat-option>
1492
1831
  </mat-select>
1493
1832
  </mat-form-field>
1833
+ <mat-form-field appearance="outline" *ngIf="mappingTrailing.type==='text' || mappingTrailing.type==='icon'">
1834
+ <mat-label>Pipe extra (ex.: map:KEY=icon,...)</mat-label>
1835
+ <input matInput [(ngModel)]="mappingTrailing.extraPipe" (ngModelChange)="onMappingChanged()" placeholder="ex.: map:PLANEJADA=event,EM_ANDAMENTO=play_circle" />
1836
+ <button mat-icon-button matSuffix [matMenuTriggerFor]="pipeMenuTrailing" aria-label="Assistente de pipes" matTooltip="Assistente de pipes"><mat-icon>build</mat-icon></button>
1837
+ </mat-form-field>
1838
+ <mat-menu #pipeMenuTrailing="matMenu">
1839
+ <button mat-menu-item (click)="setPipe(mappingTrailing, 'map:PLANEJADA=event,EM_ANDAMENTO=play_circle,PAUSADA=pause_circle,CONCLUIDA=check_circle,FALHOU=error')"><mat-icon>tune</mat-icon><span>Map: status→ícone</span></button>
1840
+ <button mat-menu-item (click)="setPipe(mappingTrailing, 'date:pt-BR:short')"><mat-icon>schedule</mat-icon><span>Data: curto</span></button>
1841
+ <button mat-menu-item (click)="setPipe(mappingTrailing, 'number:pt-BR')"><mat-icon>push_pin</mat-icon><span>Número: pt-BR</span></button>
1842
+ </mat-menu>
1494
1843
  <ng-container *ngIf="mappingTrailing.type==='chip'">
1495
1844
  <mat-form-field appearance="outline">
1496
1845
  <mat-label>Cor do chip</mat-label>
@@ -1542,6 +1891,44 @@ class PraxisListConfigEditor {
1542
1891
  <mat-label>Style inline</mat-label>
1543
1892
  <input matInput [(ngModel)]="mappingTrailing.style" (ngModelChange)="onMappingChanged()" />
1544
1893
  </mat-form-field>
1894
+ <mat-form-field appearance="outline">
1895
+ <mat-label>Posição do status (cards)</mat-label>
1896
+ <mat-select [(ngModel)]="statusPosition" (ngModelChange)="onMappingChanged()">
1897
+ <mat-option [value]="undefined">Inline</mat-option>
1898
+ <mat-option value="inline">Inline</mat-option>
1899
+ <mat-option value="top-right">Canto superior direito</mat-option>
1900
+ </mat-select>
1901
+ </mat-form-field>
1902
+ <div class="subtitle">Cores por status (ícone)</div>
1903
+ <div class="chips-row" *ngIf="(mappingTrailing?.field || '').toLowerCase()==='status'">
1904
+ <span class="muted">Sugestões:</span>
1905
+ <button mat-stroked-button type="button" (click)="iconColorMapEntries = iconColorMapEntries.concat([{key:'PLANEJADA', color:'primary'}]); onIconColorMapChanged()">PLANEJADA</button>
1906
+ <button mat-stroked-button type="button" (click)="iconColorMapEntries = iconColorMapEntries.concat([{key:'EM_ANDAMENTO', color:'accent'}]); onIconColorMapChanged()">EM_ANDAMENTO</button>
1907
+ <button mat-stroked-button type="button" (click)="iconColorMapEntries = iconColorMapEntries.concat([{key:'PAUSADA', color:'accent'}]); onIconColorMapChanged()">PAUSADA</button>
1908
+ <button mat-stroked-button type="button" (click)="iconColorMapEntries = iconColorMapEntries.concat([{key:'CONCLUIDA', color:'primary'}]); onIconColorMapChanged()">CONCLUIDA</button>
1909
+ <button mat-stroked-button type="button" (click)="iconColorMapEntries = iconColorMapEntries.concat([{key:'FALHOU', color:'warn'}]); onIconColorMapChanged()">FALHOU</button>
1910
+ </div>
1911
+ <div class="g g-auto-220 gap-12 ai-end">
1912
+ <ng-container *ngFor="let ent of iconColorMapEntries; let i = index">
1913
+ <mat-form-field appearance="outline">
1914
+ <mat-label>Chave</mat-label>
1915
+ <input matInput [(ngModel)]="ent.key" (ngModelChange)="onIconColorMapChanged()" placeholder="ex.: EM_ANDAMENTO ou play_circle" />
1916
+ </mat-form-field>
1917
+ <mat-form-field appearance="outline">
1918
+ <mat-label>Cor</mat-label>
1919
+ <mat-select [(ngModel)]="ent.color" (ngModelChange)="onIconColorMapChanged()">
1920
+ <mat-option [value]="undefined">Default</mat-option>
1921
+ <mat-option value="primary">Primary</mat-option>
1922
+ <mat-option value="accent">Accent</mat-option>
1923
+ <mat-option value="warn">Warn</mat-option>
1924
+ </mat-select>
1925
+ </mat-form-field>
1926
+ <div class="error" *ngIf="isIconColorDuplicate(i)">Chave duplicada (consolidada)</div>
1927
+ <div class="flex-end"><button mat-button color="warn" (click)="removeIconColorEntry(i)">Remover</button></div>
1928
+ </ng-container>
1929
+ <div><button mat-flat-button color="primary" (click)="addIconColorEntry()">Adicionar cor</button></div>
1930
+ </div>
1931
+ <div class="muted">Entradas vazias são ignoradas e chaves repetidas são mescladas (última vence).</div>
1545
1932
  </div>
1546
1933
  </mat-expansion-panel>
1547
1934
  <mat-expansion-panel>
@@ -1802,7 +2189,7 @@ class PraxisListConfigEditor {
1802
2189
  </div>
1803
2190
  </mat-tab>
1804
2191
  </mat-tab-group>
1805
- `, isInline: true, styles: [".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;opacity:.82}.chips-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: "component", type: i3$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$1.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: i4$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: i6.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: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i7.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: i7.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: "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: "component", type: i14.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: "component", type: i14.MatChipSet, selector: "mat-chip-set", inputs: ["disabled", "role", "tabIndex"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisListSkinPreviewComponent, selector: "praxis-list-skin-preview", inputs: ["config", "items", "theme"] }] });
2192
+ `, isInline: true, styles: [".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;opacity:.82}.chips-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.error{color:var(--mdc-theme-error,#b00020);font-size:.85rem}.muted{opacity:.7}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: "component", type: i3$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$1.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: i4$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: i6.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: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i7.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: i7.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: "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: "component", type: i14.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: "component", type: i14.MatChipSet, selector: "mat-chip-set", inputs: ["disabled", "role", "tabIndex"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i15.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: i15.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i15.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisListSkinPreviewComponent, selector: "praxis-list-skin-preview", inputs: ["config", "items", "theme"] }] });
1806
2193
  }
1807
2194
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PraxisListConfigEditor, decorators: [{
1808
2195
  type: Component,
@@ -1821,6 +2208,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1821
2208
  MatTooltipModule,
1822
2209
  MatDividerModule,
1823
2210
  MatChipsModule,
2211
+ MatMenuModule,
1824
2212
  PraxisIconDirective,
1825
2213
  PraxisListSkinPreviewComponent,
1826
2214
  ], template: `
@@ -1850,6 +2238,52 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1850
2238
  </mat-select>
1851
2239
  </mat-form-field>
1852
2240
  </div>
2241
+ <mat-divider class="my-8"></mat-divider>
2242
+ <div class="subtitle">UI (Buscar / Ordenar / Rodapé)</div>
2243
+ <div class="g g-auto-220 gap-12 ai-end">
2244
+ <mat-slide-toggle [(ngModel)]="working.ui.showSearch" (ngModelChange)="onUiChanged()">Mostrar busca</mat-slide-toggle>
2245
+ <mat-slide-toggle [(ngModel)]="working.ui.showSort" (ngModelChange)="onUiChanged()">Mostrar ordenar</mat-slide-toggle>
2246
+ <mat-slide-toggle [(ngModel)]="working.ui.showRange" (ngModelChange)="onUiChanged()">Mostrar faixa X–Y de Total</mat-slide-toggle>
2247
+ </div>
2248
+ <div class="g g-auto-220 gap-12 ai-end mt-12" *ngIf="working.ui?.showSearch">
2249
+ <mat-form-field appearance="outline">
2250
+ <mat-label>Campo para buscar</mat-label>
2251
+ <mat-select [(ngModel)]="working.ui.searchField" (ngModelChange)="onUiChanged()">
2252
+ <mat-option *ngFor="let f of fields" [value]="f">{{ f }}</mat-option>
2253
+ </mat-select>
2254
+ </mat-form-field>
2255
+ <mat-form-field appearance="outline">
2256
+ <mat-label>Placeholder</mat-label>
2257
+ <input matInput [(ngModel)]="working.ui.searchPlaceholder" (ngModelChange)="onUiChanged()" placeholder="ex.: Buscar por título" />
2258
+ </mat-form-field>
2259
+ </div>
2260
+ <div class="mt-12" *ngIf="working.ui?.showSort">
2261
+ <div class="g g-1-auto ai-center gap-8">
2262
+ <div class="muted">Opções de ordenação (rótulo → campo+direção)</div>
2263
+ <button mat-flat-button color="primary" (click)="addUiSortRow()">Adicionar opção</button>
2264
+ </div>
2265
+ <div class="g g-auto-220 gap-12 ai-end mt-12" *ngFor="let r of uiSortRows; let i = index">
2266
+ <mat-form-field appearance="outline">
2267
+ <mat-label>Rótulo</mat-label>
2268
+ <input matInput [(ngModel)]="r.label" (ngModelChange)="onUiSortRowsChanged()" placeholder="ex.: Mais recentes" />
2269
+ </mat-form-field>
2270
+ <mat-form-field appearance="outline">
2271
+ <mat-label>Campo</mat-label>
2272
+ <mat-select [(ngModel)]="r.field" (ngModelChange)="onUiSortRowsChanged()">
2273
+ <mat-option *ngFor="let f of fields" [value]="f">{{ f }}</mat-option>
2274
+ </mat-select>
2275
+ </mat-form-field>
2276
+ <mat-form-field appearance="outline">
2277
+ <mat-label>Direção</mat-label>
2278
+ <mat-select [(ngModel)]="r.dir" (ngModelChange)="onUiSortRowsChanged()">
2279
+ <mat-option value="desc">Descendente</mat-option>
2280
+ <mat-option value="asc">Ascendente</mat-option>
2281
+ </mat-select>
2282
+ </mat-form-field>
2283
+ <div class="error" *ngIf="isUiSortRowDuplicate(i)">Opção duplicada (campo+direção)</div>
2284
+ <div class="flex-end"><button mat-button color="warn" (click)="removeUiSortRow(i)">Remover</button></div>
2285
+ </div>
2286
+ </div>
1853
2287
  </div>
1854
2288
  </mat-tab>
1855
2289
  <mat-tab label="Ações">
@@ -2015,6 +2449,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
2015
2449
  </mat-select>
2016
2450
  </mat-form-field>
2017
2451
  </ng-container>
2452
+ <mat-form-field appearance="outline" *ngIf="mappingPrimary.type==='text'">
2453
+ <mat-label>Pipe extra (ex.: map:K=V)</mat-label>
2454
+ <input matInput [(ngModel)]="mappingPrimary.extraPipe" (ngModelChange)="onMappingChanged()" placeholder="ex.: map:PLANEJADA=Planejada" />
2455
+ <button mat-icon-button matSuffix [matMenuTriggerFor]="pipeMenuPrimary" aria-label="Assistente de pipes" matTooltip="Assistente de pipes"><mat-icon>build</mat-icon></button>
2456
+ </mat-form-field>
2457
+ <mat-menu #pipeMenuPrimary="matMenu">
2458
+ <button mat-menu-item (click)="setPipe(mappingPrimary, 'map:KEY=VALUE,KEY2=VALUE2')"><mat-icon>tune</mat-icon><span>Map: KEY=VALUE…</span></button>
2459
+ <button mat-menu-item (click)="setPipe(mappingPrimary, 'number:pt-BR')"><mat-icon>push_pin</mat-icon><span>Número: pt-BR</span></button>
2460
+ <button mat-menu-item (click)="setPipe(mappingPrimary, 'number:pt-BR:compact')"><mat-icon>format_list_numbered</mat-icon><span>Número: compacto</span></button>
2461
+ <button mat-menu-item (click)="setPipe(mappingPrimary, 'bool:Sim:Não')"><mat-icon>check_circle</mat-icon><span>Bool: Sim/Não</span></button>
2462
+ <button mat-menu-item (click)="setPipe(mappingPrimary, 'date:pt-BR:short')"><mat-icon>schedule</mat-icon><span>Data: curto</span></button>
2463
+ </mat-menu>
2018
2464
  <mat-form-field appearance="outline">
2019
2465
  <mat-label>Classe CSS</mat-label>
2020
2466
  <input matInput [(ngModel)]="mappingPrimary.class" (ngModelChange)="onMappingChanged()" />
@@ -2070,6 +2516,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
2070
2516
  </mat-select>
2071
2517
  </mat-form-field>
2072
2518
  </ng-container>
2519
+ <mat-form-field appearance="outline" *ngIf="mappingSecondary.type==='text'">
2520
+ <mat-label>Pipe extra (ex.: number:pt-BR:compact)</mat-label>
2521
+ <input matInput [(ngModel)]="mappingSecondary.extraPipe" (ngModelChange)="onMappingChanged()" placeholder="ex.: number:pt-BR:compact" />
2522
+ <button mat-icon-button matSuffix [matMenuTriggerFor]="pipeMenuSecondary" aria-label="Assistente de pipes" matTooltip="Assistente de pipes"><mat-icon>build</mat-icon></button>
2523
+ </mat-form-field>
2524
+ <mat-menu #pipeMenuSecondary="matMenu">
2525
+ <button mat-menu-item (click)="setPipe(mappingSecondary, 'map:KEY=VALUE,KEY2=VALUE2')"><mat-icon>tune</mat-icon><span>Map: KEY=VALUE…</span></button>
2526
+ <button mat-menu-item (click)="setPipe(mappingSecondary, 'number:pt-BR')"><mat-icon>push_pin</mat-icon><span>Número: pt-BR</span></button>
2527
+ <button mat-menu-item (click)="setPipe(mappingSecondary, 'number:pt-BR:compact')"><mat-icon>format_list_numbered</mat-icon><span>Número: compacto</span></button>
2528
+ <button mat-menu-item (click)="setPipe(mappingSecondary, 'bool:Sim:Não')"><mat-icon>check_circle</mat-icon><span>Bool: Sim/Não</span></button>
2529
+ <button mat-menu-item (click)="setPipe(mappingSecondary, 'date:pt-BR:short')"><mat-icon>schedule</mat-icon><span>Data: curto</span></button>
2530
+ </mat-menu>
2073
2531
  <mat-form-field appearance="outline">
2074
2532
  <mat-label>Classe CSS</mat-label>
2075
2533
  <input matInput [(ngModel)]="mappingSecondary.class" (ngModelChange)="onMappingChanged()" />
@@ -2164,6 +2622,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
2164
2622
  </mat-select>
2165
2623
  </mat-form-field>
2166
2624
  </ng-container>
2625
+ <mat-form-field appearance="outline" *ngIf="mappingMeta.type==='text'">
2626
+ <mat-label>Pipe extra (ex.: map:K=V)</mat-label>
2627
+ <input matInput [(ngModel)]="mappingMeta.extraPipe" (ngModelChange)="onMappingChanged()" placeholder="ex.: map:ABERTO=Aberto,FECHADO=Fechado" />
2628
+ <button mat-icon-button matSuffix [matMenuTriggerFor]="pipeMenuMeta" aria-label="Assistente de pipes" matTooltip="Assistente de pipes"><mat-icon>build</mat-icon></button>
2629
+ </mat-form-field>
2630
+ <mat-menu #pipeMenuMeta="matMenu">
2631
+ <button mat-menu-item (click)="setPipe(mappingMeta, 'map:KEY=VALUE,KEY2=VALUE2')"><mat-icon>tune</mat-icon><span>Map: KEY=VALUE…</span></button>
2632
+ <button mat-menu-item (click)="setPipe(mappingMeta, 'number:pt-BR')"><mat-icon>push_pin</mat-icon><span>Número: pt-BR</span></button>
2633
+ <button mat-menu-item (click)="setPipe(mappingMeta, 'number:pt-BR:compact')"><mat-icon>format_list_numbered</mat-icon><span>Número: compacto</span></button>
2634
+ <button mat-menu-item (click)="setPipe(mappingMeta, 'bool:Sim:Não')"><mat-icon>check_circle</mat-icon><span>Bool: Sim/Não</span></button>
2635
+ <button mat-menu-item (click)="setPipe(mappingMeta, 'date:pt-BR:short')"><mat-icon>schedule</mat-icon><span>Data: curto</span></button>
2636
+ </mat-menu>
2167
2637
  </div>
2168
2638
  </mat-expansion-panel>
2169
2639
  <mat-expansion-panel>
@@ -2184,8 +2654,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
2184
2654
  <mat-option value="chip">Chip</mat-option>
2185
2655
  <mat-option value="currency">Moeda</mat-option>
2186
2656
  <mat-option value="date">Data</mat-option>
2657
+ <mat-option value="icon">Ícone</mat-option>
2187
2658
  </mat-select>
2188
2659
  </mat-form-field>
2660
+ <mat-form-field appearance="outline" *ngIf="mappingTrailing.type==='text' || mappingTrailing.type==='icon'">
2661
+ <mat-label>Pipe extra (ex.: map:KEY=icon,...)</mat-label>
2662
+ <input matInput [(ngModel)]="mappingTrailing.extraPipe" (ngModelChange)="onMappingChanged()" placeholder="ex.: map:PLANEJADA=event,EM_ANDAMENTO=play_circle" />
2663
+ <button mat-icon-button matSuffix [matMenuTriggerFor]="pipeMenuTrailing" aria-label="Assistente de pipes" matTooltip="Assistente de pipes"><mat-icon>build</mat-icon></button>
2664
+ </mat-form-field>
2665
+ <mat-menu #pipeMenuTrailing="matMenu">
2666
+ <button mat-menu-item (click)="setPipe(mappingTrailing, 'map:PLANEJADA=event,EM_ANDAMENTO=play_circle,PAUSADA=pause_circle,CONCLUIDA=check_circle,FALHOU=error')"><mat-icon>tune</mat-icon><span>Map: status→ícone</span></button>
2667
+ <button mat-menu-item (click)="setPipe(mappingTrailing, 'date:pt-BR:short')"><mat-icon>schedule</mat-icon><span>Data: curto</span></button>
2668
+ <button mat-menu-item (click)="setPipe(mappingTrailing, 'number:pt-BR')"><mat-icon>push_pin</mat-icon><span>Número: pt-BR</span></button>
2669
+ </mat-menu>
2189
2670
  <ng-container *ngIf="mappingTrailing.type==='chip'">
2190
2671
  <mat-form-field appearance="outline">
2191
2672
  <mat-label>Cor do chip</mat-label>
@@ -2237,6 +2718,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
2237
2718
  <mat-label>Style inline</mat-label>
2238
2719
  <input matInput [(ngModel)]="mappingTrailing.style" (ngModelChange)="onMappingChanged()" />
2239
2720
  </mat-form-field>
2721
+ <mat-form-field appearance="outline">
2722
+ <mat-label>Posição do status (cards)</mat-label>
2723
+ <mat-select [(ngModel)]="statusPosition" (ngModelChange)="onMappingChanged()">
2724
+ <mat-option [value]="undefined">Inline</mat-option>
2725
+ <mat-option value="inline">Inline</mat-option>
2726
+ <mat-option value="top-right">Canto superior direito</mat-option>
2727
+ </mat-select>
2728
+ </mat-form-field>
2729
+ <div class="subtitle">Cores por status (ícone)</div>
2730
+ <div class="chips-row" *ngIf="(mappingTrailing?.field || '').toLowerCase()==='status'">
2731
+ <span class="muted">Sugestões:</span>
2732
+ <button mat-stroked-button type="button" (click)="iconColorMapEntries = iconColorMapEntries.concat([{key:'PLANEJADA', color:'primary'}]); onIconColorMapChanged()">PLANEJADA</button>
2733
+ <button mat-stroked-button type="button" (click)="iconColorMapEntries = iconColorMapEntries.concat([{key:'EM_ANDAMENTO', color:'accent'}]); onIconColorMapChanged()">EM_ANDAMENTO</button>
2734
+ <button mat-stroked-button type="button" (click)="iconColorMapEntries = iconColorMapEntries.concat([{key:'PAUSADA', color:'accent'}]); onIconColorMapChanged()">PAUSADA</button>
2735
+ <button mat-stroked-button type="button" (click)="iconColorMapEntries = iconColorMapEntries.concat([{key:'CONCLUIDA', color:'primary'}]); onIconColorMapChanged()">CONCLUIDA</button>
2736
+ <button mat-stroked-button type="button" (click)="iconColorMapEntries = iconColorMapEntries.concat([{key:'FALHOU', color:'warn'}]); onIconColorMapChanged()">FALHOU</button>
2737
+ </div>
2738
+ <div class="g g-auto-220 gap-12 ai-end">
2739
+ <ng-container *ngFor="let ent of iconColorMapEntries; let i = index">
2740
+ <mat-form-field appearance="outline">
2741
+ <mat-label>Chave</mat-label>
2742
+ <input matInput [(ngModel)]="ent.key" (ngModelChange)="onIconColorMapChanged()" placeholder="ex.: EM_ANDAMENTO ou play_circle" />
2743
+ </mat-form-field>
2744
+ <mat-form-field appearance="outline">
2745
+ <mat-label>Cor</mat-label>
2746
+ <mat-select [(ngModel)]="ent.color" (ngModelChange)="onIconColorMapChanged()">
2747
+ <mat-option [value]="undefined">Default</mat-option>
2748
+ <mat-option value="primary">Primary</mat-option>
2749
+ <mat-option value="accent">Accent</mat-option>
2750
+ <mat-option value="warn">Warn</mat-option>
2751
+ </mat-select>
2752
+ </mat-form-field>
2753
+ <div class="error" *ngIf="isIconColorDuplicate(i)">Chave duplicada (consolidada)</div>
2754
+ <div class="flex-end"><button mat-button color="warn" (click)="removeIconColorEntry(i)">Remover</button></div>
2755
+ </ng-container>
2756
+ <div><button mat-flat-button color="primary" (click)="addIconColorEntry()">Adicionar cor</button></div>
2757
+ </div>
2758
+ <div class="muted">Entradas vazias são ignoradas e chaves repetidas são mescladas (última vence).</div>
2240
2759
  </div>
2241
2760
  </mat-expansion-panel>
2242
2761
  <mat-expansion-panel>
@@ -2497,7 +3016,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
2497
3016
  </div>
2498
3017
  </mat-tab>
2499
3018
  </mat-tab-group>
2500
- `, styles: [".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;opacity:.82}.chips-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center}\n"] }]
3019
+ `, styles: [".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;opacity:.82}.chips-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.error{color:var(--mdc-theme-error,#b00020);font-size:.85rem}.muted{opacity:.7}\n"] }]
2501
3020
  }], ctorParameters: () => [{ type: undefined, decorators: [{
2502
3021
  type: Inject,
2503
3022
  args: [SETTINGS_PANEL_DATA]
@@ -2517,6 +3036,9 @@ class PraxisList {
2517
3036
  sections$;
2518
3037
  loading$;
2519
3038
  total$;
3039
+ page$;
3040
+ lastQuery = {};
3041
+ search$ = new Subject();
2520
3042
  get layoutLines() {
2521
3043
  return this.config?.layout?.lines ?? 2;
2522
3044
  }
@@ -2538,8 +3060,18 @@ class PraxisList {
2538
3060
  this.sections$ = this.data.groupedStream();
2539
3061
  this.loading$ = this.data.loading$;
2540
3062
  this.total$ = this.data.total$;
3063
+ this.page$ = this.data.pageState$;
2541
3064
  this.setupSelectionBinding();
2542
3065
  this.applySkins();
3066
+ this.lastQuery = this.config?.dataSource?.query || {};
3067
+ this.search$.pipe(debounceTime$1(300), distinctUntilChanged$1()).subscribe((term) => {
3068
+ const field = this.config?.ui?.searchField;
3069
+ if (!field)
3070
+ return;
3071
+ const q = { ...(this.lastQuery || {}), [field]: term };
3072
+ this.lastQuery = q;
3073
+ this.data.setQuery(q);
3074
+ });
2543
3075
  }
2544
3076
  ngOnChanges(changes) {
2545
3077
  if (changes['config']) {
@@ -2554,8 +3086,10 @@ class PraxisList {
2554
3086
  this.sections$ = this.data.groupedStream();
2555
3087
  this.loading$ = this.data.loading$;
2556
3088
  this.total$ = this.data.total$;
3089
+ this.page$ = this.data.pageState$;
2557
3090
  this.setupSelectionBinding();
2558
3091
  this.applySkins();
3092
+ this.lastQuery = this.config?.dataSource?.query || {};
2559
3093
  }
2560
3094
  }
2561
3095
  applyPersistence() {
@@ -2603,7 +3137,8 @@ class PraxisList {
2603
3137
  trailing = (item) => this.evalSlot('trailing', item);
2604
3138
  sectionHeader = (key) => evaluateTemplate(this.config?.templating?.sectionHeader, { key })?.value || key;
2605
3139
  featureLabel(item, expr) {
2606
- return this.evalString(expr || '', { item });
3140
+ const out = evaluateTemplate({ type: 'text', expr: expr || '' }, item);
3141
+ return out?.value ?? '';
2607
3142
  }
2608
3143
  featuresVisible() {
2609
3144
  const t = this.config?.templating;
@@ -2689,6 +3224,36 @@ class PraxisList {
2689
3224
  prevPage() {
2690
3225
  this.data.prevPage();
2691
3226
  }
3227
+ setPageSize(ps) {
3228
+ const size = Number(ps);
3229
+ if (isFinite(size) && size > 0)
3230
+ this.data.setPageSize(size);
3231
+ }
3232
+ onSortChange(val) {
3233
+ if (!val)
3234
+ return;
3235
+ this.data.setSort([val]);
3236
+ }
3237
+ onSearchInput(val) {
3238
+ this.search$.next(val || '');
3239
+ }
3240
+ sortOptionValue(op) {
3241
+ return typeof op === 'string' ? op : ((op && op.value) ? String(op.value) : '');
3242
+ }
3243
+ sortOptionLabel(op) {
3244
+ if (op && typeof op === 'object' && op.label)
3245
+ return String(op.label);
3246
+ const v = this.sortOptionValue(op);
3247
+ return v;
3248
+ }
3249
+ rangeStart(ps) {
3250
+ return (ps?.pageNumber || 0) * (ps?.pageSize || 0) + 1;
3251
+ }
3252
+ rangeEnd(currLen, ps, total) {
3253
+ const startIndex = (ps?.pageNumber || 0) * (ps?.pageSize || 0);
3254
+ const to = startIndex + (currLen || 0);
3255
+ return Math.min(to, total || 0);
3256
+ }
2692
3257
  onEditorApplied(newCfg) {
2693
3258
  this.config = newCfg;
2694
3259
  // Optionally infer templates from backend schema if not defined
@@ -2787,7 +3352,59 @@ class PraxisList {
2787
3352
  value = truthy ? (t ?? 'Sim') : (f ?? 'Não');
2788
3353
  }
2789
3354
  }
2790
- return { type: def.type, value, class: def.class, style: def.style, color: def.color, variant: def.variant };
3355
+ // Optional color mapping from config.templating.chipColorMap
3356
+ let color = def.color;
3357
+ try {
3358
+ const map = this.config?.templating?.chipColorMap;
3359
+ if (map && typeof value === 'string') {
3360
+ const key = String(value).toLowerCase();
3361
+ const found = map[key] ?? map[value];
3362
+ if (found)
3363
+ color = found;
3364
+ }
3365
+ }
3366
+ catch { }
3367
+ // Optional label mapping
3368
+ try {
3369
+ const lmap = this.config?.templating?.chipLabelMap;
3370
+ if (lmap && typeof value === 'string') {
3371
+ const key = String(value).toLowerCase();
3372
+ const lbl = lmap[key] ?? lmap[value];
3373
+ if (lbl)
3374
+ value = lbl;
3375
+ }
3376
+ else if (typeof value === 'string') {
3377
+ value = this.prettyLabel(value);
3378
+ }
3379
+ }
3380
+ catch { }
3381
+ return { type: def.type, value, class: def.class, style: def.style, color, variant: def.variant };
3382
+ }
3383
+ if (def.type === 'icon') {
3384
+ const out = evaluateTemplate(def, item);
3385
+ // Optional color mapping from config.templating.iconColorMap
3386
+ try {
3387
+ const map = this.config?.templating?.iconColorMap;
3388
+ if (map) {
3389
+ const status = String(item?.status ?? '').trim();
3390
+ const icon = String(out?.value ?? '').trim();
3391
+ const tryKeys = [
3392
+ status,
3393
+ status.toLowerCase?.(),
3394
+ icon,
3395
+ icon.toLowerCase?.(),
3396
+ ].filter(Boolean);
3397
+ for (const k of tryKeys) {
3398
+ const found = map[k];
3399
+ if (found) {
3400
+ out.color = found;
3401
+ break;
3402
+ }
3403
+ }
3404
+ }
3405
+ }
3406
+ catch { }
3407
+ return out;
2791
3408
  }
2792
3409
  return evaluateTemplate(def, item);
2793
3410
  }
@@ -2804,17 +3421,8 @@ class PraxisList {
2804
3421
  return [spec.slice(0, idx).trim(), spec.slice(idx + 1).trim()];
2805
3422
  }
2806
3423
  evalString(expr, ctx) {
2807
- return expr.replace(/\$\{([^}]+)\}/g, (_, path) => {
2808
- try {
2809
- const value = path
2810
- .split('.')
2811
- .reduce((acc, k) => (acc == null ? undefined : acc[k]), ctx);
2812
- return value == null ? '' : String(value);
2813
- }
2814
- catch {
2815
- return '';
2816
- }
2817
- });
3424
+ const res = evaluateTemplate({ type: 'text', expr }, ctx?.item ?? ctx);
3425
+ return res?.value ?? '';
2818
3426
  }
2819
3427
  parseTwoParams(param) {
2820
3428
  if (!param)
@@ -2835,6 +3443,21 @@ class PraxisList {
2835
3443
  const s = String(val).trim().toLowerCase();
2836
3444
  return s === 'true' || s === '1' || s === 'yes' || s === 'sim' || s === 'on';
2837
3445
  }
3446
+ // Format labels like 'EM_ANDAMENTO' -> 'Em andamento'; 'PLANEJADA' -> 'Planejada'
3447
+ prettyLabel(v) {
3448
+ try {
3449
+ const t = String(v ?? '')
3450
+ .replace(/[\s_\-]+/g, ' ')
3451
+ .trim()
3452
+ .toLowerCase();
3453
+ if (!t)
3454
+ return '';
3455
+ return t.replace(/\b\w/g, (m) => m.toUpperCase());
3456
+ }
3457
+ catch {
3458
+ return String(v ?? '');
3459
+ }
3460
+ }
2838
3461
  mapDateStyle(style) {
2839
3462
  switch ((style || '').toLowerCase()) {
2840
3463
  case 'short':
@@ -2861,7 +3484,7 @@ class PraxisList {
2861
3484
  trackBySection = (_, s) => s?.key ?? _;
2862
3485
  trackByItem = (i, it) => this.config?.selection?.compareBy ? (it?.[this.config.selection.compareBy] ?? i) : (it?.id ?? i);
2863
3486
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PraxisList, deps: [], target: i0.ɵɵFactoryTarget.Component });
2864
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: PraxisList, isStandalone: true, selector: "praxis-list", inputs: { config: "config", form: "form" }, outputs: { itemClick: "itemClick", actionClick: "actionClick", selectionChange: "selectionChange" }, providers: [GenericCrudService, ListDataService], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"praxis-list-root\"\n [ngClass]=\"skinClasses\"\n [attr.aria-label]=\"config.a11y?.ariaLabel || null\"\n [attr.aria-labelledby]=\"config.a11y?.ariaLabelledBy || null\"\n>\n @if (inlineCss) {\n <style [innerHTML]=\"inlineCss\"></style>\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=\"skeleton skeleton-avatar\"></div>\n <div style=\"width:100%\">\n <div class=\"skeleton skeleton-line w-60\"></div>\n @if (layoutLines > 1) { <div class=\"skeleton skeleton-line w-40\"></div> }\n </div>\n <div class=\"skeleton skeleton-chip\"></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=\"skeleton skeleton-avatar\"></div>\n <div style=\"width:100%\">\n <div class=\"skeleton skeleton-line w-60\"></div>\n @if (layoutLines > 1) { <div class=\"skeleton skeleton-line w-40\"></div> }\n </div>\n <div class=\"skeleton skeleton-chip\"></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 <!-- Empty state -->\n @if (items$ | async; as all) {\n @if (all.length === 0) {\n <div class=\"section-header\">\n {{ (config.templating?.emptyState?.expr || 'Nenhum item dispon\u00EDvel') }}\n </div>\n }\n }\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 (section of sections$ | async; track trackBySection($index, section)) {\n @if (section.key) {\n <div class=\"section-header mat-subheader\">\n {{ sectionHeader(section.key) }}\n </div>\n }\n @for (item of section.items; track trackByItem($index, item); let i = $index) {\n <mat-list-option\n [value]=\"item\"\n (click)=\"onItemClick(item, i, section.key || undefined)\"\n >\n <div class=\"list-item-content\">\n @if (leading(item); as lead) {\n @switch (lead.type) {\n @case ('icon') {\n <mat-icon [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</mat-icon>\n }\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) {\n <mat-chip class=\"lead-badge\" [color]=\"lead.badge?.color || undefined\" [ngClass]=\"(((lead.badge?.variant || 'filled') === 'outlined') ? 'chip-outlined' : '')\">{{ lead.badge?.value }}</mat-chip>\n }\n </div>\n }\n }\n }\n @if (meta(item); as m) {\n <div>\n <div class=\"primary\" [ngClass]=\"primary(item)?.class\" [style.cssText]=\"primary(item)?.style\">{{ primary(item)?.value }}</div>\n @if (layoutLines > 1) { <div class=\"secondary\" [ngClass]=\"secondary(item)?.class\" [style.cssText]=\"secondary(item)?.style\">{{ secondary(item)?.value }}</div> }\n @if ((config.templating?.metaPlacement === 'line' && m?.type === 'text') || (layoutLines > 2 && m?.type === 'text')) {\n <div class=\"tertiary\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @if (config.templating?.metaPrefixIcon; as mpi) { <mat-icon [praxisIcon]=\"mpi\"></mat-icon> }\n {{ m.value }}\n </div>\n }\n @if (featuresVisible()) {\n @for (f of (config.templating?.features || []); track $index; let fi = $index) {\n <span class=\"feature\">\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 </span>\n }\n }\n </div>\n @if (!(((config.templating?.metaPlacement === 'line') || (layoutLines > 2)) && m?.type === 'text')) {\n <div class=\"meta\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @switch (m.type) {\n @case ('chip') { <mat-chip [color]=\"m.color || undefined\" [ngClass]=\"((m.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\">{{ m.value }}</mat-chip> }\n @case ('rating') { @for (_ of [0,1,2,3,4]; track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, m.value)\"></mat-icon> } }\n @default { <span>@if (config.templating?.metaPrefixIcon; as mpi2) { <mat-icon>{{ mpi2 }}</mat-icon> }{{ m.value }}</span> }\n }\n </div>\n }\n @if (trailing(item); as tr) {\n <div class=\"trailing\" [ngClass]=\"tr.class\" [style.cssText]=\"tr.style\">\n @switch (tr.type) {\n @case ('chip') { <mat-chip [color]=\"tr.color || undefined\" [ngClass]=\"((tr.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\">{{ tr.value }}</mat-chip> }\n @default { <span>{{ tr.value }}</span> }\n }\n </div>\n }\n }\n @for (action of visibleActions(item); let aidx = $index; track action?.id ?? $index) {\n @if ((action.kind || 'icon') === 'icon') {\n <button mat-icon-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">\n <mat-icon [praxisIcon]=\"action.icon\"></mat-icon>\n </button>\n } @else {\n @if (action.buttonVariant === 'stroked') { <button mat-stroked-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n @if (action.buttonVariant === 'raised') { <button mat-raised-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n @if (!action.buttonVariant || action.buttonVariant === 'flat') { <button mat-flat-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n }\n }\n </div>\n </mat-list-option>\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 (section of sections$ | async; track trackBySection($index, section); let sidx = $index) {\n @if (section.key) {\n <div class=\"section-header mat-subheader\">\n {{ sectionHeader(section.key) }}\n </div>\n }\n @for (item of section.items; track trackByItem($index, item); let i = $index) {\n <mat-list-item (click)=\"onItemClick(item, i, section.key || undefined)\">\n <div class=\"list-item-content\">\n @if (leading(item); as lead) { <mat-icon [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</mat-icon> }\n @if (meta(item); as m) {\n <div>\n <div class=\"primary\" [ngClass]=\"primary(item)?.class\" [style.cssText]=\"primary(item)?.style\">{{ primary(item)?.value }}</div>\n @if (layoutLines > 1) { <div class=\"secondary\" [ngClass]=\"secondary(item)?.class\" [style.cssText]=\"secondary(item)?.style\">{{ secondary(item)?.value }}</div> }\n @if ((config.templating?.metaPlacement === 'line' && m?.type === 'text') || (layoutLines > 2 && m?.type === 'text')) { <div class=\"tertiary\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">{{ m.value }}</div> }\n </div>\n @if (!(((config.templating?.metaPlacement === 'line') || (layoutLines > 2)) && m?.type === 'text')) {\n <div class=\"meta\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @switch (m.type) {\n @case ('chip') { <mat-chip>{{ m.value }}</mat-chip> }\n @case ('rating') { @for (_ of [0,1,2,3,4]; track $index; let idx = $index) { <mat-icon>{{ starIcon(idx, m.value) }}</mat-icon> } }\n @default { <span>{{ m.value }}</span> }\n }\n </div>\n }\n @if (trailing(item); as tr) {\n <div class=\"trailing\" [ngClass]=\"tr.class\" [style.cssText]=\"tr.style\">\n @switch (tr.type) {\n @case ('chip') { <mat-chip>{{ tr.value }}</mat-chip> }\n @default { <span>{{ tr.value }}</span> }\n }\n </div>\n }\n }\n @for (action of visibleActions(item); let aidx = $index; track action?.id ?? $index) {\n <button mat-icon-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">\n <mat-icon>{{ action.icon }}</mat-icon>\n </button>\n }\n </div>\n </mat-list-item>\n }\n @if (config.layout?.dividers === 'between') { <mat-divider></mat-divider> }\n }\n </mat-list>\n </ng-template>\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 class=\"item-card\" (click)=\"onItemClick(it, i)\">\n <div class=\"list-item-content\">\n @if (leading(it); as lead) {\n @switch (lead.type) {\n @case ('icon') { <mat-icon [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</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\" [color]=\"lead.badge?.color || undefined\" [ngClass]=\"(((lead.badge?.variant || 'filled') === 'outlined') ? 'chip-outlined' : '')\">{{ lead.badge?.value }}</mat-chip> }\n </div>\n }\n }\n }\n @if (meta(it); as m) {\n <div>\n <div class=\"primary\" [ngClass]=\"primary(it)?.class\" [style.cssText]=\"primary(it)?.style\">{{ primary(it)?.value }}</div>\n @if (layoutLines > 1) { <div class=\"secondary\" [ngClass]=\"secondary(it)?.class\" [style.cssText]=\"secondary(it)?.style\">{{ secondary(it)?.value }}</div> }\n @if ((config.templating?.metaPlacement === 'line' && m?.type === 'text') || (layoutLines > 2 && m?.type === 'text')) { <div class=\"tertiary\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">{{ m.value }}</div> }\n @if (featuresVisible()) {\n @for (f of (config.templating?.features || []); track $index; let fi = $index) {\n <span class=\"feature\">\n @if (f.icon && featuresMode() !== 'labels-only') { <mat-icon>{{ f.icon }}</mat-icon> }\n @if (featuresMode() !== 'icons-only') { <span [ngClass]=\"f.class\" [style.cssText]=\"f.style\">{{ featureLabel(it, f.expr) }}</span> }\n </span>\n }\n }\n </div>\n @if (!(((config.templating?.metaPlacement === 'line') || (layoutLines > 2)) && m?.type === 'text')) {\n <div class=\"meta\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @switch (m.type) {\n @case ('chip') { <mat-chip [color]=\"m.color || undefined\" [ngClass]=\"((m.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\">{{ m.value }}</mat-chip> }\n @case ('rating') { @for (_ of [0,1,2,3,4]; track $index; let idx = $index) { <mat-icon>{{ starIcon(idx, m.value) }}</mat-icon> } }\n @default { <span>{{ m.value }}</span> }\n }\n </div>\n }\n @if (trailing(it); as tr) {\n <div class=\"trailing\" [ngClass]=\"tr.class\" [style.cssText]=\"tr.style\">\n @switch (tr.type) {\n @case ('chip') { <mat-chip [color]=\"tr.color || undefined\" [ngClass]=\"((tr.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\">{{ tr.value }}</mat-chip> }\n @default { <span>{{ tr.value }}</span> }\n }\n </div>\n }\n }\n </div>\n <div class=\"card-actions\" (click)=\"$event.stopPropagation()\">\n @for (action of visibleActions(it); let aidx = $index; track action?.id ?? $index) {\n @if ((action.kind || 'icon') === 'icon') {\n <button mat-icon-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, it, i)\" [attr.aria-label]=\"action.label || action.id\">\n <mat-icon>{{ action.icon }}</mat-icon>\n </button>\n } @else {\n @if (action.buttonVariant === 'stroked') { <button mat-stroked-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, it, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n @if (action.buttonVariant === 'raised') { <button mat-raised-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, it, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n @if (!action.buttonVariant || action.buttonVariant === 'flat') { <button mat-flat-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, it, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n }\n }\n </div>\n </div>\n }\n </div>\n </ng-template>\n \n <!-- Simple pagination controls for remote data -->\n @if (config.dataSource?.resourcePath) {\n <div class=\"paginator\">\n <button mat-stroked-button color=\"primary\" (click)=\"prevPage()\">Anterior</button>\n <button mat-stroked-button color=\"primary\" (click)=\"nextPage()\">Pr\u00F3ximo</button>\n @if (total$ | async; as total) { <span class=\"muted\">Total: {{ total }}</span> }\n </div>\n }\n </ng-template>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.praxis-list-root{--p-list-radius: 1rem;--p-list-shadow: 0 6px 20px rgba(0, 0, 0, .08);--p-list-border: 1px solid rgba(0, 0, 0, .08);--p-list-blur: 8px;--p-list-grad-from: #8e72ff;--p-list-grad-to: #ffaa70;--p-list-grad-angle: 135deg}.skin-elevated .item-card{background:var(--mdc-elevated-card-container-color, var(--md-sys-color-surface));border-radius:var(--p-list-radius);box-shadow:var(--p-list-shadow);border:var(--p-list-border)}.skin-pill-soft .item-card{background:var(--mdc-elevated-card-container-color, var(--md-sys-color-surface));border-radius:calc(var(--p-list-radius) * 1.3);box-shadow:0 4px 16px #00000014;border:var(--p-list-border)}.skin-pill-soft .mat-mdc-list-item .list-item-content{background:var(--mdc-elevated-card-container-color, var(--md-sys-color-surface));border-radius:calc(var(--p-list-radius) * 1.3);box-shadow:0 4px 16px #00000014;border:var(--p-list-border);color:var(--p-list-foreground, #1f2937)}.skin-gradient-tile .item-card,.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:#fff}.skin-glass .item-card{background:#ffffff2e;border-radius:var(--p-list-radius);border:1px solid rgba(255,255,255,.3);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:#ffffff2e;border-radius:var(--p-list-radius);border:1px solid rgba(255,255,255,.3);backdrop-filter:blur(var(--p-list-blur));-webkit-backdrop-filter:blur(var(--p-list-blur));color:var(--p-list-foreground, #111)}.density-compact .mat-mdc-list-item,.density-compact .item-card{--_item-padding: 4px 8px}.density-comfortable .mat-mdc-list-item,.density-comfortable .item-card{--_item-padding: 8px 12px}.list-item-content{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:12px;padding:var(--_item-padding, 12px 16px)}.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}.model-media .lead-image{width:136px;height:96px;border-radius:16px}.model-media .list-item-content{gap:16px}.model-hotel .lead-image{width:160px;height:110px;border-radius:18px}.primary{font-weight:600;color:var(--sicoob-tertiary-default, #001E24)}.secondary{opacity:.8}.tertiary{opacity:.68}.features{display:flex;flex-wrap:wrap;gap:12px;margin-top:6px;color:#000000a8}.feature{display:inline-flex;align-items:center;gap:6px}.meta{opacity:.9}.trailing{opacity:.9;margin-left:8px}.section-header{font-size:.85rem;opacity:.7;padding:8px 12px}.cards-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}.item-card{padding:12px;display:flex;flex-direction:column;min-height:160px}.item-card:hover{box-shadow:0 10px 24px var(--sicoob-shadow-medium, rgba(0, 0, 0, .12));transform:translateY(-1px);transition:box-shadow .15s ease,transform .15s ease}.card-actions{display:flex;align-items:center;justify-content:flex-end;gap:4px;padding:6px 8px 4px;border-top:1px solid var(--sicoob-stroke-medium, rgba(0, 0, 0, .08));margin-top:auto}.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:color-mix(in srgb,var(--sicoob-info-lightest, #f0f9ff),transparent 30%);color:var(--sicoob-info-default, #0090e0)}.mat-mdc-chip{--mdc-chip-container-height: 22px;font-size:12px}.meta .mat-icon{font-size:18px;height:18px;width:18px}.primary,.secondary{display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}.primary,.secondary{-webkit-line-clamp:1}.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:#f6f7f8;background:linear-gradient(to right,#eee 8%,#ddd 18%,#eee 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%}.paginator{display:flex;align-items:center;gap:8px;padding:8px 4px}.muted{opacity:.7}\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: i14.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: i7.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: i7.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3487
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: PraxisList, isStandalone: true, selector: "praxis-list", inputs: { config: "config", form: "form" }, outputs: { itemClick: "itemClick", actionClick: "actionClick", selectionChange: "selectionChange" }, providers: [GenericCrudService, ListDataService], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"praxis-list-root\"\n [ngClass]=\"skinClasses\"\n [attr.aria-label]=\"config.a11y?.ariaLabel || null\"\n [attr.aria-labelledby]=\"config.a11y?.ariaLabelledBy || null\"\n>\n @if (inlineCss) {\n <style [innerHTML]=\"inlineCss\"></style>\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=\"skeleton skeleton-avatar\"></div>\n <div style=\"width:100%\">\n <div class=\"skeleton skeleton-line w-60\"></div>\n @if (layoutLines > 1) { <div class=\"skeleton skeleton-line w-40\"></div> }\n </div>\n <div class=\"skeleton skeleton-chip\"></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=\"skeleton skeleton-avatar\"></div>\n <div style=\"width:100%\">\n <div class=\"skeleton skeleton-line w-60\"></div>\n @if (layoutLines > 1) { <div class=\"skeleton skeleton-line w-40\"></div> }\n </div>\n <div class=\"skeleton skeleton-chip\"></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 <!-- Empty state -->\n @if (items$ | async; as all) {\n @if (all.length === 0) {\n <div class=\"section-header\">\n {{ (config.templating?.emptyState?.expr || 'Nenhum item dispon\u00EDvel') }}\n </div>\n }\n }\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 (section of sections$ | async; track trackBySection($index, section)) {\n @if (section.key) {\n <div class=\"section-header mat-subheader\">\n {{ sectionHeader(section.key) }}\n </div>\n }\n @for (item of section.items; track trackByItem($index, item); let i = $index) {\n <mat-list-option\n [value]=\"item\"\n (click)=\"onItemClick(item, i, section.key || undefined)\"\n >\n <div class=\"list-item-content\">\n @if (leading(item); as lead) {\n @switch (lead.type) {\n @case ('icon') {\n <mat-icon [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</mat-icon>\n }\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) {\n <mat-chip class=\"lead-badge\" [color]=\"lead.badge?.color || undefined\" [ngClass]=\"(((lead.badge?.variant || 'filled') === 'outlined') ? 'chip-outlined' : '')\">{{ lead.badge?.value }}</mat-chip>\n }\n </div>\n }\n }\n }\n @if (meta(item); as m) {\n <div>\n <div class=\"primary\" [ngClass]=\"primary(item)?.class\" [style.cssText]=\"primary(item)?.style\">{{ primary(item)?.value }}</div>\n @if (layoutLines > 1) { <div class=\"secondary\" [ngClass]=\"secondary(item)?.class\" [style.cssText]=\"secondary(item)?.style\">{{ secondary(item)?.value }}</div> }\n @if ((config.templating?.metaPlacement === 'line' && (m?.type === 'text' || m?.type === 'date')) || (layoutLines > 2 && (m?.type === 'text' || m?.type === 'date'))) {\n <div class=\"tertiary\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @if (config.templating?.metaPrefixIcon; as mpi) { <mat-icon [praxisIcon]=\"mpi\"></mat-icon> }\n {{ m.value }}\n </div>\n }\n @if (featuresVisible()) {\n @for (f of (config.templating?.features || []); track $index; let fi = $index) {\n <span class=\"feature\">\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 </span>\n }\n }\n </div>\n @if (!(((config.templating?.metaPlacement === 'line') || (layoutLines > 2)) && (m?.type === 'text' || m?.type === 'date'))) {\n <div class=\"meta\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @switch (m.type) {\n @case ('chip') { <mat-chip [color]=\"m.color || undefined\" [ngClass]=\"((m.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\">{{ m.value }}</mat-chip> }\n @case ('rating') { @for (_ of [0,1,2,3,4]; track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, m.value)\"></mat-icon> } }\n @default { <span>@if (config.templating?.metaPrefixIcon; as mpi2) { <mat-icon>{{ mpi2 }}</mat-icon> }{{ m.value }}</span> }\n }\n </div>\n }\n @if (trailing(item); as tr) {\n <div class=\"trailing\" [ngClass]=\"tr.class\" [style.cssText]=\"tr.style\">\n @switch (tr.type) {\n @case ('chip') { <mat-chip [color]=\"tr.color || undefined\" [ngClass]=\"((tr.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\">{{ tr.value }}</mat-chip> }\n @case ('icon') { <mat-icon [praxisIcon]=\"tr.value\" [color]=\"tr.color || undefined\"></mat-icon> }\n @default { <span>{{ tr.value }}</span> }\n }\n </div>\n }\n }\n @for (action of visibleActions(item); let aidx = $index; track action?.id ?? $index) {\n @if ((action.kind || 'icon') === 'icon') {\n <button mat-icon-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">\n <mat-icon [praxisIcon]=\"action.icon\"></mat-icon>\n </button>\n } @else {\n @if (action.buttonVariant === 'stroked') { <button mat-stroked-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n @if (action.buttonVariant === 'raised') { <button mat-raised-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n @if (!action.buttonVariant || action.buttonVariant === 'flat') { <button mat-flat-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n }\n }\n </div>\n </mat-list-option>\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 (section of sections$ | async; track trackBySection($index, section); let sidx = $index) {\n @if (section.key) {\n <div class=\"section-header mat-subheader\">\n {{ sectionHeader(section.key) }}\n </div>\n }\n @for (item of section.items; track trackByItem($index, item); let i = $index) {\n <mat-list-item (click)=\"onItemClick(item, i, section.key || undefined)\">\n <div class=\"list-item-content\">\n @if (leading(item); as lead) { <mat-icon [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</mat-icon> }\n @if (meta(item); as m) {\n <div>\n <div class=\"primary\" [ngClass]=\"primary(item)?.class\" [style.cssText]=\"primary(item)?.style\">{{ primary(item)?.value }}</div>\n @if (layoutLines > 1) { <div class=\"secondary\" [ngClass]=\"secondary(item)?.class\" [style.cssText]=\"secondary(item)?.style\">{{ secondary(item)?.value }}</div> }\n @if ((config.templating?.metaPlacement === 'line' && (m?.type === 'text' || m?.type === 'date')) || (layoutLines > 2 && (m?.type === 'text' || m?.type === 'date'))) { <div class=\"tertiary\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">{{ m.value }}</div> }\n </div>\n @if (!(((config.templating?.metaPlacement === 'line') || (layoutLines > 2)) && (m?.type === 'text' || m?.type === 'date'))) {\n <div class=\"meta\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @switch (m.type) {\n @case ('chip') { <mat-chip>{{ m.value }}</mat-chip> }\n @case ('rating') { @for (_ of [0,1,2,3,4]; track $index; let idx = $index) { <mat-icon>{{ starIcon(idx, m.value) }}</mat-icon> } }\n @default { <span>{{ m.value }}</span> }\n }\n </div>\n }\n @if (trailing(item); as tr) {\n <div class=\"trailing\" [ngClass]=\"tr.class\" [style.cssText]=\"tr.style\">\n @switch (tr.type) {\n @case ('chip') { <mat-chip>{{ tr.value }}</mat-chip> }\n @case ('icon') { <mat-icon [praxisIcon]=\"tr.value\" [color]=\"tr.color || undefined\"></mat-icon> }\n @default { <span>{{ tr.value }}</span> }\n }\n </div>\n }\n }\n @for (action of visibleActions(item); let aidx = $index; track action?.id ?? $index) {\n <button mat-icon-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">\n <mat-icon>{{ action.icon }}</mat-icon>\n </button>\n }\n </div>\n </mat-list-item>\n }\n @if (config.layout?.dividers === 'between') { <mat-divider></mat-divider> }\n }\n </mat-list>\n </ng-template>\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 class=\"item-card\" (click)=\"onItemClick(it, i)\">\n <div class=\"list-item-content\">\n @if (leading(it); as lead) {\n @switch (lead.type) {\n @case ('icon') { <mat-icon [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</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\" [color]=\"lead.badge?.color || undefined\" [ngClass]=\"(((lead.badge?.variant || 'filled') === 'outlined') ? 'chip-outlined' : '')\">{{ lead.badge?.value }}</mat-chip> }\n </div>\n }\n }\n }\n @if (meta(it); as m) {\n <div>\n <div class=\"primary\" [ngClass]=\"primary(it)?.class\" [style.cssText]=\"primary(it)?.style\">{{ primary(it)?.value }}</div>\n @if (layoutLines > 1) { <div class=\"secondary\" [ngClass]=\"secondary(it)?.class\" [style.cssText]=\"secondary(it)?.style\">{{ secondary(it)?.value }}</div> }\n @if ((config.templating?.metaPlacement === 'line' && (m?.type === 'text' || m?.type === 'date')) || (layoutLines > 2 && (m?.type === 'text' || m?.type === 'date'))) { <div class=\"tertiary\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">{{ m.value }}</div> }\n @if (featuresVisible()) {\n @for (f of (config.templating?.features || []); track $index; let fi = $index) {\n <span class=\"feature\">\n @if (f.icon && featuresMode() !== 'labels-only') { <mat-icon>{{ f.icon }}</mat-icon> }\n @if (featuresMode() !== 'icons-only') { <span [ngClass]=\"f.class\" [style.cssText]=\"f.style\">{{ featureLabel(it, f.expr) }}</span> }\n </span>\n }\n }\n </div>\n @if (!(((config.templating?.metaPlacement === 'line') || (layoutLines > 2)) && (m?.type === 'text' || m?.type === 'date'))) {\n <div class=\"meta\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @switch (m.type) {\n @case ('chip') { <mat-chip [color]=\"m.color || undefined\" [ngClass]=\"((m.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\">{{ m.value }}</mat-chip> }\n @case ('rating') { @for (_ of [0,1,2,3,4]; track $index; let idx = $index) { <mat-icon>{{ starIcon(idx, m.value) }}</mat-icon> } }\n @default { <span>{{ m.value }}</span> }\n }\n </div>\n }\n @if (trailing(it); as tr) {\n @if ((config.templating?.statusPosition || 'inline') === 'top-right' && (tr?.type === 'chip' || tr?.type === 'icon')) {\n <div class=\"status-overlay\">\n @if (tr?.type === 'chip') { <mat-chip [color]=\"tr.color || undefined\" [ngClass]=\"((tr.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\">{{ tr.value }}</mat-chip> }\n @if (tr?.type === 'icon') { <mat-icon [praxisIcon]=\"tr.value\" [color]=\"tr.color || undefined\"></mat-icon> }\n </div>\n } @else {\n <div class=\"trailing\" [ngClass]=\"tr.class\" [style.cssText]=\"tr.style\">\n @switch (tr.type) {\n @case ('chip') { <mat-chip [color]=\"tr.color || undefined\" [ngClass]=\"((tr.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\">{{ tr.value }}</mat-chip> }\n @case ('icon') { <mat-icon [praxisIcon]=\"tr.value\" [color]=\"tr.color || undefined\"></mat-icon> }\n @default { <span>{{ tr.value }}</span> }\n }\n </div>\n }\n }\n }\n </div>\n <div class=\"card-actions\" (click)=\"$event.stopPropagation()\">\n @for (action of visibleActions(it); let aidx = $index; track action?.id ?? $index) {\n @if ((action.kind || 'icon') === 'icon') {\n <button mat-icon-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, it, i)\" [attr.aria-label]=\"action.label || action.id\">\n <mat-icon>{{ action.icon }}</mat-icon>\n </button>\n } @else {\n @if (action.buttonVariant === 'stroked') { <button mat-stroked-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, it, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n @if (action.buttonVariant === 'raised') { <button mat-raised-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, it, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n @if (!action.buttonVariant || action.buttonVariant === 'flat') { <button mat-flat-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, it, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n }\n }\n </div>\n </div>\n }\n </div>\n </ng-template>\n \n <!-- Simple pagination controls for remote data -->\n @if (config.dataSource?.resourcePath) {\n <div class=\"paginator\" style=\"flex-wrap: wrap; gap: 8px;\">\n <div style=\"display:flex; align-items:center; gap:8px; flex-wrap: wrap;\">\n <button mat-stroked-button color=\"primary\" (click)=\"prevPage()\">Anterior</button>\n <button mat-stroked-button color=\"primary\" (click)=\"nextPage()\">Pr\u00F3ximo</button>\n <mat-form-field style=\"width: 120px;\" appearance=\"outline\">\n <mat-label>Tam. p\u00E1gina</mat-label>\n <mat-select (selectionChange)=\"setPageSize($event.value)\" [value]=\"config.layout?.pageSize || 10\">\n <mat-option [value]=\"6\">6</mat-option>\n <mat-option [value]=\"8\">8</mat-option>\n <mat-option [value]=\"12\">12</mat-option>\n <mat-option [value]=\"24\">24</mat-option>\n </mat-select>\n </mat-form-field>\n @if (config.ui?.showSort) {\n <mat-form-field style=\"width: 180px;\" 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)\">{{ sortOptionLabel(op) }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n @if (config.ui?.showSearch && config.ui?.searchField) {\n <mat-form-field style=\"min-width: 220px;\" appearance=\"outline\">\n <mat-label>{{ config.ui?.searchPlaceholder || 'Buscar' }}</mat-label>\n <input matInput type=\"search\" (input)=\"onSearchInput($any($event.target).value)\" />\n </mat-form-field>\n }\n </div>\n <div style=\"display:flex; align-items:center; gap:8px; margin-left:auto;\">\n @if ((config.ui?.showRange ?? true)) {\n @if (page$ | async; as ps) {\n @if (total$ | async; as total) {\n @if (items$ | async; as curr) {\n <span class=\"muted\">{{ rangeStart(ps) }}\u2013{{ rangeEnd(curr?.length || 0, ps, total || 0) }} de {{ total || 0 }}</span>\n }\n }\n }\n } @else {\n @if (total$ | async; as total2) { <span class=\"muted\">Total: {{ total2 }}</span> }\n }\n </div>\n </div>\n }\n </ng-template>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.praxis-list-root{--p-list-radius: 1rem;--p-list-shadow: 0 6px 20px rgba(0, 0, 0, .08);--p-list-border: 1px solid rgba(0, 0, 0, .08);--p-list-blur: 8px;--p-list-grad-from: #8e72ff;--p-list-grad-to: #ffaa70;--p-list-grad-angle: 135deg}.skin-elevated .item-card{background:var(--mdc-elevated-card-container-color, var(--md-sys-color-surface));border-radius:var(--p-list-radius);box-shadow:var(--p-list-shadow);border:var(--p-list-border)}.skin-pill-soft .item-card{background:var(--mdc-elevated-card-container-color, var(--md-sys-color-surface));border-radius:calc(var(--p-list-radius) * 1.3);box-shadow:0 4px 16px #00000014;border:var(--p-list-border)}.skin-pill-soft .mat-mdc-list-item .list-item-content{background:var(--mdc-elevated-card-container-color, var(--md-sys-color-surface));border-radius:calc(var(--p-list-radius) * 1.3);box-shadow:0 4px 16px #00000014;border:var(--p-list-border);color:var(--p-list-foreground, #1f2937)}.skin-gradient-tile .item-card,.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:#fff}.skin-glass .item-card{background:#ffffff2e;border-radius:var(--p-list-radius);border:1px solid rgba(255,255,255,.3);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:#ffffff2e;border-radius:var(--p-list-radius);border:1px solid rgba(255,255,255,.3);backdrop-filter:blur(var(--p-list-blur));-webkit-backdrop-filter:blur(var(--p-list-blur));color:var(--p-list-foreground, #111)}.density-compact .mat-mdc-list-item,.density-compact .item-card{--_item-padding: 4px 8px}.density-comfortable .mat-mdc-list-item,.density-comfortable .item-card{--_item-padding: 8px 12px}.list-item-content{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:12px;padding:var(--_item-padding, 12px 16px)}.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}.model-media .lead-image{width:136px;height:96px;border-radius:16px}.model-media .list-item-content{gap:16px}.model-hotel .lead-image{width:160px;height:110px;border-radius:18px}.primary{font-weight:600;color:var(--sicoob-tertiary-default, #001E24)}.secondary{opacity:.8}.tertiary{opacity:.68}.features{display:flex;flex-wrap:wrap;gap:12px;margin-top:6px;color:inherit}.feature{display:inline-flex;align-items:center;gap:6px}.meta{opacity:.9}.trailing{opacity:.9;margin-left:8px}.section-header{font-size:.85rem;opacity:.7;padding:8px 12px}.cards-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}.item-card{padding:12px;display:flex;flex-direction:column;min-height:160px;position:relative;cursor:pointer}.item-card:hover{box-shadow:0 10px 24px var(--sicoob-shadow-medium, rgba(0, 0, 0, .12));transform:translateY(-1px);transition:box-shadow .15s ease,transform .15s ease}.card-actions{display:flex;align-items:center;justify-content:flex-end;gap:4px;padding:6px 8px 4px;border-top:1px solid var(--sicoob-stroke-medium, rgba(0, 0, 0, .08));margin-top:auto}.status-overlay{position:absolute;top:10px;right:10px;pointer-events:none}.status-overlay .mat-mdc-chip{pointer-events:auto}.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:color-mix(in srgb,var(--sicoob-info-lightest, #f0f9ff),transparent 30%);color:var(--sicoob-info-default, #0090e0)}.mat-mdc-chip{--mdc-chip-container-height: 22px;font-size:12px}.meta .mat-icon{font-size:18px;height:18px;width:18px}.primary,.secondary{display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}.primary,.secondary{-webkit-line-clamp:1}.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:#f6f7f8;background:linear-gradient(to right,#eee 8%,#ddd 18%,#eee 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%}.paginator{display:flex;align-items:center;gap:8px;padding:8px 4px}.muted{opacity:.7}\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: i14.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: i7.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: i7.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4$1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i6.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: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2865
3488
  }
2866
3489
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PraxisList, decorators: [{
2867
3490
  type: Component,
@@ -2874,7 +3497,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
2874
3497
  PraxisIconDirective,
2875
3498
  MatChipsModule,
2876
3499
  MatButtonModule,
2877
- ], changeDetection: ChangeDetectionStrategy.OnPush, providers: [GenericCrudService, ListDataService], template: "<div\n class=\"praxis-list-root\"\n [ngClass]=\"skinClasses\"\n [attr.aria-label]=\"config.a11y?.ariaLabel || null\"\n [attr.aria-labelledby]=\"config.a11y?.ariaLabelledBy || null\"\n>\n @if (inlineCss) {\n <style [innerHTML]=\"inlineCss\"></style>\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=\"skeleton skeleton-avatar\"></div>\n <div style=\"width:100%\">\n <div class=\"skeleton skeleton-line w-60\"></div>\n @if (layoutLines > 1) { <div class=\"skeleton skeleton-line w-40\"></div> }\n </div>\n <div class=\"skeleton skeleton-chip\"></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=\"skeleton skeleton-avatar\"></div>\n <div style=\"width:100%\">\n <div class=\"skeleton skeleton-line w-60\"></div>\n @if (layoutLines > 1) { <div class=\"skeleton skeleton-line w-40\"></div> }\n </div>\n <div class=\"skeleton skeleton-chip\"></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 <!-- Empty state -->\n @if (items$ | async; as all) {\n @if (all.length === 0) {\n <div class=\"section-header\">\n {{ (config.templating?.emptyState?.expr || 'Nenhum item dispon\u00EDvel') }}\n </div>\n }\n }\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 (section of sections$ | async; track trackBySection($index, section)) {\n @if (section.key) {\n <div class=\"section-header mat-subheader\">\n {{ sectionHeader(section.key) }}\n </div>\n }\n @for (item of section.items; track trackByItem($index, item); let i = $index) {\n <mat-list-option\n [value]=\"item\"\n (click)=\"onItemClick(item, i, section.key || undefined)\"\n >\n <div class=\"list-item-content\">\n @if (leading(item); as lead) {\n @switch (lead.type) {\n @case ('icon') {\n <mat-icon [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</mat-icon>\n }\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) {\n <mat-chip class=\"lead-badge\" [color]=\"lead.badge?.color || undefined\" [ngClass]=\"(((lead.badge?.variant || 'filled') === 'outlined') ? 'chip-outlined' : '')\">{{ lead.badge?.value }}</mat-chip>\n }\n </div>\n }\n }\n }\n @if (meta(item); as m) {\n <div>\n <div class=\"primary\" [ngClass]=\"primary(item)?.class\" [style.cssText]=\"primary(item)?.style\">{{ primary(item)?.value }}</div>\n @if (layoutLines > 1) { <div class=\"secondary\" [ngClass]=\"secondary(item)?.class\" [style.cssText]=\"secondary(item)?.style\">{{ secondary(item)?.value }}</div> }\n @if ((config.templating?.metaPlacement === 'line' && m?.type === 'text') || (layoutLines > 2 && m?.type === 'text')) {\n <div class=\"tertiary\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @if (config.templating?.metaPrefixIcon; as mpi) { <mat-icon [praxisIcon]=\"mpi\"></mat-icon> }\n {{ m.value }}\n </div>\n }\n @if (featuresVisible()) {\n @for (f of (config.templating?.features || []); track $index; let fi = $index) {\n <span class=\"feature\">\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 </span>\n }\n }\n </div>\n @if (!(((config.templating?.metaPlacement === 'line') || (layoutLines > 2)) && m?.type === 'text')) {\n <div class=\"meta\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @switch (m.type) {\n @case ('chip') { <mat-chip [color]=\"m.color || undefined\" [ngClass]=\"((m.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\">{{ m.value }}</mat-chip> }\n @case ('rating') { @for (_ of [0,1,2,3,4]; track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, m.value)\"></mat-icon> } }\n @default { <span>@if (config.templating?.metaPrefixIcon; as mpi2) { <mat-icon>{{ mpi2 }}</mat-icon> }{{ m.value }}</span> }\n }\n </div>\n }\n @if (trailing(item); as tr) {\n <div class=\"trailing\" [ngClass]=\"tr.class\" [style.cssText]=\"tr.style\">\n @switch (tr.type) {\n @case ('chip') { <mat-chip [color]=\"tr.color || undefined\" [ngClass]=\"((tr.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\">{{ tr.value }}</mat-chip> }\n @default { <span>{{ tr.value }}</span> }\n }\n </div>\n }\n }\n @for (action of visibleActions(item); let aidx = $index; track action?.id ?? $index) {\n @if ((action.kind || 'icon') === 'icon') {\n <button mat-icon-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">\n <mat-icon [praxisIcon]=\"action.icon\"></mat-icon>\n </button>\n } @else {\n @if (action.buttonVariant === 'stroked') { <button mat-stroked-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n @if (action.buttonVariant === 'raised') { <button mat-raised-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n @if (!action.buttonVariant || action.buttonVariant === 'flat') { <button mat-flat-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n }\n }\n </div>\n </mat-list-option>\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 (section of sections$ | async; track trackBySection($index, section); let sidx = $index) {\n @if (section.key) {\n <div class=\"section-header mat-subheader\">\n {{ sectionHeader(section.key) }}\n </div>\n }\n @for (item of section.items; track trackByItem($index, item); let i = $index) {\n <mat-list-item (click)=\"onItemClick(item, i, section.key || undefined)\">\n <div class=\"list-item-content\">\n @if (leading(item); as lead) { <mat-icon [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</mat-icon> }\n @if (meta(item); as m) {\n <div>\n <div class=\"primary\" [ngClass]=\"primary(item)?.class\" [style.cssText]=\"primary(item)?.style\">{{ primary(item)?.value }}</div>\n @if (layoutLines > 1) { <div class=\"secondary\" [ngClass]=\"secondary(item)?.class\" [style.cssText]=\"secondary(item)?.style\">{{ secondary(item)?.value }}</div> }\n @if ((config.templating?.metaPlacement === 'line' && m?.type === 'text') || (layoutLines > 2 && m?.type === 'text')) { <div class=\"tertiary\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">{{ m.value }}</div> }\n </div>\n @if (!(((config.templating?.metaPlacement === 'line') || (layoutLines > 2)) && m?.type === 'text')) {\n <div class=\"meta\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @switch (m.type) {\n @case ('chip') { <mat-chip>{{ m.value }}</mat-chip> }\n @case ('rating') { @for (_ of [0,1,2,3,4]; track $index; let idx = $index) { <mat-icon>{{ starIcon(idx, m.value) }}</mat-icon> } }\n @default { <span>{{ m.value }}</span> }\n }\n </div>\n }\n @if (trailing(item); as tr) {\n <div class=\"trailing\" [ngClass]=\"tr.class\" [style.cssText]=\"tr.style\">\n @switch (tr.type) {\n @case ('chip') { <mat-chip>{{ tr.value }}</mat-chip> }\n @default { <span>{{ tr.value }}</span> }\n }\n </div>\n }\n }\n @for (action of visibleActions(item); let aidx = $index; track action?.id ?? $index) {\n <button mat-icon-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">\n <mat-icon>{{ action.icon }}</mat-icon>\n </button>\n }\n </div>\n </mat-list-item>\n }\n @if (config.layout?.dividers === 'between') { <mat-divider></mat-divider> }\n }\n </mat-list>\n </ng-template>\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 class=\"item-card\" (click)=\"onItemClick(it, i)\">\n <div class=\"list-item-content\">\n @if (leading(it); as lead) {\n @switch (lead.type) {\n @case ('icon') { <mat-icon [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</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\" [color]=\"lead.badge?.color || undefined\" [ngClass]=\"(((lead.badge?.variant || 'filled') === 'outlined') ? 'chip-outlined' : '')\">{{ lead.badge?.value }}</mat-chip> }\n </div>\n }\n }\n }\n @if (meta(it); as m) {\n <div>\n <div class=\"primary\" [ngClass]=\"primary(it)?.class\" [style.cssText]=\"primary(it)?.style\">{{ primary(it)?.value }}</div>\n @if (layoutLines > 1) { <div class=\"secondary\" [ngClass]=\"secondary(it)?.class\" [style.cssText]=\"secondary(it)?.style\">{{ secondary(it)?.value }}</div> }\n @if ((config.templating?.metaPlacement === 'line' && m?.type === 'text') || (layoutLines > 2 && m?.type === 'text')) { <div class=\"tertiary\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">{{ m.value }}</div> }\n @if (featuresVisible()) {\n @for (f of (config.templating?.features || []); track $index; let fi = $index) {\n <span class=\"feature\">\n @if (f.icon && featuresMode() !== 'labels-only') { <mat-icon>{{ f.icon }}</mat-icon> }\n @if (featuresMode() !== 'icons-only') { <span [ngClass]=\"f.class\" [style.cssText]=\"f.style\">{{ featureLabel(it, f.expr) }}</span> }\n </span>\n }\n }\n </div>\n @if (!(((config.templating?.metaPlacement === 'line') || (layoutLines > 2)) && m?.type === 'text')) {\n <div class=\"meta\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @switch (m.type) {\n @case ('chip') { <mat-chip [color]=\"m.color || undefined\" [ngClass]=\"((m.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\">{{ m.value }}</mat-chip> }\n @case ('rating') { @for (_ of [0,1,2,3,4]; track $index; let idx = $index) { <mat-icon>{{ starIcon(idx, m.value) }}</mat-icon> } }\n @default { <span>{{ m.value }}</span> }\n }\n </div>\n }\n @if (trailing(it); as tr) {\n <div class=\"trailing\" [ngClass]=\"tr.class\" [style.cssText]=\"tr.style\">\n @switch (tr.type) {\n @case ('chip') { <mat-chip [color]=\"tr.color || undefined\" [ngClass]=\"((tr.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\">{{ tr.value }}</mat-chip> }\n @default { <span>{{ tr.value }}</span> }\n }\n </div>\n }\n }\n </div>\n <div class=\"card-actions\" (click)=\"$event.stopPropagation()\">\n @for (action of visibleActions(it); let aidx = $index; track action?.id ?? $index) {\n @if ((action.kind || 'icon') === 'icon') {\n <button mat-icon-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, it, i)\" [attr.aria-label]=\"action.label || action.id\">\n <mat-icon>{{ action.icon }}</mat-icon>\n </button>\n } @else {\n @if (action.buttonVariant === 'stroked') { <button mat-stroked-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, it, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n @if (action.buttonVariant === 'raised') { <button mat-raised-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, it, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n @if (!action.buttonVariant || action.buttonVariant === 'flat') { <button mat-flat-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, it, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n }\n }\n </div>\n </div>\n }\n </div>\n </ng-template>\n \n <!-- Simple pagination controls for remote data -->\n @if (config.dataSource?.resourcePath) {\n <div class=\"paginator\">\n <button mat-stroked-button color=\"primary\" (click)=\"prevPage()\">Anterior</button>\n <button mat-stroked-button color=\"primary\" (click)=\"nextPage()\">Pr\u00F3ximo</button>\n @if (total$ | async; as total) { <span class=\"muted\">Total: {{ total }}</span> }\n </div>\n }\n </ng-template>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.praxis-list-root{--p-list-radius: 1rem;--p-list-shadow: 0 6px 20px rgba(0, 0, 0, .08);--p-list-border: 1px solid rgba(0, 0, 0, .08);--p-list-blur: 8px;--p-list-grad-from: #8e72ff;--p-list-grad-to: #ffaa70;--p-list-grad-angle: 135deg}.skin-elevated .item-card{background:var(--mdc-elevated-card-container-color, var(--md-sys-color-surface));border-radius:var(--p-list-radius);box-shadow:var(--p-list-shadow);border:var(--p-list-border)}.skin-pill-soft .item-card{background:var(--mdc-elevated-card-container-color, var(--md-sys-color-surface));border-radius:calc(var(--p-list-radius) * 1.3);box-shadow:0 4px 16px #00000014;border:var(--p-list-border)}.skin-pill-soft .mat-mdc-list-item .list-item-content{background:var(--mdc-elevated-card-container-color, var(--md-sys-color-surface));border-radius:calc(var(--p-list-radius) * 1.3);box-shadow:0 4px 16px #00000014;border:var(--p-list-border);color:var(--p-list-foreground, #1f2937)}.skin-gradient-tile .item-card,.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:#fff}.skin-glass .item-card{background:#ffffff2e;border-radius:var(--p-list-radius);border:1px solid rgba(255,255,255,.3);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:#ffffff2e;border-radius:var(--p-list-radius);border:1px solid rgba(255,255,255,.3);backdrop-filter:blur(var(--p-list-blur));-webkit-backdrop-filter:blur(var(--p-list-blur));color:var(--p-list-foreground, #111)}.density-compact .mat-mdc-list-item,.density-compact .item-card{--_item-padding: 4px 8px}.density-comfortable .mat-mdc-list-item,.density-comfortable .item-card{--_item-padding: 8px 12px}.list-item-content{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:12px;padding:var(--_item-padding, 12px 16px)}.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}.model-media .lead-image{width:136px;height:96px;border-radius:16px}.model-media .list-item-content{gap:16px}.model-hotel .lead-image{width:160px;height:110px;border-radius:18px}.primary{font-weight:600;color:var(--sicoob-tertiary-default, #001E24)}.secondary{opacity:.8}.tertiary{opacity:.68}.features{display:flex;flex-wrap:wrap;gap:12px;margin-top:6px;color:#000000a8}.feature{display:inline-flex;align-items:center;gap:6px}.meta{opacity:.9}.trailing{opacity:.9;margin-left:8px}.section-header{font-size:.85rem;opacity:.7;padding:8px 12px}.cards-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}.item-card{padding:12px;display:flex;flex-direction:column;min-height:160px}.item-card:hover{box-shadow:0 10px 24px var(--sicoob-shadow-medium, rgba(0, 0, 0, .12));transform:translateY(-1px);transition:box-shadow .15s ease,transform .15s ease}.card-actions{display:flex;align-items:center;justify-content:flex-end;gap:4px;padding:6px 8px 4px;border-top:1px solid var(--sicoob-stroke-medium, rgba(0, 0, 0, .08));margin-top:auto}.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:color-mix(in srgb,var(--sicoob-info-lightest, #f0f9ff),transparent 30%);color:var(--sicoob-info-default, #0090e0)}.mat-mdc-chip{--mdc-chip-container-height: 22px;font-size:12px}.meta .mat-icon{font-size:18px;height:18px;width:18px}.primary,.secondary{display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}.primary,.secondary{-webkit-line-clamp:1}.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:#f6f7f8;background:linear-gradient(to right,#eee 8%,#ddd 18%,#eee 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%}.paginator{display:flex;align-items:center;gap:8px;padding:8px 4px}.muted{opacity:.7}\n"] }]
3500
+ MatFormFieldModule,
3501
+ MatSelectModule,
3502
+ MatInputModule,
3503
+ ], changeDetection: ChangeDetectionStrategy.OnPush, providers: [GenericCrudService, ListDataService], template: "<div\n class=\"praxis-list-root\"\n [ngClass]=\"skinClasses\"\n [attr.aria-label]=\"config.a11y?.ariaLabel || null\"\n [attr.aria-labelledby]=\"config.a11y?.ariaLabelledBy || null\"\n>\n @if (inlineCss) {\n <style [innerHTML]=\"inlineCss\"></style>\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=\"skeleton skeleton-avatar\"></div>\n <div style=\"width:100%\">\n <div class=\"skeleton skeleton-line w-60\"></div>\n @if (layoutLines > 1) { <div class=\"skeleton skeleton-line w-40\"></div> }\n </div>\n <div class=\"skeleton skeleton-chip\"></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=\"skeleton skeleton-avatar\"></div>\n <div style=\"width:100%\">\n <div class=\"skeleton skeleton-line w-60\"></div>\n @if (layoutLines > 1) { <div class=\"skeleton skeleton-line w-40\"></div> }\n </div>\n <div class=\"skeleton skeleton-chip\"></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 <!-- Empty state -->\n @if (items$ | async; as all) {\n @if (all.length === 0) {\n <div class=\"section-header\">\n {{ (config.templating?.emptyState?.expr || 'Nenhum item dispon\u00EDvel') }}\n </div>\n }\n }\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 (section of sections$ | async; track trackBySection($index, section)) {\n @if (section.key) {\n <div class=\"section-header mat-subheader\">\n {{ sectionHeader(section.key) }}\n </div>\n }\n @for (item of section.items; track trackByItem($index, item); let i = $index) {\n <mat-list-option\n [value]=\"item\"\n (click)=\"onItemClick(item, i, section.key || undefined)\"\n >\n <div class=\"list-item-content\">\n @if (leading(item); as lead) {\n @switch (lead.type) {\n @case ('icon') {\n <mat-icon [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</mat-icon>\n }\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) {\n <mat-chip class=\"lead-badge\" [color]=\"lead.badge?.color || undefined\" [ngClass]=\"(((lead.badge?.variant || 'filled') === 'outlined') ? 'chip-outlined' : '')\">{{ lead.badge?.value }}</mat-chip>\n }\n </div>\n }\n }\n }\n @if (meta(item); as m) {\n <div>\n <div class=\"primary\" [ngClass]=\"primary(item)?.class\" [style.cssText]=\"primary(item)?.style\">{{ primary(item)?.value }}</div>\n @if (layoutLines > 1) { <div class=\"secondary\" [ngClass]=\"secondary(item)?.class\" [style.cssText]=\"secondary(item)?.style\">{{ secondary(item)?.value }}</div> }\n @if ((config.templating?.metaPlacement === 'line' && (m?.type === 'text' || m?.type === 'date')) || (layoutLines > 2 && (m?.type === 'text' || m?.type === 'date'))) {\n <div class=\"tertiary\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @if (config.templating?.metaPrefixIcon; as mpi) { <mat-icon [praxisIcon]=\"mpi\"></mat-icon> }\n {{ m.value }}\n </div>\n }\n @if (featuresVisible()) {\n @for (f of (config.templating?.features || []); track $index; let fi = $index) {\n <span class=\"feature\">\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 </span>\n }\n }\n </div>\n @if (!(((config.templating?.metaPlacement === 'line') || (layoutLines > 2)) && (m?.type === 'text' || m?.type === 'date'))) {\n <div class=\"meta\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @switch (m.type) {\n @case ('chip') { <mat-chip [color]=\"m.color || undefined\" [ngClass]=\"((m.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\">{{ m.value }}</mat-chip> }\n @case ('rating') { @for (_ of [0,1,2,3,4]; track $index; let idx = $index) { <mat-icon [praxisIcon]=\"starIcon(idx, m.value)\"></mat-icon> } }\n @default { <span>@if (config.templating?.metaPrefixIcon; as mpi2) { <mat-icon>{{ mpi2 }}</mat-icon> }{{ m.value }}</span> }\n }\n </div>\n }\n @if (trailing(item); as tr) {\n <div class=\"trailing\" [ngClass]=\"tr.class\" [style.cssText]=\"tr.style\">\n @switch (tr.type) {\n @case ('chip') { <mat-chip [color]=\"tr.color || undefined\" [ngClass]=\"((tr.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\">{{ tr.value }}</mat-chip> }\n @case ('icon') { <mat-icon [praxisIcon]=\"tr.value\" [color]=\"tr.color || undefined\"></mat-icon> }\n @default { <span>{{ tr.value }}</span> }\n }\n </div>\n }\n }\n @for (action of visibleActions(item); let aidx = $index; track action?.id ?? $index) {\n @if ((action.kind || 'icon') === 'icon') {\n <button mat-icon-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">\n <mat-icon [praxisIcon]=\"action.icon\"></mat-icon>\n </button>\n } @else {\n @if (action.buttonVariant === 'stroked') { <button mat-stroked-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n @if (action.buttonVariant === 'raised') { <button mat-raised-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n @if (!action.buttonVariant || action.buttonVariant === 'flat') { <button mat-flat-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n }\n }\n </div>\n </mat-list-option>\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 (section of sections$ | async; track trackBySection($index, section); let sidx = $index) {\n @if (section.key) {\n <div class=\"section-header mat-subheader\">\n {{ sectionHeader(section.key) }}\n </div>\n }\n @for (item of section.items; track trackByItem($index, item); let i = $index) {\n <mat-list-item (click)=\"onItemClick(item, i, section.key || undefined)\">\n <div class=\"list-item-content\">\n @if (leading(item); as lead) { <mat-icon [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</mat-icon> }\n @if (meta(item); as m) {\n <div>\n <div class=\"primary\" [ngClass]=\"primary(item)?.class\" [style.cssText]=\"primary(item)?.style\">{{ primary(item)?.value }}</div>\n @if (layoutLines > 1) { <div class=\"secondary\" [ngClass]=\"secondary(item)?.class\" [style.cssText]=\"secondary(item)?.style\">{{ secondary(item)?.value }}</div> }\n @if ((config.templating?.metaPlacement === 'line' && (m?.type === 'text' || m?.type === 'date')) || (layoutLines > 2 && (m?.type === 'text' || m?.type === 'date'))) { <div class=\"tertiary\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">{{ m.value }}</div> }\n </div>\n @if (!(((config.templating?.metaPlacement === 'line') || (layoutLines > 2)) && (m?.type === 'text' || m?.type === 'date'))) {\n <div class=\"meta\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @switch (m.type) {\n @case ('chip') { <mat-chip>{{ m.value }}</mat-chip> }\n @case ('rating') { @for (_ of [0,1,2,3,4]; track $index; let idx = $index) { <mat-icon>{{ starIcon(idx, m.value) }}</mat-icon> } }\n @default { <span>{{ m.value }}</span> }\n }\n </div>\n }\n @if (trailing(item); as tr) {\n <div class=\"trailing\" [ngClass]=\"tr.class\" [style.cssText]=\"tr.style\">\n @switch (tr.type) {\n @case ('chip') { <mat-chip>{{ tr.value }}</mat-chip> }\n @case ('icon') { <mat-icon [praxisIcon]=\"tr.value\" [color]=\"tr.color || undefined\"></mat-icon> }\n @default { <span>{{ tr.value }}</span> }\n }\n </div>\n }\n }\n @for (action of visibleActions(item); let aidx = $index; track action?.id ?? $index) {\n <button mat-icon-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, item, i)\" [attr.aria-label]=\"action.label || action.id\">\n <mat-icon>{{ action.icon }}</mat-icon>\n </button>\n }\n </div>\n </mat-list-item>\n }\n @if (config.layout?.dividers === 'between') { <mat-divider></mat-divider> }\n }\n </mat-list>\n </ng-template>\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 class=\"item-card\" (click)=\"onItemClick(it, i)\">\n <div class=\"list-item-content\">\n @if (leading(it); as lead) {\n @switch (lead.type) {\n @case ('icon') { <mat-icon [ngClass]=\"lead.class\" [style.cssText]=\"lead.style\">{{ lead.value }}</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\" [color]=\"lead.badge?.color || undefined\" [ngClass]=\"(((lead.badge?.variant || 'filled') === 'outlined') ? 'chip-outlined' : '')\">{{ lead.badge?.value }}</mat-chip> }\n </div>\n }\n }\n }\n @if (meta(it); as m) {\n <div>\n <div class=\"primary\" [ngClass]=\"primary(it)?.class\" [style.cssText]=\"primary(it)?.style\">{{ primary(it)?.value }}</div>\n @if (layoutLines > 1) { <div class=\"secondary\" [ngClass]=\"secondary(it)?.class\" [style.cssText]=\"secondary(it)?.style\">{{ secondary(it)?.value }}</div> }\n @if ((config.templating?.metaPlacement === 'line' && (m?.type === 'text' || m?.type === 'date')) || (layoutLines > 2 && (m?.type === 'text' || m?.type === 'date'))) { <div class=\"tertiary\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">{{ m.value }}</div> }\n @if (featuresVisible()) {\n @for (f of (config.templating?.features || []); track $index; let fi = $index) {\n <span class=\"feature\">\n @if (f.icon && featuresMode() !== 'labels-only') { <mat-icon>{{ f.icon }}</mat-icon> }\n @if (featuresMode() !== 'icons-only') { <span [ngClass]=\"f.class\" [style.cssText]=\"f.style\">{{ featureLabel(it, f.expr) }}</span> }\n </span>\n }\n }\n </div>\n @if (!(((config.templating?.metaPlacement === 'line') || (layoutLines > 2)) && (m?.type === 'text' || m?.type === 'date'))) {\n <div class=\"meta\" [ngClass]=\"m.class\" [style.cssText]=\"m.style\">\n @switch (m.type) {\n @case ('chip') { <mat-chip [color]=\"m.color || undefined\" [ngClass]=\"((m.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\">{{ m.value }}</mat-chip> }\n @case ('rating') { @for (_ of [0,1,2,3,4]; track $index; let idx = $index) { <mat-icon>{{ starIcon(idx, m.value) }}</mat-icon> } }\n @default { <span>{{ m.value }}</span> }\n }\n </div>\n }\n @if (trailing(it); as tr) {\n @if ((config.templating?.statusPosition || 'inline') === 'top-right' && (tr?.type === 'chip' || tr?.type === 'icon')) {\n <div class=\"status-overlay\">\n @if (tr?.type === 'chip') { <mat-chip [color]=\"tr.color || undefined\" [ngClass]=\"((tr.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\">{{ tr.value }}</mat-chip> }\n @if (tr?.type === 'icon') { <mat-icon [praxisIcon]=\"tr.value\" [color]=\"tr.color || undefined\"></mat-icon> }\n </div>\n } @else {\n <div class=\"trailing\" [ngClass]=\"tr.class\" [style.cssText]=\"tr.style\">\n @switch (tr.type) {\n @case ('chip') { <mat-chip [color]=\"tr.color || undefined\" [ngClass]=\"((tr.variant || 'filled') === 'outlined') ? 'chip-outlined' : ''\">{{ tr.value }}</mat-chip> }\n @case ('icon') { <mat-icon [praxisIcon]=\"tr.value\" [color]=\"tr.color || undefined\"></mat-icon> }\n @default { <span>{{ tr.value }}</span> }\n }\n </div>\n }\n }\n }\n </div>\n <div class=\"card-actions\" (click)=\"$event.stopPropagation()\">\n @for (action of visibleActions(it); let aidx = $index; track action?.id ?? $index) {\n @if ((action.kind || 'icon') === 'icon') {\n <button mat-icon-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, it, i)\" [attr.aria-label]=\"action.label || action.id\">\n <mat-icon>{{ action.icon }}</mat-icon>\n </button>\n } @else {\n @if (action.buttonVariant === 'stroked') { <button mat-stroked-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, it, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n @if (action.buttonVariant === 'raised') { <button mat-raised-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, it, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n @if (!action.buttonVariant || action.buttonVariant === 'flat') { <button mat-flat-button [color]=\"action.color || undefined\" (click)=\"onActionClick($event, action.id, it, i)\" [attr.aria-label]=\"action.label || action.id\">{{ action.label || action.id }}</button> }\n }\n }\n </div>\n </div>\n }\n </div>\n </ng-template>\n \n <!-- Simple pagination controls for remote data -->\n @if (config.dataSource?.resourcePath) {\n <div class=\"paginator\" style=\"flex-wrap: wrap; gap: 8px;\">\n <div style=\"display:flex; align-items:center; gap:8px; flex-wrap: wrap;\">\n <button mat-stroked-button color=\"primary\" (click)=\"prevPage()\">Anterior</button>\n <button mat-stroked-button color=\"primary\" (click)=\"nextPage()\">Pr\u00F3ximo</button>\n <mat-form-field style=\"width: 120px;\" appearance=\"outline\">\n <mat-label>Tam. p\u00E1gina</mat-label>\n <mat-select (selectionChange)=\"setPageSize($event.value)\" [value]=\"config.layout?.pageSize || 10\">\n <mat-option [value]=\"6\">6</mat-option>\n <mat-option [value]=\"8\">8</mat-option>\n <mat-option [value]=\"12\">12</mat-option>\n <mat-option [value]=\"24\">24</mat-option>\n </mat-select>\n </mat-form-field>\n @if (config.ui?.showSort) {\n <mat-form-field style=\"width: 180px;\" 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)\">{{ sortOptionLabel(op) }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n @if (config.ui?.showSearch && config.ui?.searchField) {\n <mat-form-field style=\"min-width: 220px;\" appearance=\"outline\">\n <mat-label>{{ config.ui?.searchPlaceholder || 'Buscar' }}</mat-label>\n <input matInput type=\"search\" (input)=\"onSearchInput($any($event.target).value)\" />\n </mat-form-field>\n }\n </div>\n <div style=\"display:flex; align-items:center; gap:8px; margin-left:auto;\">\n @if ((config.ui?.showRange ?? true)) {\n @if (page$ | async; as ps) {\n @if (total$ | async; as total) {\n @if (items$ | async; as curr) {\n <span class=\"muted\">{{ rangeStart(ps) }}\u2013{{ rangeEnd(curr?.length || 0, ps, total || 0) }} de {{ total || 0 }}</span>\n }\n }\n }\n } @else {\n @if (total$ | async; as total2) { <span class=\"muted\">Total: {{ total2 }}</span> }\n }\n </div>\n </div>\n }\n </ng-template>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.praxis-list-root{--p-list-radius: 1rem;--p-list-shadow: 0 6px 20px rgba(0, 0, 0, .08);--p-list-border: 1px solid rgba(0, 0, 0, .08);--p-list-blur: 8px;--p-list-grad-from: #8e72ff;--p-list-grad-to: #ffaa70;--p-list-grad-angle: 135deg}.skin-elevated .item-card{background:var(--mdc-elevated-card-container-color, var(--md-sys-color-surface));border-radius:var(--p-list-radius);box-shadow:var(--p-list-shadow);border:var(--p-list-border)}.skin-pill-soft .item-card{background:var(--mdc-elevated-card-container-color, var(--md-sys-color-surface));border-radius:calc(var(--p-list-radius) * 1.3);box-shadow:0 4px 16px #00000014;border:var(--p-list-border)}.skin-pill-soft .mat-mdc-list-item .list-item-content{background:var(--mdc-elevated-card-container-color, var(--md-sys-color-surface));border-radius:calc(var(--p-list-radius) * 1.3);box-shadow:0 4px 16px #00000014;border:var(--p-list-border);color:var(--p-list-foreground, #1f2937)}.skin-gradient-tile .item-card,.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:#fff}.skin-glass .item-card{background:#ffffff2e;border-radius:var(--p-list-radius);border:1px solid rgba(255,255,255,.3);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:#ffffff2e;border-radius:var(--p-list-radius);border:1px solid rgba(255,255,255,.3);backdrop-filter:blur(var(--p-list-blur));-webkit-backdrop-filter:blur(var(--p-list-blur));color:var(--p-list-foreground, #111)}.density-compact .mat-mdc-list-item,.density-compact .item-card{--_item-padding: 4px 8px}.density-comfortable .mat-mdc-list-item,.density-comfortable .item-card{--_item-padding: 8px 12px}.list-item-content{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:12px;padding:var(--_item-padding, 12px 16px)}.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}.model-media .lead-image{width:136px;height:96px;border-radius:16px}.model-media .list-item-content{gap:16px}.model-hotel .lead-image{width:160px;height:110px;border-radius:18px}.primary{font-weight:600;color:var(--sicoob-tertiary-default, #001E24)}.secondary{opacity:.8}.tertiary{opacity:.68}.features{display:flex;flex-wrap:wrap;gap:12px;margin-top:6px;color:inherit}.feature{display:inline-flex;align-items:center;gap:6px}.meta{opacity:.9}.trailing{opacity:.9;margin-left:8px}.section-header{font-size:.85rem;opacity:.7;padding:8px 12px}.cards-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}.item-card{padding:12px;display:flex;flex-direction:column;min-height:160px;position:relative;cursor:pointer}.item-card:hover{box-shadow:0 10px 24px var(--sicoob-shadow-medium, rgba(0, 0, 0, .12));transform:translateY(-1px);transition:box-shadow .15s ease,transform .15s ease}.card-actions{display:flex;align-items:center;justify-content:flex-end;gap:4px;padding:6px 8px 4px;border-top:1px solid var(--sicoob-stroke-medium, rgba(0, 0, 0, .08));margin-top:auto}.status-overlay{position:absolute;top:10px;right:10px;pointer-events:none}.status-overlay .mat-mdc-chip{pointer-events:auto}.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:color-mix(in srgb,var(--sicoob-info-lightest, #f0f9ff),transparent 30%);color:var(--sicoob-info-default, #0090e0)}.mat-mdc-chip{--mdc-chip-container-height: 22px;font-size:12px}.meta .mat-icon{font-size:18px;height:18px;width:18px}.primary,.secondary{display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}.primary,.secondary{-webkit-line-clamp:1}.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:#f6f7f8;background:linear-gradient(to right,#eee 8%,#ddd 18%,#eee 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%}.paginator{display:flex;align-items:center;gap:8px;padding:8px 4px}.muted{opacity:.7}\n"] }]
2878
3504
  }], propDecorators: { config: [{
2879
3505
  type: Input
2880
3506
  }], form: [{