@praxisui/metadata-editor 0.0.1

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.
@@ -0,0 +1,4182 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, inject, Component, Input, EventEmitter, Output, signal } from '@angular/core';
3
+ import * as i3 from '@angular/forms';
4
+ import { FormControl, FormGroup, ReactiveFormsModule, FormsModule } from '@angular/forms';
5
+ import * as i3$1 from '@angular/common';
6
+ import { CommonModule } from '@angular/common';
7
+ import { DynamicFieldLoaderDirective } from '@praxisui/dynamic-fields';
8
+ import * as i2$1 from '@praxisui/core';
9
+ import { FieldControlType, PraxisIconDirective } from '@praxisui/core';
10
+ import * as i5 from '@angular/material/expansion';
11
+ import { MatExpansionModule } from '@angular/material/expansion';
12
+ import * as i6 from '@angular/material/icon';
13
+ import { MatIconModule } from '@angular/material/icon';
14
+ import * as i5$1 from '@angular/material/button';
15
+ import { MatButtonModule } from '@angular/material/button';
16
+ import * as i1 from '@angular/material/dialog';
17
+ import { MatDialogRef, MatDialogModule } from '@angular/material/dialog';
18
+ import * as i8 from '@angular/material/tooltip';
19
+ import { MatTooltipModule } from '@angular/material/tooltip';
20
+ import * as i2 from '@angular/material/list';
21
+ import { MatListModule } from '@angular/material/list';
22
+ import { SETTINGS_PANEL_DATA } from '@praxisui/settings-panel';
23
+ import { BehaviorSubject } from 'rxjs';
24
+ import * as i7 from '@angular/material/input';
25
+ import { MatInputModule } from '@angular/material/input';
26
+ import { MatChipsModule } from '@angular/material/chips';
27
+ import { MatDividerModule } from '@angular/material/divider';
28
+ import * as i8$1 from '@angular/material/select';
29
+ import { MatSelectModule } from '@angular/material/select';
30
+ import * as i9 from '@angular/material/checkbox';
31
+ import { MatCheckboxModule } from '@angular/material/checkbox';
32
+ import * as i11 from '@angular/material/menu';
33
+ import { MatMenuModule } from '@angular/material/menu';
34
+ import * as i12 from '@angular/cdk/scrolling';
35
+ import { ScrollingModule } from '@angular/cdk/scrolling';
36
+
37
+ class ConfigRegistryService {
38
+ map = new Map();
39
+ register(controlType, props) {
40
+ this.map.set(controlType, props);
41
+ }
42
+ getProperties(controlType) {
43
+ return this.map.get(controlType) ?? [];
44
+ }
45
+ list() {
46
+ return Array.from(this.map.entries()).map(([ct, props]) => ({
47
+ controlType: ct,
48
+ count: props.length,
49
+ }));
50
+ }
51
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ConfigRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
52
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ConfigRegistryService, providedIn: 'root' });
53
+ }
54
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ConfigRegistryService, decorators: [{
55
+ type: Injectable,
56
+ args: [{ providedIn: 'root' }]
57
+ }] });
58
+
59
+ class ContextValidatorRegistryService {
60
+ map = new Map();
61
+ register(controlType, validator) {
62
+ const list = this.map.get(controlType) ?? [];
63
+ list.push(validator);
64
+ this.map.set(controlType, list);
65
+ }
66
+ get(controlType) {
67
+ return this.map.get(controlType) ?? [];
68
+ }
69
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ContextValidatorRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
70
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ContextValidatorRegistryService, providedIn: 'root' });
71
+ }
72
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ContextValidatorRegistryService, decorators: [{
73
+ type: Injectable,
74
+ args: [{ providedIn: 'root' }]
75
+ }] });
76
+
77
+ class SchemaNormalizerService {
78
+ /** Normaliza propriedades e aplica defaultValue resolvendo do seed por dot-path. */
79
+ normalize(properties, seed) {
80
+ const out = properties.map((p) => {
81
+ const clone = { ...p };
82
+ // normalizar options se for string simples
83
+ if (Array.isArray(clone.options)) {
84
+ clone.options = clone.options.map((opt) => typeof opt === 'string' ? { value: opt, text: opt } : opt);
85
+ }
86
+ // pattern/regex
87
+ if (clone.pattern && typeof clone.pattern === 'string') {
88
+ const str = String(clone.pattern);
89
+ const match = /^\/(.*)\/([gimsuy]*)$/.exec(str);
90
+ if (match) {
91
+ try {
92
+ clone.pattern = new RegExp(match[1], match[2]);
93
+ }
94
+ catch { }
95
+ }
96
+ }
97
+ // aplicar defaultValue do seed se existir
98
+ if (seed && clone.defaultValue === undefined) {
99
+ const val = this.getByPath(seed, clone.name);
100
+ if (val !== undefined) {
101
+ // Stringify complex defaults for textarea-friendly editors
102
+ const needsJson = (clone.editorType === 'textarea' &&
103
+ (clone.name === 'options' ||
104
+ clone.name === 'filterCriteria' ||
105
+ clone.name === 'paletteSettings.colors' ||
106
+ clone.name === 'views' ||
107
+ clone.name === 'adaptiveSubtitle'));
108
+ clone.defaultValue = needsJson && typeof val !== 'string' ? JSON.stringify(val, null, 2) : val;
109
+ try {
110
+ if (clone.name === 'resourcePath' ||
111
+ clone.name === 'filterCriteria' ||
112
+ clone.name === 'optionLabelKey' ||
113
+ clone.name === 'optionValueKey' ||
114
+ clone.name === 'options' ||
115
+ clone.name === 'paletteSettings.colors' ||
116
+ clone.name === 'views') {
117
+ console.debug('[SchemaNormalizer] default from seed', {
118
+ name: clone.name,
119
+ value: needsJson && typeof val !== 'string' ? JSON.stringify(val) : val,
120
+ });
121
+ }
122
+ // Extra diagnostics for Rating editor fields
123
+ if ([
124
+ 'max',
125
+ 'allowHalf',
126
+ 'precision',
127
+ 'selection',
128
+ 'size',
129
+ 'icon',
130
+ 'emptyIcon',
131
+ 'svgIcon',
132
+ 'svgIconOutline',
133
+ 'ratingColor',
134
+ 'outlineColor',
135
+ 'readonly',
136
+ 'tabIndex',
137
+ 'ariaLabel',
138
+ ].includes(clone.name)) {
139
+ console.debug('[SchemaNormalizer][RATING] default from seed', {
140
+ name: clone.name,
141
+ value: val,
142
+ });
143
+ }
144
+ }
145
+ catch { }
146
+ }
147
+ }
148
+ else if (seed && clone.defaultValue !== undefined) {
149
+ // Log when a property defines its own defaultValue and thus does not read from seed
150
+ try {
151
+ if (clone.name === 'resourcePath' ||
152
+ clone.name === 'filterCriteria' ||
153
+ clone.name === 'optionLabelKey' ||
154
+ clone.name === 'optionValueKey' ||
155
+ clone.name === 'options') {
156
+ const val = this.getByPath(seed, clone.name);
157
+ console.debug('[SchemaNormalizer] default preserved (property default overrides seed)', {
158
+ name: clone.name,
159
+ propertyDefault: clone.defaultValue,
160
+ seedValue: val,
161
+ });
162
+ }
163
+ }
164
+ catch { }
165
+ }
166
+ return clone;
167
+ });
168
+ try {
169
+ const names = out.map((p) => p.name);
170
+ const defPreview = out
171
+ .filter((p) => ['resourcePath', 'filterCriteria', 'optionLabelKey', 'optionValueKey'].includes(p.name))
172
+ .map((p) => ({ name: p.name, defaultValue: p.defaultValue }));
173
+ console.debug('[SchemaNormalizer] normalize summary', { names, defPreview });
174
+ }
175
+ catch { }
176
+ return out;
177
+ }
178
+ /** Lê valor por dot-path */
179
+ getByPath(obj, path) {
180
+ return path.split('.').reduce((acc, key) => (acc == null ? acc : acc[key]), obj);
181
+ }
182
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SchemaNormalizerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
183
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SchemaNormalizerService, providedIn: 'root' });
184
+ }
185
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SchemaNormalizerService, decorators: [{
186
+ type: Injectable,
187
+ args: [{ providedIn: 'root' }]
188
+ }] });
189
+
190
+ class DynamicFormFactoryService {
191
+ fb;
192
+ ctxValidators;
193
+ constructor(fb, ctxValidators) {
194
+ this.fb = fb;
195
+ this.ctxValidators = ctxValidators;
196
+ }
197
+ createEditorForm(controlType, props, seed) {
198
+ const group = this.fb.group({});
199
+ for (const p of props) {
200
+ const ctrl = new FormControl(p.defaultValue ?? null, { validators: p.validators ?? [] });
201
+ this.assignControlByPath(group, p.name, ctrl);
202
+ try {
203
+ if (p.name === 'resourcePath' ||
204
+ p.name === 'filterCriteria' ||
205
+ p.name === 'optionLabelKey' ||
206
+ p.name === 'optionValueKey') {
207
+ console.debug('[EditorForm] control init', {
208
+ name: p.name,
209
+ defaultValue: p.defaultValue ?? null,
210
+ });
211
+ }
212
+ }
213
+ catch { }
214
+ }
215
+ const formValidators = this.ctxValidators.get(controlType);
216
+ if (formValidators.length) {
217
+ group.addValidators(formValidators);
218
+ }
219
+ return group;
220
+ }
221
+ getControlByPath(form, path) {
222
+ return form.get(path);
223
+ }
224
+ setValueByPath(form, path, value) {
225
+ form.get(path)?.setValue(value);
226
+ }
227
+ extractPatch(form) {
228
+ const patch = form.getRawValue();
229
+ try {
230
+ console.debug('[EditorForm] extractPatch', patch);
231
+ }
232
+ catch { }
233
+ return patch;
234
+ }
235
+ mergeIntoFieldDefinition(base, patch) {
236
+ return this.deepMerge(base, patch);
237
+ }
238
+ assignControlByPath(group, path, ctrl) {
239
+ const parts = path.split('.');
240
+ let current = group;
241
+ for (let i = 0; i < parts.length - 1; i++) {
242
+ const key = parts[i];
243
+ const next = current.get(key);
244
+ if (!next) {
245
+ const child = this.fb.group({});
246
+ current.addControl(key, child);
247
+ current = child;
248
+ }
249
+ else if (next instanceof FormGroup) {
250
+ current = next;
251
+ }
252
+ else {
253
+ // Overwrite non-group path with group to respect dot-notation
254
+ const child = this.fb.group({});
255
+ current.removeControl(key);
256
+ current.addControl(key, child);
257
+ current = child;
258
+ }
259
+ }
260
+ current.addControl(parts[parts.length - 1], ctrl);
261
+ }
262
+ deepMerge(target, source) {
263
+ if (source == null || typeof source !== 'object')
264
+ return target;
265
+ const result = Array.isArray(target) ? [...target] : { ...target };
266
+ for (const [k, v] of Object.entries(source)) {
267
+ if (v && typeof v === 'object' && !Array.isArray(v)) {
268
+ result[k] = this.deepMerge(result[k] ?? {}, v);
269
+ }
270
+ else {
271
+ result[k] = v;
272
+ }
273
+ }
274
+ return result;
275
+ }
276
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DynamicFormFactoryService, deps: [{ token: i3.FormBuilder }, { token: ContextValidatorRegistryService }], target: i0.ɵɵFactoryTarget.Injectable });
277
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DynamicFormFactoryService, providedIn: 'root' });
278
+ }
279
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DynamicFormFactoryService, decorators: [{
280
+ type: Injectable,
281
+ args: [{ providedIn: 'root' }]
282
+ }], ctorParameters: () => [{ type: i3.FormBuilder }, { type: ContextValidatorRegistryService }] });
283
+
284
+ class EditorComponentRegistryService {
285
+ registry = new Map();
286
+ cache = new Map();
287
+ register(editorType, loader) {
288
+ this.registry.set(editorType, loader);
289
+ }
290
+ async get(editorType) {
291
+ if (this.cache.has(editorType)) {
292
+ return this.cache.get(editorType);
293
+ }
294
+ const loader = this.registry.get(editorType);
295
+ if (!loader)
296
+ return null;
297
+ const type = await loader();
298
+ if (type)
299
+ this.cache.set(editorType, type);
300
+ return type ?? null;
301
+ }
302
+ isRegistered(editorType) {
303
+ return this.registry.has(editorType);
304
+ }
305
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: EditorComponentRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
306
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: EditorComponentRegistryService, providedIn: 'root' });
307
+ }
308
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: EditorComponentRegistryService, decorators: [{
309
+ type: Injectable,
310
+ args: [{ providedIn: 'root' }]
311
+ }] });
312
+
313
+ class RegexHelpDialogComponent {
314
+ dialogRef = inject((MatDialogRef));
315
+ presets = [
316
+ { label: 'Apenas letras (ASCII)', pattern: '/^[a-zA-Z]+$/' },
317
+ { label: 'Apenas dígitos', pattern: '/^\\d+$/' },
318
+ { label: 'Email simples', pattern: '/^\\w+@[\\w.-]+\\.[A-Za-z]&#123;2,&#125;$/' },
319
+ { label: 'URL básica (http/https)', pattern: '/^(https?:\\/\\/)?[\\w.-]+(\\\\.[\\w.-]+)+[\\w\\-._~:?#@!$&\'()*+,;=\\/]*$/' },
320
+ { label: 'Pelo menos 8 caracteres', pattern: '/^.&#123;8,&#125;$/' },
321
+ { label: 'CEP brasileiro (#####-###)', pattern: '/^\\d{5}-\\d{3}$/' },
322
+ { label: 'CPF (###.###.###-##)', pattern: '/^\\d{3}\\.\\d{3}\\.\\d{3}-\\d{2}$/' },
323
+ { label: 'CNPJ (##.###.###/####-##)', pattern: '/^\\d{2}\\.\\d{3}\\.\\d{3}\\/\\d{4}-\\d{2}$/' },
324
+ { label: 'Telefone (Brasil) com DDD', pattern: '/^\\(\\d{2}\\) \\d{4,5}-\\d{4}$/' },
325
+ { label: 'Somente hex (0-9 A-F)', pattern: '/^[0-9A-Fa-f]+$/' },
326
+ { label: 'Código postal (US ZIP)', pattern: '/^\\d{5}(-\\d{4})?$/' },
327
+ { label: 'Data (YYYY-MM-DD)', pattern: '/^\\d{4}-\\d{2}-\\d{2}$/' },
328
+ { label: 'Hora (HH:MM 24h)', pattern: '/^([01]\\d|2[0-3]):[0-5]\\d$/' },
329
+ { label: 'Slug (letras, dígitos, hífen)', pattern: '/^[a-z0-9-]+$/' },
330
+ { label: 'Username (3-16, alfanum., _ -)', pattern: '/^[a-zA-Z0-9_-]{3,16}$/' },
331
+ { label: 'Senha forte (8+, maiúsc., minúsc., dígito)', pattern: '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).&#123;8,&#125;$/' },
332
+ { label: 'IPv4', pattern: '/^(25[0-5]|2[0-4]\\d|[01]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[01]?\\d?\\d))&#123;3&#125;$/' },
333
+ { label: 'MAC address', pattern: '/^([0-9A-Fa-f]{2}:)&#123;5&#125;[0-9A-Fa-f]{2}$/' },
334
+ { label: 'Hashtag (#palavra)', pattern: '/^#\\w+$/' },
335
+ { label: 'Menção (@usuario)', pattern: '/^@\\w+$/' },
336
+ { label: 'Palavra específica (foo)', pattern: '/\\bfoo\\b/' },
337
+ { label: 'Não conter espaços', pattern: '/^\\S+$/' },
338
+ { label: 'Início com letra', pattern: '/^[A-Za-z].*$/' },
339
+ { label: 'Termina com ponto final', pattern: '/.*\\.$/' },
340
+ { label: 'Número com 2 casas decimais', pattern: '/^\\d+(,\\d{2}|\\.\\d{2})$/' },
341
+ { label: 'Hex color (#RRGGBB)', pattern: '/^#?[A-Fa-f0-9]{6}$/' },
342
+ { label: 'UUID v4', pattern: '/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i' },
343
+ { label: 'DOM id (letra inicial)', pattern: '/^[A-Za-z][-A-Za-z0-9_:.]*$/' },
344
+ { label: 'Base64', pattern: '/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/' },
345
+ ];
346
+ choose(pattern) {
347
+ this.dialogRef.close(pattern);
348
+ }
349
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: RegexHelpDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
350
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: RegexHelpDialogComponent, isStandalone: true, selector: "praxis-regex-help-dialog", ngImport: i0, template: `
351
+ <h2 mat-dialog-title>Modelos de Expressões Regulares</h2>
352
+ <div mat-dialog-content>
353
+ <p>Selecione um dos modelos abaixo para preencher o campo ou use como base:</p>
354
+ <mat-nav-list dense>
355
+ @for (r of presets; track r.pattern) {
356
+ <a mat-list-item (click)="choose(r.pattern)" style="cursor: pointer;">
357
+ <span matListItemTitle><code>{{ r.pattern }}</code></span>
358
+ <span matListItemLine>{{ r.label }}</span>
359
+ </a>
360
+ }
361
+ </mat-nav-list>
362
+ <p style="margin-top: 12px;">Recursos úteis:</p>
363
+ <ul>
364
+ <li><a href="https://regex101.com/" target="_blank" rel="noreferrer noopener">regex101.com</a> — Teste e depure regex</li>
365
+ <li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions" target="_blank" rel="noreferrer noopener">MDN: Regular expressions</a></li>
366
+ </ul>
367
+ </div>
368
+ <div mat-dialog-actions>
369
+ <button class="mat-mdc-button mdc-button" mat-dialog-close>Fechar</button>
370
+ </div>
371
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i2.MatNavList, selector: "mat-nav-list", exportAs: ["matNavList"] }, { kind: "component", type: i2.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "directive", type: i2.MatListItemLine, selector: "[matListItemLine]" }, { kind: "directive", type: i2.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "ngmodule", type: MatButtonModule }] });
372
+ }
373
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: RegexHelpDialogComponent, decorators: [{
374
+ type: Component,
375
+ args: [{
376
+ selector: 'praxis-regex-help-dialog',
377
+ standalone: true,
378
+ imports: [CommonModule, MatDialogModule, MatListModule, MatButtonModule],
379
+ template: `
380
+ <h2 mat-dialog-title>Modelos de Expressões Regulares</h2>
381
+ <div mat-dialog-content>
382
+ <p>Selecione um dos modelos abaixo para preencher o campo ou use como base:</p>
383
+ <mat-nav-list dense>
384
+ @for (r of presets; track r.pattern) {
385
+ <a mat-list-item (click)="choose(r.pattern)" style="cursor: pointer;">
386
+ <span matListItemTitle><code>{{ r.pattern }}</code></span>
387
+ <span matListItemLine>{{ r.label }}</span>
388
+ </a>
389
+ }
390
+ </mat-nav-list>
391
+ <p style="margin-top: 12px;">Recursos úteis:</p>
392
+ <ul>
393
+ <li><a href="https://regex101.com/" target="_blank" rel="noreferrer noopener">regex101.com</a> — Teste e depure regex</li>
394
+ <li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions" target="_blank" rel="noreferrer noopener">MDN: Regular expressions</a></li>
395
+ </ul>
396
+ </div>
397
+ <div mat-dialog-actions>
398
+ <button class="mat-mdc-button mdc-button" mat-dialog-close>Fechar</button>
399
+ </div>
400
+ `,
401
+ }]
402
+ }] });
403
+
404
+ class DynamicEditorRendererComponent {
405
+ dialog;
406
+ iconPicker;
407
+ properties = [];
408
+ form;
409
+ fields = [];
410
+ groupFields = {};
411
+ groupOrder = [];
412
+ groupRows = {};
413
+ // Evita reentrância: cria loaders após o primeiro tick
414
+ ready = false;
415
+ ngOnChanges(changes) {
416
+ if (changes['properties']) {
417
+ const mapped = this.properties.map((p) => this.toFieldMetadata(p));
418
+ this.fields = mapped;
419
+ const groupMap = new Map();
420
+ const rowMap = new Map();
421
+ for (const f of mapped) {
422
+ const g = f._group || 'Geral';
423
+ if (!groupMap.has(g))
424
+ groupMap.set(g, []);
425
+ groupMap.get(g).push(f);
426
+ const rowKey = f._row || `${f.name}`;
427
+ if (!rowMap.has(g))
428
+ rowMap.set(g, new Map());
429
+ const rmap = rowMap.get(g);
430
+ if (!rmap.has(rowKey))
431
+ rmap.set(rowKey, []);
432
+ rmap.get(rowKey).push(f);
433
+ }
434
+ this.groupFields = Object.fromEntries(groupMap.entries());
435
+ this.groupOrder = Array.from(groupMap.keys()).sort((a, b) => this.groupWeight(a) - this.groupWeight(b));
436
+ const rows = {};
437
+ for (const g of this.groupOrder) {
438
+ const rmap = rowMap.get(g);
439
+ rows[g] = Array.from(rmap.values());
440
+ }
441
+ this.groupRows = rows;
442
+ // Ativar loaders após agrupar propriedades
443
+ queueMicrotask?.(() => (this.ready = true));
444
+ }
445
+ }
446
+ toFieldMetadata(p) {
447
+ const controlType = this.mapEditorType(p.editorType);
448
+ const meta = {
449
+ name: p.name,
450
+ label: p.label,
451
+ controlType,
452
+ placeholder: p.placeholder,
453
+ // Ajuda contextual padrão quando ausente
454
+ hint: p.hint,
455
+ options: Array.isArray(p.options) ? p.options : undefined,
456
+ required: p.required,
457
+ // meta auxiliar de agrupamento (não exportado)
458
+ _group: p.group || 'Geral',
459
+ _inline: p.inline || false,
460
+ _row: p.row || undefined,
461
+ // validators do EditorProperty já foram aplicados ao FormControl pela fábrica,
462
+ // mas manter aqui não prejudica e habilita exibição de mensagens padrão.
463
+ validators: undefined,
464
+ };
465
+ // Priorização de campos principais (agrupa em uma linha "core")
466
+ const coreNames = new Set(['controlType', 'label', 'placeholder']);
467
+ if (coreNames.has(p.name) && !meta._row)
468
+ meta._row = '__core';
469
+ // Campos menores e de baixa relevância visual (agrupados inline)
470
+ const smallNames = new Set([
471
+ 'prefix',
472
+ 'suffix',
473
+ 'prefixIcon',
474
+ 'suffixIcon',
475
+ 'prefixIconColor',
476
+ 'suffixIconColor',
477
+ 'icon',
478
+ ]);
479
+ if (smallNames.has(p.name)) {
480
+ meta._inline = true;
481
+ if (!meta._row)
482
+ meta._row = '__affixes';
483
+ }
484
+ // Apenas campos de ícone devem receber o hint de Material Symbols
485
+ if (this.isIconField(p.name) && !meta.hint) {
486
+ meta.hint = 'Use o nome do ícone (Material Symbols)';
487
+ }
488
+ // Booleans em linha (switches) para melhor densidade
489
+ const booleanLike = new Set([
490
+ 'required',
491
+ 'disabled',
492
+ 'readonly',
493
+ 'showCharCount',
494
+ 'showClear',
495
+ 'autoFocus',
496
+ 'hideLabel',
497
+ ]);
498
+ if (p.editorType === 'checkbox' || booleanLike.has(p.name)) {
499
+ meta._inline = true;
500
+ if (!meta._row)
501
+ meta._row = '__toggles';
502
+ }
503
+ // Mensagem padrão de atenção ao trocar tipo de campo
504
+ if (p.name === 'controlType' && !meta.hint) {
505
+ meta.hint = 'Alterar o tipo pode impactar configurações e validações';
506
+ }
507
+ if (p.editorType === 'number') {
508
+ // Defaults for numeric editors
509
+ if (/minlength|maxlength/i.test(p.name)) {
510
+ meta.min = 0;
511
+ meta.step = 1;
512
+ }
513
+ }
514
+ // no editor-specific overrides for textarea sizing here (reverted)
515
+ return meta;
516
+ }
517
+ // Priorização das seções
518
+ groupWeight(group) {
519
+ const key = group.toLowerCase();
520
+ const order = [
521
+ 'geral',
522
+ 'apresent',
523
+ 'valida',
524
+ 'formato',
525
+ 'comport',
526
+ 'material',
527
+ 'ações',
528
+ 'acess',
529
+ ];
530
+ const idx = order.findIndex((k) => key.includes(k));
531
+ return idx >= 0 ? idx : 999;
532
+ }
533
+ // No section open by default
534
+ isInitiallyExpanded(_group) {
535
+ return false;
536
+ }
537
+ // (preview removed by request)
538
+ rowClass(row) {
539
+ // Linha compacta (toggles) quando todos são inline
540
+ if (row.every((f) => !!f._inline))
541
+ return 'row-inline';
542
+ // Se não for inline e houver 2 ou 3 itens, usar colunas iguais
543
+ const n = row.length;
544
+ if (n === 2)
545
+ return 'row-2';
546
+ if (n === 3)
547
+ return 'row-3';
548
+ return '';
549
+ }
550
+ mapEditorType(t) {
551
+ switch (t) {
552
+ case 'text':
553
+ return FieldControlType.INPUT;
554
+ case 'number':
555
+ return FieldControlType.NUMERIC_TEXT_BOX;
556
+ case 'textarea':
557
+ return FieldControlType.TEXTAREA;
558
+ case 'select':
559
+ return FieldControlType.SELECT;
560
+ case 'checkbox':
561
+ // Para booleano simples, usamos o toggle
562
+ return FieldControlType.TOGGLE;
563
+ case 'date':
564
+ return FieldControlType.DATE_INPUT;
565
+ case 'datetime-local':
566
+ return FieldControlType.DATETIME_LOCAL_INPUT;
567
+ case 'color':
568
+ return FieldControlType.COLOR_INPUT;
569
+ case 'colorPicker':
570
+ return FieldControlType.COLOR_PICKER;
571
+ default:
572
+ return FieldControlType.INPUT;
573
+ }
574
+ }
575
+ groupIcon(group) {
576
+ const key = group.toLowerCase();
577
+ if (key.includes('valida'))
578
+ return 'rule';
579
+ if (key.includes('formato') || key.includes('másc'))
580
+ return 'format_shapes';
581
+ if (key.includes('apresent'))
582
+ return 'display_settings';
583
+ if (key.includes('lista') || key.includes('opç'))
584
+ return 'list';
585
+ return 'tune';
586
+ }
587
+ constructor(dialog, iconPicker) {
588
+ this.dialog = dialog;
589
+ this.iconPicker = iconPicker;
590
+ }
591
+ isIconField(path) {
592
+ if (!path)
593
+ return false;
594
+ // Supports: prefixIcon, suffixIcon, icon, clearButton.icon, any *.icon
595
+ return path === 'prefixIcon' || path === 'suffixIcon' || path === 'icon' || /(^|\.)icon$/.test(path);
596
+ }
597
+ openRegexHelp() {
598
+ const ref = this.dialog.open(RegexHelpDialogComponent, {
599
+ width: '720px',
600
+ });
601
+ ref.afterClosed().subscribe((selected) => {
602
+ if (!selected)
603
+ return;
604
+ const ctrl = this.form?.get('pattern');
605
+ ctrl?.setValue(selected);
606
+ ctrl?.markAsDirty();
607
+ this.form?.markAsDirty();
608
+ });
609
+ }
610
+ async openIconPicker(path) {
611
+ const current = this.form?.get(path)?.value;
612
+ const picked = await this.iconPicker.openDialog({ value: current });
613
+ if (picked !== undefined) {
614
+ this.form?.get(path)?.setValue(picked);
615
+ this.form?.markAsDirty();
616
+ }
617
+ }
618
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DynamicEditorRendererComponent, deps: [{ token: i1.MatDialog }, { token: i2$1.IconPickerService }], target: i0.ɵɵFactoryTarget.Component });
619
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: DynamicEditorRendererComponent, isStandalone: true, selector: "praxis-dynamic-editor-renderer", inputs: { properties: "properties", form: "form" }, usesOnChanges: true, ngImport: i0, template: `
620
+ <ng-template #item let-field="field" let-index="index" let-content="content">
621
+ <div class="editor-item" style="position: relative;">
622
+ <ng-container [ngTemplateOutlet]="content"></ng-container>
623
+ <!-- Botão de ajuda para regex, posicionado como sufixo visual do campo -->
624
+ <button
625
+ *ngIf="field.name === 'pattern'"
626
+ type="button"
627
+ mat-icon-button
628
+ class="regex-help-btn decorator-btn"
629
+ (click)="openRegexHelp()"
630
+ aria-label="Exemplos de regex"
631
+ matTooltip="Exemplos de regex"
632
+ >
633
+ <mat-icon [praxisIcon]="'search'"></mat-icon>
634
+ </button>
635
+ <!-- Botão para escolher ícone (prefix/suffix) -->
636
+ <button
637
+ *ngIf="isIconField(field.name)"
638
+ type="button"
639
+ mat-icon-button
640
+ class="icon-picker-btn decorator-btn"
641
+ (click)="openIconPicker(field.name)"
642
+ aria-label="Escolher ícone"
643
+ matTooltip="Escolher ícone"
644
+ >
645
+ <mat-icon [praxisIcon]="'apps'"></mat-icon>
646
+ </button>
647
+ </div>
648
+ </ng-template>
649
+
650
+ <div class="editor-surface">
651
+ <mat-accordion class="section-accordion" multi>
652
+ <mat-expansion-panel class="section-card" *ngFor="let g of groupOrder">
653
+ <mat-expansion-panel-header class="section-header">
654
+ <mat-panel-title>
655
+ <span class="section-title">
656
+ <mat-icon class="section-icon" [praxisIcon]="groupIcon(g)"></mat-icon>
657
+ {{ g }}
658
+ </span>
659
+ </mat-panel-title>
660
+ </mat-expansion-panel-header>
661
+
662
+ <div *ngFor="let row of groupRows[g]" class="editor-row" [ngClass]="rowClass(row)">
663
+ <ng-container *ngIf="ready"
664
+ dynamicFieldLoader
665
+ [fields]="row"
666
+ [formGroup]="form"
667
+ [itemTemplate]="item">
668
+ </ng-container>
669
+ </div>
670
+ </mat-expansion-panel>
671
+ </mat-accordion>
672
+ </div>
673
+ `, isInline: true, styles: [":host{display:block}.editor-surface{padding:8px 8px 2px;border-radius:12px;background:transparent}.section-accordion{display:block}.section-card{margin:6px 0 8px;border-radius:12px;border:1px solid rgba(0,0,0,.08);overflow:hidden}.section-card.mat-expanded{border-color:color-mix(in srgb,var(--mat-sys-primary, #3f51b5) 20%,transparent);box-shadow:0 2px 10px #0000000d}.section-card .mat-expansion-panel-body{padding:12px 12px 8px;background:linear-gradient(180deg,color-mix(in srgb,var(--md-sys-color-surface, var(--sicoob-bg-elev-1)) 2%),transparent)}.section-header{background:linear-gradient(180deg,color-mix(in srgb,var(--md-sys-color-surface, var(--sicoob-bg-elev-2)) 92%,var(--md-sys-color-primary, var(--sicoob-primary-default))) 0%,var(--md-sys-color-surface, var(--sicoob-bg-elev-2)) 100%);border-bottom:1px solid var(--md-sys-color-outline-variant, var(--sicoob-stroke-medium));padding:2px 8px;min-height:32px;display:flex;align-items:center}.section-header .mat-expansion-panel-header-title,.section-header .mat-expansion-panel-header-description{display:flex;align-items:center}.section-title{display:inline-flex;align-items:center;gap:6px;font-weight:600;letter-spacing:.2px;color:var(--mat-sys-color-on-surface, var(--md-sys-color-on-surface, rgba(0,0,0,.87)));font-size:.9rem;line-height:1.2}.section-icon{color:var(--mat-sys-color-primary, var(--md-sys-color-primary, #3f51b5));opacity:.9}.editor-row{display:flex;gap:12px;align-items:flex-start;flex-wrap:wrap;margin-bottom:8px}.editor-row>praxis-field-shell{flex:1 1 100%;min-width:280px}.editor-row praxis-field-shell .mat-mdc-form-field{width:100%;margin-bottom:8px}.editor-row.row-inline>praxis-field-shell{flex:0 0 auto}.editor-row.row-2>praxis-field-shell{flex:1 1 calc(50% - 12px);min-width:260px}.editor-row.row-3>praxis-field-shell{flex:1 1 calc(33.333% - 12px);min-width:220px}.editor-row>praxis-field-shell[data-field-name=controlType],.editor-row>praxis-field-shell[data-field-name=label],.editor-row>praxis-field-shell[data-field-name=placeholder]{flex:1 1 calc(50% - 12px);min-width:300px}praxis-field-shell[data-field-name=label] .mdc-floating-label,praxis-field-shell[data-field-name=placeholder] .mdc-floating-label{font-weight:600}.editor-row>praxis-field-shell[data-field-name=prefix],.editor-row>praxis-field-shell[data-field-name=suffix],.editor-row>praxis-field-shell[data-field-name=prefixIcon],.editor-row>praxis-field-shell[data-field-name=suffixIcon],.editor-row>praxis-field-shell[data-field-name=prefixIconColor],.editor-row>praxis-field-shell[data-field-name=suffixIconColor]{flex:0 0 200px;min-width:160px}.decorator-btn{position:absolute;right:6px;top:8px;z-index:2;color:var(--mat-sys-primary, #3f51b5);opacity:.85}praxis-field-shell[data-field-name=showCharCount] .mdc-switch{transform:scale(1.05)}praxis-field-shell[data-field-name=showCharCount] .mat-mdc-slide-toggle{font-weight:600}praxis-field-shell[data-field-name=controlType] .mat-mdc-form-field-hint{display:flex;align-items:center;gap:8px;background:#ffab001f;border:1px solid rgba(255,171,0,.35);color:#ffca28;padding:8px 10px;border-radius:6px;line-height:1.3;white-space:normal}praxis-field-shell[data-field-name=controlType] .mat-mdc-form-field-hint:before{content:\"\\26a0\\fe0f\";display:inline-block}praxis-field-shell[data-field-name=controlType] .mat-mdc-form-field-subscript-wrapper{margin-top:6px;margin-bottom:10px;min-height:auto}praxis-field-shell[data-field-name=controlType] .mat-mdc-form-field{margin-bottom:16px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "canvasMouseEnter", "canvasMouseLeave", "canvasClick"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i5.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i5.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i5.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i5.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i5$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }] });
674
+ }
675
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DynamicEditorRendererComponent, decorators: [{
676
+ type: Component,
677
+ args: [{ selector: 'praxis-dynamic-editor-renderer', standalone: true, imports: [
678
+ CommonModule,
679
+ ReactiveFormsModule,
680
+ DynamicFieldLoaderDirective,
681
+ MatExpansionModule,
682
+ MatIconModule,
683
+ MatButtonModule,
684
+ MatDialogModule,
685
+ MatTooltipModule,
686
+ PraxisIconDirective,
687
+ ], template: `
688
+ <ng-template #item let-field="field" let-index="index" let-content="content">
689
+ <div class="editor-item" style="position: relative;">
690
+ <ng-container [ngTemplateOutlet]="content"></ng-container>
691
+ <!-- Botão de ajuda para regex, posicionado como sufixo visual do campo -->
692
+ <button
693
+ *ngIf="field.name === 'pattern'"
694
+ type="button"
695
+ mat-icon-button
696
+ class="regex-help-btn decorator-btn"
697
+ (click)="openRegexHelp()"
698
+ aria-label="Exemplos de regex"
699
+ matTooltip="Exemplos de regex"
700
+ >
701
+ <mat-icon [praxisIcon]="'search'"></mat-icon>
702
+ </button>
703
+ <!-- Botão para escolher ícone (prefix/suffix) -->
704
+ <button
705
+ *ngIf="isIconField(field.name)"
706
+ type="button"
707
+ mat-icon-button
708
+ class="icon-picker-btn decorator-btn"
709
+ (click)="openIconPicker(field.name)"
710
+ aria-label="Escolher ícone"
711
+ matTooltip="Escolher ícone"
712
+ >
713
+ <mat-icon [praxisIcon]="'apps'"></mat-icon>
714
+ </button>
715
+ </div>
716
+ </ng-template>
717
+
718
+ <div class="editor-surface">
719
+ <mat-accordion class="section-accordion" multi>
720
+ <mat-expansion-panel class="section-card" *ngFor="let g of groupOrder">
721
+ <mat-expansion-panel-header class="section-header">
722
+ <mat-panel-title>
723
+ <span class="section-title">
724
+ <mat-icon class="section-icon" [praxisIcon]="groupIcon(g)"></mat-icon>
725
+ {{ g }}
726
+ </span>
727
+ </mat-panel-title>
728
+ </mat-expansion-panel-header>
729
+
730
+ <div *ngFor="let row of groupRows[g]" class="editor-row" [ngClass]="rowClass(row)">
731
+ <ng-container *ngIf="ready"
732
+ dynamicFieldLoader
733
+ [fields]="row"
734
+ [formGroup]="form"
735
+ [itemTemplate]="item">
736
+ </ng-container>
737
+ </div>
738
+ </mat-expansion-panel>
739
+ </mat-accordion>
740
+ </div>
741
+ `, styles: [":host{display:block}.editor-surface{padding:8px 8px 2px;border-radius:12px;background:transparent}.section-accordion{display:block}.section-card{margin:6px 0 8px;border-radius:12px;border:1px solid rgba(0,0,0,.08);overflow:hidden}.section-card.mat-expanded{border-color:color-mix(in srgb,var(--mat-sys-primary, #3f51b5) 20%,transparent);box-shadow:0 2px 10px #0000000d}.section-card .mat-expansion-panel-body{padding:12px 12px 8px;background:linear-gradient(180deg,color-mix(in srgb,var(--md-sys-color-surface, var(--sicoob-bg-elev-1)) 2%),transparent)}.section-header{background:linear-gradient(180deg,color-mix(in srgb,var(--md-sys-color-surface, var(--sicoob-bg-elev-2)) 92%,var(--md-sys-color-primary, var(--sicoob-primary-default))) 0%,var(--md-sys-color-surface, var(--sicoob-bg-elev-2)) 100%);border-bottom:1px solid var(--md-sys-color-outline-variant, var(--sicoob-stroke-medium));padding:2px 8px;min-height:32px;display:flex;align-items:center}.section-header .mat-expansion-panel-header-title,.section-header .mat-expansion-panel-header-description{display:flex;align-items:center}.section-title{display:inline-flex;align-items:center;gap:6px;font-weight:600;letter-spacing:.2px;color:var(--mat-sys-color-on-surface, var(--md-sys-color-on-surface, rgba(0,0,0,.87)));font-size:.9rem;line-height:1.2}.section-icon{color:var(--mat-sys-color-primary, var(--md-sys-color-primary, #3f51b5));opacity:.9}.editor-row{display:flex;gap:12px;align-items:flex-start;flex-wrap:wrap;margin-bottom:8px}.editor-row>praxis-field-shell{flex:1 1 100%;min-width:280px}.editor-row praxis-field-shell .mat-mdc-form-field{width:100%;margin-bottom:8px}.editor-row.row-inline>praxis-field-shell{flex:0 0 auto}.editor-row.row-2>praxis-field-shell{flex:1 1 calc(50% - 12px);min-width:260px}.editor-row.row-3>praxis-field-shell{flex:1 1 calc(33.333% - 12px);min-width:220px}.editor-row>praxis-field-shell[data-field-name=controlType],.editor-row>praxis-field-shell[data-field-name=label],.editor-row>praxis-field-shell[data-field-name=placeholder]{flex:1 1 calc(50% - 12px);min-width:300px}praxis-field-shell[data-field-name=label] .mdc-floating-label,praxis-field-shell[data-field-name=placeholder] .mdc-floating-label{font-weight:600}.editor-row>praxis-field-shell[data-field-name=prefix],.editor-row>praxis-field-shell[data-field-name=suffix],.editor-row>praxis-field-shell[data-field-name=prefixIcon],.editor-row>praxis-field-shell[data-field-name=suffixIcon],.editor-row>praxis-field-shell[data-field-name=prefixIconColor],.editor-row>praxis-field-shell[data-field-name=suffixIconColor]{flex:0 0 200px;min-width:160px}.decorator-btn{position:absolute;right:6px;top:8px;z-index:2;color:var(--mat-sys-primary, #3f51b5);opacity:.85}praxis-field-shell[data-field-name=showCharCount] .mdc-switch{transform:scale(1.05)}praxis-field-shell[data-field-name=showCharCount] .mat-mdc-slide-toggle{font-weight:600}praxis-field-shell[data-field-name=controlType] .mat-mdc-form-field-hint{display:flex;align-items:center;gap:8px;background:#ffab001f;border:1px solid rgba(255,171,0,.35);color:#ffca28;padding:8px 10px;border-radius:6px;line-height:1.3;white-space:normal}praxis-field-shell[data-field-name=controlType] .mat-mdc-form-field-hint:before{content:\"\\26a0\\fe0f\";display:inline-block}praxis-field-shell[data-field-name=controlType] .mat-mdc-form-field-subscript-wrapper{margin-top:6px;margin-bottom:10px;min-height:auto}praxis-field-shell[data-field-name=controlType] .mat-mdc-form-field{margin-bottom:16px}\n"] }]
742
+ }], ctorParameters: () => [{ type: i1.MatDialog }, { type: i2$1.IconPickerService }], propDecorators: { properties: [{
743
+ type: Input
744
+ }], form: [{
745
+ type: Input
746
+ }] } });
747
+
748
+ const inputProperties = [
749
+ // Geral
750
+ { name: 'label', label: 'Label', editorType: 'text', placeholder: 'Rótulo do campo', group: 'Geral' },
751
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
752
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
753
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:search). Use o seletor ao lado para buscar." },
754
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
755
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:info). Use o seletor ao lado para buscar." },
756
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
757
+ { name: 'prefix', label: 'Texto (prefixo)', editorType: 'text', group: 'Geral' },
758
+ { name: 'suffix', label: 'Texto (sufixo)', editorType: 'text', group: 'Geral' },
759
+ { name: 'showCharacterCount', label: 'Mostrar contador de caracteres', editorType: 'checkbox', group: 'Geral' },
760
+ // Validação
761
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
762
+ { name: 'minLength', label: 'Mínimo de caracteres', editorType: 'number', group: 'Validação', row: 'validacao.minmax' },
763
+ { name: 'maxLength', label: 'Máximo de caracteres', editorType: 'number', group: 'Validação', row: 'validacao.minmax' },
764
+ { name: 'pattern', label: 'Pattern (regex)', editorType: 'text', hint: 'Ex.: /^foo.*/i', group: 'Validação' },
765
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
766
+ { name: 'validators.minLengthMessage', label: 'Mensagem: mínimo', editorType: 'text', group: 'Validação' },
767
+ { name: 'validators.maxLengthMessage', label: 'Mensagem: máximo', editorType: 'text', group: 'Validação' },
768
+ { name: 'validators.patternMessage', label: 'Mensagem: padrão inválido', editorType: 'text', group: 'Validação' },
769
+ // Máscara/Formato
770
+ { name: 'mask', label: 'Máscara', editorType: 'text', group: 'Formato/Comportamento' },
771
+ {
772
+ name: 'textTransform',
773
+ label: 'Transformação de texto',
774
+ editorType: 'select',
775
+ group: 'Formato/Comportamento',
776
+ options: [
777
+ { value: 'none', text: 'Nenhuma' },
778
+ { value: 'uppercase', text: 'MAIÚSCULAS (CSS)' },
779
+ { value: 'lowercase', text: 'minúsculas (CSS)' },
780
+ { value: 'capitalize', text: 'Capitalizar (CSS)' },
781
+ { value: 'titleCase', text: 'Title Case' },
782
+ { value: 'sentenceCase', text: 'Sentence case' },
783
+ { value: 'kebab', text: 'kebab-case' },
784
+ { value: 'snake', text: 'snake_case' },
785
+ { value: 'camel', text: 'camelCase' },
786
+ { value: 'pascal', text: 'PascalCase' },
787
+ { value: 'slugify', text: 'slugify (a-z0-9-)' },
788
+ { value: 'trim', text: 'trim (remover bordas)' },
789
+ { value: 'collapseWhitespace', text: 'collapseWhitespace (normalizar espaços)' },
790
+ { value: 'removeDiacritics', text: 'removeDiacritics (sem acentos)' },
791
+ ],
792
+ },
793
+ {
794
+ name: 'textTransformApply',
795
+ label: 'Aplicar em',
796
+ editorType: 'select',
797
+ group: 'Formato/Comportamento',
798
+ defaultValue: 'displayOnly',
799
+ options: [
800
+ { value: 'displayOnly', text: 'Exibição (visual)' },
801
+ { value: 'saveOnly', text: 'Salvar (persistência)' },
802
+ { value: 'both', text: 'Ambos' },
803
+ ],
804
+ },
805
+ // Formato/Comportamento
806
+ { name: 'autocomplete', label: 'Autocomplete', editorType: 'text', group: 'Formato/Comportamento' },
807
+ { name: 'spellcheck', label: 'Verificação ortográfica', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comportamento.toggles1', inline: true },
808
+ { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comportamento.toggles1', inline: true },
809
+ { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comportamento.toggles1', inline: true },
810
+ {
811
+ name: 'inputMode',
812
+ label: 'Modo de entrada (mobile)',
813
+ editorType: 'select',
814
+ group: 'Formato/Comportamento',
815
+ options: [
816
+ { value: 'text', text: 'Texto' },
817
+ { value: 'numeric', text: 'Numérico' },
818
+ { value: 'decimal', text: 'Decimal' },
819
+ { value: 'tel', text: 'Telefone' },
820
+ { value: 'email', text: 'Email' },
821
+ { value: 'url', text: 'URL' },
822
+ { value: 'search', text: 'Busca' },
823
+ ],
824
+ },
825
+ { name: 'autoFocus', label: 'Focar automaticamente', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comportamento.toggles2', inline: true },
826
+ {
827
+ name: 'inputType',
828
+ label: 'Tipo de input (HTML5)',
829
+ editorType: 'select',
830
+ group: 'Formato/Comportamento',
831
+ options: [
832
+ { value: 'text', text: 'Texto' },
833
+ { value: 'email', text: 'Email' },
834
+ { value: 'password', text: 'Senha' },
835
+ { value: 'tel', text: 'Telefone' },
836
+ { value: 'url', text: 'URL' },
837
+ { value: 'search', text: 'Busca' },
838
+ { value: 'number', text: 'Número' },
839
+ { value: 'date', text: 'Data' },
840
+ { value: 'datetime-local', text: 'Data/Hora local' },
841
+ { value: 'time', text: 'Hora' },
842
+ { value: 'month', text: 'Mês' },
843
+ { value: 'week', text: 'Semana' },
844
+ { value: 'color', text: 'Cor' },
845
+ ],
846
+ },
847
+ {
848
+ name: 'validators.validationTrigger',
849
+ label: 'Gatilho de validação',
850
+ editorType: 'select',
851
+ group: 'Formato/Comportamento',
852
+ row: 'comportamento.validation',
853
+ inline: true,
854
+ options: [
855
+ { value: 'change', text: 'Ao digitar' },
856
+ { value: 'blur', text: 'Ao sair do campo' },
857
+ { value: 'submit', text: 'Ao submeter' },
858
+ { value: 'immediate', text: 'Imediato' },
859
+ ],
860
+ },
861
+ {
862
+ name: 'validators.validationDebounce',
863
+ label: 'Debounce de validação (ms)',
864
+ editorType: 'number',
865
+ group: 'Formato/Comportamento',
866
+ row: 'comportamento.validation',
867
+ inline: true,
868
+ },
869
+ { name: 'validators.showInlineErrors', label: 'Mostrar erros inline', editorType: 'checkbox', group: 'Formato/Comportamento' },
870
+ {
871
+ name: 'validators.errorPosition',
872
+ label: 'Posição do erro',
873
+ editorType: 'select',
874
+ group: 'Formato/Comportamento',
875
+ options: [
876
+ { value: 'bottom', text: 'Abaixo' },
877
+ { value: 'top', text: 'Acima' },
878
+ { value: 'tooltip', text: 'Tooltip' },
879
+ ],
880
+ },
881
+ // Material Design
882
+ {
883
+ name: 'materialDesign.appearance',
884
+ label: 'Aparência',
885
+ editorType: 'select',
886
+ group: 'Material Design',
887
+ options: [
888
+ { value: 'fill', text: 'Fill' },
889
+ { value: 'outline', text: 'Outline' },
890
+ ],
891
+ },
892
+ {
893
+ name: 'materialDesign.color',
894
+ label: 'Cor do tema',
895
+ editorType: 'select',
896
+ group: 'Material Design',
897
+ options: [
898
+ { value: 'primary', text: 'Primária' },
899
+ { value: 'accent', text: 'Acento' },
900
+ { value: 'warn', text: 'Alerta' },
901
+ ],
902
+ },
903
+ {
904
+ name: 'materialDesign.floatLabel',
905
+ label: 'Comportamento do label',
906
+ editorType: 'select',
907
+ group: 'Material Design',
908
+ options: [
909
+ { value: 'auto', text: 'Auto' },
910
+ { value: 'always', text: 'Sempre' },
911
+ ],
912
+ },
913
+ {
914
+ name: 'materialDesign.subscriptSizing',
915
+ label: 'Subscript sizing',
916
+ editorType: 'select',
917
+ group: 'Material Design',
918
+ options: [
919
+ { value: 'fixed', text: 'Fixo' },
920
+ { value: 'dynamic', text: 'Dinâmico' },
921
+ ],
922
+ },
923
+ { name: 'materialDesign.hideRequiredMarker', label: 'Ocultar * obrigatório', editorType: 'checkbox', group: 'Material Design' },
924
+ // Estado de erro e interações avançadas
925
+ {
926
+ name: 'errorStateMatcher',
927
+ label: 'Estratégia de erro',
928
+ editorType: 'select',
929
+ group: 'Material Design',
930
+ options: [
931
+ { value: 'default', text: 'Padrão' },
932
+ { value: 'showOnDirtyAndInvalid', text: 'Mostrar ao sujar e inválido' },
933
+ { value: 'showOnSubmitted', text: 'Mostrar ao enviar' },
934
+ { value: 'showImmediately', text: 'Mostrar imediatamente' },
935
+ ],
936
+ },
937
+ { name: 'disabledInteractive', label: 'Desabilitado interativo', editorType: 'checkbox', group: 'Material Design' },
938
+ // Botão limpar (clear)
939
+ { name: 'clearButton.enabled', label: 'Clear: habilitar', editorType: 'checkbox', group: 'Ações' },
940
+ { name: 'clearButton.icon', label: 'Clear: ícone', editorType: 'text', group: 'Ações', hint: "Material Symbols (ex.: mi:clear). Use o seletor ao lado para buscar." },
941
+ { name: 'clearButton.iconColor', label: 'Clear: cor do ícone', editorType: 'color', group: 'Ações', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
942
+ { name: 'clearButton.tooltip', label: 'Clear: tooltip', editorType: 'text', group: 'Ações' },
943
+ { name: 'clearButton.ariaLabel', label: 'Clear: aria-label', editorType: 'text', group: 'Ações' },
944
+ { name: 'clearButton.showOnlyWhenFilled', label: 'Clear: só quando preenchido', editorType: 'checkbox', group: 'Ações' },
945
+ // Acessibilidade / Avançado
946
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
947
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
948
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
949
+ { name: 'tabIndex', label: 'TabIndex', editorType: 'number', group: 'Acessibilidade' },
950
+ { name: 'dataAttributes', label: 'Data attributes (JSON)', editorType: 'textarea', hint: '{ "testId": "input-a" }', group: 'Acessibilidade' },
951
+ // Valor padrão
952
+ { name: 'defaultValue', label: 'Valor padrão', editorType: 'text', group: 'Geral' },
953
+ ];
954
+
955
+ // Editor de metadados para componentes SELECT-like
956
+ // Paridade com INPUT: grupos, linhas e propriedades canônicas
957
+ const selectProperties = [
958
+ // 1) Geral
959
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
960
+ { name: 'defaultValue', label: 'Valor padrão', editorType: 'text', group: 'Geral' },
961
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
962
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
963
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:list). Use o seletor ao lado para buscar." },
964
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
965
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:info). Use o seletor ao lado para buscar." },
966
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
967
+ { name: 'prefix', label: 'Texto (prefixo)', editorType: 'text', group: 'Geral' },
968
+ { name: 'suffix', label: 'Texto (sufixo)', editorType: 'text', group: 'Geral' },
969
+ // 2) Dados/Opções
970
+ {
971
+ name: 'options',
972
+ label: 'Opções (estáticas)',
973
+ editorType: 'textarea',
974
+ group: 'Dados/Opções',
975
+ hint: 'JSON: [{ "value":1, "text":"Um" }] ou linhas: valor|rótulo',
976
+ },
977
+ { name: 'emptyOptionText', label: 'Texto da opção vazia', editorType: 'text', group: 'Dados/Opções' },
978
+ { name: 'multiple', label: 'Múltipla seleção', editorType: 'checkbox', group: 'Dados/Opções', row: 'dados.inline1', inline: true },
979
+ { name: 'optionLabelKey', label: 'Chave do rótulo (remoto)', editorType: 'text', group: 'Dados/Opções' },
980
+ { name: 'optionValueKey', label: 'Chave do valor (remoto)', editorType: 'text', group: 'Dados/Opções' },
981
+ // 3) Fonte Remota
982
+ { name: 'resourcePath', label: 'Resource Path', editorType: 'text', group: 'Fonte Remota' },
983
+ {
984
+ name: 'loadOn',
985
+ label: 'Carregar em',
986
+ editorType: 'select',
987
+ group: 'Fonte Remota',
988
+ options: [
989
+ { value: 'open', text: 'Ao abrir (padrão)' },
990
+ { value: 'init', text: 'Na inicialização' },
991
+ { value: 'none', text: 'Nenhum (manual)' },
992
+ ],
993
+ hint: "Define quando o async-select realiza a primeira busca remota",
994
+ },
995
+ { name: 'filterCriteria', label: 'Critérios de filtro (JSON)', editorType: 'textarea', group: 'Fonte Remota', hint: '{ "active": true }' },
996
+ // 3.1) Dependências (Cascata Nativa – Fase 1)
997
+ { name: 'dependencyFields', label: 'Campos dependentes (CSV ou JSON)', editorType: 'text', group: 'Fonte Remota', hint: "Ex.: rotaId,destinoReqId" },
998
+ { name: 'resetOnDependentChange', label: 'Resetar ao mudar dependentes', editorType: 'checkbox', group: 'Fonte Remota' },
999
+ { name: 'enableDependencyCascade', label: 'Habilitar cascata nativa', editorType: 'checkbox', group: 'Fonte Remota', hint: 'Desative quando Connections controlarem filterCriteria' },
1000
+ { name: 'dependencyFilterMap', label: 'Mapeamento de filtros (JSON)', editorType: 'textarea', group: 'Fonte Remota', hint: '{ "rotaId": "rotaId", "destinoReqId": { "key": "destinoId", "valuePath": "id" } }' },
1001
+ { name: 'dependencyValuePath', label: 'Value path padrão (dep.)', editorType: 'text', group: 'Fonte Remota', hint: "Ex.: 'id' (default) ou 'code'" },
1002
+ {
1003
+ name: 'dependencyMergeStrategy',
1004
+ label: 'Merge de filtros (dep.)',
1005
+ editorType: 'select',
1006
+ group: 'Fonte Remota',
1007
+ options: [
1008
+ { value: 'merge', text: 'merge (padrão)' },
1009
+ { value: 'replace', text: 'replace (apenas chaves mapeadas)' },
1010
+ ],
1011
+ hint: 'merge preserva chaves externas; replace substitui apenas chaves mapeadas',
1012
+ },
1013
+ { name: 'dependencyDebounceMs', label: 'Debounce (ms) dependentes', editorType: 'number', group: 'Fonte Remota', hint: 'Padrão: 150' },
1014
+ {
1015
+ name: 'dependencyLoadOnChange',
1016
+ label: 'Recarregar ao mudar dependentes',
1017
+ editorType: 'select',
1018
+ group: 'Fonte Remota',
1019
+ options: [
1020
+ { value: 'respectLoadOn', text: 'Respeitar loadOn (padrão)' },
1021
+ { value: 'immediate', text: 'Imediato' },
1022
+ { value: 'manual', text: 'Manual (Connections disparam)' },
1023
+ ],
1024
+ },
1025
+ // 4) Formato/Comportamento
1026
+ { name: 'selectAll', label: 'Selecionar todos', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comp.inline1', inline: true },
1027
+ { name: 'searchable', label: 'Buscável', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comp.inline1', inline: true },
1028
+ { name: 'maxSelections', label: 'Máximo de seleções', editorType: 'number', group: 'Formato/Comportamento' },
1029
+ // 5) Validação
1030
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
1031
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
1032
+ // 6) Material Design
1033
+ {
1034
+ name: 'materialDesign.appearance',
1035
+ label: 'Aparência',
1036
+ editorType: 'select',
1037
+ group: 'Material Design',
1038
+ options: [
1039
+ { value: 'fill', text: 'Fill' },
1040
+ { value: 'outline', text: 'Outline' },
1041
+ ],
1042
+ },
1043
+ {
1044
+ name: 'materialDesign.color',
1045
+ label: 'Cor do tema',
1046
+ editorType: 'select',
1047
+ group: 'Material Design',
1048
+ options: [
1049
+ { value: 'primary', text: 'Primária' },
1050
+ { value: 'accent', text: 'Acento' },
1051
+ { value: 'warn', text: 'Alerta' },
1052
+ ],
1053
+ },
1054
+ {
1055
+ name: 'materialDesign.floatLabel',
1056
+ label: 'Comportamento do label',
1057
+ editorType: 'select',
1058
+ group: 'Material Design',
1059
+ options: [
1060
+ { value: 'auto', text: 'Auto' },
1061
+ { value: 'always', text: 'Sempre' },
1062
+ ],
1063
+ },
1064
+ {
1065
+ name: 'materialDesign.subscriptSizing',
1066
+ label: 'Subscript sizing',
1067
+ editorType: 'select',
1068
+ group: 'Material Design',
1069
+ options: [
1070
+ { value: 'fixed', text: 'Fixo' },
1071
+ { value: 'dynamic', text: 'Dinâmico' },
1072
+ ],
1073
+ },
1074
+ { name: 'materialDesign.hideRequiredMarker', label: 'Ocultar * obrigatório', editorType: 'checkbox', group: 'Material Design' },
1075
+ {
1076
+ name: 'errorStateMatcher',
1077
+ label: 'Estratégia de erro',
1078
+ editorType: 'select',
1079
+ group: 'Material Design',
1080
+ options: [
1081
+ { value: 'default', text: 'Padrão' },
1082
+ { value: 'showOnDirtyAndInvalid', text: 'Mostrar ao sujar e inválido' },
1083
+ { value: 'showOnSubmitted', text: 'Mostrar ao enviar' },
1084
+ { value: 'showImmediately', text: 'Mostrar imediatamente' },
1085
+ ],
1086
+ },
1087
+ // 7) Acessibilidade
1088
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
1089
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
1090
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
1091
+ { name: 'tabIndex', label: 'TabIndex', editorType: 'number', group: 'Acessibilidade' },
1092
+ { name: 'dataAttributes', label: 'Data attributes (JSON)', editorType: 'textarea', group: 'Acessibilidade', hint: '{ "testId": "select-a" }' },
1093
+ // 8) Ações (Clear)
1094
+ { name: 'clearButton.enabled', label: 'Clear: habilitar', editorType: 'checkbox', group: 'Ações', row: 'clear.inline1', inline: true },
1095
+ { name: 'clearButton.icon', label: 'Clear: ícone', editorType: 'text', group: 'Ações', row: 'clear.inline2', hint: "Material Symbols (ex.: mi:clear). Use o seletor ao lado para buscar." },
1096
+ { name: 'clearButton.iconColor', label: 'Clear: cor do ícone', editorType: 'color', group: 'Ações', row: 'clear.inline2', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1097
+ { name: 'clearButton.tooltip', label: 'Clear: tooltip', editorType: 'text', group: 'Ações', row: 'clear.inline2' },
1098
+ { name: 'clearButton.ariaLabel', label: 'Clear: aria-label', editorType: 'text', group: 'Ações', row: 'clear.inline2' },
1099
+ { name: 'clearButton.showOnlyWhenFilled', label: 'Clear: só quando preenchido', editorType: 'checkbox', group: 'Ações', row: 'clear.inline1', inline: true },
1100
+ ];
1101
+
1102
+ const transferListProperties = [
1103
+ // Geral
1104
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
1105
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
1106
+ // Dados/Opções
1107
+ {
1108
+ name: 'options',
1109
+ label: 'Opções (estáticas)',
1110
+ editorType: 'textarea',
1111
+ group: 'Dados/Opções',
1112
+ hint: 'JSON: [{ "value":1, "text":"Um" }] ou linhas: valor|rótulo',
1113
+ },
1114
+ { name: 'optionLabelKey', label: 'Chave do rótulo (remoto)', editorType: 'text', group: 'Dados/Opções' },
1115
+ { name: 'optionValueKey', label: 'Chave do valor (remoto)', editorType: 'text', group: 'Dados/Opções' },
1116
+ // Fonte Remota
1117
+ { name: 'resourcePath', label: 'Resource Path', editorType: 'text', group: 'Fonte Remota' },
1118
+ { name: 'filterCriteria', label: 'Critérios de filtro (JSON)', editorType: 'textarea', group: 'Fonte Remota', hint: '{ "active": true }' },
1119
+ { name: 'searchable', label: 'Buscável', editorType: 'checkbox', group: 'Fonte Remota', row: 'fonte.inline1', inline: true },
1120
+ // Formato/Comportamento
1121
+ { name: 'maxSelections', label: 'Máximo de seleções', editorType: 'number', group: 'Formato/Comportamento' },
1122
+ // Validação
1123
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
1124
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
1125
+ // Acessibilidade
1126
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
1127
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
1128
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
1129
+ { name: 'tabIndex', label: 'TabIndex', editorType: 'number', group: 'Acessibilidade' },
1130
+ { name: 'dataAttributes', label: 'Data attributes (JSON)', editorType: 'textarea', group: 'Acessibilidade', hint: '{ "testId": "transfer-a" }' },
1131
+ // Ações (Clear)
1132
+ { name: 'clearButton.enabled', label: 'Clear: habilitar', editorType: 'checkbox', group: 'Ações' },
1133
+ { name: 'clearButton.icon', label: 'Clear: ícone', editorType: 'text', group: 'Ações', hint: "Material Symbols (ex.: mi:clear). Use o seletor ao lado para buscar." },
1134
+ { name: 'clearButton.iconColor', label: 'Clear: cor do ícone', editorType: 'color', group: 'Ações', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1135
+ { name: 'clearButton.tooltip', label: 'Clear: tooltip', editorType: 'text', group: 'Ações' },
1136
+ { name: 'clearButton.ariaLabel', label: 'Clear: aria-label', editorType: 'text', group: 'Ações' },
1137
+ { name: 'clearButton.showOnlyWhenFilled', label: 'Clear: só quando preenchido', editorType: 'checkbox', group: 'Ações' },
1138
+ ];
1139
+
1140
+ const radioProperties = [
1141
+ // Geral
1142
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
1143
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
1144
+ // Dados/Opções
1145
+ {
1146
+ name: 'providerOptions',
1147
+ label: 'Opções (estáticas)',
1148
+ editorType: 'textarea',
1149
+ group: 'Dados/Opções',
1150
+ hint: 'JSON: [{ "value":1, "text":"Um" }] ou linhas: valor|rótulo',
1151
+ },
1152
+ { name: 'optionLabelKey', label: 'Chave do rótulo (remoto)', editorType: 'text', group: 'Dados/Opções' },
1153
+ { name: 'optionValueKey', label: 'Chave do valor (remoto)', editorType: 'text', group: 'Dados/Opções' },
1154
+ // Fonte Remota
1155
+ { name: 'resourcePath', label: 'Resource Path', editorType: 'text', group: 'Fonte Remota' },
1156
+ { name: 'filterCriteria', label: 'Critérios de filtro (JSON)', editorType: 'text', group: 'Fonte Remota', hint: '{ "active": true }' },
1157
+ // Formato/Comportamento
1158
+ { name: 'color', label: 'Cor', editorType: 'select', group: 'Material Design', options: [
1159
+ { value: 'primary', text: 'Primária' },
1160
+ { value: 'accent', text: 'Acento' },
1161
+ { value: 'warn', text: 'Alerta' },
1162
+ ] },
1163
+ { name: 'layout', label: 'Layout', editorType: 'select', group: 'Formato/Comportamento', options: [
1164
+ { value: 'horizontal', text: 'Horizontal' },
1165
+ { value: 'vertical', text: 'Vertical' },
1166
+ ] },
1167
+ { name: 'labelPosition', label: 'Posição do label', editorType: 'select', group: 'Formato/Comportamento', options: [
1168
+ { value: 'before', text: 'Antes' },
1169
+ { value: 'after', text: 'Depois' },
1170
+ ] },
1171
+ // Validação
1172
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
1173
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
1174
+ // Acessibilidade
1175
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
1176
+ ];
1177
+
1178
+ const buttonToggleProperties = [
1179
+ // Geral
1180
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
1181
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
1182
+ { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Geral' },
1183
+ { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Geral' },
1184
+ // Dados/Opções
1185
+ {
1186
+ name: 'options',
1187
+ label: 'Opções (estáticas)',
1188
+ editorType: 'textarea',
1189
+ group: 'Dados/Opções',
1190
+ hint: 'JSON: [{ "value":1, "text":"Um" }] ou linhas: valor|rótulo',
1191
+ },
1192
+ { name: 'optionLabelKey', label: 'Chave do rótulo (remoto)', editorType: 'text', group: 'Dados/Opções' },
1193
+ { name: 'optionValueKey', label: 'Chave do valor (remoto)', editorType: 'text', group: 'Dados/Opções' },
1194
+ // Fonte Remota
1195
+ { name: 'resourcePath', label: 'Resource Path', editorType: 'text', group: 'Fonte Remota' },
1196
+ { name: 'filterCriteria', label: 'Critérios de filtro (JSON)', editorType: 'textarea', group: 'Fonte Remota', hint: '{ "active": true }' },
1197
+ // Formato/Comportamento
1198
+ { name: 'multiple', label: 'Múltipla seleção', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comp.inline1', inline: true },
1199
+ { name: 'maxSelections', label: 'Máximo de seleções', editorType: 'number', group: 'Formato/Comportamento' },
1200
+ // Material Design
1201
+ { name: 'appearance', label: 'Aparência', editorType: 'select', group: 'Material Design', options: [
1202
+ { value: 'legacy', text: 'Legacy' },
1203
+ { value: 'standard', text: 'Standard' },
1204
+ ] },
1205
+ // Validação
1206
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
1207
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
1208
+ // Acessibilidade
1209
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
1210
+ ];
1211
+
1212
+ const treeSelectProperties = [
1213
+ // Geral
1214
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
1215
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
1216
+ // Dados/Opções
1217
+ {
1218
+ name: 'options',
1219
+ label: 'Nós (estáticos)',
1220
+ editorType: 'textarea',
1221
+ group: 'Dados/Opções',
1222
+ hint: 'JSON: [{ "value":1, "label":"Um", children:[...] }]',
1223
+ },
1224
+ // Formato/Comportamento
1225
+ { name: 'searchable', label: 'Buscável', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comp.inline1', inline: true },
1226
+ { name: 'leafOnly', label: 'Permitir apenas folhas', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comp.inline1', inline: true },
1227
+ { name: 'autoExpandOnSearch', label: 'Auto-expansão ao buscar', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comp.inline2', inline: true },
1228
+ { name: 'returnPath', label: 'Retornar caminho', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comp.inline2', inline: true },
1229
+ { name: 'returnObject', label: 'Retornar objeto', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comp.inline2', inline: true },
1230
+ // Validação
1231
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
1232
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
1233
+ // Acessibilidade
1234
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
1235
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
1236
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
1237
+ { name: 'tabIndex', label: 'TabIndex', editorType: 'number', group: 'Acessibilidade' },
1238
+ { name: 'dataAttributes', label: 'Data attributes (JSON)', editorType: 'textarea', group: 'Acessibilidade', hint: '{ "testId": "tree-a" }' },
1239
+ // Ações (Clear)
1240
+ { name: 'clearButton.enabled', label: 'Clear: habilitar', editorType: 'checkbox', group: 'Ações' },
1241
+ { name: 'clearButton.icon', label: 'Clear: ícone', editorType: 'text', group: 'Ações', hint: "Material Symbols (ex.: mi:clear). Use o seletor ao lado para buscar." },
1242
+ { name: 'clearButton.iconColor', label: 'Clear: cor do ícone', editorType: 'color', group: 'Ações', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1243
+ { name: 'clearButton.tooltip', label: 'Clear: tooltip', editorType: 'text', group: 'Ações' },
1244
+ { name: 'clearButton.ariaLabel', label: 'Clear: aria-label', editorType: 'text', group: 'Ações' },
1245
+ { name: 'clearButton.showOnlyWhenFilled', label: 'Clear: só quando preenchido', editorType: 'checkbox', group: 'Ações' },
1246
+ ];
1247
+
1248
+ // Properties for Material Textarea editor (parity with INPUT where applicable)
1249
+ const textareaProperties = [
1250
+ // Geral / Apresentação
1251
+ { name: 'label', label: 'Label', editorType: 'text', placeholder: 'Rótulo do campo', group: 'Geral' },
1252
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
1253
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
1254
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:edit). Use o seletor ao lado para buscar." },
1255
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1256
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:info). Use o seletor ao lado para buscar." },
1257
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1258
+ { name: 'prefix', label: 'Texto (prefixo)', editorType: 'text', group: 'Geral' },
1259
+ { name: 'suffix', label: 'Texto (sufixo)', editorType: 'text', group: 'Geral' },
1260
+ { name: 'defaultValue', label: 'Valor padrão', editorType: 'text', group: 'Geral' },
1261
+ // Validação
1262
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
1263
+ { name: 'minLength', label: 'Mínimo de caracteres', editorType: 'number', group: 'Validação', row: 'validacao.minmax' },
1264
+ { name: 'maxLength', label: 'Máximo de caracteres', editorType: 'number', group: 'Validação', row: 'validacao.minmax' },
1265
+ { name: 'pattern', label: 'Pattern (regex)', editorType: 'text', hint: 'Ex.: /^foo.*/i', group: 'Validação' },
1266
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
1267
+ { name: 'validators.minLengthMessage', label: 'Mensagem: mínimo', editorType: 'text', group: 'Validação' },
1268
+ { name: 'validators.maxLengthMessage', label: 'Mensagem: máximo', editorType: 'text', group: 'Validação' },
1269
+ { name: 'validators.patternMessage', label: 'Mensagem: padrão inválido', editorType: 'text', group: 'Validação' },
1270
+ // fase 2: minWords + mensagem
1271
+ // { name: 'validators.minWords', label: 'Mínimo de palavras', editorType: 'number', group: 'Validação', row: 'validacao.minwords' },
1272
+ // { name: 'validators.minWordsMessage', label: 'Mensagem: poucas palavras', editorType: 'text', group: 'Validação', row: 'validacao.minwords' },
1273
+ // Formato/Comportamento
1274
+ {
1275
+ name: 'textTransform',
1276
+ label: 'Transformação de texto',
1277
+ editorType: 'select',
1278
+ group: 'Formato/Comportamento',
1279
+ options: [
1280
+ { value: 'none', text: 'Nenhuma' },
1281
+ { value: 'uppercase', text: 'MAIÚSCULAS (CSS)' },
1282
+ { value: 'lowercase', text: 'minúsculas (CSS)' },
1283
+ { value: 'capitalize', text: 'Capitalizar (CSS)' },
1284
+ { value: 'titleCase', text: 'Title Case' },
1285
+ { value: 'sentenceCase', text: 'Sentence case' },
1286
+ { value: 'kebab', text: 'kebab-case' },
1287
+ { value: 'snake', text: 'snake_case' },
1288
+ { value: 'camel', text: 'camelCase' },
1289
+ { value: 'pascal', text: 'PascalCase' },
1290
+ { value: 'slugify', text: 'slugify (a-z0-9-)' },
1291
+ { value: 'trim', text: 'trim (remover bordas)' },
1292
+ { value: 'collapseWhitespace', text: 'collapseWhitespace (normalizar espaços)' },
1293
+ { value: 'removeDiacritics', text: 'removeDiacritics (sem acentos)' },
1294
+ ],
1295
+ },
1296
+ {
1297
+ name: 'textTransformApply',
1298
+ label: 'Aplicar em',
1299
+ editorType: 'select',
1300
+ group: 'Formato/Comportamento',
1301
+ defaultValue: 'displayOnly',
1302
+ options: [
1303
+ { value: 'displayOnly', text: 'Exibição (visual)' },
1304
+ { value: 'saveOnly', text: 'Salvar (persistência)' },
1305
+ { value: 'both', text: 'Ambos' },
1306
+ ],
1307
+ },
1308
+ { name: 'showCharacterCount', label: 'Mostrar contador de caracteres', editorType: 'checkbox', group: 'Formato/Comportamento' },
1309
+ { name: 'rows', label: 'Linhas (rows)', editorType: 'number', group: 'Formato/Comportamento', row: 'formato.size' },
1310
+ { name: 'cols', label: 'Colunas (cols)', editorType: 'number', group: 'Formato/Comportamento', row: 'formato.size' },
1311
+ {
1312
+ name: 'wrap',
1313
+ label: 'Quebra de linha (wrap)',
1314
+ editorType: 'select',
1315
+ group: 'Formato/Comportamento',
1316
+ options: [
1317
+ { value: 'soft', text: 'Soft (visual, sem \n)' },
1318
+ { value: 'hard', text: 'Hard (visual + \n)' },
1319
+ { value: 'off', text: 'Off (sem quebra automática)' },
1320
+ ],
1321
+ },
1322
+ { name: 'autoResize', label: 'Auto-resize (CDK)', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'formato.autosize', inline: true },
1323
+ { name: 'minRows', label: 'Mín. linhas', editorType: 'number', group: 'Formato/Comportamento', row: 'formato.autosize', inline: true },
1324
+ { name: 'maxRows', label: 'Máx. linhas', editorType: 'number', group: 'Formato/Comportamento', row: 'formato.autosize', inline: true },
1325
+ { name: 'resize', label: 'Redimensionamento (CSS resize)', editorType: 'select', group: 'Formato/Comportamento', options: [
1326
+ { value: 'none', text: 'Nenhum' },
1327
+ { value: 'vertical', text: 'Vertical' },
1328
+ { value: 'horizontal', text: 'Horizontal' },
1329
+ { value: 'both', text: 'Ambos' },
1330
+ ] },
1331
+ { name: 'textAreaMode', label: 'Modo de edição', editorType: 'select', group: 'Formato/Comportamento', options: [
1332
+ { value: 'plain', text: 'Texto (normal)' },
1333
+ { value: 'code', text: 'Código (genérico)' },
1334
+ { value: 'markdown', text: 'Markdown' },
1335
+ { value: 'json', text: 'JSON' },
1336
+ { value: 'xml', text: 'XML' },
1337
+ { value: 'sql', text: 'SQL' },
1338
+ { value: 'log', text: 'Log' },
1339
+ ] },
1340
+ { name: 'showStats', label: 'Mostrar estatísticas (palavras/linhas)', editorType: 'checkbox', group: 'Formato/Comportamento' },
1341
+ // Formato/Comportamento
1342
+ { name: 'autocomplete', label: 'Autocomplete', editorType: 'text', group: 'Formato/Comportamento' },
1343
+ { name: 'spellcheck', label: 'Verificação ortográfica', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comportamento.toggles1', inline: true },
1344
+ { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comportamento.toggles1', inline: true },
1345
+ { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comportamento.toggles1', inline: true },
1346
+ { name: 'autoFocus', label: 'Focar automaticamente', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comportamento.toggles2', inline: true },
1347
+ { name: 'enableTabIndent', label: 'Tab: inserir indentação', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comportamento.toggles2', inline: true },
1348
+ { name: 'tabSize', label: 'Tamanho da indentação (espaços)', editorType: 'number', group: 'Formato/Comportamento', row: 'comportamento.toggles2', inline: true },
1349
+ { name: 'maxLines', label: 'Máx. de linhas (bloqueia Enter)', editorType: 'number', group: 'Formato/Comportamento', row: 'comportamento.validation', inline: true },
1350
+ {
1351
+ name: 'validators.validationTrigger',
1352
+ label: 'Gatilho de validação',
1353
+ editorType: 'select',
1354
+ group: 'Formato/Comportamento',
1355
+ row: 'comportamento.validation',
1356
+ inline: true,
1357
+ options: [
1358
+ { value: 'change', text: 'Ao digitar' },
1359
+ { value: 'blur', text: 'Ao sair do campo' },
1360
+ { value: 'submit', text: 'Ao submeter' },
1361
+ { value: 'immediate', text: 'Imediato' },
1362
+ ],
1363
+ },
1364
+ { name: 'validators.validationDebounce', label: 'Debounce de validação (ms)', editorType: 'number', group: 'Formato/Comportamento', row: 'comportamento.validation', inline: true },
1365
+ { name: 'validators.showInlineErrors', label: 'Mostrar erros inline', editorType: 'checkbox', group: 'Formato/Comportamento' },
1366
+ {
1367
+ name: 'validators.errorPosition',
1368
+ label: 'Posição do erro',
1369
+ editorType: 'select',
1370
+ group: 'Formato/Comportamento',
1371
+ options: [
1372
+ { value: 'bottom', text: 'Abaixo' },
1373
+ { value: 'top', text: 'Acima' },
1374
+ { value: 'tooltip', text: 'Tooltip' },
1375
+ ],
1376
+ },
1377
+ // Material Design
1378
+ {
1379
+ name: 'materialDesign.appearance',
1380
+ label: 'Aparência',
1381
+ editorType: 'select',
1382
+ group: 'Material Design',
1383
+ options: [
1384
+ { value: 'fill', text: 'Fill' },
1385
+ { value: 'outline', text: 'Outline' },
1386
+ ],
1387
+ },
1388
+ {
1389
+ name: 'materialDesign.color',
1390
+ label: 'Cor do tema',
1391
+ editorType: 'select',
1392
+ group: 'Material Design',
1393
+ options: [
1394
+ { value: 'primary', text: 'Primária' },
1395
+ { value: 'accent', text: 'Acento' },
1396
+ { value: 'warn', text: 'Alerta' },
1397
+ ],
1398
+ },
1399
+ {
1400
+ name: 'materialDesign.floatLabel',
1401
+ label: 'Comportamento do label',
1402
+ editorType: 'select',
1403
+ group: 'Material Design',
1404
+ options: [
1405
+ { value: 'auto', text: 'Auto' },
1406
+ { value: 'always', text: 'Sempre' },
1407
+ ],
1408
+ },
1409
+ {
1410
+ name: 'materialDesign.subscriptSizing',
1411
+ label: 'Subscript sizing',
1412
+ editorType: 'select',
1413
+ group: 'Material Design',
1414
+ options: [
1415
+ { value: 'fixed', text: 'Fixo' },
1416
+ { value: 'dynamic', text: 'Dinâmico' },
1417
+ ],
1418
+ },
1419
+ { name: 'materialDesign.hideRequiredMarker', label: 'Ocultar * obrigatório', editorType: 'checkbox', group: 'Material Design' },
1420
+ {
1421
+ name: 'errorStateMatcher',
1422
+ label: 'Estratégia de erro',
1423
+ editorType: 'select',
1424
+ group: 'Material Design',
1425
+ options: [
1426
+ { value: 'default', text: 'Padrão' },
1427
+ { value: 'showOnDirtyAndInvalid', text: 'Mostrar ao sujar e inválido' },
1428
+ { value: 'showOnSubmitted', text: 'Mostrar ao enviar' },
1429
+ { value: 'showImmediately', text: 'Mostrar imediatamente' },
1430
+ ],
1431
+ },
1432
+ // Acessibilidade
1433
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
1434
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
1435
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
1436
+ { name: 'tabIndex', label: 'TabIndex', editorType: 'number', group: 'Acessibilidade' },
1437
+ { name: 'dataAttributes', label: 'Data attributes (JSON)', editorType: 'textarea', hint: '{ "testId": "textarea-a" }', group: 'Acessibilidade' },
1438
+ // Ações (clear)
1439
+ { name: 'clearButton.enabled', label: 'Clear: habilitar', editorType: 'checkbox', group: 'Ações' },
1440
+ { name: 'clearButton.iconColor', label: 'Clear: cor do ícone', editorType: 'color', group: 'Ações', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1441
+ { name: 'clearButton.icon', label: 'Clear: ícone', editorType: 'text', group: 'Ações', hint: "Material Symbols (ex.: mi:clear). Use o seletor ao lado para buscar." },
1442
+ { name: 'clearButton.tooltip', label: 'Clear: tooltip', editorType: 'text', group: 'Ações' },
1443
+ { name: 'clearButton.ariaLabel', label: 'Clear: aria-label', editorType: 'text', group: 'Ações' },
1444
+ { name: 'clearButton.showOnlyWhenFilled', label: 'Clear: só quando preenchido', editorType: 'checkbox', group: 'Ações' },
1445
+ ];
1446
+
1447
+ // Editor de metadados para NUMBER (numericTextBox)
1448
+ // Paridade com INPUT, cobrindo grupos e propriedades canônicas
1449
+ const numberProperties = [
1450
+ // 1) Geral
1451
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
1452
+ { name: 'defaultValue', label: 'Valor padrão', editorType: 'number', group: 'Geral' },
1453
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
1454
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
1455
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: 'Material Symbols (ex.: mi:...). Use o seletor ao lado para buscar.' },
1456
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1457
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: 'Material Symbols (ex.: mi:...). Use o seletor ao lado para buscar.' },
1458
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1459
+ { name: 'prefix', label: 'Texto (prefixo)', editorType: 'text', group: 'Geral' },
1460
+ { name: 'suffix', label: 'Texto (sufixo)', editorType: 'text', group: 'Geral' },
1461
+ // 2) Validação
1462
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
1463
+ { name: 'min', label: 'Mínimo', editorType: 'number', group: 'Validação', row: 'validacao.minmax', inline: true },
1464
+ { name: 'max', label: 'Máximo', editorType: 'number', group: 'Validação', row: 'validacao.minmax', inline: true },
1465
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
1466
+ { name: 'validators.minMessage', label: 'Mensagem: mínimo', editorType: 'text', group: 'Validação' },
1467
+ { name: 'validators.maxMessage', label: 'Mensagem: máximo', editorType: 'text', group: 'Validação' },
1468
+ // 3) Formato/Comportamento
1469
+ { name: 'step', label: 'Step (incremento)', editorType: 'number', group: 'Formato/Comportamento' },
1470
+ { name: 'numberFormat.decimalPlaces', label: 'Casas decimais', editorType: 'number', group: 'Formato/Comportamento', row: 'formato.decimais', inline: true },
1471
+ { name: 'showGrouping', label: 'Agrupar milhares', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'formato.decimais', inline: true, hint: 'Fase 1: aplica apenas formatação visual básica' },
1472
+ {
1473
+ name: 'inputMode',
1474
+ label: 'Modo de entrada',
1475
+ editorType: 'select',
1476
+ group: 'Formato/Comportamento',
1477
+ options: [
1478
+ { value: 'numeric', text: 'Numérico' },
1479
+ { value: 'decimal', text: 'Decimal' },
1480
+ ],
1481
+ },
1482
+ // 4) Formato/Comportamento
1483
+ { name: 'autocomplete', label: 'Autocomplete', editorType: 'text', group: 'Formato/Comportamento' },
1484
+ { name: 'spellcheck', label: 'Verificação ortográfica', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comp.toggles1', inline: true },
1485
+ { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comp.toggles1', inline: true },
1486
+ { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comp.toggles1', inline: true },
1487
+ { name: 'autoFocus', label: 'Focar automaticamente', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comp.toggles2', inline: true },
1488
+ {
1489
+ name: 'validators.validationTrigger',
1490
+ label: 'Gatilho de validação',
1491
+ editorType: 'select',
1492
+ group: 'Formato/Comportamento',
1493
+ row: 'comp.validation',
1494
+ inline: true,
1495
+ options: [
1496
+ { value: 'change', text: 'Ao digitar' },
1497
+ { value: 'blur', text: 'Ao sair do campo' },
1498
+ { value: 'submit', text: 'Ao submeter' },
1499
+ { value: 'immediate', text: 'Imediato' },
1500
+ ],
1501
+ },
1502
+ { name: 'validators.validationDebounce', label: 'Debounce (ms)', editorType: 'number', group: 'Formato/Comportamento', row: 'comp.validation', inline: true },
1503
+ { name: 'validators.showInlineErrors', label: 'Mostrar erros inline', editorType: 'checkbox', group: 'Formato/Comportamento' },
1504
+ {
1505
+ name: 'validators.errorPosition',
1506
+ label: 'Posição do erro',
1507
+ editorType: 'select',
1508
+ group: 'Formato/Comportamento',
1509
+ options: [
1510
+ { value: 'bottom', text: 'Abaixo' },
1511
+ { value: 'top', text: 'Acima' },
1512
+ { value: 'tooltip', text: 'Tooltip' },
1513
+ ],
1514
+ },
1515
+ // 5) Material Design
1516
+ {
1517
+ name: 'materialDesign.appearance',
1518
+ label: 'Aparência',
1519
+ editorType: 'select',
1520
+ group: 'Material Design',
1521
+ options: [
1522
+ { value: 'fill', text: 'Fill' },
1523
+ { value: 'outline', text: 'Outline' },
1524
+ ],
1525
+ },
1526
+ {
1527
+ name: 'materialDesign.color',
1528
+ label: 'Cor do tema',
1529
+ editorType: 'select',
1530
+ group: 'Material Design',
1531
+ options: [
1532
+ { value: 'primary', text: 'Primária' },
1533
+ { value: 'accent', text: 'Acento' },
1534
+ { value: 'warn', text: 'Alerta' },
1535
+ ],
1536
+ },
1537
+ {
1538
+ name: 'materialDesign.floatLabel',
1539
+ label: 'Comportamento do label',
1540
+ editorType: 'select',
1541
+ group: 'Material Design',
1542
+ options: [
1543
+ { value: 'auto', text: 'Auto' },
1544
+ { value: 'always', text: 'Sempre' },
1545
+ ],
1546
+ },
1547
+ {
1548
+ name: 'materialDesign.subscriptSizing',
1549
+ label: 'Subscript sizing',
1550
+ editorType: 'select',
1551
+ group: 'Material Design',
1552
+ options: [
1553
+ { value: 'fixed', text: 'Fixo' },
1554
+ { value: 'dynamic', text: 'Dinâmico' },
1555
+ ],
1556
+ },
1557
+ { name: 'materialDesign.hideRequiredMarker', label: 'Ocultar * obrigatório', editorType: 'checkbox', group: 'Material Design' },
1558
+ {
1559
+ name: 'errorStateMatcher',
1560
+ label: 'Estratégia de erro',
1561
+ editorType: 'select',
1562
+ group: 'Material Design',
1563
+ options: [
1564
+ { value: 'default', text: 'Padrão' },
1565
+ { value: 'showOnDirtyAndInvalid', text: 'Mostrar ao sujar e inválido' },
1566
+ { value: 'showOnSubmitted', text: 'Mostrar ao enviar' },
1567
+ { value: 'showImmediately', text: 'Mostrar imediatamente' },
1568
+ ],
1569
+ },
1570
+ // 6) Acessibilidade
1571
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
1572
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
1573
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
1574
+ { name: 'tabIndex', label: 'TabIndex', editorType: 'number', group: 'Acessibilidade' },
1575
+ { name: 'dataAttributes', label: 'Data attributes (JSON)', editorType: 'textarea', group: 'Acessibilidade', hint: '{ "testId": "number-a" }' },
1576
+ // 7) Ações (Clear)
1577
+ { name: 'clearButton.enabled', label: 'Clear: habilitar', editorType: 'checkbox', group: 'Ações', row: 'acoes.inline1', inline: true },
1578
+ { name: 'clearButton.icon', label: 'Clear: ícone', editorType: 'text', group: 'Ações', row: 'acoes.inline2', hint: 'Material Symbols (ex.: mi:clear). Use o seletor ao lado para buscar.' },
1579
+ { name: 'clearButton.iconColor', label: 'Clear: cor do ícone', editorType: 'color', group: 'Ações', row: 'acoes.inline2', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1580
+ { name: 'clearButton.tooltip', label: 'Clear: tooltip', editorType: 'text', group: 'Ações', row: 'acoes.inline2' },
1581
+ { name: 'clearButton.ariaLabel', label: 'Clear: aria-label', editorType: 'text', group: 'Ações', row: 'acoes.inline2' },
1582
+ { name: 'clearButton.showOnlyWhenFilled', label: 'Clear: só quando preenchido', editorType: 'checkbox', group: 'Ações', row: 'acoes.inline1', inline: true },
1583
+ ];
1584
+
1585
+ const dateProperties = [
1586
+ // Geral
1587
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
1588
+ { name: 'defaultValue', label: 'Valor padrão', editorType: 'date', group: 'Geral', hint: 'Data (YYYY-MM-DD)' },
1589
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
1590
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
1591
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:calendar_today). Use o seletor ao lado para buscar." },
1592
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1593
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:info). Use o seletor ao lado para buscar." },
1594
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1595
+ { name: 'prefix', label: 'Texto (prefixo)', editorType: 'text', group: 'Geral' },
1596
+ { name: 'suffix', label: 'Texto (sufixo)', editorType: 'text', group: 'Geral' },
1597
+ // Validação
1598
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
1599
+ { name: 'minDate', label: 'Mínima', editorType: 'date', group: 'Validação', row: 'val.minmax' },
1600
+ { name: 'maxDate', label: 'Máxima', editorType: 'date', group: 'Validação', row: 'val.minmax' },
1601
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
1602
+ { name: 'validators.minMessage', label: 'Mensagem: data mínima', editorType: 'text', group: 'Validação' },
1603
+ { name: 'validators.maxMessage', label: 'Mensagem: data máxima', editorType: 'text', group: 'Validação' },
1604
+ // Formato/Comportamento
1605
+ { name: 'startView', label: 'Visão inicial', editorType: 'select', group: 'Formato/Comportamento', options: [
1606
+ { value: 'month', text: 'Mês' },
1607
+ { value: 'year', text: 'Ano' },
1608
+ { value: 'multi-year', text: 'Décadas' },
1609
+ ] },
1610
+ { name: 'startAt', label: 'Abrir em', editorType: 'date', group: 'Formato/Comportamento' },
1611
+ { name: 'touchUi', label: 'Touch UI', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'fmt.inline1', inline: true },
1612
+ { name: 'closeOnSelect', label: 'Fechar ao selecionar', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'fmt.inline1', inline: true },
1613
+ // Material Design
1614
+ { name: 'materialDesign.appearance', label: 'Aparência', editorType: 'select', group: 'Material Design', options: [
1615
+ { value: 'fill', text: 'Fill' },
1616
+ { value: 'outline', text: 'Outline' },
1617
+ ] },
1618
+ { name: 'materialDesign.color', label: 'Cor do tema', editorType: 'select', group: 'Material Design', options: [
1619
+ { value: 'primary', text: 'Primária' },
1620
+ { value: 'accent', text: 'Acento' },
1621
+ { value: 'warn', text: 'Alerta' },
1622
+ ] },
1623
+ { name: 'materialDesign.floatLabel', label: 'Comportamento do label', editorType: 'select', group: 'Material Design', options: [
1624
+ { value: 'auto', text: 'Auto' },
1625
+ { value: 'always', text: 'Sempre' },
1626
+ ] },
1627
+ { name: 'materialDesign.subscriptSizing', label: 'Subscript sizing', editorType: 'select', group: 'Material Design', options: [
1628
+ { value: 'fixed', text: 'Fixo' },
1629
+ { value: 'dynamic', text: 'Dinâmico' },
1630
+ ] },
1631
+ { name: 'materialDesign.hideRequiredMarker', label: 'Ocultar * obrigatório', editorType: 'checkbox', group: 'Material Design' },
1632
+ { name: 'errorStateMatcher', label: 'Estratégia de erro', editorType: 'select', group: 'Material Design', options: [
1633
+ { value: 'default', text: 'Padrão' },
1634
+ { value: 'showOnDirtyAndInvalid', text: 'Mostrar ao sujar e inválido' },
1635
+ { value: 'showOnSubmitted', text: 'Mostrar ao enviar' },
1636
+ { value: 'showImmediately', text: 'Mostrar imediatamente' },
1637
+ ] },
1638
+ // Acessibilidade
1639
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
1640
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
1641
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
1642
+ { name: 'tabIndex', label: 'TabIndex', editorType: 'number', group: 'Acessibilidade' },
1643
+ { name: 'dataAttributes', label: 'Data attributes (JSON)', editorType: 'textarea', group: 'Acessibilidade' },
1644
+ // Ações
1645
+ { name: 'clearButton.enabled', label: 'Clear: habilitar', editorType: 'checkbox', group: 'Ações', row: 'clear.inline1', inline: true },
1646
+ { name: 'clearButton.icon', label: 'Clear: ícone', editorType: 'text', group: 'Ações', row: 'clear.inline2', hint: "Material Symbols (ex.: mi:clear). Use o seletor ao lado para buscar." },
1647
+ { name: 'clearButton.iconColor', label: 'Clear: cor do ícone', editorType: 'color', group: 'Ações', row: 'clear.inline2', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1648
+ { name: 'clearButton.tooltip', label: 'Clear: tooltip', editorType: 'text', group: 'Ações', row: 'clear.inline2' },
1649
+ { name: 'clearButton.ariaLabel', label: 'Clear: aria-label', editorType: 'text', group: 'Ações', row: 'clear.inline2' },
1650
+ { name: 'clearButton.showOnlyWhenFilled', label: 'Clear: só quando preenchido', editorType: 'checkbox', group: 'Ações', row: 'clear.inline1', inline: true },
1651
+ ];
1652
+
1653
+ const dateRangeProperties = [
1654
+ // Geral
1655
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
1656
+ { name: 'defaultValue.startDate', label: 'Valor padrão (início)', editorType: 'date', group: 'Geral' },
1657
+ { name: 'defaultValue.endDate', label: 'Valor padrão (fim)', editorType: 'date', group: 'Geral' },
1658
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
1659
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:date_range). Use o seletor ao lado para buscar." },
1660
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1661
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:info). Use o seletor ao lado para buscar." },
1662
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1663
+ // Placeholders específicos
1664
+ { name: 'startPlaceholder', label: 'Placeholder (início)', editorType: 'text', group: 'Geral' },
1665
+ { name: 'endPlaceholder', label: 'Placeholder (fim)', editorType: 'text', group: 'Geral' },
1666
+ // Validação
1667
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
1668
+ { name: 'minDate', label: 'Mínima', editorType: 'date', group: 'Validação', row: 'val.minmax' },
1669
+ { name: 'maxDate', label: 'Máxima', editorType: 'date', group: 'Validação', row: 'val.minmax' },
1670
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
1671
+ // Formato/Comportamento
1672
+ { name: 'startView', label: 'Visão inicial', editorType: 'select', group: 'Formato/Comportamento', options: [
1673
+ { value: 'month', text: 'Mês' },
1674
+ { value: 'year', text: 'Ano' },
1675
+ { value: 'multi-year', text: 'Décadas' },
1676
+ ] },
1677
+ { name: 'startAt', label: 'Abrir em', editorType: 'date', group: 'Formato/Comportamento' },
1678
+ { name: 'touchUi', label: 'Touch UI', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'fmt.inline1', inline: true },
1679
+ // Material Design
1680
+ { name: 'materialDesign.appearance', label: 'Aparência', editorType: 'select', group: 'Material Design', options: [
1681
+ { value: 'fill', text: 'Fill' },
1682
+ { value: 'outline', text: 'Outline' },
1683
+ ] },
1684
+ { name: 'materialDesign.color', label: 'Cor do tema', editorType: 'select', group: 'Material Design', options: [
1685
+ { value: 'primary', text: 'Primária' },
1686
+ { value: 'accent', text: 'Acento' },
1687
+ { value: 'warn', text: 'Alerta' },
1688
+ ] },
1689
+ { name: 'materialDesign.floatLabel', label: 'Comportamento do label', editorType: 'select', group: 'Material Design', options: [
1690
+ { value: 'auto', text: 'Auto' },
1691
+ { value: 'always', text: 'Sempre' },
1692
+ ] },
1693
+ { name: 'materialDesign.subscriptSizing', label: 'Subscript sizing', editorType: 'select', group: 'Material Design', options: [
1694
+ { value: 'fixed', text: 'Fixo' },
1695
+ { value: 'dynamic', text: 'Dinâmico' },
1696
+ ] },
1697
+ { name: 'materialDesign.hideRequiredMarker', label: 'Ocultar * obrigatório', editorType: 'checkbox', group: 'Material Design' },
1698
+ { name: 'errorStateMatcher', label: 'Estratégia de erro', editorType: 'select', group: 'Material Design', options: [
1699
+ { value: 'default', text: 'Padrão' },
1700
+ { value: 'showOnDirtyAndInvalid', text: 'Mostrar ao sujar e inválido' },
1701
+ { value: 'showOnSubmitted', text: 'Mostrar ao enviar' },
1702
+ { value: 'showImmediately', text: 'Mostrar imediatamente' },
1703
+ ] },
1704
+ // Acessibilidade
1705
+ { name: 'startAriaLabel', label: 'ARIA (início)', editorType: 'text', group: 'Acessibilidade' },
1706
+ { name: 'endAriaLabel', label: 'ARIA (fim)', editorType: 'text', group: 'Acessibilidade' },
1707
+ { name: 'tabIndex', label: 'TabIndex', editorType: 'number', group: 'Acessibilidade' },
1708
+ { name: 'dataAttributes', label: 'Data attributes (JSON)', editorType: 'textarea', group: 'Acessibilidade' },
1709
+ ];
1710
+
1711
+ const datetimeLocalProperties = [
1712
+ // Geral
1713
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
1714
+ { name: 'defaultValue', label: 'Valor padrão (YYYY-MM-DDTHH:mm)', editorType: 'text', group: 'Geral' },
1715
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
1716
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
1717
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:schedule). Use o seletor ao lado para buscar." },
1718
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1719
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:info). Use o seletor ao lado para buscar." },
1720
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1721
+ // Validação
1722
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
1723
+ { name: 'min', label: 'Mínimo (YYYY-MM-DDTHH:mm)', editorType: 'text', group: 'Validação', row: 'val.minmax' },
1724
+ { name: 'max', label: 'Máximo (YYYY-MM-DDTHH:mm)', editorType: 'text', group: 'Validação', row: 'val.minmax' },
1725
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
1726
+ { name: 'validators.minMessage', label: 'Mensagem: mínimo', editorType: 'text', group: 'Validação' },
1727
+ { name: 'validators.maxMessage', label: 'Mensagem: máximo', editorType: 'text', group: 'Validação' },
1728
+ // Formato/Comportamento
1729
+ { name: 'step', label: 'Step (segundos)', editorType: 'number', group: 'Formato/Comportamento' },
1730
+ { name: 'inputMode', label: 'Modo de entrada (mobile)', editorType: 'select', group: 'Formato/Comportamento', options: [
1731
+ { value: 'text', text: 'Texto' },
1732
+ { value: 'numeric', text: 'Numérico' },
1733
+ { value: 'decimal', text: 'Decimal' },
1734
+ { value: 'tel', text: 'Telefone' },
1735
+ { value: 'email', text: 'Email' },
1736
+ { value: 'url', text: 'URL' },
1737
+ { value: 'search', text: 'Busca' },
1738
+ ] },
1739
+ { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'fmt.inline1', inline: true },
1740
+ { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'fmt.inline1', inline: true },
1741
+ { name: 'autoFocus', label: 'Focar automaticamente', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'fmt.inline1', inline: true },
1742
+ { name: 'autocomplete', label: 'Autocomplete', editorType: 'text', group: 'Formato/Comportamento' },
1743
+ // Material Design
1744
+ { name: 'materialDesign.appearance', label: 'Aparência', editorType: 'select', group: 'Material Design', options: [
1745
+ { value: 'fill', text: 'Fill' },
1746
+ { value: 'outline', text: 'Outline' },
1747
+ ] },
1748
+ { name: 'materialDesign.color', label: 'Cor do tema', editorType: 'select', group: 'Material Design', options: [
1749
+ { value: 'primary', text: 'Primária' },
1750
+ { value: 'accent', text: 'Acento' },
1751
+ { value: 'warn', text: 'Alerta' },
1752
+ ] },
1753
+ { name: 'materialDesign.floatLabel', label: 'Comportamento do label', editorType: 'select', group: 'Material Design', options: [
1754
+ { value: 'auto', text: 'Auto' },
1755
+ { value: 'always', text: 'Sempre' },
1756
+ ] },
1757
+ { name: 'materialDesign.subscriptSizing', label: 'Subscript sizing', editorType: 'select', group: 'Material Design', options: [
1758
+ { value: 'fixed', text: 'Fixo' },
1759
+ { value: 'dynamic', text: 'Dinâmico' },
1760
+ ] },
1761
+ { name: 'materialDesign.hideRequiredMarker', label: 'Ocultar * obrigatório', editorType: 'checkbox', group: 'Material Design' },
1762
+ { name: 'errorStateMatcher', label: 'Estratégia de erro', editorType: 'select', group: 'Material Design', options: [
1763
+ { value: 'default', text: 'Padrão' },
1764
+ { value: 'showOnDirtyAndInvalid', text: 'Mostrar ao sujar e inválido' },
1765
+ { value: 'showOnSubmitted', text: 'Mostrar ao enviar' },
1766
+ { value: 'showImmediately', text: 'Mostrar imediatamente' },
1767
+ ] },
1768
+ // Acessibilidade
1769
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
1770
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
1771
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
1772
+ { name: 'tabIndex', label: 'TabIndex', editorType: 'number', group: 'Acessibilidade' },
1773
+ { name: 'dataAttributes', label: 'Data attributes (JSON)', editorType: 'textarea', group: 'Acessibilidade' },
1774
+ // Ações: Não expõe Clear (componente não implementa botão de limpar)
1775
+ ];
1776
+
1777
+ // Native <input type="date"> editor properties
1778
+ const dateInputProperties = [
1779
+ // Geral
1780
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
1781
+ { name: 'defaultValue', label: 'Valor padrão (YYYY-MM-DD)', editorType: 'date', group: 'Geral' },
1782
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
1783
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
1784
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:event). Use o seletor ao lado para buscar." },
1785
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1786
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:info). Use o seletor ao lado para buscar." },
1787
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1788
+ { name: 'prefix', label: 'Texto (prefixo)', editorType: 'text', group: 'Geral' },
1789
+ { name: 'suffix', label: 'Texto (sufixo)', editorType: 'text', group: 'Geral' },
1790
+ // Validação
1791
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
1792
+ { name: 'min', label: 'Mínima (YYYY-MM-DD)', editorType: 'date', group: 'Validação', row: 'val.minmax' },
1793
+ { name: 'max', label: 'Máxima (YYYY-MM-DD)', editorType: 'date', group: 'Validação', row: 'val.minmax' },
1794
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
1795
+ // Formato/Comportamento
1796
+ { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'fmt.inline1', inline: true },
1797
+ { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'fmt.inline1', inline: true },
1798
+ { name: 'autoFocus', label: 'Focar automaticamente', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'fmt.inline1', inline: true },
1799
+ { name: 'autocomplete', label: 'Autocomplete', editorType: 'text', group: 'Formato/Comportamento' },
1800
+ // Material Design
1801
+ { name: 'materialDesign.appearance', label: 'Aparência', editorType: 'select', group: 'Material Design', options: [
1802
+ { value: 'fill', text: 'Fill' },
1803
+ { value: 'outline', text: 'Outline' },
1804
+ ] },
1805
+ { name: 'materialDesign.color', label: 'Cor do tema', editorType: 'select', group: 'Material Design', options: [
1806
+ { value: 'primary', text: 'Primária' },
1807
+ { value: 'accent', text: 'Acento' },
1808
+ { value: 'warn', text: 'Alerta' },
1809
+ ] },
1810
+ { name: 'materialDesign.floatLabel', label: 'Comportamento do label', editorType: 'select', group: 'Material Design', options: [
1811
+ { value: 'auto', text: 'Auto' },
1812
+ { value: 'always', text: 'Sempre' },
1813
+ ] },
1814
+ { name: 'materialDesign.subscriptSizing', label: 'Subscript sizing', editorType: 'select', group: 'Material Design', options: [
1815
+ { value: 'fixed', text: 'Fixo' },
1816
+ { value: 'dynamic', text: 'Dinâmico' },
1817
+ ] },
1818
+ { name: 'materialDesign.hideRequiredMarker', label: 'Ocultar * obrigatório', editorType: 'checkbox', group: 'Material Design' },
1819
+ { name: 'errorStateMatcher', label: 'Estratégia de erro', editorType: 'select', group: 'Material Design', options: [
1820
+ { value: 'default', text: 'Padrão' },
1821
+ { value: 'showOnDirtyAndInvalid', text: 'Mostrar ao sujar e inválido' },
1822
+ { value: 'showOnSubmitted', text: 'Mostrar ao enviar' },
1823
+ { value: 'showImmediately', text: 'Mostrar imediatamente' },
1824
+ ] },
1825
+ // Acessibilidade
1826
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
1827
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
1828
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
1829
+ { name: 'tabIndex', label: 'TabIndex', editorType: 'number', group: 'Acessibilidade' },
1830
+ { name: 'dataAttributes', label: 'Data attributes (JSON)', editorType: 'textarea', group: 'Acessibilidade' },
1831
+ // Ações: Não expõe Clear (componente não implementa botão de limpar)
1832
+ ];
1833
+
1834
+ const timeInputProperties = [
1835
+ // Geral
1836
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
1837
+ { name: 'defaultValue', label: 'Valor padrão (HH:mm)', editorType: 'text', group: 'Geral', hint: 'Ex.: 08:30' },
1838
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
1839
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
1840
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:schedule). Use o seletor ao lado para buscar." },
1841
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1842
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:info). Use o seletor ao lado para buscar." },
1843
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1844
+ { name: 'prefix', label: 'Texto (prefixo)', editorType: 'text', group: 'Geral' },
1845
+ { name: 'suffix', label: 'Texto (sufixo)', editorType: 'text', group: 'Geral' },
1846
+ // Validação
1847
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
1848
+ { name: 'min', label: 'Mínimo (HH:mm)', editorType: 'text', group: 'Validação', row: 'val.minmax' },
1849
+ { name: 'max', label: 'Máximo (HH:mm)', editorType: 'text', group: 'Validação', row: 'val.minmax' },
1850
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
1851
+ // Formato/Comportamento
1852
+ { name: 'step', label: 'Step (segundos)', editorType: 'number', group: 'Formato/Comportamento' },
1853
+ { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'fmt.inline1', inline: true },
1854
+ { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'fmt.inline1', inline: true },
1855
+ { name: 'autoFocus', label: 'Focar automaticamente', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'fmt.inline1', inline: true },
1856
+ { name: 'autocomplete', label: 'Autocomplete', editorType: 'text', group: 'Formato/Comportamento' },
1857
+ { name: 'inputMode', label: 'Modo de entrada', editorType: 'text', group: 'Formato/Comportamento' },
1858
+ // Material Design
1859
+ { name: 'materialDesign.appearance', label: 'Aparência', editorType: 'select', group: 'Material Design', options: [
1860
+ { value: 'fill', text: 'Fill' },
1861
+ { value: 'outline', text: 'Outline' },
1862
+ ] },
1863
+ { name: 'materialDesign.color', label: 'Cor do tema', editorType: 'select', group: 'Material Design', options: [
1864
+ { value: 'primary', text: 'Primária' },
1865
+ { value: 'accent', text: 'Acento' },
1866
+ { value: 'warn', text: 'Alerta' },
1867
+ ] },
1868
+ { name: 'materialDesign.floatLabel', label: 'Comportamento do label', editorType: 'select', group: 'Material Design', options: [
1869
+ { value: 'auto', text: 'Auto' },
1870
+ { value: 'always', text: 'Sempre' },
1871
+ ] },
1872
+ { name: 'materialDesign.subscriptSizing', label: 'Subscript sizing', editorType: 'select', group: 'Material Design', options: [
1873
+ { value: 'fixed', text: 'Fixo' },
1874
+ { value: 'dynamic', text: 'Dinâmico' },
1875
+ ] },
1876
+ { name: 'materialDesign.hideRequiredMarker', label: 'Ocultar * obrigatório', editorType: 'checkbox', group: 'Material Design' },
1877
+ { name: 'errorStateMatcher', label: 'Estratégia de erro', editorType: 'select', group: 'Material Design', options: [
1878
+ { value: 'default', text: 'Padrão' },
1879
+ { value: 'showOnDirtyAndInvalid', text: 'Mostrar ao sujar e inválido' },
1880
+ { value: 'showOnSubmitted', text: 'Mostrar ao enviar' },
1881
+ { value: 'showImmediately', text: 'Mostrar imediatamente' },
1882
+ ] },
1883
+ // Acessibilidade
1884
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
1885
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
1886
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
1887
+ { name: 'tabIndex', label: 'TabIndex', editorType: 'number', group: 'Acessibilidade' },
1888
+ { name: 'dataAttributes', label: 'Data attributes (JSON)', editorType: 'textarea', group: 'Acessibilidade' },
1889
+ ];
1890
+
1891
+ const timePickerProperties = [
1892
+ // Geral
1893
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
1894
+ { name: 'defaultValue', label: 'Valor padrão (exibição)', editorType: 'text', group: 'Geral' },
1895
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
1896
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: 'Material Symbols (ex.: mi:...). Use o seletor ao lado para buscar.' },
1897
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1898
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: 'Material Symbols (ex.: mi:...). Use o seletor ao lado para buscar.' },
1899
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1900
+ // Validação
1901
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
1902
+ { name: 'min', label: 'Mínimo (HH:mm[:ss])', editorType: 'text', group: 'Validação', row: 'val.minmax' },
1903
+ { name: 'max', label: 'Máximo (HH:mm[:ss])', editorType: 'text', group: 'Validação', row: 'val.minmax' },
1904
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
1905
+ // Comportamento
1906
+ { name: 'interval', label: 'Intervalo (segundos ou 30m)', editorType: 'text', group: 'Formato/Comportamento' },
1907
+ { name: 'timeOptions', label: 'Opções (uma por linha)', editorType: 'textarea', group: 'Formato/Comportamento' },
1908
+ { name: 'openOnClick', label: 'Abrir ao clicar', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'fmt.inline1', inline: true },
1909
+ { name: 'touchUi', label: 'Touch UI', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'fmt.inline1', inline: true },
1910
+ { name: 'format', label: 'Formato', editorType: 'select', group: 'Formato/Comportamento', options: [
1911
+ { value: '24h', text: '24 horas' },
1912
+ { value: '12h', text: '12 horas' },
1913
+ ] },
1914
+ { name: 'showSeconds', label: 'Exibir segundos', editorType: 'checkbox', group: 'Formato/Comportamento' },
1915
+ { name: 'stepMinute', label: 'Passo (min)', editorType: 'number', group: 'Formato/Comportamento', row: 'fmt.steps', inline: true },
1916
+ { name: 'stepSecond', label: 'Passo (seg)', editorType: 'number', group: 'Formato/Comportamento', row: 'fmt.steps', inline: true },
1917
+ { name: 'timeFilter', label: 'Filtro (id função)', editorType: 'text', group: 'Formato/Comportamento' },
1918
+ // Material/A11y/Ações básicas
1919
+ { name: 'materialDesign.appearance', label: 'Aparência', editorType: 'select', group: 'Material Design', options: [
1920
+ { value: 'fill', text: 'Fill' },
1921
+ { value: 'outline', text: 'Outline' },
1922
+ ] },
1923
+ { name: 'materialDesign.color', label: 'Cor do tema', editorType: 'select', group: 'Material Design', options: [
1924
+ { value: 'primary', text: 'Primária' },
1925
+ { value: 'accent', text: 'Acento' },
1926
+ { value: 'warn', text: 'Alerta' },
1927
+ ] },
1928
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
1929
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
1930
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
1931
+ ];
1932
+
1933
+ const timeRangeProperties = [
1934
+ // Geral (defaultValue como objeto start/end)
1935
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
1936
+ { name: 'defaultValue.start', label: 'Início (HH:mm)', editorType: 'text', group: 'Geral', row: 'geral.range', inline: true },
1937
+ { name: 'defaultValue.end', label: 'Fim (HH:mm)', editorType: 'text', group: 'Geral', row: 'geral.range', inline: true },
1938
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
1939
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: 'Material Symbols (ex.: mi:...). Use o seletor ao lado para buscar.' },
1940
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1941
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: 'Material Symbols (ex.: mi:...). Use o seletor ao lado para buscar.' },
1942
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1943
+ // Validação
1944
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
1945
+ { name: 'min', label: 'Min (HH:mm)', editorType: 'text', group: 'Validação', row: 'val.minmax', inline: true },
1946
+ { name: 'max', label: 'Max (HH:mm)', editorType: 'text', group: 'Validação', row: 'val.minmax', inline: true },
1947
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
1948
+ // Comportamento
1949
+ { name: 'step', label: 'Step (segundos)', editorType: 'number', group: 'Formato/Comportamento' },
1950
+ { name: 'touchUi', label: 'Touch UI', editorType: 'checkbox', group: 'Formato/Comportamento' },
1951
+ { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Formato/Comportamento' },
1952
+ { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Formato/Comportamento' },
1953
+ { name: 'autoFocus', label: 'Focar automaticamente', editorType: 'checkbox', group: 'Formato/Comportamento' },
1954
+ // Material/A11y
1955
+ { name: 'materialDesign.appearance', label: 'Aparência', editorType: 'select', group: 'Material Design', options: [
1956
+ { value: 'fill', text: 'Fill' },
1957
+ { value: 'outline', text: 'Outline' },
1958
+ ] },
1959
+ { name: 'materialDesign.color', label: 'Cor do tema', editorType: 'select', group: 'Material Design', options: [
1960
+ { value: 'primary', text: 'Primária' },
1961
+ { value: 'accent', text: 'Acento' },
1962
+ { value: 'warn', text: 'Alerta' },
1963
+ ] },
1964
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
1965
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
1966
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
1967
+ ];
1968
+
1969
+ const monthInputProperties = [
1970
+ // Geral
1971
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
1972
+ { name: 'defaultValue', label: 'Valor padrão (YYYY-MM)', editorType: 'text', group: 'Geral' },
1973
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
1974
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
1975
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:calendar_view_month). Use o seletor ao lado para buscar." },
1976
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1977
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: 'Material Symbols (ex.: mi:...). Use o seletor ao lado para buscar.' },
1978
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
1979
+ // Validação
1980
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
1981
+ { name: 'min', label: 'Mínimo (YYYY-MM)', editorType: 'text', group: 'Validação', row: 'val.minmax' },
1982
+ { name: 'max', label: 'Máximo (YYYY-MM)', editorType: 'text', group: 'Validação', row: 'val.minmax' },
1983
+ // Material/A11y/Ações
1984
+ { name: 'materialDesign.appearance', label: 'Aparência', editorType: 'select', group: 'Material Design', options: [
1985
+ { value: 'fill', text: 'Fill' },
1986
+ { value: 'outline', text: 'Outline' },
1987
+ ] },
1988
+ { name: 'materialDesign.color', label: 'Cor do tema', editorType: 'select', group: 'Material Design', options: [
1989
+ { value: 'primary', text: 'Primária' },
1990
+ { value: 'accent', text: 'Acento' },
1991
+ { value: 'warn', text: 'Alerta' },
1992
+ ] },
1993
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
1994
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
1995
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
1996
+ ];
1997
+
1998
+ const weekInputProperties = [
1999
+ // Geral
2000
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
2001
+ { name: 'defaultValue', label: 'Valor padrão (YYYY-Www)', editorType: 'text', group: 'Geral' },
2002
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
2003
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
2004
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:date_range). Use o seletor ao lado para buscar." },
2005
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2006
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: 'Material Symbols (ex.: mi:...). Use o seletor ao lado para buscar.' },
2007
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2008
+ // Validação
2009
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
2010
+ { name: 'min', label: 'Mínimo (YYYY-Www)', editorType: 'text', group: 'Validação', row: 'val.minmax' },
2011
+ { name: 'max', label: 'Máximo (YYYY-Www)', editorType: 'text', group: 'Validação', row: 'val.minmax' },
2012
+ // Material/A11y
2013
+ { name: 'materialDesign.appearance', label: 'Aparência', editorType: 'select', group: 'Material Design', options: [
2014
+ { value: 'fill', text: 'Fill' },
2015
+ { value: 'outline', text: 'Outline' },
2016
+ ] },
2017
+ { name: 'materialDesign.color', label: 'Cor do tema', editorType: 'select', group: 'Material Design', options: [
2018
+ { value: 'primary', text: 'Primária' },
2019
+ { value: 'accent', text: 'Acento' },
2020
+ { value: 'warn', text: 'Alerta' },
2021
+ ] },
2022
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
2023
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
2024
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
2025
+ ];
2026
+
2027
+ const emailInputProperties = [
2028
+ // Geral
2029
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
2030
+ { name: 'defaultValue', label: 'Valor padrão', editorType: 'text', group: 'Geral' },
2031
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
2032
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
2033
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:mail). Use o seletor ao lado para buscar." },
2034
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2035
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:info). Use o seletor ao lado para buscar." },
2036
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2037
+ { name: 'prefix', label: 'Texto (prefixo)', editorType: 'text', group: 'Geral' },
2038
+ { name: 'suffix', label: 'Texto (sufixo)', editorType: 'text', group: 'Geral' },
2039
+ // Validação
2040
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
2041
+ { name: 'minLength', label: 'Mín. caracteres', editorType: 'number', group: 'Validação', row: 'val.len', inline: true },
2042
+ { name: 'maxLength', label: 'Máx. caracteres', editorType: 'number', group: 'Validação', row: 'val.len', inline: true },
2043
+ { name: 'pattern', label: 'Regex', editorType: 'text', group: 'Validação' },
2044
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
2045
+ { name: 'validators.minLengthMessage', label: 'Mensagem: minLength', editorType: 'text', group: 'Validação' },
2046
+ { name: 'validators.maxLengthMessage', label: 'Mensagem: maxLength', editorType: 'text', group: 'Validação' },
2047
+ { name: 'validators.patternMessage', label: 'Mensagem: regex', editorType: 'text', group: 'Validação' },
2048
+ // Comportamento
2049
+ { name: 'autocomplete', label: 'Autocomplete', editorType: 'text', group: 'Formato/Comportamento' },
2050
+ { name: 'spellcheck', label: 'Verificação ortográfica', editorType: 'checkbox', group: 'Formato/Comportamento' },
2051
+ { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Formato/Comportamento' },
2052
+ { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Formato/Comportamento' },
2053
+ { name: 'autoFocus', label: 'Focar automaticamente', editorType: 'checkbox', group: 'Formato/Comportamento' },
2054
+ { name: 'inputMode', label: 'Modo de entrada', editorType: 'text', group: 'Formato/Comportamento' },
2055
+ // Material/A11y/Ações
2056
+ { name: 'materialDesign.appearance', label: 'Aparência', editorType: 'select', group: 'Material Design', options: [
2057
+ { value: 'fill', text: 'Fill' },
2058
+ { value: 'outline', text: 'Outline' },
2059
+ ] },
2060
+ { name: 'materialDesign.color', label: 'Cor do tema', editorType: 'select', group: 'Material Design', options: [
2061
+ { value: 'primary', text: 'Primária' },
2062
+ { value: 'accent', text: 'Acento' },
2063
+ { value: 'warn', text: 'Alerta' },
2064
+ ] },
2065
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
2066
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
2067
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
2068
+ ];
2069
+
2070
+ const passwordInputProperties = [
2071
+ // Geral
2072
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
2073
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
2074
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
2075
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:lock). Use o seletor ao lado para buscar." },
2076
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2077
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:info). Use o seletor ao lado para buscar." },
2078
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2079
+ // Validação
2080
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
2081
+ { name: 'minLength', label: 'Mín. caracteres', editorType: 'number', group: 'Validação', row: 'val.len', inline: true },
2082
+ { name: 'maxLength', label: 'Máx. caracteres', editorType: 'number', group: 'Validação', row: 'val.len', inline: true },
2083
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
2084
+ { name: 'validators.minLengthMessage', label: 'Mensagem: minLength', editorType: 'text', group: 'Validação' },
2085
+ { name: 'validators.maxLengthMessage', label: 'Mensagem: maxLength', editorType: 'text', group: 'Validação' },
2086
+ // Comportamento
2087
+ { name: 'autocomplete', label: 'Autocomplete', editorType: 'text', group: 'Formato/Comportamento' },
2088
+ { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Formato/Comportamento' },
2089
+ { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Formato/Comportamento' },
2090
+ { name: 'autoFocus', label: 'Focar automaticamente', editorType: 'checkbox', group: 'Formato/Comportamento' },
2091
+ // Material/A11y
2092
+ { name: 'materialDesign.appearance', label: 'Aparência', editorType: 'select', group: 'Material Design', options: [
2093
+ { value: 'fill', text: 'Fill' },
2094
+ { value: 'outline', text: 'Outline' },
2095
+ ] },
2096
+ { name: 'materialDesign.color', label: 'Cor do tema', editorType: 'select', group: 'Material Design', options: [
2097
+ { value: 'primary', text: 'Primária' },
2098
+ { value: 'accent', text: 'Acento' },
2099
+ { value: 'warn', text: 'Alerta' },
2100
+ ] },
2101
+ ];
2102
+
2103
+ const urlInputProperties = [
2104
+ // Geral
2105
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
2106
+ { name: 'defaultValue', label: 'Valor padrão (URL)', editorType: 'text', group: 'Geral' },
2107
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
2108
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
2109
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:link). Use o seletor ao lado para buscar." },
2110
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2111
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:info). Use o seletor ao lado para buscar." },
2112
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2113
+ // Validação
2114
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
2115
+ { name: 'pattern', label: 'Regex', editorType: 'text', group: 'Validação' },
2116
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
2117
+ { name: 'validators.patternMessage', label: 'Mensagem: regex', editorType: 'text', group: 'Validação' },
2118
+ // Comportamento
2119
+ { name: 'autocomplete', label: 'Autocomplete', editorType: 'text', group: 'Formato/Comportamento' },
2120
+ { name: 'spellcheck', label: 'Verificação ortográfica', editorType: 'checkbox', group: 'Formato/Comportamento' },
2121
+ { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Formato/Comportamento' },
2122
+ { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Formato/Comportamento' },
2123
+ { name: 'autoFocus', label: 'Focar automaticamente', editorType: 'checkbox', group: 'Formato/Comportamento' },
2124
+ ];
2125
+
2126
+ const searchInputProperties = [
2127
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
2128
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
2129
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
2130
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:search). Use o seletor ao lado para buscar." },
2131
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2132
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:info). Use o seletor ao lado para buscar." },
2133
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2134
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
2135
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
2136
+ { name: 'autocomplete', label: 'Autocomplete', editorType: 'text', group: 'Formato/Comportamento' },
2137
+ ];
2138
+
2139
+ const phoneInputProperties = [
2140
+ // Geral
2141
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
2142
+ { name: 'defaultValue', label: 'Valor padrão', editorType: 'text', group: 'Geral' },
2143
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
2144
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
2145
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:call). Use o seletor ao lado para buscar." },
2146
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2147
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:info). Use o seletor ao lado para buscar." },
2148
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2149
+ // Validação
2150
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
2151
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
2152
+ // Comportamento
2153
+ { name: 'autocomplete', label: 'Autocomplete', editorType: 'text', group: 'Formato/Comportamento' },
2154
+ { name: 'spellcheck', label: 'Verificação ortográfica', editorType: 'checkbox', group: 'Formato/Comportamento' },
2155
+ { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Formato/Comportamento' },
2156
+ { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Formato/Comportamento' },
2157
+ { name: 'autoFocus', label: 'Focar automaticamente', editorType: 'checkbox', group: 'Formato/Comportamento' },
2158
+ { name: 'inputMode', label: 'Modo de entrada', editorType: 'text', group: 'Formato/Comportamento' },
2159
+ ];
2160
+
2161
+ const colorInputProperties = [
2162
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
2163
+ { name: 'defaultValue', label: 'Valor padrão (#RRGGBB)', editorType: 'text', group: 'Geral' },
2164
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
2165
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
2166
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:palette). Use o seletor ao lado para buscar." },
2167
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2168
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:info). Use o seletor ao lado para buscar." },
2169
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2170
+ { name: 'prefix', label: 'Texto (prefixo)', editorType: 'text', group: 'Geral' },
2171
+ { name: 'suffix', label: 'Texto (sufixo)', editorType: 'text', group: 'Geral' },
2172
+ // Palette / Preview
2173
+ { name: 'palettePreset', label: 'Paleta (preset)', editorType: 'select', group: 'Palette', options: [
2174
+ { value: 'material', text: 'Material' },
2175
+ { value: 'office', text: 'Office' },
2176
+ { value: 'basic', text: 'Básico' },
2177
+ ] },
2178
+ { name: 'paletteColors', label: 'Paleta: cores (JSON ou linhas)', editorType: 'textarea', group: 'Palette', hint: "JSON array ou uma cor por linha (#hex ou rgba/hsl)" },
2179
+ { name: 'columns', label: 'Paleta: colunas', editorType: 'number', group: 'Palette', defaultValue: 8 },
2180
+ { name: 'popupWidth', label: 'Popup width (px ou CSS)', editorType: 'text', group: 'Preview/Popup', defaultValue: '320' },
2181
+ { name: 'preview', label: 'Mostrar preview', editorType: 'checkbox', group: 'Preview/Popup' },
2182
+ { name: 'showRecent', label: 'Mostrar recentes', editorType: 'checkbox', group: 'Preview/Popup' },
2183
+ { name: 'maxRecent', label: 'Qtd. de recentes', editorType: 'number', group: 'Preview/Popup', defaultValue: 12 },
2184
+ { name: 'showNativeOption', label: 'Mostrar seletor nativo', editorType: 'checkbox', group: 'Preview/Popup' },
2185
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
2186
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
2187
+ { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Formato/Comportamento' },
2188
+ { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Formato/Comportamento' },
2189
+ { name: 'autoFocus', label: 'Focar automaticamente', editorType: 'checkbox', group: 'Formato/Comportamento' },
2190
+ { name: 'materialDesign.appearance', label: 'Aparência', editorType: 'select', group: 'Material Design', options: [
2191
+ { value: 'fill', text: 'Fill' },
2192
+ { value: 'outline', text: 'Outline' },
2193
+ ] },
2194
+ { name: 'materialDesign.color', label: 'Cor do tema', editorType: 'select', group: 'Material Design', options: [
2195
+ { value: 'primary', text: 'Primária' },
2196
+ { value: 'accent', text: 'Acento' },
2197
+ { value: 'warn', text: 'Alerta' },
2198
+ ] },
2199
+ { name: 'materialDesign.floatLabel', label: 'Comportamento do label', editorType: 'select', group: 'Material Design', options: [
2200
+ { value: 'auto', text: 'Auto' },
2201
+ { value: 'always', text: 'Sempre' },
2202
+ ] },
2203
+ { name: 'materialDesign.subscriptSizing', label: 'Subscript sizing', editorType: 'select', group: 'Material Design', options: [
2204
+ { value: 'fixed', text: 'Fixo' },
2205
+ { value: 'dynamic', text: 'Dinâmico' },
2206
+ ] },
2207
+ { name: 'materialDesign.hideRequiredMarker', label: 'Ocultar * obrigatório', editorType: 'checkbox', group: 'Material Design' },
2208
+ // Acessibilidade
2209
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
2210
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
2211
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
2212
+ ];
2213
+
2214
+ // ColorPicker v2 property editor (pdx-color-picker)
2215
+ const colorPickerProperties = [
2216
+ // Geral
2217
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
2218
+ { name: 'defaultValue', label: 'Valor padrão', editorType: 'text', group: 'Geral', hint: '#rrggbb, rgba(), ...' },
2219
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
2220
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Geral' },
2221
+ // Formato/Comportamento
2222
+ {
2223
+ name: 'format',
2224
+ label: 'Formato de saída',
2225
+ editorType: 'select',
2226
+ group: 'Formato/Comportamento',
2227
+ defaultValue: 'rgba',
2228
+ options: [
2229
+ { value: 'hex', text: 'HEX' },
2230
+ { value: 'rgb', text: 'RGB' },
2231
+ { value: 'rgba', text: 'RGBA' },
2232
+ { value: 'hsl', text: 'HSL' },
2233
+ ],
2234
+ },
2235
+ // Views e Preview
2236
+ {
2237
+ name: 'views',
2238
+ label: "Views (JSON: ['gradient','palette'])",
2239
+ editorType: 'textarea',
2240
+ group: 'Views/Preview',
2241
+ hint: "JSON array. Ex.: ['gradient','palette']",
2242
+ defaultValue: "['gradient','palette']",
2243
+ },
2244
+ {
2245
+ name: 'activeView',
2246
+ label: 'View ativa',
2247
+ editorType: 'select',
2248
+ group: 'Views/Preview',
2249
+ options: [
2250
+ { value: 'gradient', text: 'Gradient' },
2251
+ { value: 'palette', text: 'Palette' },
2252
+ ],
2253
+ },
2254
+ { name: 'preview', label: 'Mostrar preview (pane)', editorType: 'checkbox', group: 'Views/Preview' },
2255
+ { name: 'clearButton', label: 'Botão Clear', editorType: 'checkbox', group: 'Views/Preview' },
2256
+ {
2257
+ name: 'actionsLayout',
2258
+ label: 'Ações (layout)',
2259
+ editorType: 'select',
2260
+ group: 'Views/Preview',
2261
+ defaultValue: 'end',
2262
+ options: [
2263
+ { value: 'start', text: 'Início' },
2264
+ { value: 'end', text: 'Fim' },
2265
+ ],
2266
+ },
2267
+ // Adaptive e Popup
2268
+ { name: 'adaptiveMode', label: 'Adaptive (BottomSheet/Drawer)', editorType: 'checkbox', group: 'Adaptive/Popup' },
2269
+ { name: 'adaptiveTitle', label: 'Adaptive title', editorType: 'text', group: 'Adaptive/Popup' },
2270
+ { name: 'adaptiveSubtitle', label: 'Adaptive subtitle', editorType: 'text', group: 'Adaptive/Popup' },
2271
+ { name: 'popupSettings.width', label: 'Popup width (px ou CSS)', editorType: 'text', group: 'Adaptive/Popup', defaultValue: '340' },
2272
+ // Gradient
2273
+ { name: 'gradientSettings.showOpacity', label: 'Gradiente: transparência (alpha)', editorType: 'checkbox', group: 'Gradient' },
2274
+ {
2275
+ name: 'gradientSettings.channel',
2276
+ label: 'Gradiente: canal',
2277
+ editorType: 'select',
2278
+ group: 'Gradient',
2279
+ defaultValue: 'hsv',
2280
+ options: [
2281
+ { value: 'hsv', text: 'HSV' },
2282
+ { value: 'hsl', text: 'HSL' },
2283
+ ],
2284
+ },
2285
+ // Palette
2286
+ {
2287
+ name: 'paletteSettings.preset',
2288
+ label: 'Palette: preset',
2289
+ editorType: 'select',
2290
+ group: 'Palette',
2291
+ defaultValue: 'material',
2292
+ options: [
2293
+ { value: 'material', text: 'Material' },
2294
+ { value: 'office', text: 'Office' },
2295
+ { value: 'basic', text: 'Básico' },
2296
+ ],
2297
+ },
2298
+ {
2299
+ name: 'paletteSettings.colors',
2300
+ label: 'Palette: cores (JSON ou linhas)',
2301
+ editorType: 'textarea',
2302
+ group: 'Palette',
2303
+ hint: "JSON array ou uma cor por linha (#hex ou rgba/hsl)",
2304
+ },
2305
+ { name: 'paletteSettings.columns', label: 'Palette: colunas', editorType: 'number', group: 'Palette', defaultValue: 8 },
2306
+ // Tema/Estilo
2307
+ {
2308
+ name: 'fillMode',
2309
+ label: 'Fill mode',
2310
+ editorType: 'select',
2311
+ group: 'Theming',
2312
+ defaultValue: 'solid',
2313
+ options: [
2314
+ { value: 'solid', text: 'Solid' },
2315
+ { value: 'flat', text: 'Flat' },
2316
+ { value: 'outline', text: 'Outline' },
2317
+ ],
2318
+ },
2319
+ {
2320
+ name: 'rounded',
2321
+ label: 'Rounded',
2322
+ editorType: 'select',
2323
+ group: 'Theming',
2324
+ defaultValue: 'medium',
2325
+ options: [
2326
+ { value: 'none', text: 'None' },
2327
+ { value: 'small', text: 'Small' },
2328
+ { value: 'medium', text: 'Medium' },
2329
+ { value: 'large', text: 'Large' },
2330
+ { value: 'full', text: 'Full' },
2331
+ ],
2332
+ },
2333
+ {
2334
+ name: 'size',
2335
+ label: 'Size',
2336
+ editorType: 'select',
2337
+ group: 'Theming',
2338
+ defaultValue: 'medium',
2339
+ options: [
2340
+ { value: 'small', text: 'Small' },
2341
+ { value: 'medium', text: 'Medium' },
2342
+ { value: 'large', text: 'Large' },
2343
+ ],
2344
+ },
2345
+ { name: 'icon', label: 'Ícone', editorType: 'text', group: 'Theming', hint: "Material Symbols (ex.: mi:palette). Use o seletor ao lado para buscar." },
2346
+ { name: 'svgIcon', label: 'SVG icon', editorType: 'text', group: 'Theming' },
2347
+ { name: 'iconClass', label: 'Icon class (JSON/string)', editorType: 'textarea', group: 'Theming', hint: 'string | string[] | Record<string, boolean>' },
2348
+ // Acessibilidade/Comportamento
2349
+ { name: 'tabindex', label: 'TabIndex', editorType: 'number', group: 'Acessibilidade' },
2350
+ { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Formato/Comportamento' },
2351
+ { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Formato/Comportamento' },
2352
+ ];
2353
+
2354
+ const ratingProperties = [
2355
+ // Geral
2356
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
2357
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
2358
+ { name: 'max', label: 'Quantidade de itens', editorType: 'number', group: 'Formato/Comportamento', hint: 'Ex.: 5' },
2359
+ { name: 'allowHalf', label: 'Permitir meio ícone', editorType: 'checkbox', group: 'Formato/Comportamento' },
2360
+ { name: 'selection', label: 'Modo de seleção', editorType: 'select', group: 'Formato/Comportamento', options: [
2361
+ { value: 'continuous', text: 'Contínuo' },
2362
+ { value: 'single', text: 'Único' },
2363
+ ] },
2364
+ { name: 'size', label: 'Tamanho', editorType: 'select', group: 'Formato/Comportamento', options: [
2365
+ { value: 'small', text: 'Pequeno' },
2366
+ { value: 'medium', text: 'Médio' },
2367
+ { value: 'large', text: 'Grande' },
2368
+ ] },
2369
+ { name: 'icon', label: 'Ícone preenchido', editorType: 'text', group: 'Formato/Comportamento', hint: 'Material Symbols (ex.: mi:star). Use o seletor ao lado para buscar.' },
2370
+ { name: 'emptyIcon', label: 'Ícone vazio', editorType: 'text', group: 'Formato/Comportamento', hint: 'Material Symbols (ex.: mi:star_border). Use o seletor ao lado para buscar.' },
2371
+ { name: 'svgIcon', label: 'SVG (preenchido)', editorType: 'text', group: 'Formato/Comportamento' },
2372
+ { name: 'svgIconOutline', label: 'SVG (vazio)', editorType: 'text', group: 'Formato/Comportamento' },
2373
+ // Cores/Comportamento
2374
+ { name: 'ratingColor', label: 'Cor do preenchimento', editorType: 'color', group: 'Formato/Comportamento', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2375
+ { name: 'outlineColor', label: 'Cor do contorno', editorType: 'color', group: 'Formato/Comportamento', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2376
+ { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Formato/Comportamento' },
2377
+ { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Formato/Comportamento' },
2378
+ { name: 'tabIndex', label: 'TabIndex', editorType: 'number', group: 'Acessibilidade' },
2379
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
2380
+ ];
2381
+
2382
+ const chipInputProperties = [
2383
+ // Geral
2384
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
2385
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
2386
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
2387
+ // Dados/Opções
2388
+ { name: 'options', label: 'Opções (JSON ou linhas valor|rótulo)', editorType: 'textarea', group: 'Dados/Opções' },
2389
+ { name: 'resourcePath', label: 'Resource Path (remoto)', editorType: 'text', group: 'Dados/Opções', row: 'dados.remoto', inline: true },
2390
+ { name: 'filterCriteria', label: 'Critérios de filtro (JSON)', editorType: 'textarea', group: 'Dados/Opções', hint: '{ "active": true }' },
2391
+ { name: 'optionLabelKey', label: 'Chave do rótulo (remoto)', editorType: 'text', group: 'Dados/Opções', row: 'dados.keys', inline: true },
2392
+ { name: 'optionValueKey', label: 'Chave do valor (remoto)', editorType: 'text', group: 'Dados/Opções', row: 'dados.keys', inline: true },
2393
+ // Formato/Comportamento
2394
+ { name: 'maxSelections', label: 'Máx. seleções', editorType: 'number', group: 'Formato/Comportamento' },
2395
+ { name: 'removable', label: 'Removível', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comp.flags', inline: true },
2396
+ { name: 'addOnBlur', label: 'Adicionar ao sair', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comp.flags', inline: true },
2397
+ { name: 'searchable', label: 'Pesquisável', editorType: 'checkbox', group: 'Formato/Comportamento' },
2398
+ { name: 'minLength', label: 'Mín. caracteres busca', editorType: 'number', group: 'Formato/Comportamento', row: 'comp.search', inline: true },
2399
+ { name: 'validationDebounce', label: 'Debounce busca (ms)', editorType: 'number', group: 'Formato/Comportamento', row: 'comp.search', inline: true },
2400
+ // Validação
2401
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
2402
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
2403
+ // Material Design
2404
+ {
2405
+ name: 'materialDesign.appearance',
2406
+ label: 'Aparência',
2407
+ editorType: 'select',
2408
+ group: 'Material Design',
2409
+ options: [
2410
+ { value: 'fill', text: 'Fill' },
2411
+ { value: 'outline', text: 'Outline' },
2412
+ ],
2413
+ },
2414
+ {
2415
+ name: 'materialDesign.color',
2416
+ label: 'Cor do tema',
2417
+ editorType: 'select',
2418
+ group: 'Material Design',
2419
+ options: [
2420
+ { value: 'primary', text: 'Primária' },
2421
+ { value: 'accent', text: 'Acento' },
2422
+ { value: 'warn', text: 'Alerta' },
2423
+ ],
2424
+ },
2425
+ {
2426
+ name: 'materialDesign.floatLabel',
2427
+ label: 'Comportamento do label',
2428
+ editorType: 'select',
2429
+ group: 'Material Design',
2430
+ options: [
2431
+ { value: 'auto', text: 'Auto' },
2432
+ { value: 'always', text: 'Sempre' },
2433
+ ],
2434
+ },
2435
+ {
2436
+ name: 'materialDesign.subscriptSizing',
2437
+ label: 'Subscript sizing',
2438
+ editorType: 'select',
2439
+ group: 'Material Design',
2440
+ options: [
2441
+ { value: 'fixed', text: 'Fixo' },
2442
+ { value: 'dynamic', text: 'Dinâmico' },
2443
+ ],
2444
+ },
2445
+ { name: 'materialDesign.hideRequiredMarker', label: 'Ocultar * obrigatório', editorType: 'checkbox', group: 'Material Design' },
2446
+ // Acessibilidade
2447
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
2448
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
2449
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
2450
+ { name: 'tabIndex', label: 'TabIndex', editorType: 'number', group: 'Acessibilidade' },
2451
+ { name: 'dataAttributes', label: 'Data attributes (JSON)', editorType: 'textarea', group: 'Acessibilidade', hint: '{ "testId": "chips-a" }' },
2452
+ // Ações (Clear)
2453
+ { name: 'clearButton.enabled', label: 'Clear: habilitar', editorType: 'checkbox', group: 'Ações' },
2454
+ { name: 'clearButton.icon', label: 'Clear: ícone', editorType: 'text', group: 'Ações', hint: "Material Symbols (ex.: mi:clear). Use o seletor ao lado para buscar." },
2455
+ { name: 'clearButton.iconColor', label: 'Clear: cor do ícone', editorType: 'color', group: 'Ações', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2456
+ { name: 'clearButton.tooltip', label: 'Clear: tooltip', editorType: 'text', group: 'Ações' },
2457
+ { name: 'clearButton.ariaLabel', label: 'Clear: aria-label', editorType: 'text', group: 'Ações' },
2458
+ { name: 'clearButton.showOnlyWhenFilled', label: 'Clear: só quando preenchido', editorType: 'checkbox', group: 'Ações' },
2459
+ ];
2460
+
2461
+ const checkboxGroupProperties = [
2462
+ // Dados/Opções
2463
+ { name: 'options', label: 'Opções (JSON ou linhas valor|rótulo)', editorType: 'textarea', group: 'Dados/Opções' },
2464
+ { name: 'resourcePath', label: 'Recurso (remoto)', editorType: 'text', group: 'Dados/Opções', row: 'dados.remoto', inline: true },
2465
+ { name: 'filterCriteria', label: 'Filtro (JSON)', editorType: 'textarea', group: 'Dados/Opções' },
2466
+ { name: 'optionLabelKey', label: 'Chave rótulo', editorType: 'text', group: 'Dados/Opções', row: 'dados.keys', inline: true },
2467
+ { name: 'optionValueKey', label: 'Chave valor', editorType: 'text', group: 'Dados/Opções', row: 'dados.keys', inline: true },
2468
+ // Formato/Comportamento
2469
+ { name: 'searchable', label: 'Pesquisável', editorType: 'checkbox', group: 'Formato/Comportamento' },
2470
+ { name: 'selectAll', label: 'Selecionar todos', editorType: 'checkbox', group: 'Formato/Comportamento' },
2471
+ { name: 'maxSelections', label: 'Máx. seleções', editorType: 'number', group: 'Formato/Comportamento' },
2472
+ { name: 'layout', label: 'Layout', editorType: 'select', group: 'Formato/Comportamento', options: [
2473
+ { value: 'horizontal', text: 'Horizontal' },
2474
+ { value: 'vertical', text: 'Vertical' },
2475
+ ] },
2476
+ { name: 'labelPosition', label: 'Posição do label', editorType: 'select', group: 'Formato/Comportamento', options: [
2477
+ { value: 'before', text: 'Antes' },
2478
+ { value: 'after', text: 'Depois' },
2479
+ ] },
2480
+ { name: 'color', label: 'Cor', editorType: 'select', group: 'Formato/Comportamento', options: [
2481
+ { value: 'primary', text: 'Primária' },
2482
+ { value: 'accent', text: 'Acento' },
2483
+ { value: 'warn', text: 'Alerta' },
2484
+ ] },
2485
+ { name: 'indeterminate', label: 'Indeterminado', editorType: 'checkbox', group: 'Formato/Comportamento' },
2486
+ { name: 'linkText', label: 'Texto do link', editorType: 'text', group: 'Formato/Comportamento' },
2487
+ { name: 'linkUrl', label: 'URL do link', editorType: 'text', group: 'Formato/Comportamento' },
2488
+ { name: 'linkTarget', label: 'Alvo do link', editorType: 'select', group: 'Formato/Comportamento', options: [
2489
+ { value: '_self', text: 'Mesma janela' },
2490
+ { value: '_blank', text: 'Nova janela' },
2491
+ ] },
2492
+ // Geral/Validação
2493
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
2494
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
2495
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
2496
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
2497
+ // Acessibilidade
2498
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
2499
+ ];
2500
+
2501
+ const toggleProperties = [
2502
+ // Geral
2503
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
2504
+ { name: 'defaultValue', label: 'Valor padrão (boolean)', editorType: 'checkbox', group: 'Geral' },
2505
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
2506
+ // Material Design
2507
+ { name: 'color', label: 'Cor', editorType: 'select', group: 'Material Design', options: [
2508
+ { value: 'primary', text: 'Primária' },
2509
+ { value: 'accent', text: 'Acento' },
2510
+ { value: 'warn', text: 'Alerta' },
2511
+ ] },
2512
+ // Formato/Comportamento
2513
+ { name: 'labelPosition', label: 'Posição do label', editorType: 'select', group: 'Formato/Comportamento', options: [
2514
+ { value: 'before', text: 'Antes' },
2515
+ { value: 'after', text: 'Depois' },
2516
+ ] },
2517
+ { name: 'hideIcon', label: 'Ocultar ícone', editorType: 'checkbox', group: 'Formato/Comportamento' },
2518
+ { name: 'disableRipple', label: 'Desabilitar ripple', editorType: 'checkbox', group: 'Formato/Comportamento' },
2519
+ { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Formato/Comportamento' },
2520
+ { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Formato/Comportamento' },
2521
+ { name: 'autoFocus', label: 'Focar automaticamente', editorType: 'checkbox', group: 'Formato/Comportamento' },
2522
+ // Validação
2523
+ { name: 'requiredTrue', label: 'Exigir verdadeiro', editorType: 'checkbox', group: 'Validação' },
2524
+ { name: 'validators.requiredTrueMessage', label: 'Mensagem: exigir verdadeiro', editorType: 'text', group: 'Validação' },
2525
+ // Acessibilidade
2526
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
2527
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
2528
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
2529
+ ];
2530
+
2531
+ const sliderProperties = [
2532
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
2533
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
2534
+ { name: 'min', label: 'Mínimo', editorType: 'number', group: 'Validação', row: 'val.minmax', inline: true },
2535
+ { name: 'max', label: 'Máximo', editorType: 'number', group: 'Validação', row: 'val.minmax', inline: true },
2536
+ { name: 'validators.minMessage', label: 'Mensagem: mínimo', editorType: 'text', group: 'Validação' },
2537
+ { name: 'validators.maxMessage', label: 'Mensagem: máximo', editorType: 'text', group: 'Validação' },
2538
+ { name: 'step', label: 'Step', editorType: 'number', group: 'Formato/Comportamento' },
2539
+ { name: 'thumbLabel', label: 'Thumb label', editorType: 'checkbox', group: 'Formato/Comportamento' },
2540
+ { name: 'vertical', label: 'Vertical', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'fmt.flags', inline: true },
2541
+ { name: 'showTicks', label: 'Ticks', editorType: 'select', group: 'Formato/Comportamento', options: [
2542
+ { value: false, text: 'Sem ticks' },
2543
+ { value: true, text: 'Com ticks' },
2544
+ { value: 'auto', text: 'Auto' },
2545
+ ] },
2546
+ { name: 'discrete', label: 'Discreto', editorType: 'checkbox', group: 'Formato/Comportamento' },
2547
+ { name: 'invert', label: 'Inverter', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'fmt.flags', inline: true },
2548
+ { name: 'color', label: 'Cor', editorType: 'select', group: 'Formato/Comportamento', options: [
2549
+ { value: 'primary', text: 'Primária' },
2550
+ { value: 'accent', text: 'Acento' },
2551
+ { value: 'warn', text: 'Alerta' },
2552
+ ] },
2553
+ ];
2554
+
2555
+ const rangeSliderProperties = [
2556
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
2557
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
2558
+ { name: 'mode', label: 'Modo', editorType: 'select', group: 'Formato/Comportamento', options: [
2559
+ { value: 'single', text: 'Único' },
2560
+ { value: 'range', text: 'Faixa' },
2561
+ ] },
2562
+ { name: 'min', label: 'Mínimo', editorType: 'number', group: 'Validação', row: 'val.minmax', inline: true },
2563
+ { name: 'max', label: 'Máximo', editorType: 'number', group: 'Validação', row: 'val.minmax', inline: true },
2564
+ { name: 'validators.minMessage', label: 'Mensagem: mínimo', editorType: 'text', group: 'Validação' },
2565
+ { name: 'validators.maxMessage', label: 'Mensagem: máximo', editorType: 'text', group: 'Validação' },
2566
+ { name: 'step', label: 'Step', editorType: 'number', group: 'Formato/Comportamento' },
2567
+ { name: 'thumbLabel', label: 'Thumb label', editorType: 'checkbox', group: 'Formato/Comportamento' },
2568
+ { name: 'showTicks', label: 'Ticks', editorType: 'select', group: 'Formato/Comportamento', options: [
2569
+ { value: false, text: 'Sem ticks' },
2570
+ { value: true, text: 'Com ticks' },
2571
+ { value: 'auto', text: 'Auto' },
2572
+ ] },
2573
+ { name: 'discrete', label: 'Discreto', editorType: 'checkbox', group: 'Formato/Comportamento' },
2574
+ { name: 'vertical', label: 'Vertical', editorType: 'checkbox', group: 'Formato/Comportamento' },
2575
+ { name: 'invert', label: 'Inverter', editorType: 'checkbox', group: 'Formato/Comportamento' },
2576
+ { name: 'minDistance', label: 'Distância mínima', editorType: 'number', group: 'Validação', row: 'val.distance', inline: true },
2577
+ { name: 'maxDistance', label: 'Distância máxima', editorType: 'number', group: 'Validação', row: 'val.distance', inline: true },
2578
+ { name: 'validators.rangeMessage', label: 'Mensagem: ordem do range', editorType: 'text', group: 'Validação' },
2579
+ { name: 'validators.distanceMessage', label: 'Mensagem: distância', editorType: 'text', group: 'Validação' },
2580
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
2581
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
2582
+ { name: 'displayWith', label: 'DisplayWith (id função)', editorType: 'text', group: 'Formato/Comportamento' },
2583
+ { name: 'showInputs', label: 'Mostrar inputs', editorType: 'checkbox', group: 'Formato/Comportamento' },
2584
+ { name: 'color', label: 'Cor', editorType: 'select', group: 'Formato/Comportamento', options: [
2585
+ { value: 'primary', text: 'Primária' },
2586
+ { value: 'accent', text: 'Acento' },
2587
+ { value: 'warn', text: 'Alerta' },
2588
+ ] },
2589
+ ];
2590
+
2591
+ const currencyInputProperties = [
2592
+ // Geral
2593
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
2594
+ { name: 'defaultValue', label: 'Valor padrão', editorType: 'number', group: 'Geral' },
2595
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
2596
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
2597
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: 'Material Symbols (ex.: mi:...). Use o seletor ao lado para buscar.' },
2598
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2599
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: 'Material Symbols (ex.: mi:...). Use o seletor ao lado para buscar.' },
2600
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2601
+ { name: 'prefix', label: 'Texto (prefixo)', editorType: 'text', group: 'Geral' },
2602
+ { name: 'suffix', label: 'Texto (sufixo)', editorType: 'text', group: 'Geral' },
2603
+ // Validação
2604
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
2605
+ { name: 'min', label: 'Mínimo', editorType: 'number', group: 'Validação', row: 'val.minmax', inline: true },
2606
+ { name: 'max', label: 'Máximo', editorType: 'number', group: 'Validação', row: 'val.minmax', inline: true },
2607
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
2608
+ { name: 'validators.minMessage', label: 'Mensagem: mínimo', editorType: 'text', group: 'Validação' },
2609
+ { name: 'validators.maxMessage', label: 'Mensagem: máximo', editorType: 'text', group: 'Validação' },
2610
+ // Formato/Comportamento
2611
+ { name: 'numberFormat.currency', label: 'Moeda', editorType: 'text', group: 'Formato/Comportamento' },
2612
+ { name: 'numberFormat.currencyPosition', label: 'Posição moeda', editorType: 'select', group: 'Formato/Comportamento', options: [
2613
+ { value: 'before', text: 'Antes' },
2614
+ { value: 'after', text: 'Depois' },
2615
+ ] },
2616
+ { name: 'numberFormat.decimalPlaces', label: 'Casas decimais', editorType: 'number', group: 'Formato/Comportamento' },
2617
+ { name: 'numberFormat.locale', label: 'Locale', editorType: 'text', group: 'Formato/Comportamento' },
2618
+ { name: 'showGrouping', label: 'Agrupar milhares', editorType: 'checkbox', group: 'Formato/Comportamento' },
2619
+ // Material/A11y/Ações
2620
+ { name: 'materialDesign.appearance', label: 'Aparência', editorType: 'select', group: 'Material Design', options: [
2621
+ { value: 'fill', text: 'Fill' },
2622
+ { value: 'outline', text: 'Outline' },
2623
+ ] },
2624
+ { name: 'materialDesign.color', label: 'Cor do tema', editorType: 'select', group: 'Material Design', options: [
2625
+ { value: 'primary', text: 'Primária' },
2626
+ { value: 'accent', text: 'Acento' },
2627
+ { value: 'warn', text: 'Alerta' },
2628
+ ] },
2629
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
2630
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
2631
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
2632
+ // Sem Clear: componente não implementa botão de limpar
2633
+ ];
2634
+
2635
+ const cpfCnpjProperties = [
2636
+ // Geral
2637
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
2638
+ { name: 'defaultValue', label: 'Valor padrão', editorType: 'text', group: 'Geral' },
2639
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
2640
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
2641
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:badge). Use o seletor ao lado para buscar." },
2642
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2643
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:info). Use o seletor ao lado para buscar." },
2644
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2645
+ { name: 'prefix', label: 'Texto (prefixo)', editorType: 'text', group: 'Geral' },
2646
+ { name: 'suffix', label: 'Texto (sufixo)', editorType: 'text', group: 'Geral' },
2647
+ // Validação
2648
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
2649
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
2650
+ { name: 'validators.patternMessage', label: 'Mensagem: formato inválido', editorType: 'text', group: 'Validação' },
2651
+ // Formato/Comportamento
2652
+ { name: 'documentType', label: 'Tipo de documento', editorType: 'select', group: 'Formato/Comportamento', options: [
2653
+ { value: 'auto', text: 'Auto' },
2654
+ { value: 'cpf', text: 'CPF' },
2655
+ { value: 'cnpj', text: 'CNPJ' },
2656
+ ] },
2657
+ { name: 'version', label: 'Versão (parsing)', editorType: 'select', group: 'Formato/Comportamento', options: [
2658
+ { value: 'auto', text: 'Auto' },
2659
+ { value: 'legacy', text: 'Legacy (numérico)' },
2660
+ { value: 'alpha', text: 'Alpha (alfa-numérico para CNPJ)' },
2661
+ ] },
2662
+ { name: 'allowFormattedInput', label: 'Permitir entrada formatada', editorType: 'checkbox', group: 'Formato/Comportamento' },
2663
+ { name: 'unmaskOnSubmit', label: 'Enviar sem máscara (modelo)', editorType: 'checkbox', group: 'Formato/Comportamento' },
2664
+ { name: 'autocomplete', label: 'Autocomplete', editorType: 'text', group: 'Formato/Comportamento' },
2665
+ { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Formato/Comportamento' },
2666
+ { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Formato/Comportamento' },
2667
+ { name: 'autoFocus', label: 'Focar automaticamente', editorType: 'checkbox', group: 'Formato/Comportamento' },
2668
+ // Material Design
2669
+ { name: 'materialDesign.appearance', label: 'Aparência', editorType: 'select', group: 'Material Design', options: [
2670
+ { value: 'fill', text: 'Fill' },
2671
+ { value: 'outline', text: 'Outline' },
2672
+ ] },
2673
+ { name: 'materialDesign.color', label: 'Cor do tema', editorType: 'select', group: 'Material Design', options: [
2674
+ { value: 'primary', text: 'Primária' },
2675
+ { value: 'accent', text: 'Acento' },
2676
+ { value: 'warn', text: 'Alerta' },
2677
+ ] },
2678
+ { name: 'materialDesign.floatLabel', label: 'Comportamento do label', editorType: 'select', group: 'Material Design', options: [
2679
+ { value: 'auto', text: 'Auto' },
2680
+ { value: 'always', text: 'Sempre' },
2681
+ ] },
2682
+ { name: 'materialDesign.subscriptSizing', label: 'Subscript sizing', editorType: 'select', group: 'Material Design', options: [
2683
+ { value: 'fixed', text: 'Fixo' },
2684
+ { value: 'dynamic', text: 'Dinâmico' },
2685
+ ] },
2686
+ { name: 'materialDesign.hideRequiredMarker', label: 'Ocultar * obrigatório', editorType: 'checkbox', group: 'Material Design' },
2687
+ // Acessibilidade
2688
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
2689
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
2690
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
2691
+ ];
2692
+
2693
+ const fileUploadProperties = [
2694
+ // Dados/Opções
2695
+ { name: 'accept', label: 'Tipos aceitos (mimes/ext.)', editorType: 'text', group: 'Dados/Opções' },
2696
+ { name: 'multiple', label: 'Múltiplos arquivos', editorType: 'checkbox', group: 'Dados/Opções' },
2697
+ { name: 'maxSizeMB', label: 'Tamanho máx (MB)', editorType: 'number', group: 'Dados/Opções' },
2698
+ { name: 'resourcePath', label: 'Recurso', editorType: 'text', group: 'Dados/Opções' },
2699
+ { name: 'uploadUrl', label: 'Upload URL', editorType: 'text', group: 'Dados/Opções' },
2700
+ { name: 'filterCriteria', label: 'Filtro (JSON)', editorType: 'textarea', group: 'Dados/Opções' },
2701
+ // Geral/Validação
2702
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
2703
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
2704
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
2705
+ { name: 'validators.requiredMessage', label: 'Mensagem: obrigatório', editorType: 'text', group: 'Validação' },
2706
+ // Acessibilidade
2707
+ { name: 'ariaLabel', label: 'ARIA label', editorType: 'text', group: 'Acessibilidade' },
2708
+ { name: 'ariaDescribedby', label: 'ARIA describedby', editorType: 'text', group: 'Acessibilidade' },
2709
+ { name: 'ariaLabelledby', label: 'ARIA labelledby', editorType: 'text', group: 'Acessibilidade' },
2710
+ ];
2711
+
2712
+ const yearInputProperties = [
2713
+ { name: 'label', label: 'Label', editorType: 'text', group: 'Geral' },
2714
+ { name: 'defaultValue', label: 'Valor padrão (YYYY)', editorType: 'number', group: 'Geral' },
2715
+ { name: 'placeholder', label: 'Placeholder', editorType: 'text', group: 'Geral' },
2716
+ { name: 'hint', label: 'Hint', editorType: 'text', group: 'Geral' },
2717
+ { name: 'prefixIcon', label: 'Ícone (prefixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:calendar_month). Use o seletor ao lado para buscar." },
2718
+ { name: 'prefixIconColor', label: 'Cor do ícone (prefixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2719
+ { name: 'suffixIcon', label: 'Ícone (sufixo)', editorType: 'text', group: 'Geral', hint: "Material Symbols (ex.: mi:info). Use o seletor ao lado para buscar." },
2720
+ { name: 'suffixIconColor', label: 'Cor do ícone (sufixo)', editorType: 'color', group: 'Geral', hint: 'primary/accent/warn ou cor CSS (ex.: #496ddb, rgb(73,109,219), red)' },
2721
+ // Validação/Comportamento
2722
+ { name: 'required', label: 'Obrigatório', editorType: 'checkbox', group: 'Validação' },
2723
+ { name: 'minYear', label: 'Ano mínimo', editorType: 'number', group: 'Validação', row: 'val.minmax', inline: true },
2724
+ { name: 'maxYear', label: 'Ano máximo', editorType: 'number', group: 'Validação', row: 'val.minmax', inline: true },
2725
+ { name: 'step', label: 'Step', editorType: 'number', group: 'Formato/Comportamento' },
2726
+ { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Formato/Comportamento' },
2727
+ { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Formato/Comportamento' },
2728
+ { name: 'autoFocus', label: 'Focar automaticamente', editorType: 'checkbox', group: 'Formato/Comportamento' },
2729
+ ];
2730
+
2731
+ /** Garante que minLength <= maxLength quando ambos definidos. */
2732
+ function minLenLeMaxLen(group) {
2733
+ const v = group.value || {};
2734
+ const min = v?.minLength;
2735
+ const max = v?.maxLength;
2736
+ if (min != null && max != null && Number(min) > Number(max)) {
2737
+ return { minLengthGtMaxLength: true };
2738
+ }
2739
+ return null;
2740
+ }
2741
+
2742
+ class FieldMetadataEditorComponent {
2743
+ registry;
2744
+ normalizer;
2745
+ factory;
2746
+ ctxValidators;
2747
+ controlType;
2748
+ seed;
2749
+ applied = new EventEmitter();
2750
+ cancel = new EventEmitter();
2751
+ form;
2752
+ normalizedProps = [];
2753
+ initialSnapshot = null;
2754
+ baselineReady = false;
2755
+ // Lista de tipos suportados no editor (curada para os tipos com config disponível)
2756
+ typeOptions = [
2757
+ { value: FieldControlType.INPUT, label: 'Texto (Input)' },
2758
+ { value: FieldControlType.TEXTAREA, label: 'Texto longo (Textarea)' },
2759
+ { value: FieldControlType.NUMERIC_TEXT_BOX, label: 'Número' },
2760
+ { value: FieldControlType.CURRENCY_INPUT, label: 'Moeda' },
2761
+ { value: FieldControlType.EMAIL_INPUT, label: 'E-mail' },
2762
+ { value: FieldControlType.PASSWORD, label: 'Senha' },
2763
+ { value: FieldControlType.URL_INPUT, label: 'URL' },
2764
+ { value: FieldControlType.SEARCH_INPUT, label: 'Busca' },
2765
+ { value: FieldControlType.PHONE, label: 'Telefone' },
2766
+ { value: FieldControlType.SELECT, label: 'Seleção (Select)' },
2767
+ { value: FieldControlType.MULTI_SELECT, label: 'Seleção múltipla' },
2768
+ { value: FieldControlType.AUTO_COMPLETE, label: 'Auto-completar' },
2769
+ { value: FieldControlType.SELECTION_LIST, label: 'Lista de seleção' },
2770
+ { value: FieldControlType.TREE_SELECT, label: 'Árvore (Tree Select)' },
2771
+ { value: FieldControlType.MULTI_SELECT_TREE, label: 'Árvore múltipla' },
2772
+ { value: FieldControlType.RADIO, label: 'Opções (Radio)' },
2773
+ { value: FieldControlType.BUTTON_TOGGLE, label: 'Alternador (Button Toggle)' },
2774
+ { value: FieldControlType.CHECKBOX, label: 'Caixa de seleção' },
2775
+ { value: FieldControlType.TOGGLE, label: 'Liga/Desliga (Toggle)' },
2776
+ { value: FieldControlType.SLIDER, label: 'Slider' },
2777
+ { value: FieldControlType.RANGE_SLIDER, label: 'Slider (intervalo)' },
2778
+ { value: FieldControlType.DATE_INPUT, label: 'Data (Input)' },
2779
+ { value: FieldControlType.DATE_RANGE, label: 'Intervalo de data' },
2780
+ { value: FieldControlType.DATETIME_LOCAL_INPUT, label: 'Data e Hora (local)' },
2781
+ { value: FieldControlType.TIME_INPUT, label: 'Hora (Input)' },
2782
+ { value: FieldControlType.TIME_PICKER, label: 'Hora (Picker)' },
2783
+ { value: FieldControlType.TIME_RANGE, label: 'Intervalo de hora' },
2784
+ { value: FieldControlType.MONTH_INPUT, label: 'Mês' },
2785
+ { value: FieldControlType.WEEK_INPUT, label: 'Semana' },
2786
+ { value: FieldControlType.YEAR_INPUT, label: 'Ano' },
2787
+ { value: FieldControlType.COLOR_INPUT, label: 'Cor (Input)' },
2788
+ { value: FieldControlType.COLOR_PICKER, label: 'Cor (Picker)' },
2789
+ { value: FieldControlType.RATING, label: 'Avaliação (Rating)' },
2790
+ { value: FieldControlType.CHIP_INPUT, label: 'Chips' },
2791
+ { value: FieldControlType.TRANSFER_LIST, label: 'Transferência de itens' },
2792
+ { value: FieldControlType.CPF_CNPJ_INPUT, label: 'CPF/CNPJ' },
2793
+ { value: FieldControlType.FILE_UPLOAD, label: 'Upload de Arquivo' },
2794
+ ];
2795
+ // Settings Panel integration (contract: isDirty$, isValid$, isBusy$)
2796
+ isDirty$ = new BehaviorSubject(false);
2797
+ isValid$ = new BehaviorSubject(true);
2798
+ isBusy$ = new BehaviorSubject(false);
2799
+ constructor(registry, normalizer, factory, ctxValidators) {
2800
+ this.registry = registry;
2801
+ this.normalizer = normalizer;
2802
+ this.factory = factory;
2803
+ this.ctxValidators = ctxValidators;
2804
+ // Support SETTINGS_PANEL_DATA injection as input fallback
2805
+ const data = inject(SETTINGS_PANEL_DATA, { optional: true });
2806
+ if (data) {
2807
+ if (data.controlType)
2808
+ this.controlType = data.controlType;
2809
+ if (data.seed)
2810
+ this.seed = data.seed;
2811
+ // Bridge: allow host to receive patches even if saved$/applied$ are missed
2812
+ try {
2813
+ this.__hostApplyPatch = data.hostBridge?.applyPatch;
2814
+ }
2815
+ catch { }
2816
+ }
2817
+ }
2818
+ ngOnInit() {
2819
+ try {
2820
+ const hasBridge = typeof this.__hostApplyPatch === 'function';
2821
+ (console.log || console.debug)('[FieldMetadataEditor] ngOnInit', { controlType: this.controlType, hasHostBridge: hasBridge });
2822
+ }
2823
+ catch { }
2824
+ this.rebuildEditorForType(this.controlType);
2825
+ }
2826
+ rebuildEditorForType(ct) {
2827
+ this.controlType = ct;
2828
+ let props = this.registry.getProperties(this.controlType);
2829
+ try {
2830
+ (console.log || console.debug)('[FieldMetadataEditor] init', {
2831
+ controlType: this.controlType,
2832
+ seedPreview: {
2833
+ resourcePath: this.seed?.resourcePath,
2834
+ optionLabelKey: this.seed?.optionLabelKey,
2835
+ optionValueKey: this.seed?.optionValueKey,
2836
+ filterCriteria: typeof this.seed?.filterCriteria === 'string'
2837
+ ? this.seed?.filterCriteria
2838
+ : this.seed?.filterCriteria != null
2839
+ ? JSON.stringify(this.seed?.filterCriteria)
2840
+ : undefined,
2841
+ },
2842
+ propsBefore: props.map((p) => p.name),
2843
+ });
2844
+ }
2845
+ catch { }
2846
+ // Fallback de registro mínimo para facilitar execução inicial
2847
+ if (!props.length) {
2848
+ if (this.controlType === FieldControlType.INPUT) {
2849
+ this.registry.register(this.controlType, inputProperties);
2850
+ props = inputProperties;
2851
+ }
2852
+ else if (
2853
+ // Aliases for select-like
2854
+ this.controlType === 'searchable-select' ||
2855
+ this.controlType === 'async-select') {
2856
+ this.registry.register(this.controlType, selectProperties);
2857
+ props = selectProperties;
2858
+ }
2859
+ else if (this.controlType === FieldControlType.NUMERIC_TEXT_BOX) {
2860
+ this.registry.register(this.controlType, numberProperties);
2861
+ props = numberProperties;
2862
+ }
2863
+ else if (this.controlType === FieldControlType.DATE_PICKER) {
2864
+ this.registry.register(this.controlType, dateProperties);
2865
+ props = dateProperties;
2866
+ }
2867
+ else if (this.controlType === FieldControlType.DATE_INPUT) {
2868
+ this.registry.register(this.controlType, dateInputProperties);
2869
+ props = dateInputProperties;
2870
+ }
2871
+ else if (this.controlType === FieldControlType.DATE_RANGE) {
2872
+ this.registry.register(this.controlType, dateRangeProperties);
2873
+ props = dateRangeProperties;
2874
+ }
2875
+ else if (this.controlType === FieldControlType.DATETIME_LOCAL_INPUT) {
2876
+ this.registry.register(this.controlType, datetimeLocalProperties);
2877
+ props = datetimeLocalProperties;
2878
+ }
2879
+ else if (this.controlType === FieldControlType.TIME_INPUT) {
2880
+ this.registry.register(this.controlType, timeInputProperties);
2881
+ props = timeInputProperties;
2882
+ }
2883
+ else if (this.controlType === FieldControlType.TIME_PICKER) {
2884
+ this.registry.register(this.controlType, timePickerProperties);
2885
+ props = timePickerProperties;
2886
+ }
2887
+ else if (this.controlType === FieldControlType.TIME_RANGE) {
2888
+ this.registry.register(this.controlType, timeRangeProperties);
2889
+ props = timeRangeProperties;
2890
+ }
2891
+ else if (this.controlType === FieldControlType.MONTH_INPUT) {
2892
+ this.registry.register(this.controlType, monthInputProperties);
2893
+ props = monthInputProperties;
2894
+ }
2895
+ else if (this.controlType === FieldControlType.WEEK_INPUT) {
2896
+ this.registry.register(this.controlType, weekInputProperties);
2897
+ props = weekInputProperties;
2898
+ }
2899
+ else if (this.controlType === FieldControlType.EMAIL_INPUT) {
2900
+ this.registry.register(this.controlType, emailInputProperties);
2901
+ props = emailInputProperties;
2902
+ }
2903
+ else if (this.controlType === FieldControlType.PASSWORD) {
2904
+ this.registry.register(this.controlType, passwordInputProperties);
2905
+ props = passwordInputProperties;
2906
+ }
2907
+ else if (this.controlType === FieldControlType.URL_INPUT) {
2908
+ this.registry.register(this.controlType, urlInputProperties);
2909
+ props = urlInputProperties;
2910
+ }
2911
+ else if (this.controlType === FieldControlType.SEARCH_INPUT) {
2912
+ this.registry.register(this.controlType, searchInputProperties);
2913
+ props = searchInputProperties;
2914
+ }
2915
+ else if (this.controlType === FieldControlType.PHONE) {
2916
+ this.registry.register(this.controlType, phoneInputProperties);
2917
+ props = phoneInputProperties;
2918
+ }
2919
+ else if (this.controlType === FieldControlType.COLOR_INPUT) {
2920
+ this.registry.register(this.controlType, colorInputProperties);
2921
+ props = colorInputProperties;
2922
+ }
2923
+ else if (this.controlType === FieldControlType.COLOR_PICKER) {
2924
+ this.registry.register(this.controlType, colorPickerProperties);
2925
+ props = colorPickerProperties;
2926
+ }
2927
+ else if (this.controlType === FieldControlType.RATING) {
2928
+ this.registry.register(this.controlType, ratingProperties);
2929
+ props = ratingProperties;
2930
+ }
2931
+ else if (this.controlType === FieldControlType.SELECT ||
2932
+ this.controlType === FieldControlType.MULTI_SELECT ||
2933
+ this.controlType === FieldControlType.AUTO_COMPLETE ||
2934
+ this.controlType === FieldControlType.SELECTION_LIST) {
2935
+ this.registry.register(this.controlType, selectProperties);
2936
+ props = selectProperties;
2937
+ }
2938
+ else if (this.controlType === FieldControlType.TEXTAREA) {
2939
+ this.registry.register(this.controlType, textareaProperties);
2940
+ props = textareaProperties;
2941
+ }
2942
+ else if (this.controlType === FieldControlType.TRANSFER_LIST) {
2943
+ this.registry.register(this.controlType, transferListProperties);
2944
+ props = transferListProperties;
2945
+ }
2946
+ else if (this.controlType === FieldControlType.RADIO) {
2947
+ this.registry.register(this.controlType, radioProperties);
2948
+ props = radioProperties;
2949
+ }
2950
+ else if (this.controlType === FieldControlType.BUTTON_TOGGLE) {
2951
+ this.registry.register(this.controlType, buttonToggleProperties);
2952
+ props = buttonToggleProperties;
2953
+ }
2954
+ else if (this.controlType === FieldControlType.CHIP_INPUT) {
2955
+ this.registry.register(this.controlType, chipInputProperties);
2956
+ props = chipInputProperties;
2957
+ }
2958
+ else if (this.controlType === FieldControlType.CHECKBOX) {
2959
+ this.registry.register(this.controlType, checkboxGroupProperties);
2960
+ props = checkboxGroupProperties;
2961
+ }
2962
+ else if (this.controlType === FieldControlType.TOGGLE) {
2963
+ this.registry.register(this.controlType, toggleProperties);
2964
+ props = toggleProperties;
2965
+ }
2966
+ else if (this.controlType === FieldControlType.SLIDER) {
2967
+ this.registry.register(this.controlType, sliderProperties);
2968
+ props = sliderProperties;
2969
+ }
2970
+ else if (this.controlType === FieldControlType.RANGE_SLIDER) {
2971
+ this.registry.register(this.controlType, rangeSliderProperties);
2972
+ props = rangeSliderProperties;
2973
+ }
2974
+ else if (this.controlType === FieldControlType.CURRENCY_INPUT) {
2975
+ this.registry.register(this.controlType, currencyInputProperties);
2976
+ props = currencyInputProperties;
2977
+ }
2978
+ else if (this.controlType === FieldControlType.CPF_CNPJ_INPUT) {
2979
+ this.registry.register(this.controlType, cpfCnpjProperties);
2980
+ props = cpfCnpjProperties;
2981
+ }
2982
+ else if (this.controlType === FieldControlType.FILE_UPLOAD) {
2983
+ this.registry.register(this.controlType, fileUploadProperties);
2984
+ props = fileUploadProperties;
2985
+ }
2986
+ else if (this.controlType === FieldControlType.YEAR_INPUT) {
2987
+ this.registry.register(this.controlType, yearInputProperties);
2988
+ props = yearInputProperties;
2989
+ }
2990
+ else if (this.controlType === FieldControlType.TREE_SELECT ||
2991
+ this.controlType === FieldControlType.MULTI_SELECT_TREE) {
2992
+ this.registry.register(this.controlType, treeSelectProperties);
2993
+ props = treeSelectProperties;
2994
+ }
2995
+ }
2996
+ try {
2997
+ (console.log || console.debug)('[FieldMetadataEditor] props after registry resolution', {
2998
+ resolvedProps: props.map((p) => p.name),
2999
+ });
3000
+ }
3001
+ catch { }
3002
+ // Inject controlType selector into 'Geral' group so users can change field type
3003
+ try {
3004
+ const typeProp = {
3005
+ name: 'controlType',
3006
+ label: 'Tipo de campo',
3007
+ editorType: 'select',
3008
+ group: 'Geral',
3009
+ // Destaque visual e aviso claro sobre risco
3010
+ prefixIcon: 'warning',
3011
+ hint: '⚠️ Alterar o tipo de campo NÃO altera o tipo de dado na API. ' +
3012
+ 'Isso pode causar erro ao salvar se o formato esperado for diferente.',
3013
+ options: this.typeOptions.map((o) => ({ value: o.value, text: o.label })),
3014
+ defaultValue: this.controlType,
3015
+ };
3016
+ props = [typeProp, ...props];
3017
+ }
3018
+ catch { }
3019
+ this.normalizedProps = this.normalizer.normalize(props, this.seed);
3020
+ try {
3021
+ console.debug('[FieldMetadataEditor] normalized props', {
3022
+ propNames: this.normalizedProps.map((p) => p.name),
3023
+ defaults: this.normalizedProps
3024
+ .filter((p) => ['resourcePath', 'filterCriteria', 'optionLabelKey', 'optionValueKey'].includes(p.name))
3025
+ .map((p) => ({ name: p.name, defaultValue: p.defaultValue })),
3026
+ optionsDefault: (() => {
3027
+ const opt = this.normalizedProps.find((p) => p.name === 'options');
3028
+ if (!opt)
3029
+ return undefined;
3030
+ const v = opt.defaultValue;
3031
+ return {
3032
+ type: v == null ? 'null' : typeof v,
3033
+ preview: typeof v === 'string' ? String(v).slice(0, 80) : undefined,
3034
+ lengthOrCount: Array.isArray(v)
3035
+ ? v.length
3036
+ : (typeof v === 'string' ? v.length : undefined),
3037
+ };
3038
+ })(),
3039
+ });
3040
+ }
3041
+ catch { }
3042
+ // Inject default for providerOptions from seed.options when present (radio/editor-friendly name)
3043
+ try {
3044
+ const hasProviderOptions = this.normalizedProps.some((p) => p.name === 'providerOptions');
3045
+ const seedOptions = this.seed?.options;
3046
+ if (hasProviderOptions && seedOptions !== undefined) {
3047
+ const v = typeof seedOptions === 'string' ? seedOptions : (() => { try {
3048
+ return JSON.stringify(seedOptions, null, 2);
3049
+ }
3050
+ catch {
3051
+ return String(seedOptions);
3052
+ } })();
3053
+ this.normalizedProps = this.normalizedProps.map((p) => p.name === 'providerOptions' && p.defaultValue === undefined ? { ...p, defaultValue: v } : p);
3054
+ (console.log || console.debug)('[FieldMetadataEditor] providerOptions default injected from seed.options', {
3055
+ type: typeof seedOptions,
3056
+ lengthOrCount: Array.isArray(seedOptions) ? seedOptions.length : (typeof seedOptions === 'string' ? seedOptions.length : undefined),
3057
+ });
3058
+ }
3059
+ }
3060
+ catch { }
3061
+ // Register contextual validators for INPUT/TEXTAREA (minLength <= maxLength)
3062
+ if (this.controlType === FieldControlType.INPUT ||
3063
+ this.controlType === FieldControlType.TEXTAREA) {
3064
+ const existing = this.ctxValidators.get(this.controlType);
3065
+ if (!existing.includes(minLenLeMaxLen)) {
3066
+ this.ctxValidators.register(this.controlType, minLenLeMaxLen);
3067
+ }
3068
+ }
3069
+ this.form = this.factory.createEditorForm(this.controlType, this.normalizedProps, this.seed);
3070
+ // Apply seed values into the form controls (only non-null/undefined)
3071
+ try {
3072
+ const getByPath = (obj, path) => {
3073
+ if (!obj || typeof obj !== 'object')
3074
+ return undefined;
3075
+ if (!path || typeof path !== 'string')
3076
+ return undefined;
3077
+ const parts = path.split('.');
3078
+ let curr = obj;
3079
+ for (const k of parts) {
3080
+ if (curr == null)
3081
+ return undefined;
3082
+ curr = curr[k];
3083
+ }
3084
+ return curr;
3085
+ };
3086
+ const subset = {};
3087
+ for (const p of this.normalizedProps || []) {
3088
+ const name = p?.name;
3089
+ if (!name)
3090
+ continue;
3091
+ const val = getByPath(this.seed, name);
3092
+ // Preserve false/0; skip only null/undefined
3093
+ if (val !== undefined && val !== null) {
3094
+ this.factory.setValueByPath(this.form, name, val);
3095
+ subset[name] = val;
3096
+ }
3097
+ }
3098
+ // Also patch the form in a single call to ensure UI reflects values immediately
3099
+ try {
3100
+ this.form.patchValue(subset, { emitEvent: false });
3101
+ }
3102
+ catch { }
3103
+ // Diagnostics for Rating seeding after applying seed → form
3104
+ try {
3105
+ const ctU = String(this.controlType || '').toUpperCase();
3106
+ if (ctU === String(FieldControlType.RATING)) {
3107
+ const pick = (k) => this.form.get(k)?.value;
3108
+ console.debug('[FieldMetadataEditor][RATING] seeded form values', {
3109
+ max: pick('max'),
3110
+ allowHalf: pick('allowHalf'),
3111
+ selection: pick('selection'),
3112
+ size: pick('size'),
3113
+ icon: pick('icon'),
3114
+ emptyIcon: pick('emptyIcon'),
3115
+ svgIcon: pick('svgIcon'),
3116
+ svgIconOutline: pick('svgIconOutline'),
3117
+ ratingColor: pick('ratingColor'),
3118
+ outlineColor: pick('outlineColor'),
3119
+ readonly: pick('readonly'),
3120
+ tabIndex: pick('tabIndex'),
3121
+ ariaLabel: pick('ariaLabel'),
3122
+ });
3123
+ }
3124
+ }
3125
+ catch { }
3126
+ }
3127
+ catch { }
3128
+ // Diagnostics: dump initial values for Rating editor
3129
+ try {
3130
+ const ctU = String(this.controlType || '').toUpperCase();
3131
+ if (ctU === String(FieldControlType.RATING)) {
3132
+ const pick = (k) => this.form.get(k)?.value;
3133
+ console.debug('[FieldMetadataEditor][RATING] initial form values', {
3134
+ max: pick('max'),
3135
+ allowHalf: pick('allowHalf'),
3136
+ selection: pick('selection'),
3137
+ size: pick('size'),
3138
+ icon: pick('icon'),
3139
+ emptyIcon: pick('emptyIcon'),
3140
+ svgIcon: pick('svgIcon'),
3141
+ svgIconOutline: pick('svgIconOutline'),
3142
+ ratingColor: pick('ratingColor'),
3143
+ outlineColor: pick('outlineColor'),
3144
+ readonly: pick('readonly'),
3145
+ tabIndex: pick('tabIndex'),
3146
+ ariaLabel: pick('ariaLabel'),
3147
+ });
3148
+ }
3149
+ }
3150
+ catch { }
3151
+ // track validity/dirty with snapshot-based detection (avoid false dirty on init)
3152
+ this.isValid$.next(this.form.valid);
3153
+ this.form.statusChanges.subscribe(() => this.isValid$.next(this.form.valid));
3154
+ this.form.valueChanges.subscribe((val) => {
3155
+ if (!this.baselineReady) {
3156
+ // Ignore initial churn until baseline is captured
3157
+ return;
3158
+ }
3159
+ if (this.initialSnapshot == null) {
3160
+ this.initialSnapshot = val;
3161
+ if (this.isDirty$.value)
3162
+ this.isDirty$.next(false);
3163
+ try {
3164
+ (console.log || console.debug)('[FieldMetadataEditor] baseline set from first stable value');
3165
+ }
3166
+ catch { }
3167
+ return;
3168
+ }
3169
+ let same = false;
3170
+ try {
3171
+ same = JSON.stringify(val) === JSON.stringify(this.initialSnapshot);
3172
+ }
3173
+ catch {
3174
+ same = false;
3175
+ }
3176
+ const dirty = !same;
3177
+ if (dirty !== this.isDirty$.value) {
3178
+ try {
3179
+ (console.log || console.debug)('[FieldMetadataEditor] dirty ->', dirty);
3180
+ }
3181
+ catch { }
3182
+ this.isDirty$.next(dirty);
3183
+ }
3184
+ });
3185
+ // Defer baseline capture until next tick to absorb any programmatic value patches
3186
+ setTimeout(() => {
3187
+ try {
3188
+ this.initialSnapshot = this.form.getRawValue();
3189
+ this.baselineReady = true;
3190
+ if (this.isDirty$.value)
3191
+ this.isDirty$.next(false);
3192
+ (console.log || console.debug)('[FieldMetadataEditor] baseline captured (deferred)');
3193
+ }
3194
+ catch {
3195
+ this.baselineReady = true;
3196
+ }
3197
+ }, 0);
3198
+ }
3199
+ apply() {
3200
+ const raw = this.factory.extractPatch(this.form);
3201
+ const patch = this.buildDeltaPatch(raw, this.initialSnapshot);
3202
+ // Map editor-friendly 'providerOptions' back to canonical 'options'
3203
+ if (patch && patch.providerOptions !== undefined && patch.options === undefined) {
3204
+ patch.options = patch.providerOptions;
3205
+ delete patch.providerOptions;
3206
+ }
3207
+ // Sempre incluir controlType no patch quando presente
3208
+ patch.controlType = this.controlType;
3209
+ try {
3210
+ console.debug('[FieldMetadataEditor] apply() patch', patch);
3211
+ }
3212
+ catch { }
3213
+ try {
3214
+ const fn = this.__hostApplyPatch;
3215
+ if (typeof fn === 'function')
3216
+ fn(patch);
3217
+ }
3218
+ catch { }
3219
+ this.applied.emit(patch);
3220
+ }
3221
+ onSave() {
3222
+ return this.getSettingsValue();
3223
+ }
3224
+ // Settings Panel value provider API
3225
+ getSettingsValue() {
3226
+ const raw = this.factory.extractPatch(this.form);
3227
+ const patch = this.buildDeltaPatch(raw, this.initialSnapshot);
3228
+ if (patch && patch.providerOptions !== undefined && patch.options === undefined) {
3229
+ patch.options = patch.providerOptions;
3230
+ delete patch.providerOptions;
3231
+ }
3232
+ patch.controlType = this.controlType;
3233
+ try {
3234
+ console.debug('[FieldMetadataEditor] getSettingsValue() patch', patch);
3235
+ }
3236
+ catch { }
3237
+ return patch;
3238
+ }
3239
+ /**
3240
+ * Constrói um patch delta a partir do valor atual do form, removendo
3241
+ * chaves não alteradas e valores nulos/strings vazias (exceto boolean/zero).
3242
+ */
3243
+ buildDeltaPatch(current, baseline) {
3244
+ try {
3245
+ const isObject = (v) => v != null && typeof v === 'object' && !Array.isArray(v);
3246
+ const changed = {};
3247
+ const keys = Object.keys(current || {});
3248
+ for (const k of keys) {
3249
+ const cv = current[k];
3250
+ const bv = baseline ? baseline[k] : undefined;
3251
+ const isNullish = cv === null || cv === undefined;
3252
+ const isEmptyString = typeof cv === 'string' && cv.trim() === '';
3253
+ // Preservar false/0 explicitamente; descartar null/undefined e strings vazias
3254
+ if (isNullish || isEmptyString)
3255
+ continue;
3256
+ if (isObject(cv)) {
3257
+ const nested = this.buildDeltaPatch(cv, isObject(bv) ? bv : undefined);
3258
+ if (nested && Object.keys(nested).length)
3259
+ changed[k] = nested;
3260
+ }
3261
+ else {
3262
+ // Incluir apenas se mudou em relação ao baseline ou se baseline não existe
3263
+ if (baseline === null || baseline === undefined || cv !== bv) {
3264
+ changed[k] = cv;
3265
+ }
3266
+ }
3267
+ }
3268
+ return changed;
3269
+ }
3270
+ catch {
3271
+ // Fallback: retornar current quando algo falhar (melhor do que perder patch)
3272
+ return current;
3273
+ }
3274
+ }
3275
+ reset() {
3276
+ this.form.reset(this.factory.extractPatch(this.form));
3277
+ this.isDirty$.next(false);
3278
+ }
3279
+ onControlTypeChange(next) {
3280
+ this.rebuildEditorForType(next);
3281
+ this.isDirty$.next(true);
3282
+ }
3283
+ // Template-safe change handler (avoid TS casts in template)
3284
+ onControlTypeChangeEvent(event) {
3285
+ const value = event.target?.value;
3286
+ if (value != null)
3287
+ this.onControlTypeChange(value);
3288
+ }
3289
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: FieldMetadataEditorComponent, deps: [{ token: ConfigRegistryService }, { token: SchemaNormalizerService }, { token: DynamicFormFactoryService }, { token: ContextValidatorRegistryService }], target: i0.ɵɵFactoryTarget.Component });
3290
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: FieldMetadataEditorComponent, isStandalone: true, selector: "praxis-field-metadata-editor", inputs: { controlType: "controlType", seed: "seed" }, outputs: { applied: "applied", cancel: "cancel" }, ngImport: i0, template: `
3291
+ <div class="p-3" *ngIf="form">
3292
+ <praxis-dynamic-editor-renderer
3293
+ [properties]="normalizedProps"
3294
+ [form]="form"
3295
+ ></praxis-dynamic-editor-renderer>
3296
+ </div>
3297
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: DynamicEditorRendererComponent, selector: "praxis-dynamic-editor-renderer", inputs: ["properties", "form"] }] });
3298
+ }
3299
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: FieldMetadataEditorComponent, decorators: [{
3300
+ type: Component,
3301
+ args: [{
3302
+ selector: 'praxis-field-metadata-editor',
3303
+ standalone: true,
3304
+ imports: [CommonModule, ReactiveFormsModule, DynamicEditorRendererComponent],
3305
+ template: `
3306
+ <div class="p-3" *ngIf="form">
3307
+ <praxis-dynamic-editor-renderer
3308
+ [properties]="normalizedProps"
3309
+ [form]="form"
3310
+ ></praxis-dynamic-editor-renderer>
3311
+ </div>
3312
+ `,
3313
+ }]
3314
+ }], ctorParameters: () => [{ type: ConfigRegistryService }, { type: SchemaNormalizerService }, { type: DynamicFormFactoryService }, { type: ContextValidatorRegistryService }], propDecorators: { controlType: [{
3315
+ type: Input
3316
+ }], seed: [{
3317
+ type: Input
3318
+ }], applied: [{
3319
+ type: Output
3320
+ }], cancel: [{
3321
+ type: Output
3322
+ }] } });
3323
+
3324
+ class CascadeRulesService {
3325
+ getFieldLabelMap(fields) {
3326
+ const map = new Map();
3327
+ fields.forEach((f) => map.set(f.name, f.label || f.name));
3328
+ return map;
3329
+ }
3330
+ hydrateRule(field) {
3331
+ const deps = field.dependencyFields;
3332
+ if (!deps || deps.length === 0)
3333
+ return null;
3334
+ const vm = {
3335
+ targetField: field.name,
3336
+ dependencyFields: [...deps],
3337
+ enableDependencyCascade: field.enableDependencyCascade === false ? false : true,
3338
+ resetOnDependentChange: !!field.resetOnDependentChange,
3339
+ dependencyFilterMap: field.dependencyFilterMap || null,
3340
+ dependencyValuePath: field.dependencyValuePath || null,
3341
+ dependencyMergeStrategy: field.dependencyMergeStrategy || 'merge',
3342
+ dependencyDebounceMs: field.dependencyDebounceMs != null
3343
+ ? Number(field.dependencyDebounceMs)
3344
+ : 150,
3345
+ dependencyLoadOnChange: field.dependencyLoadOnChange || 'respectLoadOn',
3346
+ };
3347
+ return vm;
3348
+ }
3349
+ dehydratePatch(rule) {
3350
+ const patch = {
3351
+ dependencyFields: rule.dependencyFields,
3352
+ enableDependencyCascade: rule.enableDependencyCascade,
3353
+ resetOnDependentChange: rule.resetOnDependentChange,
3354
+ dependencyMergeStrategy: rule.dependencyMergeStrategy || 'merge',
3355
+ dependencyLoadOnChange: rule.dependencyLoadOnChange || 'respectLoadOn',
3356
+ };
3357
+ if (rule.dependencyFilterMap != null)
3358
+ patch.dependencyFilterMap = rule.dependencyFilterMap;
3359
+ if (rule.dependencyValuePath != null)
3360
+ patch.dependencyValuePath = rule.dependencyValuePath;
3361
+ if (rule.dependencyDebounceMs != null)
3362
+ patch.dependencyDebounceMs = Number(rule.dependencyDebounceMs);
3363
+ return patch;
3364
+ }
3365
+ /** Suggest a basic mapping dep->dep and a default valuePath */
3366
+ suggest(rule, inferredValueKey) {
3367
+ const map = {};
3368
+ (rule.dependencyFields || []).forEach((dep) => (map[dep] = dep));
3369
+ return {
3370
+ ...rule,
3371
+ dependencyFilterMap: Object.keys(map).length ? map : rule.dependencyFilterMap,
3372
+ dependencyValuePath: rule.dependencyValuePath || inferredValueKey || 'id',
3373
+ };
3374
+ }
3375
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CascadeRulesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3376
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CascadeRulesService, providedIn: 'root' });
3377
+ }
3378
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CascadeRulesService, decorators: [{
3379
+ type: Injectable,
3380
+ args: [{ providedIn: 'root' }]
3381
+ }] });
3382
+
3383
+ class CascadeManagerTabComponent {
3384
+ svc;
3385
+ fields = [];
3386
+ connections; // optional signal for conflicts
3387
+ apply = new EventEmitter();
3388
+ cancel = new EventEmitter();
3389
+ searchTerm = '';
3390
+ searchTimer = null;
3391
+ searchValue = '';
3392
+ rules = signal(new Map(), ...(ngDevMode ? [{ debugName: "rules" }] : []));
3393
+ selectedFieldSig = signal(null, ...(ngDevMode ? [{ debugName: "selectedFieldSig" }] : []));
3394
+ // Inline editor state (MVP)
3395
+ editing = signal(false, ...(ngDevMode ? [{ debugName: "editing" }] : []));
3396
+ editDepCsv = '';
3397
+ editEnable = true;
3398
+ editReset = false;
3399
+ editMerge = 'merge';
3400
+ editLoadOn = 'respectLoadOn';
3401
+ editDebounce = 150;
3402
+ editValuePath = '';
3403
+ editFilterMapJson = '';
3404
+ importMode = false;
3405
+ importJson = '';
3406
+ editMapRows = [];
3407
+ constructor(svc) {
3408
+ this.svc = svc;
3409
+ }
3410
+ ngOnInit() {
3411
+ // hydrate from input fields
3412
+ const map = new Map();
3413
+ for (const f of this.fields) {
3414
+ const vm = this.svc.hydrateRule(f);
3415
+ if (vm)
3416
+ map.set(f.name, vm);
3417
+ }
3418
+ this.rules.set(map);
3419
+ if (!this.selectedFieldSig() && this.fields.length) {
3420
+ this.selectedFieldSig.set(this.fields[0].name);
3421
+ }
3422
+ }
3423
+ ngOnChanges(changes) {
3424
+ if (changes['fields'] && !changes['fields'].firstChange) {
3425
+ // Re-hydrate rules from incoming fields while preserving existing ones when possible
3426
+ const incoming = this.fields || [];
3427
+ const next = new Map();
3428
+ // keep existing for fields still present
3429
+ for (const f of incoming) {
3430
+ const existing = this.rules().get(f.name);
3431
+ const hydrated = this.svc.hydrateRule(f);
3432
+ if (hydrated)
3433
+ next.set(f.name, hydrated);
3434
+ else if (existing)
3435
+ next.set(f.name, existing);
3436
+ }
3437
+ this.rules.set(next);
3438
+ // preserve selection if still valid
3439
+ const sel = this.selectedField();
3440
+ if (sel && !incoming.some((f) => f.name === sel)) {
3441
+ this.selectedFieldSig.set(incoming[0]?.name ?? null);
3442
+ }
3443
+ }
3444
+ }
3445
+ filteredFields() {
3446
+ const q = (this.searchValue || '').toLowerCase();
3447
+ const labelMap = this.svc.getFieldLabelMap(this.fields);
3448
+ return this.fields
3449
+ .filter((f) => !q || f.name.toLowerCase().includes(q) || (labelMap.get(f.name) || '').toLowerCase().includes(q))
3450
+ .sort((a, b) => (labelMap.get(a.name) || a.name).localeCompare(labelMap.get(b.name) || b.name));
3451
+ }
3452
+ labelFor(name) {
3453
+ return this.svc.getFieldLabelMap(this.fields).get(name) || name;
3454
+ }
3455
+ selectedField() {
3456
+ return this.selectedFieldSig();
3457
+ }
3458
+ selectField(name) {
3459
+ this.selectedFieldSig.set(name);
3460
+ this.editing.set(false);
3461
+ }
3462
+ hasRule(name) {
3463
+ return this.rules().has(name);
3464
+ }
3465
+ ruleFor(name) {
3466
+ return this.rules().get(name) || null;
3467
+ }
3468
+ isCascadeEnabled(name) {
3469
+ return (this.ruleFor(name)?.enableDependencyCascade ?? true) === true;
3470
+ }
3471
+ loadOnChange(name) {
3472
+ return this.ruleFor(name)?.dependencyLoadOnChange || 'respectLoadOn';
3473
+ }
3474
+ hasConflict(name) {
3475
+ if (!this.connections)
3476
+ return false;
3477
+ return this.connections.some((c) => c.toFieldName === name && (c.toInputPath || '').includes('filterCriteria'));
3478
+ }
3479
+ startEdit(name) {
3480
+ const existing = this.ruleFor(name);
3481
+ const base = existing || {
3482
+ targetField: name,
3483
+ dependencyFields: [],
3484
+ enableDependencyCascade: true,
3485
+ resetOnDependentChange: false,
3486
+ dependencyFilterMap: {},
3487
+ dependencyValuePath: 'id',
3488
+ dependencyMergeStrategy: 'merge',
3489
+ dependencyDebounceMs: 150,
3490
+ dependencyLoadOnChange: 'respectLoadOn',
3491
+ };
3492
+ const suggested = this.svc.suggest(base, 'id');
3493
+ // Bind to inline editor
3494
+ this.editDepCsv = suggested.dependencyFields.join(',');
3495
+ this.editEnable = suggested.enableDependencyCascade;
3496
+ this.editReset = suggested.resetOnDependentChange;
3497
+ this.editMerge = suggested.dependencyMergeStrategy || 'merge';
3498
+ this.editLoadOn = suggested.dependencyLoadOnChange || 'respectLoadOn';
3499
+ this.editDebounce = suggested.dependencyDebounceMs ?? 150;
3500
+ this.editValuePath = (typeof suggested.dependencyValuePath === 'string' ? suggested.dependencyValuePath : 'id') || '';
3501
+ try {
3502
+ this.editFilterMapJson = suggested.dependencyFilterMap ? JSON.stringify(suggested.dependencyFilterMap, null, 2) : '';
3503
+ }
3504
+ catch {
3505
+ this.editFilterMapJson = '';
3506
+ }
3507
+ // Build mapping rows from dependencies
3508
+ this.buildMapRowsFromDeps();
3509
+ this.editing.set(true);
3510
+ }
3511
+ edit(name) { this.startEdit(name); }
3512
+ cancelEdit() { this.editing.set(false); }
3513
+ saveEdit(name) {
3514
+ // Build rule from editor
3515
+ const deps = (this.editDepCsv || '')
3516
+ .split(',')
3517
+ .map((s) => s.trim())
3518
+ .filter((s) => !!s && s !== name);
3519
+ // Build map from rows; fallback to JSON when provided
3520
+ let map = {};
3521
+ this.editMapRows.forEach((r) => { if (!r.key && r.dep)
3522
+ r.key = r.dep; if (r.dep)
3523
+ map[r.dep] = r.valuePath ? { key: r.key || r.dep, valuePath: r.valuePath } : (r.key || r.dep); });
3524
+ const raw = (this.editFilterMapJson || '').trim();
3525
+ if (raw) {
3526
+ try {
3527
+ const fromJson = JSON.parse(raw);
3528
+ map = { ...map, ...fromJson };
3529
+ }
3530
+ catch {
3531
+ alert('Mapeamento de filtros inválido (JSON)');
3532
+ return;
3533
+ }
3534
+ }
3535
+ const rule = {
3536
+ targetField: name,
3537
+ dependencyFields: deps,
3538
+ enableDependencyCascade: this.editEnable,
3539
+ resetOnDependentChange: this.editReset,
3540
+ dependencyFilterMap: map,
3541
+ dependencyValuePath: this.buildValuePathOverrides(deps),
3542
+ dependencyMergeStrategy: this.editMerge,
3543
+ dependencyDebounceMs: this.editDebounce ?? 150,
3544
+ dependencyLoadOnChange: this.editLoadOn,
3545
+ };
3546
+ const next = new Map(this.rules());
3547
+ next.set(name, rule);
3548
+ this.rules.set(next);
3549
+ this.emitPatch();
3550
+ this.editing.set(false);
3551
+ }
3552
+ remove(name) {
3553
+ const next = new Map(this.rules());
3554
+ next.delete(name);
3555
+ this.rules.set(next);
3556
+ // Removing means clearing the cascade-related keys; send empty patch
3557
+ const patch = {
3558
+ [name]: {
3559
+ dependencyFields: [],
3560
+ enableDependencyCascade: false,
3561
+ resetOnDependentChange: false,
3562
+ dependencyFilterMap: undefined,
3563
+ dependencyValuePath: undefined,
3564
+ dependencyMergeStrategy: undefined,
3565
+ dependencyDebounceMs: undefined,
3566
+ dependencyLoadOnChange: undefined,
3567
+ },
3568
+ };
3569
+ this.apply.emit(patch);
3570
+ }
3571
+ quickSet(name, action) {
3572
+ const current = this.ruleFor(name);
3573
+ if (!current)
3574
+ return;
3575
+ const next = { ...current };
3576
+ if (action === 'disable')
3577
+ next.enableDependencyCascade = false;
3578
+ if (action === 'manual')
3579
+ next.dependencyLoadOnChange = 'manual';
3580
+ const map = new Map(this.rules());
3581
+ map.set(name, next);
3582
+ this.rules.set(map);
3583
+ this.emitPatch();
3584
+ }
3585
+ emitPatch() {
3586
+ const out = {};
3587
+ for (const [name, rule] of this.rules()) {
3588
+ out[name] = this.svc.dehydratePatch(rule);
3589
+ }
3590
+ this.apply.emit(out);
3591
+ }
3592
+ // Phase 2 helpers
3593
+ onSearchChange(val) {
3594
+ this.searchTerm = val;
3595
+ if (this.searchTimer)
3596
+ clearTimeout(this.searchTimer);
3597
+ this.searchTimer = setTimeout(() => (this.searchValue = this.searchTerm), 150);
3598
+ }
3599
+ onFieldKeydown(event, name) {
3600
+ if (event.key === 'Enter') {
3601
+ this.edit(name);
3602
+ event.preventDefault();
3603
+ }
3604
+ if (event.key.toLowerCase() === 'd' && (event.ctrlKey || event.metaKey)) { /* duplicate placeholder */
3605
+ event.preventDefault();
3606
+ }
3607
+ if (event.key === 'Delete') {
3608
+ this.remove(name);
3609
+ event.preventDefault();
3610
+ }
3611
+ }
3612
+ hasField(name) { return this.fields.some((f) => f.name === name); }
3613
+ toggleImport() { this.importMode = !this.importMode; this.importJson = ''; }
3614
+ exportRules() {
3615
+ const out = {};
3616
+ for (const [name, rule] of this.rules())
3617
+ out[name] = this.svc.dehydratePatch(rule);
3618
+ const json = JSON.stringify(out, null, 2);
3619
+ try {
3620
+ navigator.clipboard?.writeText(json);
3621
+ }
3622
+ catch { }
3623
+ alert('Regras exportadas para a área de transferência.');
3624
+ }
3625
+ applyImport() {
3626
+ let obj;
3627
+ try {
3628
+ obj = JSON.parse(this.importJson || '{}');
3629
+ }
3630
+ catch {
3631
+ alert('JSON inválido');
3632
+ return;
3633
+ }
3634
+ const next = new Map(this.rules());
3635
+ Object.keys(obj || {}).forEach((field) => {
3636
+ const patch = obj[field] || {};
3637
+ const vm = this.svc.hydrateRule({ ...patch, name: field }) || {
3638
+ targetField: field,
3639
+ dependencyFields: patch.dependencyFields || [],
3640
+ enableDependencyCascade: patch.enableDependencyCascade !== false,
3641
+ resetOnDependentChange: !!patch.resetOnDependentChange,
3642
+ dependencyFilterMap: patch.dependencyFilterMap || {},
3643
+ dependencyValuePath: patch.dependencyValuePath || 'id',
3644
+ dependencyMergeStrategy: patch.dependencyMergeStrategy || 'merge',
3645
+ dependencyDebounceMs: patch.dependencyDebounceMs ?? 150,
3646
+ dependencyLoadOnChange: patch.dependencyLoadOnChange || 'respectLoadOn',
3647
+ };
3648
+ next.set(field, vm);
3649
+ });
3650
+ this.rules.set(next);
3651
+ this.emitPatch();
3652
+ this.importMode = false;
3653
+ }
3654
+ startTemplate(name, tpl) {
3655
+ const hasR = this.hasField('rotaId');
3656
+ const hasD = this.hasField('destinoReqId');
3657
+ const deps = tpl === 'rota-destino' ? (hasR ? ['rotaId'] : []) : (hasR && hasD ? ['rotaId', 'destinoReqId'] : []);
3658
+ this.editDepCsv = deps.join(',');
3659
+ this.editEnable = true;
3660
+ this.editReset = true;
3661
+ this.editMerge = 'merge';
3662
+ this.editLoadOn = 'respectLoadOn';
3663
+ this.editDebounce = 150;
3664
+ this.editValuePath = 'id';
3665
+ this.buildMapRowsFromDeps();
3666
+ this.editing.set(true);
3667
+ }
3668
+ buildMapRowsFromDeps() {
3669
+ const deps = (this.editDepCsv || '').split(',').map((s) => s.trim()).filter(Boolean);
3670
+ const existing = (() => { try {
3671
+ return JSON.parse(this.editFilterMapJson || '{}');
3672
+ }
3673
+ catch {
3674
+ return {};
3675
+ } })();
3676
+ this.editMapRows = deps.map((dep) => {
3677
+ const entry = existing[dep];
3678
+ if (!entry)
3679
+ return { dep, key: dep };
3680
+ if (typeof entry === 'string')
3681
+ return { dep, key: entry };
3682
+ return { dep, key: entry.key || dep, valuePath: entry.valuePath };
3683
+ });
3684
+ }
3685
+ buildValuePathOverrides(deps) {
3686
+ // If any row defines a valuePath, return a record; else return default string
3687
+ const record = {};
3688
+ this.editMapRows.forEach((r) => { if (r.valuePath)
3689
+ record[r.dep] = r.valuePath; });
3690
+ const hasAny = Object.keys(record).length > 0;
3691
+ return hasAny ? record : (this.editValuePath || 'id');
3692
+ }
3693
+ buildPreview() {
3694
+ try {
3695
+ const deps = (this.editDepCsv || '').split(',').map((s) => s.trim()).filter(Boolean);
3696
+ const frag = {};
3697
+ this.editMapRows.forEach((r) => {
3698
+ const example = r.valuePath ? 123 : 123; // simple placeholder id
3699
+ const parts = (r.key || r.dep).split('.');
3700
+ let ref = frag;
3701
+ for (let i = 0; i < parts.length - 1; i++) {
3702
+ const p = parts[i];
3703
+ ref[p] = ref[p] || {};
3704
+ ref = ref[p];
3705
+ }
3706
+ ref[parts[parts.length - 1]] = example;
3707
+ });
3708
+ return JSON.stringify(frag, null, 2);
3709
+ }
3710
+ catch {
3711
+ return '{}';
3712
+ }
3713
+ }
3714
+ copyPreview() { try {
3715
+ navigator.clipboard?.writeText(this.buildPreview());
3716
+ }
3717
+ catch { } }
3718
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CascadeManagerTabComponent, deps: [{ token: CascadeRulesService }], target: i0.ɵɵFactoryTarget.Component });
3719
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: CascadeManagerTabComponent, isStandalone: true, selector: "praxis-cascade-manager-tab", inputs: { fields: "fields", connections: "connections" }, outputs: { apply: "apply", cancel: "cancel" }, usesOnChanges: true, ngImport: i0, template: `
3720
+ <div class="cm-root">
3721
+ <!-- Left: Fields list -->
3722
+ <div class="cm-left">
3723
+ <mat-form-field appearance="outline" class="cm-search" subscriptSizing="dynamic">
3724
+ <mat-label>Buscar campos</mat-label>
3725
+ <input matInput [ngModel]="searchTerm" (ngModelChange)="onSearchChange($event)">
3726
+ </mat-form-field>
3727
+ <ng-container *ngIf="fields.length <= 50; else vscroll">
3728
+ <div>
3729
+ <div *ngFor="let f of filteredFields()" (click)="selectField(f.name)" class="field-item" [class.selected]="f.name===selectedField()" tabindex="0" (keydown)="onFieldKeydown($event, f.name)">
3730
+ <div>
3731
+ <div>{{ labelFor(f.name) }}</div>
3732
+ <small style="opacity:.7">{{ f.name }}</small>
3733
+ </div>
3734
+ <div class="rule-badges" *ngIf="hasRule(f.name)">
3735
+ <span class="rule-badge b-active" *ngIf="isCascadeEnabled(f.name)">Ativo</span>
3736
+ <span class="rule-badge b-manual" *ngIf="loadOnChange(f.name)==='manual'">Manual</span>
3737
+ <span class="rule-badge b-immediate" *ngIf="loadOnChange(f.name)==='immediate'">Immediate</span>
3738
+ </div>
3739
+ </div>
3740
+ </div>
3741
+ </ng-container>
3742
+ <ng-template #vscroll>
3743
+ <cdk-virtual-scroll-viewport itemSize="44" style="height: calc(100vh - 240px)">
3744
+ <div *cdkVirtualFor="let f of filteredFields()" (click)="selectField(f.name)" class="field-item" [class.selected]="f.name===selectedField()" tabindex="0" (keydown)="onFieldKeydown($event, f.name)">
3745
+ <div>
3746
+ <div>{{ labelFor(f.name) }}</div>
3747
+ <small style="opacity:.7">{{ f.name }}</small>
3748
+ </div>
3749
+ <div class="rule-badges" *ngIf="hasRule(f.name)">
3750
+ <span class="rule-badge b-active" *ngIf="isCascadeEnabled(f.name)">Ativo</span>
3751
+ <span class="rule-badge b-manual" *ngIf="loadOnChange(f.name)==='manual'">Manual</span>
3752
+ <span class="rule-badge b-immediate" *ngIf="loadOnChange(f.name)==='immediate'">Immediate</span>
3753
+ </div>
3754
+ </div>
3755
+ </cdk-virtual-scroll-viewport>
3756
+ </ng-template>
3757
+ </div>
3758
+
3759
+ <!-- Right: Rules for selected field -->
3760
+ <div class="cm-right" *ngIf="selectedField() as sel">
3761
+ <div class="cm-header">
3762
+ <div>
3763
+ <h3 style="margin:0">{{ labelFor(sel) }}</h3>
3764
+ <div style="opacity:.7">{{ sel }}</div>
3765
+ </div>
3766
+ <div>
3767
+ <button mat-stroked-button color="primary" (click)="exportRules()" style="margin-right:8px">Exportar</button>
3768
+ <button mat-stroked-button (click)="toggleImport()" style="margin-right:8px">Importar</button>
3769
+ <button mat-flat-button color="primary" [matMenuTriggerFor]="addMenu" [disabled]="editing()">Adicionar</button>
3770
+ <mat-menu #addMenu="matMenu">
3771
+ <button mat-menu-item (click)="startEdit(sel)"><mat-icon [praxisIcon]="'add'"></mat-icon><span>Em branco</span></button>
3772
+ <button mat-menu-item (click)="startTemplate(sel, 'rota-destino')" [disabled]="!hasField('rotaId') || !hasField('destinoReqId')"><mat-icon [praxisIcon]="'bolt'"></mat-icon><span>Rota → Destino (id)</span></button>
3773
+ <button mat-menu-item (click)="startTemplate(sel, 'rota-destino-propriedade')" [disabled]="!hasField('rotaId') || !hasField('destinoReqId')"><mat-icon [praxisIcon]="'bolt'"></mat-icon><span>Rota + Destino → Propriedade (ids)</span></button>
3774
+ </mat-menu>
3775
+ </div>
3776
+ </div>
3777
+
3778
+ <!-- Conflict banner (connections) -->
3779
+ <div class="banner" *ngIf="hasConflict(sel)">
3780
+ <mat-icon [praxisIcon]="'link_off'"></mat-icon>
3781
+ <div>
3782
+ Connections atualizam este campo (filterCriteria). Considere desativar a cascata nativa ou usar ‘manual’.
3783
+ </div>
3784
+ <span class="spacer"></span>
3785
+ <button mat-stroked-button color="primary" (click)="quickSet(sel,'disable')">Desativar</button>
3786
+ <button mat-stroked-button color="accent" (click)="quickSet(sel,'manual')">Definir manual</button>
3787
+ </div>
3788
+
3789
+ <div *ngIf="importMode" class="import-panel">
3790
+ <div style="display:flex; gap:8px; align-items:center; margin-bottom:8px"><mat-icon [praxisIcon]="'upload'"></mat-icon><strong>Importar regras (JSON)</strong></div>
3791
+ <textarea matInput rows="6" class="full" [(ngModel)]="importJson" placeholder='{ "campo": { "dependencyFields": ["dep"], ... } }'></textarea>
3792
+ <div style="display:flex; gap:8px; justify-content:flex-end; padding-top:8px">
3793
+ <button mat-stroked-button (click)="toggleImport()">Cancelar</button>
3794
+ <button mat-flat-button color="primary" (click)="applyImport()">Aplicar</button>
3795
+ </div>
3796
+ </div>
3797
+
3798
+ <div *ngIf="!ruleFor(sel)">
3799
+ <div class="rules-empty">Nenhuma regra de cascata. Clique em “Adicionar”.</div>
3800
+ </div>
3801
+ <div *ngIf="ruleFor(sel) as rule">
3802
+ <div class="rule-row">
3803
+ <div>
3804
+ <div><strong>Dependentes:</strong> {{ rule.dependencyFields.join(', ') || '—' }}</div>
3805
+ <div style="opacity:.7">Load on change: {{ rule.dependencyLoadOnChange || 'respectLoadOn' }} | Merge: {{ rule.dependencyMergeStrategy || 'merge' }}</div>
3806
+ </div>
3807
+ <div class="rule-actions">
3808
+ <button mat-icon-button matTooltip="Editar" (click)="edit(sel)"><mat-icon [praxisIcon]="'edit'"></mat-icon></button>
3809
+ <button mat-icon-button matTooltip="Duplicar" disabled><mat-icon [praxisIcon]="'content_copy'"></mat-icon></button>
3810
+ <button mat-icon-button matTooltip="Remover" (click)="remove(sel)"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
3811
+ </div>
3812
+ </div>
3813
+ </div>
3814
+
3815
+ <!-- Editor com seções -->
3816
+ <mat-expansion-panel [expanded]="editing()">
3817
+ <mat-expansion-panel-header>
3818
+ <mat-panel-title>Edição de Regra</mat-panel-title>
3819
+ <mat-panel-description>Configurar dependentes, execução e mapeamento</mat-panel-description>
3820
+ </mat-expansion-panel-header>
3821
+
3822
+ <!-- Básico -->
3823
+ <mat-expansion-panel [expanded]="true">
3824
+ <mat-expansion-panel-header>
3825
+ <mat-panel-title>Básico</mat-panel-title>
3826
+ </mat-expansion-panel-header>
3827
+ <div class="form-grid">
3828
+ <div class="form-row">
3829
+ <mat-form-field appearance="outline" class="full">
3830
+ <mat-label>Dependentes (CSV)</mat-label>
3831
+ <input matInput [(ngModel)]="editDepCsv">
3832
+ <mat-hint>Ex.: rotaId,destinoReqId</mat-hint>
3833
+ </mat-form-field>
3834
+ </div>
3835
+ <div class="form-row">
3836
+ <mat-checkbox [(ngModel)]="editEnable">Ativar cascata nativa</mat-checkbox>
3837
+ <mat-checkbox [(ngModel)]="editReset">Resetar ao mudar</mat-checkbox>
3838
+ </div>
3839
+ </div>
3840
+ </mat-expansion-panel>
3841
+
3842
+ <!-- Execução -->
3843
+ <mat-expansion-panel>
3844
+ <mat-expansion-panel-header>
3845
+ <mat-panel-title>Execução</mat-panel-title>
3846
+ </mat-expansion-panel-header>
3847
+ <div class="form-row">
3848
+ <mat-form-field appearance="outline">
3849
+ <mat-label>Carregar ao mudar</mat-label>
3850
+ <mat-select [(ngModel)]="editLoadOn">
3851
+ <mat-option value="respectLoadOn">Respeitar loadOn</mat-option>
3852
+ <mat-option value="immediate">Imediato</mat-option>
3853
+ <mat-option value="manual">Manual</mat-option>
3854
+ </mat-select>
3855
+ </mat-form-field>
3856
+ <mat-form-field appearance="outline">
3857
+ <mat-label>Merge de filtros</mat-label>
3858
+ <mat-select [(ngModel)]="editMerge">
3859
+ <mat-option value="merge">merge (padrão)</mat-option>
3860
+ <mat-option value="replace">replace</mat-option>
3861
+ </mat-select>
3862
+ </mat-form-field>
3863
+ <mat-form-field appearance="outline">
3864
+ <mat-label>Atraso (ms)</mat-label>
3865
+ <input matInput type="number" [(ngModel)]="editDebounce">
3866
+ </mat-form-field>
3867
+ </div>
3868
+ </mat-expansion-panel>
3869
+
3870
+ <!-- Extração de valor -->
3871
+ <mat-expansion-panel>
3872
+ <mat-expansion-panel-header>
3873
+ <mat-panel-title>Extração de valor</mat-panel-title>
3874
+ </mat-expansion-panel-header>
3875
+ <div class="form-row">
3876
+ <mat-form-field appearance="outline" class="full">
3877
+ <mat-label>ValuePath padrão</mat-label>
3878
+ <input matInput [(ngModel)]="editValuePath" placeholder="id">
3879
+ <mat-hint>Preenchido automaticamente a partir de optionValueKey() ou 'id'</mat-hint>
3880
+ </mat-form-field>
3881
+ </div>
3882
+ </mat-expansion-panel>
3883
+
3884
+ <!-- Mapeamento de filtros (tabela) -->
3885
+ <mat-expansion-panel>
3886
+ <mat-expansion-panel-header>
3887
+ <mat-panel-title>Mapeamento de filtros</mat-panel-title>
3888
+ </mat-expansion-panel-header>
3889
+ <table class="map-table" *ngIf="editMapRows.length; else noMap">
3890
+ <thead>
3891
+ <tr>
3892
+ <th>Dependente</th>
3893
+ <th>Chave (dot‑path)</th>
3894
+ <th>ValuePath (opcional)</th>
3895
+ </tr>
3896
+ </thead>
3897
+ <tbody>
3898
+ <tr *ngFor="let row of editMapRows; let i = index">
3899
+ <td>{{ row.dep }}</td>
3900
+ <td>
3901
+ <input matInput [(ngModel)]="row.key" placeholder="{{row.dep}}">
3902
+ </td>
3903
+ <td>
3904
+ <input matInput [(ngModel)]="row.valuePath" placeholder="{{editValuePath||'id'}}">
3905
+ </td>
3906
+ </tr>
3907
+ </tbody>
3908
+ </table>
3909
+ <ng-template #noMap>
3910
+ <div class="rules-empty">Defina dependentes para sugerir o mapeamento.</div>
3911
+ </ng-template>
3912
+ </mat-expansion-panel>
3913
+
3914
+ <!-- Preview -->
3915
+ <mat-expansion-panel>
3916
+ <mat-expansion-panel-header>
3917
+ <mat-panel-title>Preview</mat-panel-title>
3918
+ </mat-expansion-panel-header>
3919
+ <pre style="white-space: pre-wrap; background:#fafafa; padding:8px; border:1px solid #eee; border-radius:6px">{{ buildPreview() }}</pre>
3920
+ <div style="display:flex; justify-content:flex-end; padding-top:6px">
3921
+ <button mat-stroked-button (click)="copyPreview()">Copiar</button>
3922
+ </div>
3923
+ </mat-expansion-panel>
3924
+
3925
+ <div style="display:flex; gap:8px; justify-content:flex-end; padding:8px 0;">
3926
+ <button mat-stroked-button (click)="cancelEdit()">Cancelar</button>
3927
+ <button mat-flat-button color="primary" (click)="saveEdit(sel)">Salvar</button>
3928
+ </div>
3929
+ </mat-expansion-panel>
3930
+ </div>
3931
+ </div>
3932
+ `, isInline: true, styles: [":host{display:block}.cm-root{display:grid;grid-template-columns:320px 1fr;gap:16px;min-height:420px}.cm-left{border-right:1px solid rgba(0,0,0,.06);padding-right:8px}.cm-search{width:100%;margin-bottom:8px}.cm-right{padding-left:8px}.field-item{display:flex;align-items:center;justify-content:space-between;padding:6px 4px;cursor:pointer;border-radius:6px}.field-item.selected{background:#0000000a}.rule-badges{display:inline-flex;gap:6px}.rule-badge{font-size:10px;padding:2px 6px;border-radius:10px}.b-active{background:#e8f5e9;color:#2e7d32}.b-manual{background:#eceff1;color:#455a64}.b-immediate{background:#fff3e0;color:#ef6c00}.b-conflict{background:#ffebee;color:#c62828}.cm-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.rules-empty{opacity:.7;font-style:italic;padding:8px 0}.rule-row{display:grid;grid-template-columns:1fr auto;align-items:center;padding:8px 0;border-bottom:1px dashed rgba(0,0,0,.06)}.rule-actions button{margin-left:4px}.banner{background:#fff8e1;color:#6d4c41;padding:8px 12px;border:1px solid #ffe082;border-radius:6px;margin-bottom:8px;display:flex;gap:8px;align-items:center}.import-panel{background:#f5f5f5;border:1px solid #e0e0e0;border-radius:6px;padding:8px;margin-bottom:8px}.full{width:100%}.form-row{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.form-grid{display:grid;gap:12px;padding:8px 0}.map-table{width:100%;border-collapse:collapse}.map-table th,.map-table td{padding:6px 8px;border-bottom:1px dashed rgba(0,0,0,.06)}.kbd{font-family:monospace;background:#eee;padding:0 4px;border-radius:3px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.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: i3.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i5$1.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: i5$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i7.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i7.MatLabel, selector: "mat-label" }, { kind: "directive", type: i7.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "ngmodule", type: MatListModule }, { kind: "ngmodule", type: MatDividerModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8$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: i8$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i9.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i5.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i5.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i5.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i5.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i11.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i11.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i11.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i12.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i12.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i12.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }] });
3933
+ }
3934
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CascadeManagerTabComponent, decorators: [{
3935
+ type: Component,
3936
+ args: [{ selector: 'praxis-cascade-manager-tab', standalone: true, imports: [
3937
+ CommonModule,
3938
+ ReactiveFormsModule,
3939
+ FormsModule,
3940
+ MatIconModule,
3941
+ MatButtonModule,
3942
+ MatTooltipModule,
3943
+ MatInputModule,
3944
+ MatChipsModule,
3945
+ MatListModule,
3946
+ MatDividerModule,
3947
+ MatSelectModule,
3948
+ MatCheckboxModule,
3949
+ MatExpansionModule,
3950
+ MatMenuModule,
3951
+ ScrollingModule,
3952
+ PraxisIconDirective,
3953
+ ], template: `
3954
+ <div class="cm-root">
3955
+ <!-- Left: Fields list -->
3956
+ <div class="cm-left">
3957
+ <mat-form-field appearance="outline" class="cm-search" subscriptSizing="dynamic">
3958
+ <mat-label>Buscar campos</mat-label>
3959
+ <input matInput [ngModel]="searchTerm" (ngModelChange)="onSearchChange($event)">
3960
+ </mat-form-field>
3961
+ <ng-container *ngIf="fields.length <= 50; else vscroll">
3962
+ <div>
3963
+ <div *ngFor="let f of filteredFields()" (click)="selectField(f.name)" class="field-item" [class.selected]="f.name===selectedField()" tabindex="0" (keydown)="onFieldKeydown($event, f.name)">
3964
+ <div>
3965
+ <div>{{ labelFor(f.name) }}</div>
3966
+ <small style="opacity:.7">{{ f.name }}</small>
3967
+ </div>
3968
+ <div class="rule-badges" *ngIf="hasRule(f.name)">
3969
+ <span class="rule-badge b-active" *ngIf="isCascadeEnabled(f.name)">Ativo</span>
3970
+ <span class="rule-badge b-manual" *ngIf="loadOnChange(f.name)==='manual'">Manual</span>
3971
+ <span class="rule-badge b-immediate" *ngIf="loadOnChange(f.name)==='immediate'">Immediate</span>
3972
+ </div>
3973
+ </div>
3974
+ </div>
3975
+ </ng-container>
3976
+ <ng-template #vscroll>
3977
+ <cdk-virtual-scroll-viewport itemSize="44" style="height: calc(100vh - 240px)">
3978
+ <div *cdkVirtualFor="let f of filteredFields()" (click)="selectField(f.name)" class="field-item" [class.selected]="f.name===selectedField()" tabindex="0" (keydown)="onFieldKeydown($event, f.name)">
3979
+ <div>
3980
+ <div>{{ labelFor(f.name) }}</div>
3981
+ <small style="opacity:.7">{{ f.name }}</small>
3982
+ </div>
3983
+ <div class="rule-badges" *ngIf="hasRule(f.name)">
3984
+ <span class="rule-badge b-active" *ngIf="isCascadeEnabled(f.name)">Ativo</span>
3985
+ <span class="rule-badge b-manual" *ngIf="loadOnChange(f.name)==='manual'">Manual</span>
3986
+ <span class="rule-badge b-immediate" *ngIf="loadOnChange(f.name)==='immediate'">Immediate</span>
3987
+ </div>
3988
+ </div>
3989
+ </cdk-virtual-scroll-viewport>
3990
+ </ng-template>
3991
+ </div>
3992
+
3993
+ <!-- Right: Rules for selected field -->
3994
+ <div class="cm-right" *ngIf="selectedField() as sel">
3995
+ <div class="cm-header">
3996
+ <div>
3997
+ <h3 style="margin:0">{{ labelFor(sel) }}</h3>
3998
+ <div style="opacity:.7">{{ sel }}</div>
3999
+ </div>
4000
+ <div>
4001
+ <button mat-stroked-button color="primary" (click)="exportRules()" style="margin-right:8px">Exportar</button>
4002
+ <button mat-stroked-button (click)="toggleImport()" style="margin-right:8px">Importar</button>
4003
+ <button mat-flat-button color="primary" [matMenuTriggerFor]="addMenu" [disabled]="editing()">Adicionar</button>
4004
+ <mat-menu #addMenu="matMenu">
4005
+ <button mat-menu-item (click)="startEdit(sel)"><mat-icon [praxisIcon]="'add'"></mat-icon><span>Em branco</span></button>
4006
+ <button mat-menu-item (click)="startTemplate(sel, 'rota-destino')" [disabled]="!hasField('rotaId') || !hasField('destinoReqId')"><mat-icon [praxisIcon]="'bolt'"></mat-icon><span>Rota → Destino (id)</span></button>
4007
+ <button mat-menu-item (click)="startTemplate(sel, 'rota-destino-propriedade')" [disabled]="!hasField('rotaId') || !hasField('destinoReqId')"><mat-icon [praxisIcon]="'bolt'"></mat-icon><span>Rota + Destino → Propriedade (ids)</span></button>
4008
+ </mat-menu>
4009
+ </div>
4010
+ </div>
4011
+
4012
+ <!-- Conflict banner (connections) -->
4013
+ <div class="banner" *ngIf="hasConflict(sel)">
4014
+ <mat-icon [praxisIcon]="'link_off'"></mat-icon>
4015
+ <div>
4016
+ Connections atualizam este campo (filterCriteria). Considere desativar a cascata nativa ou usar ‘manual’.
4017
+ </div>
4018
+ <span class="spacer"></span>
4019
+ <button mat-stroked-button color="primary" (click)="quickSet(sel,'disable')">Desativar</button>
4020
+ <button mat-stroked-button color="accent" (click)="quickSet(sel,'manual')">Definir manual</button>
4021
+ </div>
4022
+
4023
+ <div *ngIf="importMode" class="import-panel">
4024
+ <div style="display:flex; gap:8px; align-items:center; margin-bottom:8px"><mat-icon [praxisIcon]="'upload'"></mat-icon><strong>Importar regras (JSON)</strong></div>
4025
+ <textarea matInput rows="6" class="full" [(ngModel)]="importJson" placeholder='{ "campo": { "dependencyFields": ["dep"], ... } }'></textarea>
4026
+ <div style="display:flex; gap:8px; justify-content:flex-end; padding-top:8px">
4027
+ <button mat-stroked-button (click)="toggleImport()">Cancelar</button>
4028
+ <button mat-flat-button color="primary" (click)="applyImport()">Aplicar</button>
4029
+ </div>
4030
+ </div>
4031
+
4032
+ <div *ngIf="!ruleFor(sel)">
4033
+ <div class="rules-empty">Nenhuma regra de cascata. Clique em “Adicionar”.</div>
4034
+ </div>
4035
+ <div *ngIf="ruleFor(sel) as rule">
4036
+ <div class="rule-row">
4037
+ <div>
4038
+ <div><strong>Dependentes:</strong> {{ rule.dependencyFields.join(', ') || '—' }}</div>
4039
+ <div style="opacity:.7">Load on change: {{ rule.dependencyLoadOnChange || 'respectLoadOn' }} | Merge: {{ rule.dependencyMergeStrategy || 'merge' }}</div>
4040
+ </div>
4041
+ <div class="rule-actions">
4042
+ <button mat-icon-button matTooltip="Editar" (click)="edit(sel)"><mat-icon [praxisIcon]="'edit'"></mat-icon></button>
4043
+ <button mat-icon-button matTooltip="Duplicar" disabled><mat-icon [praxisIcon]="'content_copy'"></mat-icon></button>
4044
+ <button mat-icon-button matTooltip="Remover" (click)="remove(sel)"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
4045
+ </div>
4046
+ </div>
4047
+ </div>
4048
+
4049
+ <!-- Editor com seções -->
4050
+ <mat-expansion-panel [expanded]="editing()">
4051
+ <mat-expansion-panel-header>
4052
+ <mat-panel-title>Edição de Regra</mat-panel-title>
4053
+ <mat-panel-description>Configurar dependentes, execução e mapeamento</mat-panel-description>
4054
+ </mat-expansion-panel-header>
4055
+
4056
+ <!-- Básico -->
4057
+ <mat-expansion-panel [expanded]="true">
4058
+ <mat-expansion-panel-header>
4059
+ <mat-panel-title>Básico</mat-panel-title>
4060
+ </mat-expansion-panel-header>
4061
+ <div class="form-grid">
4062
+ <div class="form-row">
4063
+ <mat-form-field appearance="outline" class="full">
4064
+ <mat-label>Dependentes (CSV)</mat-label>
4065
+ <input matInput [(ngModel)]="editDepCsv">
4066
+ <mat-hint>Ex.: rotaId,destinoReqId</mat-hint>
4067
+ </mat-form-field>
4068
+ </div>
4069
+ <div class="form-row">
4070
+ <mat-checkbox [(ngModel)]="editEnable">Ativar cascata nativa</mat-checkbox>
4071
+ <mat-checkbox [(ngModel)]="editReset">Resetar ao mudar</mat-checkbox>
4072
+ </div>
4073
+ </div>
4074
+ </mat-expansion-panel>
4075
+
4076
+ <!-- Execução -->
4077
+ <mat-expansion-panel>
4078
+ <mat-expansion-panel-header>
4079
+ <mat-panel-title>Execução</mat-panel-title>
4080
+ </mat-expansion-panel-header>
4081
+ <div class="form-row">
4082
+ <mat-form-field appearance="outline">
4083
+ <mat-label>Carregar ao mudar</mat-label>
4084
+ <mat-select [(ngModel)]="editLoadOn">
4085
+ <mat-option value="respectLoadOn">Respeitar loadOn</mat-option>
4086
+ <mat-option value="immediate">Imediato</mat-option>
4087
+ <mat-option value="manual">Manual</mat-option>
4088
+ </mat-select>
4089
+ </mat-form-field>
4090
+ <mat-form-field appearance="outline">
4091
+ <mat-label>Merge de filtros</mat-label>
4092
+ <mat-select [(ngModel)]="editMerge">
4093
+ <mat-option value="merge">merge (padrão)</mat-option>
4094
+ <mat-option value="replace">replace</mat-option>
4095
+ </mat-select>
4096
+ </mat-form-field>
4097
+ <mat-form-field appearance="outline">
4098
+ <mat-label>Atraso (ms)</mat-label>
4099
+ <input matInput type="number" [(ngModel)]="editDebounce">
4100
+ </mat-form-field>
4101
+ </div>
4102
+ </mat-expansion-panel>
4103
+
4104
+ <!-- Extração de valor -->
4105
+ <mat-expansion-panel>
4106
+ <mat-expansion-panel-header>
4107
+ <mat-panel-title>Extração de valor</mat-panel-title>
4108
+ </mat-expansion-panel-header>
4109
+ <div class="form-row">
4110
+ <mat-form-field appearance="outline" class="full">
4111
+ <mat-label>ValuePath padrão</mat-label>
4112
+ <input matInput [(ngModel)]="editValuePath" placeholder="id">
4113
+ <mat-hint>Preenchido automaticamente a partir de optionValueKey() ou 'id'</mat-hint>
4114
+ </mat-form-field>
4115
+ </div>
4116
+ </mat-expansion-panel>
4117
+
4118
+ <!-- Mapeamento de filtros (tabela) -->
4119
+ <mat-expansion-panel>
4120
+ <mat-expansion-panel-header>
4121
+ <mat-panel-title>Mapeamento de filtros</mat-panel-title>
4122
+ </mat-expansion-panel-header>
4123
+ <table class="map-table" *ngIf="editMapRows.length; else noMap">
4124
+ <thead>
4125
+ <tr>
4126
+ <th>Dependente</th>
4127
+ <th>Chave (dot‑path)</th>
4128
+ <th>ValuePath (opcional)</th>
4129
+ </tr>
4130
+ </thead>
4131
+ <tbody>
4132
+ <tr *ngFor="let row of editMapRows; let i = index">
4133
+ <td>{{ row.dep }}</td>
4134
+ <td>
4135
+ <input matInput [(ngModel)]="row.key" placeholder="{{row.dep}}">
4136
+ </td>
4137
+ <td>
4138
+ <input matInput [(ngModel)]="row.valuePath" placeholder="{{editValuePath||'id'}}">
4139
+ </td>
4140
+ </tr>
4141
+ </tbody>
4142
+ </table>
4143
+ <ng-template #noMap>
4144
+ <div class="rules-empty">Defina dependentes para sugerir o mapeamento.</div>
4145
+ </ng-template>
4146
+ </mat-expansion-panel>
4147
+
4148
+ <!-- Preview -->
4149
+ <mat-expansion-panel>
4150
+ <mat-expansion-panel-header>
4151
+ <mat-panel-title>Preview</mat-panel-title>
4152
+ </mat-expansion-panel-header>
4153
+ <pre style="white-space: pre-wrap; background:#fafafa; padding:8px; border:1px solid #eee; border-radius:6px">{{ buildPreview() }}</pre>
4154
+ <div style="display:flex; justify-content:flex-end; padding-top:6px">
4155
+ <button mat-stroked-button (click)="copyPreview()">Copiar</button>
4156
+ </div>
4157
+ </mat-expansion-panel>
4158
+
4159
+ <div style="display:flex; gap:8px; justify-content:flex-end; padding:8px 0;">
4160
+ <button mat-stroked-button (click)="cancelEdit()">Cancelar</button>
4161
+ <button mat-flat-button color="primary" (click)="saveEdit(sel)">Salvar</button>
4162
+ </div>
4163
+ </mat-expansion-panel>
4164
+ </div>
4165
+ </div>
4166
+ `, styles: [":host{display:block}.cm-root{display:grid;grid-template-columns:320px 1fr;gap:16px;min-height:420px}.cm-left{border-right:1px solid rgba(0,0,0,.06);padding-right:8px}.cm-search{width:100%;margin-bottom:8px}.cm-right{padding-left:8px}.field-item{display:flex;align-items:center;justify-content:space-between;padding:6px 4px;cursor:pointer;border-radius:6px}.field-item.selected{background:#0000000a}.rule-badges{display:inline-flex;gap:6px}.rule-badge{font-size:10px;padding:2px 6px;border-radius:10px}.b-active{background:#e8f5e9;color:#2e7d32}.b-manual{background:#eceff1;color:#455a64}.b-immediate{background:#fff3e0;color:#ef6c00}.b-conflict{background:#ffebee;color:#c62828}.cm-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.rules-empty{opacity:.7;font-style:italic;padding:8px 0}.rule-row{display:grid;grid-template-columns:1fr auto;align-items:center;padding:8px 0;border-bottom:1px dashed rgba(0,0,0,.06)}.rule-actions button{margin-left:4px}.banner{background:#fff8e1;color:#6d4c41;padding:8px 12px;border:1px solid #ffe082;border-radius:6px;margin-bottom:8px;display:flex;gap:8px;align-items:center}.import-panel{background:#f5f5f5;border:1px solid #e0e0e0;border-radius:6px;padding:8px;margin-bottom:8px}.full{width:100%}.form-row{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.form-grid{display:grid;gap:12px;padding:8px 0}.map-table{width:100%;border-collapse:collapse}.map-table th,.map-table td{padding:6px 8px;border-bottom:1px dashed rgba(0,0,0,.06)}.kbd{font-family:monospace;background:#eee;padding:0 4px;border-radius:3px}\n"] }]
4167
+ }], ctorParameters: () => [{ type: CascadeRulesService }], propDecorators: { fields: [{
4168
+ type: Input
4169
+ }], connections: [{
4170
+ type: Input
4171
+ }], apply: [{
4172
+ type: Output
4173
+ }], cancel: [{
4174
+ type: Output
4175
+ }] } });
4176
+
4177
+ /**
4178
+ * Generated bundle index. Do not edit.
4179
+ */
4180
+
4181
+ export { CascadeManagerTabComponent, CascadeRulesService, ConfigRegistryService, ContextValidatorRegistryService, DynamicEditorRendererComponent, DynamicFormFactoryService, EditorComponentRegistryService, FieldMetadataEditorComponent, SchemaNormalizerService };
4182
+ //# sourceMappingURL=praxisui-metadata-editor.mjs.map