@praxisui/crud 9.0.4-rc.27 → 9.0.4-rc.29

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
@@ -202,6 +202,19 @@ to” and keeps it distinct from the current child record. Both identities stay
202
202
  outside dynamic-form inputs and submit payloads; capabilities and HATEOAS remain
203
203
  the authority for whether the operation may execute.
204
204
 
205
+ When a CRUD component is already materialized inside a runtime surface drawer,
206
+ drawer actions reuse that same session. The launcher calls the active
207
+ `surfaceRuntime.push(...)`, preserves the live parent frame and returns through
208
+ the shell Back action instead of stacking a second overlay. The dynamic form
209
+ registers its dirty-state confirmation through the active frame guard, so Back,
210
+ Escape, backdrop and shell close share one discard policy. This is runtime-only:
211
+ the navigation stack, component references and guards must never be serialized
212
+ into CRUD metadata or `x-ui`.
213
+
214
+ `SURFACE_DRAWER_REF` and `SURFACE_DRAWER_CONTENT_DATA` are the canonical DI
215
+ boundary for components hosted in a runtime frame. Hosts should not manufacture
216
+ an equivalent local token or pass the drawer reference through form values.
217
+
205
218
  Route mode has a different lifecycle because `@praxisui/crud` does not own the
206
219
  destination component. Routed edit hosts must load the entity through the
207
220
  canonical read contract and materialize identity from its response schema. Do
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-08-04T23:26:34.977Z",
3
+ "generatedAt": "2026-08-05T00:54:29.592Z",
4
4
  "packageName": "@praxisui/crud",
5
- "packageVersion": "9.0.4-rc.27",
5
+ "packageVersion": "9.0.4-rc.29",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 2,
@@ -1,12 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, InjectionToken, inject, input, signal, computed, effect, ChangeDetectionStrategy, Component, EventEmitter, DestroyRef, ChangeDetectorRef, Injector, ViewChild, Output, Input, Inject, ViewEncapsulation, ENVIRONMENT_INITIALIZER } from '@angular/core';
2
+ import { Injectable, InjectionToken, inject, input, signal, computed, effect, ChangeDetectionStrategy, Component, EventEmitter, DestroyRef, ChangeDetectorRef, Injector, ViewChild, Output, Input, Optional, Inject, ViewEncapsulation, ENVIRONMENT_INITIALIZER } from '@angular/core';
3
3
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
4
  import { HttpClient } from '@angular/common/http';
5
5
  import { Router, ActivatedRoute, RouterLink } from '@angular/router';
6
6
  import { MatSnackBar } from '@angular/material/snack-bar';
7
7
  import { firstValueFrom, BehaviorSubject, Subscription } from 'rxjs';
8
8
  import * as i2$1 from '@praxisui/core';
9
- import { ASYNC_CONFIG_STORAGE, GlobalConfigService, CrudOperationResolutionService, fillUndefined, SETTINGS_PANEL_DATA, PraxisI18nService, PraxisIconDirective, providePraxisI18nConfig, createDefaultTableConfig, EnterpriseRuntimeContextService, GLOBAL_SURFACE_SERVICE, SurfaceOutletRegistryService, ComponentKeyService, ResourceDiscoveryService, ResourceActionOpenAdapterService, ResourceSurfaceOpenAdapterService, normalizeSurfaceOperationContext, translateUnavailableWorkflowMessage, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, PraxisIconButtonComponent, PraxisResourceIdentityComponent, GenericCrudService, ComponentMetadataRegistry } from '@praxisui/core';
9
+ import { ASYNC_CONFIG_STORAGE, GlobalConfigService, CrudOperationResolutionService, fillUndefined, SETTINGS_PANEL_DATA, PraxisI18nService, PraxisIconDirective, providePraxisI18nConfig, createDefaultTableConfig, EnterpriseRuntimeContextService, GLOBAL_SURFACE_SERVICE, SurfaceOutletRegistryService, ComponentKeyService, ResourceDiscoveryService, ResourceActionOpenAdapterService, ResourceSurfaceOpenAdapterService, normalizeSurfaceOperationContext, translateUnavailableWorkflowMessage, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, SURFACE_DRAWER_REF, SURFACE_DRAWER_CONTENT_DATA, PraxisIconButtonComponent, PraxisResourceIdentityComponent, GenericCrudService, ComponentMetadataRegistry } from '@praxisui/core';
10
10
  import { SettingsPanelService } from '@praxisui/settings-panel';
11
11
  import { PraxisTableInlineAuthoringEditorComponent, PraxisTable } from '@praxisui/table';
12
12
  import { ConfirmDialogComponent } from '@praxisui/dynamic-fields';
@@ -104,6 +104,7 @@ class CrudLauncherService {
104
104
  global = inject(GlobalConfigService);
105
105
  operationResolver = inject(CrudOperationResolutionService);
106
106
  dialogHostPromise;
107
+ nextSurfaceFrameId = 0;
107
108
  drawerAdapter = (() => {
108
109
  try {
109
110
  return inject(CRUD_DRAWER_ADAPTER);
@@ -145,6 +146,31 @@ class CrudLauncherService {
145
146
  ? runtime?.resourceIdentity ?? null
146
147
  : null;
147
148
  const contextIdentity = runtime?.contextIdentity ?? null;
149
+ const dialogData = {
150
+ action: actionForLaunch,
151
+ row,
152
+ metadata: merged.metadata,
153
+ inputs,
154
+ presentation: mode === 'drawer' ? 'drawer' : 'modal',
155
+ resourceIdentity,
156
+ contextIdentity,
157
+ };
158
+ if (mode === 'drawer' && runtime?.surfaceRuntime?.push) {
159
+ const frameRef = await runtime.surfaceRuntime.push({
160
+ id: this.buildSurfaceFrameId(actionForLaunch),
161
+ title: this.resolveSurfaceFrameTitle(actionForLaunch, merged.metadata),
162
+ titleIcon: stringOrUndefined$1(actionForLaunch['icon']),
163
+ subtitle: this.resolveSurfaceFrameSubtitle(actionForLaunch, merged.metadata),
164
+ content: {
165
+ component: await (prefetchedDialogHost ?? this.loadDialogHost()),
166
+ inputs: { data: dialogData },
167
+ },
168
+ });
169
+ if (frameRef) {
170
+ this.bindSurfaceFrameLifecycle(frameRef, drawerCallbacks);
171
+ return { mode };
172
+ }
173
+ }
148
174
  if (mode === 'drawer' && this.drawerAdapter) {
149
175
  await Promise.resolve(this.drawerAdapter.open({
150
176
  action: actionForLaunch,
@@ -207,14 +233,9 @@ class CrudLauncherService {
207
233
  position: dialogPosition,
208
234
  ariaLabelledBy: 'crudDialogTitle',
209
235
  data: {
210
- action: actionForLaunch,
211
- row,
212
- metadata: merged.metadata,
213
- inputs,
236
+ ...dialogData,
214
237
  presentation: drawerMode ? 'drawer' : 'modal',
215
238
  dialogPosition,
216
- resourceIdentity,
217
- contextIdentity,
218
239
  },
219
240
  });
220
241
  if (drawerMode) {
@@ -484,6 +505,46 @@ class CrudLauncherService {
484
505
  schemaType: 'request',
485
506
  };
486
507
  }
508
+ buildSurfaceFrameId(action) {
509
+ this.nextSurfaceFrameId += 1;
510
+ return `crud:${action.action}:${action.formId || 'form'}:${this.nextSurfaceFrameId}`;
511
+ }
512
+ resolveSurfaceFrameTitle(action, metadata) {
513
+ return stringOrUndefined$1(action['label'])
514
+ ?? stringOrUndefined$1(metadata.form?.['title'])
515
+ ?? stringOrUndefined$1(metadata.table?.['title'])
516
+ ?? (action.action === 'create'
517
+ ? 'Adicionar registro'
518
+ : action.action === 'view'
519
+ ? 'Consultar registro'
520
+ : 'Editar registro');
521
+ }
522
+ resolveSurfaceFrameSubtitle(action, metadata) {
523
+ return stringOrUndefined$1(action['description'])
524
+ ?? stringOrUndefined$1(action['tooltip'])
525
+ ?? stringOrUndefined$1(metadata.form?.['description'])
526
+ ?? stringOrUndefined$1(metadata.table?.['subtitle']);
527
+ }
528
+ bindSurfaceFrameLifecycle(frameRef, callbacks) {
529
+ let semanticResultHandled = false;
530
+ const handleResult = (value) => {
531
+ if (semanticResultHandled)
532
+ return;
533
+ const result = toCrudDrawerResult(value);
534
+ if (!result || result.type === 'close')
535
+ return;
536
+ semanticResultHandled = true;
537
+ callbacks?.onResult?.(result);
538
+ };
539
+ frameRef.result$?.pipe(take(1)).subscribe(handleResult);
540
+ frameRef.closed$.pipe(take(1)).subscribe((value) => {
541
+ handleResult(value);
542
+ callbacks?.onClose?.();
543
+ if (!semanticResultHandled) {
544
+ callbacks?.onResult?.({ type: 'close' });
545
+ }
546
+ });
547
+ }
487
548
  async mergeCrudOverrides(metadata, action, componentKeyId) {
488
549
  try {
489
550
  if (!componentKeyId)
@@ -565,6 +626,10 @@ function toCrudDrawerResult(value) {
565
626
  ? result
566
627
  : undefined;
567
628
  }
629
+ function stringOrUndefined$1(value) {
630
+ const text = String(value ?? '').trim();
631
+ return text || undefined;
632
+ }
568
633
 
569
634
  const DOCUMENT_KIND = 'praxis.crud.editor';
570
635
  const DOCUMENT_VERSION = 1;
@@ -3922,6 +3987,7 @@ class PraxisCrudComponent {
3922
3987
  links: this.resolveCrudRuntimeLinks(effectiveAction.action, contextualRow),
3923
3988
  resourceIdentity,
3924
3989
  contextIdentity,
3990
+ surfaceRuntime: this.resolveSurfaceRuntime(),
3925
3991
  });
3926
3992
  this.afterOpen.emit({ mode, action: effectiveAction.action });
3927
3993
  if (mode === 'drawer') {
@@ -4188,6 +4254,7 @@ class PraxisCrudComponent {
4188
4254
  }, {
4189
4255
  capabilities: this.collectionCapabilities,
4190
4256
  links: this.tableCollectionLinks,
4257
+ surfaceRuntime: this.resolveSurfaceRuntime(),
4191
4258
  });
4192
4259
  this.afterOpen.emit({ mode, action: normalizedAction });
4193
4260
  if (mode !== 'drawer' && ref) {
@@ -4207,6 +4274,14 @@ class PraxisCrudComponent {
4207
4274
  }
4208
4275
  this.table.refetch();
4209
4276
  }
4277
+ resolveSurfaceRuntime() {
4278
+ const candidate = this.context?.['surfaceRuntime'];
4279
+ if (!candidate || typeof candidate !== 'object') {
4280
+ return null;
4281
+ }
4282
+ const runtime = candidate;
4283
+ return typeof runtime.push === 'function' ? runtime : null;
4284
+ }
4210
4285
  tryRefreshMaterializedLocalData() {
4211
4286
  const readUrl = this.resolveMaterializedReadUrl();
4212
4287
  if (!readUrl || this.resolveResourcePath(this.resolvedMetadata)) {
@@ -5408,10 +5483,10 @@ function debugCrudHost(message, data) {
5408
5483
  }
5409
5484
  class DynamicFormDialogHostComponent {
5410
5485
  dialogRef;
5411
- data;
5412
5486
  dialogService;
5413
5487
  crud;
5414
5488
  configStorage;
5489
+ surfaceRef;
5415
5490
  formComp;
5416
5491
  modal = {};
5417
5492
  presentation = 'modal';
@@ -5421,6 +5496,7 @@ class DynamicFormDialogHostComponent {
5421
5496
  rememberState = false;
5422
5497
  stateKey;
5423
5498
  backDefaults = {};
5499
+ terminalClose = false;
5424
5500
  destroyRef = inject(DestroyRef);
5425
5501
  i18n = inject(PraxisI18nService);
5426
5502
  resourcePath;
@@ -5440,6 +5516,8 @@ class DynamicFormDialogHostComponent {
5440
5516
  mode = 'create';
5441
5517
  backConfig;
5442
5518
  idField = 'id';
5519
+ isSurfaceFrame;
5520
+ data;
5443
5521
  texts = {
5444
5522
  title: 'Formulário',
5445
5523
  close: 'Fechar',
@@ -5454,14 +5532,25 @@ class DynamicFormDialogHostComponent {
5454
5532
  discardConfirm: 'Descartar',
5455
5533
  discardCancel: 'Cancelar',
5456
5534
  };
5457
- constructor(dialogRef, data, dialogService, crud, configStorage) {
5535
+ constructor(dialogRef, dialogData, dialogService, crud, configStorage, surfaceRef = null, surfaceContentData = null) {
5458
5536
  this.dialogRef = dialogRef;
5459
- this.data = data;
5460
5537
  this.dialogService = dialogService;
5461
5538
  this.crud = crud;
5462
5539
  this.configStorage = configStorage;
5463
- this.dialogRef.disableClose = true;
5464
- this.presentation = this.data.presentation === 'drawer' ? 'drawer' : 'modal';
5540
+ this.surfaceRef = surfaceRef;
5541
+ this.data = dialogData ?? surfaceContentData?.['data'] ?? {};
5542
+ this.isSurfaceFrame = !!this.surfaceRef;
5543
+ if (!this.dialogRef && !this.surfaceRef) {
5544
+ throw new Error('DynamicFormDialogHostComponent requires a MatDialogRef or an active SurfaceDrawerRef.');
5545
+ }
5546
+ if (this.dialogRef) {
5547
+ this.dialogRef.disableClose = true;
5548
+ }
5549
+ this.presentation = this.isSurfaceFrame
5550
+ ? 'surface-frame'
5551
+ : this.data.presentation === 'drawer'
5552
+ ? 'drawer'
5553
+ : 'modal';
5465
5554
  // i18n
5466
5555
  this.texts = {
5467
5556
  ...this.texts,
@@ -5521,14 +5610,14 @@ class DynamicFormDialogHostComponent {
5521
5610
  backConfig: this.backConfig,
5522
5611
  });
5523
5612
  // Esc
5524
- if (!this.modal.disableCloseOnEsc) {
5613
+ if (this.dialogRef && !this.modal.disableCloseOnEsc) {
5525
5614
  this.dialogRef
5526
5615
  .keydownEvents()
5527
5616
  .pipe(filter((e) => e.key === 'Escape'), takeUntilDestroyed(this.destroyRef))
5528
5617
  .subscribe(() => this.onCancel());
5529
5618
  }
5530
5619
  // Backdrop
5531
- if (!this.modal.disableCloseOnBackdrop) {
5620
+ if (this.dialogRef && !this.modal.disableCloseOnBackdrop) {
5532
5621
  this.dialogRef
5533
5622
  .backdropClick()
5534
5623
  .pipe(takeUntilDestroyed(this.destroyRef))
@@ -5536,7 +5625,7 @@ class DynamicFormDialogHostComponent {
5536
5625
  }
5537
5626
  // Salvar estado ao fechar, se aplicável
5538
5627
  this.dialogRef
5539
- .afterClosed()
5628
+ ?.afterClosed()
5540
5629
  .pipe(takeUntilDestroyed(this.destroyRef))
5541
5630
  .subscribe(() => this.saveState());
5542
5631
  }
@@ -5720,6 +5809,13 @@ class DynamicFormDialogHostComponent {
5720
5809
  });
5721
5810
  }
5722
5811
  }
5812
+ ngAfterViewInit() {
5813
+ if (!this.surfaceRef?.setCanLeave) {
5814
+ return;
5815
+ }
5816
+ this.surfaceRef.updateTitle?.(this.dialogTitle);
5817
+ this.surfaceRef.setCanLeave(() => this.canLeaveSurfaceFrame());
5818
+ }
5723
5819
  onSave(result) {
5724
5820
  const stage = getFormSubmitStage(result);
5725
5821
  if (stage === 'before' || stage === 'error') {
@@ -5729,9 +5825,29 @@ class DynamicFormDialogHostComponent {
5729
5825
  this.saveState();
5730
5826
  return;
5731
5827
  }
5732
- this.dialogRef.close({ type: 'save', data: result });
5828
+ this.terminalClose = true;
5829
+ this.formComp?.form.markAsPristine();
5830
+ if (this.surfaceRef) {
5831
+ this.surfaceRef.emitResult?.({ type: 'save', data: result });
5832
+ this.surfaceRef.close?.({ type: 'save', data: result });
5833
+ }
5834
+ else {
5835
+ this.dialogRef?.close({ type: 'save', data: result });
5836
+ }
5733
5837
  }
5734
5838
  onCancel() {
5839
+ if (this.surfaceRef) {
5840
+ void this.canLeaveSurfaceFrame().then((allowed) => {
5841
+ if (!allowed)
5842
+ return;
5843
+ // `close()` on a nested frame consults the registered guard again.
5844
+ // Mark this user-confirmed transition so both root and nested frames
5845
+ // share one prompt and one close result.
5846
+ this.terminalClose = true;
5847
+ this.surfaceRef?.close?.({ type: 'close' });
5848
+ });
5849
+ return;
5850
+ }
5735
5851
  const dirty = this.formComp?.form.dirty;
5736
5852
  const backCfg = (this.data.action?.back || this.data.metadata?.defaults?.back) || {};
5737
5853
  const confirm = backCfg.confirmOnDirty ?? true;
@@ -5749,12 +5865,32 @@ class DynamicFormDialogHostComponent {
5749
5865
  ref
5750
5866
  .afterClosed()
5751
5867
  .pipe(filter((confirmed) => !!confirmed), takeUntilDestroyed(this.destroyRef))
5752
- .subscribe(() => this.dialogRef.close({ type: 'close' }));
5868
+ .subscribe(() => this.dialogRef?.close({ type: 'close' }));
5753
5869
  }
5754
5870
  else {
5755
- this.dialogRef.close({ type: 'close' });
5871
+ this.dialogRef?.close({ type: 'close' });
5756
5872
  }
5757
5873
  }
5874
+ async canLeaveSurfaceFrame() {
5875
+ if (this.terminalClose || !this.formComp?.form.dirty) {
5876
+ return true;
5877
+ }
5878
+ const backCfg = (this.data.action?.back || this.data.metadata?.defaults?.back) || {};
5879
+ if (backCfg.confirmOnDirty === false) {
5880
+ return true;
5881
+ }
5882
+ const ref = this.dialogService.open(ConfirmDialogComponent, {
5883
+ data: {
5884
+ title: this.texts.discardTitle,
5885
+ message: this.texts.discardMessage,
5886
+ confirmText: this.texts.discardConfirm,
5887
+ cancelText: this.texts.discardCancel,
5888
+ type: 'warning',
5889
+ },
5890
+ autoFocus: false,
5891
+ });
5892
+ return !!(await firstValueFrom(ref.afterClosed().pipe(take(1))));
5893
+ }
5758
5894
  toggleMaximize(initial = false) {
5759
5895
  const pane = this.resolveOverlayPane();
5760
5896
  if (!initial && !this.maximized && pane) {
@@ -5768,7 +5904,12 @@ class DynamicFormDialogHostComponent {
5768
5904
  const height = this.maximized
5769
5905
  ? `calc(100dvh - ${2 * (gap ?? 0)}px)`
5770
5906
  : this.initialSize.height;
5771
- this.dialogRef.updateSize(width, height);
5907
+ if (this.surfaceRef) {
5908
+ this.surfaceRef.updateSize?.(this.maximized ? 'full' : 'default');
5909
+ }
5910
+ else {
5911
+ this.dialogRef?.updateSize(width, height);
5912
+ }
5772
5913
  this.updateHostPosition();
5773
5914
  if (pane && pane.classList.contains('pfx-dialog-pane')) {
5774
5915
  if (this.presentation === 'drawer') {
@@ -5825,6 +5966,9 @@ class DynamicFormDialogHostComponent {
5825
5966
  startMaximized: this.modal.startMaximized,
5826
5967
  fullscreenBreakpoint: this.modal.fullscreenBreakpoint,
5827
5968
  });
5969
+ if (this.surfaceRef) {
5970
+ return;
5971
+ }
5828
5972
  let shouldMax = false;
5829
5973
  if (saved && typeof saved.maximized === 'boolean') {
5830
5974
  shouldMax = !!saved.maximized;
@@ -5839,16 +5983,19 @@ class DynamicFormDialogHostComponent {
5839
5983
  this.toggleMaximize(true);
5840
5984
  }
5841
5985
  else if (this.initialSize.width || this.initialSize.height) {
5842
- this.dialogRef.updateSize(this.initialSize.width, this.initialSize.height);
5986
+ this.dialogRef?.updateSize(this.initialSize.width, this.initialSize.height);
5843
5987
  this.updateHostPosition();
5844
5988
  }
5845
5989
  }
5846
5990
  updateHostPosition() {
5991
+ if (this.surfaceRef) {
5992
+ return;
5993
+ }
5847
5994
  if (this.presentation !== 'drawer') {
5848
- this.dialogRef.updatePosition();
5995
+ this.dialogRef?.updatePosition();
5849
5996
  return;
5850
5997
  }
5851
- this.dialogRef.updatePosition(this.maximized
5998
+ this.dialogRef?.updatePosition(this.maximized
5852
5999
  ? this.resolveMaximizedDrawerPosition()
5853
6000
  : this.initialPosition);
5854
6001
  }
@@ -5863,11 +6010,12 @@ class DynamicFormDialogHostComponent {
5863
6010
  : { top: offset };
5864
6011
  return { ...horizontal, ...vertical };
5865
6012
  }
5866
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormDialogHostComponent, deps: [{ token: MatDialogRef }, { token: MAT_DIALOG_DATA }, { token: DialogService }, { token: i2$1.GenericCrudService }, { token: ASYNC_CONFIG_STORAGE }], target: i0.ɵɵFactoryTarget.Component });
5867
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: DynamicFormDialogHostComponent, isStandalone: true, selector: "praxis-dynamic-form-dialog-host", host: { properties: { "attr.data-density": "modal.density || \"default\"", "attr.data-presentation": "presentation", "class.praxis-drawer": "presentation === \"drawer\"" }, classAttribute: "praxis-dialog" }, providers: [
6013
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormDialogHostComponent, deps: [{ token: MatDialogRef, optional: true }, { token: MAT_DIALOG_DATA, optional: true }, { token: DialogService }, { token: i2$1.GenericCrudService }, { token: ASYNC_CONFIG_STORAGE }, { token: SURFACE_DRAWER_REF, optional: true }, { token: SURFACE_DRAWER_CONTENT_DATA, optional: true }], target: i0.ɵɵFactoryTarget.Component });
6014
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: DynamicFormDialogHostComponent, isStandalone: true, selector: "praxis-dynamic-form-dialog-host", host: { properties: { "attr.data-density": "modal.density || \"default\"", "attr.data-presentation": "presentation", "class.praxis-drawer": "presentation === \"drawer\"", "class.praxis-surface-frame": "isSurfaceFrame" }, classAttribute: "praxis-dialog" }, providers: [
5868
6015
  GenericCrudService,
5869
6016
  providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG),
5870
6017
  ], viewQueries: [{ propertyName: "formComp", first: true, predicate: PraxisDynamicForm, descendants: true }], ngImport: i0, template: `
6018
+ @if (!isSurfaceFrame) {
5871
6019
  <div mat-dialog-title class="dialog-header">
5872
6020
  @if (dialogIcon; as icon) {
5873
6021
  <span class="dialog-title-icon" aria-hidden="true">
@@ -5903,11 +6051,14 @@ class DynamicFormDialogHostComponent {
5903
6051
  cdkFocusInitial
5904
6052
  ></button>
5905
6053
  </div>
6054
+ }
5906
6055
 
5907
6056
  <mat-dialog-content
5908
6057
  class="dialog-content"
5909
- aria-labelledby="crudDialogTitle"
5910
- [attr.aria-describedby]="dialogSubtitle ? 'crudDialogDescription' : null"
6058
+ [attr.aria-labelledby]="isSurfaceFrame ? null : 'crudDialogTitle'"
6059
+ [attr.aria-describedby]="
6060
+ !isSurfaceFrame && dialogSubtitle ? 'crudDialogDescription' : null
6061
+ "
5911
6062
  >
5912
6063
  @if (showContextIdentity || resourceIdentity) {
5913
6064
  <div class="crud-operation-context" data-testid="crud-operation-context">
@@ -5960,7 +6111,7 @@ class DynamicFormDialogHostComponent {
5960
6111
  (formCancel)="onCancel()"
5961
6112
  ></praxis-dynamic-form>
5962
6113
  </mat-dialog-content>
5963
- `, isInline: true, styles: ["praxis-dynamic-form-dialog-host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}praxis-dynamic-form-dialog-host[data-density=compact]{--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}praxis-dynamic-form-dialog-host .dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);min-height:var(--dlg-header-h);padding:10px var(--dlg-pad);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-heading{display:grid;min-width:0;gap:2px}praxis-dynamic-form-dialog-host .dialog-title-icon{display:inline-grid;flex:0 0 auto;width:36px;height:36px;place-items:center;border-radius:10px;color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent)}praxis-dynamic-form-dialog-host .dialog-title-icon mat-icon{width:22px;height:22px;font-size:22px}praxis-dynamic-form-dialog-host .dialog-subtitle{display:-webkit-box;overflow:hidden;color:var(--md-sys-color-on-surface-variant);font-size:.82rem;font-weight:400;line-height:1.35;-webkit-box-orient:vertical;-webkit-line-clamp:2}praxis-dynamic-form-dialog-host .spacer{flex:1}praxis-dynamic-form-dialog-host .dialog-content{flex:1 1 auto;overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}praxis-dynamic-form-dialog-host .crud-operation-context{position:sticky;top:0;z-index:1;display:grid;gap:.75rem;margin-block-end:var(--dlg-pad);padding-block:.5rem .75rem;background:var(--pfx-form-surface, var(--md-sys-color-surface));border-bottom:1px solid var(--md-sys-color-outline-variant)}praxis-dynamic-form-dialog-host .crud-resource-identity{display:grid;gap:.25rem;min-width:0}praxis-dynamic-form-dialog-host .crud-resource-identity__label{color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-label-medium, 500 .75rem/1rem inherit)}praxis-dynamic-form-dialog-host.praxis-drawer{--dlg-header-h: 72px;width:100%;min-width:0;max-width:100vw;height:100dvh;background:var(--pfx-form-surface, var(--md-sys-color-surface));color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-header{background:color-mix(in srgb,var(--md-sys-color-surface-container-high, var(--md-sys-color-surface)),transparent 4%)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content{max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button:hover{color:var(--md-sys-color-primary)}praxis-dynamic-form-dialog-host .dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}.pfx-blur-backdrop{background-color:var(--pfx-backdrop, rgba(15, 23, 42, .42))!important;backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%);-webkit-backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%)}.pfx-transparent-backdrop{background-color:transparent!important}.cdk-overlay-pane.pfx-dialog-pane{overflow:hidden;transition:width .2s ease,height .2s ease,margin .2s ease}.cdk-overlay-pane.pfx-dialog-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane .mdc-dialog__surface{display:flex;flex-direction:column;width:100%;min-width:0;height:100%;max-height:inherit;overflow:hidden;background:var(--pfx-form-surface, var(--md-sys-color-surface))!important;border:1px solid var(--pfx-form-stroke, var(--md-sys-color-outline-variant))!important}.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mdc-dialog__surface{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cdk-overlay-pane.pfx-drawer-pane{margin:0!important;border-radius:0!important;box-shadow:var(--md-sys-elevation-level3, 0 24px 80px rgba(15, 23, 42, .28)),0 0 0 1px color-mix(in srgb,var(--md-sys-color-outline, #64748b) 24%,transparent)}.cdk-overlay-pane.pfx-drawer-pane.pfx-drawer-maximized{margin:var(--pfx-drawer-edge-gap, 8px)!important}.cdk-overlay-pane.pfx-drawer-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-drawer-pane .mdc-dialog__surface{min-width:0!important;border-radius:0!important}\n"], dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisResourceIdentityComponent, selector: "praxis-resource-identity", inputs: ["identity", "emptyTitle", "density", "showMetadataLabels", "showKeyLabel", "ariaLabel"] }, { kind: "component", type: PraxisDynamicForm, selector: "praxis-dynamic-form", inputs: ["resourcePath", "resourceId", "initialValue", "editorialContext", "mode", "config", "actions", "schemaSource", "schemaUrl", "readUrl", "submitUrl", "submitMethod", "submitIdempotencyKey", "submitCorrelationId", "submitResourceVersion", "responseSchemaUrl", "apiEndpointKey", "apiUrlEntry", "enableCustomization", "showAiAssistant", "formId", "componentInstanceId", "configPersistenceStrategy", "layout", "generatedLayoutPreset", "layoutPolicy", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "visibleGlobal", "fieldIconPolicy", "domainRules", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "configPatchChange", "formReady", "valueChange", "syncCompleted", "initializationError", "loadingStateChange", "enableCustomizationChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError", "ruleDiagnosticsChange"] }], encapsulation: i0.ViewEncapsulation.None });
6114
+ `, isInline: true, styles: ["praxis-dynamic-form-dialog-host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}praxis-dynamic-form-dialog-host[data-density=compact]{--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}praxis-dynamic-form-dialog-host .dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);min-height:var(--dlg-header-h);padding:10px var(--dlg-pad);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-heading{display:grid;min-width:0;gap:2px}praxis-dynamic-form-dialog-host .dialog-title-icon{display:inline-grid;flex:0 0 auto;width:36px;height:36px;place-items:center;border-radius:10px;color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent)}praxis-dynamic-form-dialog-host .dialog-title-icon mat-icon{width:22px;height:22px;font-size:22px}praxis-dynamic-form-dialog-host .dialog-subtitle{display:-webkit-box;overflow:hidden;color:var(--md-sys-color-on-surface-variant);font-size:.82rem;font-weight:400;line-height:1.35;-webkit-box-orient:vertical;-webkit-line-clamp:2}praxis-dynamic-form-dialog-host .spacer{flex:1}praxis-dynamic-form-dialog-host .dialog-content{flex:1 1 auto;overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}praxis-dynamic-form-dialog-host .crud-operation-context{position:sticky;top:0;z-index:1;display:grid;gap:.75rem;margin-block-end:var(--dlg-pad);padding-block:.5rem .75rem;background:var(--pfx-form-surface, var(--md-sys-color-surface));border-bottom:1px solid var(--md-sys-color-outline-variant)}praxis-dynamic-form-dialog-host .crud-resource-identity{display:grid;gap:.25rem;min-width:0}praxis-dynamic-form-dialog-host .crud-resource-identity__label{color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-label-medium, 500 .75rem/1rem inherit)}praxis-dynamic-form-dialog-host.praxis-drawer{--dlg-header-h: 72px;width:100%;min-width:0;max-width:100vw;height:100dvh;background:var(--pfx-form-surface, var(--md-sys-color-surface));color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-header{background:color-mix(in srgb,var(--md-sys-color-surface-container-high, var(--md-sys-color-surface)),transparent 4%)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content{max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host.praxis-surface-frame .dialog-content{max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button:hover{color:var(--md-sys-color-primary)}praxis-dynamic-form-dialog-host .dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}.pfx-blur-backdrop{background-color:var(--pfx-backdrop, rgba(15, 23, 42, .42))!important;backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%);-webkit-backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%)}.pfx-transparent-backdrop{background-color:transparent!important}.cdk-overlay-pane.pfx-dialog-pane{overflow:hidden;transition:width .2s ease,height .2s ease,margin .2s ease}.cdk-overlay-pane.pfx-dialog-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane .mdc-dialog__surface{display:flex;flex-direction:column;width:100%;min-width:0;height:100%;max-height:inherit;overflow:hidden;background:var(--pfx-form-surface, var(--md-sys-color-surface))!important;border:1px solid var(--pfx-form-stroke, var(--md-sys-color-outline-variant))!important}.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mdc-dialog__surface{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cdk-overlay-pane.pfx-drawer-pane{margin:0!important;border-radius:0!important;box-shadow:var(--md-sys-elevation-level3, 0 24px 80px rgba(15, 23, 42, .28)),0 0 0 1px color-mix(in srgb,var(--md-sys-color-outline, #64748b) 24%,transparent)}.cdk-overlay-pane.pfx-drawer-pane.pfx-drawer-maximized{margin:var(--pfx-drawer-edge-gap, 8px)!important}.cdk-overlay-pane.pfx-drawer-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-drawer-pane .mdc-dialog__surface{min-width:0!important;border-radius:0!important}\n"], dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisResourceIdentityComponent, selector: "praxis-resource-identity", inputs: ["identity", "emptyTitle", "density", "showMetadataLabels", "showKeyLabel", "ariaLabel"] }, { kind: "component", type: PraxisDynamicForm, selector: "praxis-dynamic-form", inputs: ["resourcePath", "resourceId", "initialValue", "editorialContext", "mode", "config", "actions", "schemaSource", "schemaUrl", "readUrl", "submitUrl", "submitMethod", "submitIdempotencyKey", "submitCorrelationId", "submitResourceVersion", "responseSchemaUrl", "apiEndpointKey", "apiUrlEntry", "enableCustomization", "showAiAssistant", "formId", "componentInstanceId", "configPersistenceStrategy", "layout", "generatedLayoutPreset", "layoutPolicy", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "visibleGlobal", "fieldIconPolicy", "domainRules", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "configPatchChange", "formReady", "valueChange", "syncCompleted", "initializationError", "loadingStateChange", "enableCustomizationChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError", "ruleDiagnosticsChange"] }], encapsulation: i0.ViewEncapsulation.None });
5964
6115
  }
5965
6116
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
5966
6117
  type: Component,
@@ -5979,7 +6130,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5979
6130
  '[attr.data-density]': 'modal.density || "default"',
5980
6131
  '[attr.data-presentation]': 'presentation',
5981
6132
  '[class.praxis-drawer]': 'presentation === "drawer"',
6133
+ '[class.praxis-surface-frame]': 'isSurfaceFrame',
5982
6134
  }, template: `
6135
+ @if (!isSurfaceFrame) {
5983
6136
  <div mat-dialog-title class="dialog-header">
5984
6137
  @if (dialogIcon; as icon) {
5985
6138
  <span class="dialog-title-icon" aria-hidden="true">
@@ -6015,11 +6168,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
6015
6168
  cdkFocusInitial
6016
6169
  ></button>
6017
6170
  </div>
6171
+ }
6018
6172
 
6019
6173
  <mat-dialog-content
6020
6174
  class="dialog-content"
6021
- aria-labelledby="crudDialogTitle"
6022
- [attr.aria-describedby]="dialogSubtitle ? 'crudDialogDescription' : null"
6175
+ [attr.aria-labelledby]="isSurfaceFrame ? null : 'crudDialogTitle'"
6176
+ [attr.aria-describedby]="
6177
+ !isSurfaceFrame && dialogSubtitle ? 'crudDialogDescription' : null
6178
+ "
6023
6179
  >
6024
6180
  @if (showContextIdentity || resourceIdentity) {
6025
6181
  <div class="crud-operation-context" data-testid="crud-operation-context">
@@ -6072,16 +6228,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
6072
6228
  (formCancel)="onCancel()"
6073
6229
  ></praxis-dynamic-form>
6074
6230
  </mat-dialog-content>
6075
- `, styles: ["praxis-dynamic-form-dialog-host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}praxis-dynamic-form-dialog-host[data-density=compact]{--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}praxis-dynamic-form-dialog-host .dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);min-height:var(--dlg-header-h);padding:10px var(--dlg-pad);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-heading{display:grid;min-width:0;gap:2px}praxis-dynamic-form-dialog-host .dialog-title-icon{display:inline-grid;flex:0 0 auto;width:36px;height:36px;place-items:center;border-radius:10px;color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent)}praxis-dynamic-form-dialog-host .dialog-title-icon mat-icon{width:22px;height:22px;font-size:22px}praxis-dynamic-form-dialog-host .dialog-subtitle{display:-webkit-box;overflow:hidden;color:var(--md-sys-color-on-surface-variant);font-size:.82rem;font-weight:400;line-height:1.35;-webkit-box-orient:vertical;-webkit-line-clamp:2}praxis-dynamic-form-dialog-host .spacer{flex:1}praxis-dynamic-form-dialog-host .dialog-content{flex:1 1 auto;overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}praxis-dynamic-form-dialog-host .crud-operation-context{position:sticky;top:0;z-index:1;display:grid;gap:.75rem;margin-block-end:var(--dlg-pad);padding-block:.5rem .75rem;background:var(--pfx-form-surface, var(--md-sys-color-surface));border-bottom:1px solid var(--md-sys-color-outline-variant)}praxis-dynamic-form-dialog-host .crud-resource-identity{display:grid;gap:.25rem;min-width:0}praxis-dynamic-form-dialog-host .crud-resource-identity__label{color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-label-medium, 500 .75rem/1rem inherit)}praxis-dynamic-form-dialog-host.praxis-drawer{--dlg-header-h: 72px;width:100%;min-width:0;max-width:100vw;height:100dvh;background:var(--pfx-form-surface, var(--md-sys-color-surface));color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-header{background:color-mix(in srgb,var(--md-sys-color-surface-container-high, var(--md-sys-color-surface)),transparent 4%)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content{max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button:hover{color:var(--md-sys-color-primary)}praxis-dynamic-form-dialog-host .dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}.pfx-blur-backdrop{background-color:var(--pfx-backdrop, rgba(15, 23, 42, .42))!important;backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%);-webkit-backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%)}.pfx-transparent-backdrop{background-color:transparent!important}.cdk-overlay-pane.pfx-dialog-pane{overflow:hidden;transition:width .2s ease,height .2s ease,margin .2s ease}.cdk-overlay-pane.pfx-dialog-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane .mdc-dialog__surface{display:flex;flex-direction:column;width:100%;min-width:0;height:100%;max-height:inherit;overflow:hidden;background:var(--pfx-form-surface, var(--md-sys-color-surface))!important;border:1px solid var(--pfx-form-stroke, var(--md-sys-color-outline-variant))!important}.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mdc-dialog__surface{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cdk-overlay-pane.pfx-drawer-pane{margin:0!important;border-radius:0!important;box-shadow:var(--md-sys-elevation-level3, 0 24px 80px rgba(15, 23, 42, .28)),0 0 0 1px color-mix(in srgb,var(--md-sys-color-outline, #64748b) 24%,transparent)}.cdk-overlay-pane.pfx-drawer-pane.pfx-drawer-maximized{margin:var(--pfx-drawer-edge-gap, 8px)!important}.cdk-overlay-pane.pfx-drawer-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-drawer-pane .mdc-dialog__surface{min-width:0!important;border-radius:0!important}\n"] }]
6231
+ `, styles: ["praxis-dynamic-form-dialog-host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}praxis-dynamic-form-dialog-host[data-density=compact]{--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}praxis-dynamic-form-dialog-host .dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);min-height:var(--dlg-header-h);padding:10px var(--dlg-pad);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-heading{display:grid;min-width:0;gap:2px}praxis-dynamic-form-dialog-host .dialog-title-icon{display:inline-grid;flex:0 0 auto;width:36px;height:36px;place-items:center;border-radius:10px;color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent)}praxis-dynamic-form-dialog-host .dialog-title-icon mat-icon{width:22px;height:22px;font-size:22px}praxis-dynamic-form-dialog-host .dialog-subtitle{display:-webkit-box;overflow:hidden;color:var(--md-sys-color-on-surface-variant);font-size:.82rem;font-weight:400;line-height:1.35;-webkit-box-orient:vertical;-webkit-line-clamp:2}praxis-dynamic-form-dialog-host .spacer{flex:1}praxis-dynamic-form-dialog-host .dialog-content{flex:1 1 auto;overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}praxis-dynamic-form-dialog-host .crud-operation-context{position:sticky;top:0;z-index:1;display:grid;gap:.75rem;margin-block-end:var(--dlg-pad);padding-block:.5rem .75rem;background:var(--pfx-form-surface, var(--md-sys-color-surface));border-bottom:1px solid var(--md-sys-color-outline-variant)}praxis-dynamic-form-dialog-host .crud-resource-identity{display:grid;gap:.25rem;min-width:0}praxis-dynamic-form-dialog-host .crud-resource-identity__label{color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-label-medium, 500 .75rem/1rem inherit)}praxis-dynamic-form-dialog-host.praxis-drawer{--dlg-header-h: 72px;width:100%;min-width:0;max-width:100vw;height:100dvh;background:var(--pfx-form-surface, var(--md-sys-color-surface));color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-header{background:color-mix(in srgb,var(--md-sys-color-surface-container-high, var(--md-sys-color-surface)),transparent 4%)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content{max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host.praxis-surface-frame .dialog-content{max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button:hover{color:var(--md-sys-color-primary)}praxis-dynamic-form-dialog-host .dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}.pfx-blur-backdrop{background-color:var(--pfx-backdrop, rgba(15, 23, 42, .42))!important;backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%);-webkit-backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%)}.pfx-transparent-backdrop{background-color:transparent!important}.cdk-overlay-pane.pfx-dialog-pane{overflow:hidden;transition:width .2s ease,height .2s ease,margin .2s ease}.cdk-overlay-pane.pfx-dialog-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane .mdc-dialog__surface{display:flex;flex-direction:column;width:100%;min-width:0;height:100%;max-height:inherit;overflow:hidden;background:var(--pfx-form-surface, var(--md-sys-color-surface))!important;border:1px solid var(--pfx-form-stroke, var(--md-sys-color-outline-variant))!important}.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mdc-dialog__surface{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cdk-overlay-pane.pfx-drawer-pane{margin:0!important;border-radius:0!important;box-shadow:var(--md-sys-elevation-level3, 0 24px 80px rgba(15, 23, 42, .28)),0 0 0 1px color-mix(in srgb,var(--md-sys-color-outline, #64748b) 24%,transparent)}.cdk-overlay-pane.pfx-drawer-pane.pfx-drawer-maximized{margin:var(--pfx-drawer-edge-gap, 8px)!important}.cdk-overlay-pane.pfx-drawer-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-drawer-pane .mdc-dialog__surface{min-width:0!important;border-radius:0!important}\n"] }]
6076
6232
  }], ctorParameters: () => [{ type: undefined, decorators: [{
6233
+ type: Optional
6234
+ }, {
6077
6235
  type: Inject,
6078
6236
  args: [MatDialogRef]
6079
6237
  }] }, { type: undefined, decorators: [{
6238
+ type: Optional
6239
+ }, {
6080
6240
  type: Inject,
6081
6241
  args: [MAT_DIALOG_DATA]
6082
6242
  }] }, { type: DialogService }, { type: i2$1.GenericCrudService }, { type: undefined, decorators: [{
6083
6243
  type: Inject,
6084
6244
  args: [ASYNC_CONFIG_STORAGE]
6245
+ }] }, { type: undefined, decorators: [{
6246
+ type: Optional
6247
+ }, {
6248
+ type: Inject,
6249
+ args: [SURFACE_DRAWER_REF]
6250
+ }] }, { type: undefined, decorators: [{
6251
+ type: Optional
6252
+ }, {
6253
+ type: Inject,
6254
+ args: [SURFACE_DRAWER_CONTENT_DATA]
6085
6255
  }] }], propDecorators: { formComp: [{
6086
6256
  type: ViewChild,
6087
6257
  args: [PraxisDynamicForm]
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@praxisui/crud",
3
- "version": "9.0.4-rc.27",
3
+ "version": "9.0.4-rc.29",
4
4
  "description": "CRUD building blocks for Praxis UI: integrates dynamic forms and tables with unified configuration and services.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.0.0",
7
7
  "@angular/core": "^21.0.0",
8
- "@praxisui/dynamic-form": "^9.0.4-rc.27",
9
- "@praxisui/table": "^9.0.4-rc.27",
10
- "@praxisui/core": "^9.0.4-rc.27",
11
- "@praxisui/dynamic-fields": "^9.0.4-rc.27",
12
- "@praxisui/settings-panel": "^9.0.4-rc.27",
8
+ "@praxisui/dynamic-form": "^9.0.4-rc.29",
9
+ "@praxisui/table": "^9.0.4-rc.29",
10
+ "@praxisui/core": "^9.0.4-rc.29",
11
+ "@praxisui/dynamic-fields": "^9.0.4-rc.29",
12
+ "@praxisui/settings-panel": "^9.0.4-rc.29",
13
13
  "@angular/cdk": "^21.0.0",
14
14
  "@angular/forms": "^21.0.0",
15
15
  "@angular/material": "^21.0.0",
16
16
  "@angular/router": "^21.0.0",
17
- "@praxisui/ai": "^9.0.4-rc.27",
17
+ "@praxisui/ai": "^9.0.4-rc.29",
18
18
  "rxjs": "~7.8.0"
19
19
  },
20
20
  "dependencies": {
@@ -1,7 +1,7 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { NgZone, OnChanges, EventEmitter, SimpleChanges, OnInit, Provider, AfterViewInit, OnDestroy } from '@angular/core';
2
+ import { NgZone, OnChanges, EventEmitter, SimpleChanges, OnInit, AfterViewInit, Provider, OnDestroy } from '@angular/core';
3
3
  import * as _praxisui_core from '@praxisui/core';
4
- import { RowAction, ToolbarAction, ApiEndpoint, ApiUrlEntry, DynamicFormLayoutPolicy, BackConfig, TableConfig, PraxisDataQueryContext, FormConfig, ResourceActionCatalogItem, ResourceSurfaceCatalogItem, MaterializedResourceIdentity, LoadingState, RestApiLinks, ResourceCapabilitySnapshot, GenericCrudService, AsyncConfigStorage, ComponentDocMeta, AiCapabilityCatalog, AiCapability, AiCapabilityCategory, AiValueKind, ComponentAuthoringManifest, SettingsValueProvider } from '@praxisui/core';
4
+ import { RowAction, ToolbarAction, ApiEndpoint, ApiUrlEntry, DynamicFormLayoutPolicy, BackConfig, TableConfig, PraxisDataQueryContext, FormConfig, ResourceActionCatalogItem, ResourceSurfaceCatalogItem, MaterializedResourceIdentity, LoadingState, RestApiLinks, ResourceCapabilitySnapshot, SurfaceDrawerRef, GenericCrudService, AsyncConfigStorage, ComponentDocMeta, AiCapabilityCatalog, AiCapability, AiCapabilityCategory, AiValueKind, ComponentAuthoringManifest, SettingsValueProvider } from '@praxisui/core';
5
5
  export { BackConfig } from '@praxisui/core';
6
6
  import { TableConfigPersistenceStrategy } from '@praxisui/table';
7
7
  import { MatDialogConfig, MatDialogRef, MatDialog } from '@angular/material/dialog';
@@ -267,6 +267,7 @@ declare class PraxisCrudComponent implements OnChanges {
267
267
  private tryHandleCanonicalDeleteAction;
268
268
  private tryHandleCanonicalDuplicateDraftAction;
269
269
  private refreshTable;
270
+ private resolveSurfaceRuntime;
270
271
  private tryRefreshMaterializedLocalData;
271
272
  private isCurrentMaterializedRefresh;
272
273
  private resolveMaterializedReadUrl;
@@ -345,6 +346,14 @@ interface CrudDrawerResult {
345
346
  data?: Record<string, unknown>;
346
347
  }
347
348
 
349
+ interface CrudLaunchRuntime {
350
+ capabilities?: ResourceCapabilitySnapshot | null;
351
+ links?: RestApiLinks | null;
352
+ resourceIdentity?: MaterializedResourceIdentity | null;
353
+ contextIdentity?: MaterializedResourceIdentity | null;
354
+ /** Active runtime drawer session. It is never persisted in CRUD metadata. */
355
+ surfaceRuntime?: SurfaceDrawerRef | null;
356
+ }
348
357
  declare class CrudLauncherService {
349
358
  private readonly router;
350
359
  private readonly dialog;
@@ -352,16 +361,12 @@ declare class CrudLauncherService {
352
361
  private readonly global;
353
362
  private readonly operationResolver;
354
363
  private dialogHostPromise?;
364
+ private nextSurfaceFrameId;
355
365
  private readonly drawerAdapter;
356
366
  launch(action: CrudAction, row: Record<string, unknown> | undefined, metadata: CrudMetadata, componentKeyId?: string | null, drawerCallbacks?: {
357
367
  onClose?: () => void;
358
368
  onResult?: (result: CrudDrawerResult) => void;
359
- }, runtime?: {
360
- capabilities?: ResourceCapabilitySnapshot | null;
361
- links?: RestApiLinks | null;
362
- resourceIdentity?: MaterializedResourceIdentity | null;
363
- contextIdentity?: MaterializedResourceIdentity | null;
364
- }): Promise<{
369
+ }, runtime?: CrudLaunchRuntime): Promise<{
365
370
  mode: FormOpenMode;
366
371
  ref?: DialogRef<any>;
367
372
  }>;
@@ -390,26 +395,31 @@ declare class CrudLauncherService {
390
395
  private normalizeCrudOperationName;
391
396
  private isExplicitCrudAction;
392
397
  private defaultInferredLayoutPolicy;
398
+ private buildSurfaceFrameId;
399
+ private resolveSurfaceFrameTitle;
400
+ private resolveSurfaceFrameSubtitle;
401
+ private bindSurfaceFrameLifecycle;
393
402
  private mergeCrudOverrides;
394
403
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<CrudLauncherService, never>;
395
404
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<CrudLauncherService>;
396
405
  }
397
406
 
398
- declare class DynamicFormDialogHostComponent implements OnInit {
399
- dialogRef: DialogRef<DynamicFormDialogHostComponent>;
400
- data: any;
407
+ declare class DynamicFormDialogHostComponent implements OnInit, AfterViewInit {
408
+ dialogRef: DialogRef<DynamicFormDialogHostComponent> | null;
401
409
  private dialogService;
402
410
  private crud;
403
411
  private configStorage;
412
+ private readonly surfaceRef;
404
413
  formComp?: PraxisDynamicForm;
405
414
  modal: any;
406
- presentation: 'modal' | 'drawer';
415
+ presentation: 'modal' | 'drawer' | 'surface-frame';
407
416
  maximized: boolean;
408
417
  private initialSize;
409
418
  private readonly initialPosition?;
410
419
  private rememberState;
411
420
  private stateKey?;
412
421
  private backDefaults;
422
+ private terminalClose;
413
423
  private destroyRef;
414
424
  private readonly i18n;
415
425
  resourcePath?: string;
@@ -429,8 +439,10 @@ declare class DynamicFormDialogHostComponent implements OnInit {
429
439
  mode: 'create' | 'edit' | 'view';
430
440
  backConfig?: BackConfig;
431
441
  private idField;
442
+ readonly isSurfaceFrame: boolean;
443
+ readonly data: any;
432
444
  texts: Record<string, string>;
433
- constructor(dialogRef: DialogRef<DynamicFormDialogHostComponent>, data: any, dialogService: DialogService, crud: GenericCrudService<any>, configStorage: AsyncConfigStorage);
445
+ constructor(dialogRef: DialogRef<DynamicFormDialogHostComponent> | null, dialogData: any, dialogService: DialogService, crud: GenericCrudService<any>, configStorage: AsyncConfigStorage, surfaceRef?: SurfaceDrawerRef | null, surfaceContentData?: Readonly<Record<string, unknown>> | null);
434
446
  get resourceIdentityLabel(): string;
435
447
  get showContextIdentity(): boolean;
436
448
  private sameIdentity;
@@ -446,8 +458,10 @@ declare class DynamicFormDialogHostComponent implements OnInit {
446
458
  private resolveLayoutPolicy;
447
459
  private resolveSubmitLabel;
448
460
  ngOnInit(): void;
461
+ ngAfterViewInit(): void;
449
462
  onSave(result: unknown): void;
450
463
  onCancel(): void;
464
+ private canLeaveSurfaceFrame;
451
465
  toggleMaximize(initial?: boolean): void;
452
466
  private resolveOverlayPane;
453
467
  private captureRenderedInitialSize;
@@ -455,7 +469,7 @@ declare class DynamicFormDialogHostComponent implements OnInit {
455
469
  private applySavedState;
456
470
  private updateHostPosition;
457
471
  private resolveMaximizedDrawerPosition;
458
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<DynamicFormDialogHostComponent, never>;
472
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DynamicFormDialogHostComponent, [{ optional: true; }, { optional: true; }, null, null, null, { optional: true; }, { optional: true; }]>;
459
473
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<DynamicFormDialogHostComponent, "praxis-dynamic-form-dialog-host", never, {}, {}, never, never, true, never>;
460
474
  }
461
475