@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.
@@ -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 visible = this.fieldVisibility[name] === false ? '0' : '1';
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
- // A column is visible if at least one of its fields is visible.
14257
- return getFormLayoutFieldNames(items).some((fieldName) => this.fieldVisibility[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.41",
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.41",
13
- "@praxisui/dynamic-fields": "^9.0.0-beta.41",
14
- "@praxisui/metadata-editor": "^9.0.0-beta.41",
15
- "@praxisui/rich-content": "^9.0.0-beta.41",
16
- "@praxisui/settings-panel": "^9.0.0-beta.41",
17
- "@praxisui/visual-builder": "^9.0.0-beta.41",
18
- "@praxisui/core": "^9.0.0-beta.41",
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`; campos sem width permanecem full-width, a menos que `detailSummary.columns`/`responsiveColumns` declare a densidade do resumo. 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. |
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;