@praxisui/metadata-editor 8.0.0-beta.9 → 8.0.0-beta.91

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,19 +1,19 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { Injectable, inject, Component, Input, ViewEncapsulation, EventEmitter, DestroyRef, Output, signal, ENVIRONMENT_INITIALIZER } from '@angular/core';
3
- import * as i3 from '@angular/forms';
4
- import { FormControl, FormGroup, ReactiveFormsModule, FormsModule } from '@angular/forms';
5
- import * as i3$1 from '@angular/common';
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';
6
6
  import { CommonModule } from '@angular/common';
7
7
  import { DynamicFieldLoaderDirective } from '@praxisui/dynamic-fields';
8
8
  import * as i2$1 from '@praxisui/core';
9
- import { FieldControlType, PraxisIconDirective, INLINE_FILTER_CONTROL_TYPES, resolveInlineFilterControlType, normalizeControlTypeToken, ComponentMetadataRegistry } from '@praxisui/core';
9
+ import { FieldControlType, PraxisIconDirective, providePraxisI18n, PraxisI18nService, INLINE_FILTER_CONTROL_TYPES, resolveInlineFilterControlType, normalizeControlTypeToken, ComponentMetadataRegistry } from '@praxisui/core';
10
10
  import * as i5 from '@angular/material/expansion';
11
11
  import { MatExpansionModule } from '@angular/material/expansion';
12
12
  import * as i6 from '@angular/material/icon';
13
13
  import { MatIconModule } from '@angular/material/icon';
14
- import * as i5$1 from '@angular/material/button';
14
+ import * as i4 from '@angular/material/button';
15
15
  import { MatButtonModule } from '@angular/material/button';
16
- import * as i1 from '@angular/material/dialog';
16
+ import * as i1$1 from '@angular/material/dialog';
17
17
  import { MatDialogRef, MatDialogModule } from '@angular/material/dialog';
18
18
  import * as i8 from '@angular/material/tooltip';
19
19
  import { MatTooltipModule } from '@angular/material/tooltip';
@@ -22,19 +22,19 @@ import { MatListModule } from '@angular/material/list';
22
22
  import { SETTINGS_PANEL_DATA } from '@praxisui/settings-panel';
23
23
  import { BehaviorSubject } from 'rxjs';
24
24
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
25
- import * as i7 from '@angular/material/input';
25
+ import * as i6$1 from '@angular/material/input';
26
26
  import { MatInputModule } from '@angular/material/input';
27
27
  import { MatChipsModule } from '@angular/material/chips';
28
28
  import { MatDividerModule } from '@angular/material/divider';
29
- import * as i8$1 from '@angular/material/select';
29
+ import * as i7 from '@angular/material/select';
30
30
  import { MatSelectModule } from '@angular/material/select';
31
- import * as i9 from '@angular/material/checkbox';
31
+ import * as i8$1 from '@angular/material/checkbox';
32
32
  import { MatCheckboxModule } from '@angular/material/checkbox';
33
- import * as i11 from '@angular/material/menu';
33
+ import * as i10 from '@angular/material/menu';
34
34
  import { MatMenuModule } from '@angular/material/menu';
35
- import * as i12 from '@angular/material/autocomplete';
35
+ import * as i11 from '@angular/material/autocomplete';
36
36
  import { MatAutocompleteModule } from '@angular/material/autocomplete';
37
- import * as i13 from '@angular/cdk/scrolling';
37
+ import * as i12 from '@angular/cdk/scrolling';
38
38
  import { ScrollingModule } from '@angular/cdk/scrolling';
39
39
 
40
40
  class ConfigRegistryService {
@@ -51,10 +51,10 @@ class ConfigRegistryService {
51
51
  count: props.length,
52
52
  }));
53
53
  }
54
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ConfigRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
55
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ConfigRegistryService, providedIn: 'root' });
54
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ConfigRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
55
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ConfigRegistryService, providedIn: 'root' });
56
56
  }
57
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ConfigRegistryService, decorators: [{
57
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ConfigRegistryService, decorators: [{
58
58
  type: Injectable,
59
59
  args: [{ providedIn: 'root' }]
60
60
  }] });
@@ -69,10 +69,10 @@ class ContextValidatorRegistryService {
69
69
  get(controlType) {
70
70
  return this.map.get(controlType) ?? [];
71
71
  }
72
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ContextValidatorRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
73
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ContextValidatorRegistryService, providedIn: 'root' });
72
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ContextValidatorRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
73
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ContextValidatorRegistryService, providedIn: 'root' });
74
74
  }
75
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ContextValidatorRegistryService, decorators: [{
75
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ContextValidatorRegistryService, decorators: [{
76
76
  type: Injectable,
77
77
  args: [{ providedIn: 'root' }]
78
78
  }] });
@@ -182,10 +182,10 @@ class SchemaNormalizerService {
182
182
  getByPath(obj, path) {
183
183
  return path.split('.').reduce((acc, key) => (acc == null ? acc : acc[key]), obj);
184
184
  }
185
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SchemaNormalizerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
186
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SchemaNormalizerService, providedIn: 'root' });
185
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SchemaNormalizerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
186
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SchemaNormalizerService, providedIn: 'root' });
187
187
  }
188
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SchemaNormalizerService, decorators: [{
188
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SchemaNormalizerService, decorators: [{
189
189
  type: Injectable,
190
190
  args: [{ providedIn: 'root' }]
191
191
  }] });
@@ -276,13 +276,13 @@ class DynamicFormFactoryService {
276
276
  }
277
277
  return result;
278
278
  }
279
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DynamicFormFactoryService, deps: [{ token: i3.FormBuilder }, { token: ContextValidatorRegistryService }], target: i0.ɵɵFactoryTarget.Injectable });
280
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DynamicFormFactoryService, providedIn: 'root' });
279
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormFactoryService, deps: [{ token: i1.FormBuilder }, { token: ContextValidatorRegistryService }], target: i0.ɵɵFactoryTarget.Injectable });
280
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormFactoryService, providedIn: 'root' });
281
281
  }
282
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DynamicFormFactoryService, decorators: [{
282
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormFactoryService, decorators: [{
283
283
  type: Injectable,
284
284
  args: [{ providedIn: 'root' }]
285
- }], ctorParameters: () => [{ type: i3.FormBuilder }, { type: ContextValidatorRegistryService }] });
285
+ }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: ContextValidatorRegistryService }] });
286
286
 
287
287
  class EditorComponentRegistryService {
288
288
  registry = new Map();
@@ -305,10 +305,10 @@ class EditorComponentRegistryService {
305
305
  isRegistered(editorType) {
306
306
  return this.registry.has(editorType);
307
307
  }
308
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: EditorComponentRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
309
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: EditorComponentRegistryService, providedIn: 'root' });
308
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: EditorComponentRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
309
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: EditorComponentRegistryService, providedIn: 'root' });
310
310
  }
311
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: EditorComponentRegistryService, decorators: [{
311
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: EditorComponentRegistryService, decorators: [{
312
312
  type: Injectable,
313
313
  args: [{ providedIn: 'root' }]
314
314
  }] });
@@ -349,8 +349,8 @@ class RegexHelpDialogComponent {
349
349
  choose(pattern) {
350
350
  this.dialogRef.close(pattern);
351
351
  }
352
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: RegexHelpDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
353
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: RegexHelpDialogComponent, isStandalone: true, selector: "praxis-regex-help-dialog", ngImport: i0, template: `
352
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: RegexHelpDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
353
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: RegexHelpDialogComponent, isStandalone: true, selector: "praxis-regex-help-dialog", ngImport: i0, template: `
354
354
  <h2 mat-dialog-title>Modelos de Expressões Regulares</h2>
355
355
  <div mat-dialog-content>
356
356
  <p>Selecione um dos modelos abaixo para preencher o campo ou use como base:</p>
@@ -371,14 +371,14 @@ class RegexHelpDialogComponent {
371
371
  <div mat-dialog-actions>
372
372
  <button class="mat-mdc-button mdc-button" mat-dialog-close>Fechar</button>
373
373
  </div>
374
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i2.MatNavList, selector: "mat-nav-list", exportAs: ["matNavList"] }, { kind: "component", type: i2.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "directive", type: i2.MatListItemLine, selector: "[matListItemLine]" }, { kind: "directive", type: i2.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "ngmodule", type: MatButtonModule }] });
374
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1$1.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: i1$1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1$1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1$1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i2.MatNavList, selector: "mat-nav-list", exportAs: ["matNavList"] }, { kind: "component", type: i2.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "directive", type: i2.MatListItemLine, selector: "[matListItemLine]" }, { kind: "directive", type: i2.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "ngmodule", type: MatButtonModule }] });
375
375
  }
376
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: RegexHelpDialogComponent, decorators: [{
376
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: RegexHelpDialogComponent, decorators: [{
377
377
  type: Component,
378
378
  args: [{
379
379
  selector: 'praxis-regex-help-dialog',
380
380
  standalone: true,
381
- imports: [CommonModule, MatDialogModule, MatListModule, MatButtonModule],
381
+ imports: [MatDialogModule, MatListModule, MatButtonModule],
382
382
  template: `
383
383
  <h2 mat-dialog-title>Modelos de Expressões Regulares</h2>
384
384
  <div mat-dialog-content>
@@ -530,6 +530,7 @@ class DynamicEditorRendererComponent {
530
530
  const order = [
531
531
  'geral',
532
532
  'apresent',
533
+ 'display',
533
534
  'valida',
534
535
  'formato',
535
536
  'comport',
@@ -594,7 +595,7 @@ class DynamicEditorRendererComponent {
594
595
  .replace(/[ñ]/g, 'n');
595
596
  if (normalized.includes('geral'))
596
597
  return 'widgets';
597
- if (normalized.includes('apresent'))
598
+ if (normalized.includes('apresent') || normalized.includes('display'))
598
599
  return 'slideshow';
599
600
  if (normalized.includes('dados') || normalized.includes('opcoes'))
600
601
  return 'list_alt';
@@ -683,79 +684,87 @@ class DynamicEditorRendererComponent {
683
684
  this.form?.markAsDirty();
684
685
  }
685
686
  }
686
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DynamicEditorRendererComponent, deps: [{ token: i1.MatDialog }, { token: i2$1.IconPickerService }], target: i0.ɵɵFactoryTarget.Component });
687
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: DynamicEditorRendererComponent, isStandalone: true, selector: "praxis-dynamic-editor-renderer", inputs: { properties: "properties", form: "form" }, usesOnChanges: true, ngImport: i0, template: `
687
+ 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 });
688
+ 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: `
688
689
  <ng-template #item let-field="field" let-index="index" let-content="content">
689
690
  <div class="editor-item" [attr.data-control-type]="field.controlType">
690
691
  <ng-container [ngTemplateOutlet]="content"></ng-container>
691
-
692
+
692
693
  <div class="actions-container">
693
694
  <!-- Botão de ajuda para regex -->
694
- <button
695
- *ngIf="field.name === 'pattern'"
696
- type="button"
697
- mat-icon-button
698
- class="regex-help-btn decorator-btn"
699
- (click)="openRegexHelp()"
700
- aria-label="Exemplos de regex"
701
- matTooltip="Exemplos de regex"
702
- >
703
- <mat-icon [praxisIcon]="'search'"></mat-icon>
704
- </button>
695
+ @if (field.name === 'pattern') {
696
+ <button
697
+ type="button"
698
+ mat-icon-button
699
+ class="regex-help-btn decorator-btn"
700
+ (click)="openRegexHelp()"
701
+ aria-label="Exemplos de regex"
702
+ matTooltip="Exemplos de regex"
703
+ >
704
+ <mat-icon [praxisIcon]="'search'"></mat-icon>
705
+ </button>
706
+ }
705
707
  <!-- Botão para escolher ícone (prefix/suffix) -->
706
- <button
707
- *ngIf="isIconField(field.name)"
708
- type="button"
709
- mat-icon-button
710
- class="icon-picker-btn decorator-btn"
711
- (click)="openIconPicker(field.name)"
712
- aria-label="Escolher ícone"
713
- matTooltip="Escolher ícone"
714
- >
715
- <mat-icon [praxisIcon]="'apps'"></mat-icon>
716
- </button>
708
+ @if (isIconField(field.name)) {
709
+ <button
710
+ type="button"
711
+ mat-icon-button
712
+ class="icon-picker-btn decorator-btn"
713
+ (click)="openIconPicker(field.name)"
714
+ aria-label="Escolher ícone"
715
+ matTooltip="Escolher ícone"
716
+ >
717
+ <mat-icon [praxisIcon]="'apps'"></mat-icon>
718
+ </button>
719
+ }
717
720
  <!-- Botão de ajuda (fallback quando suffix não é aplicado) -->
718
- <button
719
- *ngIf="field.hint && !useHelpSuffix(field)"
720
- type="button"
721
- mat-icon-button
722
- class="hint-btn decorator-btn"
723
- [class.has-icon-picker]="isIconField(field.name)"
724
- [matTooltip]="field.hint"
725
- aria-label="Ajuda"
726
- >
727
- <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
728
- </button>
721
+ @if (field.hint && !useHelpSuffix(field)) {
722
+ <button
723
+ type="button"
724
+ mat-icon-button
725
+ class="hint-btn decorator-btn"
726
+ [class.has-icon-picker]="isIconField(field.name)"
727
+ [matTooltip]="field.hint"
728
+ aria-label="Ajuda"
729
+ >
730
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
731
+ </button>
732
+ }
729
733
  </div>
730
734
  </div>
731
735
  </ng-template>
732
-
736
+
733
737
  <div class="editor-surface">
734
738
  <mat-accordion class="section-accordion" multi>
735
- <mat-expansion-panel class="section-card" *ngFor="let g of groupOrder">
736
- <mat-expansion-panel-header class="section-header">
737
- <mat-panel-title>
738
- <span class="section-title">
739
- <mat-icon class="section-icon" [praxisIcon]="groupIcon(g)"></mat-icon>
740
- {{ g }}
741
- </span>
742
- </mat-panel-title>
743
- </mat-expansion-panel-header>
744
-
745
- <div *ngFor="let row of groupRows[g]" class="editor-row" [ngClass]="rowClass(row)">
746
- <ng-container *ngIf="ready"
747
- dynamicFieldLoader
748
- [fields]="row"
749
- [formGroup]="form"
750
- [itemTemplate]="item">
751
- </ng-container>
752
- </div>
753
- </mat-expansion-panel>
739
+ @for (g of groupOrder; track g) {
740
+ <mat-expansion-panel class="section-card">
741
+ <mat-expansion-panel-header class="section-header">
742
+ <mat-panel-title>
743
+ <span class="section-title">
744
+ <mat-icon class="section-icon" [praxisIcon]="groupIcon(g)"></mat-icon>
745
+ {{ g }}
746
+ </span>
747
+ </mat-panel-title>
748
+ </mat-expansion-panel-header>
749
+ @for (row of groupRows[g]; track row) {
750
+ <div class="editor-row" [ngClass]="rowClass(row)">
751
+ @if (ready) {
752
+ <ng-container
753
+ dynamicFieldLoader
754
+ [fields]="row"
755
+ [formGroup]="form"
756
+ [itemTemplate]="item">
757
+ </ng-container>
758
+ }
759
+ </div>
760
+ }
761
+ </mat-expansion-panel>
762
+ }
754
763
  </mat-accordion>
755
764
  </div>
756
- `, 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$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "debugTrace", "debugTraceLabel", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "renderError", "canvasMouseEnter", "canvasMouseLeave", "canvasClick"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i5.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i5.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i5.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i5.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i5$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }], encapsulation: i0.ViewEncapsulation.None });
765
+ `, 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 });
757
766
  }
758
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DynamicEditorRendererComponent, decorators: [{
767
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicEditorRendererComponent, decorators: [{
759
768
  type: Component,
760
769
  args: [{ selector: 'praxis-dynamic-editor-renderer', standalone: true, imports: [
761
770
  CommonModule,
@@ -771,78 +780,915 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
771
780
  <ng-template #item let-field="field" let-index="index" let-content="content">
772
781
  <div class="editor-item" [attr.data-control-type]="field.controlType">
773
782
  <ng-container [ngTemplateOutlet]="content"></ng-container>
774
-
783
+
775
784
  <div class="actions-container">
776
785
  <!-- Botão de ajuda para regex -->
777
- <button
778
- *ngIf="field.name === 'pattern'"
779
- type="button"
780
- mat-icon-button
781
- class="regex-help-btn decorator-btn"
782
- (click)="openRegexHelp()"
783
- aria-label="Exemplos de regex"
784
- matTooltip="Exemplos de regex"
785
- >
786
- <mat-icon [praxisIcon]="'search'"></mat-icon>
787
- </button>
786
+ @if (field.name === 'pattern') {
787
+ <button
788
+ type="button"
789
+ mat-icon-button
790
+ class="regex-help-btn decorator-btn"
791
+ (click)="openRegexHelp()"
792
+ aria-label="Exemplos de regex"
793
+ matTooltip="Exemplos de regex"
794
+ >
795
+ <mat-icon [praxisIcon]="'search'"></mat-icon>
796
+ </button>
797
+ }
788
798
  <!-- Botão para escolher ícone (prefix/suffix) -->
789
- <button
790
- *ngIf="isIconField(field.name)"
791
- type="button"
792
- mat-icon-button
793
- class="icon-picker-btn decorator-btn"
794
- (click)="openIconPicker(field.name)"
795
- aria-label="Escolher ícone"
796
- matTooltip="Escolher ícone"
797
- >
798
- <mat-icon [praxisIcon]="'apps'"></mat-icon>
799
- </button>
799
+ @if (isIconField(field.name)) {
800
+ <button
801
+ type="button"
802
+ mat-icon-button
803
+ class="icon-picker-btn decorator-btn"
804
+ (click)="openIconPicker(field.name)"
805
+ aria-label="Escolher ícone"
806
+ matTooltip="Escolher ícone"
807
+ >
808
+ <mat-icon [praxisIcon]="'apps'"></mat-icon>
809
+ </button>
810
+ }
800
811
  <!-- Botão de ajuda (fallback quando suffix não é aplicado) -->
801
- <button
802
- *ngIf="field.hint && !useHelpSuffix(field)"
803
- type="button"
804
- mat-icon-button
805
- class="hint-btn decorator-btn"
806
- [class.has-icon-picker]="isIconField(field.name)"
807
- [matTooltip]="field.hint"
808
- aria-label="Ajuda"
809
- >
810
- <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
811
- </button>
812
+ @if (field.hint && !useHelpSuffix(field)) {
813
+ <button
814
+ type="button"
815
+ mat-icon-button
816
+ class="hint-btn decorator-btn"
817
+ [class.has-icon-picker]="isIconField(field.name)"
818
+ [matTooltip]="field.hint"
819
+ aria-label="Ajuda"
820
+ >
821
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
822
+ </button>
823
+ }
812
824
  </div>
813
825
  </div>
814
826
  </ng-template>
815
-
827
+
816
828
  <div class="editor-surface">
817
829
  <mat-accordion class="section-accordion" multi>
818
- <mat-expansion-panel class="section-card" *ngFor="let g of groupOrder">
819
- <mat-expansion-panel-header class="section-header">
820
- <mat-panel-title>
821
- <span class="section-title">
822
- <mat-icon class="section-icon" [praxisIcon]="groupIcon(g)"></mat-icon>
823
- {{ g }}
824
- </span>
825
- </mat-panel-title>
826
- </mat-expansion-panel-header>
827
-
828
- <div *ngFor="let row of groupRows[g]" class="editor-row" [ngClass]="rowClass(row)">
829
- <ng-container *ngIf="ready"
830
- dynamicFieldLoader
831
- [fields]="row"
832
- [formGroup]="form"
833
- [itemTemplate]="item">
834
- </ng-container>
835
- </div>
836
- </mat-expansion-panel>
830
+ @for (g of groupOrder; track g) {
831
+ <mat-expansion-panel class="section-card">
832
+ <mat-expansion-panel-header class="section-header">
833
+ <mat-panel-title>
834
+ <span class="section-title">
835
+ <mat-icon class="section-icon" [praxisIcon]="groupIcon(g)"></mat-icon>
836
+ {{ g }}
837
+ </span>
838
+ </mat-panel-title>
839
+ </mat-expansion-panel-header>
840
+ @for (row of groupRows[g]; track row) {
841
+ <div class="editor-row" [ngClass]="rowClass(row)">
842
+ @if (ready) {
843
+ <ng-container
844
+ dynamicFieldLoader
845
+ [fields]="row"
846
+ [formGroup]="form"
847
+ [itemTemplate]="item">
848
+ </ng-container>
849
+ }
850
+ </div>
851
+ }
852
+ </mat-expansion-panel>
853
+ }
837
854
  </mat-accordion>
838
855
  </div>
839
- `, 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"] }]
840
- }], ctorParameters: () => [{ type: i1.MatDialog }, { type: i2$1.IconPickerService }], propDecorators: { properties: [{
856
+ `, 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"] }]
857
+ }], ctorParameters: () => [{ type: i1$1.MatDialog }, { type: i2$1.IconPickerService }], propDecorators: { properties: [{
841
858
  type: Input
842
859
  }], form: [{
843
860
  type: Input
844
861
  }] } });
845
862
 
863
+ const METADATA_EDITOR_EN_US = {
864
+ 'praxis.metadataEditor.arrayFields.section': 'Collection subfields',
865
+ 'praxis.metadataEditor.arrayFields.title': 'Subfields',
866
+ 'praxis.metadataEditor.arrayFields.add': 'Add subfield',
867
+ 'praxis.metadataEditor.arrayFields.duplicateNames': 'There are duplicate names: {{names}}',
868
+ 'praxis.metadataEditor.arrayFields.empty': 'No local subfields configured.',
869
+ 'praxis.metadataEditor.arrayFields.name': 'Name',
870
+ 'praxis.metadataEditor.arrayFields.nameRequired': 'Enter the subfield name.',
871
+ 'praxis.metadataEditor.arrayFields.nameDuplicated': 'This name is already used in the collection.',
872
+ 'praxis.metadataEditor.arrayFields.label': 'Label',
873
+ 'praxis.metadataEditor.arrayFields.controlType': 'Control',
874
+ 'praxis.metadataEditor.arrayFields.required': 'Required',
875
+ 'praxis.metadataEditor.arrayFields.moveUp': 'Move up',
876
+ 'praxis.metadataEditor.arrayFields.moveDown': 'Move down',
877
+ 'praxis.metadataEditor.arrayFields.duplicate': 'Duplicate',
878
+ 'praxis.metadataEditor.arrayFields.remove': 'Remove',
879
+ 'praxis.metadataEditor.arrayFields.removeBlocked': 'Remove references before deleting: {{references}}',
880
+ 'praxis.metadataEditor.arrayFields.refOnly': 'The item schema comes from itemSchemaRef; create local subfields only when you need an override.',
881
+ 'praxis.metadataEditor.arrayFields.overrideLocal': 'There is an itemSchemaRef and local subfields; local subfields work as an explicit override.',
882
+ 'praxis.metadataEditor.arrayFields.imported': 'Subfields imported. Review them before applying or saving.',
883
+ 'praxis.metadataEditor.arrayFields.localOnly': 'Local subfields will be saved in array.itemSchema.fields.',
884
+ 'praxis.metadataEditor.arrayFields.control.input': 'Text',
885
+ 'praxis.metadataEditor.arrayFields.control.textarea': 'Long text',
886
+ 'praxis.metadataEditor.arrayFields.control.numeric': 'Number',
887
+ 'praxis.metadataEditor.arrayFields.control.currency': 'Currency',
888
+ 'praxis.metadataEditor.arrayFields.control.email': 'Email',
889
+ 'praxis.metadataEditor.arrayFields.control.phone': 'Phone',
890
+ 'praxis.metadataEditor.arrayFields.control.select': 'Select',
891
+ 'praxis.metadataEditor.arrayFields.control.entityLookup': 'Entity lookup',
892
+ 'praxis.metadataEditor.arrayFields.control.toggle': 'Toggle',
893
+ 'praxis.metadataEditor.arrayFields.control.date': 'Date',
894
+ };
895
+
896
+ const METADATA_EDITOR_PT_BR = {
897
+ 'praxis.metadataEditor.arrayFields.section': 'Subcampos da cole\u00e7\u00e3o',
898
+ 'praxis.metadataEditor.arrayFields.title': 'Subcampos',
899
+ 'praxis.metadataEditor.arrayFields.add': 'Adicionar subcampo',
900
+ 'praxis.metadataEditor.arrayFields.duplicateNames': 'Existem nomes duplicados: {{names}}',
901
+ 'praxis.metadataEditor.arrayFields.empty': 'Nenhum subcampo local configurado.',
902
+ 'praxis.metadataEditor.arrayFields.name': 'Nome',
903
+ 'praxis.metadataEditor.arrayFields.nameRequired': 'Informe o nome do subcampo.',
904
+ 'praxis.metadataEditor.arrayFields.nameDuplicated': 'Este nome j\u00e1 est\u00e1 em uso na cole\u00e7\u00e3o.',
905
+ 'praxis.metadataEditor.arrayFields.label': 'Label',
906
+ 'praxis.metadataEditor.arrayFields.controlType': 'Controle',
907
+ 'praxis.metadataEditor.arrayFields.required': 'Obrigat\u00f3rio',
908
+ 'praxis.metadataEditor.arrayFields.moveUp': 'Subir',
909
+ 'praxis.metadataEditor.arrayFields.moveDown': 'Descer',
910
+ 'praxis.metadataEditor.arrayFields.duplicate': 'Duplicar',
911
+ 'praxis.metadataEditor.arrayFields.remove': 'Remover',
912
+ 'praxis.metadataEditor.arrayFields.removeBlocked': 'Remova as refer\u00eancias antes de excluir: {{references}}',
913
+ 'praxis.metadataEditor.arrayFields.refOnly': 'O schema do item vem do itemSchemaRef; crie subcampos locais apenas quando precisar de override.',
914
+ 'praxis.metadataEditor.arrayFields.overrideLocal': 'H\u00e1 um itemSchemaRef e subcampos locais; os subcampos locais funcionam como override expl\u00edcito.',
915
+ 'praxis.metadataEditor.arrayFields.imported': 'Subcampos importados. Revise antes de aplicar ou salvar.',
916
+ 'praxis.metadataEditor.arrayFields.localOnly': 'Subcampos locais ser\u00e3o salvos em array.itemSchema.fields.',
917
+ 'praxis.metadataEditor.arrayFields.control.input': 'Texto',
918
+ 'praxis.metadataEditor.arrayFields.control.textarea': 'Texto longo',
919
+ 'praxis.metadataEditor.arrayFields.control.numeric': 'N\u00famero',
920
+ 'praxis.metadataEditor.arrayFields.control.currency': 'Moeda',
921
+ 'praxis.metadataEditor.arrayFields.control.email': 'E-mail',
922
+ 'praxis.metadataEditor.arrayFields.control.phone': 'Telefone',
923
+ 'praxis.metadataEditor.arrayFields.control.select': 'Sele\u00e7\u00e3o',
924
+ 'praxis.metadataEditor.arrayFields.control.entityLookup': 'Busca de entidade',
925
+ 'praxis.metadataEditor.arrayFields.control.toggle': 'Liga/desliga',
926
+ 'praxis.metadataEditor.arrayFields.control.date': 'Data',
927
+ };
928
+
929
+ function createPraxisMetadataEditorI18nConfig(options = {}) {
930
+ const dictionaries = {
931
+ 'pt-BR': {
932
+ ...METADATA_EDITOR_PT_BR,
933
+ ...(options.dictionaries?.['pt-BR'] ?? {}),
934
+ },
935
+ 'en-US': {
936
+ ...METADATA_EDITOR_EN_US,
937
+ ...(options.dictionaries?.['en-US'] ?? {}),
938
+ },
939
+ };
940
+ for (const [locale, dictionary] of Object.entries(options.dictionaries ?? {})) {
941
+ if (locale === 'pt-BR' || locale === 'en-US') {
942
+ continue;
943
+ }
944
+ dictionaries[locale] = {
945
+ ...(dictionaries[locale] ?? {}),
946
+ ...dictionary,
947
+ };
948
+ }
949
+ return {
950
+ locale: options.locale,
951
+ fallbackLocale: options.fallbackLocale ?? 'pt-BR',
952
+ dictionaries,
953
+ };
954
+ }
955
+ const METADATA_EDITOR_I18N_CONFIG = createPraxisMetadataEditorI18nConfig();
956
+ function providePraxisMetadataEditorI18n(options = {}) {
957
+ return providePraxisI18n(createPraxisMetadataEditorI18nConfig(options));
958
+ }
959
+ function resolvePraxisMetadataEditorText(i18n, key, fallback) {
960
+ const namespacedKey = key.startsWith('praxis.metadataEditor.')
961
+ ? key
962
+ : `praxis.metadataEditor.${key}`;
963
+ return i18n.t(namespacedKey, undefined, fallback) || fallback;
964
+ }
965
+
966
+ class ArrayItemSchemaFieldsEditorComponent {
967
+ static nextInstanceId = 0;
968
+ control = null;
969
+ arrayControl = null;
970
+ formArray = new FormArray([]);
971
+ form = new FormGroup({ fields: this.formArray });
972
+ controlTypeOptions = [
973
+ FieldControlType.INPUT,
974
+ FieldControlType.TEXTAREA,
975
+ FieldControlType.NUMERIC_TEXT_BOX,
976
+ FieldControlType.CURRENCY_INPUT,
977
+ FieldControlType.EMAIL_INPUT,
978
+ FieldControlType.PHONE,
979
+ FieldControlType.SELECT,
980
+ FieldControlType.ENTITY_LOOKUP,
981
+ FieldControlType.TOGGLE,
982
+ FieldControlType.DATE_INPUT,
983
+ ];
984
+ schemaSourceState = 'local-only';
985
+ duplicateNames = [];
986
+ fb = inject(FormBuilder);
987
+ i18n = inject(PraxisI18nService);
988
+ instanceId = ++ArrayItemSchemaFieldsEditorComponent.nextInstanceId;
989
+ externalSubscription;
990
+ internalSubscription;
991
+ originalNames = [];
992
+ importedFieldsFingerprint = null;
993
+ hydrating = false;
994
+ get rows() {
995
+ return this.formArray.controls;
996
+ }
997
+ ngOnChanges(changes) {
998
+ if (changes['control']) {
999
+ this.externalSubscription?.unsubscribe();
1000
+ this.hydrateFromControl();
1001
+ this.externalSubscription = this.control?.valueChanges.subscribe(() => {
1002
+ if (!this.hydrating) {
1003
+ this.hydrateFromControl();
1004
+ }
1005
+ });
1006
+ }
1007
+ if (changes['arrayControl'] || changes['control']) {
1008
+ this.updateSchemaSourceState();
1009
+ }
1010
+ }
1011
+ ngOnDestroy() {
1012
+ this.externalSubscription?.unsubscribe();
1013
+ this.internalSubscription?.unsubscribe();
1014
+ }
1015
+ text(key, fallback, fallbackWithParams, params) {
1016
+ return this.i18n.t(key, params, fallbackWithParams ?? fallback);
1017
+ }
1018
+ controlTypeLabel(controlType) {
1019
+ const labels = {
1020
+ [FieldControlType.INPUT]: this.text('praxis.metadataEditor.arrayFields.control.input', 'Texto'),
1021
+ [FieldControlType.TEXTAREA]: this.text('praxis.metadataEditor.arrayFields.control.textarea', 'Texto longo'),
1022
+ [FieldControlType.NUMERIC_TEXT_BOX]: this.text('praxis.metadataEditor.arrayFields.control.numeric', 'N\u00famero'),
1023
+ [FieldControlType.CURRENCY_INPUT]: this.text('praxis.metadataEditor.arrayFields.control.currency', 'Moeda'),
1024
+ [FieldControlType.EMAIL_INPUT]: this.text('praxis.metadataEditor.arrayFields.control.email', 'E-mail'),
1025
+ [FieldControlType.PHONE]: this.text('praxis.metadataEditor.arrayFields.control.phone', 'Telefone'),
1026
+ [FieldControlType.SELECT]: this.text('praxis.metadataEditor.arrayFields.control.select', 'Sele\u00e7\u00e3o'),
1027
+ [FieldControlType.ENTITY_LOOKUP]: this.text('praxis.metadataEditor.arrayFields.control.entityLookup', 'Busca de entidade'),
1028
+ [FieldControlType.TOGGLE]: this.text('praxis.metadataEditor.arrayFields.control.toggle', 'Liga/desliga'),
1029
+ [FieldControlType.DATE_INPUT]: this.text('praxis.metadataEditor.arrayFields.control.date', 'Data'),
1030
+ };
1031
+ return labels[controlType] ?? controlType;
1032
+ }
1033
+ addField(seed = {}) {
1034
+ const fieldName = seed.name || this.nextAvailableName();
1035
+ this.formArray.push(this.createRow({
1036
+ ...seed,
1037
+ name: fieldName,
1038
+ label: seed.label || fieldName,
1039
+ controlType: seed.controlType || FieldControlType.INPUT,
1040
+ required: seed.required ?? false,
1041
+ }));
1042
+ this.originalNames.push(fieldName);
1043
+ this.syncToControl();
1044
+ }
1045
+ duplicateField(index) {
1046
+ const current = this.rowValue(index);
1047
+ if (!current)
1048
+ return;
1049
+ const nextName = this.nextAvailableName(`${current.name || 'field'}Copy`);
1050
+ this.addField({
1051
+ ...current,
1052
+ name: nextName,
1053
+ label: nextName,
1054
+ });
1055
+ }
1056
+ removeField(index) {
1057
+ const name = String(this.formArray.at(index)?.get('name')?.value || '').trim();
1058
+ if (this.isReferenced(name)) {
1059
+ return;
1060
+ }
1061
+ this.formArray.removeAt(index);
1062
+ this.originalNames.splice(index, 1);
1063
+ this.syncToControl();
1064
+ }
1065
+ moveField(index, delta) {
1066
+ const target = index + delta;
1067
+ if (target < 0 || target >= this.formArray.length)
1068
+ return;
1069
+ const row = this.formArray.at(index);
1070
+ this.formArray.removeAt(index);
1071
+ this.formArray.insert(target, row);
1072
+ const [originalName] = this.originalNames.splice(index, 1);
1073
+ this.originalNames.splice(target, 0, originalName);
1074
+ this.syncToControl();
1075
+ }
1076
+ onNameBlur(index) {
1077
+ const previous = this.originalNames[index];
1078
+ const next = String(this.formArray.at(index)?.get('name')?.value || '').trim();
1079
+ if (previous && !next && this.isReferenced(previous)) {
1080
+ this.formArray.at(index)?.get('name')?.setValue(previous, { emitEvent: false });
1081
+ this.syncToControl();
1082
+ return;
1083
+ }
1084
+ if (previous && next && previous !== next) {
1085
+ this.renameReferences(previous, next);
1086
+ this.originalNames[index] = next;
1087
+ }
1088
+ this.syncToControl();
1089
+ }
1090
+ importFieldsFromJson(raw) {
1091
+ try {
1092
+ const parsed = JSON.parse(String(raw || '').trim());
1093
+ if (!Array.isArray(parsed) ||
1094
+ parsed.some((entry) => !this.isImportableField(entry))) {
1095
+ return false;
1096
+ }
1097
+ this.replaceRows(parsed);
1098
+ this.schemaSourceState = 'imported';
1099
+ this.importedFieldsFingerprint = this.fieldsFingerprint();
1100
+ this.syncToControl();
1101
+ return true;
1102
+ }
1103
+ catch {
1104
+ return false;
1105
+ }
1106
+ }
1107
+ isImportableField(value) {
1108
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1109
+ return false;
1110
+ }
1111
+ const raw = value;
1112
+ return typeof raw.name === 'string' && raw.name.trim().length > 0;
1113
+ }
1114
+ isNameDuplicated(index) {
1115
+ const name = String(this.formArray.at(index)?.get('name')?.value || '').trim();
1116
+ return !!name && this.duplicateNames.includes(name);
1117
+ }
1118
+ isNameInvalid(index) {
1119
+ const control = this.formArray.at(index)?.get('name');
1120
+ return !!control?.invalid;
1121
+ }
1122
+ nameErrorId(index) {
1123
+ return `praxis-array-field-name-error-${this.instanceId}-${index}`;
1124
+ }
1125
+ nameErrorDescribedBy(index) {
1126
+ return this.nameErrorMessage(index) ? this.nameErrorId(index) : null;
1127
+ }
1128
+ nameErrorMessage(index) {
1129
+ const control = this.formArray.at(index)?.get('name');
1130
+ if (!control?.errors) {
1131
+ return '';
1132
+ }
1133
+ if (control.errors['arrayFieldNameRequired']) {
1134
+ return this.text('praxis.metadataEditor.arrayFields.nameRequired', 'Informe o nome do subcampo.');
1135
+ }
1136
+ if (control.errors['arrayFieldNameDuplicated']) {
1137
+ return this.text('praxis.metadataEditor.arrayFields.nameDuplicated', 'Este nome j\u00e1 est\u00e1 em uso na cole\u00e7\u00e3o.');
1138
+ }
1139
+ return '';
1140
+ }
1141
+ isReferenced(fieldName) {
1142
+ return this.referencedBy(fieldName).length > 0;
1143
+ }
1144
+ removeTitle(fieldName) {
1145
+ const references = this.referencedBy(fieldName);
1146
+ if (!references.length) {
1147
+ return '';
1148
+ }
1149
+ return this.text('praxis.metadataEditor.arrayFields.removeBlocked', undefined, 'Remova as refer\u00eancias antes de excluir: {{references}}', { references: references.join(', ') });
1150
+ }
1151
+ schemaStateDescription() {
1152
+ switch (this.schemaSourceState) {
1153
+ case 'ref-only':
1154
+ return this.text('praxis.metadataEditor.arrayFields.refOnly', 'O schema do item vem do itemSchemaRef; crie subcampos locais apenas quando precisar de override.');
1155
+ case 'override-local':
1156
+ return this.text('praxis.metadataEditor.arrayFields.overrideLocal', 'H\u00e1 um itemSchemaRef e subcampos locais; os subcampos locais funcionam como override expl\u00edcito.');
1157
+ case 'imported':
1158
+ return this.text('praxis.metadataEditor.arrayFields.imported', 'Subcampos importados. Revise antes de aplicar ou salvar.');
1159
+ default:
1160
+ return this.text('praxis.metadataEditor.arrayFields.localOnly', 'Subcampos locais ser\u00e3o salvos em array.itemSchema.fields.');
1161
+ }
1162
+ }
1163
+ hydrateFromControl() {
1164
+ const fields = this.normalizeFields(this.control?.value);
1165
+ this.replaceRows(fields, false);
1166
+ this.updateSchemaSourceState();
1167
+ }
1168
+ replaceRows(fields, emit = true) {
1169
+ this.hydrating = true;
1170
+ this.internalSubscription?.unsubscribe();
1171
+ while (this.formArray.length) {
1172
+ this.formArray.removeAt(0, { emitEvent: false });
1173
+ }
1174
+ this.originalNames = [];
1175
+ for (const field of fields) {
1176
+ const normalized = this.normalizeField(field);
1177
+ this.formArray.push(this.createRow(normalized), { emitEvent: false });
1178
+ this.originalNames.push(normalized.name);
1179
+ }
1180
+ this.internalSubscription = this.formArray.valueChanges.subscribe(() => this.syncToControl());
1181
+ this.hydrating = false;
1182
+ this.updateFieldListValidity();
1183
+ if (emit) {
1184
+ this.syncToControl();
1185
+ }
1186
+ }
1187
+ createRow(field) {
1188
+ return this.fb.group({
1189
+ name: [field.name || ''],
1190
+ label: [field.label || field.name || ''],
1191
+ controlType: [field.controlType || FieldControlType.INPUT],
1192
+ required: [!!field.required],
1193
+ metadata: [this.clonePlainObject(field)],
1194
+ });
1195
+ }
1196
+ rowValue(index) {
1197
+ const row = this.formArray.at(index);
1198
+ return row ? this.normalizeField(row.getRawValue()) : null;
1199
+ }
1200
+ syncToControl() {
1201
+ if (this.hydrating || !this.control)
1202
+ return;
1203
+ const normalizedRows = this.formArray.controls.map((row) => this.normalizeField(row.getRawValue()));
1204
+ this.updateDuplicateNames(normalizedRows);
1205
+ const fields = normalizedRows.filter((field) => field.name);
1206
+ this.hydrating = true;
1207
+ this.control.setValue(fields, { emitEvent: true });
1208
+ this.control.markAsDirty();
1209
+ this.control.markAsTouched();
1210
+ this.hydrating = false;
1211
+ this.updateFieldListValidity(normalizedRows);
1212
+ this.updateSchemaSourceState();
1213
+ }
1214
+ normalizeFields(value) {
1215
+ if (Array.isArray(value)) {
1216
+ return value;
1217
+ }
1218
+ if (typeof value === 'string' && value.trim()) {
1219
+ try {
1220
+ const parsed = JSON.parse(value);
1221
+ return Array.isArray(parsed) ? parsed : [];
1222
+ }
1223
+ catch {
1224
+ return [];
1225
+ }
1226
+ }
1227
+ return [];
1228
+ }
1229
+ normalizeField(value) {
1230
+ const raw = (value && typeof value === 'object' ? value : {});
1231
+ const base = raw.metadata && typeof raw.metadata === 'object' && !Array.isArray(raw.metadata)
1232
+ ? this.clonePlainObject(raw.metadata)
1233
+ : this.clonePlainObject(raw);
1234
+ const name = String(raw.name ?? base.name ?? '').trim();
1235
+ return {
1236
+ ...base,
1237
+ name,
1238
+ label: String(raw.label ?? base.label ?? name ?? '').trim(),
1239
+ controlType: raw.controlType || base.controlType || FieldControlType.INPUT,
1240
+ required: raw.required === true,
1241
+ };
1242
+ }
1243
+ nextAvailableName(prefix = 'campo') {
1244
+ const existing = new Set(this.formArray.controls
1245
+ .map((row) => String(row.get('name')?.value || '').trim())
1246
+ .filter(Boolean));
1247
+ let index = 1;
1248
+ let candidate = prefix;
1249
+ while (existing.has(candidate)) {
1250
+ index += 1;
1251
+ candidate = `${prefix}${index}`;
1252
+ }
1253
+ return candidate;
1254
+ }
1255
+ updateDuplicateNames(fields) {
1256
+ const source = fields ?? this.formArray.controls.map((row) => this.normalizeField(row.getRawValue()));
1257
+ const seen = new Set();
1258
+ const duplicated = new Set();
1259
+ for (const field of source) {
1260
+ const name = String(field.name || '').trim();
1261
+ if (!name)
1262
+ continue;
1263
+ if (seen.has(name)) {
1264
+ duplicated.add(name);
1265
+ }
1266
+ seen.add(name);
1267
+ }
1268
+ this.duplicateNames = Array.from(duplicated);
1269
+ }
1270
+ updateFieldListValidity(fields = this.formArray.controls.map((row) => this.normalizeField(row.getRawValue()))) {
1271
+ this.updateDuplicateNames(fields);
1272
+ const duplicateSet = new Set(this.duplicateNames);
1273
+ const hasRequiredNameError = fields.some((field) => !String(field.name || '').trim());
1274
+ const hasDuplicateNameError = duplicateSet.size > 0;
1275
+ this.formArray.controls.forEach((row) => {
1276
+ const nameControl = row.get('name');
1277
+ const name = String(nameControl?.value || '').trim();
1278
+ this.setControlError(nameControl, 'arrayFieldNameRequired', !name);
1279
+ this.setControlError(nameControl, 'arrayFieldNameDuplicated', !!name && duplicateSet.has(name));
1280
+ });
1281
+ const errorPayload = hasDuplicateNameError ? { names: this.duplicateNames } : null;
1282
+ this.setControlError(this.control, 'arrayFieldNameRequired', hasRequiredNameError);
1283
+ this.setControlError(this.control, 'arrayFieldNameDuplicated', errorPayload);
1284
+ this.setControlError(this.formArray, 'arrayFieldNameRequired', hasRequiredNameError);
1285
+ this.setControlError(this.formArray, 'arrayFieldNameDuplicated', errorPayload);
1286
+ }
1287
+ setControlError(control, key, value) {
1288
+ if (!control)
1289
+ return;
1290
+ const errors = { ...(control.errors || {}) };
1291
+ const hasError = value !== null && value !== undefined && value !== false;
1292
+ if (hasError) {
1293
+ errors[key] = value === true ? true : value;
1294
+ }
1295
+ else {
1296
+ delete errors[key];
1297
+ }
1298
+ control.setErrors(Object.keys(errors).length ? errors : null);
1299
+ }
1300
+ updateSchemaSourceState() {
1301
+ const rawArray = this.arrayValue();
1302
+ const hasRef = typeof rawArray.itemSchemaRef === 'string' && rawArray.itemSchemaRef.trim().length > 0;
1303
+ const hasFields = this.formArray.length > 0;
1304
+ if (this.schemaSourceState === 'imported' &&
1305
+ hasFields &&
1306
+ this.importedFieldsFingerprint === this.fieldsFingerprint()) {
1307
+ return;
1308
+ }
1309
+ if (this.schemaSourceState === 'imported') {
1310
+ this.importedFieldsFingerprint = null;
1311
+ }
1312
+ this.schemaSourceState = hasRef && hasFields ? 'override-local' : hasRef ? 'ref-only' : 'local-only';
1313
+ }
1314
+ arrayValue() {
1315
+ const value = this.arrayControl?.value;
1316
+ return value && typeof value === 'object' ? value : {};
1317
+ }
1318
+ referencedBy(fieldName) {
1319
+ const name = String(fieldName || '').trim();
1320
+ if (!name)
1321
+ return [];
1322
+ const array = this.arrayValue();
1323
+ const references = [];
1324
+ if (array.itemIdentityField === name)
1325
+ references.push('itemIdentityField');
1326
+ if (this.uniqueByFields(array.collectionValidation?.uniqueBy).includes(name)) {
1327
+ references.push('collectionValidation.uniqueBy');
1328
+ }
1329
+ if (array.collectionValidation?.exactlyOne?.field === name) {
1330
+ references.push('collectionValidation.exactlyOne.field');
1331
+ }
1332
+ if (array.collectionValidation?.atLeastOne?.field === name) {
1333
+ references.push('collectionValidation.atLeastOne.field');
1334
+ }
1335
+ if (typeof array.itemTitleTemplate === 'string' && this.templateReferencesField(array.itemTitleTemplate, name)) {
1336
+ references.push('itemTitleTemplate');
1337
+ }
1338
+ return references;
1339
+ }
1340
+ renameReferences(previous, next) {
1341
+ if (!this.arrayControl || !previous || !next)
1342
+ return;
1343
+ const array = { ...this.arrayValue() };
1344
+ if (array.itemIdentityField === previous)
1345
+ array.itemIdentityField = next;
1346
+ const collectionValidation = array.collectionValidation && typeof array.collectionValidation === 'object'
1347
+ ? { ...array.collectionValidation }
1348
+ : undefined;
1349
+ if (collectionValidation) {
1350
+ if (Array.isArray(collectionValidation.uniqueBy)) {
1351
+ collectionValidation.uniqueBy = collectionValidation.uniqueBy.map((entry) => entry === previous ? next : entry);
1352
+ }
1353
+ else if (typeof collectionValidation.uniqueBy === 'string') {
1354
+ collectionValidation.uniqueBy = this.replaceUniqueByField(collectionValidation.uniqueBy, previous, next);
1355
+ }
1356
+ if (collectionValidation.exactlyOne?.field === previous) {
1357
+ collectionValidation.exactlyOne = { ...collectionValidation.exactlyOne, field: next };
1358
+ }
1359
+ if (collectionValidation.atLeastOne?.field === previous) {
1360
+ collectionValidation.atLeastOne = { ...collectionValidation.atLeastOne, field: next };
1361
+ }
1362
+ array.collectionValidation = collectionValidation;
1363
+ }
1364
+ if (typeof array.itemTitleTemplate === 'string') {
1365
+ array.itemTitleTemplate = array.itemTitleTemplate.replace(new RegExp(`{{\\s*${this.escapeRegExp(previous)}\\s*}}`, 'g'), `{{${next}}}`);
1366
+ }
1367
+ this.arrayControl.patchValue(array);
1368
+ this.arrayControl.markAsDirty();
1369
+ }
1370
+ uniqueByFields(value) {
1371
+ if (Array.isArray(value)) {
1372
+ return value.map((entry) => String(entry || '').trim()).filter(Boolean);
1373
+ }
1374
+ if (typeof value !== 'string') {
1375
+ return [];
1376
+ }
1377
+ const trimmed = value.trim();
1378
+ if (!trimmed) {
1379
+ return [];
1380
+ }
1381
+ try {
1382
+ const parsed = JSON.parse(trimmed);
1383
+ if (Array.isArray(parsed)) {
1384
+ return parsed.map((entry) => String(entry || '').trim()).filter(Boolean);
1385
+ }
1386
+ }
1387
+ catch { }
1388
+ return trimmed
1389
+ .split(/\r?\n|,/)
1390
+ .map((entry) => entry.trim())
1391
+ .filter(Boolean);
1392
+ }
1393
+ replaceUniqueByField(value, previous, next) {
1394
+ const trimmed = value.trim();
1395
+ if (!trimmed) {
1396
+ return value;
1397
+ }
1398
+ try {
1399
+ const parsed = JSON.parse(trimmed);
1400
+ if (Array.isArray(parsed)) {
1401
+ return JSON.stringify(parsed.map((entry) => String(entry || '').trim() === previous ? next : entry));
1402
+ }
1403
+ }
1404
+ catch { }
1405
+ return value
1406
+ .split(/(\r?\n|,)/)
1407
+ .map((entry) => entry.trim() === previous ? next : entry)
1408
+ .join('');
1409
+ }
1410
+ templateReferencesField(template, fieldName) {
1411
+ return new RegExp(`{{\\s*${this.escapeRegExp(fieldName)}\\s*}}`).test(template);
1412
+ }
1413
+ clonePlainObject(value) {
1414
+ return this.cloneMetadataValue(value, new WeakMap());
1415
+ }
1416
+ cloneMetadataValue(value, seen) {
1417
+ if (!value || typeof value !== 'object') {
1418
+ return value;
1419
+ }
1420
+ if (seen.has(value)) {
1421
+ return seen.get(value);
1422
+ }
1423
+ if (value instanceof Date) {
1424
+ return new Date(value.getTime());
1425
+ }
1426
+ if (value instanceof RegExp) {
1427
+ return new RegExp(value.source, value.flags);
1428
+ }
1429
+ if (Array.isArray(value)) {
1430
+ const clone = [];
1431
+ seen.set(value, clone);
1432
+ clone.push(...value.map((entry) => this.cloneMetadataValue(entry, seen)));
1433
+ return clone;
1434
+ }
1435
+ if (value instanceof Map) {
1436
+ const clone = new Map();
1437
+ seen.set(value, clone);
1438
+ value.forEach((entry, key) => {
1439
+ clone.set(this.cloneMetadataValue(key, seen), this.cloneMetadataValue(entry, seen));
1440
+ });
1441
+ return clone;
1442
+ }
1443
+ if (value instanceof Set) {
1444
+ const clone = new Set();
1445
+ seen.set(value, clone);
1446
+ value.forEach((entry) => clone.add(this.cloneMetadataValue(entry, seen)));
1447
+ return clone;
1448
+ }
1449
+ const prototype = Object.getPrototypeOf(value);
1450
+ if (prototype !== Object.prototype && prototype !== null) {
1451
+ return value;
1452
+ }
1453
+ const clone = {};
1454
+ seen.set(value, clone);
1455
+ for (const [key, entry] of Object.entries(value)) {
1456
+ clone[key] = this.cloneMetadataValue(entry, seen);
1457
+ }
1458
+ return clone;
1459
+ }
1460
+ fieldsFingerprint() {
1461
+ return JSON.stringify(this.formArray.controls.map((row) => this.normalizeField(row.getRawValue())));
1462
+ }
1463
+ escapeRegExp(value) {
1464
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1465
+ }
1466
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ArrayItemSchemaFieldsEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1467
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: ArrayItemSchemaFieldsEditorComponent, isStandalone: true, selector: "praxis-array-item-schema-fields-editor", inputs: { control: "control", arrayControl: "arrayControl" }, providers: [providePraxisMetadataEditorI18n()], usesOnChanges: true, ngImport: i0, template: `
1468
+ <section
1469
+ class="array-fields-editor"
1470
+ [attr.data-schema-source]="schemaSourceState"
1471
+ [attr.aria-label]="text('praxis.metadataEditor.arrayFields.section', 'Subcampos da cole\u00e7\u00e3o')"
1472
+ >
1473
+ <header class="array-fields-editor__header">
1474
+ <div>
1475
+ <h3>{{ text('praxis.metadataEditor.arrayFields.title', 'Subcampos') }}</h3>
1476
+ <p>
1477
+ {{ schemaStateDescription() }}
1478
+ </p>
1479
+ </div>
1480
+ <button type="button" class="array-fields-editor__primary" (click)="addField()">
1481
+ {{ text('praxis.metadataEditor.arrayFields.add', 'Adicionar subcampo') }}
1482
+ </button>
1483
+ </header>
1484
+
1485
+ @if (duplicateNames.length) {
1486
+ <div
1487
+ class="array-fields-editor__warning"
1488
+ role="alert"
1489
+ >
1490
+ {{
1491
+ text(
1492
+ 'praxis.metadataEditor.arrayFields.duplicateNames',
1493
+ undefined,
1494
+ 'Existem nomes duplicados: {{names}}',
1495
+ { names: duplicateNames.join(', ') }
1496
+ )
1497
+ }}
1498
+ </div>
1499
+ }
1500
+
1501
+ @if (formArray.length === 0) {
1502
+ <div
1503
+ class="array-fields-editor__empty"
1504
+ >
1505
+ {{ text('praxis.metadataEditor.arrayFields.empty', 'Nenhum subcampo local configurado.') }}
1506
+ </div>
1507
+ }
1508
+
1509
+ <div class="array-fields-editor__list" [formGroup]="form">
1510
+ @for (row of rows; track row; let i = $index) {
1511
+ <div
1512
+ class="array-fields-editor__row"
1513
+ [formGroup]="row"
1514
+ >
1515
+ <label>
1516
+ <span>{{ text('praxis.metadataEditor.arrayFields.name', 'Nome') }}</span>
1517
+ <input
1518
+ type="text"
1519
+ formControlName="name"
1520
+ (blur)="onNameBlur(i)"
1521
+ [attr.aria-invalid]="isNameInvalid(i) ? 'true' : null"
1522
+ [attr.aria-describedby]="nameErrorDescribedBy(i)"
1523
+ />
1524
+ @if (nameErrorMessage(i); as nameError) {
1525
+ <span
1526
+ class="array-fields-editor__field-error"
1527
+ [id]="nameErrorId(i)"
1528
+ >
1529
+ {{ nameError }}
1530
+ </span>
1531
+ }
1532
+ </label>
1533
+ <label>
1534
+ <span>{{ text('praxis.metadataEditor.arrayFields.label', 'Label') }}</span>
1535
+ <input type="text" formControlName="label" />
1536
+ </label>
1537
+ <label>
1538
+ <span>{{ text('praxis.metadataEditor.arrayFields.controlType', 'Controle') }}</span>
1539
+ <select formControlName="controlType">
1540
+ @for (option of controlTypeOptions; track option) {
1541
+ <option [value]="option">
1542
+ {{ controlTypeLabel(option) }}
1543
+ </option>
1544
+ }
1545
+ </select>
1546
+ </label>
1547
+ <label class="array-fields-editor__check">
1548
+ <input type="checkbox" formControlName="required" />
1549
+ <span>{{ text('praxis.metadataEditor.arrayFields.required', 'Obrigat\u00f3rio') }}</span>
1550
+ </label>
1551
+ <div class="array-fields-editor__actions">
1552
+ <button type="button" (click)="moveField(i, -1)" [disabled]="i === 0">
1553
+ {{ text('praxis.metadataEditor.arrayFields.moveUp', 'Subir') }}
1554
+ </button>
1555
+ <button type="button" (click)="moveField(i, 1)" [disabled]="i === formArray.length - 1">
1556
+ {{ text('praxis.metadataEditor.arrayFields.moveDown', 'Descer') }}
1557
+ </button>
1558
+ <button type="button" (click)="duplicateField(i)">
1559
+ {{ text('praxis.metadataEditor.arrayFields.duplicate', 'Duplicar') }}
1560
+ </button>
1561
+ <button
1562
+ type="button"
1563
+ (click)="removeField(i)"
1564
+ [disabled]="isReferenced(row.get('name')?.value)"
1565
+ [title]="removeTitle(row.get('name')?.value)"
1566
+ >
1567
+ {{ text('praxis.metadataEditor.arrayFields.remove', 'Remover') }}
1568
+ </button>
1569
+ </div>
1570
+ </div>
1571
+ }
1572
+ </div>
1573
+ </section>
1574
+ `, isInline: true, styles: [":host{display:block}.array-fields-editor{display:block;margin:10px 0 2px;padding:12px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface-container-low);color:var(--md-sys-color-on-surface)}.array-fields-editor__header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px}.array-fields-editor__header h3{margin:0 0 4px;font-size:1rem;line-height:1.25}.array-fields-editor__header p,.array-fields-editor__empty{margin:0;color:var(--md-sys-color-on-surface-variant);font-size:.875rem;line-height:1.35}.array-fields-editor__primary,.array-fields-editor button{border:1px solid var(--md-sys-color-outline-variant);border-radius:6px;background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);cursor:pointer;min-height:32px;padding:5px 10px}.array-fields-editor__primary{border-color:var(--md-sys-color-primary);color:var(--md-sys-color-primary);font-weight:600;white-space:nowrap}.array-fields-editor button:disabled{cursor:not-allowed;opacity:.55}.array-fields-editor__warning{margin:8px 0 12px;padding:8px 10px;border:1px solid var(--md-sys-color-error);border-radius:6px;color:var(--md-sys-color-error);background:var(--md-sys-color-error-container)}.array-fields-editor__list{display:grid;gap:10px}.array-fields-editor__row{display:grid;grid-template-columns:minmax(140px,1fr) minmax(160px,1.2fr) minmax(140px,.9fr) auto;gap:10px;align-items:end;padding:10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface)}.array-fields-editor label{display:grid;gap:4px;font-size:.8rem;font-weight:600}.array-fields-editor input[type=text],.array-fields-editor select{box-sizing:border-box;width:100%;min-height:34px;border:1px solid var(--md-sys-color-outline-variant);border-radius:6px;background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);padding:5px 8px}.array-fields-editor input[aria-invalid=true]{border-color:var(--md-sys-color-error)}.array-fields-editor__field-error{color:var(--md-sys-color-error);font-size:.75rem;font-weight:500;line-height:1.3}.array-fields-editor__check{display:flex!important;align-items:center;gap:6px;min-height:34px}.array-fields-editor__actions{grid-column:1 / -1;display:flex;flex-wrap:wrap;gap:6px}@media(max-width:720px){.array-fields-editor__header,.array-fields-editor__row{display:grid;grid-template-columns:1fr}.array-fields-editor__primary{width:100%}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { 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.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { 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.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }] });
1575
+ }
1576
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ArrayItemSchemaFieldsEditorComponent, decorators: [{
1577
+ type: Component,
1578
+ args: [{ selector: 'praxis-array-item-schema-fields-editor', standalone: true, imports: [ReactiveFormsModule], providers: [providePraxisMetadataEditorI18n()], template: `
1579
+ <section
1580
+ class="array-fields-editor"
1581
+ [attr.data-schema-source]="schemaSourceState"
1582
+ [attr.aria-label]="text('praxis.metadataEditor.arrayFields.section', 'Subcampos da cole\u00e7\u00e3o')"
1583
+ >
1584
+ <header class="array-fields-editor__header">
1585
+ <div>
1586
+ <h3>{{ text('praxis.metadataEditor.arrayFields.title', 'Subcampos') }}</h3>
1587
+ <p>
1588
+ {{ schemaStateDescription() }}
1589
+ </p>
1590
+ </div>
1591
+ <button type="button" class="array-fields-editor__primary" (click)="addField()">
1592
+ {{ text('praxis.metadataEditor.arrayFields.add', 'Adicionar subcampo') }}
1593
+ </button>
1594
+ </header>
1595
+
1596
+ @if (duplicateNames.length) {
1597
+ <div
1598
+ class="array-fields-editor__warning"
1599
+ role="alert"
1600
+ >
1601
+ {{
1602
+ text(
1603
+ 'praxis.metadataEditor.arrayFields.duplicateNames',
1604
+ undefined,
1605
+ 'Existem nomes duplicados: {{names}}',
1606
+ { names: duplicateNames.join(', ') }
1607
+ )
1608
+ }}
1609
+ </div>
1610
+ }
1611
+
1612
+ @if (formArray.length === 0) {
1613
+ <div
1614
+ class="array-fields-editor__empty"
1615
+ >
1616
+ {{ text('praxis.metadataEditor.arrayFields.empty', 'Nenhum subcampo local configurado.') }}
1617
+ </div>
1618
+ }
1619
+
1620
+ <div class="array-fields-editor__list" [formGroup]="form">
1621
+ @for (row of rows; track row; let i = $index) {
1622
+ <div
1623
+ class="array-fields-editor__row"
1624
+ [formGroup]="row"
1625
+ >
1626
+ <label>
1627
+ <span>{{ text('praxis.metadataEditor.arrayFields.name', 'Nome') }}</span>
1628
+ <input
1629
+ type="text"
1630
+ formControlName="name"
1631
+ (blur)="onNameBlur(i)"
1632
+ [attr.aria-invalid]="isNameInvalid(i) ? 'true' : null"
1633
+ [attr.aria-describedby]="nameErrorDescribedBy(i)"
1634
+ />
1635
+ @if (nameErrorMessage(i); as nameError) {
1636
+ <span
1637
+ class="array-fields-editor__field-error"
1638
+ [id]="nameErrorId(i)"
1639
+ >
1640
+ {{ nameError }}
1641
+ </span>
1642
+ }
1643
+ </label>
1644
+ <label>
1645
+ <span>{{ text('praxis.metadataEditor.arrayFields.label', 'Label') }}</span>
1646
+ <input type="text" formControlName="label" />
1647
+ </label>
1648
+ <label>
1649
+ <span>{{ text('praxis.metadataEditor.arrayFields.controlType', 'Controle') }}</span>
1650
+ <select formControlName="controlType">
1651
+ @for (option of controlTypeOptions; track option) {
1652
+ <option [value]="option">
1653
+ {{ controlTypeLabel(option) }}
1654
+ </option>
1655
+ }
1656
+ </select>
1657
+ </label>
1658
+ <label class="array-fields-editor__check">
1659
+ <input type="checkbox" formControlName="required" />
1660
+ <span>{{ text('praxis.metadataEditor.arrayFields.required', 'Obrigat\u00f3rio') }}</span>
1661
+ </label>
1662
+ <div class="array-fields-editor__actions">
1663
+ <button type="button" (click)="moveField(i, -1)" [disabled]="i === 0">
1664
+ {{ text('praxis.metadataEditor.arrayFields.moveUp', 'Subir') }}
1665
+ </button>
1666
+ <button type="button" (click)="moveField(i, 1)" [disabled]="i === formArray.length - 1">
1667
+ {{ text('praxis.metadataEditor.arrayFields.moveDown', 'Descer') }}
1668
+ </button>
1669
+ <button type="button" (click)="duplicateField(i)">
1670
+ {{ text('praxis.metadataEditor.arrayFields.duplicate', 'Duplicar') }}
1671
+ </button>
1672
+ <button
1673
+ type="button"
1674
+ (click)="removeField(i)"
1675
+ [disabled]="isReferenced(row.get('name')?.value)"
1676
+ [title]="removeTitle(row.get('name')?.value)"
1677
+ >
1678
+ {{ text('praxis.metadataEditor.arrayFields.remove', 'Remover') }}
1679
+ </button>
1680
+ </div>
1681
+ </div>
1682
+ }
1683
+ </div>
1684
+ </section>
1685
+ `, styles: [":host{display:block}.array-fields-editor{display:block;margin:10px 0 2px;padding:12px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface-container-low);color:var(--md-sys-color-on-surface)}.array-fields-editor__header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px}.array-fields-editor__header h3{margin:0 0 4px;font-size:1rem;line-height:1.25}.array-fields-editor__header p,.array-fields-editor__empty{margin:0;color:var(--md-sys-color-on-surface-variant);font-size:.875rem;line-height:1.35}.array-fields-editor__primary,.array-fields-editor button{border:1px solid var(--md-sys-color-outline-variant);border-radius:6px;background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);cursor:pointer;min-height:32px;padding:5px 10px}.array-fields-editor__primary{border-color:var(--md-sys-color-primary);color:var(--md-sys-color-primary);font-weight:600;white-space:nowrap}.array-fields-editor button:disabled{cursor:not-allowed;opacity:.55}.array-fields-editor__warning{margin:8px 0 12px;padding:8px 10px;border:1px solid var(--md-sys-color-error);border-radius:6px;color:var(--md-sys-color-error);background:var(--md-sys-color-error-container)}.array-fields-editor__list{display:grid;gap:10px}.array-fields-editor__row{display:grid;grid-template-columns:minmax(140px,1fr) minmax(160px,1.2fr) minmax(140px,.9fr) auto;gap:10px;align-items:end;padding:10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface)}.array-fields-editor label{display:grid;gap:4px;font-size:.8rem;font-weight:600}.array-fields-editor input[type=text],.array-fields-editor select{box-sizing:border-box;width:100%;min-height:34px;border:1px solid var(--md-sys-color-outline-variant);border-radius:6px;background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);padding:5px 8px}.array-fields-editor input[aria-invalid=true]{border-color:var(--md-sys-color-error)}.array-fields-editor__field-error{color:var(--md-sys-color-error);font-size:.75rem;font-weight:500;line-height:1.3}.array-fields-editor__check{display:flex!important;align-items:center;gap:6px;min-height:34px}.array-fields-editor__actions{grid-column:1 / -1;display:flex;flex-wrap:wrap;gap:6px}@media(max-width:720px){.array-fields-editor__header,.array-fields-editor__row{display:grid;grid-template-columns:1fr}.array-fields-editor__primary{width:100%}}\n"] }]
1686
+ }], propDecorators: { control: [{
1687
+ type: Input
1688
+ }], arrayControl: [{
1689
+ type: Input
1690
+ }] } });
1691
+
846
1692
  const inputProperties = [
847
1693
  // Geral
848
1694
  { name: 'label', label: 'Label', editorType: 'text', placeholder: 'Rótulo do campo', group: 'Geral' },
@@ -1105,7 +1951,7 @@ const arrayProperties = [
1105
1951
  { name: 'array.collectionValidation.atLeastOne.field', label: 'Pelo menos um: campo', editorType: 'text', group: 'Validacao' },
1106
1952
  { name: 'array.collectionValidation.atLeastOne.value', label: 'Pelo menos um: valor tipado', editorType: 'text', group: 'Validacao', hint: 'Use true/false, numero ou texto. Ex.: true, 1, ACTIVE.' },
1107
1953
  { name: 'array.collectionValidation.atLeastOne.message', label: 'Mensagem: pelo menos um', editorType: 'text', group: 'Validacao' },
1108
- { name: 'array.itemSchema.fields', label: 'Subcampos (JSON)', editorType: 'textarea', group: 'Subcampos', hint: 'Lista FieldMetadata[] usada quando nao houver itemSchemaRef resolvido.' },
1954
+ { name: 'array.itemSchema.fields', label: 'Subcampos', editorType: 'field-list', group: 'Subcampos', hint: 'Lista FieldMetadata[] usada quando nao houver itemSchemaRef resolvido.' },
1109
1955
  { name: 'required', label: 'Obrigatorio', editorType: 'checkbox', group: 'Validacao' },
1110
1956
  ];
1111
1957
 
@@ -1278,8 +2124,39 @@ function cloneProperty$7(prop) {
1278
2124
  : prop.options,
1279
2125
  };
1280
2126
  }
2127
+ function cloneEntityLookupBaseProperty(prop) {
2128
+ const cloned = cloneProperty$7(prop);
2129
+ if (cloned.name === 'multiple') {
2130
+ return {
2131
+ ...cloned,
2132
+ group: 'Selecao em colecao',
2133
+ hint: 'Ativa selecao governada de varias entidades no mesmo campo.',
2134
+ };
2135
+ }
2136
+ if (cloned.name === 'maxSelections') {
2137
+ return {
2138
+ ...cloned,
2139
+ group: 'Selecao em colecao',
2140
+ hint: 'Limite maximo de entidades selecionadas quando multiple=true.',
2141
+ };
2142
+ }
2143
+ return cloned;
2144
+ }
1281
2145
  const entityLookupProperties = [
1282
- ...selectProperties.map((prop) => cloneProperty$7(prop)),
2146
+ ...selectProperties.map((prop) => cloneEntityLookupBaseProperty(prop)),
2147
+ {
2148
+ name: 'payloadMode',
2149
+ label: 'Modo do payload',
2150
+ editorType: 'select',
2151
+ group: 'Selecao em colecao',
2152
+ options: [
2153
+ { value: 'id', text: 'id' },
2154
+ { value: 'entityRef', text: 'entityRef' },
2155
+ { value: 'ids', text: 'ids' },
2156
+ { value: 'entityRefs', text: 'entityRefs' },
2157
+ ],
2158
+ hint: 'Define o shape salvo no payload. O runtime resolve combinacoes invalidas conforme multiple.',
2159
+ },
1283
2160
  {
1284
2161
  name: 'optionSource.key',
1285
2162
  label: 'Option source key',
@@ -1379,6 +2256,261 @@ const entityLookupProperties = [
1379
2256
  group: 'Exibicao de entidade',
1380
2257
  hint: 'Campo opcional com mensagem de bloqueio quando selectable=false.',
1381
2258
  },
2259
+ {
2260
+ name: 'optionSource.display.preset',
2261
+ label: 'Preset canonico',
2262
+ editorType: 'select',
2263
+ group: 'Display canonico',
2264
+ options: [
2265
+ { value: 'compact', text: 'compact' },
2266
+ { value: 'rich', text: 'rich' },
2267
+ { value: 'directory', text: 'directory' },
2268
+ { value: 'status', text: 'status' },
2269
+ { value: 'reference', text: 'reference' },
2270
+ { value: 'hierarchical', text: 'hierarchical' },
2271
+ ],
2272
+ hint: 'Intencao visual da entidade. Use directory para pessoas/equipes, reference para codigo + descricao e status quando o estado operacional for central.',
2273
+ },
2274
+ {
2275
+ name: 'optionSource.display.usage',
2276
+ label: 'Uso preferencial',
2277
+ editorType: 'select',
2278
+ group: 'Display canonico',
2279
+ options: [
2280
+ { value: 'form', text: 'form' },
2281
+ { value: 'filter', text: 'filter' },
2282
+ { value: 'table-cell', text: 'table-cell' },
2283
+ { value: 'dashboard', text: 'dashboard' },
2284
+ { value: 'wizard', text: 'wizard' },
2285
+ { value: 'review', text: 'review' },
2286
+ ],
2287
+ hint: 'Contexto principal de uso desta option-source sem amarrar a regra a um componente especifico.',
2288
+ },
2289
+ {
2290
+ name: 'optionSource.display.density',
2291
+ label: 'Densidade',
2292
+ editorType: 'select',
2293
+ group: 'Display canonico',
2294
+ options: [
2295
+ { value: 'compact', text: 'compact' },
2296
+ { value: 'comfortable', text: 'comfortable' },
2297
+ { value: 'rich', text: 'rich' },
2298
+ ],
2299
+ hint: 'Densidade preferencial para runtimes. Filtros costumam usar compact; formularios, comfortable; revisoes, rich.',
2300
+ },
2301
+ {
2302
+ name: 'optionSource.display.selectedLayout',
2303
+ label: 'Layout selecionado canonico',
2304
+ editorType: 'select',
2305
+ group: 'Display canonico',
2306
+ options: [
2307
+ { value: 'card', text: 'card' },
2308
+ { value: 'inline', text: 'inline' },
2309
+ { value: 'compact', text: 'compact' },
2310
+ { value: 'token', text: 'token' },
2311
+ ],
2312
+ hint: 'Layout preferencial do valor selecionado publicado pela option-source.',
2313
+ },
2314
+ {
2315
+ name: 'optionSource.display.resultLayout',
2316
+ label: 'Layout de resultados canonico',
2317
+ editorType: 'select',
2318
+ group: 'Display canonico',
2319
+ options: [
2320
+ { value: 'list', text: 'list' },
2321
+ { value: 'denseList', text: 'denseList' },
2322
+ { value: 'table', text: 'table' },
2323
+ { value: 'card', text: 'card' },
2324
+ ],
2325
+ hint: 'Layout preferencial da lista de resultados publicado pela option-source.',
2326
+ },
2327
+ {
2328
+ name: 'optionSource.display.primaryPropertyPath',
2329
+ label: 'Campo primario',
2330
+ editorType: 'text',
2331
+ group: 'Display canonico',
2332
+ hint: 'Campo semantico usado como titulo principal quando diferente de labelPropertyPath.',
2333
+ },
2334
+ {
2335
+ name: 'optionSource.display.fields',
2336
+ label: 'Campos ricos',
2337
+ editorType: 'textarea',
2338
+ group: 'Display canonico',
2339
+ hint: 'JSON array de subinformacoes ricas. Ex.: [{ "key": "role", "propertyPath": "cargo.nome", "label": "Cargo", "icon": "work", "presentation": "text", "tone": "neutral" }, { "key": "department", "propertyPath": "departamento.nome", "label": "Departamento", "icon": "groups", "presentation": "chip", "tone": "info" }].',
2340
+ },
2341
+ {
2342
+ name: 'optionSource.display.secondaryPropertyPaths',
2343
+ label: 'Campos secundarios canonicos',
2344
+ editorType: 'textarea',
2345
+ group: 'Display canonico',
2346
+ hint: 'JSON array ou uma linha por campo usado como subtitulo. Ex.: cargo.nome, departamento.nome.',
2347
+ },
2348
+ {
2349
+ name: 'optionSource.display.badgePropertyPaths',
2350
+ label: 'Campos de badges canonicos',
2351
+ editorType: 'textarea',
2352
+ group: 'Display canonico',
2353
+ hint: 'JSON array ou uma linha por campo materializado como badge/chip.',
2354
+ },
2355
+ {
2356
+ name: 'optionSource.display.avatarPropertyPath',
2357
+ label: 'Campo de avatar',
2358
+ editorType: 'text',
2359
+ group: 'Display canonico',
2360
+ hint: 'Campo usado para avatar/imagem; quando ausente, o runtime pode usar iniciais.',
2361
+ },
2362
+ {
2363
+ name: 'optionSource.display.showAvatar',
2364
+ label: 'Display: avatar',
2365
+ editorType: 'checkbox',
2366
+ group: 'Display canonico',
2367
+ },
2368
+ {
2369
+ name: 'optionSource.display.showCode',
2370
+ label: 'Display: codigo',
2371
+ editorType: 'checkbox',
2372
+ group: 'Display canonico',
2373
+ },
2374
+ {
2375
+ name: 'optionSource.display.showDescription',
2376
+ label: 'Display: descricao',
2377
+ editorType: 'checkbox',
2378
+ group: 'Display canonico',
2379
+ },
2380
+ {
2381
+ name: 'optionSource.display.showStatus',
2382
+ label: 'Display: status',
2383
+ editorType: 'checkbox',
2384
+ group: 'Display canonico',
2385
+ },
2386
+ {
2387
+ name: 'optionSource.display.showBadges',
2388
+ label: 'Display: badges',
2389
+ editorType: 'checkbox',
2390
+ group: 'Display canonico',
2391
+ },
2392
+ {
2393
+ name: 'optionSource.display.showDisabledReason',
2394
+ label: 'Display: motivo bloqueado',
2395
+ editorType: 'checkbox',
2396
+ group: 'Display canonico',
2397
+ },
2398
+ {
2399
+ name: 'optionSource.display.showResultCount',
2400
+ label: 'Display: contador de resultados',
2401
+ editorType: 'checkbox',
2402
+ group: 'Display canonico',
2403
+ },
2404
+ {
2405
+ name: 'optionSource.display.statusToneMap',
2406
+ label: 'Mapa canonico de tons',
2407
+ editorType: 'textarea',
2408
+ group: 'Display canonico',
2409
+ hint: 'JSON object. Ex.: { "ACTIVE": "success", "BLOCKED": "danger" }.',
2410
+ },
2411
+ {
2412
+ name: 'optionSource.display.badgeKeys',
2413
+ label: 'Chaves canonicas de badges',
2414
+ editorType: 'textarea',
2415
+ group: 'Display canonico',
2416
+ hint: 'JSON array ou uma linha por chave complementar de badge.',
2417
+ },
2418
+ {
2419
+ name: 'optionSource.display.maxVisibleBadges',
2420
+ label: 'Maximo canonico de badges',
2421
+ editorType: 'number',
2422
+ group: 'Display canonico',
2423
+ hint: 'Limite preferencial de badges antes de overflow.',
2424
+ },
2425
+ {
2426
+ name: 'selectedLayout',
2427
+ label: 'Layout selecionado',
2428
+ editorType: 'select',
2429
+ group: 'Exibicao de entidade',
2430
+ options: [
2431
+ { value: 'card', text: 'card' },
2432
+ { value: 'inline', text: 'inline' },
2433
+ { value: 'compact', text: 'compact' },
2434
+ { value: 'token', text: 'token' },
2435
+ ],
2436
+ hint: 'Layout do item selecionado no trigger ou card principal.',
2437
+ },
2438
+ {
2439
+ name: 'resultLayout',
2440
+ label: 'Layout dos resultados',
2441
+ editorType: 'select',
2442
+ group: 'Exibicao de entidade',
2443
+ options: [
2444
+ { value: 'list', text: 'list' },
2445
+ { value: 'denseList', text: 'denseList' },
2446
+ { value: 'table', text: 'table' },
2447
+ { value: 'card', text: 'card' },
2448
+ ],
2449
+ hint: 'Layout principal usado na lista inline e no dialog.',
2450
+ },
2451
+ {
2452
+ name: 'showCode',
2453
+ label: 'Mostrar codigo',
2454
+ editorType: 'checkbox',
2455
+ group: 'Exibicao de entidade',
2456
+ },
2457
+ {
2458
+ name: 'showDescription',
2459
+ label: 'Mostrar descricao',
2460
+ editorType: 'checkbox',
2461
+ group: 'Exibicao de entidade',
2462
+ },
2463
+ {
2464
+ name: 'showStatus',
2465
+ label: 'Mostrar status',
2466
+ editorType: 'checkbox',
2467
+ group: 'Exibicao de entidade',
2468
+ },
2469
+ {
2470
+ name: 'showAvatar',
2471
+ label: 'Mostrar avatar',
2472
+ editorType: 'checkbox',
2473
+ group: 'Exibicao de entidade',
2474
+ },
2475
+ {
2476
+ name: 'showBadges',
2477
+ label: 'Mostrar badges',
2478
+ editorType: 'checkbox',
2479
+ group: 'Exibicao de entidade',
2480
+ },
2481
+ {
2482
+ name: 'showDisabledReason',
2483
+ label: 'Mostrar motivo bloqueado',
2484
+ editorType: 'checkbox',
2485
+ group: 'Exibicao de entidade',
2486
+ },
2487
+ {
2488
+ name: 'showResultCount',
2489
+ label: 'Mostrar contador de resultados',
2490
+ editorType: 'checkbox',
2491
+ group: 'Exibicao de entidade',
2492
+ },
2493
+ {
2494
+ name: 'statusToneMap',
2495
+ label: 'Mapa de tons de status',
2496
+ editorType: 'textarea',
2497
+ group: 'Exibicao de entidade',
2498
+ hint: 'JSON object. Ex.: { \"ACTIVE\": \"success\", \"BLOCKED\": \"danger\" }.',
2499
+ },
2500
+ {
2501
+ name: 'badgeKeys',
2502
+ label: 'Chaves de badges',
2503
+ editorType: 'textarea',
2504
+ group: 'Exibicao de entidade',
2505
+ hint: 'JSON array ou uma linha por chave complementar de badge.',
2506
+ },
2507
+ {
2508
+ name: 'maxVisibleBadges',
2509
+ label: 'Maximo de badges visiveis',
2510
+ editorType: 'number',
2511
+ group: 'Exibicao de entidade',
2512
+ hint: 'Limita a quantidade de badges visiveis antes do overflow.',
2513
+ },
1382
2514
  {
1383
2515
  name: 'optionSource.searchPropertyPaths',
1384
2516
  label: 'Campos de busca',
@@ -1413,6 +2545,48 @@ const entityLookupProperties = [
1413
2545
  defaultValue: true,
1414
2546
  hint: 'Mantem entidades ja selecionadas visiveis durante busca paginada.',
1415
2547
  },
2548
+ {
2549
+ name: 'optionSource.filtering.availableFilters',
2550
+ label: 'Filtros disponiveis',
2551
+ editorType: 'textarea',
2552
+ group: 'Busca',
2553
+ hint: 'JSON array de filtros estruturados. Ex.: [{ \"field\": \"status\", \"type\": \"enum\", \"operators\": [\"in\"] }].',
2554
+ },
2555
+ {
2556
+ name: 'optionSource.filtering.defaultFilters',
2557
+ label: 'Filtros padrao',
2558
+ editorType: 'textarea',
2559
+ group: 'Busca',
2560
+ hint: 'JSON object. Ex.: { \"status\": [\"ACTIVE\"] }.',
2561
+ },
2562
+ {
2563
+ name: 'optionSource.filtering.sortOptions',
2564
+ label: 'Ordenacoes disponiveis',
2565
+ editorType: 'textarea',
2566
+ group: 'Busca',
2567
+ hint: 'JSON array. Ex.: [{ \"key\": \"code:asc\", \"field\": \"code\", \"direction\": \"asc\", \"label\": \"Codigo\" }].',
2568
+ },
2569
+ {
2570
+ name: 'optionSource.filtering.defaultSort',
2571
+ label: 'Ordenacao padrao',
2572
+ editorType: 'text',
2573
+ group: 'Busca',
2574
+ hint: 'Chave default do sort declarada em sortOptions.',
2575
+ },
2576
+ {
2577
+ name: 'optionSource.filtering.quickFilterFields',
2578
+ label: 'Campos de filtro rapido',
2579
+ editorType: 'textarea',
2580
+ group: 'Busca',
2581
+ hint: 'JSON array ou uma linha por campo rapido no toolbar do lookup.',
2582
+ },
2583
+ {
2584
+ name: 'optionSource.filtering.searchPlaceholder',
2585
+ label: 'Placeholder canonico de busca',
2586
+ editorType: 'text',
2587
+ group: 'Busca',
2588
+ hint: 'Fallback do optionSource.filtering quando o campo nao sobrescreve searchPlaceholder.',
2589
+ },
1416
2590
  {
1417
2591
  name: 'optionSource.dependsOn',
1418
2592
  label: 'Dependencias',
@@ -1558,11 +2732,226 @@ const entityLookupProperties = [
1558
2732
  editorType: 'select',
1559
2733
  group: 'Navegacao',
1560
2734
  options: [
1561
- { value: 'route', text: 'Rota' },
2735
+ { value: 'samePage', text: 'samePage' },
2736
+ { value: 'newTab', text: 'newTab' },
2737
+ { value: 'drawer', text: 'Drawer' },
2738
+ { value: 'modal', text: 'Modal' },
2739
+ ],
2740
+ },
2741
+ {
2742
+ name: 'optionSource.detail.kind',
2743
+ label: 'Tipo de detalhe',
2744
+ editorType: 'select',
2745
+ group: 'Navegacao',
2746
+ options: [
2747
+ { value: 'surface', text: 'surface' },
2748
+ { value: 'route', text: 'route' },
2749
+ { value: 'href', text: 'href' },
2750
+ ],
2751
+ hint: 'Use surface como contrato canonico de plataforma; route/href ficam como fallback legado.',
2752
+ },
2753
+ {
2754
+ name: 'optionSource.detail.surfaceId',
2755
+ label: 'Surface de detalhe',
2756
+ editorType: 'text',
2757
+ group: 'Navegacao',
2758
+ hint: 'Id da surface item-level preferida. Ex.: view, detail.',
2759
+ },
2760
+ {
2761
+ name: 'optionSource.detail.presentation',
2762
+ label: 'Apresentacao da surface',
2763
+ editorType: 'select',
2764
+ group: 'Navegacao',
2765
+ options: [
1562
2766
  { value: 'drawer', text: 'Drawer' },
1563
- { value: 'dialog', text: 'Dialog' },
1564
- { value: 'external', text: 'Externo' },
2767
+ { value: 'modal', text: 'Modal' },
1565
2768
  ],
2769
+ hint: 'Como o runtime deve materializar a surface de detalhe.',
2770
+ },
2771
+ {
2772
+ name: 'optionSource.detail.preferredWidget',
2773
+ label: 'Widget preferido',
2774
+ editorType: 'text',
2775
+ group: 'Navegacao',
2776
+ hint: 'Hint semantico para autoria/discovery. Ex.: praxis-dynamic-form.',
2777
+ },
2778
+ {
2779
+ name: 'optionSource.detail.mode',
2780
+ label: 'Modo da surface',
2781
+ editorType: 'select',
2782
+ group: 'Navegacao',
2783
+ options: [
2784
+ { value: 'view', text: 'view' },
2785
+ { value: 'edit', text: 'edit' },
2786
+ { value: 'create', text: 'create' },
2787
+ ],
2788
+ hint: 'Modo semantico da surface de detalhe.',
2789
+ },
2790
+ {
2791
+ name: 'optionSource.create.hrefTemplate',
2792
+ label: 'Template create href',
2793
+ editorType: 'text',
2794
+ group: 'Navegacao',
2795
+ hint: 'Template opcional de criacao publicado pelo optionSource.',
2796
+ },
2797
+ {
2798
+ name: 'optionSource.create.routeTemplate',
2799
+ label: 'Template create route',
2800
+ editorType: 'text',
2801
+ group: 'Navegacao',
2802
+ hint: 'Rota opcional de criacao, por exemplo /suppliers/new.',
2803
+ },
2804
+ {
2805
+ name: 'optionSource.create.openMode',
2806
+ label: 'Modo de create',
2807
+ editorType: 'select',
2808
+ group: 'Navegacao',
2809
+ options: [
2810
+ { value: 'samePage', text: 'samePage' },
2811
+ { value: 'newTab', text: 'newTab' },
2812
+ { value: 'drawer', text: 'Drawer' },
2813
+ { value: 'modal', text: 'Modal' },
2814
+ ],
2815
+ },
2816
+ {
2817
+ name: 'dialog.enabled',
2818
+ label: 'Dialog habilitado',
2819
+ editorType: 'checkbox',
2820
+ group: 'Dialogo avancado',
2821
+ },
2822
+ {
2823
+ name: 'dialog.title',
2824
+ label: 'Titulo do dialog',
2825
+ editorType: 'text',
2826
+ group: 'Dialogo avancado',
2827
+ },
2828
+ {
2829
+ name: 'dialog.size',
2830
+ label: 'Tamanho do dialog',
2831
+ editorType: 'select',
2832
+ group: 'Dialogo avancado',
2833
+ options: [
2834
+ { value: 'sm', text: 'sm' },
2835
+ { value: 'md', text: 'md' },
2836
+ { value: 'lg', text: 'lg' },
2837
+ { value: 'xl', text: 'xl' },
2838
+ { value: 'full', text: 'full' },
2839
+ ],
2840
+ },
2841
+ {
2842
+ name: 'dialog.previewPanel',
2843
+ label: 'Preview panel',
2844
+ editorType: 'checkbox',
2845
+ group: 'Dialogo avancado',
2846
+ },
2847
+ {
2848
+ name: 'dialog.allowColumnChooser',
2849
+ label: 'Permitir seletor de colunas',
2850
+ editorType: 'checkbox',
2851
+ group: 'Dialogo avancado',
2852
+ },
2853
+ {
2854
+ name: 'dialog.allowSavedViews',
2855
+ label: 'Permitir views salvas',
2856
+ editorType: 'checkbox',
2857
+ group: 'Dialogo avancado',
2858
+ },
2859
+ {
2860
+ name: 'dialog.resultColumns',
2861
+ label: 'Colunas do resultado',
2862
+ editorType: 'textarea',
2863
+ group: 'Dialogo avancado',
2864
+ hint: 'JSON array. Ex.: [{ \"field\": \"code\", \"label\": \"Codigo\", \"kind\": \"code\", \"width\": \"140px\" }].',
2865
+ },
2866
+ {
2867
+ name: 'dialog.openActionLabel',
2868
+ label: 'Label abrir dialog',
2869
+ editorType: 'text',
2870
+ group: 'Dialogo avancado',
2871
+ },
2872
+ {
2873
+ name: 'dialog.applyActionLabel',
2874
+ label: 'Label aplicar dialog',
2875
+ editorType: 'text',
2876
+ group: 'Dialogo avancado',
2877
+ },
2878
+ {
2879
+ name: 'dialog.cancelActionLabel',
2880
+ label: 'Label cancelar dialog',
2881
+ editorType: 'text',
2882
+ group: 'Dialogo avancado',
2883
+ },
2884
+ {
2885
+ name: 'detailActionLabel',
2886
+ label: 'Label detalhe',
2887
+ editorType: 'text',
2888
+ group: 'Acoes do lookup',
2889
+ },
2890
+ {
2891
+ name: 'changeActionLabel',
2892
+ label: 'Label trocar',
2893
+ editorType: 'text',
2894
+ group: 'Acoes do lookup',
2895
+ },
2896
+ {
2897
+ name: 'copyCodeActionLabel',
2898
+ label: 'Label copiar codigo',
2899
+ editorType: 'text',
2900
+ group: 'Acoes do lookup',
2901
+ },
2902
+ {
2903
+ name: 'copyIdActionLabel',
2904
+ label: 'Label copiar ID',
2905
+ editorType: 'text',
2906
+ group: 'Acoes do lookup',
2907
+ },
2908
+ {
2909
+ name: 'createActionLabel',
2910
+ label: 'Label criar',
2911
+ editorType: 'text',
2912
+ group: 'Acoes do lookup',
2913
+ },
2914
+ {
2915
+ name: 'clearActionLabel',
2916
+ label: 'Label limpar',
2917
+ editorType: 'text',
2918
+ group: 'Acoes do lookup',
2919
+ },
2920
+ {
2921
+ name: 'actions.showDetail',
2922
+ label: 'Acao: detalhe',
2923
+ editorType: 'checkbox',
2924
+ group: 'Acoes do lookup',
2925
+ },
2926
+ {
2927
+ name: 'actions.showChange',
2928
+ label: 'Acao: trocar',
2929
+ editorType: 'checkbox',
2930
+ group: 'Acoes do lookup',
2931
+ },
2932
+ {
2933
+ name: 'actions.showCopyCode',
2934
+ label: 'Acao: copiar codigo',
2935
+ editorType: 'checkbox',
2936
+ group: 'Acoes do lookup',
2937
+ },
2938
+ {
2939
+ name: 'actions.showCopyId',
2940
+ label: 'Acao: copiar ID',
2941
+ editorType: 'checkbox',
2942
+ group: 'Acoes do lookup',
2943
+ },
2944
+ {
2945
+ name: 'actions.showCreate',
2946
+ label: 'Acao: criar',
2947
+ editorType: 'checkbox',
2948
+ group: 'Acoes do lookup',
2949
+ },
2950
+ {
2951
+ name: 'actions.showClear',
2952
+ label: 'Acao: limpar',
2953
+ editorType: 'checkbox',
2954
+ group: 'Acoes do lookup',
1566
2955
  },
1567
2956
  ];
1568
2957
 
@@ -3485,6 +4874,18 @@ const sliderProperties = [
3485
4874
  // Formato/Comportamento
3486
4875
  { name: 'step', label: 'Step', editorType: 'number', group: 'Formato/Comportamento', hint: 'Incremento entre valores (ex.: 1, 0.5). Combine com min/max.' },
3487
4876
  { name: 'thumbLabel', label: 'Thumb label', editorType: 'checkbox', group: 'Formato/Comportamento', hint: 'Exibe valor sobre o thumb ao arrastar.' },
4877
+ { name: 'valueLabelDisplay', label: 'Value label', editorType: 'select', group: 'Formato/Comportamento', options: [
4878
+ { value: 'off', text: 'Desligado' },
4879
+ { value: 'auto', text: 'Ao interagir' },
4880
+ { value: 'on', text: 'Sempre visivel' },
4881
+ ], hint: 'Controla quando o valor aparece no thumb.' },
4882
+ { name: 'valueLabelFormat', label: 'Formato do valor', editorType: 'select', group: 'Formato/Comportamento', options: [
4883
+ { value: 'number', text: 'Numero' },
4884
+ { value: 'percent', text: 'Percentual' },
4885
+ { value: 'currency', text: 'Moeda' },
4886
+ { value: 'compact', text: 'Compacto' },
4887
+ { value: 'storage', text: 'Armazenamento' },
4888
+ ], hint: 'Formatter declarativo para labels e aria-valuetext.' },
3488
4889
  { name: 'vertical', label: 'Vertical', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'fmt.flags', inline: true, hint: 'Altera para eixo Y.' },
3489
4890
  { name: 'invert', label: 'Inverter', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'fmt.flags', inline: true, hint: 'Inverte direção (direita→esquerda).' },
3490
4891
  { name: 'showTicks', label: 'Ticks', editorType: 'select', group: 'Formato/Comportamento', options: [
@@ -3492,6 +4893,26 @@ const sliderProperties = [
3492
4893
  { value: true, text: 'Com ticks' },
3493
4894
  { value: 'auto', text: 'Auto' },
3494
4895
  ], hint: 'Marcas na trilha; auto calcula com base no step.' },
4896
+ { name: 'marks', label: 'Marcadores (JSON)', editorType: 'textarea', group: 'Formato/Comportamento', hint: 'Array JSON. Ex.: [{"value":0,"label":"Baixo"},{"value":100,"label":"Alto","tone":"danger"}].' },
4897
+ { name: 'semanticBands', label: 'Bandas semanticas (JSON)', editorType: 'textarea', group: 'Formato/Comportamento', hint: 'Array JSON. Ex.: [{"start":0,"end":40,"label":"Saudavel","tone":"success"}].' },
4898
+ { name: 'distribution', label: 'Distribuicao (JSON)', editorType: 'textarea', group: 'Formato/Comportamento', hint: 'Array JSON ou config de histograma compacto. Ex.: {"colorMode":"gradient","bins":[4,8,16,12,6]}.' },
4899
+ { name: 'track', label: 'Track', editorType: 'select', group: 'Formato/Comportamento', options: [
4900
+ { value: 'normal', text: 'Normal' },
4901
+ { value: 'inverted', text: 'Invertido' },
4902
+ { value: 'none', text: 'Sem track ativo' },
4903
+ ], hint: 'Apresentacao visual do track.' },
4904
+ { name: 'size', label: 'Tamanho', editorType: 'select', group: 'Formato/Comportamento', options: [
4905
+ { value: 'small', text: 'Pequeno' },
4906
+ { value: 'medium', text: 'Medio' },
4907
+ { value: 'large', text: 'Grande' },
4908
+ ], hint: 'Densidade visual do slider.' },
4909
+ { name: 'scale', label: 'Escala', editorType: 'select', group: 'Formato/Comportamento', options: [
4910
+ { value: 'linear', text: 'Linear' },
4911
+ { value: 'percent', text: 'Percentual' },
4912
+ { value: 'log', text: 'Logaritmica' },
4913
+ { value: 'pow2', text: 'Potencia 2' },
4914
+ ], hint: 'Escala declarativa para dominios nao lineares.' },
4915
+ { name: 'shiftStep', label: 'Shift step', editorType: 'number', group: 'Formato/Comportamento', hint: 'Incremento acelerado para teclado.' },
3495
4916
  { name: 'discrete', label: 'Discreto', editorType: 'checkbox', group: 'Formato/Comportamento', hint: 'Exibe ticks/labels fixos para valores discretos.' },
3496
4917
  // Validação
3497
4918
  { name: 'min', label: 'Mínimo', editorType: 'number', group: 'Validação', row: 'val.minmax', inline: true },
@@ -3531,11 +4952,44 @@ const rangeSliderProperties = [
3531
4952
  ], hint: 'Escolha faixa para dois marcadores.' },
3532
4953
  { name: 'step', label: 'Step', editorType: 'number', group: 'Formato/Comportamento', hint: 'Incremento entre valores (ex.: 1, 0.5). Combine com min/max.' },
3533
4954
  { name: 'thumbLabel', label: 'Thumb label', editorType: 'checkbox', group: 'Formato/Comportamento', hint: 'Exibe o valor sobre o thumb ao arrastar.' },
4955
+ { name: 'valueLabelDisplay', label: 'Value label', editorType: 'select', group: 'Formato/Comportamento', options: [
4956
+ { value: 'off', text: 'Desligado' },
4957
+ { value: 'auto', text: 'Ao interagir' },
4958
+ { value: 'on', text: 'Sempre visivel' },
4959
+ ], hint: 'Controla quando o valor aparece no thumb.' },
4960
+ { name: 'valueLabelFormat', label: 'Formato do valor', editorType: 'select', group: 'Formato/Comportamento', options: [
4961
+ { value: 'number', text: 'Numero' },
4962
+ { value: 'percent', text: 'Percentual' },
4963
+ { value: 'currency', text: 'Moeda' },
4964
+ { value: 'compact', text: 'Compacto' },
4965
+ { value: 'storage', text: 'Armazenamento' },
4966
+ ], hint: 'Formatter declarativo para labels e aria-valuetext.' },
3534
4967
  { name: 'showTicks', label: 'Ticks', editorType: 'select', group: 'Formato/Comportamento', options: [
3535
4968
  { value: false, text: 'Sem ticks' },
3536
4969
  { value: true, text: 'Com ticks' },
3537
4970
  { value: 'auto', text: 'Auto' },
3538
4971
  ], hint: 'Marcas na trilha; auto calcula com base no step.' },
4972
+ { name: 'marks', label: 'Marcadores (JSON)', editorType: 'textarea', group: 'Formato/Comportamento', hint: 'Array JSON. Ex.: [{"value":0,"label":"Min"},{"value":100,"label":"Max","tone":"danger"}].' },
4973
+ { name: 'semanticBands', label: 'Bandas semanticas (JSON)', editorType: 'textarea', group: 'Formato/Comportamento', hint: 'Array JSON. Ex.: [{"start":0,"end":40,"label":"Baixo","tone":"success"}].' },
4974
+ { name: 'distribution', label: 'Distribuicao (JSON)', editorType: 'textarea', group: 'Formato/Comportamento', hint: 'Array JSON ou config de histograma compacto. Ex.: {"colorMode":"gradient","bins":[4,8,16,12,6]}.' },
4975
+ { name: 'track', label: 'Track', editorType: 'select', group: 'Formato/Comportamento', options: [
4976
+ { value: 'normal', text: 'Normal' },
4977
+ { value: 'inverted', text: 'Invertido' },
4978
+ { value: 'none', text: 'Sem track ativo' },
4979
+ ], hint: 'Apresentacao visual do track.' },
4980
+ { name: 'size', label: 'Tamanho', editorType: 'select', group: 'Formato/Comportamento', options: [
4981
+ { value: 'small', text: 'Pequeno' },
4982
+ { value: 'medium', text: 'Medio' },
4983
+ { value: 'large', text: 'Grande' },
4984
+ ], hint: 'Densidade visual do slider.' },
4985
+ { name: 'scale', label: 'Escala', editorType: 'select', group: 'Formato/Comportamento', options: [
4986
+ { value: 'linear', text: 'Linear' },
4987
+ { value: 'percent', text: 'Percentual' },
4988
+ { value: 'log', text: 'Logaritmica' },
4989
+ { value: 'pow2', text: 'Potencia 2' },
4990
+ ], hint: 'Escala declarativa para dominios nao lineares.' },
4991
+ { name: 'shiftStep', label: 'Shift step', editorType: 'number', group: 'Formato/Comportamento', hint: 'Incremento acelerado para teclado.' },
4992
+ { name: 'disableThumbSwap', label: 'Bloquear troca dos thumbs', editorType: 'checkbox', group: 'Formato/Comportamento', hint: 'Mantem start/end sem permitir inversao visual no modo range.' },
3539
4993
  { name: 'discrete', label: 'Discreto', editorType: 'checkbox', group: 'Formato/Comportamento', hint: 'Mostra valor fixo por tick; use com showTicks.' },
3540
4994
  { name: 'readonly', label: 'Somente leitura', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comp.state', inline: true, hint: 'Mantém o valor renderizado sem permitir ajuste do slider.' },
3541
4995
  { name: 'disabled', label: 'Desabilitado', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'comp.state', inline: true, hint: 'Desativa arraste, foco e presets rápidos.' },
@@ -5678,6 +7132,7 @@ const INLINE_FILTER_EDITOR_PROPERTIES_BY_CONTROL_TYPE = Object.freeze({
5678
7132
  [INLINE_FILTER_CONTROL_TYPES.AUTOCOMPLETE]: selectProperties,
5679
7133
  [INLINE_FILTER_CONTROL_TYPES.MULTI_SELECT]: selectProperties,
5680
7134
  [INLINE_FILTER_CONTROL_TYPES.INPUT]: inputProperties,
7135
+ [INLINE_FILTER_CONTROL_TYPES.PHONE]: inputProperties,
5681
7136
  [INLINE_FILTER_CONTROL_TYPES.TOGGLE]: toggleProperties,
5682
7137
  [INLINE_FILTER_CONTROL_TYPES.RANGE]: rangeSliderProperties,
5683
7138
  [INLINE_FILTER_CONTROL_TYPES.PERIOD_RANGE]: rangeSliderProperties,
@@ -5717,6 +7172,7 @@ class FieldMetadataEditorComponent {
5717
7172
  cancel = new EventEmitter();
5718
7173
  form;
5719
7174
  normalizedProps = [];
7175
+ renderedProps = [];
5720
7176
  initialSnapshot = null;
5721
7177
  baselineReady = false;
5722
7178
  // Lista de tipos suportados no editor (curada para os tipos com config disponível)
@@ -6179,6 +7635,7 @@ class FieldMetadataEditorComponent {
6179
7635
  }
6180
7636
  }
6181
7637
  catch { }
7638
+ this.renderedProps = this.buildRenderedProps(this.normalizedProps);
6182
7639
  // Register contextual validators for INPUT/TEXTAREA (minLength <= maxLength)
6183
7640
  if (this.controlType === FieldControlType.INPUT ||
6184
7641
  this.controlType === FieldControlType.TEXTAREA) {
@@ -6393,6 +7850,9 @@ class FieldMetadataEditorComponent {
6393
7850
  });
6394
7851
  }
6395
7852
  apply() {
7853
+ if (!this.canEmitSettingsValue()) {
7854
+ return;
7855
+ }
6396
7856
  const raw = this.factory.extractPatch(this.form);
6397
7857
  const patch = this.buildDeltaPatch(raw, this.initialSnapshot);
6398
7858
  this.normalizeKnownTextareaPatchValues(patch);
@@ -6420,6 +7880,9 @@ class FieldMetadataEditorComponent {
6420
7880
  }
6421
7881
  // Settings Panel value provider API
6422
7882
  getSettingsValue() {
7883
+ if (!this.canEmitSettingsValue()) {
7884
+ return {};
7885
+ }
6423
7886
  const raw = this.factory.extractPatch(this.form);
6424
7887
  const patch = this.buildDeltaPatch(raw, this.initialSnapshot);
6425
7888
  this.normalizeKnownTextareaPatchValues(patch);
@@ -6524,17 +7987,38 @@ class FieldMetadataEditorComponent {
6524
7987
  }
6525
7988
  normalizeEntityLookupPatchValues(patch) {
6526
7989
  const optionSource = patch?.optionSource;
6527
- if (!optionSource || typeof optionSource !== 'object') {
6528
- return;
7990
+ if (optionSource && typeof optionSource === 'object') {
7991
+ this.normalizeStringListProperty(optionSource, 'descriptionPropertyPaths');
7992
+ this.normalizeStringListProperty(optionSource, 'searchPropertyPaths');
7993
+ this.normalizeStringListProperty(optionSource, 'dependsOn');
7994
+ this.normalizeJsonObjectProperty(optionSource, 'dependencyFilterMap');
7995
+ this.normalizeJsonObjectProperty(optionSource, 'create');
7996
+ const selectionPolicy = optionSource.selectionPolicy;
7997
+ if (selectionPolicy && typeof selectionPolicy === 'object') {
7998
+ this.normalizeStringListProperty(selectionPolicy, 'allowedStatuses');
7999
+ this.normalizeStringListProperty(selectionPolicy, 'blockedStatuses');
8000
+ }
8001
+ const filtering = optionSource.filtering;
8002
+ if (filtering && typeof filtering === 'object') {
8003
+ this.normalizeJsonArrayProperty(filtering, 'availableFilters');
8004
+ this.normalizeJsonObjectProperty(filtering, 'defaultFilters');
8005
+ this.normalizeJsonArrayProperty(filtering, 'sortOptions');
8006
+ this.normalizeStringListProperty(filtering, 'quickFilterFields');
8007
+ }
8008
+ const display = optionSource.display;
8009
+ if (display && typeof display === 'object') {
8010
+ this.normalizeJsonArrayProperty(display, 'fields');
8011
+ this.normalizeStringListProperty(display, 'secondaryPropertyPaths');
8012
+ this.normalizeStringListProperty(display, 'badgePropertyPaths');
8013
+ this.normalizeStringListProperty(display, 'badgeKeys');
8014
+ this.normalizeJsonObjectProperty(display, 'statusToneMap');
8015
+ }
6529
8016
  }
6530
- this.normalizeStringListProperty(optionSource, 'descriptionPropertyPaths');
6531
- this.normalizeStringListProperty(optionSource, 'searchPropertyPaths');
6532
- this.normalizeStringListProperty(optionSource, 'dependsOn');
6533
- this.normalizeJsonObjectProperty(optionSource, 'dependencyFilterMap');
6534
- const selectionPolicy = optionSource.selectionPolicy;
6535
- if (selectionPolicy && typeof selectionPolicy === 'object') {
6536
- this.normalizeStringListProperty(selectionPolicy, 'allowedStatuses');
6537
- this.normalizeStringListProperty(selectionPolicy, 'blockedStatuses');
8017
+ this.normalizeStringListProperty(patch, 'badgeKeys');
8018
+ this.normalizeJsonObjectProperty(patch, 'statusToneMap');
8019
+ const dialog = patch?.dialog;
8020
+ if (dialog && typeof dialog === 'object') {
8021
+ this.normalizeJsonArrayProperty(dialog, 'resultColumns');
6538
8022
  }
6539
8023
  }
6540
8024
  normalizeStringListProperty(target, key) {
@@ -6555,6 +8039,15 @@ class FieldMetadataEditorComponent {
6555
8039
  target[key] = parsed;
6556
8040
  }
6557
8041
  }
8042
+ normalizeJsonArrayProperty(target, key) {
8043
+ if (!target || typeof target !== 'object' || typeof target[key] !== 'string') {
8044
+ return;
8045
+ }
8046
+ const parsed = this.parseArrayTextarea(target[key]);
8047
+ if (parsed) {
8048
+ target[key] = parsed;
8049
+ }
8050
+ }
6558
8051
  normalizeArrayPatchValues(patch) {
6559
8052
  const arrayPatch = patch?.array;
6560
8053
  if (!arrayPatch || typeof arrayPatch !== 'object') {
@@ -6663,7 +8156,11 @@ class FieldMetadataEditorComponent {
6663
8156
  }
6664
8157
  }
6665
8158
  reset() {
6666
- this.form.reset(this.factory.extractPatch(this.form));
8159
+ const snapshot = this.cloneSnapshot(this.initialSnapshot ?? this.form.getRawValue());
8160
+ this.form.reset(snapshot);
8161
+ this.form.markAsPristine();
8162
+ this.form.markAsUntouched();
8163
+ this.form.updateValueAndValidity({ emitEvent: true });
6667
8164
  this.isDirty$.next(false);
6668
8165
  }
6669
8166
  onControlTypeChange(next) {
@@ -6681,30 +8178,167 @@ class FieldMetadataEditorComponent {
6681
8178
  const missingSharedProps = fieldSubmitProperties.filter((prop) => !propNames.has(prop.name));
6682
8179
  return missingSharedProps.length ? [...props, ...missingSharedProps] : props;
6683
8180
  }
6684
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: FieldMetadataEditorComponent, deps: [{ token: ConfigRegistryService }, { token: SchemaNormalizerService }, { token: DynamicFormFactoryService }, { token: ContextValidatorRegistryService }], target: i0.ɵɵFactoryTarget.Component });
6685
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: FieldMetadataEditorComponent, isStandalone: true, selector: "praxis-field-metadata-editor", inputs: { controlType: "controlType", seed: "seed" }, outputs: { applied: "applied", cancel: "cancel" }, ngImport: i0, template: `
6686
- <div class="p-3" *ngIf="form">
6687
- <praxis-dynamic-editor-renderer
6688
- [properties]="normalizedProps"
6689
- [form]="form"
6690
- ></praxis-dynamic-editor-renderer>
6691
- </div>
6692
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: DynamicEditorRendererComponent, selector: "praxis-dynamic-editor-renderer", inputs: ["properties", "form"] }] });
8181
+ get isArrayEditor() {
8182
+ return this.controlType === FieldControlType.ARRAY_INPUT;
8183
+ }
8184
+ buildRenderedProps(props) {
8185
+ if (!this.isArrayEditor) {
8186
+ return props;
8187
+ }
8188
+ return (props || []).filter((prop) => prop?.name !== 'array.itemSchema.fields');
8189
+ }
8190
+ cloneSnapshot(value) {
8191
+ return this.cloneMetadataValue(value, new WeakMap());
8192
+ }
8193
+ cloneMetadataValue(value, seen) {
8194
+ if (!value || typeof value !== 'object') {
8195
+ return value;
8196
+ }
8197
+ if (seen.has(value)) {
8198
+ return seen.get(value);
8199
+ }
8200
+ if (value instanceof Date) {
8201
+ return new Date(value.getTime());
8202
+ }
8203
+ if (value instanceof RegExp) {
8204
+ return new RegExp(value.source, value.flags);
8205
+ }
8206
+ if (Array.isArray(value)) {
8207
+ const clone = [];
8208
+ seen.set(value, clone);
8209
+ clone.push(...value.map((entry) => this.cloneMetadataValue(entry, seen)));
8210
+ return clone;
8211
+ }
8212
+ if (value instanceof Map) {
8213
+ const clone = new Map();
8214
+ seen.set(value, clone);
8215
+ value.forEach((entry, key) => {
8216
+ clone.set(this.cloneMetadataValue(key, seen), this.cloneMetadataValue(entry, seen));
8217
+ });
8218
+ return clone;
8219
+ }
8220
+ if (value instanceof Set) {
8221
+ const clone = new Set();
8222
+ seen.set(value, clone);
8223
+ value.forEach((entry) => clone.add(this.cloneMetadataValue(entry, seen)));
8224
+ return clone;
8225
+ }
8226
+ const prototype = Object.getPrototypeOf(value);
8227
+ if (prototype !== Object.prototype && prototype !== null) {
8228
+ return value;
8229
+ }
8230
+ const clone = {};
8231
+ seen.set(value, clone);
8232
+ for (const [key, entry] of Object.entries(value)) {
8233
+ clone[key] = this.cloneMetadataValue(entry, seen);
8234
+ }
8235
+ return clone;
8236
+ }
8237
+ canEmitSettingsValue() {
8238
+ this.validateArrayItemSchemaFields();
8239
+ this.form.updateValueAndValidity({ emitEvent: true });
8240
+ const valid = this.form.valid;
8241
+ this.isValid$.next(valid);
8242
+ if (!valid) {
8243
+ this.form.markAllAsTouched();
8244
+ }
8245
+ return valid;
8246
+ }
8247
+ validateArrayItemSchemaFields() {
8248
+ if (!this.isArrayEditor) {
8249
+ return;
8250
+ }
8251
+ const control = this.form.get('array.itemSchema.fields');
8252
+ if (!control) {
8253
+ return;
8254
+ }
8255
+ const value = control.value;
8256
+ const fields = Array.isArray(value)
8257
+ ? value
8258
+ : typeof value === 'string' && value.trim()
8259
+ ? this.parseArrayItemFields(value)
8260
+ : [];
8261
+ const seen = new Set();
8262
+ const duplicated = new Set();
8263
+ let hasEmptyName = false;
8264
+ for (const field of fields) {
8265
+ const name = String(field?.name || '').trim();
8266
+ if (!name) {
8267
+ hasEmptyName = true;
8268
+ continue;
8269
+ }
8270
+ if (seen.has(name)) {
8271
+ duplicated.add(name);
8272
+ }
8273
+ seen.add(name);
8274
+ }
8275
+ this.setControlError(control, 'arrayFieldNameRequired', hasEmptyName);
8276
+ this.setControlError(control, 'arrayFieldNameDuplicated', duplicated.size ? { names: Array.from(duplicated) } : null);
8277
+ }
8278
+ parseArrayItemFields(value) {
8279
+ try {
8280
+ const parsed = JSON.parse(value);
8281
+ return Array.isArray(parsed) ? parsed : [];
8282
+ }
8283
+ catch {
8284
+ return [];
8285
+ }
8286
+ }
8287
+ setControlError(control, key, value) {
8288
+ const errors = { ...(control.errors || {}) };
8289
+ const hasError = value !== null && value !== undefined && value !== false;
8290
+ if (hasError) {
8291
+ errors[key] = value === true ? true : value;
8292
+ }
8293
+ else {
8294
+ delete errors[key];
8295
+ }
8296
+ control.setErrors(Object.keys(errors).length ? errors : null);
8297
+ }
8298
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: FieldMetadataEditorComponent, deps: [{ token: ConfigRegistryService }, { token: SchemaNormalizerService }, { token: DynamicFormFactoryService }, { token: ContextValidatorRegistryService }], target: i0.ɵɵFactoryTarget.Component });
8299
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: FieldMetadataEditorComponent, isStandalone: true, selector: "praxis-field-metadata-editor", inputs: { controlType: "controlType", seed: "seed" }, outputs: { applied: "applied", cancel: "cancel" }, ngImport: i0, template: `
8300
+ @if (form) {
8301
+ <div class="p-3">
8302
+ <praxis-dynamic-editor-renderer
8303
+ [properties]="renderedProps"
8304
+ [form]="form"
8305
+ ></praxis-dynamic-editor-renderer>
8306
+ @if (isArrayEditor) {
8307
+ <praxis-array-item-schema-fields-editor
8308
+ [control]="form.get('array.itemSchema.fields')"
8309
+ [arrayControl]="form.get('array')"
8310
+ ></praxis-array-item-schema-fields-editor>
8311
+ }
8312
+ </div>
8313
+ }
8314
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: DynamicEditorRendererComponent, selector: "praxis-dynamic-editor-renderer", inputs: ["properties", "form"] }, { kind: "component", type: ArrayItemSchemaFieldsEditorComponent, selector: "praxis-array-item-schema-fields-editor", inputs: ["control", "arrayControl"] }] });
6693
8315
  }
6694
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: FieldMetadataEditorComponent, decorators: [{
8316
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: FieldMetadataEditorComponent, decorators: [{
6695
8317
  type: Component,
6696
8318
  args: [{
6697
8319
  selector: 'praxis-field-metadata-editor',
6698
8320
  standalone: true,
6699
- imports: [CommonModule, ReactiveFormsModule, DynamicEditorRendererComponent],
8321
+ imports: [
8322
+ ReactiveFormsModule,
8323
+ DynamicEditorRendererComponent,
8324
+ ArrayItemSchemaFieldsEditorComponent
8325
+ ],
6700
8326
  template: `
6701
- <div class="p-3" *ngIf="form">
6702
- <praxis-dynamic-editor-renderer
6703
- [properties]="normalizedProps"
6704
- [form]="form"
6705
- ></praxis-dynamic-editor-renderer>
6706
- </div>
6707
- `,
8327
+ @if (form) {
8328
+ <div class="p-3">
8329
+ <praxis-dynamic-editor-renderer
8330
+ [properties]="renderedProps"
8331
+ [form]="form"
8332
+ ></praxis-dynamic-editor-renderer>
8333
+ @if (isArrayEditor) {
8334
+ <praxis-array-item-schema-fields-editor
8335
+ [control]="form.get('array.itemSchema.fields')"
8336
+ [arrayControl]="form.get('array')"
8337
+ ></praxis-array-item-schema-fields-editor>
8338
+ }
8339
+ </div>
8340
+ }
8341
+ `,
6708
8342
  }]
6709
8343
  }], ctorParameters: () => [{ type: ConfigRegistryService }, { type: SchemaNormalizerService }, { type: DynamicFormFactoryService }, { type: ContextValidatorRegistryService }], propDecorators: { controlType: [{
6710
8344
  type: Input
@@ -6771,10 +8405,10 @@ class CascadeRulesService {
6771
8405
  dependencyValuePath: rule.dependencyValuePath || inferredValueKey || 'id',
6772
8406
  };
6773
8407
  }
6774
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CascadeRulesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
6775
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CascadeRulesService, providedIn: 'root' });
8408
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CascadeRulesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
8409
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CascadeRulesService, providedIn: 'root' });
6776
8410
  }
6777
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CascadeRulesService, decorators: [{
8411
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CascadeRulesService, decorators: [{
6778
8412
  type: Injectable,
6779
8413
  args: [{ providedIn: 'root' }]
6780
8414
  }] });
@@ -6788,10 +8422,10 @@ class CascadeManagerTabComponent {
6788
8422
  searchTerm = '';
6789
8423
  searchTimer = null;
6790
8424
  searchValue = '';
6791
- rules = signal(new Map(), ...(ngDevMode ? [{ debugName: "rules" }] : []));
6792
- selectedFieldSig = signal(null, ...(ngDevMode ? [{ debugName: "selectedFieldSig" }] : []));
8425
+ rules = signal(new Map(), ...(ngDevMode ? [{ debugName: "rules" }] : /* istanbul ignore next */ []));
8426
+ selectedFieldSig = signal(null, ...(ngDevMode ? [{ debugName: "selectedFieldSig" }] : /* istanbul ignore next */ []));
6793
8427
  // Inline editor state (MVP)
6794
- editing = signal(false, ...(ngDevMode ? [{ debugName: "editing" }] : []));
8428
+ editing = signal(false, ...(ngDevMode ? [{ debugName: "editing" }] : /* istanbul ignore next */ []));
6795
8429
  editDepList = [];
6796
8430
  editEnable = true;
6797
8431
  editReset = false;
@@ -7143,8 +8777,8 @@ class CascadeManagerTabComponent {
7143
8777
  }
7144
8778
  return [...new Set(suggestions)];
7145
8779
  }
7146
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CascadeManagerTabComponent, deps: [{ token: CascadeRulesService }], target: i0.ɵɵFactoryTarget.Component });
7147
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: CascadeManagerTabComponent, isStandalone: true, selector: "praxis-cascade-manager-tab", inputs: { fields: "fields", connections: "connections" }, outputs: { apply: "apply", cancel: "cancel" }, usesOnChanges: true, ngImport: i0, template: `
8780
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CascadeManagerTabComponent, deps: [{ token: CascadeRulesService }], target: i0.ɵɵFactoryTarget.Component });
8781
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: CascadeManagerTabComponent, isStandalone: true, selector: "praxis-cascade-manager-tab", inputs: { fields: "fields", connections: "connections" }, outputs: { apply: "apply", cancel: "cancel" }, usesOnChanges: true, ngImport: i0, template: `
7148
8782
  <div class="cm-root">
7149
8783
  <!-- Left: Fields list -->
7150
8784
  <div class="cm-left">
@@ -7152,276 +8786,301 @@ class CascadeManagerTabComponent {
7152
8786
  <mat-label>Buscar campos</mat-label>
7153
8787
  <input matInput [ngModel]="searchTerm" (ngModelChange)="onSearchChange($event)">
7154
8788
  </mat-form-field>
7155
- <ng-container *ngIf="fields.length <= 50; else vscroll">
8789
+ @if (fields.length <= 50) {
7156
8790
  <div>
7157
- <div *ngFor="let f of filteredFields()" (click)="selectField(f.name)" class="field-item" [class.selected]="f.name===selectedField()" tabindex="0" (keydown)="onFieldKeydown($event, f.name)">
7158
- <div>
7159
- <div>{{ labelFor(f.name) }}</div>
7160
- <small style="opacity:.7">{{ f.name }}</small>
7161
- </div>
7162
- <div class="rule-badges" *ngIf="hasRule(f.name)">
7163
- <span class="rule-badge b-active" *ngIf="isCascadeEnabled(f.name)">Ativo</span>
7164
- <span class="rule-badge b-manual" *ngIf="loadOnChange(f.name)==='manual'">Manual</span>
7165
- <span class="rule-badge b-immediate" *ngIf="loadOnChange(f.name)==='immediate'">Immediate</span>
8791
+ @for (f of filteredFields(); track f) {
8792
+ <div (click)="selectField(f.name)" class="field-item" [class.selected]="f.name===selectedField()" tabindex="0" (keydown)="onFieldKeydown($event, f.name)">
8793
+ <div>
8794
+ <div>{{ labelFor(f.name) }}</div>
8795
+ <small style="opacity:.7">{{ f.name }}</small>
8796
+ </div>
8797
+ @if (hasRule(f.name)) {
8798
+ <div class="rule-badges">
8799
+ @if (isCascadeEnabled(f.name)) {
8800
+ <span class="rule-badge b-active">Ativo</span>
8801
+ }
8802
+ @if (loadOnChange(f.name)==='manual') {
8803
+ <span class="rule-badge b-manual">Manual</span>
8804
+ }
8805
+ @if (loadOnChange(f.name)==='immediate') {
8806
+ <span class="rule-badge b-immediate">Immediate</span>
8807
+ }
8808
+ </div>
8809
+ }
7166
8810
  </div>
7167
- </div>
8811
+ }
7168
8812
  </div>
7169
- </ng-container>
7170
- <ng-template #vscroll>
8813
+ } @else {
7171
8814
  <cdk-virtual-scroll-viewport itemSize="44" style="height: calc(100vh - 240px)">
7172
8815
  <div *cdkVirtualFor="let f of filteredFields()" (click)="selectField(f.name)" class="field-item" [class.selected]="f.name===selectedField()" tabindex="0" (keydown)="onFieldKeydown($event, f.name)">
7173
8816
  <div>
7174
8817
  <div>{{ labelFor(f.name) }}</div>
7175
8818
  <small style="opacity:.7">{{ f.name }}</small>
7176
8819
  </div>
7177
- <div class="rule-badges" *ngIf="hasRule(f.name)">
7178
- <span class="rule-badge b-active" *ngIf="isCascadeEnabled(f.name)">Ativo</span>
7179
- <span class="rule-badge b-manual" *ngIf="loadOnChange(f.name)==='manual'">Manual</span>
7180
- <span class="rule-badge b-immediate" *ngIf="loadOnChange(f.name)==='immediate'">Immediate</span>
7181
- </div>
8820
+ @if (hasRule(f.name)) {
8821
+ <div class="rule-badges">
8822
+ @if (isCascadeEnabled(f.name)) {
8823
+ <span class="rule-badge b-active">Ativo</span>
8824
+ }
8825
+ @if (loadOnChange(f.name)==='manual') {
8826
+ <span class="rule-badge b-manual">Manual</span>
8827
+ }
8828
+ @if (loadOnChange(f.name)==='immediate') {
8829
+ <span class="rule-badge b-immediate">Immediate</span>
8830
+ }
8831
+ </div>
8832
+ }
7182
8833
  </div>
7183
8834
  </cdk-virtual-scroll-viewport>
7184
- </ng-template>
8835
+ }
7185
8836
  </div>
7186
-
8837
+
7187
8838
  <!-- Right: Rules for selected field -->
7188
- <div class="cm-right" *ngIf="selectedField() as sel">
7189
- <div class="cm-header">
7190
- <div>
7191
- <h3 style="margin:0">{{ labelFor(sel) }}</h3>
7192
- <div style="opacity:.7">{{ sel }}</div>
7193
- </div>
7194
- <div>
7195
- <button mat-stroked-button color="primary" (click)="exportRules()" style="margin-right:8px">Exportar</button>
7196
- <button mat-stroked-button (click)="toggleImport()" style="margin-right:8px">Importar</button>
7197
- <button mat-flat-button color="primary" [matMenuTriggerFor]="addMenu" [disabled]="editing()">Adicionar</button>
7198
- <mat-menu #addMenu="matMenu">
7199
- <button mat-menu-item (click)="startEdit(sel)"><mat-icon [praxisIcon]="'add'"></mat-icon><span>Em branco</span></button>
7200
- <button mat-menu-item (click)="startTemplate(sel, 'rota-destino')" [disabled]="!hasField('rotaId') || !hasField('destinoReqId')"><mat-icon [praxisIcon]="'bolt'"></mat-icon><span>Rota → Destino (id)</span></button>
7201
- <button mat-menu-item (click)="startTemplate(sel, 'rota-destino-propriedade')" [disabled]="!hasField('rotaId') || !hasField('destinoReqId')"><mat-icon [praxisIcon]="'bolt'"></mat-icon><span>Rota + Destino → Propriedade (ids)</span></button>
7202
- </mat-menu>
7203
- </div>
7204
- </div>
7205
-
7206
- <!-- Conflict banner (connections) -->
7207
- <div class="banner" *ngIf="hasConflict(sel)">
7208
- <mat-icon [praxisIcon]="'link_off'"></mat-icon>
7209
- <div>
7210
- Connections atualizam este campo (filterCriteria). Considere desativar a cascata nativa ou usar ‘manual’.
7211
- </div>
7212
- <span class="spacer"></span>
7213
- <button mat-stroked-button color="primary" (click)="quickSet(sel,'disable')">Desativar</button>
7214
- <button mat-stroked-button color="accent" (click)="quickSet(sel,'manual')">Definir manual</button>
7215
- </div>
7216
-
7217
- <div *ngIf="importMode" class="import-panel">
7218
- <div style="display:flex; gap:8px; align-items:center; margin-bottom:8px"><mat-icon [praxisIcon]="'upload'"></mat-icon><strong>Importar regras (JSON)</strong></div>
7219
- <textarea matInput rows="6" class="full" [(ngModel)]="importJson" placeholder='{ "campo": { "dependencyFields": ["dep"], ... } }'></textarea>
7220
- <div style="display:flex; gap:8px; justify-content:flex-end; padding-top:8px">
7221
- <button mat-stroked-button (click)="toggleImport()">Cancelar</button>
7222
- <button mat-flat-button color="primary" (click)="applyImport()">Aplicar</button>
7223
- </div>
7224
- </div>
7225
-
7226
- <div *ngIf="!ruleFor(sel)">
7227
- <div class="rules-empty">Nenhuma cascata configurada. Clique em “Adicionar” para montar a dependência.</div>
7228
- </div>
7229
- <div *ngIf="ruleFor(sel) as rule">
7230
- <div class="rule-row">
8839
+ @if (selectedField(); as sel) {
8840
+ <div class="cm-right">
8841
+ <div class="cm-header">
7231
8842
  <div>
7232
- <div><strong>Dependentes:</strong> {{ rule.dependencyFields.join(', ') || '—' }}</div>
7233
- <div style="opacity:.7">Load on change: {{ rule.dependencyLoadOnChange || 'respectLoadOn' }} | Merge: {{ rule.dependencyMergeStrategy || 'merge' }}</div>
8843
+ <h3 style="margin:0">{{ labelFor(sel) }}</h3>
8844
+ <div style="opacity:.7">{{ sel }}</div>
7234
8845
  </div>
7235
- <div class="rule-actions">
7236
- <button mat-icon-button matTooltip="Editar" (click)="startEdit(sel)"><mat-icon [praxisIcon]="'edit'"></mat-icon></button>
7237
- <button mat-icon-button matTooltip="Duplicar" disabled><mat-icon [praxisIcon]="'content_copy'"></mat-icon></button>
7238
- <button mat-icon-button matTooltip="Remover" (click)="remove(sel)"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
8846
+ <div>
8847
+ <button mat-stroked-button color="primary" (click)="exportRules()" style="margin-right:8px">Exportar</button>
8848
+ <button mat-stroked-button (click)="toggleImport()" style="margin-right:8px">Importar</button>
8849
+ <button mat-flat-button color="primary" [matMenuTriggerFor]="addMenu" [disabled]="editing()">Adicionar</button>
8850
+ <mat-menu #addMenu="matMenu">
8851
+ <button mat-menu-item (click)="startEdit(sel)"><mat-icon [praxisIcon]="'add'"></mat-icon><span>Em branco</span></button>
8852
+ <button mat-menu-item (click)="startTemplate(sel, 'rota-destino')" [disabled]="!hasField('rotaId') || !hasField('destinoReqId')"><mat-icon [praxisIcon]="'bolt'"></mat-icon><span>Rota → Destino (id)</span></button>
8853
+ <button mat-menu-item (click)="startTemplate(sel, 'rota-destino-propriedade')" [disabled]="!hasField('rotaId') || !hasField('destinoReqId')"><mat-icon [praxisIcon]="'bolt'"></mat-icon><span>Rota + Destino → Propriedade (ids)</span></button>
8854
+ </mat-menu>
7239
8855
  </div>
7240
8856
  </div>
7241
- </div>
7242
-
7243
- <!-- Editor com seções (sem acordeão duplo) -->
7244
- <div class="panel-surface">
7245
- <div class="panel-header" style="padding: 12px 16px; border-bottom: 1px solid var(--md-sys-color-outline-variant);">
7246
- <mat-icon [praxisIcon]="'rule'"></mat-icon>
8857
+ <!-- Conflict banner (connections) -->
8858
+ @if (hasConflict(sel)) {
8859
+ <div class="banner">
8860
+ <mat-icon [praxisIcon]="'link_off'"></mat-icon>
8861
+ <div>
8862
+ Connections atualizam este campo (filterCriteria). Considere desativar a cascata nativa ou usar ‘manual’.
8863
+ </div>
8864
+ <span class="spacer"></span>
8865
+ <button mat-stroked-button color="primary" (click)="quickSet(sel,'disable')">Desativar</button>
8866
+ <button mat-stroked-button color="accent" (click)="quickSet(sel,'manual')">Definir manual</button>
8867
+ </div>
8868
+ }
8869
+ @if (importMode) {
8870
+ <div class="import-panel">
8871
+ <div style="display:flex; gap:8px; align-items:center; margin-bottom:8px"><mat-icon [praxisIcon]="'upload'"></mat-icon><strong>Importar regras (JSON)</strong></div>
8872
+ <textarea matInput rows="6" class="full" [(ngModel)]="importJson" placeholder='{ "campo": { "dependencyFields": ["dep"], ... } }'></textarea>
8873
+ <div style="display:flex; gap:8px; justify-content:flex-end; padding-top:8px">
8874
+ <button mat-stroked-button (click)="toggleImport()">Cancelar</button>
8875
+ <button mat-flat-button color="primary" (click)="applyImport()">Aplicar</button>
8876
+ </div>
8877
+ </div>
8878
+ }
8879
+ @if (!ruleFor(sel)) {
7247
8880
  <div>
7248
- <div>Cascata do campo</div>
7249
- <small style="color: var(--md-sys-color-on-surface-variant);">Defina dependentes, quando recarregar e como mapear filtros</small>
8881
+ <div class="rules-empty">Nenhuma cascata configurada. Clique em “Adicionar” para montar a dependência.</div>
7250
8882
  </div>
7251
- </div>
7252
-
7253
- <div class="panel-stack">
7254
- <!-- Básico -->
7255
- <mat-expansion-panel [expanded]="true" class="stack-panel">
7256
- <mat-expansion-panel-header>
7257
- <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'tune'"></mat-icon> Básico</mat-panel-title>
7258
- </mat-expansion-panel-header>
7259
- <div class="form-grid">
8883
+ }
8884
+ @if (ruleFor(sel); as rule) {
8885
+ <div>
8886
+ <div class="rule-row">
8887
+ <div>
8888
+ <div><strong>Dependentes:</strong> {{ rule.dependencyFields.join(', ') || '—' }}</div>
8889
+ <div style="opacity:.7">Load on change: {{ rule.dependencyLoadOnChange || 'respectLoadOn' }} | Merge: {{ rule.dependencyMergeStrategy || 'merge' }}</div>
8890
+ </div>
8891
+ <div class="rule-actions">
8892
+ <button mat-icon-button matTooltip="Editar" (click)="startEdit(sel)"><mat-icon [praxisIcon]="'edit'"></mat-icon></button>
8893
+ <button mat-icon-button matTooltip="Duplicar" disabled><mat-icon [praxisIcon]="'content_copy'"></mat-icon></button>
8894
+ <button mat-icon-button matTooltip="Remover" (click)="remove(sel)"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
8895
+ </div>
8896
+ </div>
8897
+ </div>
8898
+ }
8899
+ <!-- Editor com seções (sem acordeão duplo) -->
8900
+ <div class="panel-surface">
8901
+ <div class="panel-header" style="padding: 12px 16px; border-bottom: 1px solid var(--md-sys-color-outline-variant);">
8902
+ <mat-icon [praxisIcon]="'rule'"></mat-icon>
8903
+ <div>
8904
+ <div>Cascata do campo</div>
8905
+ <small style="color: var(--md-sys-color-on-surface-variant);">Defina dependentes, quando recarregar e como mapear filtros</small>
8906
+ </div>
8907
+ </div>
8908
+ <div class="panel-stack">
8909
+ <!-- Básico -->
8910
+ <mat-expansion-panel [expanded]="true" class="stack-panel">
8911
+ <mat-expansion-panel-header>
8912
+ <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'tune'"></mat-icon> Básico</mat-panel-title>
8913
+ </mat-expansion-panel-header>
8914
+ <div class="form-grid">
8915
+ <div class="form-row">
8916
+ <mat-form-field appearance="outline" class="full">
8917
+ <mat-label>Campos dependentes</mat-label>
8918
+ <mat-select multiple [(ngModel)]="editDepList" (ngModelChange)="onDepListChange()">
8919
+ @for (f of availableDependencyFields(); track f) {
8920
+ <mat-option [value]="f.name">
8921
+ {{ labelFor(f.name) }} ({{ f.name }})
8922
+ </mat-option>
8923
+ }
8924
+ </mat-select>
8925
+ <button
8926
+ mat-icon-button
8927
+ matSuffix
8928
+ class="help-icon-button"
8929
+ type="button"
8930
+ [matTooltip]="'Selecione os campos que controlam este valor'"
8931
+ matTooltipPosition="above"
8932
+ >
8933
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
8934
+ </button>
8935
+ </mat-form-field>
8936
+ </div>
8937
+ <div class="form-row">
8938
+ <mat-checkbox [(ngModel)]="editEnable">Ativar cascata nativa</mat-checkbox>
8939
+ <mat-checkbox [(ngModel)]="editReset">Resetar ao mudar</mat-checkbox>
8940
+ </div>
8941
+ </div>
8942
+ </mat-expansion-panel>
8943
+ <!-- Execução -->
8944
+ <mat-expansion-panel class="stack-panel">
8945
+ <mat-expansion-panel-header>
8946
+ <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'schedule'"></mat-icon> Execução</mat-panel-title>
8947
+ </mat-expansion-panel-header>
8948
+ <div class="form-row">
8949
+ <mat-form-field appearance="outline">
8950
+ <mat-label>Carregar ao mudar</mat-label>
8951
+ <mat-select [(ngModel)]="editLoadOn">
8952
+ <mat-option value="respectLoadOn">Respeitar loadOn</mat-option>
8953
+ <mat-option value="immediate">Imediato</mat-option>
8954
+ <mat-option value="manual">Manual</mat-option>
8955
+ </mat-select>
8956
+ </mat-form-field>
8957
+ <mat-form-field appearance="outline">
8958
+ <mat-label>Merge de filtros</mat-label>
8959
+ <mat-select [(ngModel)]="editMerge">
8960
+ <mat-option value="merge">merge (padrão)</mat-option>
8961
+ <mat-option value="replace">replace</mat-option>
8962
+ </mat-select>
8963
+ </mat-form-field>
8964
+ <mat-form-field appearance="outline">
8965
+ <mat-label>Atraso (ms)</mat-label>
8966
+ <input matInput type="number" [(ngModel)]="editDebounce">
8967
+ </mat-form-field>
8968
+ </div>
8969
+ </mat-expansion-panel>
8970
+ <!-- Extração de valor -->
8971
+ <mat-expansion-panel class="stack-panel">
8972
+ <mat-expansion-panel-header>
8973
+ <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'call_split'"></mat-icon> Extração de valor</mat-panel-title>
8974
+ </mat-expansion-panel-header>
7260
8975
  <div class="form-row">
7261
- <mat-form-field appearance="outline" class="full">
7262
- <mat-label>Campos dependentes</mat-label>
7263
- <mat-select multiple [(ngModel)]="editDepList" (ngModelChange)="onDepListChange()">
7264
- <mat-option *ngFor="let f of availableDependencyFields()" [value]="f.name">
7265
- {{ labelFor(f.name) }} ({{ f.name }})
7266
- </mat-option>
7267
- </mat-select>
8976
+ <mat-form-field appearance="outline" class="full">
8977
+ <mat-label>ValuePath padrão</mat-label>
8978
+ <input matInput [(ngModel)]="editValuePath" placeholder="id">
7268
8979
  <button
7269
8980
  mat-icon-button
7270
8981
  matSuffix
7271
8982
  class="help-icon-button"
7272
8983
  type="button"
7273
- [matTooltip]="'Selecione os campos que controlam este valor'"
8984
+ [matTooltip]="'Preenchido automaticamente a partir de optionValueKey() ou \\'id\\''"
7274
8985
  matTooltipPosition="above"
7275
- >
8986
+ >
7276
8987
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
7277
8988
  </button>
7278
- </mat-form-field>
7279
- </div>
7280
- <div class="form-row">
7281
- <mat-checkbox [(ngModel)]="editEnable">Ativar cascata nativa</mat-checkbox>
7282
- <mat-checkbox [(ngModel)]="editReset">Resetar ao mudar</mat-checkbox>
8989
+ </mat-form-field>
7283
8990
  </div>
7284
- </div>
7285
- </mat-expansion-panel>
7286
-
7287
- <!-- Execução -->
7288
- <mat-expansion-panel class="stack-panel">
7289
- <mat-expansion-panel-header>
7290
- <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'schedule'"></mat-icon> Execução</mat-panel-title>
7291
- </mat-expansion-panel-header>
7292
- <div class="form-row">
7293
- <mat-form-field appearance="outline">
7294
- <mat-label>Carregar ao mudar</mat-label>
7295
- <mat-select [(ngModel)]="editLoadOn">
7296
- <mat-option value="respectLoadOn">Respeitar loadOn</mat-option>
7297
- <mat-option value="immediate">Imediato</mat-option>
7298
- <mat-option value="manual">Manual</mat-option>
7299
- </mat-select>
7300
- </mat-form-field>
7301
- <mat-form-field appearance="outline">
7302
- <mat-label>Merge de filtros</mat-label>
7303
- <mat-select [(ngModel)]="editMerge">
7304
- <mat-option value="merge">merge (padrão)</mat-option>
7305
- <mat-option value="replace">replace</mat-option>
7306
- </mat-select>
7307
- </mat-form-field>
7308
- <mat-form-field appearance="outline">
7309
- <mat-label>Atraso (ms)</mat-label>
7310
- <input matInput type="number" [(ngModel)]="editDebounce">
7311
- </mat-form-field>
7312
- </div>
7313
- </mat-expansion-panel>
7314
-
7315
- <!-- Extração de valor -->
7316
- <mat-expansion-panel class="stack-panel">
7317
- <mat-expansion-panel-header>
7318
- <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'call_split'"></mat-icon> Extração de valor</mat-panel-title>
7319
- </mat-expansion-panel-header>
7320
- <div class="form-row">
7321
- <mat-form-field appearance="outline" class="full">
7322
- <mat-label>ValuePath padrão</mat-label>
7323
- <input matInput [(ngModel)]="editValuePath" placeholder="id">
7324
- <button
7325
- mat-icon-button
7326
- matSuffix
7327
- class="help-icon-button"
7328
- type="button"
7329
- [matTooltip]="'Preenchido automaticamente a partir de optionValueKey() ou \\'id\\''"
7330
- matTooltipPosition="above"
7331
- >
7332
- <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
7333
- </button>
7334
- </mat-form-field>
7335
- </div>
7336
- </mat-expansion-panel>
7337
-
7338
- <!-- Mapeamento de filtros (tabela) -->
7339
- <mat-expansion-panel class="stack-panel">
7340
- <mat-expansion-panel-header>
7341
- <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'filter_alt'"></mat-icon> Mapeamento de filtros</mat-panel-title>
7342
- </mat-expansion-panel-header>
7343
- <table class="map-table" *ngIf="editMapRows.length; else noMap">
7344
- <thead>
7345
- <tr>
7346
- <th>Dependente</th>
7347
- <th>Chave (dot‑path)</th>
7348
- <th>ValuePath (opcional)</th>
7349
- </tr>
7350
- </thead>
7351
- <tbody>
7352
- <tr *ngFor="let row of editMapRows; let i = index">
7353
- <td>{{ row.dep }}</td>
7354
- <td>
7355
- <mat-form-field appearance="outline" class="full">
7356
- <mat-label>Chave</mat-label>
7357
- <input matInput [(ngModel)]="row.key" [matAutocomplete]="autoKey" [placeholder]="row.dep">
7358
- <mat-autocomplete #autoKey="matAutocomplete">
7359
- <mat-option *ngFor="let opt of getKeySuggestions(row.dep)" [value]="opt">{{ opt }}</mat-option>
7360
- </mat-autocomplete>
7361
- <button
7362
- mat-icon-button
7363
- matSuffix
7364
- class="help-icon-button"
7365
- type="button"
7366
- [matTooltip]="'Use dot-notation (e.g. ' + row.dep + '.id) for nested objects'"
7367
- matTooltipPosition="above"
7368
- >
7369
- <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
7370
- </button>
7371
- </mat-form-field>
7372
- </td>
7373
- <td>
7374
- <mat-form-field appearance="outline" class="full">
7375
- <mat-label>ValuePath</mat-label>
7376
- <input matInput [(ngModel)]="row.valuePath" [matAutocomplete]="autoValue" [placeholder]="editValuePath||'id'">
7377
- <mat-autocomplete #autoValue="matAutocomplete">
7378
- <mat-option *ngFor="let opt of getValuePathSuggestions(row.dep)" [value]="opt">{{ opt }}</mat-option>
7379
- </mat-autocomplete>
7380
- <button
7381
- mat-icon-button
7382
- matSuffix
7383
- class="help-icon-button"
7384
- type="button"
7385
- [matTooltip]="'Opcional'"
7386
- matTooltipPosition="above"
7387
- >
7388
- <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
7389
- </button>
7390
- </mat-form-field>
7391
- </td>
7392
- </tr>
7393
- </tbody>
7394
- </table>
7395
- <ng-template #noMap>
7396
- <div class="rules-empty">Defina dependentes para sugerir o mapeamento.</div>
7397
- </ng-template>
7398
- </mat-expansion-panel>
7399
-
7400
- <!-- Preview -->
7401
- <mat-expansion-panel class="stack-panel">
7402
- <mat-expansion-panel-header>
7403
- <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'visibility'"></mat-icon> Preview</mat-panel-title>
7404
- </mat-expansion-panel-header>
7405
- <pre class="preview-box">{{ buildPreview() }}</pre>
7406
- <div style="display:flex; justify-content:flex-end; padding-top:6px">
7407
- <button mat-stroked-button (click)="copyPreview()">Copiar</button>
7408
- </div>
7409
- </mat-expansion-panel>
7410
- </div>
7411
-
7412
- <div style="display:flex; gap:8px; justify-content:flex-end; padding:8px 12px;">
7413
- <button mat-stroked-button (click)="cancelEdit()">Cancelar</button>
7414
- <button mat-flat-button color="primary" (click)="saveEdit(sel)">Salvar</button>
8991
+ </mat-expansion-panel>
8992
+ <!-- Mapeamento de filtros (tabela) -->
8993
+ <mat-expansion-panel class="stack-panel">
8994
+ <mat-expansion-panel-header>
8995
+ <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'filter_alt'"></mat-icon> Mapeamento de filtros</mat-panel-title>
8996
+ </mat-expansion-panel-header>
8997
+ @if (editMapRows.length) {
8998
+ <table class="map-table">
8999
+ <thead>
9000
+ <tr>
9001
+ <th>Dependente</th>
9002
+ <th>Chave (dot‑path)</th>
9003
+ <th>ValuePath (opcional)</th>
9004
+ </tr>
9005
+ </thead>
9006
+ <tbody>
9007
+ @for (row of editMapRows; track row; let i = $index) {
9008
+ <tr>
9009
+ <td>{{ row.dep }}</td>
9010
+ <td>
9011
+ <mat-form-field appearance="outline" class="full">
9012
+ <mat-label>Chave</mat-label>
9013
+ <input matInput [(ngModel)]="row.key" [matAutocomplete]="autoKey" [placeholder]="row.dep">
9014
+ <mat-autocomplete #autoKey="matAutocomplete">
9015
+ @for (opt of getKeySuggestions(row.dep); track opt) {
9016
+ <mat-option [value]="opt">{{ opt }}</mat-option>
9017
+ }
9018
+ </mat-autocomplete>
9019
+ <button
9020
+ mat-icon-button
9021
+ matSuffix
9022
+ class="help-icon-button"
9023
+ type="button"
9024
+ [matTooltip]="'Use dot-notation (e.g. ' + row.dep + '.id) for nested objects'"
9025
+ matTooltipPosition="above"
9026
+ >
9027
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
9028
+ </button>
9029
+ </mat-form-field>
9030
+ </td>
9031
+ <td>
9032
+ <mat-form-field appearance="outline" class="full">
9033
+ <mat-label>ValuePath</mat-label>
9034
+ <input matInput [(ngModel)]="row.valuePath" [matAutocomplete]="autoValue" [placeholder]="editValuePath||'id'">
9035
+ <mat-autocomplete #autoValue="matAutocomplete">
9036
+ @for (opt of getValuePathSuggestions(row.dep); track opt) {
9037
+ <mat-option [value]="opt">{{ opt }}</mat-option>
9038
+ }
9039
+ </mat-autocomplete>
9040
+ <button
9041
+ mat-icon-button
9042
+ matSuffix
9043
+ class="help-icon-button"
9044
+ type="button"
9045
+ [matTooltip]="'Opcional'"
9046
+ matTooltipPosition="above"
9047
+ >
9048
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
9049
+ </button>
9050
+ </mat-form-field>
9051
+ </td>
9052
+ </tr>
9053
+ }
9054
+ </tbody>
9055
+ </table>
9056
+ } @else {
9057
+ <div class="rules-empty">Defina dependentes para sugerir o mapeamento.</div>
9058
+ }
9059
+ </mat-expansion-panel>
9060
+ <!-- Preview -->
9061
+ <mat-expansion-panel class="stack-panel">
9062
+ <mat-expansion-panel-header>
9063
+ <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'visibility'"></mat-icon> Preview</mat-panel-title>
9064
+ </mat-expansion-panel-header>
9065
+ <pre class="preview-box">{{ buildPreview() }}</pre>
9066
+ <div style="display:flex; justify-content:flex-end; padding-top:6px">
9067
+ <button mat-stroked-button (click)="copyPreview()">Copiar</button>
9068
+ </div>
9069
+ </mat-expansion-panel>
9070
+ </div>
9071
+ <div style="display:flex; gap:8px; justify-content:flex-end; padding:8px 12px;">
9072
+ <button mat-stroked-button (click)="cancelEdit()">Cancelar</button>
9073
+ <button mat-flat-button color="primary" (click)="saveEdit(sel)">Salvar</button>
9074
+ </div>
7415
9075
  </div>
7416
9076
  </div>
7417
- </div>
9077
+ }
7418
9078
  </div>
7419
- `, 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: CommonModule }, { kind: "directive", type: i3$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i5$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i5$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i7.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i7.MatLabel, selector: "mat-label" }, { kind: "directive", type: i7.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: i8$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i8$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i9.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i5.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i5.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i5.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "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"] }] });
9079
+ `, 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"] }] });
7420
9080
  }
7421
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CascadeManagerTabComponent, decorators: [{
9081
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CascadeManagerTabComponent, decorators: [{
7422
9082
  type: Component,
7423
9083
  args: [{ selector: 'praxis-cascade-manager-tab', standalone: true, imports: [
7424
- CommonModule,
7425
9084
  ReactiveFormsModule,
7426
9085
  FormsModule,
7427
9086
  MatIconModule,
@@ -7437,7 +9096,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
7437
9096
  MatMenuModule,
7438
9097
  MatAutocompleteModule,
7439
9098
  ScrollingModule,
7440
- PraxisIconDirective,
9099
+ PraxisIconDirective
7441
9100
  ], template: `
7442
9101
  <div class="cm-root">
7443
9102
  <!-- Left: Fields list -->
@@ -7446,271 +9105,297 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
7446
9105
  <mat-label>Buscar campos</mat-label>
7447
9106
  <input matInput [ngModel]="searchTerm" (ngModelChange)="onSearchChange($event)">
7448
9107
  </mat-form-field>
7449
- <ng-container *ngIf="fields.length <= 50; else vscroll">
9108
+ @if (fields.length <= 50) {
7450
9109
  <div>
7451
- <div *ngFor="let f of filteredFields()" (click)="selectField(f.name)" class="field-item" [class.selected]="f.name===selectedField()" tabindex="0" (keydown)="onFieldKeydown($event, f.name)">
7452
- <div>
7453
- <div>{{ labelFor(f.name) }}</div>
7454
- <small style="opacity:.7">{{ f.name }}</small>
7455
- </div>
7456
- <div class="rule-badges" *ngIf="hasRule(f.name)">
7457
- <span class="rule-badge b-active" *ngIf="isCascadeEnabled(f.name)">Ativo</span>
7458
- <span class="rule-badge b-manual" *ngIf="loadOnChange(f.name)==='manual'">Manual</span>
7459
- <span class="rule-badge b-immediate" *ngIf="loadOnChange(f.name)==='immediate'">Immediate</span>
9110
+ @for (f of filteredFields(); track f) {
9111
+ <div (click)="selectField(f.name)" class="field-item" [class.selected]="f.name===selectedField()" tabindex="0" (keydown)="onFieldKeydown($event, f.name)">
9112
+ <div>
9113
+ <div>{{ labelFor(f.name) }}</div>
9114
+ <small style="opacity:.7">{{ f.name }}</small>
9115
+ </div>
9116
+ @if (hasRule(f.name)) {
9117
+ <div class="rule-badges">
9118
+ @if (isCascadeEnabled(f.name)) {
9119
+ <span class="rule-badge b-active">Ativo</span>
9120
+ }
9121
+ @if (loadOnChange(f.name)==='manual') {
9122
+ <span class="rule-badge b-manual">Manual</span>
9123
+ }
9124
+ @if (loadOnChange(f.name)==='immediate') {
9125
+ <span class="rule-badge b-immediate">Immediate</span>
9126
+ }
9127
+ </div>
9128
+ }
7460
9129
  </div>
7461
- </div>
9130
+ }
7462
9131
  </div>
7463
- </ng-container>
7464
- <ng-template #vscroll>
9132
+ } @else {
7465
9133
  <cdk-virtual-scroll-viewport itemSize="44" style="height: calc(100vh - 240px)">
7466
9134
  <div *cdkVirtualFor="let f of filteredFields()" (click)="selectField(f.name)" class="field-item" [class.selected]="f.name===selectedField()" tabindex="0" (keydown)="onFieldKeydown($event, f.name)">
7467
9135
  <div>
7468
9136
  <div>{{ labelFor(f.name) }}</div>
7469
9137
  <small style="opacity:.7">{{ f.name }}</small>
7470
9138
  </div>
7471
- <div class="rule-badges" *ngIf="hasRule(f.name)">
7472
- <span class="rule-badge b-active" *ngIf="isCascadeEnabled(f.name)">Ativo</span>
7473
- <span class="rule-badge b-manual" *ngIf="loadOnChange(f.name)==='manual'">Manual</span>
7474
- <span class="rule-badge b-immediate" *ngIf="loadOnChange(f.name)==='immediate'">Immediate</span>
7475
- </div>
9139
+ @if (hasRule(f.name)) {
9140
+ <div class="rule-badges">
9141
+ @if (isCascadeEnabled(f.name)) {
9142
+ <span class="rule-badge b-active">Ativo</span>
9143
+ }
9144
+ @if (loadOnChange(f.name)==='manual') {
9145
+ <span class="rule-badge b-manual">Manual</span>
9146
+ }
9147
+ @if (loadOnChange(f.name)==='immediate') {
9148
+ <span class="rule-badge b-immediate">Immediate</span>
9149
+ }
9150
+ </div>
9151
+ }
7476
9152
  </div>
7477
9153
  </cdk-virtual-scroll-viewport>
7478
- </ng-template>
9154
+ }
7479
9155
  </div>
7480
-
9156
+
7481
9157
  <!-- Right: Rules for selected field -->
7482
- <div class="cm-right" *ngIf="selectedField() as sel">
7483
- <div class="cm-header">
7484
- <div>
7485
- <h3 style="margin:0">{{ labelFor(sel) }}</h3>
7486
- <div style="opacity:.7">{{ sel }}</div>
7487
- </div>
7488
- <div>
7489
- <button mat-stroked-button color="primary" (click)="exportRules()" style="margin-right:8px">Exportar</button>
7490
- <button mat-stroked-button (click)="toggleImport()" style="margin-right:8px">Importar</button>
7491
- <button mat-flat-button color="primary" [matMenuTriggerFor]="addMenu" [disabled]="editing()">Adicionar</button>
7492
- <mat-menu #addMenu="matMenu">
7493
- <button mat-menu-item (click)="startEdit(sel)"><mat-icon [praxisIcon]="'add'"></mat-icon><span>Em branco</span></button>
7494
- <button mat-menu-item (click)="startTemplate(sel, 'rota-destino')" [disabled]="!hasField('rotaId') || !hasField('destinoReqId')"><mat-icon [praxisIcon]="'bolt'"></mat-icon><span>Rota → Destino (id)</span></button>
7495
- <button mat-menu-item (click)="startTemplate(sel, 'rota-destino-propriedade')" [disabled]="!hasField('rotaId') || !hasField('destinoReqId')"><mat-icon [praxisIcon]="'bolt'"></mat-icon><span>Rota + Destino → Propriedade (ids)</span></button>
7496
- </mat-menu>
7497
- </div>
7498
- </div>
7499
-
7500
- <!-- Conflict banner (connections) -->
7501
- <div class="banner" *ngIf="hasConflict(sel)">
7502
- <mat-icon [praxisIcon]="'link_off'"></mat-icon>
7503
- <div>
7504
- Connections atualizam este campo (filterCriteria). Considere desativar a cascata nativa ou usar ‘manual’.
7505
- </div>
7506
- <span class="spacer"></span>
7507
- <button mat-stroked-button color="primary" (click)="quickSet(sel,'disable')">Desativar</button>
7508
- <button mat-stroked-button color="accent" (click)="quickSet(sel,'manual')">Definir manual</button>
7509
- </div>
7510
-
7511
- <div *ngIf="importMode" class="import-panel">
7512
- <div style="display:flex; gap:8px; align-items:center; margin-bottom:8px"><mat-icon [praxisIcon]="'upload'"></mat-icon><strong>Importar regras (JSON)</strong></div>
7513
- <textarea matInput rows="6" class="full" [(ngModel)]="importJson" placeholder='{ "campo": { "dependencyFields": ["dep"], ... } }'></textarea>
7514
- <div style="display:flex; gap:8px; justify-content:flex-end; padding-top:8px">
7515
- <button mat-stroked-button (click)="toggleImport()">Cancelar</button>
7516
- <button mat-flat-button color="primary" (click)="applyImport()">Aplicar</button>
7517
- </div>
7518
- </div>
7519
-
7520
- <div *ngIf="!ruleFor(sel)">
7521
- <div class="rules-empty">Nenhuma cascata configurada. Clique em “Adicionar” para montar a dependência.</div>
7522
- </div>
7523
- <div *ngIf="ruleFor(sel) as rule">
7524
- <div class="rule-row">
9158
+ @if (selectedField(); as sel) {
9159
+ <div class="cm-right">
9160
+ <div class="cm-header">
7525
9161
  <div>
7526
- <div><strong>Dependentes:</strong> {{ rule.dependencyFields.join(', ') || '—' }}</div>
7527
- <div style="opacity:.7">Load on change: {{ rule.dependencyLoadOnChange || 'respectLoadOn' }} | Merge: {{ rule.dependencyMergeStrategy || 'merge' }}</div>
9162
+ <h3 style="margin:0">{{ labelFor(sel) }}</h3>
9163
+ <div style="opacity:.7">{{ sel }}</div>
7528
9164
  </div>
7529
- <div class="rule-actions">
7530
- <button mat-icon-button matTooltip="Editar" (click)="startEdit(sel)"><mat-icon [praxisIcon]="'edit'"></mat-icon></button>
7531
- <button mat-icon-button matTooltip="Duplicar" disabled><mat-icon [praxisIcon]="'content_copy'"></mat-icon></button>
7532
- <button mat-icon-button matTooltip="Remover" (click)="remove(sel)"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
9165
+ <div>
9166
+ <button mat-stroked-button color="primary" (click)="exportRules()" style="margin-right:8px">Exportar</button>
9167
+ <button mat-stroked-button (click)="toggleImport()" style="margin-right:8px">Importar</button>
9168
+ <button mat-flat-button color="primary" [matMenuTriggerFor]="addMenu" [disabled]="editing()">Adicionar</button>
9169
+ <mat-menu #addMenu="matMenu">
9170
+ <button mat-menu-item (click)="startEdit(sel)"><mat-icon [praxisIcon]="'add'"></mat-icon><span>Em branco</span></button>
9171
+ <button mat-menu-item (click)="startTemplate(sel, 'rota-destino')" [disabled]="!hasField('rotaId') || !hasField('destinoReqId')"><mat-icon [praxisIcon]="'bolt'"></mat-icon><span>Rota → Destino (id)</span></button>
9172
+ <button mat-menu-item (click)="startTemplate(sel, 'rota-destino-propriedade')" [disabled]="!hasField('rotaId') || !hasField('destinoReqId')"><mat-icon [praxisIcon]="'bolt'"></mat-icon><span>Rota + Destino → Propriedade (ids)</span></button>
9173
+ </mat-menu>
7533
9174
  </div>
7534
9175
  </div>
7535
- </div>
7536
-
7537
- <!-- Editor com seções (sem acordeão duplo) -->
7538
- <div class="panel-surface">
7539
- <div class="panel-header" style="padding: 12px 16px; border-bottom: 1px solid var(--md-sys-color-outline-variant);">
7540
- <mat-icon [praxisIcon]="'rule'"></mat-icon>
9176
+ <!-- Conflict banner (connections) -->
9177
+ @if (hasConflict(sel)) {
9178
+ <div class="banner">
9179
+ <mat-icon [praxisIcon]="'link_off'"></mat-icon>
9180
+ <div>
9181
+ Connections atualizam este campo (filterCriteria). Considere desativar a cascata nativa ou usar ‘manual’.
9182
+ </div>
9183
+ <span class="spacer"></span>
9184
+ <button mat-stroked-button color="primary" (click)="quickSet(sel,'disable')">Desativar</button>
9185
+ <button mat-stroked-button color="accent" (click)="quickSet(sel,'manual')">Definir manual</button>
9186
+ </div>
9187
+ }
9188
+ @if (importMode) {
9189
+ <div class="import-panel">
9190
+ <div style="display:flex; gap:8px; align-items:center; margin-bottom:8px"><mat-icon [praxisIcon]="'upload'"></mat-icon><strong>Importar regras (JSON)</strong></div>
9191
+ <textarea matInput rows="6" class="full" [(ngModel)]="importJson" placeholder='{ "campo": { "dependencyFields": ["dep"], ... } }'></textarea>
9192
+ <div style="display:flex; gap:8px; justify-content:flex-end; padding-top:8px">
9193
+ <button mat-stroked-button (click)="toggleImport()">Cancelar</button>
9194
+ <button mat-flat-button color="primary" (click)="applyImport()">Aplicar</button>
9195
+ </div>
9196
+ </div>
9197
+ }
9198
+ @if (!ruleFor(sel)) {
7541
9199
  <div>
7542
- <div>Cascata do campo</div>
7543
- <small style="color: var(--md-sys-color-on-surface-variant);">Defina dependentes, quando recarregar e como mapear filtros</small>
9200
+ <div class="rules-empty">Nenhuma cascata configurada. Clique em “Adicionar” para montar a dependência.</div>
7544
9201
  </div>
7545
- </div>
7546
-
7547
- <div class="panel-stack">
7548
- <!-- Básico -->
7549
- <mat-expansion-panel [expanded]="true" class="stack-panel">
7550
- <mat-expansion-panel-header>
7551
- <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'tune'"></mat-icon> Básico</mat-panel-title>
7552
- </mat-expansion-panel-header>
7553
- <div class="form-grid">
9202
+ }
9203
+ @if (ruleFor(sel); as rule) {
9204
+ <div>
9205
+ <div class="rule-row">
9206
+ <div>
9207
+ <div><strong>Dependentes:</strong> {{ rule.dependencyFields.join(', ') || '—' }}</div>
9208
+ <div style="opacity:.7">Load on change: {{ rule.dependencyLoadOnChange || 'respectLoadOn' }} | Merge: {{ rule.dependencyMergeStrategy || 'merge' }}</div>
9209
+ </div>
9210
+ <div class="rule-actions">
9211
+ <button mat-icon-button matTooltip="Editar" (click)="startEdit(sel)"><mat-icon [praxisIcon]="'edit'"></mat-icon></button>
9212
+ <button mat-icon-button matTooltip="Duplicar" disabled><mat-icon [praxisIcon]="'content_copy'"></mat-icon></button>
9213
+ <button mat-icon-button matTooltip="Remover" (click)="remove(sel)"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
9214
+ </div>
9215
+ </div>
9216
+ </div>
9217
+ }
9218
+ <!-- Editor com seções (sem acordeão duplo) -->
9219
+ <div class="panel-surface">
9220
+ <div class="panel-header" style="padding: 12px 16px; border-bottom: 1px solid var(--md-sys-color-outline-variant);">
9221
+ <mat-icon [praxisIcon]="'rule'"></mat-icon>
9222
+ <div>
9223
+ <div>Cascata do campo</div>
9224
+ <small style="color: var(--md-sys-color-on-surface-variant);">Defina dependentes, quando recarregar e como mapear filtros</small>
9225
+ </div>
9226
+ </div>
9227
+ <div class="panel-stack">
9228
+ <!-- Básico -->
9229
+ <mat-expansion-panel [expanded]="true" class="stack-panel">
9230
+ <mat-expansion-panel-header>
9231
+ <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'tune'"></mat-icon> Básico</mat-panel-title>
9232
+ </mat-expansion-panel-header>
9233
+ <div class="form-grid">
9234
+ <div class="form-row">
9235
+ <mat-form-field appearance="outline" class="full">
9236
+ <mat-label>Campos dependentes</mat-label>
9237
+ <mat-select multiple [(ngModel)]="editDepList" (ngModelChange)="onDepListChange()">
9238
+ @for (f of availableDependencyFields(); track f) {
9239
+ <mat-option [value]="f.name">
9240
+ {{ labelFor(f.name) }} ({{ f.name }})
9241
+ </mat-option>
9242
+ }
9243
+ </mat-select>
9244
+ <button
9245
+ mat-icon-button
9246
+ matSuffix
9247
+ class="help-icon-button"
9248
+ type="button"
9249
+ [matTooltip]="'Selecione os campos que controlam este valor'"
9250
+ matTooltipPosition="above"
9251
+ >
9252
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
9253
+ </button>
9254
+ </mat-form-field>
9255
+ </div>
9256
+ <div class="form-row">
9257
+ <mat-checkbox [(ngModel)]="editEnable">Ativar cascata nativa</mat-checkbox>
9258
+ <mat-checkbox [(ngModel)]="editReset">Resetar ao mudar</mat-checkbox>
9259
+ </div>
9260
+ </div>
9261
+ </mat-expansion-panel>
9262
+ <!-- Execução -->
9263
+ <mat-expansion-panel class="stack-panel">
9264
+ <mat-expansion-panel-header>
9265
+ <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'schedule'"></mat-icon> Execução</mat-panel-title>
9266
+ </mat-expansion-panel-header>
9267
+ <div class="form-row">
9268
+ <mat-form-field appearance="outline">
9269
+ <mat-label>Carregar ao mudar</mat-label>
9270
+ <mat-select [(ngModel)]="editLoadOn">
9271
+ <mat-option value="respectLoadOn">Respeitar loadOn</mat-option>
9272
+ <mat-option value="immediate">Imediato</mat-option>
9273
+ <mat-option value="manual">Manual</mat-option>
9274
+ </mat-select>
9275
+ </mat-form-field>
9276
+ <mat-form-field appearance="outline">
9277
+ <mat-label>Merge de filtros</mat-label>
9278
+ <mat-select [(ngModel)]="editMerge">
9279
+ <mat-option value="merge">merge (padrão)</mat-option>
9280
+ <mat-option value="replace">replace</mat-option>
9281
+ </mat-select>
9282
+ </mat-form-field>
9283
+ <mat-form-field appearance="outline">
9284
+ <mat-label>Atraso (ms)</mat-label>
9285
+ <input matInput type="number" [(ngModel)]="editDebounce">
9286
+ </mat-form-field>
9287
+ </div>
9288
+ </mat-expansion-panel>
9289
+ <!-- Extração de valor -->
9290
+ <mat-expansion-panel class="stack-panel">
9291
+ <mat-expansion-panel-header>
9292
+ <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'call_split'"></mat-icon> Extração de valor</mat-panel-title>
9293
+ </mat-expansion-panel-header>
7554
9294
  <div class="form-row">
7555
- <mat-form-field appearance="outline" class="full">
7556
- <mat-label>Campos dependentes</mat-label>
7557
- <mat-select multiple [(ngModel)]="editDepList" (ngModelChange)="onDepListChange()">
7558
- <mat-option *ngFor="let f of availableDependencyFields()" [value]="f.name">
7559
- {{ labelFor(f.name) }} ({{ f.name }})
7560
- </mat-option>
7561
- </mat-select>
9295
+ <mat-form-field appearance="outline" class="full">
9296
+ <mat-label>ValuePath padrão</mat-label>
9297
+ <input matInput [(ngModel)]="editValuePath" placeholder="id">
7562
9298
  <button
7563
9299
  mat-icon-button
7564
9300
  matSuffix
7565
9301
  class="help-icon-button"
7566
9302
  type="button"
7567
- [matTooltip]="'Selecione os campos que controlam este valor'"
9303
+ [matTooltip]="'Preenchido automaticamente a partir de optionValueKey() ou \\'id\\''"
7568
9304
  matTooltipPosition="above"
7569
- >
9305
+ >
7570
9306
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
7571
9307
  </button>
7572
- </mat-form-field>
7573
- </div>
7574
- <div class="form-row">
7575
- <mat-checkbox [(ngModel)]="editEnable">Ativar cascata nativa</mat-checkbox>
7576
- <mat-checkbox [(ngModel)]="editReset">Resetar ao mudar</mat-checkbox>
9308
+ </mat-form-field>
7577
9309
  </div>
7578
- </div>
7579
- </mat-expansion-panel>
7580
-
7581
- <!-- Execução -->
7582
- <mat-expansion-panel class="stack-panel">
7583
- <mat-expansion-panel-header>
7584
- <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'schedule'"></mat-icon> Execução</mat-panel-title>
7585
- </mat-expansion-panel-header>
7586
- <div class="form-row">
7587
- <mat-form-field appearance="outline">
7588
- <mat-label>Carregar ao mudar</mat-label>
7589
- <mat-select [(ngModel)]="editLoadOn">
7590
- <mat-option value="respectLoadOn">Respeitar loadOn</mat-option>
7591
- <mat-option value="immediate">Imediato</mat-option>
7592
- <mat-option value="manual">Manual</mat-option>
7593
- </mat-select>
7594
- </mat-form-field>
7595
- <mat-form-field appearance="outline">
7596
- <mat-label>Merge de filtros</mat-label>
7597
- <mat-select [(ngModel)]="editMerge">
7598
- <mat-option value="merge">merge (padrão)</mat-option>
7599
- <mat-option value="replace">replace</mat-option>
7600
- </mat-select>
7601
- </mat-form-field>
7602
- <mat-form-field appearance="outline">
7603
- <mat-label>Atraso (ms)</mat-label>
7604
- <input matInput type="number" [(ngModel)]="editDebounce">
7605
- </mat-form-field>
7606
- </div>
7607
- </mat-expansion-panel>
7608
-
7609
- <!-- Extração de valor -->
7610
- <mat-expansion-panel class="stack-panel">
7611
- <mat-expansion-panel-header>
7612
- <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'call_split'"></mat-icon> Extração de valor</mat-panel-title>
7613
- </mat-expansion-panel-header>
7614
- <div class="form-row">
7615
- <mat-form-field appearance="outline" class="full">
7616
- <mat-label>ValuePath padrão</mat-label>
7617
- <input matInput [(ngModel)]="editValuePath" placeholder="id">
7618
- <button
7619
- mat-icon-button
7620
- matSuffix
7621
- class="help-icon-button"
7622
- type="button"
7623
- [matTooltip]="'Preenchido automaticamente a partir de optionValueKey() ou \\'id\\''"
7624
- matTooltipPosition="above"
7625
- >
7626
- <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
7627
- </button>
7628
- </mat-form-field>
7629
- </div>
7630
- </mat-expansion-panel>
7631
-
7632
- <!-- Mapeamento de filtros (tabela) -->
7633
- <mat-expansion-panel class="stack-panel">
7634
- <mat-expansion-panel-header>
7635
- <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'filter_alt'"></mat-icon> Mapeamento de filtros</mat-panel-title>
7636
- </mat-expansion-panel-header>
7637
- <table class="map-table" *ngIf="editMapRows.length; else noMap">
7638
- <thead>
7639
- <tr>
7640
- <th>Dependente</th>
7641
- <th>Chave (dot‑path)</th>
7642
- <th>ValuePath (opcional)</th>
7643
- </tr>
7644
- </thead>
7645
- <tbody>
7646
- <tr *ngFor="let row of editMapRows; let i = index">
7647
- <td>{{ row.dep }}</td>
7648
- <td>
7649
- <mat-form-field appearance="outline" class="full">
7650
- <mat-label>Chave</mat-label>
7651
- <input matInput [(ngModel)]="row.key" [matAutocomplete]="autoKey" [placeholder]="row.dep">
7652
- <mat-autocomplete #autoKey="matAutocomplete">
7653
- <mat-option *ngFor="let opt of getKeySuggestions(row.dep)" [value]="opt">{{ opt }}</mat-option>
7654
- </mat-autocomplete>
7655
- <button
7656
- mat-icon-button
7657
- matSuffix
7658
- class="help-icon-button"
7659
- type="button"
7660
- [matTooltip]="'Use dot-notation (e.g. ' + row.dep + '.id) for nested objects'"
7661
- matTooltipPosition="above"
7662
- >
7663
- <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
7664
- </button>
7665
- </mat-form-field>
7666
- </td>
7667
- <td>
7668
- <mat-form-field appearance="outline" class="full">
7669
- <mat-label>ValuePath</mat-label>
7670
- <input matInput [(ngModel)]="row.valuePath" [matAutocomplete]="autoValue" [placeholder]="editValuePath||'id'">
7671
- <mat-autocomplete #autoValue="matAutocomplete">
7672
- <mat-option *ngFor="let opt of getValuePathSuggestions(row.dep)" [value]="opt">{{ opt }}</mat-option>
7673
- </mat-autocomplete>
7674
- <button
7675
- mat-icon-button
7676
- matSuffix
7677
- class="help-icon-button"
7678
- type="button"
7679
- [matTooltip]="'Opcional'"
7680
- matTooltipPosition="above"
7681
- >
7682
- <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
7683
- </button>
7684
- </mat-form-field>
7685
- </td>
7686
- </tr>
7687
- </tbody>
7688
- </table>
7689
- <ng-template #noMap>
7690
- <div class="rules-empty">Defina dependentes para sugerir o mapeamento.</div>
7691
- </ng-template>
7692
- </mat-expansion-panel>
7693
-
7694
- <!-- Preview -->
7695
- <mat-expansion-panel class="stack-panel">
7696
- <mat-expansion-panel-header>
7697
- <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'visibility'"></mat-icon> Preview</mat-panel-title>
7698
- </mat-expansion-panel-header>
7699
- <pre class="preview-box">{{ buildPreview() }}</pre>
7700
- <div style="display:flex; justify-content:flex-end; padding-top:6px">
7701
- <button mat-stroked-button (click)="copyPreview()">Copiar</button>
7702
- </div>
7703
- </mat-expansion-panel>
7704
- </div>
7705
-
7706
- <div style="display:flex; gap:8px; justify-content:flex-end; padding:8px 12px;">
7707
- <button mat-stroked-button (click)="cancelEdit()">Cancelar</button>
7708
- <button mat-flat-button color="primary" (click)="saveEdit(sel)">Salvar</button>
9310
+ </mat-expansion-panel>
9311
+ <!-- Mapeamento de filtros (tabela) -->
9312
+ <mat-expansion-panel class="stack-panel">
9313
+ <mat-expansion-panel-header>
9314
+ <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'filter_alt'"></mat-icon> Mapeamento de filtros</mat-panel-title>
9315
+ </mat-expansion-panel-header>
9316
+ @if (editMapRows.length) {
9317
+ <table class="map-table">
9318
+ <thead>
9319
+ <tr>
9320
+ <th>Dependente</th>
9321
+ <th>Chave (dot‑path)</th>
9322
+ <th>ValuePath (opcional)</th>
9323
+ </tr>
9324
+ </thead>
9325
+ <tbody>
9326
+ @for (row of editMapRows; track row; let i = $index) {
9327
+ <tr>
9328
+ <td>{{ row.dep }}</td>
9329
+ <td>
9330
+ <mat-form-field appearance="outline" class="full">
9331
+ <mat-label>Chave</mat-label>
9332
+ <input matInput [(ngModel)]="row.key" [matAutocomplete]="autoKey" [placeholder]="row.dep">
9333
+ <mat-autocomplete #autoKey="matAutocomplete">
9334
+ @for (opt of getKeySuggestions(row.dep); track opt) {
9335
+ <mat-option [value]="opt">{{ opt }}</mat-option>
9336
+ }
9337
+ </mat-autocomplete>
9338
+ <button
9339
+ mat-icon-button
9340
+ matSuffix
9341
+ class="help-icon-button"
9342
+ type="button"
9343
+ [matTooltip]="'Use dot-notation (e.g. ' + row.dep + '.id) for nested objects'"
9344
+ matTooltipPosition="above"
9345
+ >
9346
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
9347
+ </button>
9348
+ </mat-form-field>
9349
+ </td>
9350
+ <td>
9351
+ <mat-form-field appearance="outline" class="full">
9352
+ <mat-label>ValuePath</mat-label>
9353
+ <input matInput [(ngModel)]="row.valuePath" [matAutocomplete]="autoValue" [placeholder]="editValuePath||'id'">
9354
+ <mat-autocomplete #autoValue="matAutocomplete">
9355
+ @for (opt of getValuePathSuggestions(row.dep); track opt) {
9356
+ <mat-option [value]="opt">{{ opt }}</mat-option>
9357
+ }
9358
+ </mat-autocomplete>
9359
+ <button
9360
+ mat-icon-button
9361
+ matSuffix
9362
+ class="help-icon-button"
9363
+ type="button"
9364
+ [matTooltip]="'Opcional'"
9365
+ matTooltipPosition="above"
9366
+ >
9367
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
9368
+ </button>
9369
+ </mat-form-field>
9370
+ </td>
9371
+ </tr>
9372
+ }
9373
+ </tbody>
9374
+ </table>
9375
+ } @else {
9376
+ <div class="rules-empty">Defina dependentes para sugerir o mapeamento.</div>
9377
+ }
9378
+ </mat-expansion-panel>
9379
+ <!-- Preview -->
9380
+ <mat-expansion-panel class="stack-panel">
9381
+ <mat-expansion-panel-header>
9382
+ <mat-panel-title class="panel-header"><mat-icon [praxisIcon]="'visibility'"></mat-icon> Preview</mat-panel-title>
9383
+ </mat-expansion-panel-header>
9384
+ <pre class="preview-box">{{ buildPreview() }}</pre>
9385
+ <div style="display:flex; justify-content:flex-end; padding-top:6px">
9386
+ <button mat-stroked-button (click)="copyPreview()">Copiar</button>
9387
+ </div>
9388
+ </mat-expansion-panel>
9389
+ </div>
9390
+ <div style="display:flex; gap:8px; justify-content:flex-end; padding:8px 12px;">
9391
+ <button mat-stroked-button (click)="cancelEdit()">Cancelar</button>
9392
+ <button mat-flat-button color="primary" (click)="saveEdit(sel)">Salvar</button>
9393
+ </div>
7709
9394
  </div>
7710
9395
  </div>
7711
- </div>
9396
+ }
7712
9397
  </div>
7713
- `, 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"] }]
9398
+ `, 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"] }]
7714
9399
  }], ctorParameters: () => [{ type: CascadeRulesService }], propDecorators: { fields: [{
7715
9400
  type: Input
7716
9401
  }], connections: [{
@@ -7753,6 +9438,388 @@ const METADATA_EDITOR_AI_CAPABILITY_CATALOG = {
7753
9438
  enums: {},
7754
9439
  };
7755
9440
 
9441
+ const fieldMetadataPropertySchema = {
9442
+ type: 'object',
9443
+ required: ['path', 'value'],
9444
+ properties: {
9445
+ path: {
9446
+ enum: [
9447
+ 'name',
9448
+ 'label',
9449
+ 'description',
9450
+ 'controlType',
9451
+ 'placeholder',
9452
+ 'defaultValue',
9453
+ 'group',
9454
+ 'order',
9455
+ 'required',
9456
+ 'disabled',
9457
+ 'readOnly',
9458
+ 'hidden',
9459
+ 'source',
9460
+ 'transient',
9461
+ 'submitPolicy',
9462
+ 'hint',
9463
+ 'helpText',
9464
+ 'tooltip',
9465
+ 'options',
9466
+ 'optionSource',
9467
+ 'validators',
9468
+ 'conditionalRequired',
9469
+ 'conditionalDisplay',
9470
+ 'visibleIn',
9471
+ 'ariaLabel',
9472
+ 'ariaDescribedBy',
9473
+ ],
9474
+ },
9475
+ value: {},
9476
+ merge: { type: 'boolean' },
9477
+ },
9478
+ };
9479
+ const optionSourceSchema = {
9480
+ type: 'object',
9481
+ minProperties: 1,
9482
+ properties: {
9483
+ kind: { enum: ['static', 'remote', 'resource', 'lookup'] },
9484
+ resource: { type: 'string' },
9485
+ endpoint: { type: 'string' },
9486
+ valueField: { type: 'string' },
9487
+ labelField: { type: 'string' },
9488
+ dependsOn: { type: 'string' },
9489
+ dependencyFilterMap: { type: 'object' },
9490
+ display: {
9491
+ type: 'object',
9492
+ properties: {
9493
+ preset: { enum: ['compact', 'rich', 'directory', 'status', 'reference', 'hierarchical'] },
9494
+ usage: { enum: ['form', 'filter', 'table-cell', 'dashboard', 'wizard', 'review'] },
9495
+ density: { enum: ['compact', 'comfortable', 'rich'] },
9496
+ selectedLayout: { enum: ['card', 'inline', 'compact', 'token'] },
9497
+ resultLayout: { enum: ['list', 'denseList', 'table', 'card'] },
9498
+ primaryPropertyPath: { type: 'string' },
9499
+ fields: { type: 'array' },
9500
+ secondaryPropertyPaths: { type: 'array' },
9501
+ badgePropertyPaths: { type: 'array' },
9502
+ statusToneMap: { type: 'object' },
9503
+ },
9504
+ },
9505
+ detail: {
9506
+ type: 'object',
9507
+ properties: {
9508
+ kind: { enum: ['surface', 'route', 'href'] },
9509
+ surfaceId: { type: 'string' },
9510
+ presentation: { enum: ['drawer', 'modal'] },
9511
+ preferredWidget: { type: 'string' },
9512
+ mode: { enum: ['view', 'edit', 'create'] },
9513
+ },
9514
+ },
9515
+ options: { type: 'array' },
9516
+ },
9517
+ };
9518
+ const cascadeSchema = {
9519
+ type: 'object',
9520
+ required: ['dependentField'],
9521
+ properties: {
9522
+ dependentField: { type: 'string' },
9523
+ sourceField: { type: 'string' },
9524
+ strategy: { enum: ['replace', 'merge'] },
9525
+ debounceMs: { type: 'number' },
9526
+ loadMode: { enum: ['immediate', 'manual', 'respectLoadOn'] },
9527
+ dependencyFilterMap: { type: 'object' },
9528
+ optionSource: {
9529
+ type: 'object',
9530
+ properties: {
9531
+ kind: { enum: ['static', 'remote', 'resource', 'lookup'] },
9532
+ resource: { type: 'string' },
9533
+ endpoint: { type: 'string' },
9534
+ dependsOn: { type: 'string' },
9535
+ dependencyFilterMap: { type: 'object' },
9536
+ },
9537
+ },
9538
+ },
9539
+ };
9540
+ const PRAXIS_METADATA_EDITOR_AUTHORING_MANIFEST = {
9541
+ schemaVersion: '1.0.0',
9542
+ componentId: 'praxis-metadata-editor',
9543
+ ownerPackage: '@praxisui/metadata-editor',
9544
+ configSchemaId: 'FieldMetadata',
9545
+ manifestVersion: '1.0.0',
9546
+ runtimeInputs: [
9547
+ { name: 'controlType', type: 'FieldControlType', description: 'Canonical control type used to resolve editor property coverage.' },
9548
+ { name: 'seed', type: 'Partial<FieldMetadata | FieldDefinition>', description: 'Initial canonical field metadata used to hydrate the editor.' },
9549
+ { name: 'fields', type: 'Array<FieldMetadata | FieldDefinition>', description: 'Available fields used by cascade authoring and context validation.' },
9550
+ { name: 'properties', type: 'EditorProperty[]', description: 'Renderer property catalog resolved from ConfigRegistryService.' },
9551
+ { name: 'form', type: 'FormGroup', description: 'Dynamic form generated from canonical editor properties by DynamicFormFactoryService.' },
9552
+ ],
9553
+ editableTargets: [
9554
+ { kind: 'fieldMetadata', resolver: 'field-metadata-json-path', description: 'Canonical FieldMetadata root and property paths.' },
9555
+ { kind: 'controlType', resolver: 'dynamic-fields-control-type-discovery', description: 'Control type selected from dynamic-fields discovery and editor config registry.' },
9556
+ { kind: 'optionSource', resolver: 'field-metadata-option-source', description: 'Canonical option source including backend x-ui option source fields where applicable.' },
9557
+ { kind: 'cascade', resolver: 'metadata-editor-cascade-rules', description: 'Cascade dependencies and dependency filter mappings edited through CascadeManagerTab.' },
9558
+ { kind: 'renderer', resolver: 'metadata-editor-renderer-property', description: 'Dynamic editor renderer property coverage and editor component mapping.' },
9559
+ { kind: 'validation', resolver: 'field-metadata-validation-rules', description: 'Field validation rules and contextual validators.' },
9560
+ { kind: 'contextHint', resolver: 'metadata-editor-context-hints', description: 'Authoring hints, help text and contextual guidance for metadata editors.' },
9561
+ { kind: 'normalization', resolver: 'metadata-editor-schema-normalizer', description: 'Schema normalization and seed hydration rules.' },
9562
+ ],
9563
+ operations: [
9564
+ {
9565
+ operationId: 'fieldMetadata.property.set',
9566
+ title: 'Set FieldMetadata property',
9567
+ scope: 'fieldMetadataPath',
9568
+ targetKind: 'fieldMetadata',
9569
+ target: { kind: 'fieldMetadata', resolver: 'field-metadata-json-path', ambiguityPolicy: 'fail', required: true },
9570
+ inputSchema: fieldMetadataPropertySchema,
9571
+ effects: [{ kind: 'compile-domain-patch', handler: 'metadata-field-property-set', handlerContract: {
9572
+ reads: ['fieldMetadata', 'fieldMetadataPath', 'SchemaNormalizerService'],
9573
+ writes: ['fieldMetadata'],
9574
+ identityKeys: ['field.name', 'path'],
9575
+ inputSchema: fieldMetadataPropertySchema,
9576
+ failureModes: ['unknown-field-metadata-path', 'non-canonical-shape', 'readonly-field', 'normalization-failed'],
9577
+ description: 'Sets a canonical FieldMetadata property through a governed metadata path and re-runs schema normalization.',
9578
+ } }],
9579
+ destructive: false,
9580
+ requiresConfirmation: false,
9581
+ validators: ['field-metadata-shape-canonical', 'field-path-supported-by-editor', 'metadata-round-trip'],
9582
+ affectedPaths: ['fieldMetadata'],
9583
+ submissionImpact: 'affects-schema-backed-data',
9584
+ preconditions: ['field-metadata-loaded'],
9585
+ },
9586
+ {
9587
+ operationId: 'controlType.set',
9588
+ title: 'Set control type',
9589
+ scope: 'controlType',
9590
+ targetKind: 'controlType',
9591
+ target: { kind: 'controlType', resolver: 'dynamic-fields-control-type-discovery', ambiguityPolicy: 'fail', required: true },
9592
+ inputSchema: {
9593
+ type: 'object',
9594
+ required: ['controlType'],
9595
+ properties: {
9596
+ controlType: { type: 'string' },
9597
+ preserveCompatibleProperties: { type: 'boolean' },
9598
+ },
9599
+ },
9600
+ effects: [{ kind: 'compile-domain-patch', handler: 'metadata-control-type-set', handlerContract: {
9601
+ reads: ['FieldControlType', 'ConfigRegistryService', 'dynamic-fields-discovery', 'fieldMetadata'],
9602
+ writes: ['fieldMetadata.controlType', 'properties', 'normalizedSeed'],
9603
+ identityKeys: ['field.name', 'controlType'],
9604
+ inputSchema: { type: 'object', required: ['controlType'], properties: { controlType: { type: 'string' }, preserveCompatibleProperties: { type: 'boolean' } } },
9605
+ failureModes: ['control-type-not-discovered', 'editor-coverage-missing', 'incompatible-metadata-preservation'],
9606
+ description: 'Changes control type only when dynamic-fields discovery and metadata-editor config registry both expose renderer/editor coverage.',
9607
+ } }],
9608
+ destructive: false,
9609
+ requiresConfirmation: false,
9610
+ validators: ['control-type-exists-in-discovery', 'editor-coverage-exists', 'metadata-round-trip'],
9611
+ affectedPaths: ['fieldMetadata.controlType', 'properties', 'normalizedSeed'],
9612
+ submissionImpact: 'affects-schema-backed-data',
9613
+ preconditions: ['dynamic-fields-discovery-loaded'],
9614
+ },
9615
+ {
9616
+ operationId: 'optionSource.configure',
9617
+ title: 'Configure option source',
9618
+ scope: 'dataBinding',
9619
+ targetKind: 'optionSource',
9620
+ target: { kind: 'optionSource', resolver: 'field-metadata-option-source', ambiguityPolicy: 'fail', required: false },
9621
+ inputSchema: optionSourceSchema,
9622
+ effects: [{ kind: 'compile-domain-patch', handler: 'metadata-option-source-configure', handlerContract: {
9623
+ reads: ['fieldMetadata.optionSource', 'backend.x-ui.optionSource', 'SchemaNormalizerService'],
9624
+ writes: ['fieldMetadata.optionSource'],
9625
+ identityKeys: ['field.name', 'optionSource.kind', 'optionSource.resource'],
9626
+ inputSchema: optionSourceSchema,
9627
+ failureModes: ['invalid-option-source-kind', 'remote-source-missing-governed-resource', 'dependency-filter-map-invalid'],
9628
+ description: 'Configures canonical optionSource fields while preserving backend x-ui dependsOn and dependencyFilterMap semantics.',
9629
+ } }],
9630
+ destructive: false,
9631
+ requiresConfirmation: false,
9632
+ validators: ['option-source-shape-canonical', 'remote-option-source-governed', 'cascade-backend-shape-preserved', 'metadata-round-trip'],
9633
+ affectedPaths: ['fieldMetadata.optionSource'],
9634
+ submissionImpact: 'affects-remote-binding',
9635
+ preconditions: ['field-metadata-loaded'],
9636
+ },
9637
+ {
9638
+ operationId: 'cascade.configure',
9639
+ title: 'Configure cascade rule',
9640
+ scope: 'interaction',
9641
+ targetKind: 'cascade',
9642
+ target: { kind: 'cascade', resolver: 'metadata-editor-cascade-rules', ambiguityPolicy: 'fail', required: false },
9643
+ inputSchema: cascadeSchema,
9644
+ effects: [{ kind: 'compile-domain-patch', handler: 'metadata-cascade-configure', handlerContract: {
9645
+ reads: ['CascadeManagerTabComponent', 'CascadeRulesService', 'fields', 'fieldMetadata.optionSource', 'fieldMetadata.dependencyFields', 'fieldMetadata.dependencyFilterMap'],
9646
+ writes: ['fieldMetadata.dependencyFields', 'fieldMetadata.enableDependencyCascade', 'fieldMetadata.resetOnDependentChange', 'fieldMetadata.dependencyFilterMap', 'fieldMetadata.dependencyValuePath', 'fieldMetadata.dependencyMergeStrategy', 'fieldMetadata.dependencyDebounceMs', 'fieldMetadata.dependencyLoadOnChange', 'fieldMetadata.optionSource.dependsOn', 'fieldMetadata.optionSource.dependencyFilterMap'],
9647
+ identityKeys: ['dependentField', 'sourceField'],
9648
+ inputSchema: cascadeSchema,
9649
+ failureModes: ['dependent-field-not-found', 'source-field-not-found', 'cascade-cycle-detected', 'dependency-filter-map-invalid'],
9650
+ description: 'Applies cascade dependencies through CascadeRulesService using the metadata-editor save contract while preserving backend x-ui optionSource dependency semantics during hydration.',
9651
+ } }],
9652
+ destructive: false,
9653
+ requiresConfirmation: false,
9654
+ validators: ['cascade-fields-exist', 'cascade-cycle-free', 'cascade-backend-shape-preserved', 'option-source-shape-canonical', 'remote-option-source-governed', 'metadata-round-trip'],
9655
+ affectedPaths: ['fieldMetadata.dependencyFields', 'fieldMetadata.enableDependencyCascade', 'fieldMetadata.resetOnDependentChange', 'fieldMetadata.dependencyFilterMap', 'fieldMetadata.dependencyValuePath', 'fieldMetadata.dependencyMergeStrategy', 'fieldMetadata.dependencyDebounceMs', 'fieldMetadata.dependencyLoadOnChange', 'fieldMetadata.optionSource.dependsOn', 'fieldMetadata.optionSource.dependencyFilterMap'],
9656
+ submissionImpact: 'affects-remote-binding',
9657
+ preconditions: ['field-list-loaded'],
9658
+ },
9659
+ {
9660
+ operationId: 'renderer.configure',
9661
+ title: 'Configure renderer coverage',
9662
+ scope: 'editorCoverage',
9663
+ targetKind: 'renderer',
9664
+ target: { kind: 'renderer', resolver: 'metadata-editor-renderer-property', ambiguityPolicy: 'fail', required: true },
9665
+ inputSchema: {
9666
+ type: 'object',
9667
+ required: ['propertyName', 'editorType'],
9668
+ properties: {
9669
+ propertyName: { type: 'string' },
9670
+ editorType: { type: 'string' },
9671
+ group: { type: 'string' },
9672
+ required: { type: 'boolean' },
9673
+ options: { type: 'array' },
9674
+ },
9675
+ },
9676
+ effects: [{ kind: 'compile-domain-patch', handler: 'metadata-renderer-configure', handlerContract: {
9677
+ reads: ['DynamicEditorRendererComponent', 'EditorComponentRegistryService', 'ConfigRegistryService', 'EditorProperty'],
9678
+ writes: ['properties', 'editorCoverage'],
9679
+ identityKeys: ['controlType', 'propertyName'],
9680
+ inputSchema: { type: 'object', required: ['propertyName', 'editorType'], properties: { propertyName: { type: 'string' }, editorType: { type: 'string' }, group: { type: 'string' } } },
9681
+ failureModes: ['editor-type-not-registered', 'property-not-covered', 'json-only-coverage-not-accepted'],
9682
+ description: 'Configures visual renderer coverage for a metadata property and rejects JSON-only support for required visual authoring.',
9683
+ } }],
9684
+ destructive: false,
9685
+ requiresConfirmation: false,
9686
+ validators: ['renderer-editor-type-registered', 'visual-editor-coverage-required', 'editor-coverage-exists', 'metadata-round-trip'],
9687
+ affectedPaths: ['properties', 'editorCoverage'],
9688
+ submissionImpact: 'config-only',
9689
+ preconditions: ['config-registry-loaded'],
9690
+ },
9691
+ {
9692
+ operationId: 'validationRule.add',
9693
+ title: 'Add validation rule',
9694
+ scope: 'fieldMetadataPath',
9695
+ targetKind: 'validation',
9696
+ target: { kind: 'validation', resolver: 'field-metadata-validation-rules', ambiguityPolicy: 'fail', required: false },
9697
+ inputSchema: {
9698
+ type: 'object',
9699
+ required: ['rule'],
9700
+ properties: {
9701
+ rule: { type: 'object' },
9702
+ message: { type: 'string' },
9703
+ contextValidatorId: { type: 'string' },
9704
+ },
9705
+ },
9706
+ effects: [{ kind: 'compile-domain-patch', handler: 'metadata-validation-rule-add', handlerContract: {
9707
+ reads: ['fieldMetadata.validators', 'ContextValidatorRegistryService', 'DynamicFormFactoryService'],
9708
+ writes: ['fieldMetadata.validators'],
9709
+ identityKeys: ['field.name', 'rule.type', 'contextValidatorId'],
9710
+ inputSchema: { type: 'object', required: ['rule'], properties: { rule: { type: 'object' }, message: { type: 'string' }, contextValidatorId: { type: 'string' } } },
9711
+ failureModes: ['validation-rule-invalid', 'context-validator-not-registered', 'duplicate-validation-rule'],
9712
+ description: 'Adds a canonical validation rule and verifies contextual validator availability before persistence.',
9713
+ } }],
9714
+ destructive: false,
9715
+ requiresConfirmation: false,
9716
+ validators: ['validation-rule-canonical', 'context-validator-registered', 'metadata-round-trip'],
9717
+ affectedPaths: ['fieldMetadata.validators'],
9718
+ submissionImpact: 'affects-submission',
9719
+ preconditions: ['field-metadata-loaded'],
9720
+ },
9721
+ {
9722
+ operationId: 'contextHint.set',
9723
+ title: 'Set metadata context hint',
9724
+ scope: 'meta',
9725
+ targetKind: 'contextHint',
9726
+ target: { kind: 'contextHint', resolver: 'metadata-editor-context-hints', ambiguityPolicy: 'fail', required: false },
9727
+ inputSchema: {
9728
+ type: 'object',
9729
+ required: ['hintPath', 'value'],
9730
+ properties: {
9731
+ hintPath: { enum: ['hint', 'helpText', 'description', 'tooltip', 'ariaLabel', 'ariaDescribedBy'] },
9732
+ value: {},
9733
+ localeKey: { type: 'string' },
9734
+ },
9735
+ },
9736
+ effects: [{ kind: 'compile-domain-patch', handler: 'metadata-context-hint-set', handlerContract: {
9737
+ reads: ['fieldMetadata.hint', 'fieldMetadata.helpText', 'fieldMetadata.description', 'fieldMetadata.tooltip', 'fieldMetadata.ariaLabel', 'fieldMetadata.ariaDescribedBy', 'metadata-editor-i18n'],
9738
+ writes: ['fieldMetadata.hint', 'fieldMetadata.helpText', 'fieldMetadata.description', 'fieldMetadata.tooltip', 'fieldMetadata.ariaLabel', 'fieldMetadata.ariaDescribedBy'],
9739
+ identityKeys: ['field.name', 'hintPath'],
9740
+ inputSchema: { type: 'object', required: ['hintPath', 'value'], properties: { hintPath: { enum: ['hint', 'helpText', 'description', 'tooltip', 'ariaLabel', 'ariaDescribedBy'] }, value: {}, localeKey: { type: 'string' } } },
9741
+ failureModes: ['hint-path-not-canonical', 'hint-value-not-serializable', 'i18n-key-missing'],
9742
+ description: 'Sets structured FieldMetadata hint/help/accessibility text through canonical metadata paths and metadata-editor i18n constraints.',
9743
+ } }],
9744
+ destructive: false,
9745
+ requiresConfirmation: false,
9746
+ validators: ['context-hint-shape-canonical', 'context-hint-i18n-compatible', 'metadata-round-trip'],
9747
+ affectedPaths: ['fieldMetadata.hint', 'fieldMetadata.helpText', 'fieldMetadata.description', 'fieldMetadata.tooltip', 'fieldMetadata.ariaLabel', 'fieldMetadata.ariaDescribedBy'],
9748
+ submissionImpact: 'visual-only',
9749
+ preconditions: ['field-metadata-loaded'],
9750
+ },
9751
+ {
9752
+ operationId: 'normalization.apply',
9753
+ title: 'Apply schema normalization',
9754
+ scope: 'runtimeCoverage',
9755
+ targetKind: 'normalization',
9756
+ target: { kind: 'normalization', resolver: 'metadata-editor-schema-normalizer', ambiguityPolicy: 'fail', required: false },
9757
+ inputSchema: {
9758
+ type: 'object',
9759
+ properties: {
9760
+ mode: { enum: ['hydrate-seed', 'coerce-types', 'apply-defaults', 'preserve-advanced-properties'] },
9761
+ preserveUnknownCanonicalFields: { type: 'boolean' },
9762
+ },
9763
+ },
9764
+ effects: [{ kind: 'compile-domain-patch', handler: 'metadata-normalization-apply', handlerContract: {
9765
+ reads: ['SchemaNormalizerService', 'fieldMetadata', 'properties', 'DynamicFormFactoryService'],
9766
+ writes: ['normalizedSeed', 'form', 'fieldMetadata'],
9767
+ identityKeys: ['field.name', 'controlType', 'mode'],
9768
+ inputSchema: { type: 'object', properties: { mode: { enum: ['hydrate-seed', 'coerce-types', 'apply-defaults', 'preserve-advanced-properties'] }, preserveUnknownCanonicalFields: { type: 'boolean' } } },
9769
+ failureModes: ['normalizer-missing', 'type-coercion-failed', 'advanced-property-lost', 'runtime-editor-drift'],
9770
+ description: 'Runs metadata-editor schema normalization while preserving canonical advanced FieldMetadata properties and editor/runtime round-trip.',
9771
+ } }],
9772
+ destructive: false,
9773
+ requiresConfirmation: false,
9774
+ validators: ['normalization-preserves-canonical-fields', 'runtime-editor-round-trip', 'metadata-round-trip'],
9775
+ affectedPaths: ['normalizedSeed', 'form', 'fieldMetadata'],
9776
+ submissionImpact: 'config-only',
9777
+ preconditions: ['field-metadata-loaded', 'editor-properties-loaded'],
9778
+ },
9779
+ ],
9780
+ validators: [
9781
+ { validatorId: 'field-metadata-shape-canonical', level: 'error', code: 'METADATA_FIELD_SHAPE_CANONICAL', description: 'Edited metadata must remain compatible with canonical FieldMetadata.' },
9782
+ { validatorId: 'field-path-supported-by-editor', level: 'error', code: 'METADATA_FIELD_PATH_SUPPORTED', description: 'Edited paths must be supported by visual editor coverage or explicitly delegated.' },
9783
+ { validatorId: 'control-type-exists-in-discovery', level: 'error', code: 'METADATA_CONTROL_TYPE_DISCOVERED', description: 'Control type must exist in dynamic-fields discovery.' },
9784
+ { validatorId: 'editor-coverage-exists', level: 'error', code: 'METADATA_EDITOR_COVERAGE_EXISTS', description: 'Control type and property must have metadata-editor coverage.' },
9785
+ { validatorId: 'option-source-shape-canonical', level: 'error', code: 'METADATA_OPTION_SOURCE_CANONICAL', description: 'Option source must use canonical FieldMetadata/x-ui shape.' },
9786
+ { validatorId: 'remote-option-source-governed', level: 'error', code: 'METADATA_REMOTE_OPTION_SOURCE_GOVERNED', description: 'Remote option sources must resolve through governed resource or endpoint metadata.' },
9787
+ { validatorId: 'cascade-fields-exist', level: 'error', code: 'METADATA_CASCADE_FIELDS_EXIST', description: 'Cascade source and dependent fields must exist.' },
9788
+ { validatorId: 'cascade-cycle-free', level: 'error', code: 'METADATA_CASCADE_CYCLE_FREE', description: 'Cascade dependencies must not create cycles.' },
9789
+ { validatorId: 'cascade-backend-shape-preserved', level: 'error', code: 'METADATA_CASCADE_BACKEND_SHAPE_PRESERVED', description: 'Cascade config must preserve x-ui optionSource dependsOn and dependencyFilterMap semantics.' },
9790
+ { validatorId: 'renderer-editor-type-registered', level: 'error', code: 'METADATA_RENDERER_TYPE_REGISTERED', description: 'Renderer editor type must be registered.' },
9791
+ { validatorId: 'visual-editor-coverage-required', level: 'error', code: 'METADATA_VISUAL_COVERAGE_REQUIRED', description: 'JSON-only support is not accepted for required visual authoring coverage.' },
9792
+ { validatorId: 'validation-rule-canonical', level: 'error', code: 'METADATA_VALIDATION_RULE_CANONICAL', description: 'Validation rule must use canonical metadata shape.' },
9793
+ { validatorId: 'context-validator-registered', level: 'error', code: 'METADATA_CONTEXT_VALIDATOR_REGISTERED', description: 'Contextual validator must be registered before use.' },
9794
+ { 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.' },
9795
+ { validatorId: 'context-hint-i18n-compatible', level: 'warning', code: 'METADATA_CONTEXT_HINT_I18N_COMPATIBLE', description: 'Authoring hint text must remain compatible with metadata-editor i18n.' },
9796
+ { validatorId: 'normalization-preserves-canonical-fields', level: 'error', code: 'METADATA_NORMALIZATION_PRESERVES_FIELDS', description: 'Normalization must not drop canonical advanced FieldMetadata properties.' },
9797
+ { validatorId: 'runtime-editor-round-trip', level: 'error', code: 'METADATA_RUNTIME_EDITOR_ROUND_TRIP', description: 'Runtime and editor must consume the same normalized metadata shape.' },
9798
+ { validatorId: 'metadata-round-trip', level: 'error', code: 'METADATA_ROUND_TRIP', description: 'Open, edit, apply/save, reopen and runtime consume must preserve metadata.' },
9799
+ ],
9800
+ roundTripRequirements: [
9801
+ 'FieldMetadata is the canonical edited document shape; backend x-ui-derived metadata must not be rewritten into host-only aliases.',
9802
+ 'Control type changes require both dynamic-fields discovery and metadata-editor visual coverage.',
9803
+ 'Option source cascades preserve x-ui optionSource.dependsOn and optionSource.dependencyFilterMap semantics.',
9804
+ 'JSON-only support is incomplete when visual editor coverage is required.',
9805
+ 'Schema normalization must preserve advanced canonical properties through open, edit, apply/save, reopen and runtime consumption.',
9806
+ ],
9807
+ examples: [
9808
+ { id: 'set-label', request: 'Set this field label to Customer name.', operationId: 'fieldMetadata.property.set', params: { path: 'label', value: 'Customer name' }, isPositive: true },
9809
+ { id: 'set-control-type', request: 'Change this field to a select control.', operationId: 'controlType.set', params: { controlType: 'select', preserveCompatibleProperties: true }, isPositive: true },
9810
+ { id: 'configure-remote-options', request: 'Load options from the customers resource using id and name.', operationId: 'optionSource.configure', params: { kind: 'resource', resource: 'customers', valueField: 'id', labelField: 'name' }, isPositive: true },
9811
+ { id: 'configure-cascade', request: 'Filter city options when state changes.', operationId: 'cascade.configure', params: { dependentField: 'city', sourceField: 'state', dependencyFilterMap: { stateId: 'state.id' } }, isPositive: true },
9812
+ { 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 },
9813
+ { 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 },
9814
+ { 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 },
9815
+ { 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 },
9816
+ { id: 'reject-unknown-control', request: 'Use an unknown control type named magic-picker.', operationId: 'controlType.set', params: { controlType: 'magic-picker' }, isPositive: false },
9817
+ { id: 'reject-json-only-coverage', request: 'Support this field only by editing raw JSON.', operationId: 'renderer.configure', params: { propertyName: 'optionSource', editorType: 'json' }, isPositive: false },
9818
+ { id: 'reject-cascade-cycle', request: 'Make field A depend on B and B depend on A.', operationId: 'cascade.configure', params: { dependentField: 'a', sourceField: 'b' }, isPositive: false },
9819
+ { id: 'reject-host-only-option-source', request: 'Save a host-only URL string as the option source.', operationId: 'optionSource.configure', params: { kind: 'remote', endpoint: 'local-only://options' }, isPositive: false },
9820
+ ],
9821
+ };
9822
+
7756
9823
  /** Metadata for Praxis Metadata Editor component */
7757
9824
  const PRAXIS_METADATA_EDITOR_COMPONENT_METADATA = {
7758
9825
  id: 'praxis-metadata-editor',
@@ -7807,4 +9874,4 @@ function providePraxisMetadataEditorMetadata() {
7807
9874
  * Generated bundle index. Do not edit.
7808
9875
  */
7809
9876
 
7810
- export { CascadeManagerTabComponent, CascadeRulesService, ConfigRegistryService, ContextValidatorRegistryService, DynamicEditorRendererComponent, DynamicFormFactoryService, EditorComponentRegistryService, FieldMetadataEditorComponent, METADATA_EDITOR_AI_CAPABILITIES, METADATA_EDITOR_AI_CAPABILITY_CATALOG, PRAXIS_METADATA_EDITOR_COMPONENT_METADATA, SchemaNormalizerService, providePraxisMetadataEditorMetadata };
9877
+ export { CascadeManagerTabComponent, CascadeRulesService, ConfigRegistryService, ContextValidatorRegistryService, DynamicEditorRendererComponent, DynamicFormFactoryService, EditorComponentRegistryService, FieldMetadataEditorComponent, METADATA_EDITOR_AI_CAPABILITIES, METADATA_EDITOR_AI_CAPABILITY_CATALOG, PRAXIS_METADATA_EDITOR_AUTHORING_MANIFEST, PRAXIS_METADATA_EDITOR_COMPONENT_METADATA, SchemaNormalizerService, providePraxisMetadataEditorMetadata };