@praxisui/metadata-editor 10.0.0-rc.37 → 10.0.0-rc.38
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.
package/README.md
CHANGED
|
@@ -166,6 +166,8 @@ Funcionalidades:
|
|
|
166
166
|
- Edição de dependentes, estratégia de merge, debounce, modo de carregamento (imediato/manual/respeitar loadOn).
|
|
167
167
|
- Mapeamento de filtros com dot‑path e valuePath por dependente, preview com exemplo gerado.
|
|
168
168
|
|
|
169
|
+
Abrir uma cascata e salvá-la sem alterações preserva mapeamentos de filtro não idênticos (entradas string ou objeto) e a extração `dependencyValuePath` em formato string ou mapa por dependência. Sugestões completam apenas valores ausentes. Durante a edição, as linhas de mapeamento representam o rascunho atual; um snapshot JSON anterior não sobrescreve alterações feitas na tabela. Remover uma dependência remove também o seu mapeamento e override de extração; adicioná-la novamente não recupera caminhos antigos. O preview ilustrativo não comprova a execução do filtro na API.
|
|
170
|
+
|
|
169
171
|
### Reactive Determination Diagnostics (somente leitura)
|
|
170
172
|
|
|
171
173
|
`ReactiveDeterminationDiagnosticsComponent` recebe o documento exato de
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"generatedAt": "2026-10-23T20:24:09.136Z",
|
|
4
4
|
"packageName": "@praxisui/metadata-editor",
|
|
5
|
-
"packageVersion": "10.0.0-rc.
|
|
5
|
+
"packageVersion": "10.0.0-rc.38",
|
|
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, ResourceDiscoveryService, normalizeControlTypeToken, providePraxisI18nConfig, RESOURCE_DISCOVERY_I18N_CONFIG, normalizeReactiveDeterminations, ComponentMetadataRegistry } from '@praxisui/core';
|
|
9
|
+
import { providePraxisI18n, PraxisI18nService, PraxisIconDirective, FieldControlType, INLINE_FILTER_CONTROL_TYPES, resolveInlineFilterControlType, ResourceDiscoveryService, mapCatalogFieldsToMetadata, humanizeCatalogFieldName, normalizeControlTypeToken, hasSemanticFieldCalibrationDiff, calibrateFieldMetadataFromSemantics, 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';
|
|
@@ -653,6 +653,8 @@ const METADATA_EDITOR_EN_US = {
|
|
|
653
653
|
'praxis.metadataEditor.colorPalette.moveDown': 'Move down',
|
|
654
654
|
'praxis.metadataEditor.colorPalette.remove': 'Remove',
|
|
655
655
|
'praxis.metadataEditor.colorPalette.invalid': 'Enter a valid CSS color or var(--token).',
|
|
656
|
+
'praxis.metadataEditor.smartCalibrate.action': 'Calibrate from schema',
|
|
657
|
+
'praxis.metadataEditor.smartCalibrate.tooltip': 'Automatically infers control type (Currency, CPF/CNPJ, Date, Email, Phone, Select, Textarea), humanized label, and corporate defaults from field name and metadata.',
|
|
656
658
|
};
|
|
657
659
|
|
|
658
660
|
const METADATA_EDITOR_PT_BR = {
|
|
@@ -905,6 +907,8 @@ const METADATA_EDITOR_PT_BR = {
|
|
|
905
907
|
'praxis.metadataEditor.colorPalette.moveDown': 'Mover para baixo',
|
|
906
908
|
'praxis.metadataEditor.colorPalette.remove': 'Remover',
|
|
907
909
|
'praxis.metadataEditor.colorPalette.invalid': 'Informe uma cor CSS válida ou var(--token).',
|
|
910
|
+
'praxis.metadataEditor.smartCalibrate.action': 'Calibrar pelo schema',
|
|
911
|
+
'praxis.metadataEditor.smartCalibrate.tooltip': 'Infere automaticamente o tipo de controle (Moeda, CPF/CNPJ, Data, E-mail, Telefone, Seleção, Texto longo), rótulo humanizado e padrões corporativos a partir do nome e metadados do campo.',
|
|
908
912
|
};
|
|
909
913
|
|
|
910
914
|
function createPraxisMetadataEditorI18nConfig(options = {}) {
|
|
@@ -10925,6 +10929,7 @@ class FieldMetadataEditorComponent {
|
|
|
10925
10929
|
renderedProps = [];
|
|
10926
10930
|
initialSnapshot = null;
|
|
10927
10931
|
initialControlType;
|
|
10932
|
+
initialSeed;
|
|
10928
10933
|
baselineReady = false;
|
|
10929
10934
|
formRebuild$ = new Subject();
|
|
10930
10935
|
hostApplyPatch;
|
|
@@ -11104,17 +11109,86 @@ class FieldMetadataEditorComponent {
|
|
|
11104
11109
|
if (!option?.responseFields?.length) {
|
|
11105
11110
|
continue;
|
|
11106
11111
|
}
|
|
11107
|
-
const
|
|
11108
|
-
|
|
11109
|
-
|
|
11110
|
-
|
|
11111
|
-
|
|
11112
|
-
|
|
11113
|
-
|
|
11114
|
-
|
|
11115
|
-
|
|
11112
|
+
const mappedMetas = mapCatalogFieldsToMetadata(option.responseFields);
|
|
11113
|
+
const metaByName = new Map(mappedMetas.map((meta) => [meta.name, meta]));
|
|
11114
|
+
const definitions = option.responseFields
|
|
11115
|
+
.filter((field) => Boolean(field?.name && String(field.name).trim()))
|
|
11116
|
+
.map((field) => {
|
|
11117
|
+
const name = String(field.name).trim();
|
|
11118
|
+
const rawLabel = String(field.label ?? '').trim();
|
|
11119
|
+
const label = rawLabel && rawLabel !== name
|
|
11120
|
+
? rawLabel
|
|
11121
|
+
: humanizeCatalogFieldName(name);
|
|
11122
|
+
const mapped = metaByName.get(name);
|
|
11123
|
+
return {
|
|
11124
|
+
name,
|
|
11125
|
+
label,
|
|
11126
|
+
type: String(field.type ?? field.controlType ?? 'string').trim() ||
|
|
11127
|
+
'string',
|
|
11128
|
+
controlType: String(field.controlType ?? mapped?.controlType ?? '').trim() ||
|
|
11129
|
+
undefined,
|
|
11116
11130
|
description: String(field.description ?? '').trim() || undefined,
|
|
11117
|
-
|
|
11131
|
+
...(typeof field.format === 'string' && field.format.trim()
|
|
11132
|
+
? { format: field.format.trim() }
|
|
11133
|
+
: {}),
|
|
11134
|
+
...(typeof field.required === 'boolean'
|
|
11135
|
+
? { required: field.required }
|
|
11136
|
+
: {}),
|
|
11137
|
+
...(typeof field.minimum === 'number'
|
|
11138
|
+
? { minimum: field.minimum }
|
|
11139
|
+
: {}),
|
|
11140
|
+
...(typeof field.maximum === 'number'
|
|
11141
|
+
? { maximum: field.maximum }
|
|
11142
|
+
: {}),
|
|
11143
|
+
...(typeof field.minLength === 'number'
|
|
11144
|
+
? { minLength: field.minLength }
|
|
11145
|
+
: {}),
|
|
11146
|
+
...(typeof field.maxLength === 'number'
|
|
11147
|
+
? { maxLength: field.maxLength }
|
|
11148
|
+
: {}),
|
|
11149
|
+
...(typeof field.pattern === 'string' && field.pattern.trim()
|
|
11150
|
+
? { pattern: field.pattern.trim() }
|
|
11151
|
+
: {}),
|
|
11152
|
+
...(Array.isArray(field.enumValues)
|
|
11153
|
+
? { enumValues: field.enumValues }
|
|
11154
|
+
: {}),
|
|
11155
|
+
...(mapped?.options?.length
|
|
11156
|
+
? {
|
|
11157
|
+
options: mapped.options.map((opt) => {
|
|
11158
|
+
const optLabel = String(opt.label ?? opt.text ?? opt.value);
|
|
11159
|
+
return {
|
|
11160
|
+
key: String(opt.value),
|
|
11161
|
+
value: String(opt.value),
|
|
11162
|
+
label: optLabel,
|
|
11163
|
+
text: opt.text ?? optLabel,
|
|
11164
|
+
};
|
|
11165
|
+
}),
|
|
11166
|
+
}
|
|
11167
|
+
: {}),
|
|
11168
|
+
};
|
|
11169
|
+
});
|
|
11170
|
+
if (!definitions.length) {
|
|
11171
|
+
continue;
|
|
11172
|
+
}
|
|
11173
|
+
const candidateKeys = new Set();
|
|
11174
|
+
for (const candidate of [option.value, option.path, option.resourceKey]) {
|
|
11175
|
+
const raw = String(candidate ?? '').trim();
|
|
11176
|
+
if (!raw)
|
|
11177
|
+
continue;
|
|
11178
|
+
const normalized = this.normalizeResourceCacheKey(raw);
|
|
11179
|
+
if (normalized)
|
|
11180
|
+
candidateKeys.add(normalized);
|
|
11181
|
+
const stripped = raw.replace(/^\/+|\/+$/g, '');
|
|
11182
|
+
if (stripped)
|
|
11183
|
+
candidateKeys.add(stripped);
|
|
11184
|
+
const withoutApi = stripped.replace(/^api\/+/i, '');
|
|
11185
|
+
if (withoutApi)
|
|
11186
|
+
candidateKeys.add(withoutApi);
|
|
11187
|
+
}
|
|
11188
|
+
for (const cacheKey of candidateKeys) {
|
|
11189
|
+
if (!this.resourceSchemaFieldsByPath.has(cacheKey)) {
|
|
11190
|
+
this.resourceSchemaFieldsByPath.set(cacheKey, definitions);
|
|
11191
|
+
}
|
|
11118
11192
|
}
|
|
11119
11193
|
}
|
|
11120
11194
|
}
|
|
@@ -11256,6 +11330,9 @@ class FieldMetadataEditorComponent {
|
|
|
11256
11330
|
if (this.initialControlType === undefined) {
|
|
11257
11331
|
this.initialControlType = ct;
|
|
11258
11332
|
}
|
|
11333
|
+
if (this.initialSeed === undefined) {
|
|
11334
|
+
this.initialSeed = this.cloneSnapshot(this.seed ?? {});
|
|
11335
|
+
}
|
|
11259
11336
|
this.controlType = ct;
|
|
11260
11337
|
let props = this.registry.getProperties(this.controlType);
|
|
11261
11338
|
const controlTypeToken = normalizeControlTypeToken(this.controlType);
|
|
@@ -11999,6 +12076,9 @@ class FieldMetadataEditorComponent {
|
|
|
11999
12076
|
}
|
|
12000
12077
|
}
|
|
12001
12078
|
reset() {
|
|
12079
|
+
if (this.initialSeed !== undefined) {
|
|
12080
|
+
this.seed = this.cloneSnapshot(this.initialSeed);
|
|
12081
|
+
}
|
|
12002
12082
|
if (this.initialControlType !== undefined && this.controlType !== this.initialControlType) {
|
|
12003
12083
|
this.rebuildEditorForType(this.initialControlType);
|
|
12004
12084
|
}
|
|
@@ -12019,6 +12099,79 @@ class FieldMetadataEditorComponent {
|
|
|
12019
12099
|
if (value != null)
|
|
12020
12100
|
this.onControlTypeChange(value);
|
|
12021
12101
|
}
|
|
12102
|
+
tx(key, fallback) {
|
|
12103
|
+
const namespacedKey = key.startsWith('praxis.metadataEditor.')
|
|
12104
|
+
? key
|
|
12105
|
+
: `praxis.metadataEditor.${key}`;
|
|
12106
|
+
return this.i18n?.t(namespacedKey, undefined, fallback) ?? fallback;
|
|
12107
|
+
}
|
|
12108
|
+
buildCurrentSemanticFieldSnapshot() {
|
|
12109
|
+
const currentRaw = this.form?.getRawValue?.() ?? {};
|
|
12110
|
+
const merged = {
|
|
12111
|
+
...(this.seed || {}),
|
|
12112
|
+
};
|
|
12113
|
+
for (const [k, v] of Object.entries(currentRaw)) {
|
|
12114
|
+
if (v !== undefined && v !== null && v !== '') {
|
|
12115
|
+
merged[k] = v;
|
|
12116
|
+
}
|
|
12117
|
+
}
|
|
12118
|
+
merged['name'] = String(currentRaw?.name ?? this.seed?.name ?? '').trim();
|
|
12119
|
+
merged['controlType'] = this.controlType;
|
|
12120
|
+
return merged;
|
|
12121
|
+
}
|
|
12122
|
+
canCalibrateFromSemantics() {
|
|
12123
|
+
const snapshot = this.buildCurrentSemanticFieldSnapshot();
|
|
12124
|
+
return hasSemanticFieldCalibrationDiff(snapshot);
|
|
12125
|
+
}
|
|
12126
|
+
calibrateFromSemantics() {
|
|
12127
|
+
const mergedField = this.buildCurrentSemanticFieldSnapshot();
|
|
12128
|
+
if (!hasSemanticFieldCalibrationDiff(mergedField)) {
|
|
12129
|
+
return;
|
|
12130
|
+
}
|
|
12131
|
+
const calibrated = calibrateFieldMetadataFromSemantics(mergedField);
|
|
12132
|
+
this.seed = {
|
|
12133
|
+
...(this.seed || {}),
|
|
12134
|
+
...calibrated,
|
|
12135
|
+
};
|
|
12136
|
+
const nextControlType = (calibrated.controlType ?? this.controlType);
|
|
12137
|
+
if (nextControlType && nextControlType !== this.controlType) {
|
|
12138
|
+
this.rebuildEditorForType(nextControlType);
|
|
12139
|
+
}
|
|
12140
|
+
const getByPath = (obj, path) => {
|
|
12141
|
+
if (!obj || typeof obj !== 'object' || !path)
|
|
12142
|
+
return undefined;
|
|
12143
|
+
return path.split('.').reduce((acc, part) => (acc == null ? undefined : acc[part]), obj);
|
|
12144
|
+
};
|
|
12145
|
+
const formPatch = {};
|
|
12146
|
+
for (const prop of this.normalizedProps || []) {
|
|
12147
|
+
const propName = String(prop?.name ?? '').trim();
|
|
12148
|
+
if (!propName)
|
|
12149
|
+
continue;
|
|
12150
|
+
const rawVal = getByPath(calibrated, propName);
|
|
12151
|
+
if (rawVal === undefined)
|
|
12152
|
+
continue;
|
|
12153
|
+
const controlVal = this.toEditorControlValue(prop, rawVal);
|
|
12154
|
+
if (controlVal !== undefined && controlVal !== null) {
|
|
12155
|
+
try {
|
|
12156
|
+
this.factory.setValueByPath(this.form, propName, controlVal);
|
|
12157
|
+
}
|
|
12158
|
+
catch { }
|
|
12159
|
+
if (!propName.includes('.') && this.form?.get(propName)) {
|
|
12160
|
+
formPatch[propName] = controlVal;
|
|
12161
|
+
}
|
|
12162
|
+
}
|
|
12163
|
+
}
|
|
12164
|
+
if (calibrated.options !== undefined &&
|
|
12165
|
+
!this.form?.get('options') &&
|
|
12166
|
+
this.form?.get('providerOptions')) {
|
|
12167
|
+
const prop = this.normalizedProps?.find((p) => p?.name === 'providerOptions');
|
|
12168
|
+
formPatch['providerOptions'] = this.toEditorControlValue(prop, calibrated.options);
|
|
12169
|
+
}
|
|
12170
|
+
if (Object.keys(formPatch).length > 0) {
|
|
12171
|
+
this.form.patchValue(formPatch, { emitEvent: true });
|
|
12172
|
+
}
|
|
12173
|
+
this.isDirty$.next(true);
|
|
12174
|
+
}
|
|
12022
12175
|
withSharedFieldMetadataProperties(props) {
|
|
12023
12176
|
const propNames = new Set(props.map((prop) => prop.name));
|
|
12024
12177
|
const sharedProps = [...fieldPresentationProperties, ...fieldSubmitProperties];
|
|
@@ -12323,6 +12476,7 @@ class FieldMetadataEditorComponent {
|
|
|
12323
12476
|
const statusKey = findExact([
|
|
12324
12477
|
'status',
|
|
12325
12478
|
'situacao',
|
|
12479
|
+
'situacaofuncional',
|
|
12326
12480
|
'ativo',
|
|
12327
12481
|
'active',
|
|
12328
12482
|
'estado',
|
|
@@ -12460,6 +12614,20 @@ class FieldMetadataEditorComponent {
|
|
|
12460
12614
|
], ngImport: i0, template: `
|
|
12461
12615
|
@if (form) {
|
|
12462
12616
|
<div class="p-3">
|
|
12617
|
+
@if (canCalibrateFromSemantics()) {
|
|
12618
|
+
<div class="d-flex justify-content-end mb-2">
|
|
12619
|
+
<button
|
|
12620
|
+
mat-stroked-button
|
|
12621
|
+
type="button"
|
|
12622
|
+
data-testid="field-metadata-smart-calibrate-btn"
|
|
12623
|
+
(click)="calibrateFromSemantics()"
|
|
12624
|
+
[matTooltip]="tx('smartCalibrate.tooltip', 'Infere automaticamente o tipo de controle, rótulo humanizado e padrões corporativos a partir do nome e metadados do campo.')"
|
|
12625
|
+
>
|
|
12626
|
+
<mat-icon aria-hidden="true">auto_fix_high</mat-icon>
|
|
12627
|
+
{{ tx('smartCalibrate.action', 'Calibrar pelo schema') }}
|
|
12628
|
+
</button>
|
|
12629
|
+
</div>
|
|
12630
|
+
}
|
|
12463
12631
|
<praxis-dynamic-editor-renderer
|
|
12464
12632
|
[properties]="renderedProps"
|
|
12465
12633
|
[form]="form"
|
|
@@ -12472,7 +12640,7 @@ class FieldMetadataEditorComponent {
|
|
|
12472
12640
|
}
|
|
12473
12641
|
</div>
|
|
12474
12642
|
}
|
|
12475
|
-
`, 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"] }] });
|
|
12643
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$2.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: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3$1.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { 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"] }] });
|
|
12476
12644
|
}
|
|
12477
12645
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: FieldMetadataEditorComponent, decorators: [{
|
|
12478
12646
|
type: Component,
|
|
@@ -12481,6 +12649,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
12481
12649
|
standalone: true,
|
|
12482
12650
|
imports: [
|
|
12483
12651
|
ReactiveFormsModule,
|
|
12652
|
+
MatButtonModule,
|
|
12653
|
+
MatIconModule,
|
|
12654
|
+
MatTooltipModule,
|
|
12484
12655
|
DynamicEditorRendererComponent,
|
|
12485
12656
|
ArrayItemSchemaFieldsEditorComponent
|
|
12486
12657
|
],
|
|
@@ -12492,6 +12663,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
12492
12663
|
template: `
|
|
12493
12664
|
@if (form) {
|
|
12494
12665
|
<div class="p-3">
|
|
12666
|
+
@if (canCalibrateFromSemantics()) {
|
|
12667
|
+
<div class="d-flex justify-content-end mb-2">
|
|
12668
|
+
<button
|
|
12669
|
+
mat-stroked-button
|
|
12670
|
+
type="button"
|
|
12671
|
+
data-testid="field-metadata-smart-calibrate-btn"
|
|
12672
|
+
(click)="calibrateFromSemantics()"
|
|
12673
|
+
[matTooltip]="tx('smartCalibrate.tooltip', 'Infere automaticamente o tipo de controle, rótulo humanizado e padrões corporativos a partir do nome e metadados do campo.')"
|
|
12674
|
+
>
|
|
12675
|
+
<mat-icon aria-hidden="true">auto_fix_high</mat-icon>
|
|
12676
|
+
{{ tx('smartCalibrate.action', 'Calibrar pelo schema') }}
|
|
12677
|
+
</button>
|
|
12678
|
+
</div>
|
|
12679
|
+
}
|
|
12495
12680
|
<praxis-dynamic-editor-renderer
|
|
12496
12681
|
[properties]="renderedProps"
|
|
12497
12682
|
[form]="form"
|
|
@@ -12677,10 +12862,13 @@ class CascadeRulesService {
|
|
|
12677
12862
|
dependencyLoadOnChange: null,
|
|
12678
12863
|
};
|
|
12679
12864
|
}
|
|
12680
|
-
/**
|
|
12865
|
+
/** Fill missing mappings and value paths without replacing authored values. */
|
|
12681
12866
|
suggest(rule, inferredValueKey) {
|
|
12682
|
-
const map = {};
|
|
12683
|
-
(rule.dependencyFields || []).forEach((dep) =>
|
|
12867
|
+
const map = { ...rule.dependencyFilterMap };
|
|
12868
|
+
(rule.dependencyFields || []).forEach((dep) => {
|
|
12869
|
+
if (!Object.prototype.hasOwnProperty.call(map, dep))
|
|
12870
|
+
map[dep] = dep;
|
|
12871
|
+
});
|
|
12684
12872
|
return {
|
|
12685
12873
|
...rule,
|
|
12686
12874
|
dependencyFilterMap: Object.keys(map).length ? map : rule.dependencyFilterMap,
|
|
@@ -12729,12 +12917,13 @@ class CascadeManagerTabComponent {
|
|
|
12729
12917
|
editLoadOn = 'respectLoadOn';
|
|
12730
12918
|
editDebounce = 150;
|
|
12731
12919
|
editValuePath = '';
|
|
12732
|
-
editFilterMapJson = '';
|
|
12733
12920
|
importMode = false;
|
|
12734
12921
|
importJson = '';
|
|
12735
12922
|
feedbackMessage = '';
|
|
12736
12923
|
feedbackTone = 'info';
|
|
12737
12924
|
editMapRows = [];
|
|
12925
|
+
editInitialFilterMap = {};
|
|
12926
|
+
editInitialValuePath = 'id';
|
|
12738
12927
|
setFeedback(message, tone = 'info') {
|
|
12739
12928
|
this.feedbackMessage = message;
|
|
12740
12929
|
this.feedbackTone = tone;
|
|
@@ -12836,12 +13025,9 @@ class CascadeManagerTabComponent {
|
|
|
12836
13025
|
this.editLoadOn = suggested.dependencyLoadOnChange || 'respectLoadOn';
|
|
12837
13026
|
this.editDebounce = suggested.dependencyDebounceMs ?? 150;
|
|
12838
13027
|
this.editValuePath = (typeof suggested.dependencyValuePath === 'string' ? suggested.dependencyValuePath : 'id') || '';
|
|
12839
|
-
|
|
12840
|
-
|
|
12841
|
-
|
|
12842
|
-
catch {
|
|
12843
|
-
this.editFilterMapJson = '';
|
|
12844
|
-
}
|
|
13028
|
+
this.editInitialFilterMap = structuredClone(suggested.dependencyFilterMap || {});
|
|
13029
|
+
this.editInitialValuePath = structuredClone(suggested.dependencyValuePath || 'id');
|
|
13030
|
+
this.editMapRows = [];
|
|
12845
13031
|
// Build mapping rows from dependencies
|
|
12846
13032
|
this.buildMapRowsFromDeps();
|
|
12847
13033
|
this.editing.set(true);
|
|
@@ -12851,20 +13037,45 @@ class CascadeManagerTabComponent {
|
|
|
12851
13037
|
saveEdit(name) {
|
|
12852
13038
|
// Build rule from editor
|
|
12853
13039
|
const deps = this.editDepList.filter((s) => !!s && s !== name);
|
|
12854
|
-
//
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
|
|
12862
|
-
|
|
12863
|
-
map = { ...map, ...fromJson };
|
|
13040
|
+
// The rows own the draft. The opening snapshot only preserves entry shape;
|
|
13041
|
+
// merging the original JSON after the rows would silently undo visual edits.
|
|
13042
|
+
const map = structuredClone(this.editInitialFilterMap);
|
|
13043
|
+
const dependenciesRemoved = (this.ruleFor(name)?.dependencyFields || [])
|
|
13044
|
+
.some((dep) => !deps.includes(dep));
|
|
13045
|
+
if (dependenciesRemoved) {
|
|
13046
|
+
for (const dep of Object.keys(map)) {
|
|
13047
|
+
if (!deps.includes(dep))
|
|
13048
|
+
delete map[dep];
|
|
12864
13049
|
}
|
|
12865
|
-
|
|
12866
|
-
|
|
12867
|
-
|
|
13050
|
+
}
|
|
13051
|
+
for (const row of this.editMapRows) {
|
|
13052
|
+
if (!deps.includes(row.dep))
|
|
13053
|
+
continue;
|
|
13054
|
+
const initial = this.editInitialFilterMap[row.dep];
|
|
13055
|
+
const initialPath = this.initialRowValuePath(row.dep);
|
|
13056
|
+
if (typeof initial === 'object' && initial !== null) {
|
|
13057
|
+
const entry = { ...initial, key: row.key || row.dep };
|
|
13058
|
+
if (row.valuePath !== initialPath) {
|
|
13059
|
+
if (row.valuePath)
|
|
13060
|
+
entry.valuePath = row.valuePath;
|
|
13061
|
+
else
|
|
13062
|
+
delete entry.valuePath;
|
|
13063
|
+
}
|
|
13064
|
+
map[row.dep] = entry;
|
|
13065
|
+
}
|
|
13066
|
+
else {
|
|
13067
|
+
map[row.dep] = row.valuePath && row.valuePath !== initialPath
|
|
13068
|
+
? { key: row.key || row.dep, valuePath: row.valuePath }
|
|
13069
|
+
: row.key || row.dep;
|
|
13070
|
+
}
|
|
13071
|
+
}
|
|
13072
|
+
const valuePaths = this.buildValuePathOverrides(deps);
|
|
13073
|
+
// Removed dependencies must lose their extraction overrides as well as their
|
|
13074
|
+
// filter mappings, otherwise re-adding one resurrects stale extraction state.
|
|
13075
|
+
if (dependenciesRemoved && typeof valuePaths === 'object') {
|
|
13076
|
+
for (const dep of Object.keys(valuePaths)) {
|
|
13077
|
+
if (!deps.includes(dep))
|
|
13078
|
+
delete valuePaths[dep];
|
|
12868
13079
|
}
|
|
12869
13080
|
}
|
|
12870
13081
|
this.setFeedback('');
|
|
@@ -12874,7 +13085,7 @@ class CascadeManagerTabComponent {
|
|
|
12874
13085
|
enableDependencyCascade: this.editEnable,
|
|
12875
13086
|
resetOnDependentChange: this.editReset,
|
|
12876
13087
|
dependencyFilterMap: map,
|
|
12877
|
-
dependencyValuePath:
|
|
13088
|
+
dependencyValuePath: valuePaths,
|
|
12878
13089
|
dependencyMergeStrategy: this.editMerge,
|
|
12879
13090
|
dependencyDebounceMs: this.editDebounce ?? 150,
|
|
12880
13091
|
dependencyLoadOnChange: this.editLoadOn,
|
|
@@ -12989,33 +13200,65 @@ class CascadeManagerTabComponent {
|
|
|
12989
13200
|
this.editLoadOn = 'respectLoadOn';
|
|
12990
13201
|
this.editDebounce = 150;
|
|
12991
13202
|
this.editValuePath = 'id';
|
|
13203
|
+
this.editInitialFilterMap = {};
|
|
13204
|
+
this.editInitialValuePath = 'id';
|
|
13205
|
+
this.editMapRows = [];
|
|
12992
13206
|
this.buildMapRowsFromDeps();
|
|
12993
13207
|
this.editing.set(true);
|
|
12994
13208
|
}
|
|
12995
13209
|
buildMapRowsFromDeps() {
|
|
12996
13210
|
const deps = this.editDepList;
|
|
12997
|
-
const
|
|
12998
|
-
return JSON.parse(this.editFilterMapJson || '{}');
|
|
12999
|
-
}
|
|
13000
|
-
catch {
|
|
13001
|
-
return {};
|
|
13002
|
-
} })();
|
|
13211
|
+
const current = new Map(this.editMapRows.map((row) => [row.dep, row]));
|
|
13003
13212
|
this.editMapRows = deps.map((dep) => {
|
|
13004
|
-
const
|
|
13005
|
-
if (
|
|
13006
|
-
return
|
|
13007
|
-
|
|
13008
|
-
|
|
13009
|
-
return { dep, key
|
|
13213
|
+
const edited = current.get(dep);
|
|
13214
|
+
if (edited)
|
|
13215
|
+
return edited;
|
|
13216
|
+
const entry = this.editInitialFilterMap[dep];
|
|
13217
|
+
const key = typeof entry === 'string' ? entry : entry?.key || dep;
|
|
13218
|
+
return { dep, key, valuePath: this.initialRowValuePath(dep) };
|
|
13010
13219
|
});
|
|
13011
13220
|
}
|
|
13221
|
+
initialRowValuePath(dep) {
|
|
13222
|
+
const entry = this.editInitialFilterMap[dep];
|
|
13223
|
+
if (typeof entry === 'object' && entry?.valuePath)
|
|
13224
|
+
return entry.valuePath;
|
|
13225
|
+
return typeof this.editInitialValuePath === 'object'
|
|
13226
|
+
? this.editInitialValuePath[dep]
|
|
13227
|
+
: undefined;
|
|
13228
|
+
}
|
|
13012
13229
|
buildValuePathOverrides(deps) {
|
|
13013
|
-
|
|
13014
|
-
|
|
13015
|
-
|
|
13016
|
-
|
|
13017
|
-
const
|
|
13018
|
-
|
|
13230
|
+
const initialDefault = typeof this.editInitialValuePath === 'string'
|
|
13231
|
+
? this.editInitialValuePath
|
|
13232
|
+
: 'id';
|
|
13233
|
+
const defaultChanged = this.editValuePath !== initialDefault;
|
|
13234
|
+
const pathsChanged = this.editMapRows.some((row) => row.valuePath !== this.initialRowValuePath(row.dep));
|
|
13235
|
+
if (!defaultChanged && !pathsChanged)
|
|
13236
|
+
return structuredClone(this.editInitialValuePath);
|
|
13237
|
+
const wasRecord = typeof this.editInitialValuePath === 'object';
|
|
13238
|
+
if (!pathsChanged && !wasRecord)
|
|
13239
|
+
return this.editValuePath || 'id';
|
|
13240
|
+
// A specific extraction path must not erase the default/overrides used by
|
|
13241
|
+
// other dependencies. Unchanged documents keep their original shape above.
|
|
13242
|
+
const record = typeof this.editInitialValuePath === 'object'
|
|
13243
|
+
? { ...this.editInitialValuePath }
|
|
13244
|
+
: {};
|
|
13245
|
+
for (const dep of deps) {
|
|
13246
|
+
const row = this.editMapRows.find((candidate) => candidate.dep === dep);
|
|
13247
|
+
if (row && row.valuePath !== this.initialRowValuePath(dep)) {
|
|
13248
|
+
record[dep] = row.valuePath || this.editValuePath || 'id';
|
|
13249
|
+
}
|
|
13250
|
+
else if (typeof this.editInitialValuePath === 'object') {
|
|
13251
|
+
const existing = this.editInitialValuePath[dep];
|
|
13252
|
+
if (existing !== undefined)
|
|
13253
|
+
record[dep] = existing;
|
|
13254
|
+
else if (defaultChanged)
|
|
13255
|
+
record[dep] = this.editValuePath || 'id';
|
|
13256
|
+
}
|
|
13257
|
+
else {
|
|
13258
|
+
record[dep] = this.editValuePath || 'id';
|
|
13259
|
+
}
|
|
13260
|
+
}
|
|
13261
|
+
return record;
|
|
13019
13262
|
}
|
|
13020
13263
|
buildPreview() {
|
|
13021
13264
|
try {
|
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.38",
|
|
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.38",
|
|
12
|
+
"@praxisui/core": "^10.0.0-rc.38",
|
|
13
|
+
"@praxisui/dynamic-fields": "^10.0.0-rc.38",
|
|
14
|
+
"@praxisui/settings-panel": "^10.0.0-rc.38",
|
|
15
15
|
"rxjs": "~7.8.0"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
@@ -160,6 +160,7 @@ declare class FieldMetadataEditorComponent implements OnInit {
|
|
|
160
160
|
renderedProps: any[];
|
|
161
161
|
private initialSnapshot;
|
|
162
162
|
private initialControlType?;
|
|
163
|
+
private initialSeed?;
|
|
163
164
|
private baselineReady;
|
|
164
165
|
private readonly formRebuild$;
|
|
165
166
|
private readonly hostApplyPatch?;
|
|
@@ -227,6 +228,10 @@ declare class FieldMetadataEditorComponent implements OnInit {
|
|
|
227
228
|
reset(): void;
|
|
228
229
|
onControlTypeChange(next: FieldControlType): void;
|
|
229
230
|
onControlTypeChangeEvent(event: Event): void;
|
|
231
|
+
tx(key: string, fallback: string): string;
|
|
232
|
+
private buildCurrentSemanticFieldSnapshot;
|
|
233
|
+
canCalibrateFromSemantics(): boolean;
|
|
234
|
+
calibrateFromSemantics(): void;
|
|
230
235
|
private withSharedFieldMetadataProperties;
|
|
231
236
|
get isArrayEditor(): boolean;
|
|
232
237
|
private static readonly ROOT_DEPENDENT_FIELD_KEY_PROPS;
|
|
@@ -298,7 +303,7 @@ declare class CascadeRulesService {
|
|
|
298
303
|
hydrateRule(field: FieldDefinition | FieldMetadata): CascadeRuleViewModel | null;
|
|
299
304
|
dehydratePatch(rule: CascadeRuleViewModel): Partial<FieldDefinition>;
|
|
300
305
|
clearPatch(): Partial<FieldDefinition>;
|
|
301
|
-
/**
|
|
306
|
+
/** Fill missing mappings and value paths without replacing authored values. */
|
|
302
307
|
suggest(rule: CascadeRuleViewModel, inferredValueKey?: string): CascadeRuleViewModel;
|
|
303
308
|
private normalizeDependencyFields;
|
|
304
309
|
static ɵfac: i0.ɵɵFactoryDeclaration<CascadeRulesService, never>;
|
|
@@ -327,7 +332,6 @@ declare class CascadeManagerTabComponent implements OnChanges {
|
|
|
327
332
|
editLoadOn: 'respectLoadOn' | 'immediate' | 'manual';
|
|
328
333
|
editDebounce: number | null;
|
|
329
334
|
editValuePath: string;
|
|
330
|
-
editFilterMapJson: string;
|
|
331
335
|
importMode: boolean;
|
|
332
336
|
importJson: string;
|
|
333
337
|
feedbackMessage: string;
|
|
@@ -337,6 +341,8 @@ declare class CascadeManagerTabComponent implements OnChanges {
|
|
|
337
341
|
key: string;
|
|
338
342
|
valuePath?: string;
|
|
339
343
|
}>;
|
|
344
|
+
private editInitialFilterMap;
|
|
345
|
+
private editInitialValuePath;
|
|
340
346
|
private setFeedback;
|
|
341
347
|
constructor(svc: CascadeRulesService);
|
|
342
348
|
ngOnInit(): void;
|
|
@@ -366,6 +372,7 @@ declare class CascadeManagerTabComponent implements OnChanges {
|
|
|
366
372
|
applyImport(): void;
|
|
367
373
|
startTemplate(name: string, tpl: 'rota-destino' | 'rota-destino-propriedade'): void;
|
|
368
374
|
buildMapRowsFromDeps(): void;
|
|
375
|
+
private initialRowValuePath;
|
|
369
376
|
buildValuePathOverrides(deps: string[]): string | Record<string, string>;
|
|
370
377
|
buildPreview(): string;
|
|
371
378
|
copyPreview(): void;
|