@praxisui/dynamic-form 9.0.5-rc.36 → 9.0.5-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
@@ -31,15 +31,15 @@ Peer dependencies:
31
31
 
32
32
  ## Reactive backend determinations
33
33
 
34
- When the exact request schema from `/schemas/filtered` publishes root-level `x-ui.reactiveDeterminations`, the form can execute the Metadata-compiled backend capability after a valid source changes and apply server-derived draft values. The projection is backend-owned, tenant-neutral and transient: it is never copied to `FormConfig` and cannot be authored as a callback, URL or local write policy.
34
+ When the exact request schema from `/schemas/filtered` publishes root-level `x-ui.reactiveDeterminations`, the form executes the Metadata-compiled backend capability after a valid source changes and applies server-derived draft values. After edit/create hydration, an already populated source is also validated before the draft can be reported as stable; an empty initial source settles as not applicable without an HTTP call. The projection is one closed graph: if any definition is malformed, the complete projection is rejected instead of partially executing. It remains backend-owned, tenant-neutral and transient: it is never copied to `FormConfig` and cannot be authored as a callback, URL or local write policy.
35
35
 
36
36
  Triggers, source fields and request/response bindings use JSON Pointer. The runtime supports nested object bindings, accepts at most 64 total input/output bindings and rejects duplicate or hierarchically overlapping pointers. It resolves the capability only against the configured API origin and rejects protocol-relative, encoded-separator or cross-origin targets. Output bindings have one closed meaning: server-derived draft values replace their targets. Metadata cannot select `if-pristine`, `if-empty` or another incidental UI policy.
37
37
 
38
- The runtime waits until hydration finishes and invalidates the previous generation as soon as a newer relevant value or validation status arrives, before the new debounce completes. `sourcePaths` is an intentional trigger subset of `inputs`: changing a non-trigger input invalidates the current draft, emits `input-changed-awaiting-trigger`, and blocks submit/navigation until a source trigger recalculates it. Every configured response path is resolved before target controls are written; all nested targets are populated before change observers are notified. Chained determinations receive one logical batch signal after the complete commit, rather than one execution per output control. Their dependency order uses hierarchical input/output JSON Pointer overlap, and the canonical projection rejects cycles and overlapping multi-writers. Downstream waits while upstream is pending and runs only after authoritative outputs or confirmed unchanged inputs. Null/undefined upstream outputs and failed, invalid or cancelled upstream generations settle downstream as skipped instead of invoking it with missing or stale data. If a control rejects a write, the runtime attempts to restore earlier writes and reports a terminal failure. This is a coordinated client-side batch, not a transaction or an ACID guarantee.
38
+ The runtime waits until hydration finishes and invalidates the previous generation as soon as a newer relevant value or validation status arrives, before the new debounce completes. `sourcePaths` is an intentional trigger subset of `inputs`: changing a non-trigger input invalidates the current draft, emits `input-changed-awaiting-trigger`, and blocks submit/navigation until a source trigger recalculates it. Every configured response path is resolved before target controls are written; all nested targets are populated before change observers are notified. Chained determinations receive one logical batch signal after the complete commit, rather than one execution per output control. Their dependency order uses hierarchical input/output JSON Pointer overlap, and the canonical projection rejects cycles and overlapping multi-writers. Downstream waits while upstream is pending and runs only after authoritative outputs or confirmed unchanged inputs. Null/undefined upstream outputs and failed, invalid or cancelled upstream generations settle downstream as skipped instead of invoking it with missing or stale data. If a control rejects a write, the runtime attempts to restore earlier writes and reports a terminal failure. This is a coordinated client-side batch, not a transaction or an ACID guarantee. Each capability remains a separate HTTP request, so an activation may occur between two chained previews; only a host command that resolves and revalidates one aggregate can claim snapshot consistency for the complete business decision.
39
39
 
40
40
  A determination remains pending through debounce, asynchronous validation and capability execution. Submit attempts wait with a bounded timeout and repeat the stability check after hooks that may mutate controls. Output controls are disabled transiently as server-owned draft values, but remain present in `getRawValue()`, `valueChange` and submit payloads; this state is not persisted in `FormConfig`.
41
41
 
42
- Hosts can observe metadata-only evidence through `reactiveDeterminationExecuted` and aggregate state through `reactiveDeterminationPendingChange`. Before navigating or closing a composed surface, inspect `hasPendingReactiveDeterminations()` and `hasUnsatisfiedReactiveDeterminations()` and await `waitForReactiveDeterminations(timeoutMs?)`; `false` means timeout or that the latest generation did not produce a current authoritative draft, and must block the transition. Cancelled pending correlations terminate as `skipped/superseded`. No event or method exposes form or response values.
42
+ Hosts can observe metadata-only evidence through `reactiveDeterminationExecuted` and aggregate state through `reactiveDeterminationPendingChange`. Before navigating or closing a composed surface, inspect `hasPendingReactiveDeterminations()` and `hasUnsatisfiedReactiveDeterminations()` and await `waitForReactiveDeterminations(timeoutMs?)`; `false` means timeout or that the latest generation did not produce a current authoritative draft, and must block the transition. A programmatic change to an owned output invalidates the previous authority and triggers a new determination. Cancelled pending correlations terminate as `skipped/superseded`. No event or method exposes form or response values.
43
43
 
44
44
  Use `optionSource.dependsOn` for dependent option lists and workflow actions for persistent transitions. Recommendations, side-effect refresh and backend validation are separate capabilities. The final backend command must still authorize, recompute or validate fiscal, pricing, eligibility, compliance and approval outcomes.
45
45
 
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-08-19T00:10:40.048Z",
3
+ "generatedAt": "2026-08-19T02:54:26.311Z",
4
4
  "packageName": "@praxisui/dynamic-form",
5
- "packageVersion": "9.0.5-rc.36",
5
+ "packageVersion": "9.0.5-rc.38",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 2,
@@ -20,7 +20,7 @@ import * as i9 from '@angular/material/snack-bar';
20
20
  import { MatSnackBarModule } from '@angular/material/snack-bar';
21
21
  import * as i19 from '@angular/material/badge';
22
22
  import { MatBadgeModule } from '@angular/material/badge';
23
- import { map, Subscription, merge, timer, NEVER, EMPTY, defer, TimeoutError, of, Subject, firstValueFrom, BehaviorSubject, throwError, combineLatest, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs';
23
+ import { map, Subscription, merge, of, timer, NEVER, EMPTY, defer, TimeoutError, Subject, firstValueFrom, BehaviorSubject, throwError, combineLatest, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs';
24
24
  import { filter, map as map$1, switchMap, take, timeout, throwIfEmpty, tap, catchError, ignoreElements, finalize, takeUntil, skip, debounceTime, startWith } from 'rxjs/operators';
25
25
  import * as i1 from '@praxisui/core';
26
26
  import { serializeEntityLookupValueForPayload, PraxisI18nService, PraxisIconButtonComponent, PraxisIconDirective, providePraxisI18nConfig, RULE_PROPERTY_SCHEMA, FIELD_METADATA_CAPABILITIES, migrateFormLayoutRule, PraxisJsonLogicService, normalizeFormLayoutItems, deepMerge, createDefaultFormConfig, normalizeFormConfig as normalizeFormConfig$1, ensureIds, createEmptyRichContentDocument, resolveHidden, resolveOrder, resolveSpan, projectGroupedCommandPartialRows, isPraxisRuntimeGlobalActionEffect, ASYNC_CONFIG_STORAGE, withFormConfigSections, createFieldLayoutItem, getFormLayoutFieldNames, LoggerService, createCorporateLoggerConfig, ConsoleLoggerSink, ResourceDiscoveryService, GlobalActionService, ResourceQuickConnectComponent, composeHeadersWithVersion, buildApiUrl, mapFieldDefinitionsToMetadata, reconcileFormConfig, materializeFormLayoutFromMetadata, syncWithServerMetadata, PRAXIS_LOADING_CTX, evaluateFieldAccess, normalizeControlTypeKey, normalizePraxisEffectPolicy, buildPraxisEffectDistinctKey, getFormColumnFieldNames, resolveGroupedCommandPartialRowSpans, resolveOffset, buildSchemaIdStorageKeySegment, buildSchemaId, fetchWithETag, resolveControlTypeAlias, getTextTransformer, MemoryCacheAdapter, LocalStorageCacheAdapter, SchemaMetadataClient, normalizeReactiveDeterminations, CONNECTION_STORAGE, API_URL, PRAXIS_LOADING_RENDERER, FORM_HOOKS_PRESETS, EmptyStateCardComponent, isValidFormConfig, ComponentMetadataRegistry, FieldControlType, isRequiredGlobalActionPayloadMissing, GLOBAL_ACTION_CATALOG, PRAXIS_GLOBAL_ACTION_CATALOG, getGlobalActionCatalog, SURFACE_OPEN_I18N_NAMESPACE, getGlobalActionUiSchema, getRequiredGlobalActionPayloadKeys, hasMeaningfulGlobalActionPayloadValue, SurfaceOpenActionEditorComponent, SURFACE_OPEN_I18N_CONFIG, validateGlobalActionRefs, normalizeFormMetadata } from '@praxisui/core';
@@ -265,6 +265,7 @@ class ReactiveDeterminationRuntimeService {
265
265
  const determinationKey = determinationKeyFor(definitionIndex, definition);
266
266
  let generation = 0;
267
267
  let lastSuccessfulFingerprint = null;
268
+ let lastSuccessfulOutputFingerprint = null;
268
269
  const triggerControls = this.resolveControls(form, definition.trigger.sourcePaths);
269
270
  const inputControls = this.resolveControls(form, definition.inputs.map((binding) => binding.fieldPath));
270
271
  const targetControls = this.resolveControls(form, definition.outputs.map((binding) => binding.fieldPath));
@@ -296,11 +297,24 @@ class ReactiveDeterminationRuntimeService {
296
297
  }
297
298
  composite.add(this.claimTargets(form, targetControls));
298
299
  const triggerControlSet = new Set(triggerControls);
299
- const observedControls = [...new Set([...triggerControls, ...inputControls])];
300
+ const targetControlSet = new Set(targetControls);
301
+ const dependencyObservedControls = [...new Set([...triggerControls, ...inputControls])];
302
+ const directlyObservedControls = [
303
+ ...new Set([...dependencyObservedControls, ...targetControls]),
304
+ ];
300
305
  const committedControls$ = this.getOrCreateCommittedControls(form);
301
- const changes$ = merge(...observedControls.flatMap((control) => [control.valueChanges, control.statusChanges].map((changes) => changes.pipe(filter(() => !this.publishingCommitByForm.has(form)), map$1(() => triggerControlSet.has(control))))), committedControls$.pipe(filter((committedControls) => observedControls.some((control) => committedControls.has(control))), map$1((committedControls) => triggerControls.some((control) => committedControls.has(control)))));
306
+ const changes$ = merge(of({
307
+ shouldExecute: triggerControls.some((control) => hasMeaningfulSourceValue(control.value)),
308
+ initial: true,
309
+ }), ...directlyObservedControls.flatMap((control) => [control.valueChanges, control.statusChanges].map((changes) => changes.pipe(filter(() => !this.publishingCommitByForm.has(form)), map$1(() => ({
310
+ shouldExecute: triggerControlSet.has(control) || targetControlSet.has(control),
311
+ initial: false,
312
+ }))))), committedControls$.pipe(filter((committedControls) => dependencyObservedControls.some((control) => committedControls.has(control))), map$1((committedControls) => ({
313
+ shouldExecute: triggerControls.some((control) => committedControls.has(control)),
314
+ initial: false,
315
+ }))));
302
316
  const determinationSubscription = changes$
303
- .pipe(map$1((shouldExecute) => {
317
+ .pipe(map$1(({ shouldExecute, initial }) => {
304
318
  const currentGeneration = ++generation;
305
319
  const correlationId = createCorrelationId();
306
320
  // Publish the new authority before switchMap finalizes the old one.
@@ -308,6 +322,10 @@ class ReactiveDeterminationRuntimeService {
308
322
  if (shouldExecute) {
309
323
  publishEvent(this.event(definition, 'pending', correlationId, 0));
310
324
  }
325
+ else if (initial) {
326
+ this.satisfyGeneration(form, determinationKey, currentGeneration);
327
+ publishEvent(this.event(definition, 'skipped', correlationId, 0, 'initial-source-empty'));
328
+ }
311
329
  else {
312
330
  publishEvent(this.event(definition, 'skipped', correlationId, 0, 'input-changed-awaiting-trigger'));
313
331
  }
@@ -340,7 +358,10 @@ class ReactiveDeterminationRuntimeService {
340
358
  publishEvent(this.event(definition, 'error', signal.correlationId, 0, 'request-binding-invalid'));
341
359
  return EMPTY;
342
360
  }
343
- if (invocation.fingerprint === lastSuccessfulFingerprint) {
361
+ const currentOutputFingerprint = this.buildOutputFingerprint(form, definition);
362
+ if (invocation.fingerprint === lastSuccessfulFingerprint &&
363
+ currentOutputFingerprint !== null &&
364
+ currentOutputFingerprint === lastSuccessfulOutputFingerprint) {
344
365
  this.satisfyGeneration(form, determinationKey, signal.generation);
345
366
  publishEvent(this.event(definition, 'skipped', signal.correlationId, 0, 'unchanged-inputs'));
346
367
  return EMPTY;
@@ -358,6 +379,7 @@ class ReactiveDeterminationRuntimeService {
358
379
  this.commit(form, commit);
359
380
  this.satisfyGeneration(form, determinationKey, signal.generation);
360
381
  lastSuccessfulFingerprint = invocation.fingerprint;
382
+ lastSuccessfulOutputFingerprint = this.buildOutputFingerprint(form, definition);
361
383
  publishEvent(this.event(definition, 'success', signal.correlationId, Date.now() - startedAt, undefined, commit.map((entry) => entry.targetPath)));
362
384
  }), catchError((error) => {
363
385
  if (this.isCurrentGeneration(form, determinationKey, signal.generation)) {
@@ -467,6 +489,16 @@ class ReactiveDeterminationRuntimeService {
467
489
  }
468
490
  return entries;
469
491
  }
492
+ buildOutputFingerprint(form, definition) {
493
+ const values = [];
494
+ for (const binding of definition.outputs) {
495
+ const control = form.get(jsonPointerSegments(binding.fieldPath));
496
+ if (!control)
497
+ return null;
498
+ values.push(control.value);
499
+ }
500
+ return stableFingerprint(values);
501
+ }
470
502
  commit(form, entries) {
471
503
  // Reactive Determination outputs have one closed semantic: server-derived
472
504
  // draft values. Metadata cannot choose an overwrite policy.
@@ -703,6 +735,15 @@ function stableFingerprint(value) {
703
735
  return String(value);
704
736
  }
705
737
  }
738
+ function hasMeaningfulSourceValue(value) {
739
+ if (value === null || value === undefined)
740
+ return false;
741
+ if (typeof value === 'string')
742
+ return value.trim().length > 0;
743
+ if (Array.isArray(value))
744
+ return value.length > 0;
745
+ return true;
746
+ }
706
747
  function createCorrelationId() {
707
748
  const runtimeCrypto = globalThis.crypto;
708
749
  if (runtimeCrypto && typeof runtimeCrypto.randomUUID === 'function') {
@@ -11112,6 +11153,12 @@ class PraxisDynamicFormDestroyedError extends Error {
11112
11153
  const FORM_CONFIG_LOAD_TIMEOUT_MS = 1200;
11113
11154
  const FORM_INPUT_PREFS_LOAD_TIMEOUT_MS = 1200;
11114
11155
  const SCHEMA_GLOBAL_CONFIG_READY_TIMEOUT_MS = 1200;
11156
+ class InvalidReactiveDeterminationProjectionError extends Error {
11157
+ constructor() {
11158
+ super('Invalid x-ui.reactiveDeterminations projection.');
11159
+ this.name = 'InvalidReactiveDeterminationProjectionError';
11160
+ }
11161
+ }
11115
11162
  const PRAXIS_DYNAMIC_FORM_FALLBACK_LOGGER = new LoggerService(createCorporateLoggerConfig('dev'), [new ConsoleLoggerSink()]);
11116
11163
  class PraxisDynamicForm {
11117
11164
  crud;
@@ -17506,7 +17553,7 @@ class PraxisDynamicForm {
17506
17553
  this.debugLog('[PDF] settingsPanel.open(FieldMetadataEditor)', { fieldName, composedTitle });
17507
17554
  }
17508
17555
  catch { }
17509
- const ref = this.settingsPanel.open({
17556
+ const ref = this.settingsPanel.openChild({
17510
17557
  id: `field-meta.${this.formId}.${fieldName}`,
17511
17558
  title: composedTitle,
17512
17559
  titleIcon,
@@ -17609,14 +17656,14 @@ class PraxisDynamicForm {
17609
17656
  return;
17610
17657
  Promise.resolve().then(function () { return rowEditor_component; }).then((m) => {
17611
17658
  const { RowEditorComponent } = m;
17612
- const ref = this.settingsPanel.open({
17659
+ const ref = this.settingsPanel.openChild({
17613
17660
  id: `row.${this.formId || 'form'}.${row.id}`,
17614
17661
  title: 'Configurar Linha',
17615
17662
  titleIcon: 'view_stream',
17616
17663
  content: { component: RowEditorComponent, inputs: { row } },
17617
17664
  });
17618
- ref.applied$.pipe(takeUntil(this.destroy$)).subscribe(() => this.cdr.detectChanges());
17619
- ref.saved$.pipe(takeUntil(this.destroy$)).subscribe(() => this.cdr.detectChanges());
17665
+ ref.applied$.pipe(takeUntil(this.destroy$)).subscribe((value) => this.applyRowEditorValue(row, value));
17666
+ ref.saved$.pipe(takeUntil(this.destroy$)).subscribe((value) => this.applyRowEditorValue(row, value));
17620
17667
  });
17621
17668
  }
17622
17669
  openColumnEditor(column) {
@@ -17624,14 +17671,14 @@ class PraxisDynamicForm {
17624
17671
  return;
17625
17672
  Promise.resolve().then(function () { return columnEditor_component; }).then((m) => {
17626
17673
  const { ColumnEditorComponent } = m;
17627
- const ref = this.settingsPanel.open({
17674
+ const ref = this.settingsPanel.openChild({
17628
17675
  id: `column.${this.formId || 'form'}.${column.id}`,
17629
17676
  title: 'Configurar Coluna',
17630
17677
  titleIcon: 'view_column',
17631
17678
  content: { component: ColumnEditorComponent, inputs: { column } },
17632
17679
  });
17633
- ref.applied$.pipe(takeUntil(this.destroy$)).subscribe(() => this.cdr.detectChanges());
17634
- ref.saved$.pipe(takeUntil(this.destroy$)).subscribe(() => this.cdr.detectChanges());
17680
+ ref.applied$.pipe(takeUntil(this.destroy$)).subscribe((value) => this.applyColumnEditorValue(column, value));
17681
+ ref.saved$.pipe(takeUntil(this.destroy$)).subscribe((value) => this.applyColumnEditorValue(column, value));
17635
17682
  });
17636
17683
  }
17637
17684
  openSectionEditor(section, focusTarget) {
@@ -17639,7 +17686,7 @@ class PraxisDynamicForm {
17639
17686
  return;
17640
17687
  Promise.resolve().then(function () { return sectionEditor_component; }).then((m) => {
17641
17688
  const { SectionEditorComponent } = m;
17642
- const ref = this.settingsPanel.open({
17689
+ const ref = this.settingsPanel.openChild({
17643
17690
  id: `section.${this.formId || 'form'}.${section.id}`,
17644
17691
  title: section.title || 'Configurar seção',
17645
17692
  titleIcon: 'view_agenda',
@@ -17656,6 +17703,60 @@ class PraxisDynamicForm {
17656
17703
  });
17657
17704
  });
17658
17705
  }
17706
+ applyRowEditorValue(row, value) {
17707
+ this.applyNestedLayoutEditorValue('row', row, value);
17708
+ }
17709
+ applyColumnEditorValue(column, value) {
17710
+ this.applyNestedLayoutEditorValue('column', column, value);
17711
+ }
17712
+ applyNestedLayoutEditorValue(kind, target, value) {
17713
+ if (!target || !value || typeof value !== 'object')
17714
+ return;
17715
+ const sourceSections = this.config?.sections || [];
17716
+ let targetPosition;
17717
+ for (let sectionIndex = 0; sectionIndex < sourceSections.length; sectionIndex++) {
17718
+ const section = sourceSections[sectionIndex];
17719
+ for (let rowIndex = 0; rowIndex < (section.rows || []).length; rowIndex++) {
17720
+ const row = section.rows[rowIndex];
17721
+ if (kind === 'row' &&
17722
+ (row === target || (!!row?.id && !!target?.id && row.id === target.id))) {
17723
+ targetPosition = { sectionIndex, rowIndex };
17724
+ break;
17725
+ }
17726
+ if (kind === 'column') {
17727
+ const columnIndex = (row.columns || []).findIndex((candidate) => candidate === target ||
17728
+ (!!candidate?.id && !!target?.id && candidate.id === target.id));
17729
+ if (columnIndex >= 0) {
17730
+ targetPosition = { sectionIndex, rowIndex, columnIndex };
17731
+ break;
17732
+ }
17733
+ }
17734
+ }
17735
+ if (targetPosition)
17736
+ break;
17737
+ }
17738
+ if (!targetPosition)
17739
+ return;
17740
+ const sections = structuredClone(sourceSections);
17741
+ const positionedRow = sections[targetPosition.sectionIndex].rows[targetPosition.rowIndex];
17742
+ if (kind === 'row') {
17743
+ sections[targetPosition.sectionIndex].rows[targetPosition.rowIndex] = {
17744
+ ...positionedRow,
17745
+ ...structuredClone(value),
17746
+ };
17747
+ }
17748
+ else {
17749
+ const columnIndex = targetPosition.columnIndex;
17750
+ positionedRow.columns[columnIndex] = {
17751
+ ...positionedRow.columns[columnIndex],
17752
+ ...structuredClone(value),
17753
+ };
17754
+ }
17755
+ this.config = { ...this.config, sections };
17756
+ this.configChange.emit(this.config);
17757
+ this.emitConfigPatchChange();
17758
+ this.cdr.detectChanges();
17759
+ }
17659
17760
  applySectionEditorValue(section, value) {
17660
17761
  if (!section || !value || typeof value !== 'object') {
17661
17762
  this.cdr.detectChanges();
@@ -20058,15 +20159,24 @@ class PraxisDynamicForm {
20058
20159
  if (hooks && typeof hooks === 'object') {
20059
20160
  this.schemaRootHooks = hooks;
20060
20161
  }
20162
+ const rawDeterminations = rootUi && typeof rootUi === 'object'
20163
+ ? rootUi.reactiveDeterminations
20164
+ : undefined;
20061
20165
  const normalizedDeterminations = ctx.schemaType === 'request'
20062
- ? normalizeReactiveDeterminations(rootUi && typeof rootUi === 'object'
20063
- ? rootUi.reactiveDeterminations
20064
- : undefined)
20166
+ ? normalizeReactiveDeterminations(rawDeterminations)
20065
20167
  : [];
20168
+ if (ctx.schemaType === 'request' && rawDeterminations !== undefined && (!Array.isArray(rawDeterminations)
20169
+ || normalizedDeterminations.length !== rawDeterminations.length)) {
20170
+ throw new InvalidReactiveDeterminationProjectionError();
20171
+ }
20066
20172
  this.schemaRootReactiveDeterminations = normalizedDeterminations.filter((definition) => !this.schemaRootOperationId
20067
20173
  || definition.scope.schemaOperationId === this.schemaRootOperationId);
20068
20174
  }
20069
- catch { }
20175
+ catch (error) {
20176
+ if (error instanceof InvalidReactiveDeterminationProjectionError) {
20177
+ throw error;
20178
+ }
20179
+ }
20070
20180
  const schemaId = buildSchemaId(schemaContext);
20071
20181
  this.lastSchemaMeta = { schemaId, serverHash: entry.schemaHash, context: schemaContext };
20072
20182
  this.schemaCache = defs;
@@ -20091,6 +20201,9 @@ class PraxisDynamicForm {
20091
20201
  return defs;
20092
20202
  }
20093
20203
  catch (error) {
20204
+ if (error instanceof InvalidReactiveDeterminationProjectionError) {
20205
+ throw error;
20206
+ }
20094
20207
  if (this.isDestroyed || error instanceof PraxisDynamicFormDestroyedError) {
20095
20208
  throw new PraxisDynamicFormDestroyedError();
20096
20209
  }
@@ -22715,7 +22828,7 @@ class SectionConfiguratorComponent {
22715
22828
  const SectionEditorComponent = m.SectionEditorComponent;
22716
22829
  if (!SectionEditorComponent)
22717
22830
  return;
22718
- const ref = this.settingsPanel.open({
22831
+ const ref = this.settingsPanel.openChild({
22719
22832
  id: `layout.section.${this.section.id || this.sectionIndex}`,
22720
22833
  title: this.section.title || `Seção ${this.sectionIndex + 1}`,
22721
22834
  titleIcon: 'view_agenda',
@@ -22724,8 +22837,8 @@ class SectionConfiguratorComponent {
22724
22837
  inputs: { section: this.section, fieldMetadata: this.fieldMetadata },
22725
22838
  },
22726
22839
  });
22727
- ref.applied$?.subscribe(() => this.onSectionUpdated());
22728
- ref.saved$?.subscribe(() => this.onSectionUpdated());
22840
+ ref.applied$?.subscribe((value) => this.onSectionUpdated(value));
22841
+ ref.saved$?.subscribe((value) => this.onSectionUpdated(value));
22729
22842
  });
22730
22843
  }
22731
22844
  getSectionHeaderPreview() {
@@ -23317,7 +23430,7 @@ class LayoutEditorComponent {
23317
23430
  this.applyFieldMetadataPatch(fieldName, patch);
23318
23431
  };
23319
23432
  const title = this.t('field.editMetadataTitle').replace('{field}', field.label || field.name);
23320
- const ref = this.settingsPanel.open({
23433
+ const ref = this.settingsPanel.openChild({
23321
23434
  id: `layout.fieldMetadata.${fieldName}`,
23322
23435
  title,
23323
23436
  titleIcon: 'edit_note',
@@ -23367,7 +23480,7 @@ class LayoutEditorComponent {
23367
23480
  if (!section?.id || !row?.id || !column?.id || item?.kind !== 'richContent') {
23368
23481
  return;
23369
23482
  }
23370
- const ref = this.settingsPanel.open({
23483
+ const ref = this.settingsPanel.openChild({
23371
23484
  id: `layout.richContent.${section.id}.${row.id}.${column.id}.${item.id}`,
23372
23485
  title: this.t('visualBlock.editTitle'),
23373
23486
  titleIcon: 'notes',
@@ -30902,16 +31015,45 @@ class PraxisDynamicFormConfigEditor {
30902
31015
  return;
30903
31016
  Promise.resolve().then(function () { return columnEditor_component; }).then((m) => {
30904
31017
  const { ColumnEditorComponent } = m;
30905
- const ref = this.settingsPanel.open({
31018
+ const ref = this.settingsPanel.openChild({
30906
31019
  id: `column.${this.formId || 'form'}.${column.id}`,
30907
31020
  title: this.tx('config.layout.columnEditor.title', 'Configurar Coluna'),
30908
31021
  titleIcon: 'view_column',
30909
31022
  content: { component: ColumnEditorComponent, inputs: { column } },
30910
31023
  });
30911
- ref.applied$.pipe(takeUntil(this.destroy$)).subscribe(() => this.cdr.detectChanges());
30912
- ref.saved$.pipe(takeUntil(this.destroy$)).subscribe(() => this.cdr.detectChanges());
31024
+ ref.applied$.pipe(takeUntil(this.destroy$)).subscribe((value) => this.applyColumnEditorValue(column, value));
31025
+ ref.saved$.pipe(takeUntil(this.destroy$)).subscribe((value) => this.applyColumnEditorValue(column, value));
30913
31026
  });
30914
31027
  }
31028
+ applyColumnEditorValue(column, value) {
31029
+ if (!column || !value || typeof value !== 'object')
31030
+ return;
31031
+ const sourceSections = this.editedConfig.sections || [];
31032
+ let targetPosition;
31033
+ for (let sectionIndex = 0; sectionIndex < sourceSections.length; sectionIndex++) {
31034
+ const section = sourceSections[sectionIndex];
31035
+ for (let rowIndex = 0; rowIndex < (section.rows || []).length; rowIndex++) {
31036
+ const row = section.rows[rowIndex];
31037
+ const index = (row.columns || []).findIndex((candidate) => candidate === column || (!!candidate?.id && !!column?.id && candidate.id === column.id));
31038
+ if (index >= 0) {
31039
+ targetPosition = { sectionIndex, rowIndex, columnIndex: index };
31040
+ break;
31041
+ }
31042
+ }
31043
+ if (targetPosition)
31044
+ break;
31045
+ }
31046
+ if (!targetPosition)
31047
+ return;
31048
+ const sections = structuredClone(sourceSections);
31049
+ const row = sections[targetPosition.sectionIndex].rows[targetPosition.rowIndex];
31050
+ row.columns[targetPosition.columnIndex] = {
31051
+ ...row.columns[targetPosition.columnIndex],
31052
+ ...structuredClone(value),
31053
+ };
31054
+ this.onConfigChange({ ...this.editedConfig, sections });
31055
+ this.cdr.detectChanges();
31056
+ }
30915
31057
  // Cascatas: aplicar patch granular aos campos
30916
31058
  onCascadeApply(patch) {
30917
31059
  const fields = this.editedConfig.fieldMetadata || [];
@@ -32909,7 +33051,7 @@ class SectionEditorComponent {
32909
33051
  this.fb = fb;
32910
33052
  this.data = data;
32911
33053
  this.iconPicker = iconPicker;
32912
- this.section = this.data.section;
33054
+ this.section = structuredClone(this.data.section);
32913
33055
  this.fieldMetadata = this.data.fieldMetadata || this.fieldMetadata;
32914
33056
  }
32915
33057
  ngOnInit() {
@@ -34866,7 +35008,7 @@ class RowEditorComponent {
34866
35008
  constructor(fb, data) {
34867
35009
  this.fb = fb;
34868
35010
  this.data = data;
34869
- this.row = data.row;
35011
+ this.row = structuredClone(data.row);
34870
35012
  }
34871
35013
  ngOnInit() {
34872
35014
  const r = this.data.row;
@@ -34891,7 +35033,6 @@ class RowEditorComponent {
34891
35033
  }
34892
35034
  catch { }
34893
35035
  this.form.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(() => {
34894
- Object.assign(this.data.row, this.form.value);
34895
35036
  let dirty = true;
34896
35037
  try {
34897
35038
  const nowSig = JSON.stringify(this.form.getRawValue());
@@ -34907,7 +35048,7 @@ class RowEditorComponent {
34907
35048
  this.destroy$.complete();
34908
35049
  }
34909
35050
  getSettingsValue() {
34910
- return this.form.value;
35051
+ return { ...this.row, ...this.form.getRawValue() };
34911
35052
  }
34912
35053
  displayValue(value, fallback) {
34913
35054
  if (value === null || value === undefined)
@@ -35241,7 +35382,7 @@ class ColumnEditorComponent {
35241
35382
  constructor(fb, data) {
35242
35383
  this.fb = fb;
35243
35384
  this.data = data;
35244
- this.column = data.column;
35385
+ this.column = structuredClone(data.column);
35245
35386
  }
35246
35387
  ngOnInit() {
35247
35388
  const col = this.data.column;
@@ -35302,10 +35443,10 @@ class ColumnEditorComponent {
35302
35443
  this.destroy$.complete();
35303
35444
  }
35304
35445
  applyChanges() {
35305
- Object.assign(this.data.column, this.form.value);
35446
+ this.column = { ...this.column, ...this.form.getRawValue() };
35306
35447
  }
35307
35448
  getSettingsValue() {
35308
- return this.form.value;
35449
+ return { ...this.column, ...this.form.getRawValue() };
35309
35450
  }
35310
35451
  onBreakpointChange(bp) {
35311
35452
  this.previewBreakpoint = bp;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisui/dynamic-form",
3
- "version": "9.0.5-rc.36",
3
+ "version": "9.0.5-rc.38",
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.5-rc.36",
13
- "@praxisui/dynamic-fields": "^9.0.5-rc.36",
14
- "@praxisui/metadata-editor": "^9.0.5-rc.36",
15
- "@praxisui/rich-content": "^9.0.5-rc.36",
16
- "@praxisui/settings-panel": "^9.0.5-rc.36",
17
- "@praxisui/visual-builder": "^9.0.5-rc.36",
18
- "@praxisui/core": "^9.0.5-rc.36",
12
+ "@praxisui/ai": "^9.0.5-rc.38",
13
+ "@praxisui/dynamic-fields": "^9.0.5-rc.38",
14
+ "@praxisui/metadata-editor": "^9.0.5-rc.38",
15
+ "@praxisui/rich-content": "^9.0.5-rc.38",
16
+ "@praxisui/settings-panel": "^9.0.5-rc.38",
17
+ "@praxisui/visual-builder": "^9.0.5-rc.38",
18
+ "@praxisui/core": "^9.0.5-rc.38",
19
19
  "rxjs": "^7.8.0"
20
20
  },
21
21
  "dependencies": {
@@ -78,13 +78,13 @@ Este documento e a referencia canonica da API JSON de praxis-dynamic-form.
78
78
  ## Backend-owned reactive determinations
79
79
 
80
80
  - `x-ui.reactiveDeterminations` e lido exclusivamente do request schema exato retornado por `/schemas/filtered`; nao integra nem e persistido em `FormConfig`.
81
- - O runtime aceita somente o contrato fechado normalizado por `normalizeReactiveDeterminations`: trigger `on-change`, scope de create/edit, capability `POST`, no maximo 64 bindings totais, JSON Pointers sem duplicidade ou sobreposicao hierarquica e provenance estrutural.
81
+ - O runtime aceita somente o contrato fechado normalizado por `normalizeReactiveDeterminations`: trigger `on-change`, scope de create/edit, capability `POST`, no maximo 64 bindings totais, JSON Pointers sem duplicidade ou sobreposicao hierarquica e provenance estrutural. A lista e um unico grafo canonico: uma definicao invalida rejeita a projecao inteira, sem execucao parcial.
82
82
  - `href`, request/response schemas e bindings sao compilados pelo Praxis Metadata. O Angular nao aceita callback, headers, method, URL ou write policy autorados pelo host/editor/LLM.
83
83
  - Cada valor ou status relevante invalida imediatamente a geracao anterior, antes do debounce da proxima chamada; somente a geracao ainda autoritativa pode aplicar a resposta.
84
84
  - Hrefs protocol-relative, com separadores codificados ou resolvidos fora da origem configurada da API falham de forma fechada.
85
85
  - A resposta so e aplicada quando todos os bindings existem. Os controles alvo aninhados sao preenchidos antes da notificacao dos observers; determinacoes encadeadas recebem um unico sinal logico depois do commit completo, nao um disparo por output. A ordem da cadeia e derivada dos bindings existentes: downstream aguarda a geracao upstream e so executa apos outputs autoritativos ou inputs confirmados como inalterados. Output upstream `null`/`undefined` e geracao upstream com erro, invalida ou cancelada encerram downstream como `skipped`, sem chamada com dado ausente ou obsoleto. Se um controle rejeitar a escrita, o runtime tenta restaurar os anteriores e emite falha terminal. O resultado tem semantica fechada server-derived e substitui o draft alvo; o commit e um batch coordenado no cliente, sem promessa de transacao ACID.
86
86
  - O estado permanece pending durante debounce, validacao assincrona e execucao. Uma tentativa de submit aguarda estabilizacao com timeout e repete a espera depois de hooks que podem alterar controles.
87
- - Controles de output sao desabilitados transitoriamente como valores server-derived, sem mutar/persistir `FormConfig`; seus valores continuam em `getRawValue()`, `valueChange` e no payload de submit.
87
+ - Controles de output sao desabilitados transitoriamente como valores server-derived, sem mutar/persistir `FormConfig`; seus valores continuam em `getRawValue()`, `valueChange` e no payload de submit. Depois da hidratacao, fonte inicialmente preenchida entra em `pending` e e determinada antes de o draft ser considerado estavel; fonte inicial vazia encerra como `skipped/initial-source-empty`, satisfeita e sem HTTP. Alteracao programatica posterior em output owned invalida a autoridade anterior e reexecuta a capability.
88
88
  - `reactiveDeterminationExecuted` publica apenas IDs, paths, provenance, estado (`pending`, `success`, `skipped` ou `error`), duracao, correlation ID e motivo. Valores do formulario e da resposta nao participam da evidencia.
89
89
  - Hosts compostos recebem estado agregado por `reactiveDeterminationPendingChange`. Antes de navegar ou fechar, devem consultar `hasPendingReactiveDeterminations()` e `hasUnsatisfiedReactiveDeterminations()` e aguardar `waitForReactiveDeterminations(timeoutMs?)`; retorno `false` indica timeout ou geração mais recente sem draft autoritativo e bloqueia a transição. Correlações canceladas recebem terminal `skipped/superseded`.
90
90
  - Cascatas de opcoes continuam em `optionSource.dependsOn`; recommendations, side-effect refresh, backend validation e workflow actions permanecem capacidades separadas.
@@ -380,6 +380,7 @@ declare class ReactiveDeterminationRuntimeService {
380
380
  private claimTargets;
381
381
  private buildInvocation;
382
382
  private buildCommit;
383
+ private buildOutputFingerprint;
383
384
  private commit;
384
385
  private getOrCreateCommittedControls;
385
386
  private event;
@@ -1357,6 +1358,9 @@ declare class PraxisDynamicForm implements OnInit, OnChanges, OnDestroy {
1357
1358
  private openRowEditor;
1358
1359
  private openColumnEditor;
1359
1360
  openSectionEditor(section: any, focusTarget?: 'title' | 'description'): void;
1361
+ private applyRowEditorValue;
1362
+ private applyColumnEditorValue;
1363
+ private applyNestedLayoutEditorValue;
1360
1364
  private applySectionEditorValue;
1361
1365
  private resolveControlTypeEditorial;
1362
1366
  private getControlTypeIcon;
@@ -1788,6 +1792,7 @@ declare class PraxisDynamicFormConfigEditor implements SettingsValueProvider, On
1788
1792
  ngOnDestroy(): void;
1789
1793
  onLayoutSelect(event: any): void;
1790
1794
  private openColumnEditor;
1795
+ private applyColumnEditorValue;
1791
1796
  onCascadeApply(patch: Record<string, Partial<FieldDefinition>>): void;
1792
1797
  private syncCascadeManagerFields;
1793
1798
  private stripLegacy;