@praxisui/metadata-editor 9.0.0-beta.5 → 9.0.0-beta.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,40 +1,42 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, inject, Component, Input, ViewEncapsulation, EventEmitter, DestroyRef, Output, signal, ENVIRONMENT_INITIALIZER } from '@angular/core';
2
+ import { Injectable, inject, Component, forwardRef, ElementRef, Input, Optional, ViewEncapsulation, EventEmitter, DestroyRef, Output, signal, ENVIRONMENT_INITIALIZER } from '@angular/core';
3
3
  import * as i1 from '@angular/forms';
4
- import { FormControl, FormGroup, ReactiveFormsModule, FormArray, FormBuilder, FormsModule } from '@angular/forms';
5
- import * as i3 from '@angular/common';
4
+ import { FormControl, FormGroup, FormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS, ReactiveFormsModule, FormArray, FormBuilder } from '@angular/forms';
5
+ import * as i3$1 from '@angular/common';
6
6
  import { CommonModule } from '@angular/common';
7
7
  import { DynamicFieldLoaderDirective } from '@praxisui/dynamic-fields';
8
- import * as i2$1 from '@praxisui/core';
9
- import { FieldControlType, PraxisIconDirective, providePraxisI18n, PraxisI18nService, INLINE_FILTER_CONTROL_TYPES, resolveInlineFilterControlType, normalizeControlTypeToken, ComponentMetadataRegistry } from '@praxisui/core';
10
- import * as i5 from '@angular/material/expansion';
8
+ import * as i2$2 from '@praxisui/core';
9
+ import { providePraxisI18n, PraxisI18nService, PraxisIconDirective, FieldControlType, INLINE_FILTER_CONTROL_TYPES, resolveInlineFilterControlType, normalizeControlTypeToken, ComponentMetadataRegistry } from '@praxisui/core';
10
+ import * as i5$1 from '@angular/material/expansion';
11
11
  import { MatExpansionModule } from '@angular/material/expansion';
12
- import * as i6 from '@angular/material/icon';
12
+ import * as i4 from '@angular/material/icon';
13
13
  import { MatIconModule } from '@angular/material/icon';
14
- import * as i4 from '@angular/material/button';
14
+ import * as i2$1 from '@angular/material/button';
15
15
  import { MatButtonModule } from '@angular/material/button';
16
16
  import * as i1$1 from '@angular/material/dialog';
17
17
  import { MatDialogRef, MatDialogModule } from '@angular/material/dialog';
18
- import * as i8 from '@angular/material/tooltip';
18
+ import * as i7 from '@angular/material/tooltip';
19
19
  import { MatTooltipModule } from '@angular/material/tooltip';
20
20
  import * as i2 from '@angular/material/list';
21
21
  import { MatListModule } from '@angular/material/list';
22
+ import * as i3 from '@angular/material/form-field';
23
+ import { MatFormFieldModule } from '@angular/material/form-field';
24
+ import * as i5 from '@angular/material/input';
25
+ import { MatInputModule } from '@angular/material/input';
26
+ import * as i6 from '@angular/material/select';
27
+ import { MatSelectModule } from '@angular/material/select';
22
28
  import { SETTINGS_PANEL_DATA } from '@praxisui/settings-panel';
23
29
  import { BehaviorSubject } from 'rxjs';
24
30
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
25
- import * as i6$1 from '@angular/material/input';
26
- import { MatInputModule } from '@angular/material/input';
27
31
  import { MatChipsModule } from '@angular/material/chips';
28
32
  import { MatDividerModule } from '@angular/material/divider';
29
- import * as i7 from '@angular/material/select';
30
- import { MatSelectModule } from '@angular/material/select';
31
- import * as i8$1 from '@angular/material/checkbox';
33
+ import * as i9 from '@angular/material/checkbox';
32
34
  import { MatCheckboxModule } from '@angular/material/checkbox';
33
- import * as i10 from '@angular/material/menu';
35
+ import * as i11 from '@angular/material/menu';
34
36
  import { MatMenuModule } from '@angular/material/menu';
35
- import * as i11 from '@angular/material/autocomplete';
37
+ import * as i12 from '@angular/material/autocomplete';
36
38
  import { MatAutocompleteModule } from '@angular/material/autocomplete';
37
- import * as i12 from '@angular/cdk/scrolling';
39
+ import * as i13 from '@angular/cdk/scrolling';
38
40
  import { ScrollingModule } from '@angular/cdk/scrolling';
39
41
 
40
42
  class ConfigRegistryService {
@@ -361,52 +363,1454 @@ class RegexHelpDialogComponent {
361
363
  <span matListItemLine>{{ r.label }}</span>
362
364
  </a>
363
365
  }
364
- </mat-nav-list>
365
- <p style="margin-top: 12px;">Recursos úteis:</p>
366
- <ul>
367
- <li><a href="https://regex101.com/" target="_blank" rel="noreferrer noopener">regex101.com</a> — Teste e depure regex</li>
368
- <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>
369
- </ul>
370
- </div>
371
- <div mat-dialog-actions>
372
- <button class="mat-mdc-button mdc-button" mat-dialog-close>Fechar</button>
373
- </div>
374
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1$1.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: i1$1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1$1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1$1.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 }] });
366
+ </mat-nav-list>
367
+ <p style="margin-top: 12px;">Recursos úteis:</p>
368
+ <ul>
369
+ <li><a href="https://regex101.com/" target="_blank" rel="noreferrer noopener">regex101.com</a> — Teste e depure regex</li>
370
+ <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>
371
+ </ul>
372
+ </div>
373
+ <div mat-dialog-actions>
374
+ <button class="mat-mdc-button mdc-button" mat-dialog-close>Fechar</button>
375
+ </div>
376
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1$1.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: i1$1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1$1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1$1.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 }] });
377
+ }
378
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: RegexHelpDialogComponent, decorators: [{
379
+ type: Component,
380
+ args: [{
381
+ selector: 'praxis-regex-help-dialog',
382
+ standalone: true,
383
+ imports: [MatDialogModule, MatListModule, MatButtonModule],
384
+ template: `
385
+ <h2 mat-dialog-title>Modelos de Expressões Regulares</h2>
386
+ <div mat-dialog-content>
387
+ <p>Selecione um dos modelos abaixo para preencher o campo ou use como base:</p>
388
+ <mat-nav-list dense>
389
+ @for (r of presets; track r.pattern) {
390
+ <a mat-list-item (click)="choose(r.pattern)" style="cursor: pointer;">
391
+ <span matListItemTitle><code>{{ r.pattern }}</code></span>
392
+ <span matListItemLine>{{ r.label }}</span>
393
+ </a>
394
+ }
395
+ </mat-nav-list>
396
+ <p style="margin-top: 12px;">Recursos úteis:</p>
397
+ <ul>
398
+ <li><a href="https://regex101.com/" target="_blank" rel="noreferrer noopener">regex101.com</a> — Teste e depure regex</li>
399
+ <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>
400
+ </ul>
401
+ </div>
402
+ <div mat-dialog-actions>
403
+ <button class="mat-mdc-button mdc-button" mat-dialog-close>Fechar</button>
404
+ </div>
405
+ `,
406
+ }]
407
+ }] });
408
+
409
+ const METADATA_EDITOR_EN_US = {
410
+ 'praxis.metadataEditor.arrayFields.section': 'Collection subfields',
411
+ 'praxis.metadataEditor.arrayFields.title': 'Subfields',
412
+ 'praxis.metadataEditor.arrayFields.add': 'Add subfield',
413
+ 'praxis.metadataEditor.arrayFields.duplicateNames': 'There are duplicate names: {{names}}',
414
+ 'praxis.metadataEditor.arrayFields.empty': 'No local subfields configured.',
415
+ 'praxis.metadataEditor.arrayFields.name': 'Name',
416
+ 'praxis.metadataEditor.arrayFields.nameRequired': 'Enter the subfield name.',
417
+ 'praxis.metadataEditor.arrayFields.nameDuplicated': 'This name is already used in the collection.',
418
+ 'praxis.metadataEditor.arrayFields.label': 'Label',
419
+ 'praxis.metadataEditor.arrayFields.controlType': 'Control',
420
+ 'praxis.metadataEditor.arrayFields.required': 'Required',
421
+ 'praxis.metadataEditor.arrayFields.moveUp': 'Move up',
422
+ 'praxis.metadataEditor.arrayFields.moveDown': 'Move down',
423
+ 'praxis.metadataEditor.arrayFields.duplicate': 'Duplicate',
424
+ 'praxis.metadataEditor.arrayFields.remove': 'Remove',
425
+ 'praxis.metadataEditor.arrayFields.removeBlocked': 'Remove references before deleting: {{references}}',
426
+ 'praxis.metadataEditor.arrayFields.refOnly': 'The item schema comes from itemSchemaRef; create local subfields only when you need an override.',
427
+ 'praxis.metadataEditor.arrayFields.overrideLocal': 'There is an itemSchemaRef and local subfields; local subfields work as an explicit override.',
428
+ 'praxis.metadataEditor.arrayFields.imported': 'Subfields imported. Review them before applying or saving.',
429
+ 'praxis.metadataEditor.arrayFields.localOnly': 'Local subfields will be saved in array.itemSchema.fields.',
430
+ 'praxis.metadataEditor.arrayFields.control.input': 'Text',
431
+ 'praxis.metadataEditor.arrayFields.control.textarea': 'Long text',
432
+ 'praxis.metadataEditor.arrayFields.control.numeric': 'Number',
433
+ 'praxis.metadataEditor.arrayFields.control.currency': 'Currency',
434
+ 'praxis.metadataEditor.arrayFields.control.email': 'Email',
435
+ 'praxis.metadataEditor.arrayFields.control.phone': 'Phone',
436
+ 'praxis.metadataEditor.arrayFields.control.select': 'Select',
437
+ 'praxis.metadataEditor.arrayFields.control.entityLookup': 'Entity lookup',
438
+ 'praxis.metadataEditor.arrayFields.control.toggle': 'Toggle',
439
+ 'praxis.metadataEditor.arrayFields.control.date': 'Date',
440
+ 'praxis.metadataEditor.fieldResource.label': 'Governed resource',
441
+ 'praxis.metadataEditor.fieldResource.searchPlaceholder': 'Search by name, domain, or path',
442
+ 'praxis.metadataEditor.fieldResource.hint': 'Select from the host governed catalog. The saved value remains resourcePath.',
443
+ 'praxis.metadataEditor.common.help': 'Help',
444
+ 'praxis.metadataEditor.common.iconMaterialSymbolsHint': 'Use the icon name (Material Symbols)',
445
+ 'praxis.metadataEditor.presentationFields.group': 'Presentation',
446
+ 'praxis.metadataEditor.presentationFields.valuePresentation.label': 'Value format',
447
+ 'praxis.metadataEditor.presentationFields.valuePresentation.hint': 'Canonical formatter used on readonly/presentation surfaces. Use a JSON object.',
448
+ 'praxis.metadataEditor.presentationFields.presenter.label': 'Presenter',
449
+ 'praxis.metadataEditor.presentationFields.presenter.hint': 'Semantic rendering strategy for presentationMode.',
450
+ 'praxis.metadataEditor.presentationFields.presenter.option.text': 'Text',
451
+ 'praxis.metadataEditor.presentationFields.presenter.option.status': 'Status',
452
+ 'praxis.metadataEditor.presentationFields.presenter.option.chip': 'Chip',
453
+ 'praxis.metadataEditor.presentationFields.presenter.option.badge': 'Badge',
454
+ 'praxis.metadataEditor.presentationFields.presenter.option.iconValue': 'Icon + value',
455
+ 'praxis.metadataEditor.presentationFields.presenter.option.progress': 'Progress',
456
+ 'praxis.metadataEditor.presentationFields.presenter.option.rating': 'Rating',
457
+ 'praxis.metadataEditor.presentationFields.tone.label': 'Tone',
458
+ 'praxis.metadataEditor.presentationFields.tone.hint': 'Semantic tone mapped by the runtime to theme tokens.',
459
+ 'praxis.metadataEditor.presentationFields.tone.option.neutral': 'Neutral',
460
+ 'praxis.metadataEditor.presentationFields.tone.option.info': 'Info',
461
+ 'praxis.metadataEditor.presentationFields.tone.option.success': 'Success',
462
+ 'praxis.metadataEditor.presentationFields.tone.option.warning': 'Warning',
463
+ 'praxis.metadataEditor.presentationFields.tone.option.danger': 'Critical',
464
+ 'praxis.metadataEditor.presentationFields.appearance.label': 'Emphasis',
465
+ 'praxis.metadataEditor.presentationFields.appearance.hint': 'Visual emphasis level for the presenter.',
466
+ 'praxis.metadataEditor.presentationFields.appearance.option.plain': 'Plain',
467
+ 'praxis.metadataEditor.presentationFields.appearance.option.soft': 'Soft',
468
+ 'praxis.metadataEditor.presentationFields.appearance.option.outlined': 'Outlined',
469
+ 'praxis.metadataEditor.presentationFields.appearance.option.filled': 'Filled',
470
+ 'praxis.metadataEditor.presentationFields.icon.label': 'Icon',
471
+ 'praxis.metadataEditor.presentationFields.icon.hint': 'Material Symbols icon name, without HTML.',
472
+ 'praxis.metadataEditor.presentationFields.label.label': 'Label',
473
+ 'praxis.metadataEditor.presentationFields.label.placeholder': 'Pending',
474
+ 'praxis.metadataEditor.presentationFields.label.hint': 'Semantic text shown by status, chip or badge presenters.',
475
+ 'praxis.metadataEditor.presentationFields.badge.label': 'Badge',
476
+ 'praxis.metadataEditor.presentationFields.badge.hint': 'Optional secondary marker shown with the value.',
477
+ 'praxis.metadataEditor.presentationFields.tooltip.label': 'Tooltip',
478
+ 'praxis.metadataEditor.presentationFields.tooltip.placeholder': 'Awaiting fiscal approval',
479
+ 'praxis.metadataEditor.presentationFields.tooltip.hint': 'Short explanation shown as a tooltip.',
480
+ 'praxis.metadataEditor.presentationFields.presentationRules.label': 'Presentation rules',
481
+ 'praxis.metadataEditor.presentationFields.presentationRules.hint': 'Guided builder for ordered Json Logic rules that only change presentation. Later rules win.',
482
+ 'praxis.metadataEditor.presentationChoice.help': 'Help',
483
+ 'praxis.metadataEditor.presentationChoice.clear': 'Clear selection',
484
+ 'praxis.metadataEditor.presentationRulesBuilder.title': 'Presentation rules',
485
+ 'praxis.metadataEditor.presentationRulesBuilder.description': 'Change tone, icon, and labels without changing the submitted value.',
486
+ 'praxis.metadataEditor.presentationRulesBuilder.add': 'Add rule',
487
+ 'praxis.metadataEditor.presentationRulesBuilder.empty': 'No rules configured. Add rules for states such as delay, block, or approval.',
488
+ 'praxis.metadataEditor.presentationRulesBuilder.rule': 'Rule',
489
+ 'praxis.metadataEditor.presentationRulesBuilder.mode': 'Rule mode',
490
+ 'praxis.metadataEditor.presentationRulesBuilder.guided': 'Guided',
491
+ 'praxis.metadataEditor.presentationRulesBuilder.advanced': 'Advanced JSON',
492
+ 'praxis.metadataEditor.presentationRulesBuilder.advancedRule': 'JSON rule',
493
+ 'praxis.metadataEditor.presentationRulesBuilder.moveUp': 'Move up',
494
+ 'praxis.metadataEditor.presentationRulesBuilder.moveDown': 'Move down',
495
+ 'praxis.metadataEditor.presentationRulesBuilder.remove': 'Remove',
496
+ 'praxis.metadataEditor.presentationRulesBuilder.sourcePath': 'Variable',
497
+ 'praxis.metadataEditor.presentationRulesBuilder.operator': 'Condition',
498
+ 'praxis.metadataEditor.presentationRulesBuilder.compareValue': 'Value',
499
+ 'praxis.metadataEditor.presentationRulesBuilder.keep': 'Keep',
500
+ 'praxis.metadataEditor.presentationRulesBuilder.tone': 'Tone',
501
+ 'praxis.metadataEditor.presentationRulesBuilder.appearance': 'Emphasis',
502
+ 'praxis.metadataEditor.presentationRulesBuilder.icon': 'Icon',
503
+ 'praxis.metadataEditor.presentationRulesBuilder.label': 'Label',
504
+ 'praxis.metadataEditor.presentationRulesBuilder.badge': 'Badge',
505
+ 'praxis.metadataEditor.presentationRulesBuilder.tooltip': 'Tooltip',
506
+ 'praxis.metadataEditor.presentationRulesBuilder.invalidJson': 'Invalid JSON. Fix the rule before applying.',
507
+ 'praxis.metadataEditor.presentationRulesBuilder.invalidRuleShape': 'The rule must be an object with when and set.',
508
+ 'praxis.metadataEditor.presentationRulesBuilder.unsupportedEffects': 'Use only semantic presentation effects: presenter, tone, appearance, icon, label, badge, tooltip, valuePresentation and interactions.',
509
+ 'praxis.metadataEditor.presentationRulesBuilder.summaryAdvanced': 'Advanced rule preserved as JSON.',
510
+ 'praxis.metadataEditor.presentationRulesBuilder.summaryEffects': '{{count}} effect(s)',
511
+ 'praxis.metadataEditor.presentationRulesBuilder.summaryNoEffects': 'no effects defined',
512
+ 'praxis.metadataEditor.presentationRulesBuilder.operator.equals': 'equals',
513
+ 'praxis.metadataEditor.presentationRulesBuilder.operator.notEquals': 'does not equal',
514
+ 'praxis.metadataEditor.presentationRulesBuilder.operator.greaterThan': 'greater than',
515
+ 'praxis.metadataEditor.presentationRulesBuilder.operator.greaterOrEqual': 'greater than or equal to',
516
+ 'praxis.metadataEditor.presentationRulesBuilder.operator.lessThan': 'less than',
517
+ 'praxis.metadataEditor.presentationRulesBuilder.operator.lessOrEqual': 'less than or equal to',
518
+ 'praxis.metadataEditor.presentationRulesBuilder.operator.contains': 'contains',
519
+ 'praxis.metadataEditor.presentationRulesBuilder.operator.exists': 'exists',
520
+ 'praxis.metadataEditor.presentationRulesBuilder.tone.neutral': 'Neutral',
521
+ 'praxis.metadataEditor.presentationRulesBuilder.tone.info': 'Info',
522
+ 'praxis.metadataEditor.presentationRulesBuilder.tone.success': 'Success',
523
+ 'praxis.metadataEditor.presentationRulesBuilder.tone.warning': 'Warning',
524
+ 'praxis.metadataEditor.presentationRulesBuilder.tone.danger': 'Critical',
525
+ 'praxis.metadataEditor.presentationRulesBuilder.appearance.plain': 'Plain',
526
+ 'praxis.metadataEditor.presentationRulesBuilder.appearance.soft': 'Soft',
527
+ 'praxis.metadataEditor.presentationRulesBuilder.appearance.outlined': 'Outlined',
528
+ 'praxis.metadataEditor.presentationRulesBuilder.appearance.filled': 'Filled',
529
+ };
530
+
531
+ const METADATA_EDITOR_PT_BR = {
532
+ 'praxis.metadataEditor.arrayFields.section': 'Subcampos da cole\u00e7\u00e3o',
533
+ 'praxis.metadataEditor.arrayFields.title': 'Subcampos',
534
+ 'praxis.metadataEditor.arrayFields.add': 'Adicionar subcampo',
535
+ 'praxis.metadataEditor.arrayFields.duplicateNames': 'Existem nomes duplicados: {{names}}',
536
+ 'praxis.metadataEditor.arrayFields.empty': 'Nenhum subcampo local configurado.',
537
+ 'praxis.metadataEditor.arrayFields.name': 'Nome',
538
+ 'praxis.metadataEditor.arrayFields.nameRequired': 'Informe o nome do subcampo.',
539
+ 'praxis.metadataEditor.arrayFields.nameDuplicated': 'Este nome j\u00e1 est\u00e1 em uso na cole\u00e7\u00e3o.',
540
+ 'praxis.metadataEditor.arrayFields.label': 'Label',
541
+ 'praxis.metadataEditor.arrayFields.controlType': 'Controle',
542
+ 'praxis.metadataEditor.arrayFields.required': 'Obrigat\u00f3rio',
543
+ 'praxis.metadataEditor.arrayFields.moveUp': 'Subir',
544
+ 'praxis.metadataEditor.arrayFields.moveDown': 'Descer',
545
+ 'praxis.metadataEditor.arrayFields.duplicate': 'Duplicar',
546
+ 'praxis.metadataEditor.arrayFields.remove': 'Remover',
547
+ 'praxis.metadataEditor.arrayFields.removeBlocked': 'Remova as refer\u00eancias antes de excluir: {{references}}',
548
+ 'praxis.metadataEditor.arrayFields.refOnly': 'O schema do item vem do itemSchemaRef; crie subcampos locais apenas quando precisar de override.',
549
+ 'praxis.metadataEditor.arrayFields.overrideLocal': 'H\u00e1 um itemSchemaRef e subcampos locais; os subcampos locais funcionam como override expl\u00edcito.',
550
+ 'praxis.metadataEditor.arrayFields.imported': 'Subcampos importados. Revise antes de aplicar ou salvar.',
551
+ 'praxis.metadataEditor.arrayFields.localOnly': 'Subcampos locais ser\u00e3o salvos em array.itemSchema.fields.',
552
+ 'praxis.metadataEditor.arrayFields.control.input': 'Texto',
553
+ 'praxis.metadataEditor.arrayFields.control.textarea': 'Texto longo',
554
+ 'praxis.metadataEditor.arrayFields.control.numeric': 'N\u00famero',
555
+ 'praxis.metadataEditor.arrayFields.control.currency': 'Moeda',
556
+ 'praxis.metadataEditor.arrayFields.control.email': 'E-mail',
557
+ 'praxis.metadataEditor.arrayFields.control.phone': 'Telefone',
558
+ 'praxis.metadataEditor.arrayFields.control.select': 'Sele\u00e7\u00e3o',
559
+ 'praxis.metadataEditor.arrayFields.control.entityLookup': 'Busca de entidade',
560
+ 'praxis.metadataEditor.arrayFields.control.toggle': 'Liga/desliga',
561
+ 'praxis.metadataEditor.arrayFields.control.date': 'Data',
562
+ 'praxis.metadataEditor.fieldResource.label': 'Recurso governado',
563
+ 'praxis.metadataEditor.fieldResource.searchPlaceholder': 'Buscar por nome, dom\u00ednio ou caminho',
564
+ 'praxis.metadataEditor.fieldResource.hint': 'Selecione no cat\u00e1logo governado do host. O valor gravado continua sendo resourcePath.',
565
+ 'praxis.metadataEditor.common.help': 'Ajuda',
566
+ 'praxis.metadataEditor.common.iconMaterialSymbolsHint': 'Use o nome do \u00edcone (Material Symbols)',
567
+ 'praxis.metadataEditor.presentationFields.group': 'Apresenta\u00e7\u00e3o',
568
+ 'praxis.metadataEditor.presentationFields.valuePresentation.label': 'Formato do valor',
569
+ 'praxis.metadataEditor.presentationFields.valuePresentation.hint': 'Formatador can\u00f4nico usado em superf\u00edcies readonly/presentation. Use um objeto JSON.',
570
+ 'praxis.metadataEditor.presentationFields.presenter.label': 'Apresentador',
571
+ 'praxis.metadataEditor.presentationFields.presenter.hint': 'Estrat\u00e9gia sem\u00e2ntica para renderiza\u00e7\u00e3o em presentationMode.',
572
+ 'praxis.metadataEditor.presentationFields.presenter.option.text': 'Texto',
573
+ 'praxis.metadataEditor.presentationFields.presenter.option.status': 'Status',
574
+ 'praxis.metadataEditor.presentationFields.presenter.option.chip': 'Chip',
575
+ 'praxis.metadataEditor.presentationFields.presenter.option.badge': 'Badge',
576
+ 'praxis.metadataEditor.presentationFields.presenter.option.iconValue': '\u00cdcone + valor',
577
+ 'praxis.metadataEditor.presentationFields.presenter.option.progress': 'Progresso',
578
+ 'praxis.metadataEditor.presentationFields.presenter.option.rating': 'Avalia\u00e7\u00e3o',
579
+ 'praxis.metadataEditor.presentationFields.tone.label': 'Tom',
580
+ 'praxis.metadataEditor.presentationFields.tone.hint': 'Tom sem\u00e2ntico mapeado pelo runtime para tokens de tema.',
581
+ 'praxis.metadataEditor.presentationFields.tone.option.neutral': 'Neutro',
582
+ 'praxis.metadataEditor.presentationFields.tone.option.info': 'Informa\u00e7\u00e3o',
583
+ 'praxis.metadataEditor.presentationFields.tone.option.success': 'Sucesso',
584
+ 'praxis.metadataEditor.presentationFields.tone.option.warning': 'Aten\u00e7\u00e3o',
585
+ 'praxis.metadataEditor.presentationFields.tone.option.danger': 'Cr\u00edtico',
586
+ 'praxis.metadataEditor.presentationFields.appearance.label': '\u00canfase',
587
+ 'praxis.metadataEditor.presentationFields.appearance.hint': 'N\u00edvel de \u00eanfase visual do apresentador.',
588
+ 'praxis.metadataEditor.presentationFields.appearance.option.plain': 'Simples',
589
+ 'praxis.metadataEditor.presentationFields.appearance.option.soft': 'Suave',
590
+ 'praxis.metadataEditor.presentationFields.appearance.option.outlined': 'Contornado',
591
+ 'praxis.metadataEditor.presentationFields.appearance.option.filled': 'Preenchido',
592
+ 'praxis.metadataEditor.presentationFields.icon.label': '\u00cdcone',
593
+ 'praxis.metadataEditor.presentationFields.icon.hint': 'Nome do \u00edcone Material Symbols, sem HTML.',
594
+ 'praxis.metadataEditor.presentationFields.label.label': 'R\u00f3tulo',
595
+ 'praxis.metadataEditor.presentationFields.label.placeholder': 'Pendente',
596
+ 'praxis.metadataEditor.presentationFields.label.hint': 'Texto sem\u00e2ntico exibido por status, chip ou badge.',
597
+ 'praxis.metadataEditor.presentationFields.badge.label': 'Badge',
598
+ 'praxis.metadataEditor.presentationFields.badge.hint': 'Marcador secund\u00e1rio opcional junto ao valor.',
599
+ 'praxis.metadataEditor.presentationFields.tooltip.label': 'Tooltip',
600
+ 'praxis.metadataEditor.presentationFields.tooltip.placeholder': 'Aguardando aprova\u00e7\u00e3o fiscal',
601
+ 'praxis.metadataEditor.presentationFields.tooltip.hint': 'Explica\u00e7\u00e3o curta exibida como tooltip.',
602
+ 'praxis.metadataEditor.presentationFields.presentationRules.label': 'Regras de apresenta\u00e7\u00e3o',
603
+ 'praxis.metadataEditor.presentationFields.presentationRules.hint': 'Builder guiado para regras Json Logic ordenadas que alteram somente a apresenta\u00e7\u00e3o. Regras posteriores vencem.',
604
+ 'praxis.metadataEditor.presentationChoice.help': 'Ajuda',
605
+ 'praxis.metadataEditor.presentationChoice.clear': 'Limpar sele\u00e7\u00e3o',
606
+ 'praxis.metadataEditor.presentationRulesBuilder.title': 'Regras de apresenta\u00e7\u00e3o',
607
+ 'praxis.metadataEditor.presentationRulesBuilder.description': 'Altere tom, \u00edcone e r\u00f3tulos sem modificar o valor enviado.',
608
+ 'praxis.metadataEditor.presentationRulesBuilder.add': 'Adicionar regra',
609
+ 'praxis.metadataEditor.presentationRulesBuilder.empty': 'Nenhuma regra configurada. Adicione regras para estados como atraso, bloqueio ou aprova\u00e7\u00e3o.',
610
+ 'praxis.metadataEditor.presentationRulesBuilder.rule': 'Regra',
611
+ 'praxis.metadataEditor.presentationRulesBuilder.mode': 'Modo da regra',
612
+ 'praxis.metadataEditor.presentationRulesBuilder.guided': 'Guiado',
613
+ 'praxis.metadataEditor.presentationRulesBuilder.advanced': 'JSON avan\u00e7ado',
614
+ 'praxis.metadataEditor.presentationRulesBuilder.advancedRule': 'Regra JSON',
615
+ 'praxis.metadataEditor.presentationRulesBuilder.moveUp': 'Subir',
616
+ 'praxis.metadataEditor.presentationRulesBuilder.moveDown': 'Descer',
617
+ 'praxis.metadataEditor.presentationRulesBuilder.remove': 'Remover',
618
+ 'praxis.metadataEditor.presentationRulesBuilder.sourcePath': 'Vari\u00e1vel',
619
+ 'praxis.metadataEditor.presentationRulesBuilder.operator': 'Condi\u00e7\u00e3o',
620
+ 'praxis.metadataEditor.presentationRulesBuilder.compareValue': 'Valor',
621
+ 'praxis.metadataEditor.presentationRulesBuilder.keep': 'Manter',
622
+ 'praxis.metadataEditor.presentationRulesBuilder.tone': 'Tom',
623
+ 'praxis.metadataEditor.presentationRulesBuilder.appearance': '\u00canfase',
624
+ 'praxis.metadataEditor.presentationRulesBuilder.icon': '\u00cdcone',
625
+ 'praxis.metadataEditor.presentationRulesBuilder.label': 'R\u00f3tulo',
626
+ 'praxis.metadataEditor.presentationRulesBuilder.badge': 'Badge',
627
+ 'praxis.metadataEditor.presentationRulesBuilder.tooltip': 'Tooltip',
628
+ 'praxis.metadataEditor.presentationRulesBuilder.invalidJson': 'JSON inv\u00e1lido. Corrija a regra antes de aplicar.',
629
+ 'praxis.metadataEditor.presentationRulesBuilder.invalidRuleShape': 'A regra deve ser um objeto com when e set.',
630
+ 'praxis.metadataEditor.presentationRulesBuilder.unsupportedEffects': 'Use apenas efeitos sem\u00e2nticos de apresenta\u00e7\u00e3o: presenter, tone, appearance, icon, label, badge, tooltip, valuePresentation e interactions.',
631
+ 'praxis.metadataEditor.presentationRulesBuilder.summaryAdvanced': 'Regra avan\u00e7ada preservada como JSON.',
632
+ 'praxis.metadataEditor.presentationRulesBuilder.summaryEffects': '{{count}} efeito(s)',
633
+ 'praxis.metadataEditor.presentationRulesBuilder.summaryNoEffects': 'sem efeitos definidos',
634
+ 'praxis.metadataEditor.presentationRulesBuilder.operator.equals': 'igual a',
635
+ 'praxis.metadataEditor.presentationRulesBuilder.operator.notEquals': 'diferente de',
636
+ 'praxis.metadataEditor.presentationRulesBuilder.operator.greaterThan': 'maior que',
637
+ 'praxis.metadataEditor.presentationRulesBuilder.operator.greaterOrEqual': 'maior ou igual a',
638
+ 'praxis.metadataEditor.presentationRulesBuilder.operator.lessThan': 'menor que',
639
+ 'praxis.metadataEditor.presentationRulesBuilder.operator.lessOrEqual': 'menor ou igual a',
640
+ 'praxis.metadataEditor.presentationRulesBuilder.operator.contains': 'cont\u00e9m',
641
+ 'praxis.metadataEditor.presentationRulesBuilder.operator.exists': 'existe',
642
+ 'praxis.metadataEditor.presentationRulesBuilder.tone.neutral': 'Neutro',
643
+ 'praxis.metadataEditor.presentationRulesBuilder.tone.info': 'Informa\u00e7\u00e3o',
644
+ 'praxis.metadataEditor.presentationRulesBuilder.tone.success': 'Sucesso',
645
+ 'praxis.metadataEditor.presentationRulesBuilder.tone.warning': 'Aten\u00e7\u00e3o',
646
+ 'praxis.metadataEditor.presentationRulesBuilder.tone.danger': 'Cr\u00edtico',
647
+ 'praxis.metadataEditor.presentationRulesBuilder.appearance.plain': 'Simples',
648
+ 'praxis.metadataEditor.presentationRulesBuilder.appearance.soft': 'Suave',
649
+ 'praxis.metadataEditor.presentationRulesBuilder.appearance.outlined': 'Contornado',
650
+ 'praxis.metadataEditor.presentationRulesBuilder.appearance.filled': 'Preenchido',
651
+ };
652
+
653
+ function createPraxisMetadataEditorI18nConfig(options = {}) {
654
+ const dictionaries = {
655
+ 'pt-BR': {
656
+ ...METADATA_EDITOR_PT_BR,
657
+ ...(options.dictionaries?.['pt-BR'] ?? {}),
658
+ },
659
+ 'en-US': {
660
+ ...METADATA_EDITOR_EN_US,
661
+ ...(options.dictionaries?.['en-US'] ?? {}),
662
+ },
663
+ };
664
+ for (const [locale, dictionary] of Object.entries(options.dictionaries ?? {})) {
665
+ if (locale === 'pt-BR' || locale === 'en-US') {
666
+ continue;
667
+ }
668
+ dictionaries[locale] = {
669
+ ...(dictionaries[locale] ?? {}),
670
+ ...dictionary,
671
+ };
672
+ }
673
+ return {
674
+ locale: options.locale,
675
+ fallbackLocale: options.fallbackLocale ?? 'pt-BR',
676
+ dictionaries,
677
+ };
678
+ }
679
+ const METADATA_EDITOR_I18N_CONFIG = createPraxisMetadataEditorI18nConfig();
680
+ function providePraxisMetadataEditorI18n(options = {}) {
681
+ return providePraxisI18n(createPraxisMetadataEditorI18nConfig(options));
682
+ }
683
+ function resolvePraxisMetadataEditorText(i18n, key, fallback) {
684
+ const namespacedKey = key.startsWith('praxis.metadataEditor.')
685
+ ? key
686
+ : `praxis.metadataEditor.${key}`;
687
+ return i18n.t(namespacedKey, undefined, fallback) || fallback;
688
+ }
689
+
690
+ const OPERATOR_TO_JSON_LOGIC = {
691
+ equals: '===',
692
+ notEquals: '!==',
693
+ greaterThan: '>',
694
+ greaterOrEqual: '>=',
695
+ lessThan: '<',
696
+ lessOrEqual: '<=',
697
+ };
698
+ const PRESENTATION_RULE_EFFECT_KEYS = [
699
+ 'presenter',
700
+ 'tone',
701
+ 'appearance',
702
+ 'icon',
703
+ 'label',
704
+ 'badge',
705
+ 'tooltip',
706
+ 'valuePresentation',
707
+ 'interactions',
708
+ ];
709
+ const PRESENTATION_RULE_EFFECT_KEY_SET = new Set(PRESENTATION_RULE_EFFECT_KEYS);
710
+ const GUIDED_PRESENTATION_RULE_EFFECT_KEYS = [
711
+ 'tone',
712
+ 'appearance',
713
+ 'icon',
714
+ 'label',
715
+ 'badge',
716
+ 'tooltip',
717
+ ];
718
+ const GUIDED_PRESENTATION_RULE_EFFECT_KEY_SET = new Set(GUIDED_PRESENTATION_RULE_EFFECT_KEYS);
719
+ class PresentationRulesBuilderComponent {
720
+ i18n = inject(PraxisI18nService);
721
+ rules = [];
722
+ disabled = false;
723
+ nextId = 1;
724
+ operatorOptions = [
725
+ { value: 'equals', label: this.text('presentationRulesBuilder.operator.equals', 'igual a') },
726
+ { value: 'notEquals', label: this.text('presentationRulesBuilder.operator.notEquals', 'diferente de') },
727
+ { value: 'greaterThan', label: this.text('presentationRulesBuilder.operator.greaterThan', 'maior que') },
728
+ { value: 'greaterOrEqual', label: this.text('presentationRulesBuilder.operator.greaterOrEqual', 'maior ou igual a') },
729
+ { value: 'lessThan', label: this.text('presentationRulesBuilder.operator.lessThan', 'menor que') },
730
+ { value: 'lessOrEqual', label: this.text('presentationRulesBuilder.operator.lessOrEqual', 'menor ou igual a') },
731
+ { value: 'contains', label: this.text('presentationRulesBuilder.operator.contains', 'contém') },
732
+ { value: 'exists', label: this.text('presentationRulesBuilder.operator.exists', 'existe') },
733
+ ];
734
+ toneOptions = [
735
+ { value: 'neutral', label: this.text('presentationRulesBuilder.tone.neutral', 'Neutro') },
736
+ { value: 'info', label: this.text('presentationRulesBuilder.tone.info', 'Informação') },
737
+ { value: 'success', label: this.text('presentationRulesBuilder.tone.success', 'Sucesso') },
738
+ { value: 'warning', label: this.text('presentationRulesBuilder.tone.warning', 'Atenção') },
739
+ { value: 'danger', label: this.text('presentationRulesBuilder.tone.danger', 'Crítico') },
740
+ ];
741
+ appearanceOptions = [
742
+ { value: 'plain', label: this.text('presentationRulesBuilder.appearance.plain', 'Simples') },
743
+ { value: 'soft', label: this.text('presentationRulesBuilder.appearance.soft', 'Suave') },
744
+ { value: 'outlined', label: this.text('presentationRulesBuilder.appearance.outlined', 'Contornado') },
745
+ { value: 'filled', label: this.text('presentationRulesBuilder.appearance.filled', 'Preenchido') },
746
+ ];
747
+ onChange = () => { };
748
+ onTouched = () => { };
749
+ onValidatorChange = () => { };
750
+ writeValue(value) {
751
+ const parsed = this.coerceRules(value);
752
+ this.rules = parsed.map((rule) => this.hydrateRule(rule));
753
+ }
754
+ registerOnChange(fn) {
755
+ this.onChange = fn;
756
+ }
757
+ registerOnTouched(fn) {
758
+ this.onTouched = fn;
759
+ }
760
+ registerOnValidatorChange(fn) {
761
+ this.onValidatorChange = fn;
762
+ }
763
+ validate(_control) {
764
+ return this.hasInvalidAdvancedJson()
765
+ ? { presentationRulesInvalidJson: true }
766
+ : null;
767
+ }
768
+ setDisabledState(isDisabled) {
769
+ this.disabled = isDisabled;
770
+ }
771
+ text(key, fallback) {
772
+ return resolvePraxisMetadataEditorText(this.i18n, key, fallback);
773
+ }
774
+ addRule() {
775
+ this.rules = [...this.rules, this.createDraft()];
776
+ this.emit();
777
+ }
778
+ removeRule(index) {
779
+ this.rules = this.rules.filter((_, candidateIndex) => candidateIndex !== index);
780
+ this.emit();
781
+ }
782
+ moveRule(index, delta) {
783
+ const target = index + delta;
784
+ if (target < 0 || target >= this.rules.length) {
785
+ return;
786
+ }
787
+ const next = [...this.rules];
788
+ const [rule] = next.splice(index, 1);
789
+ next.splice(target, 0, rule);
790
+ this.rules = next;
791
+ this.emit();
792
+ }
793
+ setMode(rule, mode) {
794
+ if (rule.mode === mode) {
795
+ return;
796
+ }
797
+ rule.mode = mode;
798
+ if (mode === 'advanced' && !rule.advancedJson) {
799
+ rule.advancedJson = JSON.stringify(this.buildGuidedRule(rule), null, 2);
800
+ }
801
+ this.emit();
802
+ }
803
+ updateRule(rule, key, value) {
804
+ rule[key] = value;
805
+ this.emit();
806
+ }
807
+ updateAdvanced(rule, value) {
808
+ rule.advancedJson = value;
809
+ rule.parseError = this.validateAdvancedJson(value);
810
+ this.emit();
811
+ }
812
+ summarizeRule(rule) {
813
+ if (rule.mode === 'advanced') {
814
+ return this.text('presentationRulesBuilder.summaryAdvanced', 'Regra avançada preservada como JSON.');
815
+ }
816
+ const variable = rule.sourcePath || 'value';
817
+ const operator = this.operatorOptions.find((option) => option.value === rule.operator)?.label ?? rule.operator;
818
+ const value = rule.operator === 'exists' ? '' : ` ${rule.compareValue || '?'}`;
819
+ const effects = this.buildSet(rule);
820
+ const effectCount = Object.keys(effects).length;
821
+ const effectText = effectCount
822
+ ? this.text('presentationRulesBuilder.summaryEffects', '{{count}} efeito(s)').replace('{{count}}', String(effectCount))
823
+ : this.text('presentationRulesBuilder.summaryNoEffects', 'sem efeitos definidos');
824
+ return `${variable} ${operator}${value} -> ${effectText}`;
825
+ }
826
+ emit() {
827
+ this.onTouched();
828
+ this.onValidatorChange();
829
+ if (this.hasInvalidAdvancedJson()) {
830
+ return;
831
+ }
832
+ this.onChange(this.rules.map((rule) => this.toRule(rule)).filter(Boolean));
833
+ }
834
+ hasInvalidAdvancedJson() {
835
+ return this.rules.some((rule) => rule.mode === 'advanced' && !!rule.parseError);
836
+ }
837
+ toRule(rule) {
838
+ if (rule.mode === 'advanced') {
839
+ const text = rule.advancedJson.trim();
840
+ if (!text || rule.parseError) {
841
+ return null;
842
+ }
843
+ const parsed = JSON.parse(text);
844
+ return this.isRecord(parsed) ? parsed : null;
845
+ }
846
+ const set = this.buildSet(rule);
847
+ if (!Object.keys(set).length) {
848
+ return null;
849
+ }
850
+ return this.buildGuidedRule(rule);
851
+ }
852
+ buildGuidedRule(rule) {
853
+ return {
854
+ when: this.buildCondition(rule),
855
+ set: this.buildSet(rule),
856
+ };
857
+ }
858
+ buildCondition(rule) {
859
+ const sourcePath = rule.sourcePath.trim() || 'value';
860
+ const variable = { var: sourcePath };
861
+ if (rule.operator === 'exists') {
862
+ return { '!=': [variable, null] };
863
+ }
864
+ const compareValue = this.parseScalar(rule.compareValue);
865
+ if (rule.operator === 'contains') {
866
+ return { in: [compareValue, variable] };
867
+ }
868
+ return {
869
+ [OPERATOR_TO_JSON_LOGIC[rule.operator]]: [variable, compareValue],
870
+ };
871
+ }
872
+ buildSet(rule) {
873
+ const set = {};
874
+ for (const key of GUIDED_PRESENTATION_RULE_EFFECT_KEYS) {
875
+ const value = String(rule[key] ?? '').trim();
876
+ if (value) {
877
+ set[key] = value;
878
+ }
879
+ }
880
+ return set;
881
+ }
882
+ hydrateRule(rule) {
883
+ const draft = this.createDraft();
884
+ const parsedCondition = this.parseCondition(rule.when);
885
+ const effect = this.resolveRuleEffect(rule);
886
+ if (!parsedCondition ||
887
+ !this.isSupportedGuidedEffectSet(effect) ||
888
+ !this.isRecord(rule.set)) {
889
+ return this.createAdvancedDraft(rule, draft);
890
+ }
891
+ return {
892
+ ...draft,
893
+ sourcePath: parsedCondition.sourcePath,
894
+ operator: parsedCondition.operator,
895
+ compareValue: parsedCondition.compareValue,
896
+ tone: this.stringValue(effect?.['tone']),
897
+ appearance: this.stringValue(effect?.['appearance']),
898
+ icon: this.stringValue(effect?.['icon']),
899
+ label: this.stringValue(effect?.['label']),
900
+ badge: this.stringValue(effect?.['badge']),
901
+ tooltip: this.stringValue(effect?.['tooltip']),
902
+ advancedJson: JSON.stringify(rule, null, 2),
903
+ };
904
+ }
905
+ createAdvancedDraft(rule, draft = this.createDraft()) {
906
+ const advancedJson = JSON.stringify(rule, null, 2);
907
+ return {
908
+ ...draft,
909
+ mode: 'advanced',
910
+ advancedJson,
911
+ parseError: this.validateAdvancedJson(advancedJson),
912
+ };
913
+ }
914
+ validateAdvancedJson(value) {
915
+ const text = value.trim();
916
+ if (!text) {
917
+ return '';
918
+ }
919
+ let parsed;
920
+ try {
921
+ parsed = JSON.parse(text);
922
+ }
923
+ catch {
924
+ return this.text('presentationRulesBuilder.invalidJson', 'JSON inválido. Corrija a regra antes de aplicar.');
925
+ }
926
+ if (!this.isRecord(parsed)) {
927
+ return this.text('presentationRulesBuilder.invalidRuleShape', 'A regra deve ser um objeto com when e set.');
928
+ }
929
+ const unsupportedTopLevelKeys = Object.keys(parsed).filter((key) => key !== 'when' && key !== 'set' && key !== 'effect');
930
+ if (unsupportedTopLevelKeys.length) {
931
+ return this.text('presentationRulesBuilder.invalidRuleShape', 'A regra deve ser um objeto com when e set.');
932
+ }
933
+ const effect = this.resolveRuleEffect(parsed);
934
+ if (!this.isRecord(effect)) {
935
+ return this.text('presentationRulesBuilder.invalidRuleShape', 'A regra deve ser um objeto com when e set.');
936
+ }
937
+ const unsupportedEffectKeys = Object.keys(effect).filter((key) => !PRESENTATION_RULE_EFFECT_KEY_SET.has(key));
938
+ if (unsupportedEffectKeys.length) {
939
+ return this.text('presentationRulesBuilder.unsupportedEffects', 'Use apenas efeitos semânticos de apresentação: presenter, tone, appearance, icon, label, badge, tooltip, valuePresentation e interactions.');
940
+ }
941
+ return '';
942
+ }
943
+ isSupportedEffectSet(set) {
944
+ return this.isRecord(set) && Object.keys(set).every((key) => PRESENTATION_RULE_EFFECT_KEY_SET.has(key));
945
+ }
946
+ isSupportedGuidedEffectSet(set) {
947
+ return this.isRecord(set) && Object.keys(set).every((key) => GUIDED_PRESENTATION_RULE_EFFECT_KEY_SET.has(key));
948
+ }
949
+ resolveRuleEffect(rule) {
950
+ return this.isRecord(rule.set)
951
+ ? rule.set
952
+ : this.isRecord(rule.effect)
953
+ ? rule.effect
954
+ : undefined;
955
+ }
956
+ parseCondition(when) {
957
+ if (!this.isRecord(when)) {
958
+ return null;
959
+ }
960
+ const [operator, operands] = Object.entries(when)[0] ?? [];
961
+ if (!operator || !Array.isArray(operands)) {
962
+ return null;
963
+ }
964
+ if (operator === '!=' && operands.length === 2 && operands[1] === null) {
965
+ const sourcePath = this.parseVar(operands[0]);
966
+ return sourcePath ? { sourcePath, operator: 'exists', compareValue: '' } : null;
967
+ }
968
+ if (operator === 'in' && operands.length === 2) {
969
+ const sourcePath = this.parseVar(operands[1]);
970
+ return sourcePath
971
+ ? { sourcePath, operator: 'contains', compareValue: this.formatScalar(operands[0]) }
972
+ : null;
973
+ }
974
+ const entry = Object.entries(OPERATOR_TO_JSON_LOGIC).find(([, symbol]) => symbol === operator);
975
+ if (!entry || operands.length !== 2) {
976
+ return null;
977
+ }
978
+ const sourcePath = this.parseVar(operands[0]);
979
+ return sourcePath
980
+ ? {
981
+ sourcePath,
982
+ operator: entry[0],
983
+ compareValue: this.formatScalar(operands[1]),
984
+ }
985
+ : null;
986
+ }
987
+ parseVar(value) {
988
+ return this.isRecord(value) && typeof value['var'] === 'string'
989
+ ? value['var']
990
+ : null;
991
+ }
992
+ coerceRules(value) {
993
+ if (Array.isArray(value)) {
994
+ return value.filter((item) => this.isRecord(item));
995
+ }
996
+ if (typeof value === 'string' && value.trim()) {
997
+ try {
998
+ const parsed = JSON.parse(value);
999
+ return Array.isArray(parsed)
1000
+ ? parsed.filter((item) => this.isRecord(item))
1001
+ : [];
1002
+ }
1003
+ catch {
1004
+ return [];
1005
+ }
1006
+ }
1007
+ return [];
1008
+ }
1009
+ createDraft() {
1010
+ return {
1011
+ id: this.nextId++,
1012
+ mode: 'guided',
1013
+ sourcePath: 'value',
1014
+ operator: 'equals',
1015
+ compareValue: '',
1016
+ tone: '',
1017
+ appearance: '',
1018
+ icon: '',
1019
+ label: '',
1020
+ badge: '',
1021
+ tooltip: '',
1022
+ advancedJson: '',
1023
+ parseError: '',
1024
+ };
1025
+ }
1026
+ parseScalar(value) {
1027
+ const trimmed = value.trim();
1028
+ if (trimmed === 'true')
1029
+ return true;
1030
+ if (trimmed === 'false')
1031
+ return false;
1032
+ if (trimmed === 'null')
1033
+ return null;
1034
+ if (trimmed !== '' && !Number.isNaN(Number(trimmed))) {
1035
+ return Number(trimmed);
1036
+ }
1037
+ return trimmed;
1038
+ }
1039
+ formatScalar(value) {
1040
+ if (value === null)
1041
+ return 'null';
1042
+ if (typeof value === 'string')
1043
+ return value;
1044
+ return JSON.stringify(value);
1045
+ }
1046
+ stringValue(value) {
1047
+ return typeof value === 'string' ? value : '';
1048
+ }
1049
+ isRecord(value) {
1050
+ return !!value && typeof value === 'object' && !Array.isArray(value);
1051
+ }
1052
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PresentationRulesBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1053
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PresentationRulesBuilderComponent, isStandalone: true, selector: "praxis-presentation-rules-builder", providers: [
1054
+ {
1055
+ provide: NG_VALUE_ACCESSOR,
1056
+ useExisting: forwardRef(() => PresentationRulesBuilderComponent),
1057
+ multi: true,
1058
+ },
1059
+ {
1060
+ provide: NG_VALIDATORS,
1061
+ useExisting: forwardRef(() => PresentationRulesBuilderComponent),
1062
+ multi: true,
1063
+ },
1064
+ ], ngImport: i0, template: `
1065
+ <section class="pme-rules" [class.is-disabled]="disabled">
1066
+ <div class="pme-rules__header">
1067
+ <div>
1068
+ <h3>{{ text('presentationRulesBuilder.title', 'Regras de apresentação') }}</h3>
1069
+ <p>{{ text('presentationRulesBuilder.description', 'Altere tom, ícone e rótulos sem modificar o valor enviado.') }}</p>
1070
+ </div>
1071
+ <button
1072
+ type="button"
1073
+ mat-stroked-button
1074
+ color="primary"
1075
+ (click)="addRule()"
1076
+ [disabled]="disabled"
1077
+ >
1078
+ <mat-icon [praxisIcon]="'add'"></mat-icon>
1079
+ {{ text('presentationRulesBuilder.add', 'Adicionar regra') }}
1080
+ </button>
1081
+ </div>
1082
+
1083
+ @if (!rules.length) {
1084
+ <div class="pme-rules__empty">
1085
+ {{ text('presentationRulesBuilder.empty', 'Nenhuma regra configurada. Adicione regras para estados como atraso, bloqueio ou aprovação.') }}
1086
+ </div>
1087
+ }
1088
+
1089
+ <div class="pme-rules__list">
1090
+ @for (rule of rules; track rule.id; let index = $index) {
1091
+ <article class="pme-rule">
1092
+ <div class="pme-rule__toolbar">
1093
+ <strong>{{ text('presentationRulesBuilder.rule', 'Regra') }} {{ index + 1 }}</strong>
1094
+ <span class="pme-rule__summary">{{ summarizeRule(rule) }}</span>
1095
+ <span class="pme-rule__spacer"></span>
1096
+ <button
1097
+ type="button"
1098
+ mat-icon-button
1099
+ [matTooltip]="text('presentationRulesBuilder.moveUp', 'Subir')"
1100
+ [attr.aria-label]="text('presentationRulesBuilder.moveUp', 'Subir')"
1101
+ (click)="moveRule(index, -1)"
1102
+ [disabled]="disabled || index === 0"
1103
+ >
1104
+ <mat-icon [praxisIcon]="'arrow_upward'"></mat-icon>
1105
+ </button>
1106
+ <button
1107
+ type="button"
1108
+ mat-icon-button
1109
+ [matTooltip]="text('presentationRulesBuilder.moveDown', 'Descer')"
1110
+ [attr.aria-label]="text('presentationRulesBuilder.moveDown', 'Descer')"
1111
+ (click)="moveRule(index, 1)"
1112
+ [disabled]="disabled || index === rules.length - 1"
1113
+ >
1114
+ <mat-icon [praxisIcon]="'arrow_downward'"></mat-icon>
1115
+ </button>
1116
+ <button
1117
+ type="button"
1118
+ mat-icon-button
1119
+ color="warn"
1120
+ [matTooltip]="text('presentationRulesBuilder.remove', 'Remover')"
1121
+ [attr.aria-label]="text('presentationRulesBuilder.remove', 'Remover')"
1122
+ (click)="removeRule(index)"
1123
+ [disabled]="disabled"
1124
+ >
1125
+ <mat-icon [praxisIcon]="'delete'"></mat-icon>
1126
+ </button>
1127
+ </div>
1128
+
1129
+ <div class="pme-rule__mode" role="group" [attr.aria-label]="text('presentationRulesBuilder.mode', 'Modo da regra')">
1130
+ <button
1131
+ type="button"
1132
+ mat-button
1133
+ [class.is-active]="rule.mode === 'guided'"
1134
+ [attr.aria-pressed]="rule.mode === 'guided'"
1135
+ (click)="setMode(rule, 'guided')"
1136
+ [disabled]="disabled"
1137
+ >
1138
+ {{ text('presentationRulesBuilder.guided', 'Guiado') }}
1139
+ </button>
1140
+ <button
1141
+ type="button"
1142
+ mat-button
1143
+ [class.is-active]="rule.mode === 'advanced'"
1144
+ [attr.aria-pressed]="rule.mode === 'advanced'"
1145
+ (click)="setMode(rule, 'advanced')"
1146
+ [disabled]="disabled"
1147
+ >
1148
+ {{ text('presentationRulesBuilder.advanced', 'JSON avançado') }}
1149
+ </button>
1150
+ </div>
1151
+
1152
+ @if (rule.mode === 'guided') {
1153
+ <div class="pme-rule__grid">
1154
+ <mat-form-field appearance="outline">
1155
+ <mat-label>{{ text('presentationRulesBuilder.sourcePath', 'Variável') }}</mat-label>
1156
+ <input
1157
+ matInput
1158
+ [ngModel]="rule.sourcePath"
1159
+ (ngModelChange)="updateRule(rule, 'sourcePath', $event)"
1160
+ [disabled]="disabled"
1161
+ placeholder="value"
1162
+ />
1163
+ </mat-form-field>
1164
+
1165
+ <mat-form-field appearance="outline">
1166
+ <mat-label>{{ text('presentationRulesBuilder.operator', 'Condição') }}</mat-label>
1167
+ <mat-select
1168
+ [ngModel]="rule.operator"
1169
+ (ngModelChange)="updateRule(rule, 'operator', $event)"
1170
+ [disabled]="disabled"
1171
+ >
1172
+ @for (option of operatorOptions; track option.value) {
1173
+ <mat-option [value]="option.value">{{ option.label }}</mat-option>
1174
+ }
1175
+ </mat-select>
1176
+ </mat-form-field>
1177
+
1178
+ @if (rule.operator !== 'exists') {
1179
+ <mat-form-field appearance="outline">
1180
+ <mat-label>{{ text('presentationRulesBuilder.compareValue', 'Valor') }}</mat-label>
1181
+ <input
1182
+ matInput
1183
+ [ngModel]="rule.compareValue"
1184
+ (ngModelChange)="updateRule(rule, 'compareValue', $event)"
1185
+ [disabled]="disabled"
1186
+ placeholder="BLOQUEADO"
1187
+ />
1188
+ </mat-form-field>
1189
+ }
1190
+ </div>
1191
+
1192
+ <div class="pme-rule__grid pme-rule__grid--effects">
1193
+ <mat-form-field appearance="outline">
1194
+ <mat-label>{{ text('presentationRulesBuilder.tone', 'Tom') }}</mat-label>
1195
+ <mat-select
1196
+ [ngModel]="rule.tone"
1197
+ (ngModelChange)="updateRule(rule, 'tone', $event)"
1198
+ [disabled]="disabled"
1199
+ >
1200
+ <mat-option value="">{{ text('presentationRulesBuilder.keep', 'Manter') }}</mat-option>
1201
+ @for (option of toneOptions; track option.value) {
1202
+ <mat-option [value]="option.value">{{ option.label }}</mat-option>
1203
+ }
1204
+ </mat-select>
1205
+ </mat-form-field>
1206
+
1207
+ <mat-form-field appearance="outline">
1208
+ <mat-label>{{ text('presentationRulesBuilder.appearance', 'Ênfase') }}</mat-label>
1209
+ <mat-select
1210
+ [ngModel]="rule.appearance"
1211
+ (ngModelChange)="updateRule(rule, 'appearance', $event)"
1212
+ [disabled]="disabled"
1213
+ >
1214
+ <mat-option value="">{{ text('presentationRulesBuilder.keep', 'Manter') }}</mat-option>
1215
+ @for (option of appearanceOptions; track option.value) {
1216
+ <mat-option [value]="option.value">{{ option.label }}</mat-option>
1217
+ }
1218
+ </mat-select>
1219
+ </mat-form-field>
1220
+
1221
+ <mat-form-field appearance="outline">
1222
+ <mat-label>{{ text('presentationRulesBuilder.icon', 'Ícone') }}</mat-label>
1223
+ <input
1224
+ matInput
1225
+ [ngModel]="rule.icon"
1226
+ (ngModelChange)="updateRule(rule, 'icon', $event)"
1227
+ [disabled]="disabled"
1228
+ placeholder="lock"
1229
+ />
1230
+ </mat-form-field>
1231
+
1232
+ <mat-form-field appearance="outline">
1233
+ <mat-label>{{ text('presentationRulesBuilder.label', 'Rótulo') }}</mat-label>
1234
+ <input
1235
+ matInput
1236
+ [ngModel]="rule.label"
1237
+ (ngModelChange)="updateRule(rule, 'label', $event)"
1238
+ [disabled]="disabled"
1239
+ placeholder="Bloqueado"
1240
+ />
1241
+ </mat-form-field>
1242
+
1243
+ <mat-form-field appearance="outline">
1244
+ <mat-label>{{ text('presentationRulesBuilder.badge', 'Badge') }}</mat-label>
1245
+ <input
1246
+ matInput
1247
+ [ngModel]="rule.badge"
1248
+ (ngModelChange)="updateRule(rule, 'badge', $event)"
1249
+ [disabled]="disabled"
1250
+ placeholder="SLA"
1251
+ />
1252
+ </mat-form-field>
1253
+
1254
+ <mat-form-field appearance="outline">
1255
+ <mat-label>{{ text('presentationRulesBuilder.tooltip', 'Tooltip') }}</mat-label>
1256
+ <input
1257
+ matInput
1258
+ [ngModel]="rule.tooltip"
1259
+ (ngModelChange)="updateRule(rule, 'tooltip', $event)"
1260
+ [disabled]="disabled"
1261
+ placeholder="Aguardando aprovação fiscal"
1262
+ />
1263
+ </mat-form-field>
1264
+ </div>
1265
+ } @else {
1266
+ <mat-form-field appearance="outline" class="pme-rule__advanced">
1267
+ <mat-label>{{ text('presentationRulesBuilder.advancedRule', 'Regra JSON') }}</mat-label>
1268
+ <textarea
1269
+ matInput
1270
+ rows="8"
1271
+ [ngModel]="rule.advancedJson"
1272
+ (ngModelChange)="updateAdvanced(rule, $event)"
1273
+ [disabled]="disabled"
1274
+ ></textarea>
1275
+ @if (rule.parseError) {
1276
+ <mat-error>{{ rule.parseError }}</mat-error>
1277
+ }
1278
+ </mat-form-field>
1279
+ }
1280
+ </article>
1281
+ }
1282
+ </div>
1283
+ </section>
1284
+ `, isInline: true, styles: [":host{display:block;width:100%}.pme-rules{display:grid;gap:12px;width:100%}.pme-rules.is-disabled{opacity:.72}.pme-rules__header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.pme-rules__header h3{margin:0;color:var(--md-sys-color-on-surface);font-size:.95rem;font-weight:650;line-height:1.3}.pme-rules__header p{margin:3px 0 0;color:var(--md-sys-color-on-surface-variant);font-size:.82rem;line-height:1.35}.pme-rules__empty{border:1px dashed var(--md-sys-color-outline-variant);border-radius:8px;color:var(--md-sys-color-on-surface-variant);padding:12px;font-size:.86rem;line-height:1.4}.pme-rules__list{display:grid;gap:10px}.pme-rule{border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface-container-low);padding:10px}.pme-rule__toolbar{display:flex;align-items:center;gap:6px;min-height:32px}.pme-rule__toolbar strong{color:var(--md-sys-color-on-surface);font-size:.88rem;font-weight:650}.pme-rule__summary{color:var(--md-sys-color-on-surface-variant);font-size:.8rem;line-height:1.3}.pme-rule__spacer{flex:1 1 auto}.pme-rule__mode{display:inline-flex;gap:2px;margin:8px 0 10px;padding:2px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface-container)}.pme-rule__mode button{min-height:30px;line-height:30px}.pme-rule__mode button.is-active{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.pme-rule__grid{display:grid;grid-template-columns:repeat(3,minmax(160px,1fr));gap:10px}.pme-rule__grid--effects{grid-template-columns:repeat(3,minmax(150px,1fr))}.pme-rule__advanced{width:100%}@media(max-width:720px){.pme-rules__header{display:grid}.pme-rules__header button{justify-self:start}.pme-rule__toolbar{flex-wrap:wrap}.pme-rule__summary{flex:1 1 100%}.pme-rule__grid,.pme-rule__grid--effects{grid-template-columns:1fr}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$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: i2$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i6.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }] });
1285
+ }
1286
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PresentationRulesBuilderComponent, decorators: [{
1287
+ type: Component,
1288
+ args: [{ selector: 'praxis-presentation-rules-builder', standalone: true, imports: [
1289
+ CommonModule,
1290
+ FormsModule,
1291
+ MatButtonModule,
1292
+ MatFormFieldModule,
1293
+ MatIconModule,
1294
+ MatInputModule,
1295
+ MatSelectModule,
1296
+ MatTooltipModule,
1297
+ PraxisIconDirective,
1298
+ ], providers: [
1299
+ {
1300
+ provide: NG_VALUE_ACCESSOR,
1301
+ useExisting: forwardRef(() => PresentationRulesBuilderComponent),
1302
+ multi: true,
1303
+ },
1304
+ {
1305
+ provide: NG_VALIDATORS,
1306
+ useExisting: forwardRef(() => PresentationRulesBuilderComponent),
1307
+ multi: true,
1308
+ },
1309
+ ], template: `
1310
+ <section class="pme-rules" [class.is-disabled]="disabled">
1311
+ <div class="pme-rules__header">
1312
+ <div>
1313
+ <h3>{{ text('presentationRulesBuilder.title', 'Regras de apresentação') }}</h3>
1314
+ <p>{{ text('presentationRulesBuilder.description', 'Altere tom, ícone e rótulos sem modificar o valor enviado.') }}</p>
1315
+ </div>
1316
+ <button
1317
+ type="button"
1318
+ mat-stroked-button
1319
+ color="primary"
1320
+ (click)="addRule()"
1321
+ [disabled]="disabled"
1322
+ >
1323
+ <mat-icon [praxisIcon]="'add'"></mat-icon>
1324
+ {{ text('presentationRulesBuilder.add', 'Adicionar regra') }}
1325
+ </button>
1326
+ </div>
1327
+
1328
+ @if (!rules.length) {
1329
+ <div class="pme-rules__empty">
1330
+ {{ text('presentationRulesBuilder.empty', 'Nenhuma regra configurada. Adicione regras para estados como atraso, bloqueio ou aprovação.') }}
1331
+ </div>
1332
+ }
1333
+
1334
+ <div class="pme-rules__list">
1335
+ @for (rule of rules; track rule.id; let index = $index) {
1336
+ <article class="pme-rule">
1337
+ <div class="pme-rule__toolbar">
1338
+ <strong>{{ text('presentationRulesBuilder.rule', 'Regra') }} {{ index + 1 }}</strong>
1339
+ <span class="pme-rule__summary">{{ summarizeRule(rule) }}</span>
1340
+ <span class="pme-rule__spacer"></span>
1341
+ <button
1342
+ type="button"
1343
+ mat-icon-button
1344
+ [matTooltip]="text('presentationRulesBuilder.moveUp', 'Subir')"
1345
+ [attr.aria-label]="text('presentationRulesBuilder.moveUp', 'Subir')"
1346
+ (click)="moveRule(index, -1)"
1347
+ [disabled]="disabled || index === 0"
1348
+ >
1349
+ <mat-icon [praxisIcon]="'arrow_upward'"></mat-icon>
1350
+ </button>
1351
+ <button
1352
+ type="button"
1353
+ mat-icon-button
1354
+ [matTooltip]="text('presentationRulesBuilder.moveDown', 'Descer')"
1355
+ [attr.aria-label]="text('presentationRulesBuilder.moveDown', 'Descer')"
1356
+ (click)="moveRule(index, 1)"
1357
+ [disabled]="disabled || index === rules.length - 1"
1358
+ >
1359
+ <mat-icon [praxisIcon]="'arrow_downward'"></mat-icon>
1360
+ </button>
1361
+ <button
1362
+ type="button"
1363
+ mat-icon-button
1364
+ color="warn"
1365
+ [matTooltip]="text('presentationRulesBuilder.remove', 'Remover')"
1366
+ [attr.aria-label]="text('presentationRulesBuilder.remove', 'Remover')"
1367
+ (click)="removeRule(index)"
1368
+ [disabled]="disabled"
1369
+ >
1370
+ <mat-icon [praxisIcon]="'delete'"></mat-icon>
1371
+ </button>
1372
+ </div>
1373
+
1374
+ <div class="pme-rule__mode" role="group" [attr.aria-label]="text('presentationRulesBuilder.mode', 'Modo da regra')">
1375
+ <button
1376
+ type="button"
1377
+ mat-button
1378
+ [class.is-active]="rule.mode === 'guided'"
1379
+ [attr.aria-pressed]="rule.mode === 'guided'"
1380
+ (click)="setMode(rule, 'guided')"
1381
+ [disabled]="disabled"
1382
+ >
1383
+ {{ text('presentationRulesBuilder.guided', 'Guiado') }}
1384
+ </button>
1385
+ <button
1386
+ type="button"
1387
+ mat-button
1388
+ [class.is-active]="rule.mode === 'advanced'"
1389
+ [attr.aria-pressed]="rule.mode === 'advanced'"
1390
+ (click)="setMode(rule, 'advanced')"
1391
+ [disabled]="disabled"
1392
+ >
1393
+ {{ text('presentationRulesBuilder.advanced', 'JSON avançado') }}
1394
+ </button>
1395
+ </div>
1396
+
1397
+ @if (rule.mode === 'guided') {
1398
+ <div class="pme-rule__grid">
1399
+ <mat-form-field appearance="outline">
1400
+ <mat-label>{{ text('presentationRulesBuilder.sourcePath', 'Variável') }}</mat-label>
1401
+ <input
1402
+ matInput
1403
+ [ngModel]="rule.sourcePath"
1404
+ (ngModelChange)="updateRule(rule, 'sourcePath', $event)"
1405
+ [disabled]="disabled"
1406
+ placeholder="value"
1407
+ />
1408
+ </mat-form-field>
1409
+
1410
+ <mat-form-field appearance="outline">
1411
+ <mat-label>{{ text('presentationRulesBuilder.operator', 'Condição') }}</mat-label>
1412
+ <mat-select
1413
+ [ngModel]="rule.operator"
1414
+ (ngModelChange)="updateRule(rule, 'operator', $event)"
1415
+ [disabled]="disabled"
1416
+ >
1417
+ @for (option of operatorOptions; track option.value) {
1418
+ <mat-option [value]="option.value">{{ option.label }}</mat-option>
1419
+ }
1420
+ </mat-select>
1421
+ </mat-form-field>
1422
+
1423
+ @if (rule.operator !== 'exists') {
1424
+ <mat-form-field appearance="outline">
1425
+ <mat-label>{{ text('presentationRulesBuilder.compareValue', 'Valor') }}</mat-label>
1426
+ <input
1427
+ matInput
1428
+ [ngModel]="rule.compareValue"
1429
+ (ngModelChange)="updateRule(rule, 'compareValue', $event)"
1430
+ [disabled]="disabled"
1431
+ placeholder="BLOQUEADO"
1432
+ />
1433
+ </mat-form-field>
1434
+ }
1435
+ </div>
1436
+
1437
+ <div class="pme-rule__grid pme-rule__grid--effects">
1438
+ <mat-form-field appearance="outline">
1439
+ <mat-label>{{ text('presentationRulesBuilder.tone', 'Tom') }}</mat-label>
1440
+ <mat-select
1441
+ [ngModel]="rule.tone"
1442
+ (ngModelChange)="updateRule(rule, 'tone', $event)"
1443
+ [disabled]="disabled"
1444
+ >
1445
+ <mat-option value="">{{ text('presentationRulesBuilder.keep', 'Manter') }}</mat-option>
1446
+ @for (option of toneOptions; track option.value) {
1447
+ <mat-option [value]="option.value">{{ option.label }}</mat-option>
1448
+ }
1449
+ </mat-select>
1450
+ </mat-form-field>
1451
+
1452
+ <mat-form-field appearance="outline">
1453
+ <mat-label>{{ text('presentationRulesBuilder.appearance', 'Ênfase') }}</mat-label>
1454
+ <mat-select
1455
+ [ngModel]="rule.appearance"
1456
+ (ngModelChange)="updateRule(rule, 'appearance', $event)"
1457
+ [disabled]="disabled"
1458
+ >
1459
+ <mat-option value="">{{ text('presentationRulesBuilder.keep', 'Manter') }}</mat-option>
1460
+ @for (option of appearanceOptions; track option.value) {
1461
+ <mat-option [value]="option.value">{{ option.label }}</mat-option>
1462
+ }
1463
+ </mat-select>
1464
+ </mat-form-field>
1465
+
1466
+ <mat-form-field appearance="outline">
1467
+ <mat-label>{{ text('presentationRulesBuilder.icon', 'Ícone') }}</mat-label>
1468
+ <input
1469
+ matInput
1470
+ [ngModel]="rule.icon"
1471
+ (ngModelChange)="updateRule(rule, 'icon', $event)"
1472
+ [disabled]="disabled"
1473
+ placeholder="lock"
1474
+ />
1475
+ </mat-form-field>
1476
+
1477
+ <mat-form-field appearance="outline">
1478
+ <mat-label>{{ text('presentationRulesBuilder.label', 'Rótulo') }}</mat-label>
1479
+ <input
1480
+ matInput
1481
+ [ngModel]="rule.label"
1482
+ (ngModelChange)="updateRule(rule, 'label', $event)"
1483
+ [disabled]="disabled"
1484
+ placeholder="Bloqueado"
1485
+ />
1486
+ </mat-form-field>
1487
+
1488
+ <mat-form-field appearance="outline">
1489
+ <mat-label>{{ text('presentationRulesBuilder.badge', 'Badge') }}</mat-label>
1490
+ <input
1491
+ matInput
1492
+ [ngModel]="rule.badge"
1493
+ (ngModelChange)="updateRule(rule, 'badge', $event)"
1494
+ [disabled]="disabled"
1495
+ placeholder="SLA"
1496
+ />
1497
+ </mat-form-field>
1498
+
1499
+ <mat-form-field appearance="outline">
1500
+ <mat-label>{{ text('presentationRulesBuilder.tooltip', 'Tooltip') }}</mat-label>
1501
+ <input
1502
+ matInput
1503
+ [ngModel]="rule.tooltip"
1504
+ (ngModelChange)="updateRule(rule, 'tooltip', $event)"
1505
+ [disabled]="disabled"
1506
+ placeholder="Aguardando aprovação fiscal"
1507
+ />
1508
+ </mat-form-field>
1509
+ </div>
1510
+ } @else {
1511
+ <mat-form-field appearance="outline" class="pme-rule__advanced">
1512
+ <mat-label>{{ text('presentationRulesBuilder.advancedRule', 'Regra JSON') }}</mat-label>
1513
+ <textarea
1514
+ matInput
1515
+ rows="8"
1516
+ [ngModel]="rule.advancedJson"
1517
+ (ngModelChange)="updateAdvanced(rule, $event)"
1518
+ [disabled]="disabled"
1519
+ ></textarea>
1520
+ @if (rule.parseError) {
1521
+ <mat-error>{{ rule.parseError }}</mat-error>
1522
+ }
1523
+ </mat-form-field>
1524
+ }
1525
+ </article>
1526
+ }
1527
+ </div>
1528
+ </section>
1529
+ `, styles: [":host{display:block;width:100%}.pme-rules{display:grid;gap:12px;width:100%}.pme-rules.is-disabled{opacity:.72}.pme-rules__header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.pme-rules__header h3{margin:0;color:var(--md-sys-color-on-surface);font-size:.95rem;font-weight:650;line-height:1.3}.pme-rules__header p{margin:3px 0 0;color:var(--md-sys-color-on-surface-variant);font-size:.82rem;line-height:1.35}.pme-rules__empty{border:1px dashed var(--md-sys-color-outline-variant);border-radius:8px;color:var(--md-sys-color-on-surface-variant);padding:12px;font-size:.86rem;line-height:1.4}.pme-rules__list{display:grid;gap:10px}.pme-rule{border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface-container-low);padding:10px}.pme-rule__toolbar{display:flex;align-items:center;gap:6px;min-height:32px}.pme-rule__toolbar strong{color:var(--md-sys-color-on-surface);font-size:.88rem;font-weight:650}.pme-rule__summary{color:var(--md-sys-color-on-surface-variant);font-size:.8rem;line-height:1.3}.pme-rule__spacer{flex:1 1 auto}.pme-rule__mode{display:inline-flex;gap:2px;margin:8px 0 10px;padding:2px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface-container)}.pme-rule__mode button{min-height:30px;line-height:30px}.pme-rule__mode button.is-active{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.pme-rule__grid{display:grid;grid-template-columns:repeat(3,minmax(160px,1fr));gap:10px}.pme-rule__grid--effects{grid-template-columns:repeat(3,minmax(150px,1fr))}.pme-rule__advanced{width:100%}@media(max-width:720px){.pme-rules__header{display:grid}.pme-rules__header button{justify-self:start}.pme-rule__toolbar{flex-wrap:wrap}.pme-rule__summary{flex:1 1 100%}.pme-rule__grid,.pme-rule__grid--effects{grid-template-columns:1fr}}\n"] }]
1530
+ }] });
1531
+
1532
+ const PRESENTER_ICONS = {
1533
+ text: 'text_fields',
1534
+ status: 'radio_button_checked',
1535
+ chip: 'label',
1536
+ badge: 'new_releases',
1537
+ iconValue: 'add_reaction',
1538
+ progress: 'progress_activity',
1539
+ rating: 'star',
1540
+ };
1541
+ const APPEARANCE_ICONS = {
1542
+ plain: 'horizontal_rule',
1543
+ soft: 'tonality',
1544
+ outlined: 'crop_square',
1545
+ filled: 'stop',
1546
+ };
1547
+ class PresentationChoiceControlComponent {
1548
+ i18n = inject(PraxisI18nService);
1549
+ elementRef = inject(ElementRef);
1550
+ field;
1551
+ value = '';
1552
+ disabled = false;
1553
+ onChange = () => { };
1554
+ onTouched = () => { };
1555
+ get kind() {
1556
+ const name = this.field?.name ?? '';
1557
+ if (name.endsWith('.tone'))
1558
+ return 'tone';
1559
+ if (name.endsWith('.appearance'))
1560
+ return 'appearance';
1561
+ return 'presenter';
1562
+ }
1563
+ get options() {
1564
+ return Array.isArray(this.field?.options)
1565
+ ? this.field?.options
1566
+ : [];
1567
+ }
1568
+ writeValue(value) {
1569
+ this.value = typeof value === 'string' ? value : '';
1570
+ }
1571
+ registerOnChange(fn) {
1572
+ this.onChange = fn;
1573
+ }
1574
+ registerOnTouched(fn) {
1575
+ this.onTouched = fn;
1576
+ }
1577
+ setDisabledState(isDisabled) {
1578
+ this.disabled = isDisabled;
1579
+ }
1580
+ select(nextValue) {
1581
+ if (this.disabled) {
1582
+ return;
1583
+ }
1584
+ this.value = nextValue;
1585
+ this.onTouched();
1586
+ this.onChange(nextValue);
1587
+ }
1588
+ clear() {
1589
+ if (this.disabled) {
1590
+ return;
1591
+ }
1592
+ this.value = '';
1593
+ this.onTouched();
1594
+ this.onChange(null);
1595
+ }
1596
+ tabIndexFor(optionValue) {
1597
+ if (this.disabled) {
1598
+ return -1;
1599
+ }
1600
+ if (this.value) {
1601
+ return this.value === optionValue ? 0 : -1;
1602
+ }
1603
+ return this.options[0]?.value === optionValue ? 0 : -1;
1604
+ }
1605
+ onOptionKeydown(event, optionValue) {
1606
+ if (this.disabled) {
1607
+ return;
1608
+ }
1609
+ const key = event.key;
1610
+ if (key === ' ' || key === 'Enter') {
1611
+ event.preventDefault();
1612
+ this.select(optionValue);
1613
+ return;
1614
+ }
1615
+ const direction = key === 'ArrowRight' || key === 'ArrowDown'
1616
+ ? 1
1617
+ : key === 'ArrowLeft' || key === 'ArrowUp'
1618
+ ? -1
1619
+ : 0;
1620
+ if (direction) {
1621
+ event.preventDefault();
1622
+ this.focusAndSelectRelative(optionValue, direction);
1623
+ return;
1624
+ }
1625
+ if (key === 'Home' || key === 'End') {
1626
+ event.preventDefault();
1627
+ this.focusAndSelectIndex(key === 'Home' ? 0 : this.options.length - 1);
1628
+ }
1629
+ }
1630
+ focusAndSelectRelative(currentValue, delta) {
1631
+ const options = this.options;
1632
+ if (!options.length) {
1633
+ return;
1634
+ }
1635
+ const currentIndex = Math.max(0, options.findIndex((option) => option.value === currentValue));
1636
+ const nextIndex = (currentIndex + delta + options.length) % options.length;
1637
+ this.focusAndSelectIndex(nextIndex);
1638
+ }
1639
+ focusAndSelectIndex(index) {
1640
+ const option = this.options[index];
1641
+ if (!option) {
1642
+ return;
1643
+ }
1644
+ this.select(option.value);
1645
+ queueMicrotask(() => {
1646
+ const selector = `.pme-choice__option[role="radio"][data-choice-value="${CSS.escape(option.value)}"]`;
1647
+ this.elementRef.nativeElement.querySelector(selector)?.focus();
1648
+ });
1649
+ }
1650
+ iconFor(optionValue) {
1651
+ return this.kind === 'appearance'
1652
+ ? APPEARANCE_ICONS[optionValue] ?? 'radio_button_unchecked'
1653
+ : PRESENTER_ICONS[optionValue] ?? 'radio_button_unchecked';
1654
+ }
1655
+ text(key, fallback) {
1656
+ return resolvePraxisMetadataEditorText(this.i18n, key, fallback);
1657
+ }
1658
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PresentationChoiceControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1659
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PresentationChoiceControlComponent, isStandalone: true, selector: "praxis-presentation-choice-control", inputs: { field: "field" }, providers: [
1660
+ {
1661
+ provide: NG_VALUE_ACCESSOR,
1662
+ useExisting: forwardRef(() => PresentationChoiceControlComponent),
1663
+ multi: true,
1664
+ },
1665
+ ], ngImport: i0, template: `
1666
+ <section class="pme-choice" [class.is-disabled]="disabled">
1667
+ <div class="pme-choice__header">
1668
+ <span class="pme-choice__label">{{ field?.label }}</span>
1669
+ @if (field?.hint) {
1670
+ <button
1671
+ type="button"
1672
+ mat-icon-button
1673
+ class="pme-choice__help"
1674
+ [matTooltip]="field?.hint || ''"
1675
+ [attr.aria-label]="text('presentationChoice.help', 'Ajuda')"
1676
+ [disabled]="disabled"
1677
+ >
1678
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
1679
+ </button>
1680
+ }
1681
+ </div>
1682
+
1683
+ <div
1684
+ class="pme-choice__options"
1685
+ [class.pme-choice__options--tone]="kind === 'tone'"
1686
+ role="radiogroup"
1687
+ [attr.aria-label]="field?.label || ''"
1688
+ >
1689
+ @for (option of options; track option.value) {
1690
+ <button
1691
+ type="button"
1692
+ class="pme-choice__option"
1693
+ [class.is-selected]="value === option.value"
1694
+ [class.is-tone]="kind === 'tone'"
1695
+ [attr.data-tone]="kind === 'tone' ? option.value : null"
1696
+ [attr.data-choice-value]="option.value"
1697
+ [attr.aria-checked]="value === option.value"
1698
+ [attr.tabindex]="tabIndexFor(option.value)"
1699
+ role="radio"
1700
+ [disabled]="disabled"
1701
+ (click)="select(option.value)"
1702
+ (keydown)="onOptionKeydown($event, option.value)"
1703
+ >
1704
+ @if (kind === 'tone') {
1705
+ <span class="pme-choice__swatch" aria-hidden="true"></span>
1706
+ } @else {
1707
+ <mat-icon [praxisIcon]="iconFor(option.value)"></mat-icon>
1708
+ }
1709
+ <span>{{ option.text }}</span>
1710
+ </button>
1711
+ }
1712
+ </div>
1713
+
1714
+ @if (value) {
1715
+ <button
1716
+ type="button"
1717
+ mat-button
1718
+ class="pme-choice__clear"
1719
+ [disabled]="disabled"
1720
+ (click)="clear()"
1721
+ >
1722
+ <mat-icon [praxisIcon]="'close'"></mat-icon>
1723
+ {{ text('presentationChoice.clear', 'Limpar seleção') }}
1724
+ </button>
1725
+ }
1726
+ </section>
1727
+ `, isInline: true, styles: [":host{display:block;min-width:0;width:100%}.pme-choice{display:grid;gap:8px}.pme-choice.is-disabled{opacity:.7}.pme-choice__header{display:flex;align-items:center;gap:4px;min-height:28px}.pme-choice__label{color:var(--md-sys-color-on-surface);font-size:.88rem;font-weight:650;line-height:1.25}.pme-choice__help{width:28px;height:28px;min-width:28px;padding:0;color:var(--md-sys-color-primary)}.pme-choice__options{display:flex;flex-wrap:wrap;gap:6px}.pme-choice__option{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:34px;padding:0 10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant);cursor:pointer;font:inherit;line-height:1.2}.pme-choice__option:hover:not(:disabled){border-color:var(--md-sys-color-primary);color:var(--md-sys-color-on-surface)}.pme-choice__option:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.pme-choice__option.is-selected{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.pme-choice__option mat-icon{font-size:18px;width:18px;height:18px}.pme-choice__option.is-tone{padding-left:8px}.pme-choice__swatch{width:14px;height:14px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-highest)}.pme-choice__option[data-tone=info] .pme-choice__swatch{background:var(--md-sys-color-primary-container);border-color:var(--md-sys-color-primary)}.pme-choice__option[data-tone=success] .pme-choice__swatch{background:var(--md-sys-color-tertiary-container);border-color:var(--md-sys-color-tertiary)}.pme-choice__option[data-tone=warning] .pme-choice__swatch{background:var(--md-sys-color-secondary-container);border-color:var(--md-sys-color-secondary)}.pme-choice__option[data-tone=danger] .pme-choice__swatch{background:var(--md-sys-color-error-container);border-color:var(--md-sys-color-error)}.pme-choice__clear{justify-self:start;min-height:30px;padding:0 8px;color:var(--md-sys-color-on-surface-variant)}.pme-choice__clear mat-icon{font-size:16px;width:16px;height:16px;margin-right:4px}@media(max-width:520px){.pme-choice__options{display:grid;grid-template-columns:1fr}.pme-choice__option{justify-content:flex-start;width:100%}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$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: i2$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }] });
375
1728
  }
376
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: RegexHelpDialogComponent, decorators: [{
1729
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PresentationChoiceControlComponent, decorators: [{
377
1730
  type: Component,
378
- args: [{
379
- selector: 'praxis-regex-help-dialog',
380
- standalone: true,
381
- imports: [MatDialogModule, MatListModule, MatButtonModule],
382
- template: `
383
- <h2 mat-dialog-title>Modelos de Expressões Regulares</h2>
384
- <div mat-dialog-content>
385
- <p>Selecione um dos modelos abaixo para preencher o campo ou use como base:</p>
386
- <mat-nav-list dense>
387
- @for (r of presets; track r.pattern) {
388
- <a mat-list-item (click)="choose(r.pattern)" style="cursor: pointer;">
389
- <span matListItemTitle><code>{{ r.pattern }}</code></span>
390
- <span matListItemLine>{{ r.label }}</span>
391
- </a>
1731
+ args: [{ selector: 'praxis-presentation-choice-control', standalone: true, imports: [
1732
+ CommonModule,
1733
+ MatButtonModule,
1734
+ MatIconModule,
1735
+ MatTooltipModule,
1736
+ PraxisIconDirective,
1737
+ ], providers: [
1738
+ {
1739
+ provide: NG_VALUE_ACCESSOR,
1740
+ useExisting: forwardRef(() => PresentationChoiceControlComponent),
1741
+ multi: true,
1742
+ },
1743
+ ], template: `
1744
+ <section class="pme-choice" [class.is-disabled]="disabled">
1745
+ <div class="pme-choice__header">
1746
+ <span class="pme-choice__label">{{ field?.label }}</span>
1747
+ @if (field?.hint) {
1748
+ <button
1749
+ type="button"
1750
+ mat-icon-button
1751
+ class="pme-choice__help"
1752
+ [matTooltip]="field?.hint || ''"
1753
+ [attr.aria-label]="text('presentationChoice.help', 'Ajuda')"
1754
+ [disabled]="disabled"
1755
+ >
1756
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
1757
+ </button>
392
1758
  }
393
- </mat-nav-list>
394
- <p style="margin-top: 12px;">Recursos úteis:</p>
395
- <ul>
396
- <li><a href="https://regex101.com/" target="_blank" rel="noreferrer noopener">regex101.com</a> — Teste e depure regex</li>
397
- <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>
398
- </ul>
399
- </div>
400
- <div mat-dialog-actions>
401
- <button class="mat-mdc-button mdc-button" mat-dialog-close>Fechar</button>
402
- </div>
403
- `,
404
- }]
405
- }] });
1759
+ </div>
1760
+
1761
+ <div
1762
+ class="pme-choice__options"
1763
+ [class.pme-choice__options--tone]="kind === 'tone'"
1764
+ role="radiogroup"
1765
+ [attr.aria-label]="field?.label || ''"
1766
+ >
1767
+ @for (option of options; track option.value) {
1768
+ <button
1769
+ type="button"
1770
+ class="pme-choice__option"
1771
+ [class.is-selected]="value === option.value"
1772
+ [class.is-tone]="kind === 'tone'"
1773
+ [attr.data-tone]="kind === 'tone' ? option.value : null"
1774
+ [attr.data-choice-value]="option.value"
1775
+ [attr.aria-checked]="value === option.value"
1776
+ [attr.tabindex]="tabIndexFor(option.value)"
1777
+ role="radio"
1778
+ [disabled]="disabled"
1779
+ (click)="select(option.value)"
1780
+ (keydown)="onOptionKeydown($event, option.value)"
1781
+ >
1782
+ @if (kind === 'tone') {
1783
+ <span class="pme-choice__swatch" aria-hidden="true"></span>
1784
+ } @else {
1785
+ <mat-icon [praxisIcon]="iconFor(option.value)"></mat-icon>
1786
+ }
1787
+ <span>{{ option.text }}</span>
1788
+ </button>
1789
+ }
1790
+ </div>
1791
+
1792
+ @if (value) {
1793
+ <button
1794
+ type="button"
1795
+ mat-button
1796
+ class="pme-choice__clear"
1797
+ [disabled]="disabled"
1798
+ (click)="clear()"
1799
+ >
1800
+ <mat-icon [praxisIcon]="'close'"></mat-icon>
1801
+ {{ text('presentationChoice.clear', 'Limpar seleção') }}
1802
+ </button>
1803
+ }
1804
+ </section>
1805
+ `, styles: [":host{display:block;min-width:0;width:100%}.pme-choice{display:grid;gap:8px}.pme-choice.is-disabled{opacity:.7}.pme-choice__header{display:flex;align-items:center;gap:4px;min-height:28px}.pme-choice__label{color:var(--md-sys-color-on-surface);font-size:.88rem;font-weight:650;line-height:1.25}.pme-choice__help{width:28px;height:28px;min-width:28px;padding:0;color:var(--md-sys-color-primary)}.pme-choice__options{display:flex;flex-wrap:wrap;gap:6px}.pme-choice__option{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:34px;padding:0 10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant);cursor:pointer;font:inherit;line-height:1.2}.pme-choice__option:hover:not(:disabled){border-color:var(--md-sys-color-primary);color:var(--md-sys-color-on-surface)}.pme-choice__option:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.pme-choice__option.is-selected{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.pme-choice__option mat-icon{font-size:18px;width:18px;height:18px}.pme-choice__option.is-tone{padding-left:8px}.pme-choice__swatch{width:14px;height:14px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-highest)}.pme-choice__option[data-tone=info] .pme-choice__swatch{background:var(--md-sys-color-primary-container);border-color:var(--md-sys-color-primary)}.pme-choice__option[data-tone=success] .pme-choice__swatch{background:var(--md-sys-color-tertiary-container);border-color:var(--md-sys-color-tertiary)}.pme-choice__option[data-tone=warning] .pme-choice__swatch{background:var(--md-sys-color-secondary-container);border-color:var(--md-sys-color-secondary)}.pme-choice__option[data-tone=danger] .pme-choice__swatch{background:var(--md-sys-color-error-container);border-color:var(--md-sys-color-error)}.pme-choice__clear{justify-self:start;min-height:30px;padding:0 8px;color:var(--md-sys-color-on-surface-variant)}.pme-choice__clear mat-icon{font-size:16px;width:16px;height:16px;margin-right:4px}@media(max-width:520px){.pme-choice__options{display:grid;grid-template-columns:1fr}.pme-choice__option{justify-content:flex-start;width:100%}}\n"] }]
1806
+ }], propDecorators: { field: [{
1807
+ type: Input
1808
+ }] } });
406
1809
 
407
1810
  class DynamicEditorRendererComponent {
408
1811
  dialog;
409
1812
  iconPicker;
1813
+ i18n;
410
1814
  properties = [];
411
1815
  form;
412
1816
  fields = [];
@@ -448,29 +1852,32 @@ class DynamicEditorRendererComponent {
448
1852
  }
449
1853
  toFieldMetadata(p) {
450
1854
  const controlType = this.mapEditorType(p.editorType);
1855
+ const label = this.resolveEditorText(p.labelKey, p.label);
1856
+ const placeholder = this.resolveEditorText(p.placeholderKey, p.placeholder);
1857
+ const hint = this.resolveEditorText(p.hintKey, p.hint);
451
1858
  const meta = {
452
1859
  name: p.name,
453
- label: p.label,
1860
+ label: label ?? p.label,
454
1861
  controlType,
455
- placeholder: p.placeholder,
1862
+ placeholder,
456
1863
  // Ajuda contextual padrão quando ausente
457
- hint: p.hint,
1864
+ hint,
458
1865
  // Help suffix dentro do campo (padrao) - somente para campos compatíveis
459
- suffixIcon: p.hint && !this.isIconField(p.name) && this.isSuffixCapable(p.editorType)
1866
+ suffixIcon: hint && !this.isIconField(p.name) && this.isSuffixCapable(p.editorType)
460
1867
  ? 'help_outline'
461
1868
  : undefined,
462
- suffixIconTooltip: p.hint && !this.isIconField(p.name) && this.isSuffixCapable(p.editorType)
463
- ? p.hint
1869
+ suffixIconTooltip: hint && !this.isIconField(p.name) && this.isSuffixCapable(p.editorType)
1870
+ ? hint
464
1871
  : undefined,
465
- suffixIconAriaLabel: p.hint && !this.isIconField(p.name) && this.isSuffixCapable(p.editorType)
466
- ? 'Ajuda'
1872
+ suffixIconAriaLabel: hint && !this.isIconField(p.name) && this.isSuffixCapable(p.editorType)
1873
+ ? this.resolveEditorText('common.help', 'Ajuda')
467
1874
  : undefined,
468
- options: Array.isArray(p.options) ? p.options : undefined,
1875
+ options: this.resolveOptions(p.options),
469
1876
  required: p.required,
470
1877
  searchable: p.searchable,
471
- searchPlaceholder: p.searchPlaceholder,
1878
+ searchPlaceholder: this.resolveEditorText(p.searchPlaceholderKey, p.searchPlaceholder),
472
1879
  // meta auxiliar de agrupamento (não exportado)
473
- _group: p.group || 'Geral',
1880
+ _group: this.resolveEditorText(p.groupKey, p.group || 'Geral'),
474
1881
  _inline: p.inline || false,
475
1882
  _row: p.row || undefined,
476
1883
  _editorType: p.editorType,
@@ -495,7 +1902,7 @@ class DynamicEditorRendererComponent {
495
1902
  }
496
1903
  // Apenas campos de ícone devem receber o hint de Material Symbols
497
1904
  if (this.isIconField(p.name) && !meta.hint) {
498
- meta.hint = 'Use o nome do ícone (Material Symbols)';
1905
+ meta.hint = this.resolveEditorText('common.iconMaterialSymbolsHint', 'Use o nome do ícone (Material Symbols)');
499
1906
  }
500
1907
  // Booleans em linha (switches) para melhor densidade
501
1908
  const booleanLike = new Set([
@@ -549,6 +1956,10 @@ class DynamicEditorRendererComponent {
549
1956
  }
550
1957
  // (preview removed by request)
551
1958
  rowClass(row) {
1959
+ if (this.isPresentationRulesRow(row))
1960
+ return 'row-presentation-rules';
1961
+ if (this.isPresentationChoiceRow(row))
1962
+ return 'row-presentation-choice';
552
1963
  // Linha compacta (toggles) quando todos são inline
553
1964
  if (row.every((f) => !!f._inline))
554
1965
  return 'row-inline';
@@ -560,6 +1971,28 @@ class DynamicEditorRendererComponent {
560
1971
  return 'row-3';
561
1972
  return '';
562
1973
  }
1974
+ isPresentationRulesRow(row) {
1975
+ return row.length > 0 && row.every((field) => field._editorType === 'presentation-rules-builder');
1976
+ }
1977
+ isPresentationChoiceRow(row) {
1978
+ return row.length > 0 && row.every((field) => field._editorType === 'presentation-choice');
1979
+ }
1980
+ canRenderPresentationRulesRow(row) {
1981
+ return this.ready && this.isPresentationRulesRow(row) && this.hasControlsForRow(row);
1982
+ }
1983
+ canRenderPresentationChoiceRow(row) {
1984
+ return this.ready && this.isPresentationChoiceRow(row) && this.hasControlsForRow(row);
1985
+ }
1986
+ hasControlsForRow(row) {
1987
+ return row.every((field) => !!this.form?.get(field.name));
1988
+ }
1989
+ controlFor(path) {
1990
+ const control = this.form.get(path);
1991
+ if (!control) {
1992
+ throw new Error(`Missing editor control for ${path}`);
1993
+ }
1994
+ return control;
1995
+ }
563
1996
  mapEditorType(t) {
564
1997
  switch (t) {
565
1998
  case 'text':
@@ -635,9 +2068,10 @@ class DynamicEditorRendererComponent {
635
2068
  return 'visibility';
636
2069
  return 'settings';
637
2070
  }
638
- constructor(dialog, iconPicker) {
2071
+ constructor(dialog, iconPicker, i18n) {
639
2072
  this.dialog = dialog;
640
2073
  this.iconPicker = iconPicker;
2074
+ this.i18n = i18n;
641
2075
  }
642
2076
  isIconField(path) {
643
2077
  if (!path)
@@ -686,7 +2120,22 @@ class DynamicEditorRendererComponent {
686
2120
  this.form?.markAsDirty();
687
2121
  }
688
2122
  }
689
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicEditorRendererComponent, deps: [{ token: i1$1.MatDialog }, { token: i2$1.IconPickerService }], target: i0.ɵɵFactoryTarget.Component });
2123
+ resolveEditorText(key, fallback) {
2124
+ if (!key || !this.i18n) {
2125
+ return fallback;
2126
+ }
2127
+ return resolvePraxisMetadataEditorText(this.i18n, key, fallback ?? key);
2128
+ }
2129
+ resolveOptions(options) {
2130
+ if (!Array.isArray(options)) {
2131
+ return undefined;
2132
+ }
2133
+ return options.map((option) => ({
2134
+ ...option,
2135
+ text: this.resolveEditorText(option.textKey, option.text) ?? option.text,
2136
+ }));
2137
+ }
2138
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicEditorRendererComponent, deps: [{ token: i1$1.MatDialog }, { token: i2$2.IconPickerService }, { token: i2$2.PraxisI18nService, optional: true }], target: i0.ɵɵFactoryTarget.Component });
690
2139
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: DynamicEditorRendererComponent, isStandalone: true, selector: "praxis-dynamic-editor-renderer", inputs: { properties: "properties", form: "form" }, usesOnChanges: true, ngImport: i0, template: `
691
2140
  <ng-template #item let-field="field" let-index="index" let-content="content">
692
2141
  <div class="editor-item" [attr.data-control-type]="field.controlType">
@@ -750,7 +2199,22 @@ class DynamicEditorRendererComponent {
750
2199
  </mat-expansion-panel-header>
751
2200
  @for (row of groupRows[g]; track row) {
752
2201
  <div class="editor-row" [ngClass]="rowClass(row)">
753
- @if (ready) {
2202
+ @if (canRenderPresentationRulesRow(row)) {
2203
+ @for (field of row; track field.name) {
2204
+ <praxis-presentation-rules-builder
2205
+ class="presentation-rules-field"
2206
+ [formControl]="controlFor(field.name)">
2207
+ </praxis-presentation-rules-builder>
2208
+ }
2209
+ } @else if (canRenderPresentationChoiceRow(row)) {
2210
+ @for (field of row; track field.name) {
2211
+ <praxis-presentation-choice-control
2212
+ class="presentation-choice-field"
2213
+ [field]="field"
2214
+ [formControl]="controlFor(field.name)">
2215
+ </praxis-presentation-choice-control>
2216
+ }
2217
+ } @else if (ready && hasControlsForRow(row)) {
754
2218
  <ng-container
755
2219
  dynamicFieldLoader
756
2220
  [fields]="row"
@@ -764,7 +2228,7 @@ class DynamicEditorRendererComponent {
764
2228
  }
765
2229
  </mat-accordion>
766
2230
  </div>
767
- `, isInline: true, styles: [":host{display:block}.editor-surface{padding:8px 0 2px;border-radius:12px;background:transparent}.section-accordion{display:block}.section-card{margin:6px 0 8px;border-radius:12px;border:1px solid var(--md-sys-color-outline-variant);overflow:hidden}.section-card.mat-expanded{border-color:var(--md-sys-color-primary);box-shadow:0 2px 10px #0000000d}.section-card .mat-expansion-panel-body{background:var(--md-sys-color-surface-container-low)}.section-header{background:var(--md-sys-color-surface-container-low);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;color:var(--md-sys-color-on-surface)}.section-header .mat-expansion-panel-header-title,.section-header .mat-expansion-panel-header-description{display:flex;align-items:center;color:var(--md-sys-color-on-surface)}.section-title{display:inline-flex;align-items:center;gap:6px;font-weight:600;letter-spacing:.2px;color:var(--md-sys-color-on-surface);font-size:.9rem;line-height:1.2}.section-icon{color:var(--md-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]{flex:0 0 200px;min-width:160px}.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:1 1 auto;min-width:160px}.editor-item{display:flex;align-items:flex-start;gap:4px}.editor-item>ng-container,.editor-item>praxis-field-shell{flex:1;min-width:0}.actions-container{display:flex;align-items:center;gap:0;margin-top:12px;flex-shrink:0}.decorator-btn{position:static;color:var(--md-sys-color-primary, #3f51b5);opacity:.6;transform:scale(.8);width:32px;height:32px;line-height:32px;padding:0}.decorator-btn:hover{opacity:1}.icon-picker-btn,.hint-btn,.hint-btn.has-icon-picker{right:auto}.editor-row .mat-mdc-form-field-subscript-wrapper mat-hint{display:none!important}.editor-row .mat-mdc-form-field-subscript-wrapper mat-error{display:block!important}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:var(--md-sys-color-tertiary-container);border:1px solid var(--md-sys-color-tertiary);color:var(--md-sys-color-on-tertiary-container);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.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "debugTrace", "debugTraceLabel", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "renderError", "canvasMouseEnter", "canvasMouseLeave", "canvasClick", "canvasFocus"] }, { 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: i4.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"] }], encapsulation: i0.ViewEncapsulation.None });
2231
+ `, isInline: true, styles: [":host{display:block}.editor-surface{padding:8px 0 2px;border-radius:12px;background:transparent}.section-accordion{display:block}.section-card{margin:6px 0 8px;border-radius:12px;border:1px solid var(--md-sys-color-outline-variant);overflow:hidden}.section-card.mat-expanded{border-color:var(--md-sys-color-primary);box-shadow:0 2px 10px #0000000d}.section-card .mat-expansion-panel-body{background:var(--md-sys-color-surface-container-low)}.section-header{background:var(--md-sys-color-surface-container-low);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;color:var(--md-sys-color-on-surface)}.section-header .mat-expansion-panel-header-title,.section-header .mat-expansion-panel-header-description{display:flex;align-items:center;color:var(--md-sys-color-on-surface)}.section-title{display:inline-flex;align-items:center;gap:6px;font-weight:600;letter-spacing:.2px;color:var(--md-sys-color-on-surface);font-size:.9rem;line-height:1.2}.section-icon{color:var(--md-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}.editor-row>praxis-presentation-rules-builder.presentation-rules-field{flex:1 1 100%;min-width:0}.editor-row>praxis-presentation-choice-control.presentation-choice-field{flex:1 1 100%;min-width:0}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]{flex:0 0 200px;min-width:160px}.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:1 1 auto;min-width:160px}.editor-item{display:flex;align-items:flex-start;gap:4px;min-width:0;width:100%}.editor-item>ng-container,.editor-item>praxis-field-shell{flex:1;min-width:0}.actions-container{display:flex;align-items:center;gap:0;margin-top:12px;flex-shrink:0}.decorator-btn{position:static;color:var(--md-sys-color-primary, #3f51b5);opacity:.6;transform:scale(.8);width:32px;height:32px;line-height:32px;padding:0}.decorator-btn:hover{opacity:1}.icon-picker-btn,.hint-btn,.hint-btn.has-icon-picker{right:auto}.editor-row .mat-mdc-form-field-subscript-wrapper mat-hint{display:none!important}.editor-row .mat-mdc-form-field-subscript-wrapper mat-error{display:block!important}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:var(--md-sys-color-tertiary-container);border:1px solid var(--md-sys-color-tertiary);color:var(--md-sys-color-on-tertiary-container);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}@media(max-width:520px){.editor-row,.editor-row.row-inline{gap:8px}.editor-item{flex-direction:column;gap:2px}.editor-row.row-inline>praxis-field-shell,.editor-row.row-2>praxis-field-shell,.editor-row.row-3>praxis-field-shell{flex:1 1 100%;min-width:0}.actions-container{width:100%;justify-content:flex-end;margin-top:0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "debugTrace", "debugTraceLabel", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "renderError", "canvasMouseEnter", "canvasMouseLeave", "canvasClick", "canvasFocus"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i5$1.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i5$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i5$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i5$1.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$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: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PresentationRulesBuilderComponent, selector: "praxis-presentation-rules-builder" }, { kind: "component", type: PresentationChoiceControlComponent, selector: "praxis-presentation-choice-control", inputs: ["field"] }], encapsulation: i0.ViewEncapsulation.None });
768
2232
  }
769
2233
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicEditorRendererComponent, decorators: [{
770
2234
  type: Component,
@@ -778,6 +2242,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
778
2242
  MatDialogModule,
779
2243
  MatTooltipModule,
780
2244
  PraxisIconDirective,
2245
+ PresentationRulesBuilderComponent,
2246
+ PresentationChoiceControlComponent,
781
2247
  ], encapsulation: ViewEncapsulation.None, template: `
782
2248
  <ng-template #item let-field="field" let-index="index" let-content="content">
783
2249
  <div class="editor-item" [attr.data-control-type]="field.controlType">
@@ -841,7 +2307,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
841
2307
  </mat-expansion-panel-header>
842
2308
  @for (row of groupRows[g]; track row) {
843
2309
  <div class="editor-row" [ngClass]="rowClass(row)">
844
- @if (ready) {
2310
+ @if (canRenderPresentationRulesRow(row)) {
2311
+ @for (field of row; track field.name) {
2312
+ <praxis-presentation-rules-builder
2313
+ class="presentation-rules-field"
2314
+ [formControl]="controlFor(field.name)">
2315
+ </praxis-presentation-rules-builder>
2316
+ }
2317
+ } @else if (canRenderPresentationChoiceRow(row)) {
2318
+ @for (field of row; track field.name) {
2319
+ <praxis-presentation-choice-control
2320
+ class="presentation-choice-field"
2321
+ [field]="field"
2322
+ [formControl]="controlFor(field.name)">
2323
+ </praxis-presentation-choice-control>
2324
+ }
2325
+ } @else if (ready && hasControlsForRow(row)) {
845
2326
  <ng-container
846
2327
  dynamicFieldLoader
847
2328
  [fields]="row"
@@ -855,122 +2336,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
855
2336
  }
856
2337
  </mat-accordion>
857
2338
  </div>
858
- `, styles: [":host{display:block}.editor-surface{padding:8px 0 2px;border-radius:12px;background:transparent}.section-accordion{display:block}.section-card{margin:6px 0 8px;border-radius:12px;border:1px solid var(--md-sys-color-outline-variant);overflow:hidden}.section-card.mat-expanded{border-color:var(--md-sys-color-primary);box-shadow:0 2px 10px #0000000d}.section-card .mat-expansion-panel-body{background:var(--md-sys-color-surface-container-low)}.section-header{background:var(--md-sys-color-surface-container-low);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;color:var(--md-sys-color-on-surface)}.section-header .mat-expansion-panel-header-title,.section-header .mat-expansion-panel-header-description{display:flex;align-items:center;color:var(--md-sys-color-on-surface)}.section-title{display:inline-flex;align-items:center;gap:6px;font-weight:600;letter-spacing:.2px;color:var(--md-sys-color-on-surface);font-size:.9rem;line-height:1.2}.section-icon{color:var(--md-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]{flex:0 0 200px;min-width:160px}.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:1 1 auto;min-width:160px}.editor-item{display:flex;align-items:flex-start;gap:4px}.editor-item>ng-container,.editor-item>praxis-field-shell{flex:1;min-width:0}.actions-container{display:flex;align-items:center;gap:0;margin-top:12px;flex-shrink:0}.decorator-btn{position:static;color:var(--md-sys-color-primary, #3f51b5);opacity:.6;transform:scale(.8);width:32px;height:32px;line-height:32px;padding:0}.decorator-btn:hover{opacity:1}.icon-picker-btn,.hint-btn,.hint-btn.has-icon-picker{right:auto}.editor-row .mat-mdc-form-field-subscript-wrapper mat-hint{display:none!important}.editor-row .mat-mdc-form-field-subscript-wrapper mat-error{display:block!important}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:var(--md-sys-color-tertiary-container);border:1px solid var(--md-sys-color-tertiary);color:var(--md-sys-color-on-tertiary-container);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"] }]
859
- }], ctorParameters: () => [{ type: i1$1.MatDialog }, { type: i2$1.IconPickerService }], propDecorators: { properties: [{
2339
+ `, styles: [":host{display:block}.editor-surface{padding:8px 0 2px;border-radius:12px;background:transparent}.section-accordion{display:block}.section-card{margin:6px 0 8px;border-radius:12px;border:1px solid var(--md-sys-color-outline-variant);overflow:hidden}.section-card.mat-expanded{border-color:var(--md-sys-color-primary);box-shadow:0 2px 10px #0000000d}.section-card .mat-expansion-panel-body{background:var(--md-sys-color-surface-container-low)}.section-header{background:var(--md-sys-color-surface-container-low);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;color:var(--md-sys-color-on-surface)}.section-header .mat-expansion-panel-header-title,.section-header .mat-expansion-panel-header-description{display:flex;align-items:center;color:var(--md-sys-color-on-surface)}.section-title{display:inline-flex;align-items:center;gap:6px;font-weight:600;letter-spacing:.2px;color:var(--md-sys-color-on-surface);font-size:.9rem;line-height:1.2}.section-icon{color:var(--md-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}.editor-row>praxis-presentation-rules-builder.presentation-rules-field{flex:1 1 100%;min-width:0}.editor-row>praxis-presentation-choice-control.presentation-choice-field{flex:1 1 100%;min-width:0}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]{flex:0 0 200px;min-width:160px}.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:1 1 auto;min-width:160px}.editor-item{display:flex;align-items:flex-start;gap:4px;min-width:0;width:100%}.editor-item>ng-container,.editor-item>praxis-field-shell{flex:1;min-width:0}.actions-container{display:flex;align-items:center;gap:0;margin-top:12px;flex-shrink:0}.decorator-btn{position:static;color:var(--md-sys-color-primary, #3f51b5);opacity:.6;transform:scale(.8);width:32px;height:32px;line-height:32px;padding:0}.decorator-btn:hover{opacity:1}.icon-picker-btn,.hint-btn,.hint-btn.has-icon-picker{right:auto}.editor-row .mat-mdc-form-field-subscript-wrapper mat-hint{display:none!important}.editor-row .mat-mdc-form-field-subscript-wrapper mat-error{display:block!important}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:var(--md-sys-color-tertiary-container);border:1px solid var(--md-sys-color-tertiary);color:var(--md-sys-color-on-tertiary-container);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}@media(max-width:520px){.editor-row,.editor-row.row-inline{gap:8px}.editor-item{flex-direction:column;gap:2px}.editor-row.row-inline>praxis-field-shell,.editor-row.row-2>praxis-field-shell,.editor-row.row-3>praxis-field-shell{flex:1 1 100%;min-width:0}.actions-container{width:100%;justify-content:flex-end;margin-top:0}}\n"] }]
2340
+ }], ctorParameters: () => [{ type: i1$1.MatDialog }, { type: i2$2.IconPickerService }, { type: i2$2.PraxisI18nService, decorators: [{
2341
+ type: Optional
2342
+ }] }], propDecorators: { properties: [{
860
2343
  type: Input
861
2344
  }], form: [{
862
2345
  type: Input
863
2346
  }] } });
864
2347
 
865
- const METADATA_EDITOR_EN_US = {
866
- 'praxis.metadataEditor.arrayFields.section': 'Collection subfields',
867
- 'praxis.metadataEditor.arrayFields.title': 'Subfields',
868
- 'praxis.metadataEditor.arrayFields.add': 'Add subfield',
869
- 'praxis.metadataEditor.arrayFields.duplicateNames': 'There are duplicate names: {{names}}',
870
- 'praxis.metadataEditor.arrayFields.empty': 'No local subfields configured.',
871
- 'praxis.metadataEditor.arrayFields.name': 'Name',
872
- 'praxis.metadataEditor.arrayFields.nameRequired': 'Enter the subfield name.',
873
- 'praxis.metadataEditor.arrayFields.nameDuplicated': 'This name is already used in the collection.',
874
- 'praxis.metadataEditor.arrayFields.label': 'Label',
875
- 'praxis.metadataEditor.arrayFields.controlType': 'Control',
876
- 'praxis.metadataEditor.arrayFields.required': 'Required',
877
- 'praxis.metadataEditor.arrayFields.moveUp': 'Move up',
878
- 'praxis.metadataEditor.arrayFields.moveDown': 'Move down',
879
- 'praxis.metadataEditor.arrayFields.duplicate': 'Duplicate',
880
- 'praxis.metadataEditor.arrayFields.remove': 'Remove',
881
- 'praxis.metadataEditor.arrayFields.removeBlocked': 'Remove references before deleting: {{references}}',
882
- 'praxis.metadataEditor.arrayFields.refOnly': 'The item schema comes from itemSchemaRef; create local subfields only when you need an override.',
883
- 'praxis.metadataEditor.arrayFields.overrideLocal': 'There is an itemSchemaRef and local subfields; local subfields work as an explicit override.',
884
- 'praxis.metadataEditor.arrayFields.imported': 'Subfields imported. Review them before applying or saving.',
885
- 'praxis.metadataEditor.arrayFields.localOnly': 'Local subfields will be saved in array.itemSchema.fields.',
886
- 'praxis.metadataEditor.arrayFields.control.input': 'Text',
887
- 'praxis.metadataEditor.arrayFields.control.textarea': 'Long text',
888
- 'praxis.metadataEditor.arrayFields.control.numeric': 'Number',
889
- 'praxis.metadataEditor.arrayFields.control.currency': 'Currency',
890
- 'praxis.metadataEditor.arrayFields.control.email': 'Email',
891
- 'praxis.metadataEditor.arrayFields.control.phone': 'Phone',
892
- 'praxis.metadataEditor.arrayFields.control.select': 'Select',
893
- 'praxis.metadataEditor.arrayFields.control.entityLookup': 'Entity lookup',
894
- 'praxis.metadataEditor.arrayFields.control.toggle': 'Toggle',
895
- 'praxis.metadataEditor.arrayFields.control.date': 'Date',
896
- 'praxis.metadataEditor.fieldResource.label': 'Governed resource',
897
- 'praxis.metadataEditor.fieldResource.searchPlaceholder': 'Search by name, domain, or path',
898
- 'praxis.metadataEditor.fieldResource.hint': 'Select from the host governed catalog. The saved value remains resourcePath.',
899
- };
900
-
901
- const METADATA_EDITOR_PT_BR = {
902
- 'praxis.metadataEditor.arrayFields.section': 'Subcampos da cole\u00e7\u00e3o',
903
- 'praxis.metadataEditor.arrayFields.title': 'Subcampos',
904
- 'praxis.metadataEditor.arrayFields.add': 'Adicionar subcampo',
905
- 'praxis.metadataEditor.arrayFields.duplicateNames': 'Existem nomes duplicados: {{names}}',
906
- 'praxis.metadataEditor.arrayFields.empty': 'Nenhum subcampo local configurado.',
907
- 'praxis.metadataEditor.arrayFields.name': 'Nome',
908
- 'praxis.metadataEditor.arrayFields.nameRequired': 'Informe o nome do subcampo.',
909
- 'praxis.metadataEditor.arrayFields.nameDuplicated': 'Este nome j\u00e1 est\u00e1 em uso na cole\u00e7\u00e3o.',
910
- 'praxis.metadataEditor.arrayFields.label': 'Label',
911
- 'praxis.metadataEditor.arrayFields.controlType': 'Controle',
912
- 'praxis.metadataEditor.arrayFields.required': 'Obrigat\u00f3rio',
913
- 'praxis.metadataEditor.arrayFields.moveUp': 'Subir',
914
- 'praxis.metadataEditor.arrayFields.moveDown': 'Descer',
915
- 'praxis.metadataEditor.arrayFields.duplicate': 'Duplicar',
916
- 'praxis.metadataEditor.arrayFields.remove': 'Remover',
917
- 'praxis.metadataEditor.arrayFields.removeBlocked': 'Remova as refer\u00eancias antes de excluir: {{references}}',
918
- 'praxis.metadataEditor.arrayFields.refOnly': 'O schema do item vem do itemSchemaRef; crie subcampos locais apenas quando precisar de override.',
919
- 'praxis.metadataEditor.arrayFields.overrideLocal': 'H\u00e1 um itemSchemaRef e subcampos locais; os subcampos locais funcionam como override expl\u00edcito.',
920
- 'praxis.metadataEditor.arrayFields.imported': 'Subcampos importados. Revise antes de aplicar ou salvar.',
921
- 'praxis.metadataEditor.arrayFields.localOnly': 'Subcampos locais ser\u00e3o salvos em array.itemSchema.fields.',
922
- 'praxis.metadataEditor.arrayFields.control.input': 'Texto',
923
- 'praxis.metadataEditor.arrayFields.control.textarea': 'Texto longo',
924
- 'praxis.metadataEditor.arrayFields.control.numeric': 'N\u00famero',
925
- 'praxis.metadataEditor.arrayFields.control.currency': 'Moeda',
926
- 'praxis.metadataEditor.arrayFields.control.email': 'E-mail',
927
- 'praxis.metadataEditor.arrayFields.control.phone': 'Telefone',
928
- 'praxis.metadataEditor.arrayFields.control.select': 'Sele\u00e7\u00e3o',
929
- 'praxis.metadataEditor.arrayFields.control.entityLookup': 'Busca de entidade',
930
- 'praxis.metadataEditor.arrayFields.control.toggle': 'Liga/desliga',
931
- 'praxis.metadataEditor.arrayFields.control.date': 'Data',
932
- 'praxis.metadataEditor.fieldResource.label': 'Recurso governado',
933
- 'praxis.metadataEditor.fieldResource.searchPlaceholder': 'Buscar por nome, dom\u00ednio ou caminho',
934
- 'praxis.metadataEditor.fieldResource.hint': 'Selecione no cat\u00e1logo governado do host. O valor gravado continua sendo resourcePath.',
935
- };
936
-
937
- function createPraxisMetadataEditorI18nConfig(options = {}) {
938
- const dictionaries = {
939
- 'pt-BR': {
940
- ...METADATA_EDITOR_PT_BR,
941
- ...(options.dictionaries?.['pt-BR'] ?? {}),
942
- },
943
- 'en-US': {
944
- ...METADATA_EDITOR_EN_US,
945
- ...(options.dictionaries?.['en-US'] ?? {}),
946
- },
947
- };
948
- for (const [locale, dictionary] of Object.entries(options.dictionaries ?? {})) {
949
- if (locale === 'pt-BR' || locale === 'en-US') {
950
- continue;
951
- }
952
- dictionaries[locale] = {
953
- ...(dictionaries[locale] ?? {}),
954
- ...dictionary,
955
- };
956
- }
957
- return {
958
- locale: options.locale,
959
- fallbackLocale: options.fallbackLocale ?? 'pt-BR',
960
- dictionaries,
961
- };
962
- }
963
- const METADATA_EDITOR_I18N_CONFIG = createPraxisMetadataEditorI18nConfig();
964
- function providePraxisMetadataEditorI18n(options = {}) {
965
- return providePraxisI18n(createPraxisMetadataEditorI18nConfig(options));
966
- }
967
- function resolvePraxisMetadataEditorText(i18n, key, fallback) {
968
- const namespacedKey = key.startsWith('praxis.metadataEditor.')
969
- ? key
970
- : `praxis.metadataEditor.${key}`;
971
- return i18n.t(namespacedKey, undefined, fallback) || fallback;
972
- }
973
-
974
2348
  class ArrayItemSchemaFieldsEditorComponent {
975
2349
  static nextInstanceId = 0;
976
2350
  control = null;
@@ -6038,6 +7412,140 @@ const fieldSubmitProperties = [
6038
7412
  },
6039
7413
  ];
6040
7414
 
7415
+ const fieldPresentationProperties = [
7416
+ {
7417
+ name: 'valuePresentation',
7418
+ label: 'Formato do valor',
7419
+ labelKey: 'presentationFields.valuePresentation.label',
7420
+ editorType: 'textarea',
7421
+ group: 'Apresentação',
7422
+ groupKey: 'presentationFields.group',
7423
+ placeholder: '{\n "type": "currency",\n "currency": { "code": "BRL" }\n}',
7424
+ hint: 'Formatador canônico usado em superfícies readonly/presentation. Use um objeto JSON.',
7425
+ hintKey: 'presentationFields.valuePresentation.hint',
7426
+ },
7427
+ {
7428
+ name: 'presentation.presenter',
7429
+ label: 'Apresentador',
7430
+ labelKey: 'presentationFields.presenter.label',
7431
+ editorType: 'presentation-choice',
7432
+ group: 'Apresentação',
7433
+ groupKey: 'presentationFields.group',
7434
+ row: 'presentation.semantic',
7435
+ inline: true,
7436
+ options: [
7437
+ { value: 'text', text: 'Texto', textKey: 'presentationFields.presenter.option.text' },
7438
+ { value: 'status', text: 'Status', textKey: 'presentationFields.presenter.option.status' },
7439
+ { value: 'chip', text: 'Chip', textKey: 'presentationFields.presenter.option.chip' },
7440
+ { value: 'badge', text: 'Badge', textKey: 'presentationFields.presenter.option.badge' },
7441
+ { value: 'iconValue', text: 'Ícone + valor', textKey: 'presentationFields.presenter.option.iconValue' },
7442
+ { value: 'progress', text: 'Progresso', textKey: 'presentationFields.presenter.option.progress' },
7443
+ { value: 'rating', text: 'Avaliação', textKey: 'presentationFields.presenter.option.rating' },
7444
+ ],
7445
+ hint: 'Estratégia semântica para renderização em presentationMode.',
7446
+ hintKey: 'presentationFields.presenter.hint',
7447
+ },
7448
+ {
7449
+ name: 'presentation.tone',
7450
+ label: 'Tom',
7451
+ labelKey: 'presentationFields.tone.label',
7452
+ editorType: 'presentation-choice',
7453
+ group: 'Apresentação',
7454
+ groupKey: 'presentationFields.group',
7455
+ row: 'presentation.semantic',
7456
+ inline: true,
7457
+ options: [
7458
+ { value: 'neutral', text: 'Neutro', textKey: 'presentationFields.tone.option.neutral' },
7459
+ { value: 'info', text: 'Informação', textKey: 'presentationFields.tone.option.info' },
7460
+ { value: 'success', text: 'Sucesso', textKey: 'presentationFields.tone.option.success' },
7461
+ { value: 'warning', text: 'Atenção', textKey: 'presentationFields.tone.option.warning' },
7462
+ { value: 'danger', text: 'Crítico', textKey: 'presentationFields.tone.option.danger' },
7463
+ ],
7464
+ hint: 'Tom semântico mapeado pelo runtime para tokens de tema.',
7465
+ hintKey: 'presentationFields.tone.hint',
7466
+ },
7467
+ {
7468
+ name: 'presentation.appearance',
7469
+ label: 'Ênfase',
7470
+ labelKey: 'presentationFields.appearance.label',
7471
+ editorType: 'presentation-choice',
7472
+ group: 'Apresentação',
7473
+ groupKey: 'presentationFields.group',
7474
+ row: 'presentation.semantic',
7475
+ inline: true,
7476
+ options: [
7477
+ { value: 'plain', text: 'Simples', textKey: 'presentationFields.appearance.option.plain' },
7478
+ { value: 'soft', text: 'Suave', textKey: 'presentationFields.appearance.option.soft' },
7479
+ { value: 'outlined', text: 'Contornado', textKey: 'presentationFields.appearance.option.outlined' },
7480
+ { value: 'filled', text: 'Preenchido', textKey: 'presentationFields.appearance.option.filled' },
7481
+ ],
7482
+ hint: 'Nível de ênfase visual do apresentador.',
7483
+ hintKey: 'presentationFields.appearance.hint',
7484
+ },
7485
+ {
7486
+ name: 'presentation.icon',
7487
+ label: 'Ícone',
7488
+ labelKey: 'presentationFields.icon.label',
7489
+ editorType: 'text',
7490
+ group: 'Apresentação',
7491
+ groupKey: 'presentationFields.group',
7492
+ row: 'presentation.content',
7493
+ inline: true,
7494
+ placeholder: 'schedule',
7495
+ hint: 'Nome do ícone Material Symbols, sem HTML.',
7496
+ hintKey: 'presentationFields.icon.hint',
7497
+ },
7498
+ {
7499
+ name: 'presentation.label',
7500
+ label: 'Rótulo',
7501
+ labelKey: 'presentationFields.label.label',
7502
+ editorType: 'text',
7503
+ group: 'Apresentação',
7504
+ groupKey: 'presentationFields.group',
7505
+ row: 'presentation.content',
7506
+ placeholder: 'Pendente',
7507
+ placeholderKey: 'presentationFields.label.placeholder',
7508
+ hint: 'Texto semântico exibido por status, chip ou badge.',
7509
+ hintKey: 'presentationFields.label.hint',
7510
+ },
7511
+ {
7512
+ name: 'presentation.badge',
7513
+ label: 'Badge',
7514
+ labelKey: 'presentationFields.badge.label',
7515
+ editorType: 'text',
7516
+ group: 'Apresentação',
7517
+ groupKey: 'presentationFields.group',
7518
+ row: 'presentation.content',
7519
+ inline: true,
7520
+ placeholder: 'SLA',
7521
+ hint: 'Marcador secundário opcional junto ao valor.',
7522
+ hintKey: 'presentationFields.badge.hint',
7523
+ },
7524
+ {
7525
+ name: 'presentation.tooltip',
7526
+ label: 'Tooltip',
7527
+ labelKey: 'presentationFields.tooltip.label',
7528
+ editorType: 'text',
7529
+ group: 'Apresentação',
7530
+ groupKey: 'presentationFields.group',
7531
+ placeholder: 'Aguardando aprovação fiscal',
7532
+ placeholderKey: 'presentationFields.tooltip.placeholder',
7533
+ hint: 'Explicação curta exibida como tooltip.',
7534
+ hintKey: 'presentationFields.tooltip.hint',
7535
+ },
7536
+ {
7537
+ name: 'presentationRules',
7538
+ label: 'Regras de apresentação',
7539
+ labelKey: 'presentationFields.presentationRules.label',
7540
+ editorType: 'presentation-rules-builder',
7541
+ group: 'Apresentação',
7542
+ groupKey: 'presentationFields.group',
7543
+ placeholder: '[\n {\n "when": { "===": [{ "var": "value" }, "BLOQUEADO"] },\n "set": { "tone": "danger", "icon": "lock", "label": "Bloqueado" }\n }\n]',
7544
+ hint: 'Builder guiado para regras Json Logic ordenadas que alteram somente a apresentação. Regras posteriores vencem.',
7545
+ hintKey: 'presentationFields.presentationRules.hint',
7546
+ },
7547
+ ];
7548
+
6041
7549
  /** Garante que minLength <= maxLength quando ambos definidos. */
6042
7550
  function minLenLeMaxLen(group) {
6043
7551
  const v = group.value || {};
@@ -8842,6 +10350,12 @@ class FieldMetadataEditorComponent {
8842
10350
  }
8843
10351
  this.normalizeEntityLookupPatchValues(patch);
8844
10352
  this.normalizeArrayPatchValues(patch);
10353
+ this.normalizePresentationPatchValues(patch);
10354
+ }
10355
+ normalizePresentationPatchValues(patch) {
10356
+ this.normalizeJsonObjectProperty(patch, 'valuePresentation');
10357
+ this.normalizeJsonObjectProperty(patch, 'presentation');
10358
+ this.normalizeJsonArrayProperty(patch, 'presentationRules');
8845
10359
  }
8846
10360
  normalizeEntityLookupPatchValues(patch) {
8847
10361
  const optionSource = patch?.optionSource;
@@ -9033,7 +10547,8 @@ class FieldMetadataEditorComponent {
9033
10547
  }
9034
10548
  withSharedFieldMetadataProperties(props) {
9035
10549
  const propNames = new Set(props.map((prop) => prop.name));
9036
- const missingSharedProps = fieldSubmitProperties.filter((prop) => !propNames.has(prop.name));
10550
+ const sharedProps = [...fieldPresentationProperties, ...fieldSubmitProperties];
10551
+ const missingSharedProps = sharedProps.filter((prop) => !propNames.has(prop.name));
9037
10552
  return missingSharedProps.length ? [...props, ...missingSharedProps] : props;
9038
10553
  }
9039
10554
  get isArrayEditor() {
@@ -9299,7 +10814,6 @@ class CascadeManagerTabComponent {
9299
10814
  this.svc = svc;
9300
10815
  }
9301
10816
  ngOnInit() {
9302
- console.log('CascadeManagerTabComponent ngOnInit', this.fields);
9303
10817
  // hydrate from input fields
9304
10818
  const map = new Map();
9305
10819
  for (const f of this.fields) {
@@ -9314,7 +10828,6 @@ class CascadeManagerTabComponent {
9314
10828
  }
9315
10829
  }
9316
10830
  ngOnChanges(changes) {
9317
- console.log('CascadeManagerTabComponent ngOnChanges', changes);
9318
10831
  if (changes['fields'] && !changes['fields'].firstChange) {
9319
10832
  // Re-hydrate rules from incoming fields while preserving existing ones when possible
9320
10833
  const incoming = this.fields || [];
@@ -9934,7 +11447,7 @@ class CascadeManagerTabComponent {
9934
11447
  </div>
9935
11448
  }
9936
11449
  </div>
9937
- `, 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 var(--md-sys-color-outline-variant);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:8px 6px;cursor:pointer;border-radius:10px;transition:background .12s ease,border .12s ease;border:1px solid transparent}.field-item:hover{background:var(--md-sys-color-surface-container-high)}.field-item.selected{background:var(--md-sys-color-surface-container-highest);border-color:var(--md-sys-color-outline-variant)}.rule-badges{display:inline-flex;gap:6px}.rule-badge{font-size:11px;padding:2px 8px;border-radius:999px;font-weight:600}.b-active{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.b-manual{background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant)}.b-immediate{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container)}.b-conflict{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.cm-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.rules-empty{opacity:.8;font-style:italic;padding:8px 0;color:var(--md-sys-color-on-surface-variant)}.rule-row{display:grid;grid-template-columns:1fr auto;align-items:center;padding:10px 0;border-bottom:1px dashed var(--md-sys-color-outline-variant)}.rule-actions button{margin-left:4px}.banner{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container);padding:10px 12px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;margin-bottom:10px;display:flex;gap:8px;align-items:center}.import-panel{background:var(--md-sys-color-surface-container-high);border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;padding:10px;margin-bottom:10px}.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:8px;border-bottom:1px dashed var(--md-sys-color-outline-variant)}.kbd{font-family:monospace;background:var(--md-sys-color-surface-container-highest);padding:0 6px;border-radius:4px;color:var(--md-sys-color-on-surface-variant)}.panel-surface{background:var(--md-sys-color-surface-container-high);border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;box-shadow:var(--md-sys-elevation-level1, none);margin-bottom:12px}.panel-stack{display:grid;gap:12px;margin:12px 0}.stack-panel{background:var(--md-sys-color-surface-container-high);border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;box-shadow:var(--md-sys-elevation-level1, none)}.stack-panel .mat-expansion-panel-content{padding:0 16px 12px}.panel-header{display:flex;align-items:center;gap:8px}.preview-box{background:var(--md-sys-color-surface-container-low);border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;padding:8px;color:var(--md-sys-color-on-surface)}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;margin-right:-4px}.help-icon-button mat-icon{font-size:18px;width:18px;height:18px}.mat-mdc-form-field-icon-suffix{align-self:center}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.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: i4.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: i4.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: i6$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i6$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i6$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i6$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "ngmodule", type: MatListModule }, { kind: "ngmodule", type: MatDividerModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.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: i7.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i8$1.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: "ngmodule", type: MatMenuModule }, { kind: "component", type: i10.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: i10.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i10.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: MatAutocompleteModule }, { kind: "component", type: i11.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "directive", type: i11.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { 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"] }] });
11450
+ `, 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 var(--md-sys-color-outline-variant);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:8px 6px;cursor:pointer;border-radius:10px;transition:background .12s ease,border .12s ease;border:1px solid transparent}.field-item:hover{background:var(--md-sys-color-surface-container-high)}.field-item.selected{background:var(--md-sys-color-surface-container-highest);border-color:var(--md-sys-color-outline-variant)}.rule-badges{display:inline-flex;gap:6px}.rule-badge{font-size:11px;padding:2px 8px;border-radius:999px;font-weight:600}.b-active{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.b-manual{background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant)}.b-immediate{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container)}.b-conflict{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.cm-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.rules-empty{opacity:.8;font-style:italic;padding:8px 0;color:var(--md-sys-color-on-surface-variant)}.rule-row{display:grid;grid-template-columns:1fr auto;align-items:center;padding:10px 0;border-bottom:1px dashed var(--md-sys-color-outline-variant)}.rule-actions button{margin-left:4px}.banner{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container);padding:10px 12px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;margin-bottom:10px;display:flex;gap:8px;align-items:center}.import-panel{background:var(--md-sys-color-surface-container-high);border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;padding:10px;margin-bottom:10px}.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:8px;border-bottom:1px dashed var(--md-sys-color-outline-variant)}.kbd{font-family:monospace;background:var(--md-sys-color-surface-container-highest);padding:0 6px;border-radius:4px;color:var(--md-sys-color-on-surface-variant)}.panel-surface{background:var(--md-sys-color-surface-container-high);border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;box-shadow:var(--md-sys-elevation-level1, none);margin-bottom:12px}.panel-stack{display:grid;gap:12px;margin:12px 0}.stack-panel{background:var(--md-sys-color-surface-container-high);border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;box-shadow:var(--md-sys-elevation-level1, none)}.stack-panel .mat-expansion-panel-content{padding:0 16px 12px}.panel-header{display:flex;align-items:center;gap:8px}.preview-box{background:var(--md-sys-color-surface-container-low);border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;padding:8px;color:var(--md-sys-color-on-surface)}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;margin-right:-4px}.help-icon-button mat-icon{font-size:18px;width:18px;height:18px}.mat-mdc-form-field-icon-suffix{align-self:center}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$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: i2$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: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "ngmodule", type: MatListModule }, { kind: "ngmodule", type: MatDividerModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i6.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: 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$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i5$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i5$1.MatExpansionPanelTitle, selector: "mat-panel-title" }, { 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: MatAutocompleteModule }, { kind: "component", type: i12.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "directive", type: i12.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i13.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i13.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i13.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }] });
9938
11451
  }
9939
11452
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CascadeManagerTabComponent, decorators: [{
9940
11453
  type: Component,
@@ -10288,6 +11801,72 @@ const BASE_CAPABILITIES = [
10288
11801
  valueKind: 'object',
10289
11802
  description: 'Root configuration payload for the metadata editor.',
10290
11803
  },
11804
+ {
11805
+ path: 'seed.valuePresentation',
11806
+ category: 'transform',
11807
+ valueKind: 'object',
11808
+ description: 'Canonical readonly value formatter metadata edited on FieldMetadata.',
11809
+ safetyNotes: 'Use canonical valuePresentation objects only; do not encode CSS, HTML, commands, or display-only aliases.',
11810
+ },
11811
+ {
11812
+ path: 'seed.presentation',
11813
+ category: 'appearance',
11814
+ valueKind: 'object',
11815
+ description: 'Semantic presentationMode state for FieldMetadata, including presenter, tone, icon, label and badge intent.',
11816
+ safetyNotes: 'Presentation metadata must remain semantic and renderer-safe; do not include raw HTML or imperative behavior.',
11817
+ },
11818
+ {
11819
+ path: 'seed.presentation.presenter',
11820
+ category: 'appearance',
11821
+ valueKind: 'enum',
11822
+ allowedValues: ['text', 'badge', 'chip', 'status', 'iconValue', 'progress', 'rating'],
11823
+ description: 'Guided semantic presenter for presentationMode.',
11824
+ },
11825
+ {
11826
+ path: 'seed.presentation.tone',
11827
+ category: 'appearance',
11828
+ valueKind: 'enum',
11829
+ allowedValues: ['neutral', 'info', 'success', 'warning', 'danger'],
11830
+ description: 'Guided semantic tone for presentationMode.',
11831
+ },
11832
+ {
11833
+ path: 'seed.presentation.appearance',
11834
+ category: 'appearance',
11835
+ valueKind: 'enum',
11836
+ allowedValues: ['plain', 'soft', 'outlined', 'filled'],
11837
+ description: 'Guided visual emphasis for presentationMode.',
11838
+ },
11839
+ {
11840
+ path: 'seed.presentation.icon',
11841
+ category: 'appearance',
11842
+ valueKind: 'string',
11843
+ description: 'Material Symbols icon name used by semantic presentation.',
11844
+ },
11845
+ {
11846
+ path: 'seed.presentation.label',
11847
+ category: 'identity',
11848
+ valueKind: 'string',
11849
+ description: 'Semantic label shown by status, chip and badge presenters.',
11850
+ },
11851
+ {
11852
+ path: 'seed.presentation.badge',
11853
+ category: 'appearance',
11854
+ valueKind: 'string',
11855
+ description: 'Secondary badge marker shown with the presentation value.',
11856
+ },
11857
+ {
11858
+ path: 'seed.presentation.tooltip',
11859
+ category: 'accessibility',
11860
+ valueKind: 'string',
11861
+ description: 'Tooltip text for the presentation wrapper.',
11862
+ },
11863
+ {
11864
+ path: 'seed.presentationRules',
11865
+ category: 'behavior',
11866
+ valueKind: 'array',
11867
+ description: 'Ordered Json Logic presentation rules edited by a guided builder for common conditions and semantic visual effects.',
11868
+ safetyNotes: 'Rules may only affect tone, icon, label, badge, appearance and tooltip; they must not mutate submitted values or execute commands.',
11869
+ },
10291
11870
  ];
10292
11871
  const METADATA_EDITOR_AI_CAPABILITY_CATALOG = {
10293
11872
  version: '1.0.0',
@@ -10320,6 +11899,16 @@ const fieldMetadataPropertySchema = {
10320
11899
  'hint',
10321
11900
  'helpText',
10322
11901
  'tooltip',
11902
+ 'valuePresentation',
11903
+ 'presentation',
11904
+ 'presentation.presenter',
11905
+ 'presentation.tone',
11906
+ 'presentation.appearance',
11907
+ 'presentation.icon',
11908
+ 'presentation.label',
11909
+ 'presentation.badge',
11910
+ 'presentation.tooltip',
11911
+ 'presentationRules',
10323
11912
  'options',
10324
11913
  'optionSource',
10325
11914
  'validators',