@praxisui/list 3.0.0-beta.6 → 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.
- package/README.md +6 -1
- package/fesm2022/praxisui-list.mjs +278 -97
- package/fesm2022/praxisui-list.mjs.map +1 -1
- package/index.d.ts +30 -6
- package/package.json +2 -2
|
@@ -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,
|
|
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
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
+
const presentation = resolveTemplatePresentation('currency', {
|
|
111
|
+
localeArg,
|
|
112
|
+
currencyArg,
|
|
113
|
+
options,
|
|
114
|
+
});
|
|
115
|
+
const opt = {
|
|
101
116
|
style: 'currency',
|
|
102
|
-
currency:
|
|
103
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
3454
|
-
'
|
|
3455
|
-
|
|
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: '
|
|
3580
|
+
status: 'DisponÃvel',
|
|
3475
3581
|
price: 980.0,
|
|
3476
3582
|
imageUrl: '/list-demo-hotel-1.svg',
|
|
3477
3583
|
},
|
|
3478
3584
|
{
|
|
3479
|
-
title: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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
|
|
3758
|
+
action.confirmation.title = 'Confirmar ação';
|
|
3653
3759
|
if (!action.confirmation.message)
|
|
3654
3760
|
action.confirmation.message =
|
|
3655
|
-
'Esta
|
|
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 = '
|
|
3765
|
+
action.confirmation.title = 'Atenção';
|
|
3660
3766
|
if (!action.confirmation.message)
|
|
3661
|
-
action.confirmation.message = 'Deseja prosseguir com esta
|
|
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 = '
|
|
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
|
|
3810
|
+
return entry?.payloadSchema?.example ? 'Exemplo disponÃvel' : '';
|
|
3705
3811
|
}
|
|
3706
3812
|
globalPayloadSchemaTooltip(action) {
|
|
3707
3813
|
if (!action?.command)
|
|
3708
|
-
return 'Sem schema
|
|
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
|
|
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) ? ' (
|
|
3830
|
+
const req = required.has(name) ? ' (OBRIGATÓRIO)' : '';
|
|
3725
3831
|
const type = meta?.type ? `: ${meta.type}` : '';
|
|
3726
|
-
const desc = 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
|
|
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 = /
|
|
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.: {\"status\":\"active\",\"department\":\"sales\"}'\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.: ${item.status} ==\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: "${item.campo} == 'valor'". 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 {\n border-radius: 14px;\n border: 1px solid var(--md-sys-color-outline-variant);\n box-shadow: var(--md-sys-elevation-level2);\n}\n.my-list-skin .mat-mdc-list-item .list-item-content {\n backdrop-filter: blur(6px);\n}</pre\n >\n </div>\n </div>\n </div>\n </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.: {\"status\":\"active\",\"department\":\"sales\"}'\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.: ${item.status} ==\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: "${item.campo} == 'valor'". 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 {\n border-radius: 14px;\n border: 1px solid var(--md-sys-color-outline-variant);\n box-shadow: var(--md-sys-elevation-level2);\n}\n.my-list-skin .mat-mdc-list-item .list-item-content {\n backdrop-filter: blur(6px);\n}</pre\n >\n </div>\n </div>\n </div>\n </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.: {\"status\":\"active\",\"department\":\"sales\"}'\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.: ${item.status} ==\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: "${item.campo} == 'valor'". 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 {\n border-radius: 14px;\n border: 1px solid var(--md-sys-color-outline-variant);\n box-shadow: var(--md-sys-elevation-level2);\n}\n.my-list-skin .mat-mdc-list-item .list-item-content {\n backdrop-filter: blur(6px);\n}</pre\n >\n </div>\n </div>\n </div>\n </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.: {\"status\":\"active\",\"department\":\"sales\"}'\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.: ${item.status} ==\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: "${item.campo} == 'valor'". 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 {\n border-radius: 14px;\n border: 1px solid var(--md-sys-color-outline-variant);\n box-shadow: var(--md-sys-elevation-level2);\n}\n.my-list-skin .mat-mdc-list-item .list-item-content {\n backdrop-filter: blur(6px);\n}</pre\n >\n </div>\n </div>\n </div>\n </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];
|