@praxisui/dynamic-form 9.0.0-beta.41 → 9.0.0-beta.43
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 +13 -0
- package/ai/component-registry.json +31 -9
- package/docs/schema-driven-layout-materialization-rfc.md +7 -1
- package/fesm2022/praxisui-dynamic-form.mjs +59 -7
- package/package.json +8 -8
- package/src/lib/praxis-dynamic-form.json-api.md +7 -1
- package/types/praxisui-dynamic-form.d.ts +2 -0
|
@@ -113,6 +113,7 @@ export interface DynamicFormDetailSummaryPolicy {
|
|
|
113
113
|
groupPriority?: Record<string, number>;
|
|
114
114
|
columns?: number;
|
|
115
115
|
responsiveColumns?: Partial<Record<'xs' | 'sm' | 'md' | 'lg' | 'xl', number>>;
|
|
116
|
+
widthPrecedence?: 'schema' | 'summary';
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
export interface DynamicFormLayoutPolicy {
|
|
@@ -160,7 +161,9 @@ Expected behavior:
|
|
|
160
161
|
- Avoid card grids and decorative section containers as the default for operational detail screens.
|
|
161
162
|
- Preserve stable section ids derived from deterministic group keys.
|
|
162
163
|
- In `mode="view"`, activate presentation rendering when the host does not explicitly provide `presentationModeGlobal`; an explicit `false` keeps the same schema in traditional read-only controls.
|
|
163
|
-
- Honor explicit schema `x-ui.width` as 12-column spans so migrated operational detail screens can use dense fieldset rows. Preserve full-width rows when width is absent.
|
|
164
|
+
- Honor explicit schema `x-ui.width` as 12-column spans by default so migrated operational detail screens can use dense fieldset rows. Preserve full-width rows when width is absent.
|
|
165
|
+
- Let `layoutPolicy.detailSummary.widthPrecedence: 'summary'` make `detailSummary.columns`/`responsiveColumns` override explicit `x-ui.width` only for the compact read-only summary surface.
|
|
166
|
+
- Treat `widthPrecedence: 'summary'` as a curated-summary density tool: long narrative, legal, multiline or audit-heavy values should stay schema-width owned or be moved out of the compact summary through include/exclude policies.
|
|
164
167
|
- Derive compact section header icons from semantic group roles (`identity`, `rules`, `markers`) while keeping field-level icons owned by existing schema metadata.
|
|
165
168
|
- Allow `layoutPolicy.detailSummary` to select/exclude fields and groups, cap the first compact panel with `maxFields`, and sort with field/group priority when the schema publishes summary/detail roles or priorities.
|
|
166
169
|
- Keep excluded summary fields out of generated rows and mark them hidden in generated field metadata for that transient compact surface, without mutating backend schema semantics.
|
|
@@ -192,6 +195,9 @@ const detailLayoutPolicy: DynamicFormLayoutPolicy = {
|
|
|
192
195
|
mnemonico: 1,
|
|
193
196
|
ativo: 2,
|
|
194
197
|
},
|
|
198
|
+
columns: 3,
|
|
199
|
+
responsiveColumns: { xs: 1, sm: 1, md: 2, lg: 3, xl: 3 },
|
|
200
|
+
widthPrecedence: 'summary',
|
|
195
201
|
},
|
|
196
202
|
};
|
|
197
203
|
```
|
|
@@ -5952,7 +5952,7 @@ const PRAXIS_DYNAMIC_FORM_AUTHORING_MANIFEST = {
|
|
|
5952
5952
|
{ name: 'readUrl', type: 'string', description: 'Endpoint canonico de leitura para surfaces VIEW/READ_PROJECTION' },
|
|
5953
5953
|
{ name: 'mode', type: 'string', allowedValues: ['create', 'edit', 'view'], description: 'Modo de operação' },
|
|
5954
5954
|
{ name: 'generatedLayoutPreset', type: 'string', allowedValues: ['default', 'compactPresentation'], description: 'Preset usado apenas ao gerar a configuracao inicial a partir de metadata/schema; nao reprocessa layouts persistidos ou autorados.' },
|
|
5955
|
-
{ name: 'layoutPolicy', type: 'DynamicFormLayoutPolicy', description: 'Politica opt-in para layout schema-driven transient. Use source=schema apenas quando o backend schema for a fonte canonica do agrupamento; use detailSummary para seleção de campos e densidade responsiva do resumo read-only compacto sem FormConfig.sections locais.' },
|
|
5955
|
+
{ name: 'layoutPolicy', type: 'DynamicFormLayoutPolicy', description: 'Politica opt-in para layout schema-driven transient. Use source=schema apenas quando o backend schema for a fonte canonica do agrupamento; use detailSummary para seleção de campos e densidade responsiva do resumo read-only compacto sem FormConfig.sections locais. Quando o resumo precisar vencer x-ui.width publicado para outras surfaces, use detailSummary.widthPrecedence=summary.' },
|
|
5956
5956
|
{ name: 'fieldIconPolicy', type: 'string', allowedValues: ['all', 'presentation-only', 'none'], description: 'Politica visual do host para icones prefix/suffix de campos vindos da metadata. Use presentation-only para manter icones decorativos em apresentacao e ocultar em controles tradicionais.' },
|
|
5957
5957
|
{ name: 'enableCustomization', type: 'boolean', description: 'Habilita edição visual' }
|
|
5958
5958
|
],
|
|
@@ -12207,11 +12207,19 @@ class PraxisDynamicForm {
|
|
|
12207
12207
|
}
|
|
12208
12208
|
return;
|
|
12209
12209
|
}
|
|
12210
|
+
const configuredReadUrl = String(this.readUrl || '').trim();
|
|
12211
|
+
if (!configuredReadUrl && !String(this.resourcePath || '').trim()) {
|
|
12212
|
+
if (this.hydrateEntityFromInitialValue()) {
|
|
12213
|
+
return;
|
|
12214
|
+
}
|
|
12215
|
+
this.entityHydrationPending = false;
|
|
12216
|
+
this.warnLog('Skipping entity load because no readUrl or resourcePath is configured.', { id: this.pendingEntityId, formId: this.formId }, { actionId: 'entity.load.skip', throttleKey: 'praxis-dynamic-form:entity-load-without-resource' });
|
|
12217
|
+
return;
|
|
12218
|
+
}
|
|
12210
12219
|
this.loadedEntityId = this.pendingEntityId;
|
|
12211
12220
|
this.entityHydrationPending = true;
|
|
12212
12221
|
const httpContext = new HttpContext().set(PRAXIS_LOADING_CTX, this.buildLoadingContext('data', 'Carregando dados...', false));
|
|
12213
12222
|
this.emitLoadingState('data', 'loading', 'Carregando dados do formulário...');
|
|
12214
|
-
const configuredReadUrl = String(this.readUrl || '').trim();
|
|
12215
12223
|
const request$ = configuredReadUrl
|
|
12216
12224
|
? this.http
|
|
12217
12225
|
.get(this.resolveReadHref(configuredReadUrl), {
|
|
@@ -12248,6 +12256,22 @@ class PraxisDynamicForm {
|
|
|
12248
12256
|
},
|
|
12249
12257
|
});
|
|
12250
12258
|
}
|
|
12259
|
+
hydrateEntityFromInitialValue() {
|
|
12260
|
+
const normalized = this.getNormalizedInitialValue();
|
|
12261
|
+
if (!normalized) {
|
|
12262
|
+
return false;
|
|
12263
|
+
}
|
|
12264
|
+
this.loadedEntityId = this.pendingEntityId;
|
|
12265
|
+
this.loadedEntityData = normalized;
|
|
12266
|
+
this.entityHydrationPending = false;
|
|
12267
|
+
if (Object.keys(this.form.controls || {}).length) {
|
|
12268
|
+
this.form.patchValue(normalized, { emitEvent: false });
|
|
12269
|
+
}
|
|
12270
|
+
this.hydratedEntityId = this.pendingEntityId;
|
|
12271
|
+
this.debugLog('[PDF] loadEntity:initialValue', { id: this.pendingEntityId });
|
|
12272
|
+
this.emitLoadingState('data', 'success', 'Dados carregados.');
|
|
12273
|
+
return true;
|
|
12274
|
+
}
|
|
12251
12275
|
buildFormFromConfig(options = { reason: 'initialization' }) {
|
|
12252
12276
|
this.runtimeFieldMetadata = null;
|
|
12253
12277
|
const fieldMetadata = this.getRuntimeFieldMetadata();
|
|
@@ -13985,9 +14009,9 @@ class PraxisDynamicForm {
|
|
|
13985
14009
|
const byName = new Map(fieldMetadata.map((f) => [f.name, f]));
|
|
13986
14010
|
const ordered = [];
|
|
13987
14011
|
for (const name of fieldNames) {
|
|
13988
|
-
if (this.fieldVisibility[name] === false)
|
|
13989
|
-
continue;
|
|
13990
14012
|
const meta = byName.get(name);
|
|
14013
|
+
if (!this.isFieldRenderableInLayout(name, meta))
|
|
14014
|
+
continue;
|
|
13991
14015
|
if (meta) {
|
|
13992
14016
|
const overrides = this.fieldRuleProps[name] || {};
|
|
13993
14017
|
if (Object.keys(overrides).length > 0) {
|
|
@@ -14010,6 +14034,18 @@ class PraxisDynamicForm {
|
|
|
14010
14034
|
this.columnFieldsCache.set(key, { signature, value: ordered });
|
|
14011
14035
|
return ordered;
|
|
14012
14036
|
}
|
|
14037
|
+
isFieldRenderableInLayout(fieldName, field) {
|
|
14038
|
+
const meta = field ??
|
|
14039
|
+
this.getRuntimeFieldMetadata().find((candidate) => candidate.name === fieldName) ??
|
|
14040
|
+
null;
|
|
14041
|
+
if (!meta) {
|
|
14042
|
+
return false;
|
|
14043
|
+
}
|
|
14044
|
+
if (this.fieldVisibility[fieldName] !== undefined) {
|
|
14045
|
+
return this.fieldVisibility[fieldName] === true;
|
|
14046
|
+
}
|
|
14047
|
+
return this.resolveFieldVisibility(meta, this.fieldRuleProps[fieldName] || null);
|
|
14048
|
+
}
|
|
14013
14049
|
getRichContentLayoutItemDocument(item) {
|
|
14014
14050
|
return item.kind === 'richContent' && item.document?.kind === 'praxis.rich-content'
|
|
14015
14051
|
? item.document
|
|
@@ -14029,9 +14065,14 @@ class PraxisDynamicForm {
|
|
|
14029
14065
|
return this.resolveRuleVisibility(this.getVisualBlockRuleProps(item.id), true);
|
|
14030
14066
|
}
|
|
14031
14067
|
getColumnFieldsSignature(column) {
|
|
14068
|
+
const fieldMetadata = this.getRuntimeFieldMetadata();
|
|
14069
|
+
const byName = new Map(fieldMetadata.map((field) => [field.name, field]));
|
|
14032
14070
|
return getFormColumnFieldNames(column)
|
|
14033
14071
|
.map((name) => {
|
|
14034
|
-
const
|
|
14072
|
+
const meta = byName.get(name);
|
|
14073
|
+
const visible = this.fieldVisibility[name] !== undefined
|
|
14074
|
+
? this.fieldVisibility[name] === false ? '0' : '1'
|
|
14075
|
+
: this.resolveFieldVisibility(meta ?? null, this.fieldRuleProps[name] || null) ? '1' : '0';
|
|
14035
14076
|
const overrides = this.fieldRuleProps[name];
|
|
14036
14077
|
const overrideState = overrides && Object.keys(overrides).length > 0 ? '1' : '0';
|
|
14037
14078
|
return `${name}:${visible}:${overrideState}`;
|
|
@@ -14253,8 +14294,10 @@ class PraxisDynamicForm {
|
|
|
14253
14294
|
if (items.some((item) => item.kind === 'richContent' && this.isRichContentLayoutItemVisible(item))) {
|
|
14254
14295
|
return true;
|
|
14255
14296
|
}
|
|
14256
|
-
//
|
|
14257
|
-
return getFormLayoutFieldNames(items).some((fieldName) =>
|
|
14297
|
+
// Explicit runtime visibility wins; otherwise resolve metadata/rule visibility directly.
|
|
14298
|
+
return getFormLayoutFieldNames(items).some((fieldName) => {
|
|
14299
|
+
return this.isFieldRenderableInLayout(fieldName);
|
|
14300
|
+
});
|
|
14258
14301
|
}
|
|
14259
14302
|
isResponsiveHiddenActive(hidden) {
|
|
14260
14303
|
if (!hidden || typeof hidden !== 'object' || Array.isArray(hidden)) {
|
|
@@ -34611,6 +34654,7 @@ const ENUMS = {
|
|
|
34611
34654
|
layoutPolicyIntent: ['detail', 'command'],
|
|
34612
34655
|
layoutPolicyLifecycle: ['initial', 'live'],
|
|
34613
34656
|
layoutPolicyPersistence: ['transient', 'authorable'],
|
|
34657
|
+
layoutPolicyWidthPrecedence: ['schema', 'summary'],
|
|
34614
34658
|
fieldIconPolicy: ['all', 'presentation-only', 'none'],
|
|
34615
34659
|
notifyIfOutdated: ['inline', 'snackbar', 'both', 'none'],
|
|
34616
34660
|
labelPosition: ['above', 'left'],
|
|
@@ -34875,6 +34919,14 @@ const FORM_COMPONENT_AI_CAPABILITIES = {
|
|
|
34875
34919
|
description: 'Mapa de colunas por breakpoint xs/sm/md/lg/xl para detalhes read-only compactos em drawers, side sheets e desktop largo.',
|
|
34876
34920
|
safetyNotes: 'Prefira valores simples como { lg: 3, md: 2, sm: 1 }; nao copie layout manual para sections.',
|
|
34877
34921
|
},
|
|
34922
|
+
{
|
|
34923
|
+
path: 'inputs.layoutPolicy.detailSummary.widthPrecedence',
|
|
34924
|
+
category: 'inputs',
|
|
34925
|
+
valueKind: 'enum',
|
|
34926
|
+
allowedValues: ENUMS.layoutPolicyWidthPrecedence,
|
|
34927
|
+
description: 'Define se larguras explicitas do schema ou a densidade do resumo controlam os spans no compactPresentation read-only.',
|
|
34928
|
+
safetyNotes: 'Default schema preserva x-ui.width; use summary apenas quando columns/responsiveColumns devem vencer larguras genericas do schema nessa surface.',
|
|
34929
|
+
},
|
|
34878
34930
|
{
|
|
34879
34931
|
path: 'inputs.fieldIconPolicy',
|
|
34880
34932
|
category: 'inputs',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisui/dynamic-form",
|
|
3
|
-
"version": "9.0.0-beta.
|
|
3
|
+
"version": "9.0.0-beta.43",
|
|
4
4
|
"description": "Angular dynamic form engine for Praxis UI: metadata-driven forms, hooks, and services integrating @praxisui/* packages.",
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"@angular/common": "^21.0.0",
|
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
"@angular/forms": "^21.0.0",
|
|
10
10
|
"@angular/material": "^21.0.0",
|
|
11
11
|
"@angular/router": "^21.0.0",
|
|
12
|
-
"@praxisui/ai": "^9.0.0-beta.
|
|
13
|
-
"@praxisui/dynamic-fields": "^9.0.0-beta.
|
|
14
|
-
"@praxisui/metadata-editor": "^9.0.0-beta.
|
|
15
|
-
"@praxisui/rich-content": "^9.0.0-beta.
|
|
16
|
-
"@praxisui/settings-panel": "^9.0.0-beta.
|
|
17
|
-
"@praxisui/visual-builder": "^9.0.0-beta.
|
|
18
|
-
"@praxisui/core": "^9.0.0-beta.
|
|
12
|
+
"@praxisui/ai": "^9.0.0-beta.43",
|
|
13
|
+
"@praxisui/dynamic-fields": "^9.0.0-beta.43",
|
|
14
|
+
"@praxisui/metadata-editor": "^9.0.0-beta.43",
|
|
15
|
+
"@praxisui/rich-content": "^9.0.0-beta.43",
|
|
16
|
+
"@praxisui/settings-panel": "^9.0.0-beta.43",
|
|
17
|
+
"@praxisui/visual-builder": "^9.0.0-beta.43",
|
|
18
|
+
"@praxisui/core": "^9.0.0-beta.43",
|
|
19
19
|
"rxjs": "^7.8.0"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
@@ -578,7 +578,7 @@ No ecossistema Praxis, ele funciona tanto como runtime final quanto como superfi
|
|
|
578
578
|
| `componentInstanceId` | `string \| undefined` | `undefined` | Active | Isola instancias com mesmo `formId`. |
|
|
579
579
|
| `layout` | `FormLayout \| undefined` | `undefined` | Partial | Funciona como override estrutural em parte dos fluxos de layout/editor. |
|
|
580
580
|
| `generatedLayoutPreset` | `'default' \| 'compactPresentation'` | `'default'` | Active | Usado apenas ao criar a configuracao inicial a partir de schema/metadata; nao reprocessa layouts persistidos ou authorados. |
|
|
581
|
-
| `layoutPolicy` | `DynamicFormLayoutPolicy \| null` | `null` | Active | Politica opt-in para materializar layout a partir do schema atual. Use `source: 'schema'` com `persistence: 'transient'` para detalhe/read-only e command forms schema-driven sem persistir `sections` geradas nem reabrir configuracao local salva; politicas de host em `config`, como `helpPresentation`, continuam sendo aplicadas ao layout materializado. Em `mode='view'`, `intent: 'detail'` ou `preset: 'compactPresentation'` ativa apresentacao quando `presentationModeGlobal` nao foi informado; use `[presentationModeGlobal]=false` para controles read-only tradicionais. No preset `compactPresentation`, `x-ui.width` explicito empacota campos em linhas de 12 colunas e tem precedencia sobre `detailSummary.columns
|
|
581
|
+
| `layoutPolicy` | `DynamicFormLayoutPolicy \| null` | `null` | Active | Politica opt-in para materializar layout a partir do schema atual. Use `source: 'schema'` com `persistence: 'transient'` para detalhe/read-only e command forms schema-driven sem persistir `sections` geradas nem reabrir configuracao local salva; politicas de host em `config`, como `helpPresentation`, continuam sendo aplicadas ao layout materializado. Em `mode='view'`, `intent: 'detail'` ou `preset: 'compactPresentation'` ativa apresentacao quando `presentationModeGlobal` nao foi informado; use `[presentationModeGlobal]=false` para controles read-only tradicionais. No preset `compactPresentation`, `x-ui.width` explicito empacota campos em linhas de 12 colunas e tem precedencia sobre `detailSummary.columns` por default; campos sem width permanecem full-width, a menos que `detailSummary.columns`/`responsiveColumns` declare a densidade do resumo. Use `detailSummary.widthPrecedence='summary'` quando a densidade do resumo read-only deve sobrescrever `x-ui.width` apenas nessa surface compacta. Use `responsiveColumns`, por exemplo `{ lg: 3, md: 2, sm: 1 }`, para drawers, side sheets e painéis corporativos estreitos sem copiar `FormConfig.sections` locais. `schemaType` e `schemaOperation` sao validados contra `schemaUrl` quando a URL declara esses parametros. |
|
|
582
582
|
| `backConfig` | `BackConfig \| undefined` | `undefined` | Active | Integra retorno/navegacao no painel de configuracao; `confirmOnDirty` tem precedencia sobre `behavior.confirmOnUnsavedChanges` no cancel. |
|
|
583
583
|
| `hooks` | `FormHooksLayout \| undefined` | `undefined` | Active | Override direto de hooks sobre `config.hooks`. |
|
|
584
584
|
| `customEndpoints` | `EndpointConfig` | `{}` | Active | Override de endpoints no CRUD service interno. |
|
|
@@ -594,6 +594,12 @@ No ecossistema Praxis, ele funciona tanto como runtime final quanto como superfi
|
|
|
594
594
|
| `fieldIconPolicy` | `'all' \| 'presentation-only' \| 'none'` | `'all'` | Active | Controla icones `prefixIcon`/`suffixIcon` vindos da metadata dos campos. `all` preserva o comportamento atual; `presentation-only` mantem icones decorativos no modo apresentacao e oculta os slots de icone em controles input/read-only tradicionais; `none` oculta esses slots em todos os modos. |
|
|
595
595
|
| `visibleGlobal` | `boolean \| null` | `null` | Active | Visibilidade global propagada para loader. |
|
|
596
596
|
|
|
597
|
+
In `compactPresentation` runtime detail surfaces, rows, columns and sections are visible only when
|
|
598
|
+
they contain at least one field that can be materialized after metadata visibility and field-rule
|
|
599
|
+
overrides are resolved. Boolean values, including `false`, are valid presentation values and must
|
|
600
|
+
not be treated as empty detail content. If no field in a section is materialized, the section is
|
|
601
|
+
hidden instead of rendering an orphan title.
|
|
602
|
+
|
|
597
603
|
#### Migration baseline for ErgonX-style screens
|
|
598
604
|
|
|
599
605
|
For enterprise migrations that must become templates for many screens, `layoutPolicy`,
|
|
@@ -1017,6 +1017,7 @@ declare class PraxisDynamicForm implements OnInit, OnChanges, OnDestroy {
|
|
|
1017
1017
|
private hostConfigHasAuthoredLocalFieldsNotInLocalConfig;
|
|
1018
1018
|
private syncWithServer;
|
|
1019
1019
|
private loadEntity;
|
|
1020
|
+
private hydrateEntityFromInitialValue;
|
|
1020
1021
|
private buildFormFromConfig;
|
|
1021
1022
|
private resetFormValueChangesSubscription;
|
|
1022
1023
|
private resetDependencyPolicySubscription;
|
|
@@ -1148,6 +1149,7 @@ declare class PraxisDynamicForm implements OnInit, OnChanges, OnDestroy {
|
|
|
1148
1149
|
items?: FormLayoutItem[];
|
|
1149
1150
|
id?: string;
|
|
1150
1151
|
}): FieldMetadata[];
|
|
1152
|
+
private isFieldRenderableInLayout;
|
|
1151
1153
|
getRichContentLayoutItemDocument(item: FormLayoutItem): RichContentDocument | null;
|
|
1152
1154
|
getRichContentLayoutItemLayout(item: FormLayoutItem): 'block' | 'inline';
|
|
1153
1155
|
private getVisualBlockRuleProps;
|