@praxisui/list 3.0.0-beta.5 → 3.0.0-beta.7

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.
@@ -1,13 +1,13 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, Injectable, Input, ChangeDetectionStrategy, Component, CSP_NONCE, EventEmitter, Output, Optional, Inject, ChangeDetectorRef, isDevMode, booleanAttribute, ENVIRONMENT_INITIALIZER, signal, computed } from '@angular/core';
2
+ import { inject, Injectable, Input, ChangeDetectionStrategy, Component, LOCALE_ID, CSP_NONCE, EventEmitter, Output, Optional, Inject, ChangeDetectorRef, isDevMode, booleanAttribute, ENVIRONMENT_INITIALIZER, signal, computed } from '@angular/core';
3
3
  import { ActivatedRoute } from '@angular/router';
4
4
  import * as i1 from '@angular/common';
5
- import { CommonModule } from '@angular/common';
5
+ import { formatDate, CommonModule } from '@angular/common';
6
6
  import * as i3 from '@angular/material/list';
7
7
  import { MatListModule } from '@angular/material/list';
8
8
  import * as i4 from '@angular/material/icon';
9
9
  import { MatIconModule } from '@angular/material/icon';
10
- import { LoggerService, GenericCrudService, PraxisIconDirective, IconPickerService, GLOBAL_ACTION_CATALOG, PRAXIS_GLOBAL_ACTION_CATALOG, getGlobalActionCatalog, deepMerge, providePraxisI18n, ASYNC_CONFIG_STORAGE, ComponentKeyService, PraxisI18nService, GlobalActionService, GLOBAL_DIALOG_SERVICE, ComponentMetadataRegistry, API_URL } from '@praxisui/core';
10
+ import { resolveValuePresentation, LoggerService, GenericCrudService, PraxisIconDirective, PraxisI18nService, IconPickerService, GLOBAL_ACTION_CATALOG, PRAXIS_GLOBAL_ACTION_CATALOG, getGlobalActionCatalog, SURFACE_OPEN_I18N_NAMESPACE, getGlobalActionUiSchema, SurfaceOpenActionEditorComponent, providePraxisI18nConfig, SURFACE_OPEN_I18N_CONFIG, deepMerge, providePraxisI18n, ASYNC_CONFIG_STORAGE, ComponentKeyService, GlobalActionService, GLOBAL_DIALOG_SERVICE, ComponentMetadataRegistry, API_URL } from '@praxisui/core';
11
11
  import * as i5 from '@angular/material/chips';
12
12
  import { MatChipsModule } from '@angular/material/chips';
13
13
  import { MatDividerModule } from '@angular/material/divider';
@@ -49,7 +49,7 @@ import { BaseAiAdapter, PraxisAiAssistantComponent } from '@praxisui/ai';
49
49
  * Very small template evaluator that supports `${item.foo}` expressions.
50
50
  * For security, only string concatenation is supported; no arbitrary code.
51
51
  */
52
- function evalExpr(expr, ctx) {
52
+ function evalExpr(expr, ctx, options) {
53
53
  if (!expr)
54
54
  return '';
55
55
  // Replace ${...} with values from context (item), supporting simple pipes inside placeholders
@@ -71,8 +71,12 @@ function evalExpr(expr, ctx) {
71
71
  const [localeArg, styleArg] = parseTwoArgs(args);
72
72
  const dt = value ? new Date(value) : null;
73
73
  if (dt && !isNaN(dt.getTime())) {
74
- const fmt = new Intl.DateTimeFormat(localeArg || undefined, mapDateStyle(styleArg)).format(dt);
75
- out = fmt;
74
+ const presentation = resolveTemplatePresentation('date', {
75
+ localeArg,
76
+ styleArg,
77
+ options,
78
+ });
79
+ out = formatDate(dt, presentation.format || 'shortDate', presentation.locale);
76
80
  }
77
81
  else {
78
82
  out = '';
@@ -87,20 +91,33 @@ function evalExpr(expr, ctx) {
87
91
  const [localeArg, styleArg] = parseTwoArgs(args);
88
92
  const num = Number(value);
89
93
  if (isFinite(num)) {
94
+ const presentation = resolveTemplatePresentation('number', {
95
+ localeArg,
96
+ styleArg,
97
+ options,
98
+ });
90
99
  const opt = {};
91
100
  if ((styleArg || '').toLowerCase() === 'compact')
92
101
  opt.notation = 'compact';
93
- out = new Intl.NumberFormat(localeArg || undefined, opt).format(num);
102
+ applyNumberPrecision(opt, presentation.format);
103
+ out = new Intl.NumberFormat(presentation.locale || undefined, opt).format(num);
94
104
  }
95
105
  }
96
106
  if (name === 'currency') {
97
107
  const [currencyArg, localeArg] = parseTwoArgs(args);
98
108
  const num = Number(value);
99
109
  if (isFinite(num)) {
100
- out = new Intl.NumberFormat(localeArg || undefined, {
110
+ const presentation = resolveTemplatePresentation('currency', {
111
+ localeArg,
112
+ currencyArg,
113
+ options,
114
+ });
115
+ const opt = {
101
116
  style: 'currency',
102
- currency: currencyArg || 'USD',
103
- }).format(num);
117
+ currency: presentation.currency || 'USD',
118
+ };
119
+ applyCurrencyPrecision(opt, presentation.format);
120
+ out = new Intl.NumberFormat(presentation.locale || undefined, opt).format(num);
104
121
  }
105
122
  }
106
123
  }
@@ -116,13 +133,19 @@ function evalExpr(expr, ctx) {
116
133
  * Supported pipes:
117
134
  * - bool:TrueLabel:FalseLabel (maps truthy/falsey values to provided labels)
118
135
  */
119
- function evaluateTemplate(def, item) {
136
+ function evaluateTemplate(def, item, options) {
120
137
  if (!def)
121
138
  return null;
139
+ if (def.type === 'currency') {
140
+ return evaluateCurrencyTemplate(def, item, options);
141
+ }
142
+ if (def.type === 'date') {
143
+ return evaluateDateTemplate(def, item, options);
144
+ }
122
145
  // Support a top-level pipe outside of template placeholders,
123
146
  // e.g.: "${item.active}|bool:Ativo:Inativo".
124
147
  const { baseExpr, pipe } = splitFirstTopLevelPipe(def.expr || '');
125
- let value = evalExpr(baseExpr, { item });
148
+ let value = evalExpr(baseExpr, { item }, options);
126
149
  if (pipe) {
127
150
  const { name, args } = parsePipe(pipe);
128
151
  if (name === 'bool') {
@@ -134,7 +157,7 @@ function evaluateTemplate(def, item) {
134
157
  const out = { type: def.type, value, class: def.class, style: def.style, color: def.color, variant: def.variant, props: def.props, imageAlt: def.imageAlt };
135
158
  if (def.badge?.expr) {
136
159
  const { baseExpr: b2, pipe: p2 } = splitFirstTopLevelPipe(def.badge.expr);
137
- let badgeVal = evalExpr(b2, { item });
160
+ let badgeVal = evalExpr(b2, { item }, options);
138
161
  if (p2) {
139
162
  const { name, args } = parsePipe(p2);
140
163
  if (name === 'bool') {
@@ -151,6 +174,48 @@ function evaluateTemplate(def, item) {
151
174
  }
152
175
  return out;
153
176
  }
177
+ function evaluateCurrencyTemplate(def, item, options) {
178
+ const { baseExpr, pipe } = splitFirstTopLevelPipe(def.expr || '');
179
+ const [currencyArg, localeArg] = parseTwoArgs(pipe);
180
+ const raw = Number(evalExpr(baseExpr, { item }, options));
181
+ const presentation = resolveTemplatePresentation('currency', {
182
+ localeArg,
183
+ currencyArg,
184
+ options,
185
+ });
186
+ const intlOptions = {
187
+ style: 'currency',
188
+ currency: presentation.currency || 'USD',
189
+ };
190
+ applyCurrencyPrecision(intlOptions, presentation.format);
191
+ return {
192
+ type: def.type,
193
+ value: isFinite(raw)
194
+ ? new Intl.NumberFormat(presentation.locale || undefined, intlOptions).format(raw)
195
+ : '',
196
+ class: def.class,
197
+ style: def.style,
198
+ };
199
+ }
200
+ function evaluateDateTemplate(def, item, options) {
201
+ const { baseExpr, pipe } = splitFirstTopLevelPipe(def.expr || '');
202
+ const [localeArg, styleArg] = parseTwoArgs(pipe);
203
+ const raw = evalExpr(baseExpr, { item }, options);
204
+ const dt = raw ? new Date(raw) : null;
205
+ const presentation = resolveTemplatePresentation('date', {
206
+ localeArg,
207
+ styleArg,
208
+ options,
209
+ });
210
+ return {
211
+ type: def.type,
212
+ value: dt && !isNaN(dt.getTime())
213
+ ? formatDate(dt, presentation.format || 'shortDate', presentation.locale)
214
+ : '',
215
+ class: def.class,
216
+ style: def.style,
217
+ };
218
+ }
154
219
  function splitFirstPipe(expr) {
155
220
  const idx = expr.indexOf('|');
156
221
  if (idx === -1)
@@ -226,6 +291,87 @@ function parseMap(spec) {
226
291
  }
227
292
  return out;
228
293
  }
294
+ function resolveTemplatePresentation(type, params) {
295
+ const listI18n = params.options?.i18n;
296
+ const localization = listI18n?.localization ||
297
+ (listI18n?.locale || listI18n?.currency
298
+ ? {
299
+ locale: listI18n.locale || undefined,
300
+ direction: 'ltr',
301
+ currency: listI18n.currency
302
+ ? { code: listI18n.currency }
303
+ : undefined,
304
+ }
305
+ : undefined);
306
+ const presentation = resolveValuePresentation({
307
+ type,
308
+ style: mapValuePresentationStyle(params.styleArg),
309
+ currency: params.currencyArg
310
+ ? {
311
+ code: params.currencyArg,
312
+ }
313
+ : undefined,
314
+ }, {
315
+ valueLocale: params.localeArg || undefined,
316
+ localization: localization,
317
+ surfaceLocale: listI18n?.locale || undefined,
318
+ appLocale: params.options?.appLocale || undefined,
319
+ });
320
+ return {
321
+ locale: presentation.locale,
322
+ format: presentation.format,
323
+ currency: presentation.currency?.code,
324
+ };
325
+ }
326
+ function mapValuePresentationStyle(style) {
327
+ switch ((style || '').toLowerCase()) {
328
+ case 'short':
329
+ return 'short';
330
+ case 'medium':
331
+ return 'medium';
332
+ case 'long':
333
+ return 'long';
334
+ case 'full':
335
+ return 'full';
336
+ case 'compact':
337
+ return 'compact';
338
+ default:
339
+ return undefined;
340
+ }
341
+ }
342
+ function applyNumberPrecision(options, format) {
343
+ const digits = parseDigitsFormat(format);
344
+ if (!digits)
345
+ return;
346
+ options.minimumIntegerDigits = digits.minimumIntegerDigits;
347
+ options.minimumFractionDigits = digits.minimumFractionDigits;
348
+ options.maximumFractionDigits = digits.maximumFractionDigits;
349
+ }
350
+ function applyCurrencyPrecision(options, format) {
351
+ if (!format)
352
+ return;
353
+ const parts = format.split('|').filter(Boolean);
354
+ const precision = Number(parts[2]);
355
+ if (Number.isFinite(precision)) {
356
+ options.minimumFractionDigits = precision;
357
+ options.maximumFractionDigits = precision;
358
+ }
359
+ if ((parts[1] || '').trim() === 'code') {
360
+ options.currencyDisplay = 'code';
361
+ }
362
+ }
363
+ function parseDigitsFormat(format) {
364
+ if (!format)
365
+ return null;
366
+ const match = /^(\d+)\.(\d+)-(\d+)$/.exec(format.trim());
367
+ if (!match)
368
+ return null;
369
+ return {
370
+ minimumIntegerDigits: Number(match[1]),
371
+ minimumFractionDigits: Number(match[2]),
372
+ maximumFractionDigits: Number(match[3]),
373
+ };
374
+ }
229
375
 
230
376
  function adaptSelection(config, items) {
231
377
  const mode = (config?.selection?.mode ?? 'none');
@@ -1122,6 +1268,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
1122
1268
  class PraxisListSkinPreviewComponent {
1123
1269
  skin;
1124
1270
  static nextSkinScopeId = 0;
1271
+ hostLocale = inject(LOCALE_ID, { optional: true }) ?? 'en-US';
1125
1272
  config;
1126
1273
  items = [];
1127
1274
  theme = 'light';
@@ -1196,30 +1343,7 @@ class PraxisListSkinPreviewComponent {
1196
1343
  const def = this.config?.templating?.[slot];
1197
1344
  if (!def)
1198
1345
  return null;
1199
- if (def.type === 'currency') {
1200
- const { baseExpr, param } = this.splitPipe(def.expr || '');
1201
- const [codeFromExpr, localeFromExpr] = this.parseTwoParams(param);
1202
- const currencyCode = codeFromExpr || this.config?.i18n?.currency || 'USD';
1203
- const locale = localeFromExpr || this.config?.i18n?.locale || undefined;
1204
- const rawStr = this.evalString(baseExpr, { item });
1205
- const raw = Number(rawStr?.replace?.(/[^0-9.,-]/g, '').replace(',', '.') ?? rawStr);
1206
- const fmt = new Intl.NumberFormat(locale, {
1207
- style: 'currency',
1208
- currency: currencyCode,
1209
- }).format(raw ?? 0);
1210
- return this.applyMaps({ type: def.type, value: fmt, class: def.class, style: def.style }, item);
1211
- }
1212
- if (def.type === 'date') {
1213
- const { baseExpr, param } = this.splitPipe(def.expr || '');
1214
- const [localeFromExpr, styleFromExpr] = this.parseTwoParams(param);
1215
- const locale = localeFromExpr || this.config?.i18n?.locale || undefined;
1216
- const dateStyle = this.mapDateStyle(styleFromExpr);
1217
- const raw = this.evalString(baseExpr, { item });
1218
- const dt = raw ? new Date(raw) : null;
1219
- const fmt = dt ? new Intl.DateTimeFormat(locale, dateStyle).format(dt) : '';
1220
- return this.applyMaps({ type: def.type, value: fmt, class: def.class, style: def.style }, item);
1221
- }
1222
- const out = evaluateTemplate(def, item);
1346
+ const out = evaluateTemplate(def, item, this.templateEvaluatorOptions());
1223
1347
  return this.applyMaps(out, item);
1224
1348
  }
1225
1349
  applyMaps(out, item) {
@@ -1300,33 +1424,14 @@ class PraxisListSkinPreviewComponent {
1300
1424
  return { baseExpr: expr.slice(0, idx).trim(), param: expr.slice(idx + 1).trim() };
1301
1425
  }
1302
1426
  evalString(expr, ctx) {
1303
- const res = evaluateTemplate({ type: 'text', expr }, ctx?.item ?? ctx);
1427
+ const res = evaluateTemplate({ type: 'text', expr }, ctx?.item ?? ctx, this.templateEvaluatorOptions());
1304
1428
  return res?.value ?? '';
1305
1429
  }
1306
- parseTwoParams(param) {
1307
- if (!param)
1308
- return [undefined, undefined];
1309
- const parts = param.split(':');
1310
- const first = parts[0]?.trim();
1311
- const second = parts[1]?.trim();
1312
- if (first === '')
1313
- return [undefined, second || undefined];
1314
- if (parts.length === 1)
1315
- return [first || undefined, undefined];
1316
- return [first || undefined, second || undefined];
1317
- }
1318
- mapDateStyle(style) {
1319
- switch ((style || '').toLowerCase()) {
1320
- case 'short':
1321
- return { dateStyle: 'short' };
1322
- case 'long':
1323
- return { dateStyle: 'long' };
1324
- case 'full':
1325
- return { dateStyle: 'full' };
1326
- case 'medium':
1327
- default:
1328
- return { dateStyle: 'medium' };
1329
- }
1430
+ templateEvaluatorOptions() {
1431
+ return {
1432
+ i18n: this.config?.i18n,
1433
+ appLocale: this.hostLocale,
1434
+ };
1330
1435
  }
1331
1436
  isThemeColor(color) {
1332
1437
  return color === 'primary' || color === 'accent' || color === 'warn';
@@ -3132,7 +3237,7 @@ class PraxisListConfigEditor {
3132
3237
  mappingTrailing = {};
3133
3238
  mappingLeading = { type: 'icon' };
3134
3239
  mappingSectionHeader = { type: 'text', expr: '${item.key}' };
3135
- mappingEmptyState = { type: 'text', expr: 'Nenhum item disponível' };
3240
+ mappingEmptyState = { type: 'text', expr: 'Nenhum item disponível' };
3136
3241
  mapping = {};
3137
3242
  mappingDirty = false;
3138
3243
  appliedMappingSnapshot = '';
@@ -3146,7 +3251,7 @@ class PraxisListConfigEditor {
3146
3251
  uiSortRows = [];
3147
3252
  // Meta composition (multi-field)
3148
3253
  mappingMetaFields = [];
3149
- mappingMetaSeparator = ' ';
3254
+ mappingMetaSeparator = ' • ';
3150
3255
  mappingMetaWrapSecondInParens = false;
3151
3256
  featuresSyncWithMeta = false;
3152
3257
  // Features editor state
@@ -3157,6 +3262,7 @@ class PraxisListConfigEditor {
3157
3262
  queryJson = '';
3158
3263
  queryError = '';
3159
3264
  crud = null;
3265
+ i18n = inject(PraxisI18nService);
3160
3266
  resourcePathChanges = new Subject();
3161
3267
  constructor(injected) {
3162
3268
  // Disable Immer auto-freeze to allow [(ngModel)] mutations on produced state
@@ -3281,11 +3387,11 @@ class PraxisListConfigEditor {
3281
3387
  this.mappingSectionHeader = { type: 'text', expr: '${item.key}' };
3282
3388
  this.mappingEmptyState = {
3283
3389
  type: 'text',
3284
- expr: 'Nenhum item disponível',
3390
+ expr: 'Nenhum item disponível',
3285
3391
  };
3286
3392
  this.mappingMetaPrefixIcon = undefined;
3287
3393
  this.mappingMetaFields = [];
3288
- this.mappingMetaSeparator = ' ';
3394
+ this.mappingMetaSeparator = ' • ';
3289
3395
  this.mappingMetaWrapSecondInParens = false;
3290
3396
  this.features = [];
3291
3397
  this.featuresVisible = true;
@@ -3450,9 +3556,9 @@ class PraxisListConfigEditor {
3450
3556
  metaPlacement: 'side',
3451
3557
  statusPosition: 'top-right',
3452
3558
  chipColorMap: {
3453
- disponível: 'primary',
3454
- 'últimas vagas': 'accent',
3455
- indisponível: 'warn',
3559
+ disponivel: 'primary',
3560
+ 'ultimas vagas': 'accent',
3561
+ indisponivel: 'warn',
3456
3562
  },
3457
3563
  };
3458
3564
  });
@@ -3471,19 +3577,19 @@ class PraxisListConfigEditor {
3471
3577
  hireDate: '2022-03-01',
3472
3578
  rating: 4.5,
3473
3579
  name: 'Aurora Deluxe',
3474
- status: 'Disponível',
3580
+ status: 'Disponível',
3475
3581
  price: 980.0,
3476
3582
  imageUrl: '/list-demo-hotel-1.svg',
3477
3583
  },
3478
3584
  {
3479
- title: 'João',
3585
+ title: 'João',
3480
3586
  subtitle: 'Frontend Engineer',
3481
3587
  tag: 'B',
3482
3588
  salary: 9876.0,
3483
3589
  hireDate: '2023-01-12',
3484
3590
  rating: 3.8,
3485
3591
  name: 'Skyline Classic',
3486
- status: 'Últimas vagas',
3592
+ status: 'Últimas vagas',
3487
3593
  price: 640.0,
3488
3594
  imageUrl: '/list-demo-hotel-2.svg',
3489
3595
  },
@@ -3495,7 +3601,7 @@ class PraxisListConfigEditor {
3495
3601
  hireDate: '2021-10-05',
3496
3602
  rating: 4.9,
3497
3603
  name: 'Urban Loft',
3498
- status: 'Indisponível',
3604
+ status: 'Indisponível',
3499
3605
  price: 520.0,
3500
3606
  imageUrl: '/list-demo-hotel-1.svg',
3501
3607
  },
@@ -3517,7 +3623,7 @@ class PraxisListConfigEditor {
3517
3623
  },
3518
3624
  {
3519
3625
  type: 'icon',
3520
- label: 'Ícone',
3626
+ label: 'Ícone',
3521
3627
  icon: 'emoji_symbols',
3522
3628
  props: ['iconColor'],
3523
3629
  },
@@ -3542,7 +3648,7 @@ class PraxisListConfigEditor {
3542
3648
  sectionHeaderTypeConfigs = this.metaTypeConfigs.filter((t) => ['text', 'chip', 'icon', 'image', 'rating', 'html'].includes(t.type));
3543
3649
  emptyStateTypeConfigs = this.metaTypeConfigs.filter((t) => ['text', 'chip', 'icon', 'image', 'rating', 'html'].includes(t.type));
3544
3650
  paletteOptions = [
3545
- { value: undefined, label: 'Padrão' },
3651
+ { value: undefined, label: 'Padrão' },
3546
3652
  { value: 'primary', label: 'Primary (Theme)' },
3547
3653
  { value: 'accent', label: 'Accent (Theme)' },
3548
3654
  { value: 'warn', label: 'Warn (Theme)' },
@@ -3608,7 +3714,7 @@ class PraxisListConfigEditor {
3608
3714
  draft.actions.push({
3609
3715
  id: 'newAction',
3610
3716
  icon: 'bolt',
3611
- label: 'Ação',
3717
+ label: 'Ação',
3612
3718
  color: undefined,
3613
3719
  kind: 'icon',
3614
3720
  });
@@ -3649,20 +3755,20 @@ class PraxisListConfigEditor {
3649
3755
  action.confirmation.type = normalized || undefined;
3650
3756
  if (normalized === 'danger') {
3651
3757
  if (!action.confirmation.title)
3652
- action.confirmation.title = 'Confirmar ação';
3758
+ action.confirmation.title = 'Confirmar ação';
3653
3759
  if (!action.confirmation.message)
3654
3760
  action.confirmation.message =
3655
- 'Esta ação pode ser irreversível. Deseja continuar?';
3761
+ 'Esta ação pode ser irreversível. Deseja continuar?';
3656
3762
  }
3657
3763
  if (normalized === 'warning') {
3658
3764
  if (!action.confirmation.title)
3659
- action.confirmation.title = 'Atenção';
3765
+ action.confirmation.title = 'Atenção';
3660
3766
  if (!action.confirmation.message)
3661
- action.confirmation.message = 'Deseja prosseguir com esta ação?';
3767
+ action.confirmation.message = 'Deseja prosseguir com esta ação?';
3662
3768
  }
3663
3769
  if (normalized === 'info') {
3664
3770
  if (!action.confirmation.title)
3665
- action.confirmation.title = 'Confirmação';
3771
+ action.confirmation.title = 'Confirmação';
3666
3772
  if (!action.confirmation.message)
3667
3773
  action.confirmation.message = 'Tem certeza que deseja continuar?';
3668
3774
  }
@@ -3701,16 +3807,16 @@ class PraxisListConfigEditor {
3701
3807
  return '';
3702
3808
  const cmd = action.command.replace(/^global[:.]/, '').trim();
3703
3809
  const entry = this.globalActionCatalog.find((e) => e.id === cmd);
3704
- return entry?.payloadSchema?.example ? 'Exemplo disponível' : '';
3810
+ return entry?.payloadSchema?.example ? 'Exemplo disponível' : '';
3705
3811
  }
3706
3812
  globalPayloadSchemaTooltip(action) {
3707
3813
  if (!action?.command)
3708
- return 'Sem schema disponível.';
3814
+ return 'Sem schema disponível.';
3709
3815
  const cmd = action.command.replace(/^global[:.]/, '').trim();
3710
3816
  const entry = this.globalActionCatalog.find((e) => e.id === cmd);
3711
3817
  const schema = entry?.payloadSchema;
3712
3818
  if (!schema)
3713
- return 'Sem schema disponível.';
3819
+ return 'Sem schema disponível.';
3714
3820
  const lines = [];
3715
3821
  if (entry?.label)
3716
3822
  lines.push(`${entry.label}`);
@@ -3721,9 +3827,9 @@ class PraxisListConfigEditor {
3721
3827
  if (props.length) {
3722
3828
  lines.push('Campos:');
3723
3829
  for (const [name, meta] of props) {
3724
- const req = required.has(name) ? ' (OBRIGATÓRIO)' : '';
3830
+ const req = required.has(name) ? ' (OBRIGATÓRIO)' : '';
3725
3831
  const type = meta?.type ? `: ${meta.type}` : '';
3726
- const desc = meta?.description ? ` ${meta.description}` : '';
3832
+ const desc = meta?.description ? ` — ${meta.description}` : '';
3727
3833
  lines.push(`- ${name}${type}${req}${desc}`);
3728
3834
  }
3729
3835
  }
@@ -3736,6 +3842,29 @@ class PraxisListConfigEditor {
3736
3842
  }
3737
3843
  return lines.join('\n');
3738
3844
  }
3845
+ isSurfaceOpenCommand(action) {
3846
+ return this.getGlobalActionSchema(action)?.editorMode === 'surface-open';
3847
+ }
3848
+ getSurfaceOpenGlobalPayload(action) {
3849
+ const raw = String(action?.globalPayload || '').trim();
3850
+ if (raw) {
3851
+ try {
3852
+ const parsed = JSON.parse(raw);
3853
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
3854
+ return this.normalizeSurfaceOpenPayload(parsed);
3855
+ }
3856
+ }
3857
+ catch {
3858
+ // keep default payload while authoring invalid JSON elsewhere
3859
+ }
3860
+ }
3861
+ return this.normalizeSurfaceOpenPayload(undefined);
3862
+ }
3863
+ onSurfaceOpenGlobalPayloadChange(action, payload) {
3864
+ action.command = 'global:surface.open';
3865
+ action.globalPayload = JSON.stringify(this.normalizeSurfaceOpenPayload(payload));
3866
+ this.onActionsChanged();
3867
+ }
3739
3868
  onGlobalActionSelected(id) {
3740
3869
  const action = this.globalActionCatalog.find((a) => a.id === id);
3741
3870
  if (!action)
@@ -4165,7 +4294,7 @@ class PraxisListConfigEditor {
4165
4294
  t.primary = p;
4166
4295
  if (s) {
4167
4296
  t.secondary = s;
4168
- // Garantir que a UI tenha espaço para exibir o secondary
4297
+ // Garantir que a UI tenha espaço para exibir o secondary
4169
4298
  const currentLines = Number(this.working.layout.lines) || 1;
4170
4299
  if (currentLines < 2)
4171
4300
  this.working.layout.lines = 2;
@@ -4564,8 +4693,8 @@ class PraxisListConfigEditor {
4564
4693
  // Try to detect composed text: split by common separators if it contains ${item.
4565
4694
  if (typeof templ.meta?.expr === 'string' &&
4566
4695
  /\$\{\s*item\./.test(templ.meta.expr)) {
4567
- const guessSep = / /.test(templ.meta.expr)
4568
- ? ' '
4696
+ const guessSep = / • /.test(templ.meta.expr)
4697
+ ? ' • '
4569
4698
  : /,\s*/.test(templ.meta.expr)
4570
4699
  ? ', '
4571
4700
  : ' ';
@@ -4808,7 +4937,51 @@ class PraxisListConfigEditor {
4808
4937
  const injected = inject(GLOBAL_ACTION_CATALOG, { optional: true });
4809
4938
  if (!injected)
4810
4939
  return PRAXIS_GLOBAL_ACTION_CATALOG.slice();
4811
- return getGlobalActionCatalog(injected);
4940
+ return getGlobalActionCatalog(injected).map((entry) => ({
4941
+ ...entry,
4942
+ label: this.getGlobalActionEntryLabel(entry),
4943
+ description: this.getGlobalActionEntryDescription(entry),
4944
+ }));
4945
+ }
4946
+ getGlobalActionEntryLabel(entry) {
4947
+ if (entry?.id === 'surface.open') {
4948
+ return this.i18n.t('action.surfaceOpen.label', undefined, entry.label || '', SURFACE_OPEN_I18N_NAMESPACE);
4949
+ }
4950
+ return entry?.label || '';
4951
+ }
4952
+ getGlobalActionEntryDescription(entry) {
4953
+ if (entry?.id === 'surface.open') {
4954
+ return this.i18n.t('action.surfaceOpen.description', undefined, entry.description || '', SURFACE_OPEN_I18N_NAMESPACE);
4955
+ }
4956
+ return entry?.description || '';
4957
+ }
4958
+ getGlobalActionSchema(action) {
4959
+ const id = String(action?.command || '').replace(/^global[:.]/, '').trim();
4960
+ return getGlobalActionUiSchema(id);
4961
+ }
4962
+ normalizeSurfaceOpenPayload(payload) {
4963
+ return {
4964
+ presentation: payload?.presentation === 'drawer' ? 'drawer' : 'modal',
4965
+ title: payload?.title,
4966
+ subtitle: payload?.subtitle,
4967
+ icon: payload?.icon,
4968
+ size: payload?.size && Object.keys(payload.size).length
4969
+ ? { ...payload.size }
4970
+ : undefined,
4971
+ widget: {
4972
+ id: String(payload?.widget?.id || ''),
4973
+ inputs: { ...(payload?.widget?.inputs || {}) },
4974
+ bindingOrder: payload?.widget?.bindingOrder?.length
4975
+ ? [...payload.widget.bindingOrder]
4976
+ : undefined,
4977
+ },
4978
+ bindings: payload?.bindings?.length
4979
+ ? payload.bindings.map((binding) => ({ ...binding }))
4980
+ : [],
4981
+ context: payload?.context && typeof payload.context === 'object' && !Array.isArray(payload.context)
4982
+ ? { ...payload.context }
4983
+ : undefined,
4984
+ };
4812
4985
  }
4813
4986
  verify() {
4814
4987
  setTimeout(() => {
@@ -4981,11 +5154,11 @@ class PraxisListConfigEditor {
4981
5154
  }
4982
5155
  }
4983
5156
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisListConfigEditor, deps: [{ token: SETTINGS_PANEL_DATA, optional: true }], target: i0.ɵɵFactoryTarget.Component });
4984
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisListConfigEditor, isStandalone: true, selector: "praxis-list-config-editor", inputs: { config: "config", listId: "listId" }, ngImport: i0, template: "<mat-tab-group class=\"list-editor-tabs\">\n <mat-tab label=\"Dados\">\n <div class=\"editor-content\">\n <div class=\"g g-1-auto gap-8 ai-center\">\n <div class=\"muted\">\n Observa\u00E7\u00E3o: ajustes aplicados pelo assistente substituem o objeto de\n configura\u00E7\u00E3o inteiro.\n </div>\n <button\n mat-icon-button\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"O applyConfigFromAdapter n\u00E3o faz merge profundo. Garanta que o adapter envie a config completa.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </div>\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Recurso (API)</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.dataSource.resourcePath\"\n (ngModelChange)=\"onResourcePathChange($event)\"\n placeholder=\"ex.: users\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Endpoint do recurso (resourcePath).\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Query (JSON)</mat-label>\n <textarea\n matInput\n rows=\"3\"\n [(ngModel)]=\"queryJson\"\n (ngModelChange)=\"onQueryChanged($event)\"\n placeholder='ex.: &#123;\"status\":\"active\",\"department\":\"sales\"&#125;'\n ></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Opcional. Use JSON v\u00E1lido para filtros iniciais.\"\n *ngIf=\"!queryError\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error *ngIf=\"queryError\">{{ queryError }}</mat-error>\n </mat-form-field>\n <div class=\"g g-auto-220 gap-12 ai-end mt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Ordenar por</mat-label>\n <mat-select\n [(ngModel)]=\"sortField\"\n (ngModelChange)=\"updateSortConfig()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Campo base do recurso.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Dire\u00E7\u00E3o</mat-label>\n <mat-select\n [(ngModel)]=\"sortDir\"\n (ngModelChange)=\"updateSortConfig()\"\n >\n <mat-option value=\"asc\">Ascendente</mat-option>\n <mat-option value=\"desc\">Descendente</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"JSON\">\n <div class=\"editor-content\">\n <praxis-list-json-config-editor\n [document]=\"document\"\n (documentChange)=\"onJsonConfigChange($event)\"\n (validationChange)=\"onJsonValidationChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n >\n </praxis-list-json-config-editor>\n </div>\n </mat-tab>\n <mat-tab label=\"A\u00E7\u00F5es\">\n <div class=\"editor-content g gap-12\">\n <div class=\"g g-1-auto gap-8 ai-center\">\n <div class=\"muted\">\n Configure bot\u00F5es de a\u00E7\u00E3o por item (\u00EDcone, r\u00F3tulo, cor, visibilidade)\n </div>\n <button mat-flat-button color=\"primary\" (click)=\"addAction()\">\n Adicionar a\u00E7\u00E3o\n </button>\n </div>\n <div class=\"g g-1-auto gap-8 ai-center\">\n <mat-form-field appearance=\"outline\">\n <mat-label>A\u00E7\u00E3o global (Praxis)</mat-label>\n <mat-select\n [(ngModel)]=\"selectedGlobalActionId\"\n (ngModelChange)=\"onGlobalActionSelected($event)\"\n >\n <mat-option [value]=\"undefined\">-- Selecionar --</mat-option>\n <mat-option *ngFor=\"let ga of globalActionCatalog\" [value]=\"ga.id\">\n <mat-icon class=\"option-icon\">{{ ga.icon || \"bolt\" }}</mat-icon>\n {{ ga.label }}\n </mat-option>\n </mat-select>\n <mat-hint\n *ngIf=\"!globalActionCatalog.length\"\n class=\"text-caption muted\"\n >Nenhuma a\u00E7\u00E3o global registrada.</mat-hint\n >\n </mat-form-field>\n <div class=\"muted text-caption\">\n Selecione para adicionar com `command` global.\n </div>\n </div>\n <div\n *ngFor=\"let a of working.actions || []; let i = index\"\n class=\"g g-auto-200 gap-12 ai-end\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>ID</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.id\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo de a\u00E7\u00E3o</mat-label>\n <mat-select [(ngModel)]=\"a.kind\" (ngModelChange)=\"onActionsChanged()\">\n <mat-option value=\"icon\">\u00CDcone</mat-option>\n <mat-option value=\"button\">Bot\u00E3o</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>\u00CDcone</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.icon\"\n (ngModelChange)=\"onActionsChanged()\"\n placeholder=\"ex.: edit, delete\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Command (global)</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.command\"\n (ngModelChange)=\"onActionsChanged()\"\n placeholder=\"global:toast.success\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>R\u00F3tulo</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.label\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n </mat-form-field>\n <ng-container *ngIf=\"a.kind === 'button'\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Variante</mat-label>\n <mat-select\n [(ngModel)]=\"a.buttonVariant\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option value=\"stroked\">Contorno</mat-option>\n <mat-option value=\"raised\">Elevado</mat-option>\n <mat-option value=\"flat\">Preenchido</mat-option>\n </mat-select>\n </mat-form-field>\n </ng-container>\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor da a\u00E7\u00E3o</mat-label>\n <mat-select\n [(ngModel)]=\"a.color\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option *ngFor=\"let c of paletteOptions\" [value]=\"c.value\">\n <span\n class=\"color-dot\"\n [style.background]=\"colorDotBackground(c.value)\"\n ></span\n >{{ c.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <div\n class=\"g gap-8\"\n *ngIf=\"isCustomColor(a.color); else actionCustomBtn\"\n >\n <pdx-color-picker\n label=\"Cor personalizada\"\n [format]=\"'hex'\"\n [(ngModel)]=\"a.color\"\n (ngModelChange)=\"onActionsChanged()\"\n ></pdx-color-picker>\n </div>\n <ng-template #actionCustomBtn>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"enableCustomActionColor(a)\"\n >\n Usar cor personalizada\n </button>\n </ng-template>\n <mat-form-field appearance=\"outline\">\n <mat-label>Payload da a\u00E7\u00E3o</mat-label>\n <mat-select\n [(ngModel)]=\"a.emitPayload\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option [value]=\"undefined\">Padr\u00E3o</mat-option>\n <mat-option value=\"item\">item</mat-option>\n <mat-option value=\"id\">id</mat-option>\n <mat-option value=\"value\">value</mat-option>\n </mat-select>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"emitPayload\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"col-span-2\">\n <mat-label\n >Exibir quando (ex.: &#36;&#123;item.status&#125; ==\n 'done')</mat-label\n >\n <input\n matInput\n [(ngModel)]=\"a.showIf\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Sintaxe suportada: &#34;&#36;{item.campo} == &#39;valor&#39;&#34;. Express\u00F5es avan\u00E7adas n\u00E3o s\u00E3o avaliadas.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"g row-flow gap-8 ai-center\">\n <button\n *ngIf=\"(a.kind || 'icon') === 'icon'\"\n mat-icon-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n >\n <mat-icon\n [praxisIcon]=\"a.icon || 'bolt'\"\n [style.cssText]=\"iconStyle(a.color)\"\n ></mat-icon>\n </button>\n <ng-container *ngIf=\"a.kind === 'button'\">\n <button\n *ngIf=\"a.buttonVariant === 'stroked'\"\n mat-stroked-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'stroked')\"\n >\n {{ a.label || a.id || \"A\u00E7\u00E3o\" }}\n </button>\n <button\n *ngIf=\"a.buttonVariant === 'raised'\"\n mat-raised-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'raised')\"\n >\n {{ a.label || a.id || \"A\u00E7\u00E3o\" }}\n </button>\n <button\n *ngIf=\"!a.buttonVariant || a.buttonVariant === 'flat'\"\n mat-flat-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'flat')\"\n >\n {{ a.label || a.id || \"A\u00E7\u00E3o\" }}\n </button>\n </ng-container>\n <span class=\"muted\">Pr\u00E9-visualiza\u00E7\u00E3o</span>\n </div>\n <div class=\"flex-end\">\n <button mat-button color=\"warn\" (click)=\"removeAction(i)\">\n Remover\n </button>\n </div>\n <div class=\"g gap-8 col-span-2\" *ngIf=\"a.command\">\n <mat-slide-toggle\n [(ngModel)]=\"a.showLoading\"\n (ngModelChange)=\"onActionsChanged()\"\n >Mostrar loading</mat-slide-toggle\n >\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Confirma\u00E7\u00E3o</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <div class=\"g row-flow gap-8 ai-center\">\n <span class=\"text-caption muted\">Tipo</span>\n <mat-button-toggle-group\n [value]=\"a.confirmation?.type || ''\"\n (change)=\"applyConfirmationPreset(a, $event.value)\"\n >\n <mat-button-toggle value=\"\">Padr\u00E3o</mat-button-toggle>\n <mat-button-toggle value=\"danger\">Danger</mat-button-toggle>\n <mat-button-toggle value=\"warning\">Warning</mat-button-toggle>\n <mat-button-toggle value=\"info\">Info</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>T\u00EDtulo</mat-label>\n <input\n matInput\n [ngModel]=\"a.confirmation?.title\"\n (ngModelChange)=\"setConfirmationField(a, 'title', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mensagem</mat-label>\n <input\n matInput\n [ngModel]=\"a.confirmation?.message\"\n (ngModelChange)=\"setConfirmationField(a, 'message', $event)\"\n />\n </mat-form-field>\n <div class=\"g gap-6\">\n <div class=\"text-caption muted\">Pr\u00E9via</div>\n <div class=\"text-caption\">\n <strong>{{\n a.confirmation?.title || \"Confirmar a\u00E7\u00E3o\"\n }}</strong>\n </div>\n <div class=\"text-caption muted\">\n {{\n a.confirmation?.message ||\n \"Tem certeza que deseja continuar?\"\n }}\n </div>\n <div class=\"text-caption\">\n <span\n class=\"confirm-type\"\n [ngClass]=\"a.confirmation?.type || 'default'\"\n >Tipo: {{ a.confirmation?.type || \"padr\u00E3o\" }}</span\n >\n </div>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!a.confirmation?.title && !a.confirmation?.message\"\n >\n Defina um t\u00EDtulo ou mensagem para a confirma\u00E7\u00E3o.\n </div>\n </div>\n </div>\n </mat-expansion-panel>\n <mat-form-field appearance=\"outline\" class=\"col-span-2\">\n <mat-label>Payload (JSON/Template)</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [(ngModel)]=\"a.globalPayload\"\n (ngModelChange)=\"onActionsChanged()\"\n placeholder='{\"message\":\"${item.name} favoritado\"}'\n ></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"globalPayloadSchemaTooltip(a)\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error *ngIf=\"isGlobalPayloadInvalid(a.globalPayload)\"\n >JSON inv\u00E1lido</mat-error\n >\n </mat-form-field>\n <div class=\"g row-flow gap-8 ai-center\">\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"applyGlobalPayloadExample(a)\"\n >\n Inserir exemplo\n </button>\n <span class=\"muted text-caption\">{{\n globalPayloadExampleHint(a)\n }}</span>\n </div>\n <mat-slide-toggle\n [(ngModel)]=\"a.emitLocal\"\n (ngModelChange)=\"onActionsChanged()\"\n >Emitir evento local tamb\u00E9m</mat-slide-toggle\n >\n </div>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"Layout\">\n <div class=\"editor-content grid gap-3\">\n <div class=\"preset-row g row-flow gap-8\">\n <button mat-stroked-button (click)=\"applyLayoutPreset('tiles-modern')\">\n Preset Tiles Moderno\n </button>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Variante</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.variant\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"list\">Lista</mat-option>\n <mat-option value=\"cards\">Cards</mat-option>\n <mat-option value=\"tiles\">Tiles</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Modelo</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.model\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <ng-container\n *ngIf=\"working.layout.variant === 'list'; else cardModels\"\n >\n <mat-option value=\"standard\">Padr\u00E3o</mat-option>\n <mat-option value=\"media\">M\u00EDdia \u00E0 esquerda</mat-option>\n <mat-option value=\"hotel\">Hotel (m\u00EDdia grande)</mat-option>\n </ng-container>\n <ng-template #cardModels>\n <ng-container\n *ngIf=\"working.layout.variant === 'tiles'; else cardsOnly\"\n >\n <mat-option value=\"standard\">Tile padr\u00E3o</mat-option>\n <mat-option value=\"media\">Tile com m\u00EDdia</mat-option>\n <mat-option value=\"hotel\">Tile hotel</mat-option>\n </ng-container>\n <ng-template #cardsOnly>\n <mat-option value=\"standard\">Padr\u00E3o</mat-option>\n <mat-option value=\"media\">Card com m\u00EDdia</mat-option>\n <mat-option value=\"hotel\">Hotel</mat-option>\n </ng-template>\n </ng-template>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Linhas</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.lines\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option [value]=\"1\">1</mat-option>\n <mat-option [value]=\"2\">2</mat-option>\n <mat-option [value]=\"3\">3</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Itens por p\u00E1gina</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"1\"\n [(ngModel)]=\"working.layout.pageSize\"\n (ngModelChange)=\"onPageSizeChange($event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Densidade</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.density\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"default\">Padr\u00E3o</mat-option>\n <mat-option value=\"comfortable\">Confort\u00E1vel</mat-option>\n <mat-option value=\"compact\">Compacta</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Espa\u00E7amento entre itens</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.itemSpacing\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"none\">Sem espa\u00E7o extra</mat-option>\n <mat-option value=\"tight\">Justo</mat-option>\n <mat-option value=\"default\">Padr\u00E3o</mat-option>\n <mat-option value=\"relaxed\">Relaxado</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"working.layout.variant !== 'tiles'\"\n >\n <mat-label>Divisores</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.dividers\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"none\">Sem</mat-option>\n <mat-option value=\"between\">Entre grupos</mat-option>\n <mat-option value=\"all\">Todos</mat-option>\n </mat-select>\n </mat-form-field>\n <ng-container *ngIf=\"fields.length > 0; else groupByText\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Agrupar por</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.groupBy\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option [value]=\"\">Nenhum</mat-option>\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n </ng-container>\n <ng-template #groupByText>\n <mat-form-field appearance=\"outline\">\n <mat-label>Agrupar por</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.layout.groupBy\"\n (ngModelChange)=\"onLayoutChanged()\"\n placeholder=\"ex.: departamento\"\n />\n </mat-form-field>\n </ng-template>\n <mat-slide-toggle\n [(ngModel)]=\"working.layout.stickySectionHeader\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n Header de se\u00E7\u00E3o fixo\n </mat-slide-toggle>\n <mat-slide-toggle\n [(ngModel)]=\"working.layout.virtualScroll\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n Scroll virtual\n </mat-slide-toggle>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">Ferramentas da lista</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showSearch\"\n (ngModelChange)=\"onUiChanged()\"\n >Mostrar busca</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showSort\"\n (ngModelChange)=\"onUiChanged()\"\n >Mostrar ordenar</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showRange\"\n (ngModelChange)=\"onUiChanged()\"\n >Mostrar faixa X\u2013Y de Total</mat-slide-toggle\n >\n </div>\n <div\n class=\"g g-auto-220 gap-12 ai-end mt-12\"\n *ngIf=\"working.ui?.showSearch\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo para buscar</mat-label>\n <mat-select\n [(ngModel)]=\"working.ui.searchField\"\n (ngModelChange)=\"onUiChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Placeholder da busca</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.ui.searchPlaceholder\"\n (ngModelChange)=\"onUiChanged()\"\n placeholder=\"ex.: Buscar por t\u00EDtulo\"\n />\n </mat-form-field>\n </div>\n <div class=\"mt-12\" *ngIf=\"working.ui?.showSort\">\n <div class=\"g g-1-auto ai-center gap-8\">\n <div class=\"muted\">Op\u00E7\u00F5es de ordena\u00E7\u00E3o (r\u00F3tulo \u2192 campo+dire\u00E7\u00E3o)</div>\n <button mat-flat-button color=\"primary\" (click)=\"addUiSortRow()\">\n Adicionar op\u00E7\u00E3o\n </button>\n </div>\n <div\n class=\"g g-auto-220 gap-12 ai-end mt-12\"\n *ngFor=\"let r of uiSortRows; let i = index\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>R\u00F3tulo</mat-label>\n <input\n matInput\n [(ngModel)]=\"r.label\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n placeholder=\"ex.: Mais recentes\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"r.field\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Dire\u00E7\u00E3o</mat-label>\n <mat-select\n [(ngModel)]=\"r.dir\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n >\n <mat-option value=\"desc\">Descendente</mat-option>\n <mat-option value=\"asc\">Ascendente</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"error\" *ngIf=\"isUiSortRowDuplicate(i)\">\n Op\u00E7\u00E3o duplicada (campo+dire\u00E7\u00E3o)\n </div>\n <div class=\"flex-end\">\n <button mat-button color=\"warn\" (click)=\"removeUiSortRow(i)\">\n Remover\n </button>\n </div>\n </div>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"Conte\u00FAdo\">\n <div class=\"editor-content\">\n <div class=\"editor-main\">\n <mat-accordion multi>\n <!-- Primary -->\n <mat-expansion-panel [expanded]=\"true\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingPrimary.type) }}</mat-icon>\n <span>Primary (T\u00EDtulo)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingPrimary.field || \"N\u00E3o mapeado\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'name';\n onMappingChanged()\n \"\n >\n Nome\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'title';\n onMappingChanged()\n \"\n >\n T\u00EDtulo\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'name';\n mappingSecondary.type = 'text';\n mappingSecondary.field = 'role';\n onMappingChanged()\n \"\n >\n Nome + Papel\n </button>\n </div>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingPrimary.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingPrimary.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of primaryTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n @switch (mappingPrimary.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingPrimary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingPrimary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingPrimary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingPrimary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n }\n\n <!-- Advanced -->\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header>\n <mat-panel-title>Formata\u00E7\u00E3o e Estilo</mat-panel-title>\n </mat-expansion-panel-header>\n <div class=\"g gap-12 pt-12\">\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"\n mappingPrimary.type === 'text' ||\n mappingPrimary.type === 'html'\n \"\n >\n <mat-label>Classe CSS</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"\n mappingPrimary.type === 'text' ||\n mappingPrimary.type === 'html'\n \"\n >\n <mat-label>Estilo Inline</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Classe CSS</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Estilo Inline</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Secondary -->\n <mat-expansion-panel [expanded]=\"!!mappingSecondary.field\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingSecondary.type) }}</mat-icon>\n <span>Secondary (Resumo)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingSecondary.field || \"N\u00E3o mapeado\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'text';\n mappingSecondary.field = 'subtitle';\n onMappingChanged()\n \"\n >\n Subt\u00EDtulo\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'date';\n mappingSecondary.field = 'hireDate';\n mappingSecondary.dateStyle = 'short';\n onMappingChanged()\n \"\n >\n Data curta\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'currency';\n mappingSecondary.field = 'salary';\n mappingSecondary.currencyCode = 'BRL';\n mappingSecondary.locale = 'pt-BR';\n onMappingChanged()\n \"\n >\n Sal\u00E1rio\n </button>\n </div>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSecondary.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSecondary.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of secondaryTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n @switch (mappingSecondary.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingSecondary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingSecondary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingSecondary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingSecondary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header>\n <mat-panel-title>Formata\u00E7\u00E3o e Estilo</mat-panel-title>\n </mat-expansion-panel-header>\n <div class=\"g gap-12 pt-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe CSS</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSecondary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Estilo Inline</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSecondary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <mat-expansion-panel\n [expanded]=\"!!mappingMeta.field || mappingMetaFields.length > 0\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingMeta.type || \"text\")\n }}</mat-icon>\n <span>Meta (Detalhe/Lateral)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>\n {{\n mappingMetaFields.length\n ? \"Campo composto (\" + mappingMetaFields.length + \")\"\n : mappingMeta.field || \"N\u00E3o mapeado\"\n }}\n </mat-panel-description>\n </mat-expansion-panel-header>\n\n <div class=\"g gap-12\">\n <!-- Composition Mode Toggle -->\n <div class=\"g g-1-1 gap-12 p-12 bg-subtle rounded\">\n <div class=\"text-caption muted\">Modo de composi\u00E7\u00E3o</div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Campos para compor (Multi-select)</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMetaFields\"\n multiple\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <div\n class=\"g g-1-1 ai-center gap-12\"\n *ngIf=\"mappingMetaFields.length\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>Separador</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMetaSeparator\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-slide-toggle\n [(ngModel)]=\"mappingMetaWrapSecondInParens\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n (Seg) entre par\u00EAnteses\n </mat-slide-toggle>\n </div>\n </div>\n\n <!-- Single Field Mode (if no composition) -->\n <div class=\"g g-1-1 gap-12\" *ngIf=\"!mappingMetaFields.length\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo \u00DAnico</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option [value]=\"undefined\">-- Nenhum --</mat-option>\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of metaTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- Type configuration (pluggable editors) -->\n @switch (mappingMeta.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingMeta\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingMeta\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <praxis-meta-editor-image\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <!-- Advanced -->\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Op\u00E7\u00F5es avan\u00E7adas</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Posi\u00E7\u00E3o</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.placement\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option value=\"side\">Lateral (Direita)</mat-option>\n <mat-option value=\"line\">Na linha (Abaixo)</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Classe CSS</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMeta.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Estilo</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMeta.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n <!-- Trailing -->\n <mat-expansion-panel [expanded]=\"!!mappingTrailing.field\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingTrailing.type || \"text\")\n }}</mat-icon>\n <span>Trailing (Direita)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingTrailing.field || \"N\u00E3o mapeado\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingTrailing.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option [value]=\"undefined\">-- Nenhum --</mat-option>\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingTrailing.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of trailingTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'chip';\n mappingTrailing.chipColor = 'primary';\n mappingTrailing.chipVariant = 'filled';\n mappingTrailing.field = 'status';\n onMappingChanged()\n \"\n >\n Status Chip\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'icon';\n mappingTrailing.field = 'status';\n mappingTrailing.iconColor = 'primary';\n onMappingChanged()\n \"\n >\n Status \u00CDcone\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'currency';\n mappingTrailing.field = 'price';\n mappingTrailing.currencyCode = 'BRL';\n mappingTrailing.locale = 'pt-BR';\n onMappingChanged()\n \"\n >\n Pre\u00E7o\n </button>\n </div>\n\n @switch (mappingTrailing.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingTrailing\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingTrailing\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>URL / Expr</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingTrailing.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n placeholder=\"https://... ou ${item.imageUrl}\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Use URL absoluta/relativa ou express\u00E3o ${item.campo}.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error\n *ngIf=\"\n isImageUrlRequiredInvalid(mappingTrailing.imageUrl)\n \"\n >URL/expr obrigat\u00F3ria</mat-error\n >\n </mat-form-field>\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!mappingTrailing.imageUrl\"\n >\n Defina a URL/expr para renderizar a imagem.\n </div>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Estilo</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingTrailing.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Style</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingTrailing.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Leading -->\n <mat-expansion-panel\n [expanded]=\"\n !!mappingLeading.field ||\n (mappingLeading.type === 'icon' && !!mappingLeading.icon) ||\n (mappingLeading.type === 'image' && !!mappingLeading.imageUrl)\n \"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingLeading.type) }}</mat-icon>\n <span>Leading (Esquerda)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>\n {{\n mappingLeading.type === \"icon\"\n ? mappingLeading.icon || \"\u00CDcone est\u00E1tico\"\n : mappingLeading.field ||\n (mappingLeading.imageUrl\n ? \"Imagem est\u00E1tica\"\n : \"N\u00E3o mapeado\")\n }}\n </mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingLeading.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of leadingTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <!-- Field (only if not static icon/image, though user might want dynamic) -->\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"\n mappingLeading.type !== 'icon' &&\n mappingLeading.type !== 'image'\n \"\n >\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingLeading.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'icon';\n mappingLeading.icon = 'person';\n mappingLeading.iconColor = 'primary';\n onMappingChanged()\n \"\n >\n Avatar \u00CDcone\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'image';\n mappingLeading.imageUrl = 'https://placehold.co/64x64';\n mappingLeading.imageAlt = 'Avatar';\n mappingLeading.badgeText = '${item.status}';\n onMappingChanged()\n \"\n >\n Avatar Imagem + Badge\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'chip';\n mappingLeading.field = 'tag';\n mappingLeading.chipColor = 'accent';\n mappingLeading.chipVariant = 'filled';\n onMappingChanged()\n \"\n >\n Chip Tag\n </button>\n </div>\n\n <!-- Icon Specific -->\n <div\n class=\"g g-1-auto gap-12 ai-center\"\n *ngIf=\"mappingLeading.type === 'icon'\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>\u00CDcone</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.icon\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <button mat-icon-button matSuffix (click)=\"pickLeadingIcon()\">\n <mat-icon>search</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"text-caption muted\">\n Use pipe <code>|iconMap</code> no extra pipe para din\u00E2mico\n </div>\n </div>\n <div *ngIf=\"mappingLeading.type === 'icon'\">\n <praxis-meta-editor-icon\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n </div>\n\n <!-- Image Specific -->\n <div\n class=\"g g-1-1 gap-12\"\n *ngIf=\"mappingLeading.type === 'image'\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>URL da Imagem</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n placeholder=\"https://... ou ${item.imageUrl}\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Use URL absoluta/relativa ou express\u00E3o ${item.campo}.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error\n *ngIf=\"isImageUrlRequiredInvalid(mappingLeading.imageUrl)\"\n >URL/expr obrigat\u00F3ria</mat-error\n >\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Alt Text</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.imageAlt\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Badge Texto</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.badgeText\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n\n @switch (mappingLeading.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingLeading\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingLeading\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Estilo</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingLeading.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Style</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingLeading.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Features -->\n <mat-expansion-panel\n [expanded]=\"featuresVisible && features.length > 0\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>view_list</mat-icon>\n <span>Recursos (Features)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description\n >{{ features.length }} item(s)</mat-panel-description\n >\n </mat-expansion-panel-header>\n\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-12 ai-center\">\n <mat-slide-toggle\n [(ngModel)]=\"featuresVisible\"\n (ngModelChange)=\"onFeaturesChanged()\"\n >Ativar recursos</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"featuresSyncWithMeta\"\n (ngModelChange)=\"onMappingChanged()\"\n >Sincronizar com Meta</mat-slide-toggle\n >\n <span class=\"flex-1\"></span>\n <mat-button-toggle-group\n [(ngModel)]=\"featuresMode\"\n (change)=\"onFeaturesChanged()\"\n appearance=\"legacy\"\n >\n <mat-button-toggle value=\"icons+labels\"\n ><mat-icon>view_list</mat-icon></mat-button-toggle\n >\n <mat-button-toggle value=\"icons-only\"\n ><mat-icon>more_horiz</mat-icon></mat-button-toggle\n >\n </mat-button-toggle-group>\n </div>\n\n <div\n *ngFor=\"let f of features; let i = index\"\n class=\"g g-auto-1 gap-8 ai-center p-8 border rounded mb-2\"\n >\n <button mat-icon-button (click)=\"pickFeatureIcon(i)\">\n <mat-icon>{{ f.icon || \"search\" }}</mat-icon>\n </button>\n <mat-form-field\n appearance=\"outline\"\n class=\"dense-form-field no-sub\"\n >\n <input\n matInput\n [(ngModel)]=\"f.expr\"\n (ngModelChange)=\"onFeaturesChanged()\"\n placeholder=\"Expr/Texto\"\n />\n </mat-form-field>\n <button mat-icon-button color=\"warn\" (click)=\"removeFeature(i)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n <button mat-button color=\"primary\" (click)=\"addFeature()\">\n <mat-icon>add</mat-icon> Adicionar recurso\n </button>\n </div>\n </mat-expansion-panel>\n <!-- Section Header -->\n <mat-expansion-panel [expanded]=\"!!mappingSectionHeader.expr\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingSectionHeader.type)\n }}</mat-icon>\n <span>Cabe\u00E7alho de Se\u00E7\u00E3o</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingSectionHeader.expr || \"N\u00E3o configurado\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSectionHeader.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of sectionHeaderTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Express\u00E3o (item.key)</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingSectionHeader.expr\"\n (ngModelChange)=\"onMappingChanged()\"\n placeholder=\"item.key\"\n />\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSectionHeader.type = 'text';\n mappingSectionHeader.expr = '${item.key}';\n onMappingChanged()\n \"\n >\n Texto padr\u00E3o\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSectionHeader.type = 'chip';\n mappingSectionHeader.chipColor = 'primary';\n mappingSectionHeader.chipVariant = 'filled';\n mappingSectionHeader.expr = '${item.key}';\n onMappingChanged()\n \"\n >\n Chip padr\u00E3o\n </button>\n </div>\n\n @switch (mappingSectionHeader.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingSectionHeader\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingSectionHeader\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>URL Imagem</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingSectionHeader.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <mat-error\n *ngIf=\"\n isImageUrlRequiredInvalid(\n mappingSectionHeader.imageUrl\n )\n \"\n >URL/expr obrigat\u00F3ria</mat-error\n >\n </mat-form-field>\n </div>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!mappingSectionHeader.imageUrl\"\n >\n Defina a URL/expr para renderizar a imagem.\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingSectionHeader\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Estilo</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSectionHeader.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Style</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSectionHeader.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Empty State -->\n <mat-expansion-panel [expanded]=\"!!mappingEmptyState.expr\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>inbox</mat-icon>\n <span>Estado Vazio</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingEmptyState.expr || \"Padr\u00E3o\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingEmptyState.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of emptyStateTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mensagem / Expr</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingEmptyState.expr\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingEmptyState.type = 'text';\n mappingEmptyState.expr = 'Nenhum item dispon\u00EDvel';\n onMappingChanged()\n \"\n >\n Mensagem padr\u00E3o\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingEmptyState.type = 'image';\n mappingEmptyState.imageUrl = '/list-empty-state.svg';\n mappingEmptyState.imageAlt = 'Sem resultados';\n onMappingChanged()\n \"\n >\n Imagem padr\u00E3o\n </button>\n </div>\n\n @switch (mappingEmptyState.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingEmptyState\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingEmptyState\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>URL Imagem</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <mat-error\n *ngIf=\"\n isImageUrlRequiredInvalid(mappingEmptyState.imageUrl)\n \"\n >URL/expr obrigat\u00F3ria</mat-error\n >\n </mat-form-field>\n </div>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!mappingEmptyState.imageUrl\"\n >\n Defina a URL/expr para renderizar a imagem.\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingEmptyState\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Estilo</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Style</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n </mat-accordion>\n\n <button mat-flat-button color=\"primary\" (click)=\"applyTemplate()\">\n Aplicar mapeamento\n </button>\n <button\n mat-button\n (click)=\"inferFromFields()\"\n [disabled]=\"!fields.length\"\n >\n Inferir do schema\n </button>\n <div class=\"g g-auto-220 gap-12 ai-end mt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Skeleton (quantidade)</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"0\"\n [(ngModel)]=\"skeletonCountInput\"\n (ngModelChange)=\"onSkeletonChanged($event)\"\n />\n </mat-form-field>\n </div>\n\n <div class=\"g gap-12 mt-12\">\n <div class=\"g row-flow gap-8 ai-center\">\n <span class=\"section-title mat-subtitle-1\">Pr\u00E9via de tema</span>\n <mat-button-toggle-group\n [(ngModel)]=\"skinPreviewTheme\"\n (change)=\"onSkinChanged()\"\n appearance=\"legacy\"\n >\n <mat-button-toggle [value]=\"'light'\">Claro</mat-button-toggle>\n <mat-button-toggle [value]=\"'dark'\">Escuro</mat-button-toggle>\n <mat-button-toggle [value]=\"'grid'\">Grade</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <div class=\"skin-preview-wrap\">\n <praxis-list-skin-preview\n [config]=\"working\"\n [items]=\"previewData\"\n [theme]=\"skinPreviewTheme\"\n ></praxis-list-skin-preview>\n </div>\n </div>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"i18n/A11y\">\n <div\n class=\"editor-content grid gap-3\"\n *ngIf=\"working?.a11y && working?.events\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>Locale padr\u00E3o</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.i18n.locale\"\n (ngModelChange)=\"markDirty()\"\n placeholder=\"ex.: pt-BR\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Moeda padr\u00E3o</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.i18n.currency\"\n (ngModelChange)=\"markDirty()\"\n placeholder=\"ex.: BRL\"\n />\n </mat-form-field>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">Acessibilidade</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-form-field appearance=\"outline\">\n <mat-label>aria-label</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.a11y!.ariaLabel\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>aria-labelledby</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.a11y!.ariaLabelledBy\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n </div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-slide-toggle\n [(ngModel)]=\"working!.a11y!.highContrast\"\n (ngModelChange)=\"markDirty()\"\n >Alto contraste</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working!.a11y!.reduceMotion\"\n (ngModelChange)=\"markDirty()\"\n >Reduzir movimento</mat-slide-toggle\n >\n </div>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">Eventos</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-form-field appearance=\"outline\">\n <mat-label>itemClick</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.itemClick\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>actionClick</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.actionClick\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>selectionChange</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.selectionChange\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>loaded</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.loaded\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"Sele\u00E7\u00E3o\">\n <div class=\"editor-content grid gap-3\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Modo</mat-label>\n <mat-select\n [(ngModel)]=\"working.selection.mode\"\n (ngModelChange)=\"onSelectionChanged()\"\n >\n <mat-option value=\"none\">Sem sele\u00E7\u00E3o</mat-option>\n <mat-option value=\"single\">\u00DAnica</mat-option>\n <mat-option value=\"multiple\">M\u00FAltipla</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Nome no formul\u00E1rio</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.formControlName\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"formControlName\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Caminho no formul\u00E1rio</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.formControlPath\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"formControlPath\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Comparar por (campo)</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.compareBy\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Chave unica do item.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Retorno</mat-label>\n <mat-select\n [(ngModel)]=\"working.selection.return\"\n (ngModelChange)=\"onSelectionChanged()\"\n >\n <mat-option value=\"value\">value</mat-option>\n <mat-option value=\"item\">item</mat-option>\n <mat-option value=\"id\">id</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </mat-tab>\n <mat-tab label=\"Apar\u00EAncia\">\n <div class=\"editor-content grid gap-3\">\n <div class=\"preset-row g row-flow gap-8\">\n <button mat-button (click)=\"applySkinPreset('pill-soft')\">\n Pill Soft\n </button>\n <button mat-button (click)=\"applySkinPreset('gradient-tile')\">\n Gradient Tile\n </button>\n <button mat-button (click)=\"applySkinPreset('glass')\">Glass</button>\n <button mat-button (click)=\"applySkinPreset('elevated')\">\n Elevated\n </button>\n <button mat-button (click)=\"applySkinPreset('outline')\">Outline</button>\n <button mat-button (click)=\"applySkinPreset('flat')\">Flat</button>\n <button mat-button (click)=\"applySkinPreset('neumorphism')\">\n Neumorphism\n </button>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Estilo</mat-label>\n <mat-select\n [(ngModel)]=\"working.skin.type\"\n (ngModelChange)=\"onSkinTypeChanged($event)\"\n >\n <mat-option value=\"pill-soft\">Pill Soft</mat-option>\n <mat-option value=\"gradient-tile\">Gradient Tile</mat-option>\n <mat-option value=\"glass\">Glass</mat-option>\n <mat-option value=\"elevated\">Elevated</mat-option>\n <mat-option value=\"outline\">Outline</mat-option>\n <mat-option value=\"flat\">Flat</mat-option>\n <mat-option value=\"neumorphism\">Neumorphism</mat-option>\n <mat-option value=\"custom\">Custom</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Raio</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.radius\"\n (ngModelChange)=\"onSkinChanged()\"\n placeholder=\"ex.: 1.25rem\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Sombra</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.shadow\"\n (ngModelChange)=\"onSkinChanged()\"\n placeholder=\"ex.: var(--md-sys-elevation-level2)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Borda</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.border\"\n (ngModelChange)=\"onSkinChanged()\"\n />\n </mat-form-field>\n <mat-form-field\n *ngIf=\"working.skin.type === 'glass'\"\n appearance=\"outline\"\n >\n <mat-label>Desfoque</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.backdropBlur\"\n (ngModelChange)=\"onSkinChanged()\"\n placeholder=\"ex.: 8px\"\n />\n </mat-form-field>\n <div *ngIf=\"working.skin.type === 'gradient-tile'\" class=\"g gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Degrad\u00EA de</mat-label>\n <input\n matInput\n [ngModel]=\"working.skin.gradient.from || ''\"\n (ngModelChange)=\"onSkinGradientChanged('from', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Degrad\u00EA at\u00E9</mat-label>\n <input\n matInput\n [ngModel]=\"working.skin.gradient.to || ''\"\n (ngModelChange)=\"onSkinGradientChanged('to', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>\u00C2ngulo</mat-label>\n <input\n matInput\n type=\"number\"\n [ngModel]=\"working.skin.gradient.angle ?? 135\"\n (ngModelChange)=\"onSkinGradientChanged('angle', $event)\"\n />\n </mat-form-field>\n </div>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>Classe CSS extra (skin.class)</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.class\"\n (ngModelChange)=\"onSkinChanged()\"\n placeholder=\"ex.: my-list-skin\"\n />\n </mat-form-field>\n\n <div\n *ngIf=\"working.skin.type === 'custom'\"\n class=\"g g-auto-220 gap-12 ai-end\"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Estilo inline (skin.inlineStyle)</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [(ngModel)]=\"working.skin.inlineStyle\"\n (ngModelChange)=\"onSkinChanged()\"\n [attr.placeholder]=\"':host{--p-list-radius: 1rem}'\"\n ></textarea>\n </mat-form-field>\n <div class=\"text-caption\">\n Exemplo de CSS por classe (adicione no seu styles global):\n <pre class=\"code-block\">\n.my-list-skin .item-card &#123;\n border-radius: 14px;\n border: 1px solid var(--md-sys-color-outline-variant);\n box-shadow: var(--md-sys-elevation-level2);\n&#125;\n.my-list-skin .mat-mdc-list-item .list-item-content &#123;\n backdrop-filter: blur(6px);\n&#125;</pre\n >\n </div>\n </div>\n </div>\n </mat-tab>\n</mat-tab-group>\n", styles: [".confirm-type{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;line-height:16px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant)}.confirm-type.danger{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.confirm-type.warning{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.confirm-type.info{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}:host{display:block;color:var(--md-sys-color-on-surface)}.list-editor-tabs{--editor-surface: var(--md-sys-color-surface-container-lowest);--editor-border: 1px solid var(--md-sys-color-outline-variant);--editor-radius: var(--md-sys-shape-corner-large, 16px);--editor-muted: var(--md-sys-color-on-surface-variant);--editor-accent: var(--md-sys-color-primary)}.editor-content{padding:16px;background:var(--editor-surface);border:var(--editor-border);border-radius:var(--editor-radius);display:grid;gap:12px}.editor-content .mat-mdc-form-field{width:100%;max-width:none;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var( --md-sys-color-outline-variant );--mdc-outlined-text-field-hover-outline-color: var( --md-sys-color-outline );--mdc-outlined-text-field-focus-outline-color: var( --md-sys-color-primary );--mdc-outlined-text-field-error-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-error-focus-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-error-hover-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-label-text-color: var( --md-sys-color-on-surface-variant );--mdc-outlined-text-field-input-text-color: var( --md-sys-color-on-surface );--mdc-outlined-text-field-supporting-text-color: var( --md-sys-color-on-surface-variant )}.editor-content .mat-mdc-form-field.w-full{max-width:none}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}.editor-split{grid-template-columns:minmax(0,1fr);align-items:start}.editor-main,.editor-aside{display:grid;gap:12px}.skin-preview-wrap{border-radius:calc(var(--editor-radius) - 4px);border:var(--editor-border);background:var(--md-sys-color-surface-container);padding:12px}.g{display:grid}.g-auto-220{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.g-auto-200{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}.g-1-auto{grid-template-columns:1fr auto}.row-flow{grid-auto-flow:column}.gap-6{gap:6px}.gap-8{gap:8px}.gap-12{gap:12px}.ai-center{align-items:center}.ai-end{align-items:end}.mt-12{margin-top:12px}.mb-8{margin-bottom:8px}.mb-6{margin-bottom:6px}.my-8{margin:8px 0}.subtitle{margin:8px 0 4px;color:var(--editor-muted);font-weight:500}.section-title{color:var(--editor-muted);font-weight:600}.chips-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.error{color:var(--md-sys-color-error);font-size:.85rem}.muted{color:var(--editor-muted)}.text-caption{color:var(--editor-muted);font-size:.8rem}:host ::ng-deep .mat-mdc-select-panel .option-icon{font-size:18px;margin-right:6px;vertical-align:middle}:host ::ng-deep .mat-mdc-select-panel .color-dot{width:10px;height:10px;border-radius:999px;display:inline-block;margin-right:6px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-outline)}:host ::ng-deep .mat-mdc-select-panel .color-primary{background:var(--md-sys-color-primary)}:host ::ng-deep .mat-mdc-select-panel .color-accent{background:var(--md-sys-color-tertiary)}:host ::ng-deep .mat-mdc-select-panel .color-warn{background:var(--md-sys-color-error)}:host ::ng-deep .mat-mdc-select-panel .color-default{background:var(--md-sys-color-outline)}@media(max-width:1024px){.editor-split{grid-template-columns:minmax(0,1fr)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i3$3.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$3.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$1.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i2$1.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i2$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i8.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i10.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i10.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i10.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i10.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i10.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatButtonToggleModule }, { kind: "directive", type: i11.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i11.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i12.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i3.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "ngmodule", type: MatMenuModule }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisListSkinPreviewComponent, selector: "praxis-list-skin-preview", inputs: ["config", "items", "theme"] }, { kind: "component", type: PraxisMetaEditorTextComponent, selector: "praxis-meta-editor-text", inputs: ["model", "setPipe"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorChipComponent, selector: "praxis-meta-editor-chip", inputs: ["model", "paletteOptions", "colorDotBackground", "isCustomColor", "enableCustomColor"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorRatingComponent, selector: "praxis-meta-editor-rating", inputs: ["model", "paletteOptions", "colorDotBackground", "isCustomColor", "enableCustomColor"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorCurrencyComponent, selector: "praxis-meta-editor-currency", inputs: ["model"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorDateComponent, selector: "praxis-meta-editor-date", inputs: ["model"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorIconComponent, selector: "praxis-meta-editor-icon", inputs: ["model", "paletteOptions", "colorDotBackground", "isCustomColor", "enableCustomColor"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorImageComponent, selector: "praxis-meta-editor-image", inputs: ["model"], outputs: ["change"] }, { kind: "component", type: PraxisListJsonConfigEditorComponent, selector: "praxis-list-json-config-editor", inputs: ["document"], outputs: ["documentChange", "validationChange", "editorEvent"] }, { kind: "component", type: PdxColorPickerComponent, selector: "pdx-color-picker", inputs: ["actionsLayout", "activeView", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "clearButton", "disabledMode", "readonlyMode", "visible", "presentationMode", "fillMode", "format", "gradientSettings", "icon", "iconClass", "svgIcon", "paletteSettings", "popupSettings", "preview", "rounded", "size", "tabindex", "views", "maxRecent", "showRecent"], outputs: ["valueChange", "open", "close", "cancel", "activeViewChange", "activeColorClick", "focusEvent", "blurEvent"] }] });
5157
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisListConfigEditor, isStandalone: true, selector: "praxis-list-config-editor", inputs: { config: "config", listId: "listId" }, providers: [providePraxisI18nConfig(SURFACE_OPEN_I18N_CONFIG)], ngImport: i0, template: "<mat-tab-group class=\"list-editor-tabs\">\n <mat-tab label=\"Dados\">\n <div class=\"editor-content\">\n <div class=\"g g-1-auto gap-8 ai-center\">\n <div class=\"muted\">\n Observa\u00C3\u00A7\u00C3\u00A3o: ajustes aplicados pelo assistente substituem o objeto de\n configura\u00C3\u00A7\u00C3\u00A3o inteiro.\n </div>\n <button\n mat-icon-button\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"O applyConfigFromAdapter n\u00C3\u00A3o faz merge profundo. Garanta que o adapter envie a config completa.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </div>\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Recurso (API)</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.dataSource.resourcePath\"\n (ngModelChange)=\"onResourcePathChange($event)\"\n placeholder=\"ex.: users\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Endpoint do recurso (resourcePath).\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Query (JSON)</mat-label>\n <textarea\n matInput\n rows=\"3\"\n [(ngModel)]=\"queryJson\"\n (ngModelChange)=\"onQueryChanged($event)\"\n placeholder='ex.: &#123;\"status\":\"active\",\"department\":\"sales\"&#125;'\n ></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Opcional. Use JSON v\u00C3\u00A1lido para filtros iniciais.\"\n *ngIf=\"!queryError\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error *ngIf=\"queryError\">{{ queryError }}</mat-error>\n </mat-form-field>\n <div class=\"g g-auto-220 gap-12 ai-end mt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Ordenar por</mat-label>\n <mat-select\n [(ngModel)]=\"sortField\"\n (ngModelChange)=\"updateSortConfig()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Campo base do recurso.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Dire\u00C3\u00A7\u00C3\u00A3o</mat-label>\n <mat-select\n [(ngModel)]=\"sortDir\"\n (ngModelChange)=\"updateSortConfig()\"\n >\n <mat-option value=\"asc\">Ascendente</mat-option>\n <mat-option value=\"desc\">Descendente</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"JSON\">\n <div class=\"editor-content\">\n <praxis-list-json-config-editor\n [document]=\"document\"\n (documentChange)=\"onJsonConfigChange($event)\"\n (validationChange)=\"onJsonValidationChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n >\n </praxis-list-json-config-editor>\n </div>\n </mat-tab>\n <mat-tab label=\"A\u00C3\u00A7\u00C3\u00B5es\">\n <div class=\"editor-content g gap-12\">\n <div class=\"g g-1-auto gap-8 ai-center\">\n <div class=\"muted\">\n Configure bot\u00C3\u00B5es de a\u00C3\u00A7\u00C3\u00A3o por item (\u00C3\u00ADcone, r\u00C3\u00B3tulo, cor, visibilidade)\n </div>\n <button mat-flat-button color=\"primary\" (click)=\"addAction()\">\n Adicionar a\u00C3\u00A7\u00C3\u00A3o\n </button>\n </div>\n <div class=\"g g-1-auto gap-8 ai-center\">\n <mat-form-field appearance=\"outline\">\n <mat-label>A\u00C3\u00A7\u00C3\u00A3o global (Praxis)</mat-label>\n <mat-select\n [(ngModel)]=\"selectedGlobalActionId\"\n (ngModelChange)=\"onGlobalActionSelected($event)\"\n >\n <mat-option [value]=\"undefined\">-- Selecionar --</mat-option>\n <mat-option *ngFor=\"let ga of globalActionCatalog\" [value]=\"ga.id\">\n <mat-icon class=\"option-icon\">{{ ga.icon || \"bolt\" }}</mat-icon>\n {{ ga.label }}\n </mat-option>\n </mat-select>\n <mat-hint\n *ngIf=\"!globalActionCatalog.length\"\n class=\"text-caption muted\"\n >Nenhuma a\u00C3\u00A7\u00C3\u00A3o global registrada.</mat-hint\n >\n </mat-form-field>\n <div class=\"muted text-caption\">\n Selecione para adicionar com `command` global.\n </div>\n </div>\n <div\n *ngFor=\"let a of working.actions || []; let i = index\"\n class=\"g g-auto-200 gap-12 ai-end\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>ID</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.id\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo de a\u00C3\u00A7\u00C3\u00A3o</mat-label>\n <mat-select [(ngModel)]=\"a.kind\" (ngModelChange)=\"onActionsChanged()\">\n <mat-option value=\"icon\">\u00C3\u008Dcone</mat-option>\n <mat-option value=\"button\">Bot\u00C3\u00A3o</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>\u00C3\u008Dcone</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.icon\"\n (ngModelChange)=\"onActionsChanged()\"\n placeholder=\"ex.: edit, delete\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Command (global)</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.command\"\n (ngModelChange)=\"onActionsChanged()\"\n placeholder=\"global:toast.success\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>R\u00C3\u00B3tulo</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.label\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n </mat-form-field>\n <ng-container *ngIf=\"a.kind === 'button'\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Variante</mat-label>\n <mat-select\n [(ngModel)]=\"a.buttonVariant\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option value=\"stroked\">Contorno</mat-option>\n <mat-option value=\"raised\">Elevado</mat-option>\n <mat-option value=\"flat\">Preenchido</mat-option>\n </mat-select>\n </mat-form-field>\n </ng-container>\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor da a\u00C3\u00A7\u00C3\u00A3o</mat-label>\n <mat-select\n [(ngModel)]=\"a.color\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option *ngFor=\"let c of paletteOptions\" [value]=\"c.value\">\n <span\n class=\"color-dot\"\n [style.background]=\"colorDotBackground(c.value)\"\n ></span\n >{{ c.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <div\n class=\"g gap-8\"\n *ngIf=\"isCustomColor(a.color); else actionCustomBtn\"\n >\n <pdx-color-picker\n label=\"Cor personalizada\"\n [format]=\"'hex'\"\n [(ngModel)]=\"a.color\"\n (ngModelChange)=\"onActionsChanged()\"\n ></pdx-color-picker>\n </div>\n <ng-template #actionCustomBtn>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"enableCustomActionColor(a)\"\n >\n Usar cor personalizada\n </button>\n </ng-template>\n <mat-form-field appearance=\"outline\">\n <mat-label>Payload da a\u00C3\u00A7\u00C3\u00A3o</mat-label>\n <mat-select\n [(ngModel)]=\"a.emitPayload\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option [value]=\"undefined\">Padr\u00C3\u00A3o</mat-option>\n <mat-option value=\"item\">item</mat-option>\n <mat-option value=\"id\">id</mat-option>\n <mat-option value=\"value\">value</mat-option>\n </mat-select>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"emitPayload\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"col-span-2\">\n <mat-label\n >Exibir quando (ex.: &#36;&#123;item.status&#125; ==\n 'done')</mat-label\n >\n <input\n matInput\n [(ngModel)]=\"a.showIf\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Sintaxe suportada: &#34;&#36;{item.campo} == &#39;valor&#39;&#34;. Express\u00C3\u00B5es avan\u00C3\u00A7adas n\u00C3\u00A3o s\u00C3\u00A3o avaliadas.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"g row-flow gap-8 ai-center\">\n <button\n *ngIf=\"(a.kind || 'icon') === 'icon'\"\n mat-icon-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n >\n <mat-icon\n [praxisIcon]=\"a.icon || 'bolt'\"\n [style.cssText]=\"iconStyle(a.color)\"\n ></mat-icon>\n </button>\n <ng-container *ngIf=\"a.kind === 'button'\">\n <button\n *ngIf=\"a.buttonVariant === 'stroked'\"\n mat-stroked-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'stroked')\"\n >\n {{ a.label || a.id || \"A\u00C3\u00A7\u00C3\u00A3o\" }}\n </button>\n <button\n *ngIf=\"a.buttonVariant === 'raised'\"\n mat-raised-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'raised')\"\n >\n {{ a.label || a.id || \"A\u00C3\u00A7\u00C3\u00A3o\" }}\n </button>\n <button\n *ngIf=\"!a.buttonVariant || a.buttonVariant === 'flat'\"\n mat-flat-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'flat')\"\n >\n {{ a.label || a.id || \"A\u00C3\u00A7\u00C3\u00A3o\" }}\n </button>\n </ng-container>\n <span class=\"muted\">Pr\u00C3\u00A9-visualiza\u00C3\u00A7\u00C3\u00A3o</span>\n </div>\n <div class=\"flex-end\">\n <button mat-button color=\"warn\" (click)=\"removeAction(i)\">\n Remover\n </button>\n </div>\n <div class=\"g gap-8 col-span-2\" *ngIf=\"a.command\">\n <mat-slide-toggle\n [(ngModel)]=\"a.showLoading\"\n (ngModelChange)=\"onActionsChanged()\"\n >Mostrar loading</mat-slide-toggle\n >\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Confirma\u00C3\u00A7\u00C3\u00A3o</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <div class=\"g row-flow gap-8 ai-center\">\n <span class=\"text-caption muted\">Tipo</span>\n <mat-button-toggle-group\n [value]=\"a.confirmation?.type || ''\"\n (change)=\"applyConfirmationPreset(a, $event.value)\"\n >\n <mat-button-toggle value=\"\">Padr\u00C3\u00A3o</mat-button-toggle>\n <mat-button-toggle value=\"danger\">Danger</mat-button-toggle>\n <mat-button-toggle value=\"warning\">Warning</mat-button-toggle>\n <mat-button-toggle value=\"info\">Info</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>T\u00C3\u00ADtulo</mat-label>\n <input\n matInput\n [ngModel]=\"a.confirmation?.title\"\n (ngModelChange)=\"setConfirmationField(a, 'title', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mensagem</mat-label>\n <input\n matInput\n [ngModel]=\"a.confirmation?.message\"\n (ngModelChange)=\"setConfirmationField(a, 'message', $event)\"\n />\n </mat-form-field>\n <div class=\"g gap-6\">\n <div class=\"text-caption muted\">Pr\u00C3\u00A9via</div>\n <div class=\"text-caption\">\n <strong>{{\n a.confirmation?.title || \"Confirmar a\u00C3\u00A7\u00C3\u00A3o\"\n }}</strong>\n </div>\n <div class=\"text-caption muted\">\n {{\n a.confirmation?.message ||\n \"Tem certeza que deseja continuar?\"\n }}\n </div>\n <div class=\"text-caption\">\n <span\n class=\"confirm-type\"\n [ngClass]=\"a.confirmation?.type || 'default'\"\n >Tipo: {{ a.confirmation?.type || \"padr\u00C3\u00A3o\" }}</span\n >\n </div>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!a.confirmation?.title && !a.confirmation?.message\"\n >\n Defina um t\u00C3\u00ADtulo ou mensagem para a confirma\u00C3\u00A7\u00C3\u00A3o.\n </div>\n </div>\n </div>\n </mat-expansion-panel>\n <ng-container *ngIf=\"isSurfaceOpenCommand(a); else defaultGlobalPayloadEditor\">\n <div class=\"col-span-2\">\n <praxis-surface-open-action-editor\n [value]=\"getSurfaceOpenGlobalPayload(a)\"\n hostKind=\"list\"\n (valueChange)=\"onSurfaceOpenGlobalPayloadChange(a, $event)\"\n ></praxis-surface-open-action-editor>\n </div>\n </ng-container>\n <ng-template #defaultGlobalPayloadEditor>\n <mat-form-field appearance=\"outline\" class=\"col-span-2\">\n <mat-label>Payload (JSON/Template)</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [(ngModel)]=\"a.globalPayload\"\n (ngModelChange)=\"onActionsChanged()\"\n placeholder='{\"message\":\"${item.name} favoritado\"}'\n ></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"globalPayloadSchemaTooltip(a)\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error *ngIf=\"isGlobalPayloadInvalid(a.globalPayload)\"\n >JSON inv\u00C3\u00A1lido</mat-error\n >\n </mat-form-field>\n <div class=\"g row-flow gap-8 ai-center\">\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"applyGlobalPayloadExample(a)\"\n >\n Inserir exemplo\n </button>\n <span class=\"muted text-caption\">{{\n globalPayloadExampleHint(a)\n }}</span>\n </div>\n </ng-template>\n <mat-slide-toggle\n [(ngModel)]=\"a.emitLocal\"\n (ngModelChange)=\"onActionsChanged()\"\n >Emitir evento local tamb\u00C3\u00A9m</mat-slide-toggle\n >\n </div>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"Layout\">\n <div class=\"editor-content grid gap-3\">\n <div class=\"preset-row g row-flow gap-8\">\n <button mat-stroked-button (click)=\"applyLayoutPreset('tiles-modern')\">\n Preset Tiles Moderno\n </button>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Variante</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.variant\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"list\">Lista</mat-option>\n <mat-option value=\"cards\">Cards</mat-option>\n <mat-option value=\"tiles\">Tiles</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Modelo</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.model\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <ng-container\n *ngIf=\"working.layout.variant === 'list'; else cardModels\"\n >\n <mat-option value=\"standard\">Padr\u00C3\u00A3o</mat-option>\n <mat-option value=\"media\">M\u00C3\u00ADdia \u00C3\u00A0 esquerda</mat-option>\n <mat-option value=\"hotel\">Hotel (m\u00C3\u00ADdia grande)</mat-option>\n </ng-container>\n <ng-template #cardModels>\n <ng-container\n *ngIf=\"working.layout.variant === 'tiles'; else cardsOnly\"\n >\n <mat-option value=\"standard\">Tile padr\u00C3\u00A3o</mat-option>\n <mat-option value=\"media\">Tile com m\u00C3\u00ADdia</mat-option>\n <mat-option value=\"hotel\">Tile hotel</mat-option>\n </ng-container>\n <ng-template #cardsOnly>\n <mat-option value=\"standard\">Padr\u00C3\u00A3o</mat-option>\n <mat-option value=\"media\">Card com m\u00C3\u00ADdia</mat-option>\n <mat-option value=\"hotel\">Hotel</mat-option>\n </ng-template>\n </ng-template>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Linhas</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.lines\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option [value]=\"1\">1</mat-option>\n <mat-option [value]=\"2\">2</mat-option>\n <mat-option [value]=\"3\">3</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Itens por p\u00C3\u00A1gina</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"1\"\n [(ngModel)]=\"working.layout.pageSize\"\n (ngModelChange)=\"onPageSizeChange($event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Densidade</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.density\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"default\">Padr\u00C3\u00A3o</mat-option>\n <mat-option value=\"comfortable\">Confort\u00C3\u00A1vel</mat-option>\n <mat-option value=\"compact\">Compacta</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Espa\u00C3\u00A7amento entre itens</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.itemSpacing\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"none\">Sem espa\u00C3\u00A7o extra</mat-option>\n <mat-option value=\"tight\">Justo</mat-option>\n <mat-option value=\"default\">Padr\u00C3\u00A3o</mat-option>\n <mat-option value=\"relaxed\">Relaxado</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"working.layout.variant !== 'tiles'\"\n >\n <mat-label>Divisores</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.dividers\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"none\">Sem</mat-option>\n <mat-option value=\"between\">Entre grupos</mat-option>\n <mat-option value=\"all\">Todos</mat-option>\n </mat-select>\n </mat-form-field>\n <ng-container *ngIf=\"fields.length > 0; else groupByText\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Agrupar por</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.groupBy\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option [value]=\"\">Nenhum</mat-option>\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n </ng-container>\n <ng-template #groupByText>\n <mat-form-field appearance=\"outline\">\n <mat-label>Agrupar por</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.layout.groupBy\"\n (ngModelChange)=\"onLayoutChanged()\"\n placeholder=\"ex.: departamento\"\n />\n </mat-form-field>\n </ng-template>\n <mat-slide-toggle\n [(ngModel)]=\"working.layout.stickySectionHeader\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n Header de se\u00C3\u00A7\u00C3\u00A3o fixo\n </mat-slide-toggle>\n <mat-slide-toggle\n [(ngModel)]=\"working.layout.virtualScroll\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n Scroll virtual\n </mat-slide-toggle>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">Ferramentas da lista</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showSearch\"\n (ngModelChange)=\"onUiChanged()\"\n >Mostrar busca</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showSort\"\n (ngModelChange)=\"onUiChanged()\"\n >Mostrar ordenar</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showRange\"\n (ngModelChange)=\"onUiChanged()\"\n >Mostrar faixa X\u00E2\u20AC\u201CY de Total</mat-slide-toggle\n >\n </div>\n <div\n class=\"g g-auto-220 gap-12 ai-end mt-12\"\n *ngIf=\"working.ui?.showSearch\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo para buscar</mat-label>\n <mat-select\n [(ngModel)]=\"working.ui.searchField\"\n (ngModelChange)=\"onUiChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Placeholder da busca</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.ui.searchPlaceholder\"\n (ngModelChange)=\"onUiChanged()\"\n placeholder=\"ex.: Buscar por t\u00C3\u00ADtulo\"\n />\n </mat-form-field>\n </div>\n <div class=\"mt-12\" *ngIf=\"working.ui?.showSort\">\n <div class=\"g g-1-auto ai-center gap-8\">\n <div class=\"muted\">Op\u00C3\u00A7\u00C3\u00B5es de ordena\u00C3\u00A7\u00C3\u00A3o (r\u00C3\u00B3tulo \u00E2\u2020\u2019 campo+dire\u00C3\u00A7\u00C3\u00A3o)</div>\n <button mat-flat-button color=\"primary\" (click)=\"addUiSortRow()\">\n Adicionar op\u00C3\u00A7\u00C3\u00A3o\n </button>\n </div>\n <div\n class=\"g g-auto-220 gap-12 ai-end mt-12\"\n *ngFor=\"let r of uiSortRows; let i = index\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>R\u00C3\u00B3tulo</mat-label>\n <input\n matInput\n [(ngModel)]=\"r.label\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n placeholder=\"ex.: Mais recentes\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"r.field\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Dire\u00C3\u00A7\u00C3\u00A3o</mat-label>\n <mat-select\n [(ngModel)]=\"r.dir\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n >\n <mat-option value=\"desc\">Descendente</mat-option>\n <mat-option value=\"asc\">Ascendente</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"error\" *ngIf=\"isUiSortRowDuplicate(i)\">\n Op\u00C3\u00A7\u00C3\u00A3o duplicada (campo+dire\u00C3\u00A7\u00C3\u00A3o)\n </div>\n <div class=\"flex-end\">\n <button mat-button color=\"warn\" (click)=\"removeUiSortRow(i)\">\n Remover\n </button>\n </div>\n </div>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"Conte\u00C3\u00BAdo\">\n <div class=\"editor-content\">\n <div class=\"editor-main\">\n <mat-accordion multi>\n <!-- Primary -->\n <mat-expansion-panel [expanded]=\"true\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingPrimary.type) }}</mat-icon>\n <span>Primary (T\u00C3\u00ADtulo)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingPrimary.field || \"N\u00C3\u00A3o mapeado\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'name';\n onMappingChanged()\n \"\n >\n Nome\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'title';\n onMappingChanged()\n \"\n >\n T\u00C3\u00ADtulo\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'name';\n mappingSecondary.type = 'text';\n mappingSecondary.field = 'role';\n onMappingChanged()\n \"\n >\n Nome + Papel\n </button>\n </div>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingPrimary.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingPrimary.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of primaryTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n @switch (mappingPrimary.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingPrimary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingPrimary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingPrimary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingPrimary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n }\n\n <!-- Advanced -->\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header>\n <mat-panel-title>Formata\u00C3\u00A7\u00C3\u00A3o e Estilo</mat-panel-title>\n </mat-expansion-panel-header>\n <div class=\"g gap-12 pt-12\">\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"\n mappingPrimary.type === 'text' ||\n mappingPrimary.type === 'html'\n \"\n >\n <mat-label>Classe CSS</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"\n mappingPrimary.type === 'text' ||\n mappingPrimary.type === 'html'\n \"\n >\n <mat-label>Estilo Inline</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Classe CSS</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Estilo Inline</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Secondary -->\n <mat-expansion-panel [expanded]=\"!!mappingSecondary.field\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingSecondary.type) }}</mat-icon>\n <span>Secondary (Resumo)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingSecondary.field || \"N\u00C3\u00A3o mapeado\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'text';\n mappingSecondary.field = 'subtitle';\n onMappingChanged()\n \"\n >\n Subt\u00C3\u00ADtulo\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'date';\n mappingSecondary.field = 'hireDate';\n mappingSecondary.dateStyle = 'short';\n onMappingChanged()\n \"\n >\n Data curta\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'currency';\n mappingSecondary.field = 'salary';\n mappingSecondary.currencyCode = 'BRL';\n mappingSecondary.locale = 'pt-BR';\n onMappingChanged()\n \"\n >\n Sal\u00C3\u00A1rio\n </button>\n </div>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSecondary.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSecondary.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of secondaryTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n @switch (mappingSecondary.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingSecondary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingSecondary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingSecondary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingSecondary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header>\n <mat-panel-title>Formata\u00C3\u00A7\u00C3\u00A3o e Estilo</mat-panel-title>\n </mat-expansion-panel-header>\n <div class=\"g gap-12 pt-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe CSS</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSecondary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Estilo Inline</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSecondary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <mat-expansion-panel\n [expanded]=\"!!mappingMeta.field || mappingMetaFields.length > 0\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingMeta.type || \"text\")\n }}</mat-icon>\n <span>Meta (Detalhe/Lateral)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>\n {{\n mappingMetaFields.length\n ? \"Campo composto (\" + mappingMetaFields.length + \")\"\n : mappingMeta.field || \"N\u00C3\u00A3o mapeado\"\n }}\n </mat-panel-description>\n </mat-expansion-panel-header>\n\n <div class=\"g gap-12\">\n <!-- Composition Mode Toggle -->\n <div class=\"g g-1-1 gap-12 p-12 bg-subtle rounded\">\n <div class=\"text-caption muted\">Modo de composi\u00C3\u00A7\u00C3\u00A3o</div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Campos para compor (Multi-select)</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMetaFields\"\n multiple\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <div\n class=\"g g-1-1 ai-center gap-12\"\n *ngIf=\"mappingMetaFields.length\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>Separador</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMetaSeparator\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-slide-toggle\n [(ngModel)]=\"mappingMetaWrapSecondInParens\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n (Seg) entre par\u00C3\u00AAnteses\n </mat-slide-toggle>\n </div>\n </div>\n\n <!-- Single Field Mode (if no composition) -->\n <div class=\"g g-1-1 gap-12\" *ngIf=\"!mappingMetaFields.length\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo \u00C3\u0161nico</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option [value]=\"undefined\">-- Nenhum --</mat-option>\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of metaTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- Type configuration (pluggable editors) -->\n @switch (mappingMeta.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingMeta\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingMeta\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <praxis-meta-editor-image\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <!-- Advanced -->\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Op\u00C3\u00A7\u00C3\u00B5es avan\u00C3\u00A7adas</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Posi\u00C3\u00A7\u00C3\u00A3o</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.placement\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option value=\"side\">Lateral (Direita)</mat-option>\n <mat-option value=\"line\">Na linha (Abaixo)</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Classe CSS</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMeta.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Estilo</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMeta.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n <!-- Trailing -->\n <mat-expansion-panel [expanded]=\"!!mappingTrailing.field\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingTrailing.type || \"text\")\n }}</mat-icon>\n <span>Trailing (Direita)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingTrailing.field || \"N\u00C3\u00A3o mapeado\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingTrailing.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option [value]=\"undefined\">-- Nenhum --</mat-option>\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingTrailing.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of trailingTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'chip';\n mappingTrailing.chipColor = 'primary';\n mappingTrailing.chipVariant = 'filled';\n mappingTrailing.field = 'status';\n onMappingChanged()\n \"\n >\n Status Chip\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'icon';\n mappingTrailing.field = 'status';\n mappingTrailing.iconColor = 'primary';\n onMappingChanged()\n \"\n >\n Status \u00C3\u008Dcone\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'currency';\n mappingTrailing.field = 'price';\n mappingTrailing.currencyCode = 'BRL';\n mappingTrailing.locale = 'pt-BR';\n onMappingChanged()\n \"\n >\n Pre\u00C3\u00A7o\n </button>\n </div>\n\n @switch (mappingTrailing.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingTrailing\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingTrailing\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>URL / Expr</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingTrailing.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n placeholder=\"https://... ou ${item.imageUrl}\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Use URL absoluta/relativa ou express\u00C3\u00A3o ${item.campo}.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error\n *ngIf=\"\n isImageUrlRequiredInvalid(mappingTrailing.imageUrl)\n \"\n >URL/expr obrigat\u00C3\u00B3ria</mat-error\n >\n </mat-form-field>\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!mappingTrailing.imageUrl\"\n >\n Defina a URL/expr para renderizar a imagem.\n </div>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Estilo</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingTrailing.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Style</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingTrailing.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Leading -->\n <mat-expansion-panel\n [expanded]=\"\n !!mappingLeading.field ||\n (mappingLeading.type === 'icon' && !!mappingLeading.icon) ||\n (mappingLeading.type === 'image' && !!mappingLeading.imageUrl)\n \"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingLeading.type) }}</mat-icon>\n <span>Leading (Esquerda)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>\n {{\n mappingLeading.type === \"icon\"\n ? mappingLeading.icon || \"\u00C3\u008Dcone est\u00C3\u00A1tico\"\n : mappingLeading.field ||\n (mappingLeading.imageUrl\n ? \"Imagem est\u00C3\u00A1tica\"\n : \"N\u00C3\u00A3o mapeado\")\n }}\n </mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingLeading.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of leadingTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <!-- Field (only if not static icon/image, though user might want dynamic) -->\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"\n mappingLeading.type !== 'icon' &&\n mappingLeading.type !== 'image'\n \"\n >\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingLeading.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'icon';\n mappingLeading.icon = 'person';\n mappingLeading.iconColor = 'primary';\n onMappingChanged()\n \"\n >\n Avatar \u00C3\u008Dcone\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'image';\n mappingLeading.imageUrl = 'https://placehold.co/64x64';\n mappingLeading.imageAlt = 'Avatar';\n mappingLeading.badgeText = '${item.status}';\n onMappingChanged()\n \"\n >\n Avatar Imagem + Badge\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'chip';\n mappingLeading.field = 'tag';\n mappingLeading.chipColor = 'accent';\n mappingLeading.chipVariant = 'filled';\n onMappingChanged()\n \"\n >\n Chip Tag\n </button>\n </div>\n\n <!-- Icon Specific -->\n <div\n class=\"g g-1-auto gap-12 ai-center\"\n *ngIf=\"mappingLeading.type === 'icon'\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>\u00C3\u008Dcone</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.icon\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <button mat-icon-button matSuffix (click)=\"pickLeadingIcon()\">\n <mat-icon>search</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"text-caption muted\">\n Use pipe <code>|iconMap</code> no extra pipe para din\u00C3\u00A2mico\n </div>\n </div>\n <div *ngIf=\"mappingLeading.type === 'icon'\">\n <praxis-meta-editor-icon\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n </div>\n\n <!-- Image Specific -->\n <div\n class=\"g g-1-1 gap-12\"\n *ngIf=\"mappingLeading.type === 'image'\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>URL da Imagem</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n placeholder=\"https://... ou ${item.imageUrl}\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Use URL absoluta/relativa ou express\u00C3\u00A3o ${item.campo}.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error\n *ngIf=\"isImageUrlRequiredInvalid(mappingLeading.imageUrl)\"\n >URL/expr obrigat\u00C3\u00B3ria</mat-error\n >\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Alt Text</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.imageAlt\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Badge Texto</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.badgeText\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n\n @switch (mappingLeading.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingLeading\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingLeading\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Estilo</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingLeading.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Style</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingLeading.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Features -->\n <mat-expansion-panel\n [expanded]=\"featuresVisible && features.length > 0\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>view_list</mat-icon>\n <span>Recursos (Features)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description\n >{{ features.length }} item(s)</mat-panel-description\n >\n </mat-expansion-panel-header>\n\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-12 ai-center\">\n <mat-slide-toggle\n [(ngModel)]=\"featuresVisible\"\n (ngModelChange)=\"onFeaturesChanged()\"\n >Ativar recursos</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"featuresSyncWithMeta\"\n (ngModelChange)=\"onMappingChanged()\"\n >Sincronizar com Meta</mat-slide-toggle\n >\n <span class=\"flex-1\"></span>\n <mat-button-toggle-group\n [(ngModel)]=\"featuresMode\"\n (change)=\"onFeaturesChanged()\"\n appearance=\"legacy\"\n >\n <mat-button-toggle value=\"icons+labels\"\n ><mat-icon>view_list</mat-icon></mat-button-toggle\n >\n <mat-button-toggle value=\"icons-only\"\n ><mat-icon>more_horiz</mat-icon></mat-button-toggle\n >\n </mat-button-toggle-group>\n </div>\n\n <div\n *ngFor=\"let f of features; let i = index\"\n class=\"g g-auto-1 gap-8 ai-center p-8 border rounded mb-2\"\n >\n <button mat-icon-button (click)=\"pickFeatureIcon(i)\">\n <mat-icon>{{ f.icon || \"search\" }}</mat-icon>\n </button>\n <mat-form-field\n appearance=\"outline\"\n class=\"dense-form-field no-sub\"\n >\n <input\n matInput\n [(ngModel)]=\"f.expr\"\n (ngModelChange)=\"onFeaturesChanged()\"\n placeholder=\"Expr/Texto\"\n />\n </mat-form-field>\n <button mat-icon-button color=\"warn\" (click)=\"removeFeature(i)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n <button mat-button color=\"primary\" (click)=\"addFeature()\">\n <mat-icon>add</mat-icon> Adicionar recurso\n </button>\n </div>\n </mat-expansion-panel>\n <!-- Section Header -->\n <mat-expansion-panel [expanded]=\"!!mappingSectionHeader.expr\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingSectionHeader.type)\n }}</mat-icon>\n <span>Cabe\u00C3\u00A7alho de Se\u00C3\u00A7\u00C3\u00A3o</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingSectionHeader.expr || \"N\u00C3\u00A3o configurado\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSectionHeader.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of sectionHeaderTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Express\u00C3\u00A3o (item.key)</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingSectionHeader.expr\"\n (ngModelChange)=\"onMappingChanged()\"\n placeholder=\"item.key\"\n />\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSectionHeader.type = 'text';\n mappingSectionHeader.expr = '${item.key}';\n onMappingChanged()\n \"\n >\n Texto padr\u00C3\u00A3o\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSectionHeader.type = 'chip';\n mappingSectionHeader.chipColor = 'primary';\n mappingSectionHeader.chipVariant = 'filled';\n mappingSectionHeader.expr = '${item.key}';\n onMappingChanged()\n \"\n >\n Chip padr\u00C3\u00A3o\n </button>\n </div>\n\n @switch (mappingSectionHeader.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingSectionHeader\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingSectionHeader\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>URL Imagem</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingSectionHeader.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <mat-error\n *ngIf=\"\n isImageUrlRequiredInvalid(\n mappingSectionHeader.imageUrl\n )\n \"\n >URL/expr obrigat\u00C3\u00B3ria</mat-error\n >\n </mat-form-field>\n </div>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!mappingSectionHeader.imageUrl\"\n >\n Defina a URL/expr para renderizar a imagem.\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingSectionHeader\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Estilo</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSectionHeader.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Style</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSectionHeader.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Empty State -->\n <mat-expansion-panel [expanded]=\"!!mappingEmptyState.expr\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>inbox</mat-icon>\n <span>Estado Vazio</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingEmptyState.expr || \"Padr\u00C3\u00A3o\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingEmptyState.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of emptyStateTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mensagem / Expr</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingEmptyState.expr\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingEmptyState.type = 'text';\n mappingEmptyState.expr = 'Nenhum item dispon\u00C3\u00ADvel';\n onMappingChanged()\n \"\n >\n Mensagem padr\u00C3\u00A3o\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingEmptyState.type = 'image';\n mappingEmptyState.imageUrl = '/list-empty-state.svg';\n mappingEmptyState.imageAlt = 'Sem resultados';\n onMappingChanged()\n \"\n >\n Imagem padr\u00C3\u00A3o\n </button>\n </div>\n\n @switch (mappingEmptyState.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingEmptyState\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingEmptyState\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>URL Imagem</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <mat-error\n *ngIf=\"\n isImageUrlRequiredInvalid(mappingEmptyState.imageUrl)\n \"\n >URL/expr obrigat\u00C3\u00B3ria</mat-error\n >\n </mat-form-field>\n </div>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!mappingEmptyState.imageUrl\"\n >\n Defina a URL/expr para renderizar a imagem.\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingEmptyState\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Estilo</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Style</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n </mat-accordion>\n\n <button mat-flat-button color=\"primary\" (click)=\"applyTemplate()\">\n Aplicar mapeamento\n </button>\n <button\n mat-button\n (click)=\"inferFromFields()\"\n [disabled]=\"!fields.length\"\n >\n Inferir do schema\n </button>\n <div class=\"g g-auto-220 gap-12 ai-end mt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Skeleton (quantidade)</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"0\"\n [(ngModel)]=\"skeletonCountInput\"\n (ngModelChange)=\"onSkeletonChanged($event)\"\n />\n </mat-form-field>\n </div>\n\n <div class=\"g gap-12 mt-12\">\n <div class=\"g row-flow gap-8 ai-center\">\n <span class=\"section-title mat-subtitle-1\">Pr\u00C3\u00A9via de tema</span>\n <mat-button-toggle-group\n [(ngModel)]=\"skinPreviewTheme\"\n (change)=\"onSkinChanged()\"\n appearance=\"legacy\"\n >\n <mat-button-toggle [value]=\"'light'\">Claro</mat-button-toggle>\n <mat-button-toggle [value]=\"'dark'\">Escuro</mat-button-toggle>\n <mat-button-toggle [value]=\"'grid'\">Grade</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <div class=\"skin-preview-wrap\">\n <praxis-list-skin-preview\n [config]=\"working\"\n [items]=\"previewData\"\n [theme]=\"skinPreviewTheme\"\n ></praxis-list-skin-preview>\n </div>\n </div>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"i18n/A11y\">\n <div\n class=\"editor-content grid gap-3\"\n *ngIf=\"working?.a11y && working?.events\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>Locale padr\u00C3\u00A3o</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.i18n.locale\"\n (ngModelChange)=\"markDirty()\"\n placeholder=\"ex.: pt-BR\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Moeda padr\u00C3\u00A3o</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.i18n.currency\"\n (ngModelChange)=\"markDirty()\"\n placeholder=\"ex.: BRL\"\n />\n </mat-form-field>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">Acessibilidade</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-form-field appearance=\"outline\">\n <mat-label>aria-label</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.a11y!.ariaLabel\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>aria-labelledby</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.a11y!.ariaLabelledBy\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n </div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-slide-toggle\n [(ngModel)]=\"working!.a11y!.highContrast\"\n (ngModelChange)=\"markDirty()\"\n >Alto contraste</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working!.a11y!.reduceMotion\"\n (ngModelChange)=\"markDirty()\"\n >Reduzir movimento</mat-slide-toggle\n >\n </div>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">Eventos</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-form-field appearance=\"outline\">\n <mat-label>itemClick</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.itemClick\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>actionClick</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.actionClick\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>selectionChange</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.selectionChange\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>loaded</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.loaded\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"Sele\u00C3\u00A7\u00C3\u00A3o\">\n <div class=\"editor-content grid gap-3\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Modo</mat-label>\n <mat-select\n [(ngModel)]=\"working.selection.mode\"\n (ngModelChange)=\"onSelectionChanged()\"\n >\n <mat-option value=\"none\">Sem sele\u00C3\u00A7\u00C3\u00A3o</mat-option>\n <mat-option value=\"single\">\u00C3\u0161nica</mat-option>\n <mat-option value=\"multiple\">M\u00C3\u00BAltipla</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Nome no formul\u00C3\u00A1rio</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.formControlName\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"formControlName\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Caminho no formul\u00C3\u00A1rio</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.formControlPath\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"formControlPath\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Comparar por (campo)</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.compareBy\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Chave unica do item.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Retorno</mat-label>\n <mat-select\n [(ngModel)]=\"working.selection.return\"\n (ngModelChange)=\"onSelectionChanged()\"\n >\n <mat-option value=\"value\">value</mat-option>\n <mat-option value=\"item\">item</mat-option>\n <mat-option value=\"id\">id</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </mat-tab>\n <mat-tab label=\"Apar\u00C3\u00AAncia\">\n <div class=\"editor-content grid gap-3\">\n <div class=\"preset-row g row-flow gap-8\">\n <button mat-button (click)=\"applySkinPreset('pill-soft')\">\n Pill Soft\n </button>\n <button mat-button (click)=\"applySkinPreset('gradient-tile')\">\n Gradient Tile\n </button>\n <button mat-button (click)=\"applySkinPreset('glass')\">Glass</button>\n <button mat-button (click)=\"applySkinPreset('elevated')\">\n Elevated\n </button>\n <button mat-button (click)=\"applySkinPreset('outline')\">Outline</button>\n <button mat-button (click)=\"applySkinPreset('flat')\">Flat</button>\n <button mat-button (click)=\"applySkinPreset('neumorphism')\">\n Neumorphism\n </button>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Estilo</mat-label>\n <mat-select\n [(ngModel)]=\"working.skin.type\"\n (ngModelChange)=\"onSkinTypeChanged($event)\"\n >\n <mat-option value=\"pill-soft\">Pill Soft</mat-option>\n <mat-option value=\"gradient-tile\">Gradient Tile</mat-option>\n <mat-option value=\"glass\">Glass</mat-option>\n <mat-option value=\"elevated\">Elevated</mat-option>\n <mat-option value=\"outline\">Outline</mat-option>\n <mat-option value=\"flat\">Flat</mat-option>\n <mat-option value=\"neumorphism\">Neumorphism</mat-option>\n <mat-option value=\"custom\">Custom</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Raio</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.radius\"\n (ngModelChange)=\"onSkinChanged()\"\n placeholder=\"ex.: 1.25rem\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Sombra</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.shadow\"\n (ngModelChange)=\"onSkinChanged()\"\n placeholder=\"ex.: var(--md-sys-elevation-level2)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Borda</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.border\"\n (ngModelChange)=\"onSkinChanged()\"\n />\n </mat-form-field>\n <mat-form-field\n *ngIf=\"working.skin.type === 'glass'\"\n appearance=\"outline\"\n >\n <mat-label>Desfoque</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.backdropBlur\"\n (ngModelChange)=\"onSkinChanged()\"\n placeholder=\"ex.: 8px\"\n />\n </mat-form-field>\n <div *ngIf=\"working.skin.type === 'gradient-tile'\" class=\"g gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Degrad\u00C3\u00AA de</mat-label>\n <input\n matInput\n [ngModel]=\"working.skin.gradient.from || ''\"\n (ngModelChange)=\"onSkinGradientChanged('from', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Degrad\u00C3\u00AA at\u00C3\u00A9</mat-label>\n <input\n matInput\n [ngModel]=\"working.skin.gradient.to || ''\"\n (ngModelChange)=\"onSkinGradientChanged('to', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>\u00C3\u201Angulo</mat-label>\n <input\n matInput\n type=\"number\"\n [ngModel]=\"working.skin.gradient.angle ?? 135\"\n (ngModelChange)=\"onSkinGradientChanged('angle', $event)\"\n />\n </mat-form-field>\n </div>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>Classe CSS extra (skin.class)</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.class\"\n (ngModelChange)=\"onSkinChanged()\"\n placeholder=\"ex.: my-list-skin\"\n />\n </mat-form-field>\n\n <div\n *ngIf=\"working.skin.type === 'custom'\"\n class=\"g g-auto-220 gap-12 ai-end\"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Estilo inline (skin.inlineStyle)</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [(ngModel)]=\"working.skin.inlineStyle\"\n (ngModelChange)=\"onSkinChanged()\"\n [attr.placeholder]=\"':host{--p-list-radius: 1rem}'\"\n ></textarea>\n </mat-form-field>\n <div class=\"text-caption\">\n Exemplo de CSS por classe (adicione no seu styles global):\n <pre class=\"code-block\">\n.my-list-skin .item-card &#123;\n border-radius: 14px;\n border: 1px solid var(--md-sys-color-outline-variant);\n box-shadow: var(--md-sys-elevation-level2);\n&#125;\n.my-list-skin .mat-mdc-list-item .list-item-content &#123;\n backdrop-filter: blur(6px);\n&#125;</pre\n >\n </div>\n </div>\n </div>\n </mat-tab>\n</mat-tab-group>\n\n", styles: [".confirm-type{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;line-height:16px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant)}.confirm-type.danger{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.confirm-type.warning{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.confirm-type.info{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}:host{display:block;color:var(--md-sys-color-on-surface)}.list-editor-tabs{--editor-surface: var(--md-sys-color-surface-container-lowest);--editor-border: 1px solid var(--md-sys-color-outline-variant);--editor-radius: var(--md-sys-shape-corner-large, 16px);--editor-muted: var(--md-sys-color-on-surface-variant);--editor-accent: var(--md-sys-color-primary)}.editor-content{padding:16px;background:var(--editor-surface);border:var(--editor-border);border-radius:var(--editor-radius);display:grid;gap:12px}.editor-content .mat-mdc-form-field{width:100%;max-width:none;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var( --md-sys-color-outline-variant );--mdc-outlined-text-field-hover-outline-color: var( --md-sys-color-outline );--mdc-outlined-text-field-focus-outline-color: var( --md-sys-color-primary );--mdc-outlined-text-field-error-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-error-focus-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-error-hover-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-label-text-color: var( --md-sys-color-on-surface-variant );--mdc-outlined-text-field-input-text-color: var( --md-sys-color-on-surface );--mdc-outlined-text-field-supporting-text-color: var( --md-sys-color-on-surface-variant )}.editor-content .mat-mdc-form-field.w-full{max-width:none}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}.editor-split{grid-template-columns:minmax(0,1fr);align-items:start}.editor-main,.editor-aside{display:grid;gap:12px}.skin-preview-wrap{border-radius:calc(var(--editor-radius) - 4px);border:var(--editor-border);background:var(--md-sys-color-surface-container);padding:12px}.g{display:grid}.g-auto-220{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.g-auto-200{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}.g-1-auto{grid-template-columns:1fr auto}.row-flow{grid-auto-flow:column}.gap-6{gap:6px}.gap-8{gap:8px}.gap-12{gap:12px}.ai-center{align-items:center}.ai-end{align-items:end}.mt-12{margin-top:12px}.mb-8{margin-bottom:8px}.mb-6{margin-bottom:6px}.my-8{margin:8px 0}.subtitle{margin:8px 0 4px;color:var(--editor-muted);font-weight:500}.section-title{color:var(--editor-muted);font-weight:600}.chips-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.error{color:var(--md-sys-color-error);font-size:.85rem}.muted{color:var(--editor-muted)}.text-caption{color:var(--editor-muted);font-size:.8rem}:host ::ng-deep .mat-mdc-select-panel .option-icon{font-size:18px;margin-right:6px;vertical-align:middle}:host ::ng-deep .mat-mdc-select-panel .color-dot{width:10px;height:10px;border-radius:999px;display:inline-block;margin-right:6px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-outline)}:host ::ng-deep .mat-mdc-select-panel .color-primary{background:var(--md-sys-color-primary)}:host ::ng-deep .mat-mdc-select-panel .color-accent{background:var(--md-sys-color-tertiary)}:host ::ng-deep .mat-mdc-select-panel .color-warn{background:var(--md-sys-color-error)}:host ::ng-deep .mat-mdc-select-panel .color-default{background:var(--md-sys-color-outline)}@media(max-width:1024px){.editor-split{grid-template-columns:minmax(0,1fr)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i3$3.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$3.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$1.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i2$1.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i2$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i8.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i10.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i10.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i10.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i10.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i10.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatButtonToggleModule }, { kind: "directive", type: i11.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i11.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i12.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i3.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "ngmodule", type: MatMenuModule }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisListSkinPreviewComponent, selector: "praxis-list-skin-preview", inputs: ["config", "items", "theme"] }, { kind: "component", type: PraxisMetaEditorTextComponent, selector: "praxis-meta-editor-text", inputs: ["model", "setPipe"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorChipComponent, selector: "praxis-meta-editor-chip", inputs: ["model", "paletteOptions", "colorDotBackground", "isCustomColor", "enableCustomColor"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorRatingComponent, selector: "praxis-meta-editor-rating", inputs: ["model", "paletteOptions", "colorDotBackground", "isCustomColor", "enableCustomColor"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorCurrencyComponent, selector: "praxis-meta-editor-currency", inputs: ["model"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorDateComponent, selector: "praxis-meta-editor-date", inputs: ["model"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorIconComponent, selector: "praxis-meta-editor-icon", inputs: ["model", "paletteOptions", "colorDotBackground", "isCustomColor", "enableCustomColor"], outputs: ["change"] }, { kind: "component", type: PraxisMetaEditorImageComponent, selector: "praxis-meta-editor-image", inputs: ["model"], outputs: ["change"] }, { kind: "component", type: PraxisListJsonConfigEditorComponent, selector: "praxis-list-json-config-editor", inputs: ["document"], outputs: ["documentChange", "validationChange", "editorEvent"] }, { kind: "component", type: PdxColorPickerComponent, selector: "pdx-color-picker", inputs: ["actionsLayout", "activeView", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "clearButton", "disabledMode", "readonlyMode", "visible", "presentationMode", "fillMode", "format", "gradientSettings", "icon", "iconClass", "svgIcon", "paletteSettings", "popupSettings", "preview", "rounded", "size", "tabindex", "views", "maxRecent", "showRecent"], outputs: ["valueChange", "open", "close", "cancel", "activeViewChange", "activeColorClick", "focusEvent", "blurEvent"] }, { kind: "component", type: SurfaceOpenActionEditorComponent, selector: "praxis-surface-open-action-editor", inputs: ["value", "hostKind"], outputs: ["valueChange"] }] });
4985
5158
  }
4986
5159
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisListConfigEditor, decorators: [{
4987
5160
  type: Component,
4988
- args: [{ selector: 'praxis-list-config-editor', standalone: true, imports: [
5161
+ args: [{ selector: 'praxis-list-config-editor', standalone: true, providers: [providePraxisI18nConfig(SURFACE_OPEN_I18N_CONFIG)], imports: [
4989
5162
  CommonModule,
4990
5163
  FormsModule,
4991
5164
  MatTabsModule,
@@ -5012,7 +5185,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
5012
5185
  PraxisMetaEditorImageComponent,
5013
5186
  PraxisListJsonConfigEditorComponent,
5014
5187
  PdxColorPickerComponent,
5015
- ], template: "<mat-tab-group class=\"list-editor-tabs\">\n <mat-tab label=\"Dados\">\n <div class=\"editor-content\">\n <div class=\"g g-1-auto gap-8 ai-center\">\n <div class=\"muted\">\n Observa\u00E7\u00E3o: ajustes aplicados pelo assistente substituem o objeto de\n configura\u00E7\u00E3o inteiro.\n </div>\n <button\n mat-icon-button\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"O applyConfigFromAdapter n\u00E3o faz merge profundo. Garanta que o adapter envie a config completa.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </div>\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Recurso (API)</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.dataSource.resourcePath\"\n (ngModelChange)=\"onResourcePathChange($event)\"\n placeholder=\"ex.: users\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Endpoint do recurso (resourcePath).\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Query (JSON)</mat-label>\n <textarea\n matInput\n rows=\"3\"\n [(ngModel)]=\"queryJson\"\n (ngModelChange)=\"onQueryChanged($event)\"\n placeholder='ex.: &#123;\"status\":\"active\",\"department\":\"sales\"&#125;'\n ></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Opcional. Use JSON v\u00E1lido para filtros iniciais.\"\n *ngIf=\"!queryError\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error *ngIf=\"queryError\">{{ queryError }}</mat-error>\n </mat-form-field>\n <div class=\"g g-auto-220 gap-12 ai-end mt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Ordenar por</mat-label>\n <mat-select\n [(ngModel)]=\"sortField\"\n (ngModelChange)=\"updateSortConfig()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Campo base do recurso.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Dire\u00E7\u00E3o</mat-label>\n <mat-select\n [(ngModel)]=\"sortDir\"\n (ngModelChange)=\"updateSortConfig()\"\n >\n <mat-option value=\"asc\">Ascendente</mat-option>\n <mat-option value=\"desc\">Descendente</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"JSON\">\n <div class=\"editor-content\">\n <praxis-list-json-config-editor\n [document]=\"document\"\n (documentChange)=\"onJsonConfigChange($event)\"\n (validationChange)=\"onJsonValidationChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n >\n </praxis-list-json-config-editor>\n </div>\n </mat-tab>\n <mat-tab label=\"A\u00E7\u00F5es\">\n <div class=\"editor-content g gap-12\">\n <div class=\"g g-1-auto gap-8 ai-center\">\n <div class=\"muted\">\n Configure bot\u00F5es de a\u00E7\u00E3o por item (\u00EDcone, r\u00F3tulo, cor, visibilidade)\n </div>\n <button mat-flat-button color=\"primary\" (click)=\"addAction()\">\n Adicionar a\u00E7\u00E3o\n </button>\n </div>\n <div class=\"g g-1-auto gap-8 ai-center\">\n <mat-form-field appearance=\"outline\">\n <mat-label>A\u00E7\u00E3o global (Praxis)</mat-label>\n <mat-select\n [(ngModel)]=\"selectedGlobalActionId\"\n (ngModelChange)=\"onGlobalActionSelected($event)\"\n >\n <mat-option [value]=\"undefined\">-- Selecionar --</mat-option>\n <mat-option *ngFor=\"let ga of globalActionCatalog\" [value]=\"ga.id\">\n <mat-icon class=\"option-icon\">{{ ga.icon || \"bolt\" }}</mat-icon>\n {{ ga.label }}\n </mat-option>\n </mat-select>\n <mat-hint\n *ngIf=\"!globalActionCatalog.length\"\n class=\"text-caption muted\"\n >Nenhuma a\u00E7\u00E3o global registrada.</mat-hint\n >\n </mat-form-field>\n <div class=\"muted text-caption\">\n Selecione para adicionar com `command` global.\n </div>\n </div>\n <div\n *ngFor=\"let a of working.actions || []; let i = index\"\n class=\"g g-auto-200 gap-12 ai-end\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>ID</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.id\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo de a\u00E7\u00E3o</mat-label>\n <mat-select [(ngModel)]=\"a.kind\" (ngModelChange)=\"onActionsChanged()\">\n <mat-option value=\"icon\">\u00CDcone</mat-option>\n <mat-option value=\"button\">Bot\u00E3o</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>\u00CDcone</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.icon\"\n (ngModelChange)=\"onActionsChanged()\"\n placeholder=\"ex.: edit, delete\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Command (global)</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.command\"\n (ngModelChange)=\"onActionsChanged()\"\n placeholder=\"global:toast.success\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>R\u00F3tulo</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.label\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n </mat-form-field>\n <ng-container *ngIf=\"a.kind === 'button'\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Variante</mat-label>\n <mat-select\n [(ngModel)]=\"a.buttonVariant\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option value=\"stroked\">Contorno</mat-option>\n <mat-option value=\"raised\">Elevado</mat-option>\n <mat-option value=\"flat\">Preenchido</mat-option>\n </mat-select>\n </mat-form-field>\n </ng-container>\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor da a\u00E7\u00E3o</mat-label>\n <mat-select\n [(ngModel)]=\"a.color\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option *ngFor=\"let c of paletteOptions\" [value]=\"c.value\">\n <span\n class=\"color-dot\"\n [style.background]=\"colorDotBackground(c.value)\"\n ></span\n >{{ c.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <div\n class=\"g gap-8\"\n *ngIf=\"isCustomColor(a.color); else actionCustomBtn\"\n >\n <pdx-color-picker\n label=\"Cor personalizada\"\n [format]=\"'hex'\"\n [(ngModel)]=\"a.color\"\n (ngModelChange)=\"onActionsChanged()\"\n ></pdx-color-picker>\n </div>\n <ng-template #actionCustomBtn>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"enableCustomActionColor(a)\"\n >\n Usar cor personalizada\n </button>\n </ng-template>\n <mat-form-field appearance=\"outline\">\n <mat-label>Payload da a\u00E7\u00E3o</mat-label>\n <mat-select\n [(ngModel)]=\"a.emitPayload\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option [value]=\"undefined\">Padr\u00E3o</mat-option>\n <mat-option value=\"item\">item</mat-option>\n <mat-option value=\"id\">id</mat-option>\n <mat-option value=\"value\">value</mat-option>\n </mat-select>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"emitPayload\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"col-span-2\">\n <mat-label\n >Exibir quando (ex.: &#36;&#123;item.status&#125; ==\n 'done')</mat-label\n >\n <input\n matInput\n [(ngModel)]=\"a.showIf\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Sintaxe suportada: &#34;&#36;{item.campo} == &#39;valor&#39;&#34;. Express\u00F5es avan\u00E7adas n\u00E3o s\u00E3o avaliadas.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"g row-flow gap-8 ai-center\">\n <button\n *ngIf=\"(a.kind || 'icon') === 'icon'\"\n mat-icon-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n >\n <mat-icon\n [praxisIcon]=\"a.icon || 'bolt'\"\n [style.cssText]=\"iconStyle(a.color)\"\n ></mat-icon>\n </button>\n <ng-container *ngIf=\"a.kind === 'button'\">\n <button\n *ngIf=\"a.buttonVariant === 'stroked'\"\n mat-stroked-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'stroked')\"\n >\n {{ a.label || a.id || \"A\u00E7\u00E3o\" }}\n </button>\n <button\n *ngIf=\"a.buttonVariant === 'raised'\"\n mat-raised-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'raised')\"\n >\n {{ a.label || a.id || \"A\u00E7\u00E3o\" }}\n </button>\n <button\n *ngIf=\"!a.buttonVariant || a.buttonVariant === 'flat'\"\n mat-flat-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'flat')\"\n >\n {{ a.label || a.id || \"A\u00E7\u00E3o\" }}\n </button>\n </ng-container>\n <span class=\"muted\">Pr\u00E9-visualiza\u00E7\u00E3o</span>\n </div>\n <div class=\"flex-end\">\n <button mat-button color=\"warn\" (click)=\"removeAction(i)\">\n Remover\n </button>\n </div>\n <div class=\"g gap-8 col-span-2\" *ngIf=\"a.command\">\n <mat-slide-toggle\n [(ngModel)]=\"a.showLoading\"\n (ngModelChange)=\"onActionsChanged()\"\n >Mostrar loading</mat-slide-toggle\n >\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Confirma\u00E7\u00E3o</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <div class=\"g row-flow gap-8 ai-center\">\n <span class=\"text-caption muted\">Tipo</span>\n <mat-button-toggle-group\n [value]=\"a.confirmation?.type || ''\"\n (change)=\"applyConfirmationPreset(a, $event.value)\"\n >\n <mat-button-toggle value=\"\">Padr\u00E3o</mat-button-toggle>\n <mat-button-toggle value=\"danger\">Danger</mat-button-toggle>\n <mat-button-toggle value=\"warning\">Warning</mat-button-toggle>\n <mat-button-toggle value=\"info\">Info</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>T\u00EDtulo</mat-label>\n <input\n matInput\n [ngModel]=\"a.confirmation?.title\"\n (ngModelChange)=\"setConfirmationField(a, 'title', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mensagem</mat-label>\n <input\n matInput\n [ngModel]=\"a.confirmation?.message\"\n (ngModelChange)=\"setConfirmationField(a, 'message', $event)\"\n />\n </mat-form-field>\n <div class=\"g gap-6\">\n <div class=\"text-caption muted\">Pr\u00E9via</div>\n <div class=\"text-caption\">\n <strong>{{\n a.confirmation?.title || \"Confirmar a\u00E7\u00E3o\"\n }}</strong>\n </div>\n <div class=\"text-caption muted\">\n {{\n a.confirmation?.message ||\n \"Tem certeza que deseja continuar?\"\n }}\n </div>\n <div class=\"text-caption\">\n <span\n class=\"confirm-type\"\n [ngClass]=\"a.confirmation?.type || 'default'\"\n >Tipo: {{ a.confirmation?.type || \"padr\u00E3o\" }}</span\n >\n </div>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!a.confirmation?.title && !a.confirmation?.message\"\n >\n Defina um t\u00EDtulo ou mensagem para a confirma\u00E7\u00E3o.\n </div>\n </div>\n </div>\n </mat-expansion-panel>\n <mat-form-field appearance=\"outline\" class=\"col-span-2\">\n <mat-label>Payload (JSON/Template)</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [(ngModel)]=\"a.globalPayload\"\n (ngModelChange)=\"onActionsChanged()\"\n placeholder='{\"message\":\"${item.name} favoritado\"}'\n ></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"globalPayloadSchemaTooltip(a)\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error *ngIf=\"isGlobalPayloadInvalid(a.globalPayload)\"\n >JSON inv\u00E1lido</mat-error\n >\n </mat-form-field>\n <div class=\"g row-flow gap-8 ai-center\">\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"applyGlobalPayloadExample(a)\"\n >\n Inserir exemplo\n </button>\n <span class=\"muted text-caption\">{{\n globalPayloadExampleHint(a)\n }}</span>\n </div>\n <mat-slide-toggle\n [(ngModel)]=\"a.emitLocal\"\n (ngModelChange)=\"onActionsChanged()\"\n >Emitir evento local tamb\u00E9m</mat-slide-toggle\n >\n </div>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"Layout\">\n <div class=\"editor-content grid gap-3\">\n <div class=\"preset-row g row-flow gap-8\">\n <button mat-stroked-button (click)=\"applyLayoutPreset('tiles-modern')\">\n Preset Tiles Moderno\n </button>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Variante</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.variant\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"list\">Lista</mat-option>\n <mat-option value=\"cards\">Cards</mat-option>\n <mat-option value=\"tiles\">Tiles</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Modelo</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.model\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <ng-container\n *ngIf=\"working.layout.variant === 'list'; else cardModels\"\n >\n <mat-option value=\"standard\">Padr\u00E3o</mat-option>\n <mat-option value=\"media\">M\u00EDdia \u00E0 esquerda</mat-option>\n <mat-option value=\"hotel\">Hotel (m\u00EDdia grande)</mat-option>\n </ng-container>\n <ng-template #cardModels>\n <ng-container\n *ngIf=\"working.layout.variant === 'tiles'; else cardsOnly\"\n >\n <mat-option value=\"standard\">Tile padr\u00E3o</mat-option>\n <mat-option value=\"media\">Tile com m\u00EDdia</mat-option>\n <mat-option value=\"hotel\">Tile hotel</mat-option>\n </ng-container>\n <ng-template #cardsOnly>\n <mat-option value=\"standard\">Padr\u00E3o</mat-option>\n <mat-option value=\"media\">Card com m\u00EDdia</mat-option>\n <mat-option value=\"hotel\">Hotel</mat-option>\n </ng-template>\n </ng-template>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Linhas</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.lines\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option [value]=\"1\">1</mat-option>\n <mat-option [value]=\"2\">2</mat-option>\n <mat-option [value]=\"3\">3</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Itens por p\u00E1gina</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"1\"\n [(ngModel)]=\"working.layout.pageSize\"\n (ngModelChange)=\"onPageSizeChange($event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Densidade</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.density\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"default\">Padr\u00E3o</mat-option>\n <mat-option value=\"comfortable\">Confort\u00E1vel</mat-option>\n <mat-option value=\"compact\">Compacta</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Espa\u00E7amento entre itens</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.itemSpacing\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"none\">Sem espa\u00E7o extra</mat-option>\n <mat-option value=\"tight\">Justo</mat-option>\n <mat-option value=\"default\">Padr\u00E3o</mat-option>\n <mat-option value=\"relaxed\">Relaxado</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"working.layout.variant !== 'tiles'\"\n >\n <mat-label>Divisores</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.dividers\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"none\">Sem</mat-option>\n <mat-option value=\"between\">Entre grupos</mat-option>\n <mat-option value=\"all\">Todos</mat-option>\n </mat-select>\n </mat-form-field>\n <ng-container *ngIf=\"fields.length > 0; else groupByText\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Agrupar por</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.groupBy\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option [value]=\"\">Nenhum</mat-option>\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n </ng-container>\n <ng-template #groupByText>\n <mat-form-field appearance=\"outline\">\n <mat-label>Agrupar por</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.layout.groupBy\"\n (ngModelChange)=\"onLayoutChanged()\"\n placeholder=\"ex.: departamento\"\n />\n </mat-form-field>\n </ng-template>\n <mat-slide-toggle\n [(ngModel)]=\"working.layout.stickySectionHeader\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n Header de se\u00E7\u00E3o fixo\n </mat-slide-toggle>\n <mat-slide-toggle\n [(ngModel)]=\"working.layout.virtualScroll\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n Scroll virtual\n </mat-slide-toggle>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">Ferramentas da lista</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showSearch\"\n (ngModelChange)=\"onUiChanged()\"\n >Mostrar busca</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showSort\"\n (ngModelChange)=\"onUiChanged()\"\n >Mostrar ordenar</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showRange\"\n (ngModelChange)=\"onUiChanged()\"\n >Mostrar faixa X\u2013Y de Total</mat-slide-toggle\n >\n </div>\n <div\n class=\"g g-auto-220 gap-12 ai-end mt-12\"\n *ngIf=\"working.ui?.showSearch\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo para buscar</mat-label>\n <mat-select\n [(ngModel)]=\"working.ui.searchField\"\n (ngModelChange)=\"onUiChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Placeholder da busca</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.ui.searchPlaceholder\"\n (ngModelChange)=\"onUiChanged()\"\n placeholder=\"ex.: Buscar por t\u00EDtulo\"\n />\n </mat-form-field>\n </div>\n <div class=\"mt-12\" *ngIf=\"working.ui?.showSort\">\n <div class=\"g g-1-auto ai-center gap-8\">\n <div class=\"muted\">Op\u00E7\u00F5es de ordena\u00E7\u00E3o (r\u00F3tulo \u2192 campo+dire\u00E7\u00E3o)</div>\n <button mat-flat-button color=\"primary\" (click)=\"addUiSortRow()\">\n Adicionar op\u00E7\u00E3o\n </button>\n </div>\n <div\n class=\"g g-auto-220 gap-12 ai-end mt-12\"\n *ngFor=\"let r of uiSortRows; let i = index\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>R\u00F3tulo</mat-label>\n <input\n matInput\n [(ngModel)]=\"r.label\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n placeholder=\"ex.: Mais recentes\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"r.field\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Dire\u00E7\u00E3o</mat-label>\n <mat-select\n [(ngModel)]=\"r.dir\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n >\n <mat-option value=\"desc\">Descendente</mat-option>\n <mat-option value=\"asc\">Ascendente</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"error\" *ngIf=\"isUiSortRowDuplicate(i)\">\n Op\u00E7\u00E3o duplicada (campo+dire\u00E7\u00E3o)\n </div>\n <div class=\"flex-end\">\n <button mat-button color=\"warn\" (click)=\"removeUiSortRow(i)\">\n Remover\n </button>\n </div>\n </div>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"Conte\u00FAdo\">\n <div class=\"editor-content\">\n <div class=\"editor-main\">\n <mat-accordion multi>\n <!-- Primary -->\n <mat-expansion-panel [expanded]=\"true\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingPrimary.type) }}</mat-icon>\n <span>Primary (T\u00EDtulo)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingPrimary.field || \"N\u00E3o mapeado\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'name';\n onMappingChanged()\n \"\n >\n Nome\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'title';\n onMappingChanged()\n \"\n >\n T\u00EDtulo\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'name';\n mappingSecondary.type = 'text';\n mappingSecondary.field = 'role';\n onMappingChanged()\n \"\n >\n Nome + Papel\n </button>\n </div>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingPrimary.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingPrimary.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of primaryTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n @switch (mappingPrimary.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingPrimary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingPrimary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingPrimary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingPrimary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n }\n\n <!-- Advanced -->\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header>\n <mat-panel-title>Formata\u00E7\u00E3o e Estilo</mat-panel-title>\n </mat-expansion-panel-header>\n <div class=\"g gap-12 pt-12\">\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"\n mappingPrimary.type === 'text' ||\n mappingPrimary.type === 'html'\n \"\n >\n <mat-label>Classe CSS</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"\n mappingPrimary.type === 'text' ||\n mappingPrimary.type === 'html'\n \"\n >\n <mat-label>Estilo Inline</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Classe CSS</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Estilo Inline</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Secondary -->\n <mat-expansion-panel [expanded]=\"!!mappingSecondary.field\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingSecondary.type) }}</mat-icon>\n <span>Secondary (Resumo)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingSecondary.field || \"N\u00E3o mapeado\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'text';\n mappingSecondary.field = 'subtitle';\n onMappingChanged()\n \"\n >\n Subt\u00EDtulo\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'date';\n mappingSecondary.field = 'hireDate';\n mappingSecondary.dateStyle = 'short';\n onMappingChanged()\n \"\n >\n Data curta\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'currency';\n mappingSecondary.field = 'salary';\n mappingSecondary.currencyCode = 'BRL';\n mappingSecondary.locale = 'pt-BR';\n onMappingChanged()\n \"\n >\n Sal\u00E1rio\n </button>\n </div>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSecondary.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSecondary.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of secondaryTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n @switch (mappingSecondary.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingSecondary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingSecondary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingSecondary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingSecondary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header>\n <mat-panel-title>Formata\u00E7\u00E3o e Estilo</mat-panel-title>\n </mat-expansion-panel-header>\n <div class=\"g gap-12 pt-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe CSS</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSecondary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Estilo Inline</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSecondary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <mat-expansion-panel\n [expanded]=\"!!mappingMeta.field || mappingMetaFields.length > 0\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingMeta.type || \"text\")\n }}</mat-icon>\n <span>Meta (Detalhe/Lateral)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>\n {{\n mappingMetaFields.length\n ? \"Campo composto (\" + mappingMetaFields.length + \")\"\n : mappingMeta.field || \"N\u00E3o mapeado\"\n }}\n </mat-panel-description>\n </mat-expansion-panel-header>\n\n <div class=\"g gap-12\">\n <!-- Composition Mode Toggle -->\n <div class=\"g g-1-1 gap-12 p-12 bg-subtle rounded\">\n <div class=\"text-caption muted\">Modo de composi\u00E7\u00E3o</div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Campos para compor (Multi-select)</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMetaFields\"\n multiple\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <div\n class=\"g g-1-1 ai-center gap-12\"\n *ngIf=\"mappingMetaFields.length\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>Separador</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMetaSeparator\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-slide-toggle\n [(ngModel)]=\"mappingMetaWrapSecondInParens\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n (Seg) entre par\u00EAnteses\n </mat-slide-toggle>\n </div>\n </div>\n\n <!-- Single Field Mode (if no composition) -->\n <div class=\"g g-1-1 gap-12\" *ngIf=\"!mappingMetaFields.length\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo \u00DAnico</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option [value]=\"undefined\">-- Nenhum --</mat-option>\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of metaTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- Type configuration (pluggable editors) -->\n @switch (mappingMeta.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingMeta\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingMeta\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <praxis-meta-editor-image\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <!-- Advanced -->\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Op\u00E7\u00F5es avan\u00E7adas</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Posi\u00E7\u00E3o</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.placement\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option value=\"side\">Lateral (Direita)</mat-option>\n <mat-option value=\"line\">Na linha (Abaixo)</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Classe CSS</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMeta.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Estilo</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMeta.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n <!-- Trailing -->\n <mat-expansion-panel [expanded]=\"!!mappingTrailing.field\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingTrailing.type || \"text\")\n }}</mat-icon>\n <span>Trailing (Direita)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingTrailing.field || \"N\u00E3o mapeado\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingTrailing.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option [value]=\"undefined\">-- Nenhum --</mat-option>\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingTrailing.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of trailingTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'chip';\n mappingTrailing.chipColor = 'primary';\n mappingTrailing.chipVariant = 'filled';\n mappingTrailing.field = 'status';\n onMappingChanged()\n \"\n >\n Status Chip\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'icon';\n mappingTrailing.field = 'status';\n mappingTrailing.iconColor = 'primary';\n onMappingChanged()\n \"\n >\n Status \u00CDcone\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'currency';\n mappingTrailing.field = 'price';\n mappingTrailing.currencyCode = 'BRL';\n mappingTrailing.locale = 'pt-BR';\n onMappingChanged()\n \"\n >\n Pre\u00E7o\n </button>\n </div>\n\n @switch (mappingTrailing.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingTrailing\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingTrailing\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>URL / Expr</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingTrailing.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n placeholder=\"https://... ou ${item.imageUrl}\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Use URL absoluta/relativa ou express\u00E3o ${item.campo}.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error\n *ngIf=\"\n isImageUrlRequiredInvalid(mappingTrailing.imageUrl)\n \"\n >URL/expr obrigat\u00F3ria</mat-error\n >\n </mat-form-field>\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!mappingTrailing.imageUrl\"\n >\n Defina a URL/expr para renderizar a imagem.\n </div>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Estilo</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingTrailing.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Style</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingTrailing.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Leading -->\n <mat-expansion-panel\n [expanded]=\"\n !!mappingLeading.field ||\n (mappingLeading.type === 'icon' && !!mappingLeading.icon) ||\n (mappingLeading.type === 'image' && !!mappingLeading.imageUrl)\n \"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingLeading.type) }}</mat-icon>\n <span>Leading (Esquerda)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>\n {{\n mappingLeading.type === \"icon\"\n ? mappingLeading.icon || \"\u00CDcone est\u00E1tico\"\n : mappingLeading.field ||\n (mappingLeading.imageUrl\n ? \"Imagem est\u00E1tica\"\n : \"N\u00E3o mapeado\")\n }}\n </mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingLeading.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of leadingTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <!-- Field (only if not static icon/image, though user might want dynamic) -->\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"\n mappingLeading.type !== 'icon' &&\n mappingLeading.type !== 'image'\n \"\n >\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingLeading.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'icon';\n mappingLeading.icon = 'person';\n mappingLeading.iconColor = 'primary';\n onMappingChanged()\n \"\n >\n Avatar \u00CDcone\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'image';\n mappingLeading.imageUrl = 'https://placehold.co/64x64';\n mappingLeading.imageAlt = 'Avatar';\n mappingLeading.badgeText = '${item.status}';\n onMappingChanged()\n \"\n >\n Avatar Imagem + Badge\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'chip';\n mappingLeading.field = 'tag';\n mappingLeading.chipColor = 'accent';\n mappingLeading.chipVariant = 'filled';\n onMappingChanged()\n \"\n >\n Chip Tag\n </button>\n </div>\n\n <!-- Icon Specific -->\n <div\n class=\"g g-1-auto gap-12 ai-center\"\n *ngIf=\"mappingLeading.type === 'icon'\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>\u00CDcone</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.icon\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <button mat-icon-button matSuffix (click)=\"pickLeadingIcon()\">\n <mat-icon>search</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"text-caption muted\">\n Use pipe <code>|iconMap</code> no extra pipe para din\u00E2mico\n </div>\n </div>\n <div *ngIf=\"mappingLeading.type === 'icon'\">\n <praxis-meta-editor-icon\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n </div>\n\n <!-- Image Specific -->\n <div\n class=\"g g-1-1 gap-12\"\n *ngIf=\"mappingLeading.type === 'image'\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>URL da Imagem</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n placeholder=\"https://... ou ${item.imageUrl}\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Use URL absoluta/relativa ou express\u00E3o ${item.campo}.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error\n *ngIf=\"isImageUrlRequiredInvalid(mappingLeading.imageUrl)\"\n >URL/expr obrigat\u00F3ria</mat-error\n >\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Alt Text</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.imageAlt\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Badge Texto</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.badgeText\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n\n @switch (mappingLeading.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingLeading\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingLeading\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Estilo</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingLeading.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Style</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingLeading.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Features -->\n <mat-expansion-panel\n [expanded]=\"featuresVisible && features.length > 0\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>view_list</mat-icon>\n <span>Recursos (Features)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description\n >{{ features.length }} item(s)</mat-panel-description\n >\n </mat-expansion-panel-header>\n\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-12 ai-center\">\n <mat-slide-toggle\n [(ngModel)]=\"featuresVisible\"\n (ngModelChange)=\"onFeaturesChanged()\"\n >Ativar recursos</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"featuresSyncWithMeta\"\n (ngModelChange)=\"onMappingChanged()\"\n >Sincronizar com Meta</mat-slide-toggle\n >\n <span class=\"flex-1\"></span>\n <mat-button-toggle-group\n [(ngModel)]=\"featuresMode\"\n (change)=\"onFeaturesChanged()\"\n appearance=\"legacy\"\n >\n <mat-button-toggle value=\"icons+labels\"\n ><mat-icon>view_list</mat-icon></mat-button-toggle\n >\n <mat-button-toggle value=\"icons-only\"\n ><mat-icon>more_horiz</mat-icon></mat-button-toggle\n >\n </mat-button-toggle-group>\n </div>\n\n <div\n *ngFor=\"let f of features; let i = index\"\n class=\"g g-auto-1 gap-8 ai-center p-8 border rounded mb-2\"\n >\n <button mat-icon-button (click)=\"pickFeatureIcon(i)\">\n <mat-icon>{{ f.icon || \"search\" }}</mat-icon>\n </button>\n <mat-form-field\n appearance=\"outline\"\n class=\"dense-form-field no-sub\"\n >\n <input\n matInput\n [(ngModel)]=\"f.expr\"\n (ngModelChange)=\"onFeaturesChanged()\"\n placeholder=\"Expr/Texto\"\n />\n </mat-form-field>\n <button mat-icon-button color=\"warn\" (click)=\"removeFeature(i)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n <button mat-button color=\"primary\" (click)=\"addFeature()\">\n <mat-icon>add</mat-icon> Adicionar recurso\n </button>\n </div>\n </mat-expansion-panel>\n <!-- Section Header -->\n <mat-expansion-panel [expanded]=\"!!mappingSectionHeader.expr\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingSectionHeader.type)\n }}</mat-icon>\n <span>Cabe\u00E7alho de Se\u00E7\u00E3o</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingSectionHeader.expr || \"N\u00E3o configurado\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSectionHeader.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of sectionHeaderTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Express\u00E3o (item.key)</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingSectionHeader.expr\"\n (ngModelChange)=\"onMappingChanged()\"\n placeholder=\"item.key\"\n />\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSectionHeader.type = 'text';\n mappingSectionHeader.expr = '${item.key}';\n onMappingChanged()\n \"\n >\n Texto padr\u00E3o\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSectionHeader.type = 'chip';\n mappingSectionHeader.chipColor = 'primary';\n mappingSectionHeader.chipVariant = 'filled';\n mappingSectionHeader.expr = '${item.key}';\n onMappingChanged()\n \"\n >\n Chip padr\u00E3o\n </button>\n </div>\n\n @switch (mappingSectionHeader.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingSectionHeader\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingSectionHeader\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>URL Imagem</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingSectionHeader.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <mat-error\n *ngIf=\"\n isImageUrlRequiredInvalid(\n mappingSectionHeader.imageUrl\n )\n \"\n >URL/expr obrigat\u00F3ria</mat-error\n >\n </mat-form-field>\n </div>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!mappingSectionHeader.imageUrl\"\n >\n Defina a URL/expr para renderizar a imagem.\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingSectionHeader\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Estilo</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSectionHeader.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Style</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSectionHeader.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Empty State -->\n <mat-expansion-panel [expanded]=\"!!mappingEmptyState.expr\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>inbox</mat-icon>\n <span>Estado Vazio</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingEmptyState.expr || \"Padr\u00E3o\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingEmptyState.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of emptyStateTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mensagem / Expr</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingEmptyState.expr\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingEmptyState.type = 'text';\n mappingEmptyState.expr = 'Nenhum item dispon\u00EDvel';\n onMappingChanged()\n \"\n >\n Mensagem padr\u00E3o\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingEmptyState.type = 'image';\n mappingEmptyState.imageUrl = '/list-empty-state.svg';\n mappingEmptyState.imageAlt = 'Sem resultados';\n onMappingChanged()\n \"\n >\n Imagem padr\u00E3o\n </button>\n </div>\n\n @switch (mappingEmptyState.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingEmptyState\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingEmptyState\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>URL Imagem</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <mat-error\n *ngIf=\"\n isImageUrlRequiredInvalid(mappingEmptyState.imageUrl)\n \"\n >URL/expr obrigat\u00F3ria</mat-error\n >\n </mat-form-field>\n </div>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!mappingEmptyState.imageUrl\"\n >\n Defina a URL/expr para renderizar a imagem.\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingEmptyState\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Estilo</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Style</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n </mat-accordion>\n\n <button mat-flat-button color=\"primary\" (click)=\"applyTemplate()\">\n Aplicar mapeamento\n </button>\n <button\n mat-button\n (click)=\"inferFromFields()\"\n [disabled]=\"!fields.length\"\n >\n Inferir do schema\n </button>\n <div class=\"g g-auto-220 gap-12 ai-end mt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Skeleton (quantidade)</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"0\"\n [(ngModel)]=\"skeletonCountInput\"\n (ngModelChange)=\"onSkeletonChanged($event)\"\n />\n </mat-form-field>\n </div>\n\n <div class=\"g gap-12 mt-12\">\n <div class=\"g row-flow gap-8 ai-center\">\n <span class=\"section-title mat-subtitle-1\">Pr\u00E9via de tema</span>\n <mat-button-toggle-group\n [(ngModel)]=\"skinPreviewTheme\"\n (change)=\"onSkinChanged()\"\n appearance=\"legacy\"\n >\n <mat-button-toggle [value]=\"'light'\">Claro</mat-button-toggle>\n <mat-button-toggle [value]=\"'dark'\">Escuro</mat-button-toggle>\n <mat-button-toggle [value]=\"'grid'\">Grade</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <div class=\"skin-preview-wrap\">\n <praxis-list-skin-preview\n [config]=\"working\"\n [items]=\"previewData\"\n [theme]=\"skinPreviewTheme\"\n ></praxis-list-skin-preview>\n </div>\n </div>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"i18n/A11y\">\n <div\n class=\"editor-content grid gap-3\"\n *ngIf=\"working?.a11y && working?.events\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>Locale padr\u00E3o</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.i18n.locale\"\n (ngModelChange)=\"markDirty()\"\n placeholder=\"ex.: pt-BR\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Moeda padr\u00E3o</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.i18n.currency\"\n (ngModelChange)=\"markDirty()\"\n placeholder=\"ex.: BRL\"\n />\n </mat-form-field>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">Acessibilidade</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-form-field appearance=\"outline\">\n <mat-label>aria-label</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.a11y!.ariaLabel\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>aria-labelledby</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.a11y!.ariaLabelledBy\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n </div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-slide-toggle\n [(ngModel)]=\"working!.a11y!.highContrast\"\n (ngModelChange)=\"markDirty()\"\n >Alto contraste</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working!.a11y!.reduceMotion\"\n (ngModelChange)=\"markDirty()\"\n >Reduzir movimento</mat-slide-toggle\n >\n </div>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">Eventos</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-form-field appearance=\"outline\">\n <mat-label>itemClick</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.itemClick\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>actionClick</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.actionClick\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>selectionChange</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.selectionChange\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>loaded</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.loaded\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"Sele\u00E7\u00E3o\">\n <div class=\"editor-content grid gap-3\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Modo</mat-label>\n <mat-select\n [(ngModel)]=\"working.selection.mode\"\n (ngModelChange)=\"onSelectionChanged()\"\n >\n <mat-option value=\"none\">Sem sele\u00E7\u00E3o</mat-option>\n <mat-option value=\"single\">\u00DAnica</mat-option>\n <mat-option value=\"multiple\">M\u00FAltipla</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Nome no formul\u00E1rio</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.formControlName\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"formControlName\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Caminho no formul\u00E1rio</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.formControlPath\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"formControlPath\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Comparar por (campo)</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.compareBy\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Chave unica do item.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Retorno</mat-label>\n <mat-select\n [(ngModel)]=\"working.selection.return\"\n (ngModelChange)=\"onSelectionChanged()\"\n >\n <mat-option value=\"value\">value</mat-option>\n <mat-option value=\"item\">item</mat-option>\n <mat-option value=\"id\">id</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </mat-tab>\n <mat-tab label=\"Apar\u00EAncia\">\n <div class=\"editor-content grid gap-3\">\n <div class=\"preset-row g row-flow gap-8\">\n <button mat-button (click)=\"applySkinPreset('pill-soft')\">\n Pill Soft\n </button>\n <button mat-button (click)=\"applySkinPreset('gradient-tile')\">\n Gradient Tile\n </button>\n <button mat-button (click)=\"applySkinPreset('glass')\">Glass</button>\n <button mat-button (click)=\"applySkinPreset('elevated')\">\n Elevated\n </button>\n <button mat-button (click)=\"applySkinPreset('outline')\">Outline</button>\n <button mat-button (click)=\"applySkinPreset('flat')\">Flat</button>\n <button mat-button (click)=\"applySkinPreset('neumorphism')\">\n Neumorphism\n </button>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Estilo</mat-label>\n <mat-select\n [(ngModel)]=\"working.skin.type\"\n (ngModelChange)=\"onSkinTypeChanged($event)\"\n >\n <mat-option value=\"pill-soft\">Pill Soft</mat-option>\n <mat-option value=\"gradient-tile\">Gradient Tile</mat-option>\n <mat-option value=\"glass\">Glass</mat-option>\n <mat-option value=\"elevated\">Elevated</mat-option>\n <mat-option value=\"outline\">Outline</mat-option>\n <mat-option value=\"flat\">Flat</mat-option>\n <mat-option value=\"neumorphism\">Neumorphism</mat-option>\n <mat-option value=\"custom\">Custom</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Raio</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.radius\"\n (ngModelChange)=\"onSkinChanged()\"\n placeholder=\"ex.: 1.25rem\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Sombra</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.shadow\"\n (ngModelChange)=\"onSkinChanged()\"\n placeholder=\"ex.: var(--md-sys-elevation-level2)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Borda</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.border\"\n (ngModelChange)=\"onSkinChanged()\"\n />\n </mat-form-field>\n <mat-form-field\n *ngIf=\"working.skin.type === 'glass'\"\n appearance=\"outline\"\n >\n <mat-label>Desfoque</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.backdropBlur\"\n (ngModelChange)=\"onSkinChanged()\"\n placeholder=\"ex.: 8px\"\n />\n </mat-form-field>\n <div *ngIf=\"working.skin.type === 'gradient-tile'\" class=\"g gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Degrad\u00EA de</mat-label>\n <input\n matInput\n [ngModel]=\"working.skin.gradient.from || ''\"\n (ngModelChange)=\"onSkinGradientChanged('from', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Degrad\u00EA at\u00E9</mat-label>\n <input\n matInput\n [ngModel]=\"working.skin.gradient.to || ''\"\n (ngModelChange)=\"onSkinGradientChanged('to', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>\u00C2ngulo</mat-label>\n <input\n matInput\n type=\"number\"\n [ngModel]=\"working.skin.gradient.angle ?? 135\"\n (ngModelChange)=\"onSkinGradientChanged('angle', $event)\"\n />\n </mat-form-field>\n </div>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>Classe CSS extra (skin.class)</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.class\"\n (ngModelChange)=\"onSkinChanged()\"\n placeholder=\"ex.: my-list-skin\"\n />\n </mat-form-field>\n\n <div\n *ngIf=\"working.skin.type === 'custom'\"\n class=\"g g-auto-220 gap-12 ai-end\"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Estilo inline (skin.inlineStyle)</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [(ngModel)]=\"working.skin.inlineStyle\"\n (ngModelChange)=\"onSkinChanged()\"\n [attr.placeholder]=\"':host{--p-list-radius: 1rem}'\"\n ></textarea>\n </mat-form-field>\n <div class=\"text-caption\">\n Exemplo de CSS por classe (adicione no seu styles global):\n <pre class=\"code-block\">\n.my-list-skin .item-card &#123;\n border-radius: 14px;\n border: 1px solid var(--md-sys-color-outline-variant);\n box-shadow: var(--md-sys-elevation-level2);\n&#125;\n.my-list-skin .mat-mdc-list-item .list-item-content &#123;\n backdrop-filter: blur(6px);\n&#125;</pre\n >\n </div>\n </div>\n </div>\n </mat-tab>\n</mat-tab-group>\n", styles: [".confirm-type{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;line-height:16px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant)}.confirm-type.danger{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.confirm-type.warning{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.confirm-type.info{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}:host{display:block;color:var(--md-sys-color-on-surface)}.list-editor-tabs{--editor-surface: var(--md-sys-color-surface-container-lowest);--editor-border: 1px solid var(--md-sys-color-outline-variant);--editor-radius: var(--md-sys-shape-corner-large, 16px);--editor-muted: var(--md-sys-color-on-surface-variant);--editor-accent: var(--md-sys-color-primary)}.editor-content{padding:16px;background:var(--editor-surface);border:var(--editor-border);border-radius:var(--editor-radius);display:grid;gap:12px}.editor-content .mat-mdc-form-field{width:100%;max-width:none;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var( --md-sys-color-outline-variant );--mdc-outlined-text-field-hover-outline-color: var( --md-sys-color-outline );--mdc-outlined-text-field-focus-outline-color: var( --md-sys-color-primary );--mdc-outlined-text-field-error-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-error-focus-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-error-hover-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-label-text-color: var( --md-sys-color-on-surface-variant );--mdc-outlined-text-field-input-text-color: var( --md-sys-color-on-surface );--mdc-outlined-text-field-supporting-text-color: var( --md-sys-color-on-surface-variant )}.editor-content .mat-mdc-form-field.w-full{max-width:none}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}.editor-split{grid-template-columns:minmax(0,1fr);align-items:start}.editor-main,.editor-aside{display:grid;gap:12px}.skin-preview-wrap{border-radius:calc(var(--editor-radius) - 4px);border:var(--editor-border);background:var(--md-sys-color-surface-container);padding:12px}.g{display:grid}.g-auto-220{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.g-auto-200{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}.g-1-auto{grid-template-columns:1fr auto}.row-flow{grid-auto-flow:column}.gap-6{gap:6px}.gap-8{gap:8px}.gap-12{gap:12px}.ai-center{align-items:center}.ai-end{align-items:end}.mt-12{margin-top:12px}.mb-8{margin-bottom:8px}.mb-6{margin-bottom:6px}.my-8{margin:8px 0}.subtitle{margin:8px 0 4px;color:var(--editor-muted);font-weight:500}.section-title{color:var(--editor-muted);font-weight:600}.chips-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.error{color:var(--md-sys-color-error);font-size:.85rem}.muted{color:var(--editor-muted)}.text-caption{color:var(--editor-muted);font-size:.8rem}:host ::ng-deep .mat-mdc-select-panel .option-icon{font-size:18px;margin-right:6px;vertical-align:middle}:host ::ng-deep .mat-mdc-select-panel .color-dot{width:10px;height:10px;border-radius:999px;display:inline-block;margin-right:6px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-outline)}:host ::ng-deep .mat-mdc-select-panel .color-primary{background:var(--md-sys-color-primary)}:host ::ng-deep .mat-mdc-select-panel .color-accent{background:var(--md-sys-color-tertiary)}:host ::ng-deep .mat-mdc-select-panel .color-warn{background:var(--md-sys-color-error)}:host ::ng-deep .mat-mdc-select-panel .color-default{background:var(--md-sys-color-outline)}@media(max-width:1024px){.editor-split{grid-template-columns:minmax(0,1fr)}}\n"] }]
5188
+ SurfaceOpenActionEditorComponent,
5189
+ ], template: "<mat-tab-group class=\"list-editor-tabs\">\n <mat-tab label=\"Dados\">\n <div class=\"editor-content\">\n <div class=\"g g-1-auto gap-8 ai-center\">\n <div class=\"muted\">\n Observa\u00C3\u00A7\u00C3\u00A3o: ajustes aplicados pelo assistente substituem o objeto de\n configura\u00C3\u00A7\u00C3\u00A3o inteiro.\n </div>\n <button\n mat-icon-button\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"O applyConfigFromAdapter n\u00C3\u00A3o faz merge profundo. Garanta que o adapter envie a config completa.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </div>\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Recurso (API)</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.dataSource.resourcePath\"\n (ngModelChange)=\"onResourcePathChange($event)\"\n placeholder=\"ex.: users\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Endpoint do recurso (resourcePath).\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Query (JSON)</mat-label>\n <textarea\n matInput\n rows=\"3\"\n [(ngModel)]=\"queryJson\"\n (ngModelChange)=\"onQueryChanged($event)\"\n placeholder='ex.: &#123;\"status\":\"active\",\"department\":\"sales\"&#125;'\n ></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Opcional. Use JSON v\u00C3\u00A1lido para filtros iniciais.\"\n *ngIf=\"!queryError\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error *ngIf=\"queryError\">{{ queryError }}</mat-error>\n </mat-form-field>\n <div class=\"g g-auto-220 gap-12 ai-end mt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Ordenar por</mat-label>\n <mat-select\n [(ngModel)]=\"sortField\"\n (ngModelChange)=\"updateSortConfig()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Campo base do recurso.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Dire\u00C3\u00A7\u00C3\u00A3o</mat-label>\n <mat-select\n [(ngModel)]=\"sortDir\"\n (ngModelChange)=\"updateSortConfig()\"\n >\n <mat-option value=\"asc\">Ascendente</mat-option>\n <mat-option value=\"desc\">Descendente</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"JSON\">\n <div class=\"editor-content\">\n <praxis-list-json-config-editor\n [document]=\"document\"\n (documentChange)=\"onJsonConfigChange($event)\"\n (validationChange)=\"onJsonValidationChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n >\n </praxis-list-json-config-editor>\n </div>\n </mat-tab>\n <mat-tab label=\"A\u00C3\u00A7\u00C3\u00B5es\">\n <div class=\"editor-content g gap-12\">\n <div class=\"g g-1-auto gap-8 ai-center\">\n <div class=\"muted\">\n Configure bot\u00C3\u00B5es de a\u00C3\u00A7\u00C3\u00A3o por item (\u00C3\u00ADcone, r\u00C3\u00B3tulo, cor, visibilidade)\n </div>\n <button mat-flat-button color=\"primary\" (click)=\"addAction()\">\n Adicionar a\u00C3\u00A7\u00C3\u00A3o\n </button>\n </div>\n <div class=\"g g-1-auto gap-8 ai-center\">\n <mat-form-field appearance=\"outline\">\n <mat-label>A\u00C3\u00A7\u00C3\u00A3o global (Praxis)</mat-label>\n <mat-select\n [(ngModel)]=\"selectedGlobalActionId\"\n (ngModelChange)=\"onGlobalActionSelected($event)\"\n >\n <mat-option [value]=\"undefined\">-- Selecionar --</mat-option>\n <mat-option *ngFor=\"let ga of globalActionCatalog\" [value]=\"ga.id\">\n <mat-icon class=\"option-icon\">{{ ga.icon || \"bolt\" }}</mat-icon>\n {{ ga.label }}\n </mat-option>\n </mat-select>\n <mat-hint\n *ngIf=\"!globalActionCatalog.length\"\n class=\"text-caption muted\"\n >Nenhuma a\u00C3\u00A7\u00C3\u00A3o global registrada.</mat-hint\n >\n </mat-form-field>\n <div class=\"muted text-caption\">\n Selecione para adicionar com `command` global.\n </div>\n </div>\n <div\n *ngFor=\"let a of working.actions || []; let i = index\"\n class=\"g g-auto-200 gap-12 ai-end\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>ID</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.id\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo de a\u00C3\u00A7\u00C3\u00A3o</mat-label>\n <mat-select [(ngModel)]=\"a.kind\" (ngModelChange)=\"onActionsChanged()\">\n <mat-option value=\"icon\">\u00C3\u008Dcone</mat-option>\n <mat-option value=\"button\">Bot\u00C3\u00A3o</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>\u00C3\u008Dcone</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.icon\"\n (ngModelChange)=\"onActionsChanged()\"\n placeholder=\"ex.: edit, delete\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Command (global)</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.command\"\n (ngModelChange)=\"onActionsChanged()\"\n placeholder=\"global:toast.success\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>R\u00C3\u00B3tulo</mat-label>\n <input\n matInput\n [(ngModel)]=\"a.label\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n </mat-form-field>\n <ng-container *ngIf=\"a.kind === 'button'\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Variante</mat-label>\n <mat-select\n [(ngModel)]=\"a.buttonVariant\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option value=\"stroked\">Contorno</mat-option>\n <mat-option value=\"raised\">Elevado</mat-option>\n <mat-option value=\"flat\">Preenchido</mat-option>\n </mat-select>\n </mat-form-field>\n </ng-container>\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor da a\u00C3\u00A7\u00C3\u00A3o</mat-label>\n <mat-select\n [(ngModel)]=\"a.color\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option *ngFor=\"let c of paletteOptions\" [value]=\"c.value\">\n <span\n class=\"color-dot\"\n [style.background]=\"colorDotBackground(c.value)\"\n ></span\n >{{ c.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <div\n class=\"g gap-8\"\n *ngIf=\"isCustomColor(a.color); else actionCustomBtn\"\n >\n <pdx-color-picker\n label=\"Cor personalizada\"\n [format]=\"'hex'\"\n [(ngModel)]=\"a.color\"\n (ngModelChange)=\"onActionsChanged()\"\n ></pdx-color-picker>\n </div>\n <ng-template #actionCustomBtn>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"enableCustomActionColor(a)\"\n >\n Usar cor personalizada\n </button>\n </ng-template>\n <mat-form-field appearance=\"outline\">\n <mat-label>Payload da a\u00C3\u00A7\u00C3\u00A3o</mat-label>\n <mat-select\n [(ngModel)]=\"a.emitPayload\"\n (ngModelChange)=\"onActionsChanged()\"\n >\n <mat-option [value]=\"undefined\">Padr\u00C3\u00A3o</mat-option>\n <mat-option value=\"item\">item</mat-option>\n <mat-option value=\"id\">id</mat-option>\n <mat-option value=\"value\">value</mat-option>\n </mat-select>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"emitPayload\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"col-span-2\">\n <mat-label\n >Exibir quando (ex.: &#36;&#123;item.status&#125; ==\n 'done')</mat-label\n >\n <input\n matInput\n [(ngModel)]=\"a.showIf\"\n (ngModelChange)=\"onActionsChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Sintaxe suportada: &#34;&#36;{item.campo} == &#39;valor&#39;&#34;. Express\u00C3\u00B5es avan\u00C3\u00A7adas n\u00C3\u00A3o s\u00C3\u00A3o avaliadas.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"g row-flow gap-8 ai-center\">\n <button\n *ngIf=\"(a.kind || 'icon') === 'icon'\"\n mat-icon-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n >\n <mat-icon\n [praxisIcon]=\"a.icon || 'bolt'\"\n [style.cssText]=\"iconStyle(a.color)\"\n ></mat-icon>\n </button>\n <ng-container *ngIf=\"a.kind === 'button'\">\n <button\n *ngIf=\"a.buttonVariant === 'stroked'\"\n mat-stroked-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'stroked')\"\n >\n {{ a.label || a.id || \"A\u00C3\u00A7\u00C3\u00A3o\" }}\n </button>\n <button\n *ngIf=\"a.buttonVariant === 'raised'\"\n mat-raised-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'raised')\"\n >\n {{ a.label || a.id || \"A\u00C3\u00A7\u00C3\u00A3o\" }}\n </button>\n <button\n *ngIf=\"!a.buttonVariant || a.buttonVariant === 'flat'\"\n mat-flat-button\n [color]=\"isThemeColor(a.color) ? a.color : undefined\"\n [style.cssText]=\"buttonStyle(a.color, 'flat')\"\n >\n {{ a.label || a.id || \"A\u00C3\u00A7\u00C3\u00A3o\" }}\n </button>\n </ng-container>\n <span class=\"muted\">Pr\u00C3\u00A9-visualiza\u00C3\u00A7\u00C3\u00A3o</span>\n </div>\n <div class=\"flex-end\">\n <button mat-button color=\"warn\" (click)=\"removeAction(i)\">\n Remover\n </button>\n </div>\n <div class=\"g gap-8 col-span-2\" *ngIf=\"a.command\">\n <mat-slide-toggle\n [(ngModel)]=\"a.showLoading\"\n (ngModelChange)=\"onActionsChanged()\"\n >Mostrar loading</mat-slide-toggle\n >\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Confirma\u00C3\u00A7\u00C3\u00A3o</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <div class=\"g row-flow gap-8 ai-center\">\n <span class=\"text-caption muted\">Tipo</span>\n <mat-button-toggle-group\n [value]=\"a.confirmation?.type || ''\"\n (change)=\"applyConfirmationPreset(a, $event.value)\"\n >\n <mat-button-toggle value=\"\">Padr\u00C3\u00A3o</mat-button-toggle>\n <mat-button-toggle value=\"danger\">Danger</mat-button-toggle>\n <mat-button-toggle value=\"warning\">Warning</mat-button-toggle>\n <mat-button-toggle value=\"info\">Info</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>T\u00C3\u00ADtulo</mat-label>\n <input\n matInput\n [ngModel]=\"a.confirmation?.title\"\n (ngModelChange)=\"setConfirmationField(a, 'title', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mensagem</mat-label>\n <input\n matInput\n [ngModel]=\"a.confirmation?.message\"\n (ngModelChange)=\"setConfirmationField(a, 'message', $event)\"\n />\n </mat-form-field>\n <div class=\"g gap-6\">\n <div class=\"text-caption muted\">Pr\u00C3\u00A9via</div>\n <div class=\"text-caption\">\n <strong>{{\n a.confirmation?.title || \"Confirmar a\u00C3\u00A7\u00C3\u00A3o\"\n }}</strong>\n </div>\n <div class=\"text-caption muted\">\n {{\n a.confirmation?.message ||\n \"Tem certeza que deseja continuar?\"\n }}\n </div>\n <div class=\"text-caption\">\n <span\n class=\"confirm-type\"\n [ngClass]=\"a.confirmation?.type || 'default'\"\n >Tipo: {{ a.confirmation?.type || \"padr\u00C3\u00A3o\" }}</span\n >\n </div>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!a.confirmation?.title && !a.confirmation?.message\"\n >\n Defina um t\u00C3\u00ADtulo ou mensagem para a confirma\u00C3\u00A7\u00C3\u00A3o.\n </div>\n </div>\n </div>\n </mat-expansion-panel>\n <ng-container *ngIf=\"isSurfaceOpenCommand(a); else defaultGlobalPayloadEditor\">\n <div class=\"col-span-2\">\n <praxis-surface-open-action-editor\n [value]=\"getSurfaceOpenGlobalPayload(a)\"\n hostKind=\"list\"\n (valueChange)=\"onSurfaceOpenGlobalPayloadChange(a, $event)\"\n ></praxis-surface-open-action-editor>\n </div>\n </ng-container>\n <ng-template #defaultGlobalPayloadEditor>\n <mat-form-field appearance=\"outline\" class=\"col-span-2\">\n <mat-label>Payload (JSON/Template)</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [(ngModel)]=\"a.globalPayload\"\n (ngModelChange)=\"onActionsChanged()\"\n placeholder='{\"message\":\"${item.name} favoritado\"}'\n ></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n [matTooltip]=\"globalPayloadSchemaTooltip(a)\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error *ngIf=\"isGlobalPayloadInvalid(a.globalPayload)\"\n >JSON inv\u00C3\u00A1lido</mat-error\n >\n </mat-form-field>\n <div class=\"g row-flow gap-8 ai-center\">\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"applyGlobalPayloadExample(a)\"\n >\n Inserir exemplo\n </button>\n <span class=\"muted text-caption\">{{\n globalPayloadExampleHint(a)\n }}</span>\n </div>\n </ng-template>\n <mat-slide-toggle\n [(ngModel)]=\"a.emitLocal\"\n (ngModelChange)=\"onActionsChanged()\"\n >Emitir evento local tamb\u00C3\u00A9m</mat-slide-toggle\n >\n </div>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"Layout\">\n <div class=\"editor-content grid gap-3\">\n <div class=\"preset-row g row-flow gap-8\">\n <button mat-stroked-button (click)=\"applyLayoutPreset('tiles-modern')\">\n Preset Tiles Moderno\n </button>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Variante</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.variant\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"list\">Lista</mat-option>\n <mat-option value=\"cards\">Cards</mat-option>\n <mat-option value=\"tiles\">Tiles</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Modelo</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.model\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <ng-container\n *ngIf=\"working.layout.variant === 'list'; else cardModels\"\n >\n <mat-option value=\"standard\">Padr\u00C3\u00A3o</mat-option>\n <mat-option value=\"media\">M\u00C3\u00ADdia \u00C3\u00A0 esquerda</mat-option>\n <mat-option value=\"hotel\">Hotel (m\u00C3\u00ADdia grande)</mat-option>\n </ng-container>\n <ng-template #cardModels>\n <ng-container\n *ngIf=\"working.layout.variant === 'tiles'; else cardsOnly\"\n >\n <mat-option value=\"standard\">Tile padr\u00C3\u00A3o</mat-option>\n <mat-option value=\"media\">Tile com m\u00C3\u00ADdia</mat-option>\n <mat-option value=\"hotel\">Tile hotel</mat-option>\n </ng-container>\n <ng-template #cardsOnly>\n <mat-option value=\"standard\">Padr\u00C3\u00A3o</mat-option>\n <mat-option value=\"media\">Card com m\u00C3\u00ADdia</mat-option>\n <mat-option value=\"hotel\">Hotel</mat-option>\n </ng-template>\n </ng-template>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Linhas</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.lines\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option [value]=\"1\">1</mat-option>\n <mat-option [value]=\"2\">2</mat-option>\n <mat-option [value]=\"3\">3</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Itens por p\u00C3\u00A1gina</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"1\"\n [(ngModel)]=\"working.layout.pageSize\"\n (ngModelChange)=\"onPageSizeChange($event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Densidade</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.density\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"default\">Padr\u00C3\u00A3o</mat-option>\n <mat-option value=\"comfortable\">Confort\u00C3\u00A1vel</mat-option>\n <mat-option value=\"compact\">Compacta</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Espa\u00C3\u00A7amento entre itens</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.itemSpacing\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"none\">Sem espa\u00C3\u00A7o extra</mat-option>\n <mat-option value=\"tight\">Justo</mat-option>\n <mat-option value=\"default\">Padr\u00C3\u00A3o</mat-option>\n <mat-option value=\"relaxed\">Relaxado</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"working.layout.variant !== 'tiles'\"\n >\n <mat-label>Divisores</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.dividers\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option value=\"none\">Sem</mat-option>\n <mat-option value=\"between\">Entre grupos</mat-option>\n <mat-option value=\"all\">Todos</mat-option>\n </mat-select>\n </mat-form-field>\n <ng-container *ngIf=\"fields.length > 0; else groupByText\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Agrupar por</mat-label>\n <mat-select\n [(ngModel)]=\"working.layout.groupBy\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n <mat-option [value]=\"\">Nenhum</mat-option>\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n </ng-container>\n <ng-template #groupByText>\n <mat-form-field appearance=\"outline\">\n <mat-label>Agrupar por</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.layout.groupBy\"\n (ngModelChange)=\"onLayoutChanged()\"\n placeholder=\"ex.: departamento\"\n />\n </mat-form-field>\n </ng-template>\n <mat-slide-toggle\n [(ngModel)]=\"working.layout.stickySectionHeader\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n Header de se\u00C3\u00A7\u00C3\u00A3o fixo\n </mat-slide-toggle>\n <mat-slide-toggle\n [(ngModel)]=\"working.layout.virtualScroll\"\n (ngModelChange)=\"onLayoutChanged()\"\n >\n Scroll virtual\n </mat-slide-toggle>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">Ferramentas da lista</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showSearch\"\n (ngModelChange)=\"onUiChanged()\"\n >Mostrar busca</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showSort\"\n (ngModelChange)=\"onUiChanged()\"\n >Mostrar ordenar</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working.ui.showRange\"\n (ngModelChange)=\"onUiChanged()\"\n >Mostrar faixa X\u00E2\u20AC\u201CY de Total</mat-slide-toggle\n >\n </div>\n <div\n class=\"g g-auto-220 gap-12 ai-end mt-12\"\n *ngIf=\"working.ui?.showSearch\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo para buscar</mat-label>\n <mat-select\n [(ngModel)]=\"working.ui.searchField\"\n (ngModelChange)=\"onUiChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Placeholder da busca</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.ui.searchPlaceholder\"\n (ngModelChange)=\"onUiChanged()\"\n placeholder=\"ex.: Buscar por t\u00C3\u00ADtulo\"\n />\n </mat-form-field>\n </div>\n <div class=\"mt-12\" *ngIf=\"working.ui?.showSort\">\n <div class=\"g g-1-auto ai-center gap-8\">\n <div class=\"muted\">Op\u00C3\u00A7\u00C3\u00B5es de ordena\u00C3\u00A7\u00C3\u00A3o (r\u00C3\u00B3tulo \u00E2\u2020\u2019 campo+dire\u00C3\u00A7\u00C3\u00A3o)</div>\n <button mat-flat-button color=\"primary\" (click)=\"addUiSortRow()\">\n Adicionar op\u00C3\u00A7\u00C3\u00A3o\n </button>\n </div>\n <div\n class=\"g g-auto-220 gap-12 ai-end mt-12\"\n *ngFor=\"let r of uiSortRows; let i = index\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>R\u00C3\u00B3tulo</mat-label>\n <input\n matInput\n [(ngModel)]=\"r.label\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n placeholder=\"ex.: Mais recentes\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"r.field\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Dire\u00C3\u00A7\u00C3\u00A3o</mat-label>\n <mat-select\n [(ngModel)]=\"r.dir\"\n (ngModelChange)=\"onUiSortRowsChanged()\"\n >\n <mat-option value=\"desc\">Descendente</mat-option>\n <mat-option value=\"asc\">Ascendente</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"error\" *ngIf=\"isUiSortRowDuplicate(i)\">\n Op\u00C3\u00A7\u00C3\u00A3o duplicada (campo+dire\u00C3\u00A7\u00C3\u00A3o)\n </div>\n <div class=\"flex-end\">\n <button mat-button color=\"warn\" (click)=\"removeUiSortRow(i)\">\n Remover\n </button>\n </div>\n </div>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"Conte\u00C3\u00BAdo\">\n <div class=\"editor-content\">\n <div class=\"editor-main\">\n <mat-accordion multi>\n <!-- Primary -->\n <mat-expansion-panel [expanded]=\"true\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingPrimary.type) }}</mat-icon>\n <span>Primary (T\u00C3\u00ADtulo)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingPrimary.field || \"N\u00C3\u00A3o mapeado\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'name';\n onMappingChanged()\n \"\n >\n Nome\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'title';\n onMappingChanged()\n \"\n >\n T\u00C3\u00ADtulo\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingPrimary.type = 'text';\n mappingPrimary.field = 'name';\n mappingSecondary.type = 'text';\n mappingSecondary.field = 'role';\n onMappingChanged()\n \"\n >\n Nome + Papel\n </button>\n </div>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingPrimary.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingPrimary.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of primaryTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n @switch (mappingPrimary.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingPrimary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingPrimary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingPrimary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingPrimary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n }\n\n <!-- Advanced -->\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header>\n <mat-panel-title>Formata\u00C3\u00A7\u00C3\u00A3o e Estilo</mat-panel-title>\n </mat-expansion-panel-header>\n <div class=\"g gap-12 pt-12\">\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"\n mappingPrimary.type === 'text' ||\n mappingPrimary.type === 'html'\n \"\n >\n <mat-label>Classe CSS</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"\n mappingPrimary.type === 'text' ||\n mappingPrimary.type === 'html'\n \"\n >\n <mat-label>Estilo Inline</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Classe CSS</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Estilo Inline</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingPrimary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Secondary -->\n <mat-expansion-panel [expanded]=\"!!mappingSecondary.field\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingSecondary.type) }}</mat-icon>\n <span>Secondary (Resumo)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingSecondary.field || \"N\u00C3\u00A3o mapeado\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'text';\n mappingSecondary.field = 'subtitle';\n onMappingChanged()\n \"\n >\n Subt\u00C3\u00ADtulo\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'date';\n mappingSecondary.field = 'hireDate';\n mappingSecondary.dateStyle = 'short';\n onMappingChanged()\n \"\n >\n Data curta\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSecondary.type = 'currency';\n mappingSecondary.field = 'salary';\n mappingSecondary.currencyCode = 'BRL';\n mappingSecondary.locale = 'pt-BR';\n onMappingChanged()\n \"\n >\n Sal\u00C3\u00A1rio\n </button>\n </div>\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSecondary.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSecondary.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of secondaryTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n @switch (mappingSecondary.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingSecondary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingSecondary\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingSecondary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingSecondary\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header>\n <mat-panel-title>Formata\u00C3\u00A7\u00C3\u00A3o e Estilo</mat-panel-title>\n </mat-expansion-panel-header>\n <div class=\"g gap-12 pt-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe CSS</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSecondary.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Estilo Inline</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSecondary.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <mat-expansion-panel\n [expanded]=\"!!mappingMeta.field || mappingMetaFields.length > 0\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingMeta.type || \"text\")\n }}</mat-icon>\n <span>Meta (Detalhe/Lateral)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>\n {{\n mappingMetaFields.length\n ? \"Campo composto (\" + mappingMetaFields.length + \")\"\n : mappingMeta.field || \"N\u00C3\u00A3o mapeado\"\n }}\n </mat-panel-description>\n </mat-expansion-panel-header>\n\n <div class=\"g gap-12\">\n <!-- Composition Mode Toggle -->\n <div class=\"g g-1-1 gap-12 p-12 bg-subtle rounded\">\n <div class=\"text-caption muted\">Modo de composi\u00C3\u00A7\u00C3\u00A3o</div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Campos para compor (Multi-select)</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMetaFields\"\n multiple\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <div\n class=\"g g-1-1 ai-center gap-12\"\n *ngIf=\"mappingMetaFields.length\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>Separador</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMetaSeparator\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-slide-toggle\n [(ngModel)]=\"mappingMetaWrapSecondInParens\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n (Seg) entre par\u00C3\u00AAnteses\n </mat-slide-toggle>\n </div>\n </div>\n\n <!-- Single Field Mode (if no composition) -->\n <div class=\"g g-1-1 gap-12\" *ngIf=\"!mappingMetaFields.length\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo \u00C3\u0161nico</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option [value]=\"undefined\">-- Nenhum --</mat-option>\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of metaTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- Type configuration (pluggable editors) -->\n @switch (mappingMeta.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingMeta\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingMeta\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingMeta\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <praxis-meta-editor-image\n [model]=\"mappingMeta\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <!-- Advanced -->\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Op\u00C3\u00A7\u00C3\u00B5es avan\u00C3\u00A7adas</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Posi\u00C3\u00A7\u00C3\u00A3o</mat-label>\n <mat-select\n [(ngModel)]=\"mappingMeta.placement\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option value=\"side\">Lateral (Direita)</mat-option>\n <mat-option value=\"line\">Na linha (Abaixo)</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Classe CSS</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMeta.class\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Estilo</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingMeta.style\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n <!-- Trailing -->\n <mat-expansion-panel [expanded]=\"!!mappingTrailing.field\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingTrailing.type || \"text\")\n }}</mat-icon>\n <span>Trailing (Direita)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingTrailing.field || \"N\u00C3\u00A3o mapeado\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingTrailing.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option [value]=\"undefined\">-- Nenhum --</mat-option>\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingTrailing.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of trailingTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'chip';\n mappingTrailing.chipColor = 'primary';\n mappingTrailing.chipVariant = 'filled';\n mappingTrailing.field = 'status';\n onMappingChanged()\n \"\n >\n Status Chip\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'icon';\n mappingTrailing.field = 'status';\n mappingTrailing.iconColor = 'primary';\n onMappingChanged()\n \"\n >\n Status \u00C3\u008Dcone\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingTrailing.type = 'currency';\n mappingTrailing.field = 'price';\n mappingTrailing.currencyCode = 'BRL';\n mappingTrailing.locale = 'pt-BR';\n onMappingChanged()\n \"\n >\n Pre\u00C3\u00A7o\n </button>\n </div>\n\n @switch (mappingTrailing.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingTrailing\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingTrailing\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"currency\") {\n <praxis-meta-editor-currency\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-currency>\n }\n @case (\"date\") {\n <praxis-meta-editor-date\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-date>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingTrailing\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>URL / Expr</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingTrailing.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n placeholder=\"https://... ou ${item.imageUrl}\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Use URL absoluta/relativa ou express\u00C3\u00A3o ${item.campo}.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error\n *ngIf=\"\n isImageUrlRequiredInvalid(mappingTrailing.imageUrl)\n \"\n >URL/expr obrigat\u00C3\u00B3ria</mat-error\n >\n </mat-form-field>\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingTrailing\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!mappingTrailing.imageUrl\"\n >\n Defina a URL/expr para renderizar a imagem.\n </div>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Estilo</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingTrailing.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Style</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingTrailing.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Leading -->\n <mat-expansion-panel\n [expanded]=\"\n !!mappingLeading.field ||\n (mappingLeading.type === 'icon' && !!mappingLeading.icon) ||\n (mappingLeading.type === 'image' && !!mappingLeading.imageUrl)\n \"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{ getTypeIcon(mappingLeading.type) }}</mat-icon>\n <span>Leading (Esquerda)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>\n {{\n mappingLeading.type === \"icon\"\n ? mappingLeading.icon || \"\u00C3\u008Dcone est\u00C3\u00A1tico\"\n : mappingLeading.field ||\n (mappingLeading.imageUrl\n ? \"Imagem est\u00C3\u00A1tica\"\n : \"N\u00C3\u00A3o mapeado\")\n }}\n </mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingLeading.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of leadingTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <!-- Field (only if not static icon/image, though user might want dynamic) -->\n <mat-form-field\n appearance=\"outline\"\n *ngIf=\"\n mappingLeading.type !== 'icon' &&\n mappingLeading.type !== 'image'\n \"\n >\n <mat-label>Campo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingLeading.field\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option *ngFor=\"let f of fields\" [value]=\"f\">{{\n f\n }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'icon';\n mappingLeading.icon = 'person';\n mappingLeading.iconColor = 'primary';\n onMappingChanged()\n \"\n >\n Avatar \u00C3\u008Dcone\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'image';\n mappingLeading.imageUrl = 'https://placehold.co/64x64';\n mappingLeading.imageAlt = 'Avatar';\n mappingLeading.badgeText = '${item.status}';\n onMappingChanged()\n \"\n >\n Avatar Imagem + Badge\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingLeading.type = 'chip';\n mappingLeading.field = 'tag';\n mappingLeading.chipColor = 'accent';\n mappingLeading.chipVariant = 'filled';\n onMappingChanged()\n \"\n >\n Chip Tag\n </button>\n </div>\n\n <!-- Icon Specific -->\n <div\n class=\"g g-1-auto gap-12 ai-center\"\n *ngIf=\"mappingLeading.type === 'icon'\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>\u00C3\u008Dcone</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.icon\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <button mat-icon-button matSuffix (click)=\"pickLeadingIcon()\">\n <mat-icon>search</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"text-caption muted\">\n Use pipe <code>|iconMap</code> no extra pipe para din\u00C3\u00A2mico\n </div>\n </div>\n <div *ngIf=\"mappingLeading.type === 'icon'\">\n <praxis-meta-editor-icon\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n </div>\n\n <!-- Image Specific -->\n <div\n class=\"g g-1-1 gap-12\"\n *ngIf=\"mappingLeading.type === 'image'\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>URL da Imagem</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n placeholder=\"https://... ou ${item.imageUrl}\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Use URL absoluta/relativa ou express\u00C3\u00A3o ${item.campo}.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n <mat-error\n *ngIf=\"isImageUrlRequiredInvalid(mappingLeading.imageUrl)\"\n >URL/expr obrigat\u00C3\u00B3ria</mat-error\n >\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Alt Text</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.imageAlt\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Badge Texto</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingLeading.badgeText\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n\n @switch (mappingLeading.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingLeading\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingLeading\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingLeading\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Estilo</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingLeading.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Style</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingLeading.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Features -->\n <mat-expansion-panel\n [expanded]=\"featuresVisible && features.length > 0\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>view_list</mat-icon>\n <span>Recursos (Features)</span>\n </div>\n </mat-panel-title>\n <mat-panel-description\n >{{ features.length }} item(s)</mat-panel-description\n >\n </mat-expansion-panel-header>\n\n <div class=\"g gap-12\">\n <div class=\"g row-flow gap-12 ai-center\">\n <mat-slide-toggle\n [(ngModel)]=\"featuresVisible\"\n (ngModelChange)=\"onFeaturesChanged()\"\n >Ativar recursos</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"featuresSyncWithMeta\"\n (ngModelChange)=\"onMappingChanged()\"\n >Sincronizar com Meta</mat-slide-toggle\n >\n <span class=\"flex-1\"></span>\n <mat-button-toggle-group\n [(ngModel)]=\"featuresMode\"\n (change)=\"onFeaturesChanged()\"\n appearance=\"legacy\"\n >\n <mat-button-toggle value=\"icons+labels\"\n ><mat-icon>view_list</mat-icon></mat-button-toggle\n >\n <mat-button-toggle value=\"icons-only\"\n ><mat-icon>more_horiz</mat-icon></mat-button-toggle\n >\n </mat-button-toggle-group>\n </div>\n\n <div\n *ngFor=\"let f of features; let i = index\"\n class=\"g g-auto-1 gap-8 ai-center p-8 border rounded mb-2\"\n >\n <button mat-icon-button (click)=\"pickFeatureIcon(i)\">\n <mat-icon>{{ f.icon || \"search\" }}</mat-icon>\n </button>\n <mat-form-field\n appearance=\"outline\"\n class=\"dense-form-field no-sub\"\n >\n <input\n matInput\n [(ngModel)]=\"f.expr\"\n (ngModelChange)=\"onFeaturesChanged()\"\n placeholder=\"Expr/Texto\"\n />\n </mat-form-field>\n <button mat-icon-button color=\"warn\" (click)=\"removeFeature(i)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n <button mat-button color=\"primary\" (click)=\"addFeature()\">\n <mat-icon>add</mat-icon> Adicionar recurso\n </button>\n </div>\n </mat-expansion-panel>\n <!-- Section Header -->\n <mat-expansion-panel [expanded]=\"!!mappingSectionHeader.expr\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>{{\n getTypeIcon(mappingSectionHeader.type)\n }}</mat-icon>\n <span>Cabe\u00C3\u00A7alho de Se\u00C3\u00A7\u00C3\u00A3o</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingSectionHeader.expr || \"N\u00C3\u00A3o configurado\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingSectionHeader.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of sectionHeaderTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Express\u00C3\u00A3o (item.key)</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingSectionHeader.expr\"\n (ngModelChange)=\"onMappingChanged()\"\n placeholder=\"item.key\"\n />\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSectionHeader.type = 'text';\n mappingSectionHeader.expr = '${item.key}';\n onMappingChanged()\n \"\n >\n Texto padr\u00C3\u00A3o\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingSectionHeader.type = 'chip';\n mappingSectionHeader.chipColor = 'primary';\n mappingSectionHeader.chipVariant = 'filled';\n mappingSectionHeader.expr = '${item.key}';\n onMappingChanged()\n \"\n >\n Chip padr\u00C3\u00A3o\n </button>\n </div>\n\n @switch (mappingSectionHeader.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingSectionHeader\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingSectionHeader\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingSectionHeader\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>URL Imagem</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingSectionHeader.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <mat-error\n *ngIf=\"\n isImageUrlRequiredInvalid(\n mappingSectionHeader.imageUrl\n )\n \"\n >URL/expr obrigat\u00C3\u00B3ria</mat-error\n >\n </mat-form-field>\n </div>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!mappingSectionHeader.imageUrl\"\n >\n Defina a URL/expr para renderizar a imagem.\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingSectionHeader\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Estilo</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSectionHeader.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Style</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingSectionHeader.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n\n <!-- Empty State -->\n <mat-expansion-panel [expanded]=\"!!mappingEmptyState.expr\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n <div class=\"g row-flow gap-8 ai-center\">\n <mat-icon>inbox</mat-icon>\n <span>Estado Vazio</span>\n </div>\n </mat-panel-title>\n <mat-panel-description>{{\n mappingEmptyState.expr || \"Padr\u00C3\u00A3o\"\n }}</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"g gap-12\">\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo</mat-label>\n <mat-select\n [(ngModel)]=\"mappingEmptyState.type\"\n (ngModelChange)=\"onMappingChanged()\"\n >\n <mat-option\n *ngFor=\"let mt of emptyStateTypeConfigs\"\n [value]=\"mt.type\"\n >\n <mat-icon class=\"option-icon\">{{ mt.icon }}</mat-icon>\n {{ mt.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mensagem / Expr</mat-label>\n <input\n matInput\n [(ngModel)]=\"mappingEmptyState.expr\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n </mat-form-field>\n </div>\n <div class=\"g row-flow gap-8\">\n <span class=\"text-caption muted\">Presets</span>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingEmptyState.type = 'text';\n mappingEmptyState.expr = 'Nenhum item dispon\u00C3\u00ADvel';\n onMappingChanged()\n \"\n >\n Mensagem padr\u00C3\u00A3o\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"\n mappingEmptyState.type = 'image';\n mappingEmptyState.imageUrl = '/list-empty-state.svg';\n mappingEmptyState.imageAlt = 'Sem resultados';\n onMappingChanged()\n \"\n >\n Imagem padr\u00C3\u00A3o\n </button>\n </div>\n\n @switch (mappingEmptyState.type) {\n @case (\"text\") {\n <praxis-meta-editor-text\n [model]=\"mappingEmptyState\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"html\") {\n <praxis-meta-editor-text\n [model]=\"mappingEmptyState\"\n [setPipe]=\"setPipe.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-text>\n }\n @case (\"chip\") {\n <praxis-meta-editor-chip\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-chip>\n }\n @case (\"rating\") {\n <praxis-meta-editor-rating\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-rating>\n }\n @case (\"icon\") {\n <praxis-meta-editor-icon\n [model]=\"mappingEmptyState\"\n [paletteOptions]=\"paletteOptions\"\n [colorDotBackground]=\"colorDotBackground\"\n [isCustomColor]=\"isCustomColor\"\n [enableCustomColor]=\"enableCustomColor.bind(this)\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-icon>\n }\n @case (\"image\") {\n <div class=\"g g-1-1 gap-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>URL Imagem</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.imageUrl\"\n (ngModelChange)=\"onMappingChanged()\"\n />\n <mat-error\n *ngIf=\"\n isImageUrlRequiredInvalid(mappingEmptyState.imageUrl)\n \"\n >URL/expr obrigat\u00C3\u00B3ria</mat-error\n >\n </mat-form-field>\n </div>\n <div\n class=\"text-caption muted\"\n *ngIf=\"!mappingEmptyState.imageUrl\"\n >\n Defina a URL/expr para renderizar a imagem.\n </div>\n <praxis-meta-editor-image\n [model]=\"mappingEmptyState\"\n (change)=\"onMappingChanged()\"\n ></praxis-meta-editor-image>\n }\n }\n\n <mat-expansion-panel class=\"mat-elevation-z0 advanced-panel\">\n <mat-expansion-panel-header\n ><mat-panel-title\n >Estilo</mat-panel-title\n ></mat-expansion-panel-header\n >\n <div class=\"g gap-12 pt-12\">\n <mat-form-field appearance=\"outline\"\n ><mat-label>Classe</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.class\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n <mat-form-field appearance=\"outline\"\n ><mat-label>Style</mat-label\n ><input\n matInput\n [(ngModel)]=\"mappingEmptyState.style\"\n (ngModelChange)=\"onMappingChanged()\"\n /></mat-form-field>\n </div>\n </mat-expansion-panel>\n </div>\n </mat-expansion-panel>\n </mat-accordion>\n\n <button mat-flat-button color=\"primary\" (click)=\"applyTemplate()\">\n Aplicar mapeamento\n </button>\n <button\n mat-button\n (click)=\"inferFromFields()\"\n [disabled]=\"!fields.length\"\n >\n Inferir do schema\n </button>\n <div class=\"g g-auto-220 gap-12 ai-end mt-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Skeleton (quantidade)</mat-label>\n <input\n matInput\n type=\"number\"\n min=\"0\"\n [(ngModel)]=\"skeletonCountInput\"\n (ngModelChange)=\"onSkeletonChanged($event)\"\n />\n </mat-form-field>\n </div>\n\n <div class=\"g gap-12 mt-12\">\n <div class=\"g row-flow gap-8 ai-center\">\n <span class=\"section-title mat-subtitle-1\">Pr\u00C3\u00A9via de tema</span>\n <mat-button-toggle-group\n [(ngModel)]=\"skinPreviewTheme\"\n (change)=\"onSkinChanged()\"\n appearance=\"legacy\"\n >\n <mat-button-toggle [value]=\"'light'\">Claro</mat-button-toggle>\n <mat-button-toggle [value]=\"'dark'\">Escuro</mat-button-toggle>\n <mat-button-toggle [value]=\"'grid'\">Grade</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <div class=\"skin-preview-wrap\">\n <praxis-list-skin-preview\n [config]=\"working\"\n [items]=\"previewData\"\n [theme]=\"skinPreviewTheme\"\n ></praxis-list-skin-preview>\n </div>\n </div>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"i18n/A11y\">\n <div\n class=\"editor-content grid gap-3\"\n *ngIf=\"working?.a11y && working?.events\"\n >\n <mat-form-field appearance=\"outline\">\n <mat-label>Locale padr\u00C3\u00A3o</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.i18n.locale\"\n (ngModelChange)=\"markDirty()\"\n placeholder=\"ex.: pt-BR\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Moeda padr\u00C3\u00A3o</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.i18n.currency\"\n (ngModelChange)=\"markDirty()\"\n placeholder=\"ex.: BRL\"\n />\n </mat-form-field>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">Acessibilidade</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-form-field appearance=\"outline\">\n <mat-label>aria-label</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.a11y!.ariaLabel\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>aria-labelledby</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.a11y!.ariaLabelledBy\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n </div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-slide-toggle\n [(ngModel)]=\"working!.a11y!.highContrast\"\n (ngModelChange)=\"markDirty()\"\n >Alto contraste</mat-slide-toggle\n >\n <mat-slide-toggle\n [(ngModel)]=\"working!.a11y!.reduceMotion\"\n (ngModelChange)=\"markDirty()\"\n >Reduzir movimento</mat-slide-toggle\n >\n </div>\n <mat-divider class=\"my-8\"></mat-divider>\n <div class=\"subtitle\">Eventos</div>\n <div class=\"g g-auto-220 gap-12 ai-end\">\n <mat-form-field appearance=\"outline\">\n <mat-label>itemClick</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.itemClick\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>actionClick</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.actionClick\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>selectionChange</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.selectionChange\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>loaded</mat-label>\n <input\n matInput\n [(ngModel)]=\"working!.events!.loaded\"\n (ngModelChange)=\"markDirty()\"\n />\n </mat-form-field>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"Sele\u00C3\u00A7\u00C3\u00A3o\">\n <div class=\"editor-content grid gap-3\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Modo</mat-label>\n <mat-select\n [(ngModel)]=\"working.selection.mode\"\n (ngModelChange)=\"onSelectionChanged()\"\n >\n <mat-option value=\"none\">Sem sele\u00C3\u00A7\u00C3\u00A3o</mat-option>\n <mat-option value=\"single\">\u00C3\u0161nica</mat-option>\n <mat-option value=\"multiple\">M\u00C3\u00BAltipla</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Nome no formul\u00C3\u00A1rio</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.formControlName\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"formControlName\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Caminho no formul\u00C3\u00A1rio</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.formControlPath\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"formControlPath\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Comparar por (campo)</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.selection.compareBy\"\n (ngModelChange)=\"onSelectionChanged()\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Chave unica do item.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Retorno</mat-label>\n <mat-select\n [(ngModel)]=\"working.selection.return\"\n (ngModelChange)=\"onSelectionChanged()\"\n >\n <mat-option value=\"value\">value</mat-option>\n <mat-option value=\"item\">item</mat-option>\n <mat-option value=\"id\">id</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </mat-tab>\n <mat-tab label=\"Apar\u00C3\u00AAncia\">\n <div class=\"editor-content grid gap-3\">\n <div class=\"preset-row g row-flow gap-8\">\n <button mat-button (click)=\"applySkinPreset('pill-soft')\">\n Pill Soft\n </button>\n <button mat-button (click)=\"applySkinPreset('gradient-tile')\">\n Gradient Tile\n </button>\n <button mat-button (click)=\"applySkinPreset('glass')\">Glass</button>\n <button mat-button (click)=\"applySkinPreset('elevated')\">\n Elevated\n </button>\n <button mat-button (click)=\"applySkinPreset('outline')\">Outline</button>\n <button mat-button (click)=\"applySkinPreset('flat')\">Flat</button>\n <button mat-button (click)=\"applySkinPreset('neumorphism')\">\n Neumorphism\n </button>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Estilo</mat-label>\n <mat-select\n [(ngModel)]=\"working.skin.type\"\n (ngModelChange)=\"onSkinTypeChanged($event)\"\n >\n <mat-option value=\"pill-soft\">Pill Soft</mat-option>\n <mat-option value=\"gradient-tile\">Gradient Tile</mat-option>\n <mat-option value=\"glass\">Glass</mat-option>\n <mat-option value=\"elevated\">Elevated</mat-option>\n <mat-option value=\"outline\">Outline</mat-option>\n <mat-option value=\"flat\">Flat</mat-option>\n <mat-option value=\"neumorphism\">Neumorphism</mat-option>\n <mat-option value=\"custom\">Custom</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Raio</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.radius\"\n (ngModelChange)=\"onSkinChanged()\"\n placeholder=\"ex.: 1.25rem\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Sombra</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.shadow\"\n (ngModelChange)=\"onSkinChanged()\"\n placeholder=\"ex.: var(--md-sys-elevation-level2)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Borda</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.border\"\n (ngModelChange)=\"onSkinChanged()\"\n />\n </mat-form-field>\n <mat-form-field\n *ngIf=\"working.skin.type === 'glass'\"\n appearance=\"outline\"\n >\n <mat-label>Desfoque</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.backdropBlur\"\n (ngModelChange)=\"onSkinChanged()\"\n placeholder=\"ex.: 8px\"\n />\n </mat-form-field>\n <div *ngIf=\"working.skin.type === 'gradient-tile'\" class=\"g gap-12\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Degrad\u00C3\u00AA de</mat-label>\n <input\n matInput\n [ngModel]=\"working.skin.gradient.from || ''\"\n (ngModelChange)=\"onSkinGradientChanged('from', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Degrad\u00C3\u00AA at\u00C3\u00A9</mat-label>\n <input\n matInput\n [ngModel]=\"working.skin.gradient.to || ''\"\n (ngModelChange)=\"onSkinGradientChanged('to', $event)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>\u00C3\u201Angulo</mat-label>\n <input\n matInput\n type=\"number\"\n [ngModel]=\"working.skin.gradient.angle ?? 135\"\n (ngModelChange)=\"onSkinGradientChanged('angle', $event)\"\n />\n </mat-form-field>\n </div>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>Classe CSS extra (skin.class)</mat-label>\n <input\n matInput\n [(ngModel)]=\"working.skin.class\"\n (ngModelChange)=\"onSkinChanged()\"\n placeholder=\"ex.: my-list-skin\"\n />\n </mat-form-field>\n\n <div\n *ngIf=\"working.skin.type === 'custom'\"\n class=\"g g-auto-220 gap-12 ai-end\"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Estilo inline (skin.inlineStyle)</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [(ngModel)]=\"working.skin.inlineStyle\"\n (ngModelChange)=\"onSkinChanged()\"\n [attr.placeholder]=\"':host{--p-list-radius: 1rem}'\"\n ></textarea>\n </mat-form-field>\n <div class=\"text-caption\">\n Exemplo de CSS por classe (adicione no seu styles global):\n <pre class=\"code-block\">\n.my-list-skin .item-card &#123;\n border-radius: 14px;\n border: 1px solid var(--md-sys-color-outline-variant);\n box-shadow: var(--md-sys-elevation-level2);\n&#125;\n.my-list-skin .mat-mdc-list-item .list-item-content &#123;\n backdrop-filter: blur(6px);\n&#125;</pre\n >\n </div>\n </div>\n </div>\n </mat-tab>\n</mat-tab-group>\n\n", styles: [".confirm-type{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;line-height:16px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant)}.confirm-type.danger{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.confirm-type.warning{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.confirm-type.info{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}:host{display:block;color:var(--md-sys-color-on-surface)}.list-editor-tabs{--editor-surface: var(--md-sys-color-surface-container-lowest);--editor-border: 1px solid var(--md-sys-color-outline-variant);--editor-radius: var(--md-sys-shape-corner-large, 16px);--editor-muted: var(--md-sys-color-on-surface-variant);--editor-accent: var(--md-sys-color-primary)}.editor-content{padding:16px;background:var(--editor-surface);border:var(--editor-border);border-radius:var(--editor-radius);display:grid;gap:12px}.editor-content .mat-mdc-form-field{width:100%;max-width:none;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var( --md-sys-color-outline-variant );--mdc-outlined-text-field-hover-outline-color: var( --md-sys-color-outline );--mdc-outlined-text-field-focus-outline-color: var( --md-sys-color-primary );--mdc-outlined-text-field-error-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-error-focus-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-error-hover-outline-color: var( --md-sys-color-error );--mdc-outlined-text-field-label-text-color: var( --md-sys-color-on-surface-variant );--mdc-outlined-text-field-input-text-color: var( --md-sys-color-on-surface );--mdc-outlined-text-field-supporting-text-color: var( --md-sys-color-on-surface-variant )}.editor-content .mat-mdc-form-field.w-full{max-width:none}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}.editor-split{grid-template-columns:minmax(0,1fr);align-items:start}.editor-main,.editor-aside{display:grid;gap:12px}.skin-preview-wrap{border-radius:calc(var(--editor-radius) - 4px);border:var(--editor-border);background:var(--md-sys-color-surface-container);padding:12px}.g{display:grid}.g-auto-220{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.g-auto-200{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}.g-1-auto{grid-template-columns:1fr auto}.row-flow{grid-auto-flow:column}.gap-6{gap:6px}.gap-8{gap:8px}.gap-12{gap:12px}.ai-center{align-items:center}.ai-end{align-items:end}.mt-12{margin-top:12px}.mb-8{margin-bottom:8px}.mb-6{margin-bottom:6px}.my-8{margin:8px 0}.subtitle{margin:8px 0 4px;color:var(--editor-muted);font-weight:500}.section-title{color:var(--editor-muted);font-weight:600}.chips-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.error{color:var(--md-sys-color-error);font-size:.85rem}.muted{color:var(--editor-muted)}.text-caption{color:var(--editor-muted);font-size:.8rem}:host ::ng-deep .mat-mdc-select-panel .option-icon{font-size:18px;margin-right:6px;vertical-align:middle}:host ::ng-deep .mat-mdc-select-panel .color-dot{width:10px;height:10px;border-radius:999px;display:inline-block;margin-right:6px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-outline)}:host ::ng-deep .mat-mdc-select-panel .color-primary{background:var(--md-sys-color-primary)}:host ::ng-deep .mat-mdc-select-panel .color-accent{background:var(--md-sys-color-tertiary)}:host ::ng-deep .mat-mdc-select-panel .color-warn{background:var(--md-sys-color-error)}:host ::ng-deep .mat-mdc-select-panel .color-default{background:var(--md-sys-color-outline)}@media(max-width:1024px){.editor-split{grid-template-columns:minmax(0,1fr)}}\n"] }]
5016
5190
  }], ctorParameters: () => [{ type: undefined, decorators: [{
5017
5191
  type: Optional
5018
5192
  }, {
@@ -6964,6 +7138,7 @@ const ROW_LAYOUT_SUPPORTED_SLOTS = [
6964
7138
  class PraxisList {
6965
7139
  static nextSkinScopeId = 0;
6966
7140
  customizationEnabled = false;
7141
+ hostLocale = inject(LOCALE_ID, { optional: true }) ?? 'en-US';
6967
7142
  config;
6968
7143
  listId;
6969
7144
  componentInstanceId;
@@ -7363,7 +7538,7 @@ class PraxisList {
7363
7538
  .replace(/[^a-z0-9_-]+/g, '-');
7364
7539
  }
7365
7540
  featureLabel(item, expr) {
7366
- const out = evaluateTemplate({ type: 'text', expr: expr || '' }, item);
7541
+ const out = evaluateTemplate({ type: 'text', expr: expr || '' }, item, this.templateEvaluatorOptions());
7367
7542
  return out?.value ?? '';
7368
7543
  }
7369
7544
  plainText(value) {
@@ -8153,7 +8328,7 @@ class PraxisList {
8153
8328
  };
8154
8329
  }
8155
8330
  if (def.type === 'icon') {
8156
- const out = evaluateTemplate(def, item);
8331
+ const out = evaluateTemplate(def, item, this.templateEvaluatorOptions());
8157
8332
  // Optional color mapping from config.templating.iconColorMap
8158
8333
  try {
8159
8334
  const map = this.config?.templating?.iconColorMap;
@@ -8178,7 +8353,7 @@ class PraxisList {
8178
8353
  catch { }
8179
8354
  return out;
8180
8355
  }
8181
- return evaluateTemplate(def, item);
8356
+ return evaluateTemplate(def, item, this.templateEvaluatorOptions());
8182
8357
  }
8183
8358
  buildUnsupportedComposeNode(def) {
8184
8359
  return {
@@ -8366,9 +8541,15 @@ class PraxisList {
8366
8541
  return [spec.slice(0, idx).trim(), spec.slice(idx + 1).trim()];
8367
8542
  }
8368
8543
  evalString(expr, ctx) {
8369
- const res = evaluateTemplate({ type: 'text', expr }, ctx?.item ?? ctx);
8544
+ const res = evaluateTemplate({ type: 'text', expr }, ctx?.item ?? ctx, this.templateEvaluatorOptions());
8370
8545
  return res?.value ?? '';
8371
8546
  }
8547
+ templateEvaluatorOptions() {
8548
+ return {
8549
+ i18n: this.config?.i18n,
8550
+ appLocale: this.hostLocale,
8551
+ };
8552
+ }
8372
8553
  parseTwoParams(param) {
8373
8554
  if (!param)
8374
8555
  return [undefined, undefined];