@praxisui/metadata-editor 9.0.0-beta.9 → 9.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 {
@@ -390,23 +392,1425 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
390
392
  <span matListItemLine>{{ r.label }}</span>
391
393
  </a>
392
394
  }
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
- }] });
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"] }] });
1728
+ }
1729
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PresentationChoiceControlComponent, decorators: [{
1730
+ type: Component,
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>
1758
+ }
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;
@@ -3798,25 +5172,30 @@ const dateRangeProperties = [
3798
5172
  { name: 'showShortcuts', label: 'Habilitar atalhos', editorType: 'checkbox', group: 'Atalhos', hint: 'Exibe painel lateral com presets de datas.' },
3799
5173
  {
3800
5174
  name: 'shortcutsPosition', label: 'Posição do painel', editorType: 'select', group: 'Atalhos', options: [
5175
+ { value: 'auto', text: 'Automática' },
5176
+ { value: 'below', text: 'Abaixo' },
3801
5177
  { value: 'left', text: 'Esquerda' },
3802
5178
  { value: 'right', text: 'Direita' },
3803
- ], hint: 'Escolhe em qual lado o painel de atalhos abre.'
5179
+ ], hint: 'Escolhe a posição preferencial do overlay externo de atalhos do dateRange.'
3804
5180
  },
3805
5181
  { name: 'applyOnShortcutClick', label: 'Aplicar ao clicar', editorType: 'checkbox', group: 'Atalhos', hint: 'Compatibilidade legada para atalhos do date-range base. No inlineDateRange, prefira inlineQuickPresetsApplyMode.' },
3806
5182
  {
3807
- name: 'shortcuts', label: 'Atalhos visíveis', editorType: 'select', group: 'Atalhos', multiple: true, options: [
3808
- { value: 'today', text: 'Hoje' },
3809
- { value: 'yesterday', text: 'Ontem' },
3810
- { value: 'thisWeek', text: 'Esta semana' },
3811
- { value: 'lastWeek', text: 'Semana passada' },
3812
- { value: 'thisMonth', text: 'Este mês' },
3813
- { value: 'lastMonth', text: 'Mês passado' },
3814
- { value: 'thisYear', text: 'Este ano' },
3815
- { value: 'lastYear', text: 'Ano passado' },
3816
- ], hint: 'Selecione quais presets mostrar no painel de atalhos.'
5183
+ name: 'shortcuts',
5184
+ label: 'Atalhos visíveis (JSON)',
5185
+ editorType: 'textarea',
5186
+ group: 'Atalhos',
5187
+ hint: 'Array canônico com ids built-in e presets estáticos resolvidos pelo backend. Ex.: ["today", {"id":"periodo-votacao-2026","label":"Período de votação","startDate":"2026-07-06","endDate":"2026-10-04","timeZone":"America/Sao_Paulo","tone":"info"}]. Não informe calculateRange em JSON.'
3817
5188
  },
3818
5189
  { name: 'inlineQuickPresets.enabled', label: 'Atalhos inline', editorType: 'checkbox', group: 'Atalhos', hint: 'Exibe chips rápidos ao lado do campo no modo inlineDateRange.' },
3819
5190
  { name: 'inlineQuickPresets.maxVisible', label: 'Qtd. chips inline', editorType: 'number', group: 'Atalhos', hint: 'Define o número máximo de atalhos inline visíveis (1 a 8).' },
5191
+ {
5192
+ name: 'inlineQuickPresets.position', label: 'Posição inline', editorType: 'select', group: 'Atalhos', options: [
5193
+ { value: 'auto', text: 'Automática' },
5194
+ { value: 'footer', text: 'Rodapé' },
5195
+ { value: 'start', text: 'Início lógico' },
5196
+ { value: 'end', text: 'Fim lógico' },
5197
+ ], hint: 'Define se atalhos inline aparecem no rodapé, início lógico, fim lógico ou em fallback automático.'
5198
+ },
3820
5199
  { name: 'inlineQuickPresetsAriaLabel', label: 'ARIA atalhos inline', editorType: 'text', group: 'Atalhos', hint: 'Rótulo de acessibilidade do grupo de atalhos rápidos inline.' },
3821
5200
  {
3822
5201
  name: 'inlineOverlay.applyMode',
@@ -6038,6 +7417,140 @@ const fieldSubmitProperties = [
6038
7417
  },
6039
7418
  ];
6040
7419
 
7420
+ const fieldPresentationProperties = [
7421
+ {
7422
+ name: 'valuePresentation',
7423
+ label: 'Formato do valor',
7424
+ labelKey: 'presentationFields.valuePresentation.label',
7425
+ editorType: 'textarea',
7426
+ group: 'Apresentação',
7427
+ groupKey: 'presentationFields.group',
7428
+ placeholder: '{\n "type": "currency",\n "currency": { "code": "BRL" }\n}',
7429
+ hint: 'Formatador canônico usado em superfícies readonly/presentation. Use um objeto JSON.',
7430
+ hintKey: 'presentationFields.valuePresentation.hint',
7431
+ },
7432
+ {
7433
+ name: 'presentation.presenter',
7434
+ label: 'Apresentador',
7435
+ labelKey: 'presentationFields.presenter.label',
7436
+ editorType: 'presentation-choice',
7437
+ group: 'Apresentação',
7438
+ groupKey: 'presentationFields.group',
7439
+ row: 'presentation.semantic',
7440
+ inline: true,
7441
+ options: [
7442
+ { value: 'text', text: 'Texto', textKey: 'presentationFields.presenter.option.text' },
7443
+ { value: 'status', text: 'Status', textKey: 'presentationFields.presenter.option.status' },
7444
+ { value: 'chip', text: 'Chip', textKey: 'presentationFields.presenter.option.chip' },
7445
+ { value: 'badge', text: 'Badge', textKey: 'presentationFields.presenter.option.badge' },
7446
+ { value: 'iconValue', text: 'Ícone + valor', textKey: 'presentationFields.presenter.option.iconValue' },
7447
+ { value: 'progress', text: 'Progresso', textKey: 'presentationFields.presenter.option.progress' },
7448
+ { value: 'rating', text: 'Avaliação', textKey: 'presentationFields.presenter.option.rating' },
7449
+ ],
7450
+ hint: 'Estratégia semântica para renderização em presentationMode.',
7451
+ hintKey: 'presentationFields.presenter.hint',
7452
+ },
7453
+ {
7454
+ name: 'presentation.tone',
7455
+ label: 'Tom',
7456
+ labelKey: 'presentationFields.tone.label',
7457
+ editorType: 'presentation-choice',
7458
+ group: 'Apresentação',
7459
+ groupKey: 'presentationFields.group',
7460
+ row: 'presentation.semantic',
7461
+ inline: true,
7462
+ options: [
7463
+ { value: 'neutral', text: 'Neutro', textKey: 'presentationFields.tone.option.neutral' },
7464
+ { value: 'info', text: 'Informação', textKey: 'presentationFields.tone.option.info' },
7465
+ { value: 'success', text: 'Sucesso', textKey: 'presentationFields.tone.option.success' },
7466
+ { value: 'warning', text: 'Atenção', textKey: 'presentationFields.tone.option.warning' },
7467
+ { value: 'danger', text: 'Crítico', textKey: 'presentationFields.tone.option.danger' },
7468
+ ],
7469
+ hint: 'Tom semântico mapeado pelo runtime para tokens de tema.',
7470
+ hintKey: 'presentationFields.tone.hint',
7471
+ },
7472
+ {
7473
+ name: 'presentation.appearance',
7474
+ label: 'Ênfase',
7475
+ labelKey: 'presentationFields.appearance.label',
7476
+ editorType: 'presentation-choice',
7477
+ group: 'Apresentação',
7478
+ groupKey: 'presentationFields.group',
7479
+ row: 'presentation.semantic',
7480
+ inline: true,
7481
+ options: [
7482
+ { value: 'plain', text: 'Simples', textKey: 'presentationFields.appearance.option.plain' },
7483
+ { value: 'soft', text: 'Suave', textKey: 'presentationFields.appearance.option.soft' },
7484
+ { value: 'outlined', text: 'Contornado', textKey: 'presentationFields.appearance.option.outlined' },
7485
+ { value: 'filled', text: 'Preenchido', textKey: 'presentationFields.appearance.option.filled' },
7486
+ ],
7487
+ hint: 'Nível de ênfase visual do apresentador.',
7488
+ hintKey: 'presentationFields.appearance.hint',
7489
+ },
7490
+ {
7491
+ name: 'presentation.icon',
7492
+ label: 'Ícone',
7493
+ labelKey: 'presentationFields.icon.label',
7494
+ editorType: 'text',
7495
+ group: 'Apresentação',
7496
+ groupKey: 'presentationFields.group',
7497
+ row: 'presentation.content',
7498
+ inline: true,
7499
+ placeholder: 'schedule',
7500
+ hint: 'Nome do ícone Material Symbols, sem HTML.',
7501
+ hintKey: 'presentationFields.icon.hint',
7502
+ },
7503
+ {
7504
+ name: 'presentation.label',
7505
+ label: 'Rótulo',
7506
+ labelKey: 'presentationFields.label.label',
7507
+ editorType: 'text',
7508
+ group: 'Apresentação',
7509
+ groupKey: 'presentationFields.group',
7510
+ row: 'presentation.content',
7511
+ placeholder: 'Pendente',
7512
+ placeholderKey: 'presentationFields.label.placeholder',
7513
+ hint: 'Texto semântico exibido por status, chip ou badge.',
7514
+ hintKey: 'presentationFields.label.hint',
7515
+ },
7516
+ {
7517
+ name: 'presentation.badge',
7518
+ label: 'Badge',
7519
+ labelKey: 'presentationFields.badge.label',
7520
+ editorType: 'text',
7521
+ group: 'Apresentação',
7522
+ groupKey: 'presentationFields.group',
7523
+ row: 'presentation.content',
7524
+ inline: true,
7525
+ placeholder: 'SLA',
7526
+ hint: 'Marcador secundário opcional junto ao valor.',
7527
+ hintKey: 'presentationFields.badge.hint',
7528
+ },
7529
+ {
7530
+ name: 'presentation.tooltip',
7531
+ label: 'Tooltip',
7532
+ labelKey: 'presentationFields.tooltip.label',
7533
+ editorType: 'text',
7534
+ group: 'Apresentação',
7535
+ groupKey: 'presentationFields.group',
7536
+ placeholder: 'Aguardando aprovação fiscal',
7537
+ placeholderKey: 'presentationFields.tooltip.placeholder',
7538
+ hint: 'Explicação curta exibida como tooltip.',
7539
+ hintKey: 'presentationFields.tooltip.hint',
7540
+ },
7541
+ {
7542
+ name: 'presentationRules',
7543
+ label: 'Regras de apresentação',
7544
+ labelKey: 'presentationFields.presentationRules.label',
7545
+ editorType: 'presentation-rules-builder',
7546
+ group: 'Apresentação',
7547
+ groupKey: 'presentationFields.group',
7548
+ placeholder: '[\n {\n "when": { "===": [{ "var": "value" }, "BLOQUEADO"] },\n "set": { "tone": "danger", "icon": "lock", "label": "Bloqueado" }\n }\n]',
7549
+ hint: 'Builder guiado para regras Json Logic ordenadas que alteram somente a apresentação. Regras posteriores vencem.',
7550
+ hintKey: 'presentationFields.presentationRules.hint',
7551
+ },
7552
+ ];
7553
+
6041
7554
  /** Garante que minLength <= maxLength quando ambos definidos. */
6042
7555
  function minLenLeMaxLen(group) {
6043
7556
  const v = group.value || {};
@@ -8840,8 +10353,20 @@ class FieldMetadataEditorComponent {
8840
10353
  patch.links = parsedLinks;
8841
10354
  }
8842
10355
  }
10356
+ if (typeof patch.shortcuts === 'string') {
10357
+ const parsedShortcuts = this.parseArrayTextarea(patch.shortcuts);
10358
+ if (parsedShortcuts) {
10359
+ patch.shortcuts = parsedShortcuts;
10360
+ }
10361
+ }
8843
10362
  this.normalizeEntityLookupPatchValues(patch);
8844
10363
  this.normalizeArrayPatchValues(patch);
10364
+ this.normalizePresentationPatchValues(patch);
10365
+ }
10366
+ normalizePresentationPatchValues(patch) {
10367
+ this.normalizeJsonObjectProperty(patch, 'valuePresentation');
10368
+ this.normalizeJsonObjectProperty(patch, 'presentation');
10369
+ this.normalizeJsonArrayProperty(patch, 'presentationRules');
8845
10370
  }
8846
10371
  normalizeEntityLookupPatchValues(patch) {
8847
10372
  const optionSource = patch?.optionSource;
@@ -9033,7 +10558,8 @@ class FieldMetadataEditorComponent {
9033
10558
  }
9034
10559
  withSharedFieldMetadataProperties(props) {
9035
10560
  const propNames = new Set(props.map((prop) => prop.name));
9036
- const missingSharedProps = fieldSubmitProperties.filter((prop) => !propNames.has(prop.name));
10561
+ const sharedProps = [...fieldPresentationProperties, ...fieldSubmitProperties];
10562
+ const missingSharedProps = sharedProps.filter((prop) => !propNames.has(prop.name));
9037
10563
  return missingSharedProps.length ? [...props, ...missingSharedProps] : props;
9038
10564
  }
9039
10565
  get isArrayEditor() {
@@ -9215,7 +10741,7 @@ class CascadeRulesService {
9215
10741
  return map;
9216
10742
  }
9217
10743
  hydrateRule(field) {
9218
- const deps = (field.dependencyFields ??
10744
+ const deps = this.normalizeDependencyFields(field.dependencyFields ??
9219
10745
  field.optionSource?.dependsOn);
9220
10746
  if (!deps || deps.length === 0)
9221
10747
  return null;
@@ -9224,7 +10750,7 @@ class CascadeRulesService {
9224
10750
  null;
9225
10751
  const vm = {
9226
10752
  targetField: field.name,
9227
- dependencyFields: [...deps],
10753
+ dependencyFields: deps,
9228
10754
  enableDependencyCascade: field.enableDependencyCascade === false ? false : true,
9229
10755
  resetOnDependentChange: !!field.resetOnDependentChange,
9230
10756
  dependencyFilterMap,
@@ -9253,6 +10779,18 @@ class CascadeRulesService {
9253
10779
  patch.dependencyDebounceMs = Number(rule.dependencyDebounceMs);
9254
10780
  return patch;
9255
10781
  }
10782
+ clearPatch() {
10783
+ return {
10784
+ dependencyFields: [],
10785
+ enableDependencyCascade: false,
10786
+ resetOnDependentChange: false,
10787
+ dependencyFilterMap: null,
10788
+ dependencyValuePath: null,
10789
+ dependencyMergeStrategy: null,
10790
+ dependencyDebounceMs: null,
10791
+ dependencyLoadOnChange: null,
10792
+ };
10793
+ }
9256
10794
  /** Suggest a basic mapping dep->dep and a default valuePath */
9257
10795
  suggest(rule, inferredValueKey) {
9258
10796
  const map = {};
@@ -9263,6 +10801,20 @@ class CascadeRulesService {
9263
10801
  dependencyValuePath: rule.dependencyValuePath || inferredValueKey || 'id',
9264
10802
  };
9265
10803
  }
10804
+ normalizeDependencyFields(value) {
10805
+ if (Array.isArray(value)) {
10806
+ const deps = value
10807
+ .filter((item) => typeof item === 'string')
10808
+ .map((item) => item.trim())
10809
+ .filter(Boolean);
10810
+ return deps.length ? deps : null;
10811
+ }
10812
+ if (typeof value === 'string') {
10813
+ const trimmed = value.trim();
10814
+ return trimmed ? [trimmed] : null;
10815
+ }
10816
+ return null;
10817
+ }
9266
10818
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CascadeRulesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
9267
10819
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CascadeRulesService, providedIn: 'root' });
9268
10820
  }
@@ -9299,7 +10851,6 @@ class CascadeManagerTabComponent {
9299
10851
  this.svc = svc;
9300
10852
  }
9301
10853
  ngOnInit() {
9302
- console.log('CascadeManagerTabComponent ngOnInit', this.fields);
9303
10854
  // hydrate from input fields
9304
10855
  const map = new Map();
9305
10856
  for (const f of this.fields) {
@@ -9314,7 +10865,6 @@ class CascadeManagerTabComponent {
9314
10865
  }
9315
10866
  }
9316
10867
  ngOnChanges(changes) {
9317
- console.log('CascadeManagerTabComponent ngOnChanges', changes);
9318
10868
  if (changes['fields'] && !changes['fields'].firstChange) {
9319
10869
  // Re-hydrate rules from incoming fields while preserving existing ones when possible
9320
10870
  const incoming = this.fields || [];
@@ -9446,18 +10996,8 @@ class CascadeManagerTabComponent {
9446
10996
  const next = new Map(this.rules());
9447
10997
  next.delete(name);
9448
10998
  this.rules.set(next);
9449
- // Removing means clearing the cascade-related keys; send empty patch
9450
10999
  const patch = {
9451
- [name]: {
9452
- dependencyFields: [],
9453
- enableDependencyCascade: false,
9454
- resetOnDependentChange: false,
9455
- dependencyFilterMap: undefined,
9456
- dependencyValuePath: undefined,
9457
- dependencyMergeStrategy: undefined,
9458
- dependencyDebounceMs: undefined,
9459
- dependencyLoadOnChange: undefined,
9460
- },
11000
+ [name]: this.svc.clearPatch(),
9461
11001
  };
9462
11002
  this.apply.emit(patch);
9463
11003
  }
@@ -9934,7 +11474,7 @@ class CascadeManagerTabComponent {
9934
11474
  </div>
9935
11475
  }
9936
11476
  </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"] }] });
11477
+ `, 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
11478
  }
9939
11479
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CascadeManagerTabComponent, decorators: [{
9940
11480
  type: Component,
@@ -10288,6 +11828,86 @@ const BASE_CAPABILITIES = [
10288
11828
  valueKind: 'object',
10289
11829
  description: 'Root configuration payload for the metadata editor.',
10290
11830
  },
11831
+ {
11832
+ path: 'seed.valuePresentation',
11833
+ category: 'transform',
11834
+ valueKind: 'object',
11835
+ description: 'Canonical readonly value formatter metadata edited on FieldMetadata.',
11836
+ safetyNotes: 'Use canonical valuePresentation objects only; do not encode CSS, HTML, commands, or display-only aliases.',
11837
+ },
11838
+ {
11839
+ path: 'seed.presentation',
11840
+ category: 'appearance',
11841
+ valueKind: 'object',
11842
+ description: 'Semantic presentationMode state for FieldMetadata, including presenter, tone, icon, label and badge intent.',
11843
+ safetyNotes: 'Presentation metadata must remain semantic and renderer-safe; do not include raw HTML or imperative behavior.',
11844
+ },
11845
+ {
11846
+ path: 'seed.presentation.presenter',
11847
+ category: 'appearance',
11848
+ valueKind: 'enum',
11849
+ allowedValues: ['text', 'badge', 'chip', 'status', 'iconValue', 'progress', 'rating'],
11850
+ description: 'Guided semantic presenter for presentationMode.',
11851
+ },
11852
+ {
11853
+ path: 'seed.presentation.tone',
11854
+ category: 'appearance',
11855
+ valueKind: 'enum',
11856
+ allowedValues: ['neutral', 'info', 'success', 'warning', 'danger'],
11857
+ description: 'Guided semantic tone for presentationMode.',
11858
+ },
11859
+ {
11860
+ path: 'seed.presentation.appearance',
11861
+ category: 'appearance',
11862
+ valueKind: 'enum',
11863
+ allowedValues: ['plain', 'soft', 'outlined', 'filled'],
11864
+ description: 'Guided visual emphasis for presentationMode.',
11865
+ },
11866
+ {
11867
+ path: 'seed.presentation.icon',
11868
+ category: 'appearance',
11869
+ valueKind: 'string',
11870
+ description: 'Material Symbols icon name used by semantic presentation.',
11871
+ },
11872
+ {
11873
+ path: 'seed.presentation.label',
11874
+ category: 'identity',
11875
+ valueKind: 'string',
11876
+ description: 'Semantic label shown by status, chip and badge presenters.',
11877
+ },
11878
+ {
11879
+ path: 'seed.presentation.badge',
11880
+ category: 'appearance',
11881
+ valueKind: 'string',
11882
+ description: 'Secondary badge marker shown with the presentation value.',
11883
+ },
11884
+ {
11885
+ path: 'seed.presentation.tooltip',
11886
+ category: 'accessibility',
11887
+ valueKind: 'string',
11888
+ description: 'Tooltip text for the presentation wrapper.',
11889
+ },
11890
+ {
11891
+ path: 'seed.presentationRules',
11892
+ category: 'behavior',
11893
+ valueKind: 'array',
11894
+ description: 'Ordered Json Logic presentation rules edited by a guided builder for common conditions and semantic visual effects.',
11895
+ safetyNotes: 'Rules may only affect tone, icon, label, badge, appearance and tooltip; they must not mutate submitted values or execute commands.',
11896
+ },
11897
+ {
11898
+ path: 'seed.shortcuts',
11899
+ category: 'behavior',
11900
+ valueKind: 'array',
11901
+ description: 'Canonical dateRange/inlineDateRange shortcut catalog with built-in ids and backend-resolved static business periods.',
11902
+ safetyNotes: 'Use dateRangeShortcuts.configure; JSON metadata must not contain calculateRange, executable expressions, fiscal/legal/electoral rules, holiday rules, or business-day counting logic.',
11903
+ },
11904
+ {
11905
+ path: 'seed.inlineQuickPresets.position',
11906
+ category: 'layout',
11907
+ valueKind: 'enum',
11908
+ allowedValues: ['auto', 'footer', 'start', 'end'],
11909
+ description: 'Logical placement for inline date range quick presets, with runtime fallback for narrow/touch layouts.',
11910
+ },
10291
11911
  ];
10292
11912
  const METADATA_EDITOR_AI_CAPABILITY_CATALOG = {
10293
11913
  version: '1.0.0',
@@ -10320,6 +11940,16 @@ const fieldMetadataPropertySchema = {
10320
11940
  'hint',
10321
11941
  'helpText',
10322
11942
  'tooltip',
11943
+ 'valuePresentation',
11944
+ 'presentation',
11945
+ 'presentation.presenter',
11946
+ 'presentation.tone',
11947
+ 'presentation.appearance',
11948
+ 'presentation.icon',
11949
+ 'presentation.label',
11950
+ 'presentation.badge',
11951
+ 'presentation.tooltip',
11952
+ 'presentationRules',
10323
11953
  'options',
10324
11954
  'optionSource',
10325
11955
  'validators',
@@ -10395,6 +12025,48 @@ const cascadeSchema = {
10395
12025
  },
10396
12026
  },
10397
12027
  };
12028
+ const staticDateRangeShortcutSchema = {
12029
+ type: 'object',
12030
+ required: ['id', 'label', 'startDate', 'endDate'],
12031
+ additionalProperties: false,
12032
+ properties: {
12033
+ id: { type: 'string', minLength: 1 },
12034
+ label: { type: 'string', minLength: 1 },
12035
+ startDate: { type: 'string', pattern: '^\\d{4}-\\d{2}-\\d{2}$' },
12036
+ endDate: { type: 'string', pattern: '^\\d{4}-\\d{2}-\\d{2}$' },
12037
+ timeZone: { type: 'string' },
12038
+ icon: { type: 'string' },
12039
+ description: { type: 'string' },
12040
+ tone: { enum: ['neutral', 'info', 'success', 'warning'] },
12041
+ effectiveFrom: { type: 'string', pattern: '^\\d{4}-\\d{2}-\\d{2}$' },
12042
+ effectiveTo: { type: 'string', pattern: '^\\d{4}-\\d{2}-\\d{2}$' },
12043
+ order: { type: 'number' },
12044
+ },
12045
+ };
12046
+ const dateRangeShortcutsSchema = {
12047
+ type: 'object',
12048
+ required: ['shortcuts'],
12049
+ properties: {
12050
+ shortcuts: {
12051
+ type: 'array',
12052
+ items: {
12053
+ anyOf: [
12054
+ { enum: ['today', 'yesterday', 'thisWeek', 'lastWeek', 'thisMonth', 'lastMonth', 'thisYear', 'lastYear'] },
12055
+ staticDateRangeShortcutSchema,
12056
+ ],
12057
+ },
12058
+ },
12059
+ inlineQuickPresets: {
12060
+ type: 'object',
12061
+ properties: {
12062
+ enabled: { type: 'boolean' },
12063
+ maxVisible: { type: 'number' },
12064
+ position: { enum: ['auto', 'footer', 'start', 'end'] },
12065
+ },
12066
+ },
12067
+ applyMode: { enum: ['auto', 'explicit'] },
12068
+ },
12069
+ };
10398
12070
  const PRAXIS_METADATA_EDITOR_AUTHORING_MANIFEST = {
10399
12071
  schemaVersion: '1.0.0',
10400
12072
  componentId: 'praxis-metadata-editor',
@@ -10415,6 +12087,7 @@ const PRAXIS_METADATA_EDITOR_AUTHORING_MANIFEST = {
10415
12087
  { kind: 'cascade', resolver: 'metadata-editor-cascade-rules', description: 'Cascade dependencies and dependency filter mappings edited through CascadeManagerTab.' },
10416
12088
  { kind: 'renderer', resolver: 'metadata-editor-renderer-property', description: 'Dynamic editor renderer property coverage and editor component mapping.' },
10417
12089
  { kind: 'validation', resolver: 'field-metadata-validation-rules', description: 'Field validation rules and contextual validators.' },
12090
+ { kind: 'dateRangeShortcuts', resolver: 'metadata-editor-date-range-shortcut-catalog', description: 'Governed static date range shortcut catalog for dateRange and inlineDateRange metadata.' },
10418
12091
  { kind: 'contextHint', resolver: 'metadata-editor-context-hints', description: 'Authoring hints, help text and contextual guidance for metadata editors.' },
10419
12092
  { kind: 'normalization', resolver: 'metadata-editor-schema-normalizer', description: 'Schema normalization and seed hydration rules.' },
10420
12093
  ],
@@ -10606,6 +12279,28 @@ const PRAXIS_METADATA_EDITOR_AUTHORING_MANIFEST = {
10606
12279
  submissionImpact: 'visual-only',
10607
12280
  preconditions: ['field-metadata-loaded'],
10608
12281
  },
12282
+ {
12283
+ operationId: 'dateRangeShortcuts.configure',
12284
+ title: 'Configure governed date range shortcuts',
12285
+ scope: 'fieldMetadataPath',
12286
+ targetKind: 'dateRangeShortcuts',
12287
+ target: { kind: 'dateRangeShortcuts', resolver: 'metadata-editor-date-range-shortcut-catalog', ambiguityPolicy: 'fail', required: false },
12288
+ inputSchema: dateRangeShortcutsSchema,
12289
+ effects: [{ kind: 'compile-domain-patch', handler: 'metadata-date-range-shortcuts-configure', handlerContract: {
12290
+ reads: ['fieldMetadata.controlType', 'fieldMetadata.shortcuts', 'fieldMetadata.inlineQuickPresets', 'fieldMetadata.inlineOverlay', 'ConfigRegistryService'],
12291
+ writes: ['fieldMetadata.shortcuts', 'fieldMetadata.inlineQuickPresets', 'fieldMetadata.inlineOverlay.applyMode'],
12292
+ identityKeys: ['field.name', 'shortcuts[].id'],
12293
+ inputSchema: dateRangeShortcutsSchema,
12294
+ failureModes: ['control-type-not-date-range', 'duplicate-shortcut-id', 'invalid-static-date-range-shortcut', 'unsupported-shortcut-tone', 'executable-shortcut-metadata'],
12295
+ description: 'Configures built-in and backend-resolved static date range shortcuts through the canonical shortcuts array; JSON metadata may not contain calculateRange or executable expressions.',
12296
+ } }],
12297
+ destructive: false,
12298
+ requiresConfirmation: false,
12299
+ validators: ['date-range-shortcuts-static-canonical', 'date-range-shortcuts-no-executable-metadata', 'date-range-shortcuts-unique-ids', 'metadata-round-trip'],
12300
+ affectedPaths: ['fieldMetadata.shortcuts', 'fieldMetadata.inlineQuickPresets', 'fieldMetadata.inlineOverlay.applyMode'],
12301
+ submissionImpact: 'affects-schema-backed-data',
12302
+ preconditions: ['field-metadata-loaded', 'editor-properties-loaded'],
12303
+ },
10609
12304
  {
10610
12305
  operationId: 'normalization.apply',
10611
12306
  title: 'Apply schema normalization',
@@ -10651,6 +12346,9 @@ const PRAXIS_METADATA_EDITOR_AUTHORING_MANIFEST = {
10651
12346
  { validatorId: 'context-validator-registered', level: 'error', code: 'METADATA_CONTEXT_VALIDATOR_REGISTERED', description: 'Contextual validator must be registered before use.' },
10652
12347
  { validatorId: 'context-hint-shape-canonical', level: 'error', code: 'METADATA_CONTEXT_HINT_CANONICAL', description: 'Context hints must remain structured metadata, not free-form host-only strings.' },
10653
12348
  { validatorId: 'context-hint-i18n-compatible', level: 'warning', code: 'METADATA_CONTEXT_HINT_I18N_COMPATIBLE', description: 'Authoring hint text must remain compatible with metadata-editor i18n.' },
12349
+ { validatorId: 'date-range-shortcuts-static-canonical', level: 'error', code: 'METADATA_DATE_RANGE_SHORTCUTS_STATIC_CANONICAL', description: 'Date range shortcuts must be built-in ids or static backend-resolved ranges with id, label, startDate and endDate.' },
12350
+ { validatorId: 'date-range-shortcuts-no-executable-metadata', level: 'error', code: 'METADATA_DATE_RANGE_SHORTCUTS_NO_EXECUTABLE_METADATA', description: 'Metadata shortcuts must not contain calculateRange, executable expressions, local calendar-law rules, or frontend business-day logic.' },
12351
+ { validatorId: 'date-range-shortcuts-unique-ids', level: 'error', code: 'METADATA_DATE_RANGE_SHORTCUTS_UNIQUE_IDS', description: 'Static business shortcuts must use unique ids inside the date range field.' },
10654
12352
  { validatorId: 'normalization-preserves-canonical-fields', level: 'error', code: 'METADATA_NORMALIZATION_PRESERVES_FIELDS', description: 'Normalization must not drop canonical advanced FieldMetadata properties.' },
10655
12353
  { validatorId: 'runtime-editor-round-trip', level: 'error', code: 'METADATA_RUNTIME_EDITOR_ROUND_TRIP', description: 'Runtime and editor must consume the same normalized metadata shape.' },
10656
12354
  { validatorId: 'metadata-round-trip', level: 'error', code: 'METADATA_ROUND_TRIP', description: 'Open, edit, apply/save, reopen and runtime consume must preserve metadata.' },
@@ -10659,6 +12357,7 @@ const PRAXIS_METADATA_EDITOR_AUTHORING_MANIFEST = {
10659
12357
  'FieldMetadata is the canonical edited document shape; backend x-ui-derived metadata must not be rewritten into host-only aliases.',
10660
12358
  'Control type changes require both dynamic-fields discovery and metadata-editor visual coverage.',
10661
12359
  'Option source cascades preserve x-ui optionSource.dependsOn and optionSource.dependencyFilterMap semantics.',
12360
+ 'Date range business shortcuts are authored as built-in ids or backend-resolved static ranges; browser metadata never carries calculateRange code or business calendar rules.',
10662
12361
  'JSON-only support is incomplete when visual editor coverage is required.',
10663
12362
  'Schema normalization must preserve advanced canonical properties through open, edit, apply/save, reopen and runtime consumption.',
10664
12363
  ],
@@ -10670,6 +12369,8 @@ const PRAXIS_METADATA_EDITOR_AUTHORING_MANIFEST = {
10670
12369
  { id: 'configure-renderer', request: 'Expose placeholder in the visual editor as a text field.', operationId: 'renderer.configure', params: { propertyName: 'placeholder', editorType: 'text', group: 'Presentation' }, isPositive: true },
10671
12370
  { id: 'add-required-validation', request: 'Make this field required with a validation message.', operationId: 'validationRule.add', params: { rule: { type: 'required' }, message: 'Required field' }, isPositive: true },
10672
12371
  { id: 'set-context-hint', request: 'Add a contextual hint for sales users.', operationId: 'contextHint.set', params: { hintPath: 'helpText', value: 'Use the legal customer name.' }, isPositive: true },
12372
+ { id: 'configure-date-range-business-shortcut', request: 'Add a fiscal competence period resolved by the backend to this inline date range.', operationId: 'dateRangeShortcuts.configure', params: { shortcuts: ['thisMonth', { id: 'competencia-fiscal-marco', label: 'Fiscal competence March', startDate: '2026-03-01', endDate: '2026-03-31', timeZone: 'America/Sao_Paulo', tone: 'success' }], inlineQuickPresets: { enabled: true, position: 'start', maxVisible: 4 }, applyMode: 'explicit' }, isPositive: true },
12373
+ { id: 'reject-date-range-executable-shortcut', request: 'Add a date range shortcut that calculates fiscal holidays in calculateRange from JSON.', operationId: 'dateRangeShortcuts.configure', params: { shortcuts: [{ id: 'legal-days', label: 'Legal days', startDate: '2026-03-01', endDate: '2026-03-31', calculateRange: '() => legalCalendar()' }] }, isPositive: false },
10673
12374
  { id: 'normalize-seed', request: 'Normalize this imported field metadata while preserving advanced properties.', operationId: 'normalization.apply', params: { mode: 'preserve-advanced-properties', preserveUnknownCanonicalFields: true }, isPositive: true },
10674
12375
  { id: 'reject-unknown-control', request: 'Use an unknown control type named magic-picker.', operationId: 'controlType.set', params: { controlType: 'magic-picker' }, isPositive: false },
10675
12376
  { id: 'reject-json-only-coverage', request: 'Support this field only by editing raw JSON.', operationId: 'renderer.configure', params: { propertyName: 'optionSource', editorType: 'json' }, isPositive: false },