@praxisui/metadata-editor 10.0.0-rc.31 → 10.0.0-rc.33
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,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"generatedAt": "2026-10-18T21:58:32.782Z",
|
|
4
4
|
"packageName": "@praxisui/metadata-editor",
|
|
5
|
-
"packageVersion": "10.0.0-rc.
|
|
5
|
+
"packageVersion": "10.0.0-rc.33",
|
|
6
6
|
"sourceRegistry": "praxis-component-registry-ingestion",
|
|
7
7
|
"sourceRegistryVersion": "1.0.0",
|
|
8
8
|
"componentCount": 1,
|
|
@@ -6,7 +6,7 @@ import * as i3$2 from '@angular/common';
|
|
|
6
6
|
import { CommonModule } from '@angular/common';
|
|
7
7
|
import { isValidCssColor, DynamicFieldLoaderDirective, normalizeCssColorToRgb, providePraxisDynamicFieldsI18n } from '@praxisui/dynamic-fields';
|
|
8
8
|
import * as i2$2 from '@praxisui/core';
|
|
9
|
-
import { providePraxisI18n, PraxisI18nService, PraxisIconDirective, FieldControlType, INLINE_FILTER_CONTROL_TYPES, resolveInlineFilterControlType, normalizeControlTypeToken, normalizeReactiveDeterminations, ComponentMetadataRegistry } from '@praxisui/core';
|
|
9
|
+
import { providePraxisI18n, PraxisI18nService, PraxisIconDirective, FieldControlType, INLINE_FILTER_CONTROL_TYPES, resolveInlineFilterControlType, ResourceDiscoveryService, normalizeControlTypeToken, providePraxisI18nConfig, RESOURCE_DISCOVERY_I18N_CONFIG, normalizeReactiveDeterminations, ComponentMetadataRegistry } from '@praxisui/core';
|
|
10
10
|
import * as i5$1 from '@angular/material/expansion';
|
|
11
11
|
import { MatExpansionModule } from '@angular/material/expansion';
|
|
12
12
|
import * as i2$1 from '@angular/material/icon';
|
|
@@ -26,7 +26,7 @@ import { MatInputModule } from '@angular/material/input';
|
|
|
26
26
|
import * as i6 from '@angular/material/select';
|
|
27
27
|
import { MatSelectModule } from '@angular/material/select';
|
|
28
28
|
import { SETTINGS_PANEL_DATA } from '@praxisui/settings-panel';
|
|
29
|
-
import { BehaviorSubject } from 'rxjs';
|
|
29
|
+
import { Subject, BehaviorSubject, takeUntil, catchError, of } from 'rxjs';
|
|
30
30
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
31
31
|
import { MatChipsModule } from '@angular/material/chips';
|
|
32
32
|
import { MatDividerModule } from '@angular/material/divider';
|
|
@@ -103,14 +103,11 @@ class SchemaNormalizerService {
|
|
|
103
103
|
if (seed && clone.defaultValue === undefined) {
|
|
104
104
|
const val = this.getByPath(seed, clone.name);
|
|
105
105
|
if (val !== undefined) {
|
|
106
|
-
// Stringify complex defaults for textarea-friendly editors
|
|
107
|
-
const needsJson =
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
clone.name === 'views' ||
|
|
112
|
-
clone.name === 'adaptiveSubtitle'));
|
|
113
|
-
clone.defaultValue = needsJson && typeof val !== 'string' ? JSON.stringify(val, null, 2) : val;
|
|
106
|
+
// Stringify complex defaults (arrays/objects) for textarea-friendly JSON editors
|
|
107
|
+
const needsJson = clone.editorType === 'textarea' &&
|
|
108
|
+
val !== null &&
|
|
109
|
+
typeof val === 'object';
|
|
110
|
+
clone.defaultValue = needsJson ? JSON.stringify(val, null, 2) : val;
|
|
114
111
|
try {
|
|
115
112
|
if (clone.name === 'resourcePath' ||
|
|
116
113
|
clone.name === 'filterCriteria' ||
|
|
@@ -443,6 +440,7 @@ const METADATA_EDITOR_EN_US = {
|
|
|
443
440
|
'praxis.metadataEditor.arrayFields.control.date': 'Date',
|
|
444
441
|
'praxis.metadataEditor.fieldResource.label': 'Governed resource',
|
|
445
442
|
'praxis.metadataEditor.fieldResource.searchPlaceholder': 'Search by name, domain, or path',
|
|
443
|
+
'praxis.metadataEditor.fieldResource.fieldSearchPlaceholder': 'Search field by name or label',
|
|
446
444
|
'praxis.metadataEditor.fieldResource.hint': 'Select from the host governed catalog. The saved value remains resourcePath.',
|
|
447
445
|
'praxis.metadataEditor.common.help': 'Help',
|
|
448
446
|
'praxis.metadataEditor.common.iconMaterialSymbolsHint': 'Use the icon name (Material Symbols)',
|
|
@@ -694,6 +692,7 @@ const METADATA_EDITOR_PT_BR = {
|
|
|
694
692
|
'praxis.metadataEditor.arrayFields.control.date': 'Data',
|
|
695
693
|
'praxis.metadataEditor.fieldResource.label': 'Recurso governado',
|
|
696
694
|
'praxis.metadataEditor.fieldResource.searchPlaceholder': 'Buscar por nome, dom\u00ednio ou caminho',
|
|
695
|
+
'praxis.metadataEditor.fieldResource.fieldSearchPlaceholder': 'Buscar campo por nome ou r\u00f3tulo',
|
|
697
696
|
'praxis.metadataEditor.fieldResource.hint': 'Selecione no cat\u00e1logo governado do host. O valor gravado continua sendo resourcePath.',
|
|
698
697
|
'praxis.metadataEditor.common.help': 'Ajuda',
|
|
699
698
|
'praxis.metadataEditor.common.iconMaterialSymbolsHint': 'Use o nome do \u00edcone (Material Symbols)',
|
|
@@ -2721,6 +2720,9 @@ class DynamicEditorRendererComponent {
|
|
|
2721
2720
|
return options.map((option) => ({
|
|
2722
2721
|
...option,
|
|
2723
2722
|
text: this.resolveEditorText(option.textKey, option.text) ?? option.text,
|
|
2723
|
+
code: option.code ?? undefined,
|
|
2724
|
+
badge: option.badge ?? undefined,
|
|
2725
|
+
description: option.description ?? undefined,
|
|
2724
2726
|
}));
|
|
2725
2727
|
}
|
|
2726
2728
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicEditorRendererComponent, deps: [{ token: i1$1.MatDialog }, { token: i2$2.IconPickerService }, { token: i2$2.PraxisI18nService, optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
@@ -5546,6 +5548,46 @@ const numberProperties = [
|
|
|
5546
5548
|
{ name: 'step', label: 'Step (incremento)', editorType: 'number', group: 'Formato/Comportamento', hint: 'Incremento em unidades; combine com min/max (ex.: 0.01).' },
|
|
5547
5549
|
{ name: 'numberFormat.decimalPlaces', label: 'Casas decimais', editorType: 'number', group: 'Formato/Comportamento', row: 'formato.decimais', inline: true, hint: 'Precisão exibida/aceita (ex.: 2).' },
|
|
5548
5550
|
{ name: 'showGrouping', label: 'Agrupar milhares', editorType: 'checkbox', group: 'Formato/Comportamento', row: 'formato.decimais', inline: true, hint: 'Mostra separador de milhar sem alterar o valor salvo.' },
|
|
5551
|
+
{ name: 'numberFormat.percentage', label: 'Modo Percentual (Percent Studio)', editorType: 'checkbox', group: 'Formato/Comportamento', hint: 'Ativa pílula de progresso, micro-anel interativo e barra/popover visual de seleção de percentual.' },
|
|
5552
|
+
{
|
|
5553
|
+
name: 'numericFormat',
|
|
5554
|
+
label: 'Formato numérico canônico',
|
|
5555
|
+
editorType: 'select',
|
|
5556
|
+
group: 'Formato/Comportamento',
|
|
5557
|
+
options: [
|
|
5558
|
+
{ value: 'decimal', text: 'Decimal' },
|
|
5559
|
+
{ value: 'integer', text: 'Inteiro' },
|
|
5560
|
+
{ value: 'currency', text: 'Moeda' },
|
|
5561
|
+
{ value: 'percent', text: 'Percentual (Percent Studio)' },
|
|
5562
|
+
{ value: 'scientific', text: 'Científico' },
|
|
5563
|
+
],
|
|
5564
|
+
hint: 'Formato semântico do campo numérico vindo do contrato x-ui.',
|
|
5565
|
+
},
|
|
5566
|
+
{
|
|
5567
|
+
name: 'showPercentStudio',
|
|
5568
|
+
label: 'Exibir barra Percent Studio no formulário',
|
|
5569
|
+
editorType: 'checkbox',
|
|
5570
|
+
group: 'Formato/Comportamento',
|
|
5571
|
+
hint: 'Mostra o trilho interativo, presets de quartis e steppers rápidos abaixo do pdx-number-input quando em modo percentual.',
|
|
5572
|
+
},
|
|
5573
|
+
{
|
|
5574
|
+
name: 'inlineVisualStyle',
|
|
5575
|
+
label: 'Estilo visual inline (percentual)',
|
|
5576
|
+
editorType: 'select',
|
|
5577
|
+
group: 'Formato/Comportamento',
|
|
5578
|
+
options: [
|
|
5579
|
+
{ value: 'compact', text: 'Compacto (Pílula líquida + popover)' },
|
|
5580
|
+
{ value: 'graphic', text: 'Gráfico expandido (Card com donut + slider)' },
|
|
5581
|
+
],
|
|
5582
|
+
hint: 'Define a apresentação de filtros numéricos percentuais em superfícies inline.',
|
|
5583
|
+
},
|
|
5584
|
+
{
|
|
5585
|
+
name: 'percentPresets',
|
|
5586
|
+
label: 'Presets rápidos de percentual',
|
|
5587
|
+
editorType: 'json',
|
|
5588
|
+
group: 'Formato/Comportamento',
|
|
5589
|
+
hint: 'Lista de atalhos rápidos no Percent Studio (ex.: [0, 25, 50, 75, 100] ou [{"label": "Meta", "value": 12.5}]).',
|
|
5590
|
+
},
|
|
5549
5591
|
{
|
|
5550
5592
|
name: 'inputMode',
|
|
5551
5593
|
label: 'Modo de entrada',
|
|
@@ -7718,6 +7760,7 @@ const rangeSliderProperties = [
|
|
|
7718
7760
|
{ value: 'compact', text: 'Compacto' },
|
|
7719
7761
|
{ value: 'storage', text: 'Armazenamento' },
|
|
7720
7762
|
], hint: 'Formatter declarativo para labels e aria-valuetext.' },
|
|
7763
|
+
{ name: 'numberFormat.percentage', label: 'Percentual (%)', editorType: 'checkbox', group: 'Formato/Comportamento', hint: 'Ativa apresentação Percent Studio (pílula líquida de intervalo, micro-anel e quartis automáticos 0%–100%).' },
|
|
7721
7764
|
{ name: 'showTicks', label: 'Ticks', editorType: 'select', group: 'Formato/Comportamento', options: [
|
|
7722
7765
|
{ value: false, text: 'Sem ticks' },
|
|
7723
7766
|
{ value: true, text: 'Com ticks' },
|
|
@@ -10732,6 +10775,93 @@ function mergeEditorProperties(base, contextual) {
|
|
|
10732
10775
|
}
|
|
10733
10776
|
return merged;
|
|
10734
10777
|
}
|
|
10778
|
+
const inlinePhoneEditorProperties = mergeEditorProperties(inputProperties, phoneInputProperties);
|
|
10779
|
+
const inlinePeriodRangeEditorProperties = mergeEditorProperties(rangeSliderProperties, [
|
|
10780
|
+
{
|
|
10781
|
+
name: 'granularity',
|
|
10782
|
+
label: 'Granularidade do período',
|
|
10783
|
+
editorType: 'select',
|
|
10784
|
+
group: 'Formato/Comportamento',
|
|
10785
|
+
options: [
|
|
10786
|
+
{ value: 'month', text: 'Mês (1-12 ou YYYY-MM)' },
|
|
10787
|
+
{ value: 'year', text: 'Ano (YYYY)' },
|
|
10788
|
+
],
|
|
10789
|
+
hint: 'Define se o seletor opera em competência mensal ou exercício anual.',
|
|
10790
|
+
},
|
|
10791
|
+
{
|
|
10792
|
+
name: 'defaultGranularity',
|
|
10793
|
+
label: 'Granularidade inicial',
|
|
10794
|
+
editorType: 'select',
|
|
10795
|
+
group: 'Formato/Comportamento',
|
|
10796
|
+
options: [
|
|
10797
|
+
{ value: 'month', text: 'Mês' },
|
|
10798
|
+
{ value: 'year', text: 'Ano' },
|
|
10799
|
+
],
|
|
10800
|
+
hint: 'Granularidade inicial aberta no painel.',
|
|
10801
|
+
},
|
|
10802
|
+
{
|
|
10803
|
+
name: 'selectionGrid',
|
|
10804
|
+
label: 'Exibir grade de seleção (Jan-Dez / Anos)',
|
|
10805
|
+
editorType: 'checkbox',
|
|
10806
|
+
group: 'Formato/Comportamento',
|
|
10807
|
+
hint: 'Mostra botões clicáveis de meses ou anos para seleção de único item ou faixa.',
|
|
10808
|
+
},
|
|
10809
|
+
{
|
|
10810
|
+
name: 'showSlider',
|
|
10811
|
+
label: 'Exibir slider complementar',
|
|
10812
|
+
editorType: 'checkbox',
|
|
10813
|
+
group: 'Formato/Comportamento',
|
|
10814
|
+
hint: 'Mostra barra deslizante sincronizada com a grade de meses/anos.',
|
|
10815
|
+
},
|
|
10816
|
+
{
|
|
10817
|
+
name: 'inlineChipDisplay',
|
|
10818
|
+
label: 'Exibição no chip (inline)',
|
|
10819
|
+
editorType: 'select',
|
|
10820
|
+
group: 'Formato/Comportamento',
|
|
10821
|
+
options: [
|
|
10822
|
+
{ value: 'badge', text: 'Rótulo + badge (ex.: Mês: Jan – Jun)' },
|
|
10823
|
+
{ value: 'value', text: 'Apenas valor (ex.: Jan – Jun)' },
|
|
10824
|
+
{ value: 'label-value', text: 'Rótulo + valor' },
|
|
10825
|
+
],
|
|
10826
|
+
hint: 'Define como o intervalo selecionado aparece na pílula fechada.',
|
|
10827
|
+
},
|
|
10828
|
+
{
|
|
10829
|
+
name: 'minYear',
|
|
10830
|
+
label: 'Ano mínimo',
|
|
10831
|
+
editorType: 'number',
|
|
10832
|
+
group: 'Validação',
|
|
10833
|
+
row: 'val.yearminmax',
|
|
10834
|
+
inline: true,
|
|
10835
|
+
hint: 'Ano inicial exibido na grade de anos (ex.: 2020).',
|
|
10836
|
+
},
|
|
10837
|
+
{
|
|
10838
|
+
name: 'maxYear',
|
|
10839
|
+
label: 'Ano máximo',
|
|
10840
|
+
editorType: 'number',
|
|
10841
|
+
group: 'Validação',
|
|
10842
|
+
row: 'val.yearminmax',
|
|
10843
|
+
inline: true,
|
|
10844
|
+
hint: 'Ano final exibido na grade de anos (ex.: 2030).',
|
|
10845
|
+
},
|
|
10846
|
+
{
|
|
10847
|
+
name: 'minPeriod',
|
|
10848
|
+
label: 'Período mínimo',
|
|
10849
|
+
editorType: 'text',
|
|
10850
|
+
group: 'Validação',
|
|
10851
|
+
row: 'val.periodminmax',
|
|
10852
|
+
inline: true,
|
|
10853
|
+
hint: 'Limite inferior de competência (ex.: 2020-01 ou 1).',
|
|
10854
|
+
},
|
|
10855
|
+
{
|
|
10856
|
+
name: 'maxPeriod',
|
|
10857
|
+
label: 'Período máximo',
|
|
10858
|
+
editorType: 'text',
|
|
10859
|
+
group: 'Validação',
|
|
10860
|
+
row: 'val.periodminmax',
|
|
10861
|
+
inline: true,
|
|
10862
|
+
hint: 'Limite superior de competência (ex.: 2027-12 ou 12).',
|
|
10863
|
+
},
|
|
10864
|
+
]);
|
|
10735
10865
|
function hasCanonicalDocumentSemantics(metadata) {
|
|
10736
10866
|
if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {
|
|
10737
10867
|
return false;
|
|
@@ -10747,12 +10877,12 @@ const INLINE_FILTER_EDITOR_PROPERTIES_BY_CONTROL_TYPE = Object.freeze({
|
|
|
10747
10877
|
[INLINE_FILTER_CONTROL_TYPES.AUTOCOMPLETE]: selectProperties,
|
|
10748
10878
|
[INLINE_FILTER_CONTROL_TYPES.MULTI_SELECT]: selectProperties,
|
|
10749
10879
|
[INLINE_FILTER_CONTROL_TYPES.INPUT]: inputProperties,
|
|
10750
|
-
[INLINE_FILTER_CONTROL_TYPES.PHONE]:
|
|
10880
|
+
[INLINE_FILTER_CONTROL_TYPES.PHONE]: inlinePhoneEditorProperties,
|
|
10751
10881
|
[INLINE_FILTER_CONTROL_TYPES.TOGGLE]: toggleProperties,
|
|
10752
10882
|
[INLINE_FILTER_CONTROL_TYPES.RANGE]: rangeSliderProperties,
|
|
10753
|
-
[INLINE_FILTER_CONTROL_TYPES.PERIOD_RANGE]:
|
|
10754
|
-
[INLINE_FILTER_CONTROL_TYPES.YEAR_RANGE]:
|
|
10755
|
-
[INLINE_FILTER_CONTROL_TYPES.MONTH_RANGE]:
|
|
10883
|
+
[INLINE_FILTER_CONTROL_TYPES.PERIOD_RANGE]: inlinePeriodRangeEditorProperties,
|
|
10884
|
+
[INLINE_FILTER_CONTROL_TYPES.YEAR_RANGE]: inlinePeriodRangeEditorProperties,
|
|
10885
|
+
[INLINE_FILTER_CONTROL_TYPES.MONTH_RANGE]: inlinePeriodRangeEditorProperties,
|
|
10756
10886
|
[INLINE_FILTER_CONTROL_TYPES.DATE]: dateProperties,
|
|
10757
10887
|
[INLINE_FILTER_CONTROL_TYPES.DATE_RANGE]: dateRangeProperties,
|
|
10758
10888
|
[INLINE_FILTER_CONTROL_TYPES.TIME]: timePickerProperties,
|
|
@@ -10794,7 +10924,10 @@ class FieldMetadataEditorComponent {
|
|
|
10794
10924
|
normalizedProps = [];
|
|
10795
10925
|
renderedProps = [];
|
|
10796
10926
|
initialSnapshot = null;
|
|
10927
|
+
initialControlType;
|
|
10797
10928
|
baselineReady = false;
|
|
10929
|
+
formRebuild$ = new Subject();
|
|
10930
|
+
hostApplyPatch;
|
|
10798
10931
|
// Lista de tipos suportados no editor (curada para os tipos com config disponível)
|
|
10799
10932
|
typeOptions = [
|
|
10800
10933
|
{ value: FieldControlType.INPUT, label: 'Texto (Input)' },
|
|
@@ -10847,6 +10980,9 @@ class FieldMetadataEditorComponent {
|
|
|
10847
10980
|
isValid$ = new BehaviorSubject(true);
|
|
10848
10981
|
isBusy$ = new BehaviorSubject(false);
|
|
10849
10982
|
destroyRef = inject(DestroyRef);
|
|
10983
|
+
resourceDiscovery = inject(ResourceDiscoveryService, { optional: true });
|
|
10984
|
+
i18n = inject(PraxisI18nService, { optional: true });
|
|
10985
|
+
discoveredResourceOptions = [];
|
|
10850
10986
|
lastCurrencyPreset = null;
|
|
10851
10987
|
constructor(registry, normalizer, factory, ctxValidators) {
|
|
10852
10988
|
this.registry = registry;
|
|
@@ -10861,10 +10997,7 @@ class FieldMetadataEditorComponent {
|
|
|
10861
10997
|
if (data.seed)
|
|
10862
10998
|
this.seed = data.seed;
|
|
10863
10999
|
// Bridge: allow host to receive patches even if saved$/applied$ are missed
|
|
10864
|
-
|
|
10865
|
-
this.__hostApplyPatch = data.hostBridge?.applyPatch;
|
|
10866
|
-
}
|
|
10867
|
-
catch { }
|
|
11000
|
+
this.hostApplyPatch = data.hostBridge?.applyPatch;
|
|
10868
11001
|
}
|
|
10869
11002
|
// Normalize legacy avatar seed (root imageSrc/imageAlt -> extra.* for editor binding)
|
|
10870
11003
|
try {
|
|
@@ -10886,14 +11019,145 @@ class FieldMetadataEditorComponent {
|
|
|
10886
11019
|
}
|
|
10887
11020
|
catch { }
|
|
10888
11021
|
}
|
|
11022
|
+
catalogLookupRequested = false;
|
|
11023
|
+
resourceSchemaFieldsByPath = new Map();
|
|
11024
|
+
resourceSchemaRequestIdByScope = {
|
|
11025
|
+
root: 0,
|
|
11026
|
+
optionSource: 0,
|
|
11027
|
+
};
|
|
10889
11028
|
ngOnInit() {
|
|
10890
|
-
try {
|
|
10891
|
-
const hasBridge = typeof this.__hostApplyPatch === 'function';
|
|
10892
|
-
(console.log || console.debug)('[FieldMetadataEditor] ngOnInit', { controlType: this.controlType, hasHostBridge: hasBridge });
|
|
10893
|
-
}
|
|
10894
|
-
catch { }
|
|
10895
11029
|
this.rebuildEditorForType(this.controlType);
|
|
10896
11030
|
}
|
|
11031
|
+
loadCatalogResourceOptionsIfNeeded() {
|
|
11032
|
+
const hasResourcePathProp = this.normalizedProps.some((prop) => prop?.name === 'resourcePath' ||
|
|
11033
|
+
prop?.name === 'optionSource.resourcePath');
|
|
11034
|
+
if (!hasResourcePathProp) {
|
|
11035
|
+
return;
|
|
11036
|
+
}
|
|
11037
|
+
const optionSourceTypeCtrl = this.form?.get('optionSource.type');
|
|
11038
|
+
if (optionSourceTypeCtrl) {
|
|
11039
|
+
optionSourceTypeCtrl.valueChanges
|
|
11040
|
+
.pipe(takeUntil(this.formRebuild$), takeUntilDestroyed(this.destroyRef))
|
|
11041
|
+
.subscribe(() => {
|
|
11042
|
+
if (this.discoveredResourceOptions.length > 0 && this.normalizedProps.length > 0) {
|
|
11043
|
+
this.renderedProps = this.buildRenderedProps(this.normalizedProps);
|
|
11044
|
+
}
|
|
11045
|
+
});
|
|
11046
|
+
}
|
|
11047
|
+
this.bindResourcePathSchemaSync('resourcePath', 'root');
|
|
11048
|
+
this.bindResourcePathSchemaSync('optionSource.resourcePath', 'optionSource');
|
|
11049
|
+
if (this.catalogLookupRequested || !this.resourceDiscovery) {
|
|
11050
|
+
return;
|
|
11051
|
+
}
|
|
11052
|
+
this.catalogLookupRequested = true;
|
|
11053
|
+
const endpointKey = this.resolveDiscoveryEndpointKey();
|
|
11054
|
+
this.resourceDiscovery
|
|
11055
|
+
.getDiscoveredResources({
|
|
11056
|
+
intent: 'any',
|
|
11057
|
+
...(endpointKey ? { endpointKey } : {}),
|
|
11058
|
+
})
|
|
11059
|
+
.pipe(catchError(() => of([])), takeUntilDestroyed(this.destroyRef))
|
|
11060
|
+
.subscribe((options) => {
|
|
11061
|
+
this.discoveredResourceOptions = options;
|
|
11062
|
+
this.seedCatalogResponseFieldsCache(options);
|
|
11063
|
+
if (options.length > 0 && this.normalizedProps.length > 0) {
|
|
11064
|
+
this.renderedProps = this.buildRenderedProps(this.normalizedProps);
|
|
11065
|
+
}
|
|
11066
|
+
});
|
|
11067
|
+
}
|
|
11068
|
+
resolveDiscoveryEndpointKey() {
|
|
11069
|
+
const raw = this.form?.get('endpointKey')?.value ??
|
|
11070
|
+
this.form?.get('apiEndpointKey')?.value ??
|
|
11071
|
+
this.seed?.endpointKey ??
|
|
11072
|
+
this.seed?.apiEndpointKey;
|
|
11073
|
+
const trimmed = String(raw ?? '').trim();
|
|
11074
|
+
return trimmed || undefined;
|
|
11075
|
+
}
|
|
11076
|
+
bindResourcePathSchemaSync(controlPath, scope) {
|
|
11077
|
+
const ctrl = this.form?.get(controlPath);
|
|
11078
|
+
if (!ctrl) {
|
|
11079
|
+
return;
|
|
11080
|
+
}
|
|
11081
|
+
let lastPath = String(ctrl.value ?? '').trim();
|
|
11082
|
+
if (lastPath) {
|
|
11083
|
+
this.resolveAndApplyResourceSchemaFields(lastPath, scope, false);
|
|
11084
|
+
}
|
|
11085
|
+
ctrl.valueChanges
|
|
11086
|
+
.pipe(takeUntil(this.formRebuild$), takeUntilDestroyed(this.destroyRef))
|
|
11087
|
+
.subscribe((nextValue) => {
|
|
11088
|
+
const nextPath = String(nextValue ?? '').trim();
|
|
11089
|
+
if (nextPath === lastPath) {
|
|
11090
|
+
return;
|
|
11091
|
+
}
|
|
11092
|
+
lastPath = nextPath;
|
|
11093
|
+
if (!nextPath) {
|
|
11094
|
+
if (this.normalizedProps.length > 0) {
|
|
11095
|
+
this.renderedProps = this.buildRenderedProps(this.normalizedProps);
|
|
11096
|
+
}
|
|
11097
|
+
return;
|
|
11098
|
+
}
|
|
11099
|
+
this.resolveAndApplyResourceSchemaFields(nextPath, scope, this.baselineReady);
|
|
11100
|
+
});
|
|
11101
|
+
}
|
|
11102
|
+
seedCatalogResponseFieldsCache(options) {
|
|
11103
|
+
for (const option of options) {
|
|
11104
|
+
if (!option?.responseFields?.length) {
|
|
11105
|
+
continue;
|
|
11106
|
+
}
|
|
11107
|
+
const key = this.normalizeResourceCacheKey(option.value);
|
|
11108
|
+
if (key && !this.resourceSchemaFieldsByPath.has(key)) {
|
|
11109
|
+
this.resourceSchemaFieldsByPath.set(key, option.responseFields
|
|
11110
|
+
.filter((field) => Boolean(field?.name && String(field.name).trim()))
|
|
11111
|
+
.map((field) => ({
|
|
11112
|
+
name: String(field.name).trim(),
|
|
11113
|
+
label: String(field.label ?? '').trim() || String(field.name).trim(),
|
|
11114
|
+
type: String(field.type ?? field.controlType ?? 'string').trim() || 'string',
|
|
11115
|
+
controlType: String(field.controlType ?? '').trim() || undefined,
|
|
11116
|
+
description: String(field.description ?? '').trim() || undefined,
|
|
11117
|
+
})));
|
|
11118
|
+
}
|
|
11119
|
+
}
|
|
11120
|
+
}
|
|
11121
|
+
resolveAndApplyResourceSchemaFields(resourcePath, scope, autoSuggestDefaults) {
|
|
11122
|
+
const trimmed = String(resourcePath ?? '').trim();
|
|
11123
|
+
if (!trimmed) {
|
|
11124
|
+
return;
|
|
11125
|
+
}
|
|
11126
|
+
const cached = this.getCachedFieldsForResource(trimmed);
|
|
11127
|
+
if (cached.length > 0) {
|
|
11128
|
+
if (autoSuggestDefaults) {
|
|
11129
|
+
this.applyAutoSuggestedDependentKeys(scope, cached);
|
|
11130
|
+
}
|
|
11131
|
+
if (this.normalizedProps.length > 0) {
|
|
11132
|
+
this.renderedProps = this.buildRenderedProps(this.normalizedProps);
|
|
11133
|
+
}
|
|
11134
|
+
}
|
|
11135
|
+
if (typeof this.resourceDiscovery?.getResourceSchemaFields !== 'function') {
|
|
11136
|
+
return;
|
|
11137
|
+
}
|
|
11138
|
+
const requestId = ++this.resourceSchemaRequestIdByScope[scope];
|
|
11139
|
+
const endpointKey = this.resolveDiscoveryEndpointKey();
|
|
11140
|
+
const request$ = endpointKey
|
|
11141
|
+
? this.resourceDiscovery.getResourceSchemaFields(trimmed, { endpointKey })
|
|
11142
|
+
: this.resourceDiscovery.getResourceSchemaFields(trimmed);
|
|
11143
|
+
request$
|
|
11144
|
+
.pipe(catchError(() => of([])), takeUntilDestroyed(this.destroyRef))
|
|
11145
|
+
.subscribe((fields) => {
|
|
11146
|
+
if (requestId !== this.resourceSchemaRequestIdByScope[scope]) {
|
|
11147
|
+
return;
|
|
11148
|
+
}
|
|
11149
|
+
const validFields = (fields || []).filter((field) => Boolean(field?.name && String(field.name).trim()));
|
|
11150
|
+
if (validFields.length > 0) {
|
|
11151
|
+
this.setCachedFieldsForResource(trimmed, validFields);
|
|
11152
|
+
if (autoSuggestDefaults) {
|
|
11153
|
+
this.applyAutoSuggestedDependentKeys(scope, validFields);
|
|
11154
|
+
}
|
|
11155
|
+
}
|
|
11156
|
+
if (this.normalizedProps.length > 0) {
|
|
11157
|
+
this.renderedProps = this.buildRenderedProps(this.normalizedProps);
|
|
11158
|
+
}
|
|
11159
|
+
});
|
|
11160
|
+
}
|
|
10897
11161
|
resolveCanonicalEditorProperties(controlType, controlTypeToken) {
|
|
10898
11162
|
const inlineProps = resolveInlineFilterEditorProperties(controlTypeToken, this.seed);
|
|
10899
11163
|
if (inlineProps?.length)
|
|
@@ -10988,6 +11252,10 @@ class FieldMetadataEditorComponent {
|
|
|
10988
11252
|
return undefined;
|
|
10989
11253
|
}
|
|
10990
11254
|
rebuildEditorForType(ct) {
|
|
11255
|
+
this.formRebuild$.next();
|
|
11256
|
+
if (this.initialControlType === undefined) {
|
|
11257
|
+
this.initialControlType = ct;
|
|
11258
|
+
}
|
|
10991
11259
|
this.controlType = ct;
|
|
10992
11260
|
let props = this.registry.getProperties(this.controlType);
|
|
10993
11261
|
const controlTypeToken = normalizeControlTypeToken(this.controlType);
|
|
@@ -11008,23 +11276,6 @@ class FieldMetadataEditorComponent {
|
|
|
11008
11276
|
}
|
|
11009
11277
|
}
|
|
11010
11278
|
}
|
|
11011
|
-
try {
|
|
11012
|
-
(console.log || console.debug)('[FieldMetadataEditor] init', {
|
|
11013
|
-
controlType: this.controlType,
|
|
11014
|
-
seedPreview: {
|
|
11015
|
-
resourcePath: this.seed?.resourcePath,
|
|
11016
|
-
optionLabelKey: this.seed?.optionLabelKey,
|
|
11017
|
-
optionValueKey: this.seed?.optionValueKey,
|
|
11018
|
-
filterCriteria: typeof this.seed?.filterCriteria === 'string'
|
|
11019
|
-
? this.seed?.filterCriteria
|
|
11020
|
-
: this.seed?.filterCriteria != null
|
|
11021
|
-
? JSON.stringify(this.seed?.filterCriteria)
|
|
11022
|
-
: undefined,
|
|
11023
|
-
},
|
|
11024
|
-
propsBefore: props.map((p) => p.name),
|
|
11025
|
-
});
|
|
11026
|
-
}
|
|
11027
|
-
catch { }
|
|
11028
11279
|
// Fallback de registro mínimo para facilitar execução inicial
|
|
11029
11280
|
if (!props.length && canonicalProps?.length) {
|
|
11030
11281
|
this.registry.register(this.controlType, canonicalProps);
|
|
@@ -11197,12 +11448,6 @@ class FieldMetadataEditorComponent {
|
|
|
11197
11448
|
props = treeSelectProperties;
|
|
11198
11449
|
}
|
|
11199
11450
|
}
|
|
11200
|
-
try {
|
|
11201
|
-
(console.log || console.debug)('[FieldMetadataEditor] props after registry resolution', {
|
|
11202
|
-
resolvedProps: props.map((p) => p.name),
|
|
11203
|
-
});
|
|
11204
|
-
}
|
|
11205
|
-
catch { }
|
|
11206
11451
|
// Inject controlType selector into 'Geral' group so users can change field type
|
|
11207
11452
|
try {
|
|
11208
11453
|
const typeProp = {
|
|
@@ -11221,28 +11466,6 @@ class FieldMetadataEditorComponent {
|
|
|
11221
11466
|
}
|
|
11222
11467
|
catch { }
|
|
11223
11468
|
this.normalizedProps = this.normalizer.normalize(props, this.seed);
|
|
11224
|
-
try {
|
|
11225
|
-
console.debug('[FieldMetadataEditor] normalized props', {
|
|
11226
|
-
propNames: this.normalizedProps.map((p) => p.name),
|
|
11227
|
-
defaults: this.normalizedProps
|
|
11228
|
-
.filter((p) => ['resourcePath', 'filterCriteria', 'optionLabelKey', 'optionValueKey'].includes(p.name))
|
|
11229
|
-
.map((p) => ({ name: p.name, defaultValue: p.defaultValue })),
|
|
11230
|
-
optionsDefault: (() => {
|
|
11231
|
-
const opt = this.normalizedProps.find((p) => p.name === 'options');
|
|
11232
|
-
if (!opt)
|
|
11233
|
-
return undefined;
|
|
11234
|
-
const v = opt.defaultValue;
|
|
11235
|
-
return {
|
|
11236
|
-
type: v == null ? 'null' : typeof v,
|
|
11237
|
-
preview: typeof v === 'string' ? String(v).slice(0, 80) : undefined,
|
|
11238
|
-
lengthOrCount: Array.isArray(v)
|
|
11239
|
-
? v.length
|
|
11240
|
-
: (typeof v === 'string' ? v.length : undefined),
|
|
11241
|
-
};
|
|
11242
|
-
})(),
|
|
11243
|
-
});
|
|
11244
|
-
}
|
|
11245
|
-
catch { }
|
|
11246
11469
|
// Backward-compatible bridge: if a config still uses providerOptions, hydrate from seed.options.
|
|
11247
11470
|
try {
|
|
11248
11471
|
const hasProviderOptions = this.normalizedProps.some((p) => p.name === 'providerOptions');
|
|
@@ -11255,10 +11478,6 @@ class FieldMetadataEditorComponent {
|
|
|
11255
11478
|
return String(seedOptions);
|
|
11256
11479
|
} })();
|
|
11257
11480
|
this.normalizedProps = this.normalizedProps.map((p) => p.name === 'providerOptions' && p.defaultValue === undefined ? { ...p, defaultValue: v } : p);
|
|
11258
|
-
(console.log || console.debug)('[FieldMetadataEditor] providerOptions default injected from seed.options', {
|
|
11259
|
-
type: typeof seedOptions,
|
|
11260
|
-
lengthOrCount: Array.isArray(seedOptions) ? seedOptions.length : (typeof seedOptions === 'string' ? seedOptions.length : undefined),
|
|
11261
|
-
});
|
|
11262
11481
|
}
|
|
11263
11482
|
}
|
|
11264
11483
|
catch { }
|
|
@@ -11305,61 +11524,19 @@ class FieldMetadataEditorComponent {
|
|
|
11305
11524
|
this.form.patchValue(subset, { emitEvent: false });
|
|
11306
11525
|
}
|
|
11307
11526
|
catch { }
|
|
11308
|
-
// Diagnostics for Rating seeding after applying seed → form
|
|
11309
|
-
try {
|
|
11310
|
-
const ctU = String(this.controlType || '').toUpperCase();
|
|
11311
|
-
if (ctU === String(FieldControlType.RATING)) {
|
|
11312
|
-
const pick = (k) => this.form.get(k)?.value;
|
|
11313
|
-
console.debug('[FieldMetadataEditor][RATING] seeded form values', {
|
|
11314
|
-
max: pick('max'),
|
|
11315
|
-
allowHalf: pick('allowHalf'),
|
|
11316
|
-
selection: pick('selection'),
|
|
11317
|
-
size: pick('size'),
|
|
11318
|
-
icon: pick('icon'),
|
|
11319
|
-
emptyIcon: pick('emptyIcon'),
|
|
11320
|
-
svgIcon: pick('svgIcon'),
|
|
11321
|
-
svgIconOutline: pick('svgIconOutline'),
|
|
11322
|
-
ratingColor: pick('ratingColor'),
|
|
11323
|
-
outlineColor: pick('outlineColor'),
|
|
11324
|
-
readonly: pick('readonly'),
|
|
11325
|
-
tabIndex: pick('tabIndex'),
|
|
11326
|
-
ariaLabel: pick('ariaLabel'),
|
|
11327
|
-
});
|
|
11328
|
-
}
|
|
11329
|
-
}
|
|
11330
|
-
catch { }
|
|
11331
|
-
}
|
|
11332
|
-
catch { }
|
|
11333
|
-
// Diagnostics: dump initial values for Rating editor
|
|
11334
|
-
try {
|
|
11335
|
-
const ctU = String(this.controlType || '').toUpperCase();
|
|
11336
|
-
if (ctU === String(FieldControlType.RATING)) {
|
|
11337
|
-
const pick = (k) => this.form.get(k)?.value;
|
|
11338
|
-
console.debug('[FieldMetadataEditor][RATING] initial form values', {
|
|
11339
|
-
max: pick('max'),
|
|
11340
|
-
allowHalf: pick('allowHalf'),
|
|
11341
|
-
selection: pick('selection'),
|
|
11342
|
-
size: pick('size'),
|
|
11343
|
-
icon: pick('icon'),
|
|
11344
|
-
emptyIcon: pick('emptyIcon'),
|
|
11345
|
-
svgIcon: pick('svgIcon'),
|
|
11346
|
-
svgIconOutline: pick('svgIconOutline'),
|
|
11347
|
-
ratingColor: pick('ratingColor'),
|
|
11348
|
-
outlineColor: pick('outlineColor'),
|
|
11349
|
-
readonly: pick('readonly'),
|
|
11350
|
-
tabIndex: pick('tabIndex'),
|
|
11351
|
-
ariaLabel: pick('ariaLabel'),
|
|
11352
|
-
});
|
|
11353
|
-
}
|
|
11354
11527
|
}
|
|
11355
11528
|
catch { }
|
|
11356
11529
|
// Capture baseline after seed is applied to avoid missing first user change
|
|
11357
11530
|
try {
|
|
11358
|
-
this.initialSnapshot
|
|
11531
|
+
if (this.initialSnapshot == null) {
|
|
11532
|
+
this.initialSnapshot = this.form.getRawValue();
|
|
11533
|
+
}
|
|
11359
11534
|
this.baselineReady = true;
|
|
11360
|
-
|
|
11535
|
+
const controlTypeChanged = this.initialControlType !== undefined &&
|
|
11536
|
+
this.controlType !== this.initialControlType;
|
|
11537
|
+
if (!controlTypeChanged && this.isDirty$.value) {
|
|
11361
11538
|
this.isDirty$.next(false);
|
|
11362
|
-
|
|
11539
|
+
}
|
|
11363
11540
|
}
|
|
11364
11541
|
catch {
|
|
11365
11542
|
this.baselineReady = true;
|
|
@@ -11367,12 +11544,12 @@ class FieldMetadataEditorComponent {
|
|
|
11367
11544
|
// track validity/dirty with snapshot-based detection (avoid false dirty on init)
|
|
11368
11545
|
this.isValid$.next(this.form.valid);
|
|
11369
11546
|
this.form.statusChanges
|
|
11370
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
11547
|
+
.pipe(takeUntil(this.formRebuild$), takeUntilDestroyed(this.destroyRef))
|
|
11371
11548
|
.subscribe(() => this.isValid$.next(this.form.valid));
|
|
11372
11549
|
this.setupGradientStopValidationSync();
|
|
11373
11550
|
this.setupCurrencyPresetSync();
|
|
11374
11551
|
this.form.valueChanges
|
|
11375
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
11552
|
+
.pipe(takeUntil(this.formRebuild$), takeUntilDestroyed(this.destroyRef))
|
|
11376
11553
|
.subscribe((val) => {
|
|
11377
11554
|
if (!this.baselineReady) {
|
|
11378
11555
|
// Ignore initial churn until baseline is captured
|
|
@@ -11382,12 +11559,10 @@ class FieldMetadataEditorComponent {
|
|
|
11382
11559
|
this.initialSnapshot = val;
|
|
11383
11560
|
if (this.isDirty$.value)
|
|
11384
11561
|
this.isDirty$.next(false);
|
|
11385
|
-
try {
|
|
11386
|
-
(console.log || console.debug)('[FieldMetadataEditor] baseline set from first stable value');
|
|
11387
|
-
}
|
|
11388
|
-
catch { }
|
|
11389
11562
|
return;
|
|
11390
11563
|
}
|
|
11564
|
+
const controlTypeChanged = this.initialControlType !== undefined &&
|
|
11565
|
+
this.controlType !== this.initialControlType;
|
|
11391
11566
|
let same = false;
|
|
11392
11567
|
try {
|
|
11393
11568
|
same = JSON.stringify(val) === JSON.stringify(this.initialSnapshot);
|
|
@@ -11395,15 +11570,15 @@ class FieldMetadataEditorComponent {
|
|
|
11395
11570
|
catch {
|
|
11396
11571
|
same = false;
|
|
11397
11572
|
}
|
|
11398
|
-
const dirty = !same;
|
|
11573
|
+
const dirty = controlTypeChanged || !same;
|
|
11399
11574
|
if (dirty !== this.isDirty$.value) {
|
|
11400
|
-
try {
|
|
11401
|
-
(console.log || console.debug)('[FieldMetadataEditor] dirty ->', dirty);
|
|
11402
|
-
}
|
|
11403
|
-
catch { }
|
|
11404
11575
|
this.isDirty$.next(dirty);
|
|
11405
11576
|
}
|
|
11406
11577
|
});
|
|
11578
|
+
if (this.discoveredResourceOptions.length > 0) {
|
|
11579
|
+
this.renderedProps = this.buildRenderedProps(this.normalizedProps);
|
|
11580
|
+
}
|
|
11581
|
+
this.loadCatalogResourceOptionsIfNeeded();
|
|
11407
11582
|
}
|
|
11408
11583
|
setupGradientStopValidationSync() {
|
|
11409
11584
|
const rows = new Map();
|
|
@@ -11419,7 +11594,7 @@ class FieldMetadataEditorComponent {
|
|
|
11419
11594
|
if (!source)
|
|
11420
11595
|
continue;
|
|
11421
11596
|
source.valueChanges
|
|
11422
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
11597
|
+
.pipe(takeUntil(this.formRebuild$), takeUntilDestroyed(this.destroyRef))
|
|
11423
11598
|
.subscribe(() => {
|
|
11424
11599
|
for (const targetField of fieldNames) {
|
|
11425
11600
|
if (targetField === sourceField)
|
|
@@ -11471,7 +11646,7 @@ class FieldMetadataEditorComponent {
|
|
|
11471
11646
|
VES: { locale: 'es-VE', decimalPlaces: 2, currencyPosition: 'before' },
|
|
11472
11647
|
};
|
|
11473
11648
|
currencyControl.valueChanges
|
|
11474
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
11649
|
+
.pipe(takeUntil(this.formRebuild$), takeUntilDestroyed(this.destroyRef))
|
|
11475
11650
|
.subscribe((value) => {
|
|
11476
11651
|
const code = String(value || '').toUpperCase();
|
|
11477
11652
|
const preset = presets[code];
|
|
@@ -11522,13 +11697,7 @@ class FieldMetadataEditorComponent {
|
|
|
11522
11697
|
// Sempre incluir controlType no patch quando presente
|
|
11523
11698
|
patch.controlType = this.controlType;
|
|
11524
11699
|
try {
|
|
11525
|
-
|
|
11526
|
-
}
|
|
11527
|
-
catch { }
|
|
11528
|
-
try {
|
|
11529
|
-
const fn = this.__hostApplyPatch;
|
|
11530
|
-
if (typeof fn === 'function')
|
|
11531
|
-
fn(patch);
|
|
11700
|
+
this.hostApplyPatch?.(patch);
|
|
11532
11701
|
}
|
|
11533
11702
|
catch { }
|
|
11534
11703
|
this.applied.emit(patch);
|
|
@@ -11549,10 +11718,6 @@ class FieldMetadataEditorComponent {
|
|
|
11549
11718
|
delete patch.providerOptions;
|
|
11550
11719
|
}
|
|
11551
11720
|
patch.controlType = this.controlType;
|
|
11552
|
-
try {
|
|
11553
|
-
console.debug('[FieldMetadataEditor] getSettingsValue() patch', patch);
|
|
11554
|
-
}
|
|
11555
|
-
catch { }
|
|
11556
11721
|
return patch;
|
|
11557
11722
|
}
|
|
11558
11723
|
/**
|
|
@@ -11834,6 +11999,9 @@ class FieldMetadataEditorComponent {
|
|
|
11834
11999
|
}
|
|
11835
12000
|
}
|
|
11836
12001
|
reset() {
|
|
12002
|
+
if (this.initialControlType !== undefined && this.controlType !== this.initialControlType) {
|
|
12003
|
+
this.rebuildEditorForType(this.initialControlType);
|
|
12004
|
+
}
|
|
11837
12005
|
const snapshot = this.cloneSnapshot(this.initialSnapshot ?? this.form.getRawValue());
|
|
11838
12006
|
this.form.reset(snapshot);
|
|
11839
12007
|
this.form.markAsPristine();
|
|
@@ -11860,11 +12028,321 @@ class FieldMetadataEditorComponent {
|
|
|
11860
12028
|
get isArrayEditor() {
|
|
11861
12029
|
return this.controlType === FieldControlType.ARRAY_INPUT;
|
|
11862
12030
|
}
|
|
12031
|
+
static ROOT_DEPENDENT_FIELD_KEY_PROPS = new Set([
|
|
12032
|
+
'optionValueKey',
|
|
12033
|
+
'optionLabelKey',
|
|
12034
|
+
'lookupIdKey',
|
|
12035
|
+
'lookupLabelKey',
|
|
12036
|
+
'lookupSubtitleKey',
|
|
12037
|
+
'parentIdKey',
|
|
12038
|
+
]);
|
|
12039
|
+
static OPTION_SOURCE_DEPENDENT_FIELD_KEY_PROPS = new Set([
|
|
12040
|
+
'optionSource.valuePropertyPath',
|
|
12041
|
+
'optionSource.labelPropertyPath',
|
|
12042
|
+
'optionSource.codePropertyPath',
|
|
12043
|
+
'optionSource.statusPropertyPath',
|
|
12044
|
+
'optionSource.subtitlePropertyPath',
|
|
12045
|
+
'optionSource.descriptionPropertyPath',
|
|
12046
|
+
]);
|
|
11863
12047
|
buildRenderedProps(props) {
|
|
11864
|
-
|
|
11865
|
-
|
|
12048
|
+
const base = !this.isArrayEditor
|
|
12049
|
+
? props || []
|
|
12050
|
+
: (props || []).filter((prop) => prop?.name !== 'array.itemSchema.fields');
|
|
12051
|
+
if (!this.discoveredResourceOptions.length &&
|
|
12052
|
+
this.resourceSchemaFieldsByPath.size === 0) {
|
|
12053
|
+
return base;
|
|
12054
|
+
}
|
|
12055
|
+
return base.map((prop) => {
|
|
12056
|
+
const resourceEnriched = this.enrichResourcePathProperty(prop);
|
|
12057
|
+
return this.enrichDependentFieldKeyProperty(resourceEnriched);
|
|
12058
|
+
});
|
|
12059
|
+
}
|
|
12060
|
+
enrichResourcePathProperty(prop) {
|
|
12061
|
+
if (!prop ||
|
|
12062
|
+
(prop.name !== 'resourcePath' && prop.name !== 'optionSource.resourcePath')) {
|
|
12063
|
+
return prop;
|
|
12064
|
+
}
|
|
12065
|
+
const optionSourceType = String(this.form?.get('optionSource.type')?.value ??
|
|
12066
|
+
this.seed?.optionSource?.type ??
|
|
12067
|
+
'')
|
|
12068
|
+
.trim()
|
|
12069
|
+
.toLowerCase();
|
|
12070
|
+
const useStatsIntent = prop.name === 'optionSource.resourcePath' && optionSourceType === 'stats';
|
|
12071
|
+
const filteredResources = this.discoveredResourceOptions.filter((item) => useStatsIntent
|
|
12072
|
+
? item.capabilities?.stats !== false
|
|
12073
|
+
: item.capabilities?.options !== false);
|
|
12074
|
+
if (!filteredResources.length) {
|
|
12075
|
+
return prop;
|
|
12076
|
+
}
|
|
12077
|
+
const currentValue = String(this.form?.get(prop.name)?.value ?? prop.defaultValue ?? '').trim();
|
|
12078
|
+
const matchedCatalogOption = currentValue
|
|
12079
|
+
? this.resourceDiscovery?.findCatalogResourceOption?.(currentValue, filteredResources)
|
|
12080
|
+
: null;
|
|
12081
|
+
const readOnlyLabel = this.i18n?.t('quickConnect.readOnly', undefined, 'Somente leitura', 'resourceDiscovery') ?? 'Somente leitura';
|
|
12082
|
+
const options = filteredResources.map((item) => {
|
|
12083
|
+
const readOnlySuffix = item.readOnly ? readOnlyLabel : null;
|
|
12084
|
+
const hasDistinctPath = Boolean(item.path && item.path !== item.label);
|
|
12085
|
+
const code = hasDistinctPath ? item.path : null;
|
|
12086
|
+
const badge = [item.group, readOnlySuffix]
|
|
12087
|
+
.filter((part) => Boolean(part))
|
|
12088
|
+
.join(' · ') || null;
|
|
12089
|
+
const description = String(item.description ?? '').trim() || null;
|
|
12090
|
+
return {
|
|
12091
|
+
value: item === matchedCatalogOption && currentValue ? currentValue : item.value,
|
|
12092
|
+
text: item.label,
|
|
12093
|
+
icon: item.icon ?? 'api',
|
|
12094
|
+
code,
|
|
12095
|
+
badge,
|
|
12096
|
+
description,
|
|
12097
|
+
searchTerms: [
|
|
12098
|
+
...item.searchTerms,
|
|
12099
|
+
...(readOnlySuffix ? [readOnlySuffix] : []),
|
|
12100
|
+
],
|
|
12101
|
+
};
|
|
12102
|
+
});
|
|
12103
|
+
if (currentValue && !matchedCatalogOption) {
|
|
12104
|
+
options.unshift({
|
|
12105
|
+
value: currentValue,
|
|
12106
|
+
text: currentValue,
|
|
12107
|
+
icon: 'link',
|
|
12108
|
+
code: null,
|
|
12109
|
+
badge: null,
|
|
12110
|
+
description: null,
|
|
12111
|
+
searchTerms: [currentValue],
|
|
12112
|
+
});
|
|
12113
|
+
}
|
|
12114
|
+
return {
|
|
12115
|
+
...prop,
|
|
12116
|
+
editorType: 'select',
|
|
12117
|
+
searchable: true,
|
|
12118
|
+
searchPlaceholderKey: 'fieldResource.searchPlaceholder',
|
|
12119
|
+
searchPlaceholder: 'Buscar por nome, domínio ou caminho',
|
|
12120
|
+
options,
|
|
12121
|
+
};
|
|
12122
|
+
}
|
|
12123
|
+
enrichDependentFieldKeyProperty(prop) {
|
|
12124
|
+
const propName = String(prop?.name ?? '').trim();
|
|
12125
|
+
const isRootDependent = FieldMetadataEditorComponent.ROOT_DEPENDENT_FIELD_KEY_PROPS.has(propName);
|
|
12126
|
+
const isOptionSourceDependent = FieldMetadataEditorComponent.OPTION_SOURCE_DEPENDENT_FIELD_KEY_PROPS.has(propName);
|
|
12127
|
+
if (!isRootDependent && !isOptionSourceDependent) {
|
|
12128
|
+
return prop;
|
|
12129
|
+
}
|
|
12130
|
+
const resourceControlPath = isOptionSourceDependent
|
|
12131
|
+
? 'optionSource.resourcePath'
|
|
12132
|
+
: 'resourcePath';
|
|
12133
|
+
const resourcePath = String(this.form?.get(resourceControlPath)?.value ??
|
|
12134
|
+
(isOptionSourceDependent
|
|
12135
|
+
? this.seed?.optionSource?.resourcePath
|
|
12136
|
+
: this.seed?.resourcePath) ??
|
|
12137
|
+
'').trim();
|
|
12138
|
+
if (!resourcePath) {
|
|
12139
|
+
return prop;
|
|
12140
|
+
}
|
|
12141
|
+
const fields = this.getCachedFieldsForResource(resourcePath);
|
|
12142
|
+
if (!fields.length) {
|
|
12143
|
+
return prop;
|
|
12144
|
+
}
|
|
12145
|
+
const currentValue = String(this.form?.get(propName)?.value ?? prop.defaultValue ?? '').trim();
|
|
12146
|
+
const options = fields.map((field) => {
|
|
12147
|
+
const fieldName = String(field.name ?? '').trim();
|
|
12148
|
+
const fieldLabel = String(field.label ?? '').trim();
|
|
12149
|
+
const hasDistinctLabel = Boolean(fieldLabel && fieldLabel !== fieldName);
|
|
12150
|
+
const typeHint = String(field.type || field.controlType || '').trim();
|
|
12151
|
+
const description = String(field.description ?? '').trim() || null;
|
|
12152
|
+
return {
|
|
12153
|
+
value: fieldName,
|
|
12154
|
+
text: hasDistinctLabel ? fieldLabel : fieldName,
|
|
12155
|
+
code: hasDistinctLabel ? fieldName : null,
|
|
12156
|
+
badge: typeHint || null,
|
|
12157
|
+
description,
|
|
12158
|
+
searchTerms: [
|
|
12159
|
+
fieldName,
|
|
12160
|
+
fieldLabel,
|
|
12161
|
+
typeHint,
|
|
12162
|
+
description ?? '',
|
|
12163
|
+
].filter(Boolean),
|
|
12164
|
+
};
|
|
12165
|
+
});
|
|
12166
|
+
if (currentValue &&
|
|
12167
|
+
!options.some((option) => option.value === currentValue)) {
|
|
12168
|
+
options.unshift({
|
|
12169
|
+
value: currentValue,
|
|
12170
|
+
text: currentValue,
|
|
12171
|
+
code: null,
|
|
12172
|
+
badge: null,
|
|
12173
|
+
description: null,
|
|
12174
|
+
searchTerms: [currentValue],
|
|
12175
|
+
});
|
|
12176
|
+
}
|
|
12177
|
+
return {
|
|
12178
|
+
...prop,
|
|
12179
|
+
editorType: 'select',
|
|
12180
|
+
searchable: true,
|
|
12181
|
+
searchPlaceholderKey: 'fieldResource.fieldSearchPlaceholder',
|
|
12182
|
+
searchPlaceholder: 'Buscar campo por nome ou rótulo',
|
|
12183
|
+
options,
|
|
12184
|
+
};
|
|
12185
|
+
}
|
|
12186
|
+
normalizeResourceCacheKey(resourcePath) {
|
|
12187
|
+
const raw = String(resourcePath ?? '').trim();
|
|
12188
|
+
if (!raw) {
|
|
12189
|
+
return '';
|
|
12190
|
+
}
|
|
12191
|
+
if (this.discoveredResourceOptions.length > 0) {
|
|
12192
|
+
const matched = this.resourceDiscovery?.findCatalogResourceOption?.(raw, this.discoveredResourceOptions);
|
|
12193
|
+
if (matched?.value) {
|
|
12194
|
+
return matched.value.trim().replace(/^\/+|\/+$/g, '');
|
|
12195
|
+
}
|
|
12196
|
+
}
|
|
12197
|
+
return raw.replace(/^\/+|\/+$/g, '').replace(/^api\/+/i, '');
|
|
12198
|
+
}
|
|
12199
|
+
getCachedFieldsForResource(resourcePath) {
|
|
12200
|
+
const key = this.normalizeResourceCacheKey(resourcePath);
|
|
12201
|
+
if (!key) {
|
|
12202
|
+
return [];
|
|
12203
|
+
}
|
|
12204
|
+
const direct = this.resourceSchemaFieldsByPath.get(key);
|
|
12205
|
+
if (direct?.length) {
|
|
12206
|
+
return direct;
|
|
12207
|
+
}
|
|
12208
|
+
const rawKey = String(resourcePath ?? '').trim().replace(/^\/+|\/+$/g, '');
|
|
12209
|
+
return this.resourceSchemaFieldsByPath.get(rawKey) ?? [];
|
|
12210
|
+
}
|
|
12211
|
+
setCachedFieldsForResource(resourcePath, fields) {
|
|
12212
|
+
const key = this.normalizeResourceCacheKey(resourcePath);
|
|
12213
|
+
if (key) {
|
|
12214
|
+
this.resourceSchemaFieldsByPath.set(key, fields);
|
|
12215
|
+
}
|
|
12216
|
+
const rawKey = String(resourcePath ?? '').trim().replace(/^\/+|\/+$/g, '');
|
|
12217
|
+
if (rawKey) {
|
|
12218
|
+
this.resourceSchemaFieldsByPath.set(rawKey, fields);
|
|
12219
|
+
}
|
|
12220
|
+
}
|
|
12221
|
+
applyAutoSuggestedDependentKeys(scope, fields) {
|
|
12222
|
+
if (!this.form || !fields.length) {
|
|
12223
|
+
return;
|
|
11866
12224
|
}
|
|
11867
|
-
|
|
12225
|
+
const fieldNameSet = new Set(fields.map((field) => String(field.name ?? '').trim()).filter(Boolean));
|
|
12226
|
+
const suggestions = this.selectSuggestedFieldKeys(fields);
|
|
12227
|
+
const targets = scope === 'optionSource'
|
|
12228
|
+
? [
|
|
12229
|
+
{
|
|
12230
|
+
path: 'optionSource.valuePropertyPath',
|
|
12231
|
+
suggested: suggestions.valueKey,
|
|
12232
|
+
},
|
|
12233
|
+
{
|
|
12234
|
+
path: 'optionSource.labelPropertyPath',
|
|
12235
|
+
suggested: suggestions.labelKey,
|
|
12236
|
+
},
|
|
12237
|
+
{
|
|
12238
|
+
path: 'optionSource.codePropertyPath',
|
|
12239
|
+
suggested: suggestions.codeKey,
|
|
12240
|
+
},
|
|
12241
|
+
{
|
|
12242
|
+
path: 'optionSource.statusPropertyPath',
|
|
12243
|
+
suggested: suggestions.statusKey,
|
|
12244
|
+
},
|
|
12245
|
+
]
|
|
12246
|
+
: [
|
|
12247
|
+
{ path: 'optionValueKey', suggested: suggestions.valueKey },
|
|
12248
|
+
{ path: 'optionLabelKey', suggested: suggestions.labelKey },
|
|
12249
|
+
{ path: 'lookupIdKey', suggested: suggestions.valueKey },
|
|
12250
|
+
{ path: 'lookupLabelKey', suggested: suggestions.labelKey },
|
|
12251
|
+
{ path: 'lookupSubtitleKey', suggested: suggestions.subtitleKey },
|
|
12252
|
+
];
|
|
12253
|
+
for (const target of targets) {
|
|
12254
|
+
const ctrl = this.form.get(target.path);
|
|
12255
|
+
if (!ctrl) {
|
|
12256
|
+
continue;
|
|
12257
|
+
}
|
|
12258
|
+
const current = String(ctrl.value ?? '').trim();
|
|
12259
|
+
if (!current || !fieldNameSet.has(current)) {
|
|
12260
|
+
const nextValue = target.suggested ?? '';
|
|
12261
|
+
if (current !== nextValue) {
|
|
12262
|
+
ctrl.setValue(nextValue);
|
|
12263
|
+
}
|
|
12264
|
+
}
|
|
12265
|
+
}
|
|
12266
|
+
}
|
|
12267
|
+
selectSuggestedFieldKeys(fields) {
|
|
12268
|
+
const names = fields
|
|
12269
|
+
.map((field) => String(field.name ?? '').trim())
|
|
12270
|
+
.filter(Boolean);
|
|
12271
|
+
const findExact = (candidates, exclude = new Set()) => {
|
|
12272
|
+
for (const candidate of candidates) {
|
|
12273
|
+
const lower = candidate.toLowerCase();
|
|
12274
|
+
const found = names.find((name) => !exclude.has(name) && name.toLowerCase() === lower);
|
|
12275
|
+
if (found) {
|
|
12276
|
+
return found;
|
|
12277
|
+
}
|
|
12278
|
+
}
|
|
12279
|
+
return null;
|
|
12280
|
+
};
|
|
12281
|
+
const valueKey = findExact([
|
|
12282
|
+
'id',
|
|
12283
|
+
'codigo',
|
|
12284
|
+
'code',
|
|
12285
|
+
'uuid',
|
|
12286
|
+
'key',
|
|
12287
|
+
'valor',
|
|
12288
|
+
'value',
|
|
12289
|
+
'matricula',
|
|
12290
|
+
'sigla',
|
|
12291
|
+
]) ??
|
|
12292
|
+
names.find((name) => /(?:Id|Codigo|Code)$/.test(name)) ??
|
|
12293
|
+
names[0] ??
|
|
12294
|
+
null;
|
|
12295
|
+
const usedForValue = new Set(valueKey ? [valueKey] : []);
|
|
12296
|
+
const labelKey = findExact([
|
|
12297
|
+
'nome',
|
|
12298
|
+
'name',
|
|
12299
|
+
'label',
|
|
12300
|
+
'descricao',
|
|
12301
|
+
'description',
|
|
12302
|
+
'titulo',
|
|
12303
|
+
'title',
|
|
12304
|
+
'razaosocial',
|
|
12305
|
+
'nomecompleto',
|
|
12306
|
+
'rotulo',
|
|
12307
|
+
'text',
|
|
12308
|
+
], usedForValue) ??
|
|
12309
|
+
fields
|
|
12310
|
+
.find((field) => !usedForValue.has(String(field.name ?? '').trim()) &&
|
|
12311
|
+
String(field.type ?? 'string').toLowerCase() === 'string')
|
|
12312
|
+
?.name?.trim() ??
|
|
12313
|
+
names.find((name) => !usedForValue.has(name)) ??
|
|
12314
|
+
valueKey;
|
|
12315
|
+
const codeKey = findExact([
|
|
12316
|
+
'codigo',
|
|
12317
|
+
'code',
|
|
12318
|
+
'sigla',
|
|
12319
|
+
'chave',
|
|
12320
|
+
'matricula',
|
|
12321
|
+
'referencia',
|
|
12322
|
+
]);
|
|
12323
|
+
const statusKey = findExact([
|
|
12324
|
+
'status',
|
|
12325
|
+
'situacao',
|
|
12326
|
+
'ativo',
|
|
12327
|
+
'active',
|
|
12328
|
+
'estado',
|
|
12329
|
+
'enabled',
|
|
12330
|
+
]);
|
|
12331
|
+
const usedForPrimary = new Set([valueKey, labelKey].filter((item) => Boolean(item)));
|
|
12332
|
+
const subtitleKey = findExact([
|
|
12333
|
+
'descricao',
|
|
12334
|
+
'description',
|
|
12335
|
+
'subtitulo',
|
|
12336
|
+
'subtitle',
|
|
12337
|
+
'resumo',
|
|
12338
|
+
'summary',
|
|
12339
|
+
'email',
|
|
12340
|
+
'cargo',
|
|
12341
|
+
'departamento',
|
|
12342
|
+
'sigla',
|
|
12343
|
+
'codigo',
|
|
12344
|
+
], usedForPrimary);
|
|
12345
|
+
return { valueKey, labelKey, codeKey, statusKey, subtitleKey };
|
|
11868
12346
|
}
|
|
11869
12347
|
cloneSnapshot(value) {
|
|
11870
12348
|
return this.cloneMetadataValue(value, new WeakMap());
|
|
@@ -11977,6 +12455,7 @@ class FieldMetadataEditorComponent {
|
|
|
11977
12455
|
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 });
|
|
11978
12456
|
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" }, providers: [
|
|
11979
12457
|
providePraxisMetadataEditorI18n(),
|
|
12458
|
+
providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
|
|
11980
12459
|
...providePraxisDynamicFieldsI18n(),
|
|
11981
12460
|
], ngImport: i0, template: `
|
|
11982
12461
|
@if (form) {
|
|
@@ -12007,6 +12486,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
12007
12486
|
],
|
|
12008
12487
|
providers: [
|
|
12009
12488
|
providePraxisMetadataEditorI18n(),
|
|
12489
|
+
providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
|
|
12010
12490
|
...providePraxisDynamicFieldsI18n(),
|
|
12011
12491
|
],
|
|
12012
12492
|
template: `
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisui/metadata-editor",
|
|
3
|
-
"version": "10.0.0-rc.
|
|
3
|
+
"version": "10.0.0-rc.33",
|
|
4
4
|
"description": "Metadata editor for Praxis UI fields and components with runtime integration.",
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"@angular/common": "^21.0.0",
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
"@angular/cdk": "^21.0.0",
|
|
9
9
|
"@angular/forms": "^21.0.0",
|
|
10
10
|
"@angular/material": "^21.0.0",
|
|
11
|
-
"@praxisui/ai": "^10.0.0-rc.
|
|
12
|
-
"@praxisui/core": "^10.0.0-rc.
|
|
13
|
-
"@praxisui/dynamic-fields": "^10.0.0-rc.
|
|
14
|
-
"@praxisui/settings-panel": "^10.0.0-rc.
|
|
11
|
+
"@praxisui/ai": "^10.0.0-rc.33",
|
|
12
|
+
"@praxisui/core": "^10.0.0-rc.33",
|
|
13
|
+
"@praxisui/dynamic-fields": "^10.0.0-rc.33",
|
|
14
|
+
"@praxisui/settings-panel": "^10.0.0-rc.33",
|
|
15
15
|
"rxjs": "~7.8.0"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
@@ -22,6 +22,11 @@ interface EditorProperty {
|
|
|
22
22
|
value: any;
|
|
23
23
|
text: string;
|
|
24
24
|
textKey?: string;
|
|
25
|
+
icon?: string;
|
|
26
|
+
code?: string | null;
|
|
27
|
+
badge?: string | null;
|
|
28
|
+
description?: string | null;
|
|
29
|
+
searchTerms?: string[];
|
|
25
30
|
}> | {
|
|
26
31
|
type: 'static' | 'http' | 'fn';
|
|
27
32
|
config?: any;
|
|
@@ -154,7 +159,10 @@ declare class FieldMetadataEditorComponent implements OnInit {
|
|
|
154
159
|
normalizedProps: any[];
|
|
155
160
|
renderedProps: any[];
|
|
156
161
|
private initialSnapshot;
|
|
162
|
+
private initialControlType?;
|
|
157
163
|
private baselineReady;
|
|
164
|
+
private readonly formRebuild$;
|
|
165
|
+
private readonly hostApplyPatch?;
|
|
158
166
|
typeOptions: Array<{
|
|
159
167
|
value: FieldControlType;
|
|
160
168
|
label: string;
|
|
@@ -163,9 +171,20 @@ declare class FieldMetadataEditorComponent implements OnInit {
|
|
|
163
171
|
isValid$: BehaviorSubject<boolean>;
|
|
164
172
|
isBusy$: BehaviorSubject<boolean>;
|
|
165
173
|
private readonly destroyRef;
|
|
174
|
+
private readonly resourceDiscovery;
|
|
175
|
+
private readonly i18n;
|
|
176
|
+
private discoveredResourceOptions;
|
|
166
177
|
private lastCurrencyPreset;
|
|
167
178
|
constructor(registry: ConfigRegistryService, normalizer: SchemaNormalizerService, factory: DynamicFormFactoryService, ctxValidators: ContextValidatorRegistryService);
|
|
179
|
+
private catalogLookupRequested;
|
|
180
|
+
private readonly resourceSchemaFieldsByPath;
|
|
181
|
+
private readonly resourceSchemaRequestIdByScope;
|
|
168
182
|
ngOnInit(): void;
|
|
183
|
+
private loadCatalogResourceOptionsIfNeeded;
|
|
184
|
+
private resolveDiscoveryEndpointKey;
|
|
185
|
+
private bindResourcePathSchemaSync;
|
|
186
|
+
private seedCatalogResponseFieldsCache;
|
|
187
|
+
private resolveAndApplyResourceSchemaFields;
|
|
169
188
|
private resolveCanonicalEditorProperties;
|
|
170
189
|
private rebuildEditorForType;
|
|
171
190
|
private setupGradientStopValidationSync;
|
|
@@ -210,7 +229,16 @@ declare class FieldMetadataEditorComponent implements OnInit {
|
|
|
210
229
|
onControlTypeChangeEvent(event: Event): void;
|
|
211
230
|
private withSharedFieldMetadataProperties;
|
|
212
231
|
get isArrayEditor(): boolean;
|
|
232
|
+
private static readonly ROOT_DEPENDENT_FIELD_KEY_PROPS;
|
|
233
|
+
private static readonly OPTION_SOURCE_DEPENDENT_FIELD_KEY_PROPS;
|
|
213
234
|
private buildRenderedProps;
|
|
235
|
+
private enrichResourcePathProperty;
|
|
236
|
+
private enrichDependentFieldKeyProperty;
|
|
237
|
+
private normalizeResourceCacheKey;
|
|
238
|
+
private getCachedFieldsForResource;
|
|
239
|
+
private setCachedFieldsForResource;
|
|
240
|
+
private applyAutoSuggestedDependentKeys;
|
|
241
|
+
private selectSuggestedFieldKeys;
|
|
214
242
|
private cloneSnapshot;
|
|
215
243
|
private cloneMetadataValue;
|
|
216
244
|
private canEmitSettingsValue;
|