@praxisui/crud 9.0.0-beta.4 → 9.0.0-beta.41

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.
@@ -1,5 +1,5 @@
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, ENVIRONMENT_INITIALIZER } 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';
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';
@@ -10,6 +10,7 @@ import { ASYNC_CONFIG_STORAGE, GlobalConfigService, CrudOperationResolutionServi
10
10
  import { SettingsPanelService } from '@praxisui/settings-panel';
11
11
  import { PraxisTableInlineAuthoringEditorComponent, PraxisTable } from '@praxisui/table';
12
12
  import { ConfirmDialogComponent } from '@praxisui/dynamic-fields';
13
+ import { take, filter } from 'rxjs/operators';
13
14
  import * as i1 from '@angular/material/dialog';
14
15
  import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
15
16
  export { MAT_DIALOG_DATA as DIALOG_DATA } from '@angular/material/dialog';
@@ -34,7 +35,6 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
34
35
  import * as i10 from '@angular/material/tabs';
35
36
  import { MatTabsModule } from '@angular/material/tabs';
36
37
  import { PraxisDynamicForm } from '@praxisui/dynamic-form';
37
- import { filter, take } from 'rxjs/operators';
38
38
 
39
39
  class DialogService {
40
40
  matDialog;
@@ -95,22 +95,6 @@ class CrudLauncherService {
95
95
  await this.router.navigateByUrl(url);
96
96
  return { mode };
97
97
  }
98
- if (mode === 'drawer' && this.drawerAdapter) {
99
- const actionForLaunch = this.resolveActionForLaunch(merged.action, merged.metadata);
100
- const inputs = this.mapInputs(actionForLaunch, row, merged.metadata, runtime);
101
- const idField = merged.metadata.resource?.idField ?? 'id';
102
- if (row && inputs[idField] === undefined && row[idField] !== undefined) {
103
- inputs[idField] = row[idField];
104
- }
105
- await Promise.resolve(this.drawerAdapter.open({
106
- action: actionForLaunch,
107
- metadata: merged.metadata,
108
- inputs,
109
- onClose: drawerCallbacks?.onClose,
110
- onResult: drawerCallbacks?.onResult,
111
- }));
112
- return { mode };
113
- }
114
98
  const actionForLaunch = this.resolveActionForLaunch(merged.action, merged.metadata);
115
99
  if (!actionForLaunch.formId) {
116
100
  throw new Error(`formId not provided for action ${actionForLaunch.action}`);
@@ -123,6 +107,16 @@ class CrudLauncherService {
123
107
  row[idField] !== undefined) {
124
108
  inputs[idField] = row[idField];
125
109
  }
110
+ if (mode === 'drawer' && this.drawerAdapter) {
111
+ await Promise.resolve(this.drawerAdapter.open({
112
+ action: actionForLaunch,
113
+ metadata: merged.metadata,
114
+ inputs,
115
+ onClose: drawerCallbacks?.onClose,
116
+ onResult: drawerCallbacks?.onResult,
117
+ }));
118
+ return { mode };
119
+ }
126
120
  const modalCfg = { ...(merged.metadata.defaults?.modal || {}) };
127
121
  console.debug('[CRUD:Launcher] opening dialog with:', {
128
122
  action: merged.action.action,
@@ -131,10 +125,8 @@ class CrudLauncherService {
131
125
  modalCfg,
132
126
  resourcePath: merged.metadata.resource?.path ?? merged.metadata.table?.resourcePath,
133
127
  });
134
- const panelClasses = ['pfx-dialog-pane', 'pfx-dialog-frosted'];
135
- if (modalCfg.panelClass) {
136
- panelClasses.push(modalCfg.panelClass);
137
- }
128
+ const drawerMode = mode === 'drawer';
129
+ const panelClasses = mergeClassList(['pfx-dialog-pane', drawerMode ? 'pfx-drawer-pane' : 'pfx-dialog-frosted'], modalCfg.panelClass);
138
130
  // Backdrop style presets
139
131
  const backdropClasses = [];
140
132
  const style = modalCfg.backdropStyle;
@@ -147,20 +139,44 @@ class CrudLauncherService {
147
139
  else if (style === 'transparent') {
148
140
  backdropClasses.push('pfx-transparent-backdrop');
149
141
  }
150
- if (modalCfg.backdropClass) {
151
- backdropClasses.push(modalCfg.backdropClass);
152
- }
142
+ const mergedBackdropClasses = mergeClassList(backdropClasses, modalCfg.backdropClass);
153
143
  const ref = await this.dialog.openAsync(() => Promise.resolve().then(function () { return dynamicFormDialogHost_component; }).then((m) => m.DynamicFormDialogHostComponent), {
154
144
  ...modalCfg,
155
145
  panelClass: panelClasses,
156
- backdropClass: backdropClasses,
146
+ backdropClass: mergedBackdropClasses,
157
147
  autoFocus: modalCfg.autoFocus ?? true,
158
148
  restoreFocus: modalCfg.restoreFocus ?? true,
159
- minWidth: '360px',
160
- maxWidth: '95vw',
149
+ minWidth: drawerMode ? (modalCfg.minWidth ?? '0') : '360px',
150
+ width: drawerMode ? (modalCfg.width ?? 'min(560px, 100vw)') : modalCfg.width,
151
+ height: drawerMode ? (modalCfg.height ?? '100dvh') : modalCfg.height,
152
+ maxWidth: drawerMode ? (modalCfg.maxWidth ?? '100vw') : '95vw',
153
+ maxHeight: drawerMode ? (modalCfg.maxHeight ?? '100dvh') : modalCfg.maxHeight,
154
+ position: drawerMode ? (modalCfg.position ?? { right: '0', top: '0' }) : modalCfg.position,
161
155
  ariaLabelledBy: 'crudDialogTitle',
162
- data: { action: actionForLaunch, row, metadata: merged.metadata, inputs },
156
+ data: {
157
+ action: actionForLaunch,
158
+ row,
159
+ metadata: merged.metadata,
160
+ inputs,
161
+ presentation: drawerMode ? 'drawer' : 'modal',
162
+ },
163
163
  });
164
+ if (drawerMode) {
165
+ ref
166
+ .afterClosed()
167
+ .pipe(take(1))
168
+ .subscribe((closedValue) => {
169
+ const result = toCrudDrawerResult(closedValue);
170
+ drawerCallbacks?.onClose?.();
171
+ if (result?.type) {
172
+ drawerCallbacks?.onResult?.(result);
173
+ }
174
+ else {
175
+ drawerCallbacks?.onResult?.({ type: 'close' });
176
+ }
177
+ });
178
+ return { mode, ref };
179
+ }
164
180
  return { mode, ref };
165
181
  }
166
182
  resolveOpenMode(action, metadata) {
@@ -248,6 +264,9 @@ class CrudLauncherService {
248
264
  if (resolved.apiUrlEntry != null) {
249
265
  inputs['apiUrlEntry'] = resolved.apiUrlEntry;
250
266
  }
267
+ if (resolved.layoutPolicy != null) {
268
+ inputs['layoutPolicy'] = resolved.layoutPolicy;
269
+ }
251
270
  if (action.form?.initialValue != null) {
252
271
  inputs['initialValue'] = action.form.initialValue;
253
272
  }
@@ -274,15 +293,18 @@ class CrudLauncherService {
274
293
  submitMethod: action.form?.submitMethod ?? null,
275
294
  apiEndpointKey: action.form?.apiEndpointKey ?? null,
276
295
  apiUrlEntry: action.form?.apiUrlEntry ?? null,
296
+ layoutPolicy: action.form?.layoutPolicy ?? null,
277
297
  };
278
298
  }
279
299
  if (!this.isCanonicalCrudAction(action.action)) {
280
300
  return {
281
301
  apiEndpointKey: action.form?.apiEndpointKey ?? null,
282
302
  apiUrlEntry: action.form?.apiUrlEntry ?? null,
303
+ layoutPolicy: action.form?.layoutPolicy ?? null,
283
304
  };
284
305
  }
285
306
  const resourcePath = String(metadata.resource?.path ?? metadata.table?.resourcePath ?? '').trim();
307
+ const schemaResourcePath = String(metadata.resource?.schemaPath || '').trim() || null;
286
308
  if (!resourcePath) {
287
309
  return {
288
310
  apiEndpointKey: action.form?.apiEndpointKey ?? null,
@@ -294,6 +316,7 @@ class CrudLauncherService {
294
316
  const resolved = this.operationResolver.resolve({
295
317
  operation: action.action,
296
318
  resourcePath,
319
+ schemaResourcePath,
297
320
  resourceId: resourceId ?? null,
298
321
  capabilities: runtime?.capabilities ?? null,
299
322
  links: runtime?.links ?? null,
@@ -301,14 +324,15 @@ class CrudLauncherService {
301
324
  endpointKey: action.form?.apiEndpointKey ??
302
325
  metadata.resource?.endpointKey ??
303
326
  undefined,
304
- apiUrlEntry: action.form?.apiUrlEntry ?? null,
327
+ apiUrlEntry: action.form?.apiUrlEntry ?? metadata.resource?.apiUrlEntry ?? null,
305
328
  });
306
329
  return {
307
330
  schemaUrl: resolved?.schemaUrl ?? null,
308
331
  submitUrl: resolved?.submitUrl ?? null,
309
332
  submitMethod: resolved?.submitMethod ?? null,
310
333
  apiEndpointKey: action.form?.apiEndpointKey ?? metadata.resource?.endpointKey ?? null,
311
- apiUrlEntry: action.form?.apiUrlEntry ?? null,
334
+ apiUrlEntry: action.form?.apiUrlEntry ?? metadata.resource?.apiUrlEntry ?? null,
335
+ layoutPolicy: action.form?.layoutPolicy ?? null,
312
336
  };
313
337
  }
314
338
  resolveRuntimeContract(action, row, metadata, runtime) {
@@ -392,6 +416,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
392
416
  type: Injectable,
393
417
  args: [{ providedIn: 'root' }]
394
418
  }] });
419
+ function mergeClassList(base, extra) {
420
+ if (!extra) {
421
+ return base;
422
+ }
423
+ if (typeof extra === 'string') {
424
+ return [...base, extra];
425
+ }
426
+ if (Array.isArray(extra)) {
427
+ return [...base, ...extra];
428
+ }
429
+ return [
430
+ ...base,
431
+ ...Object.keys(extra).filter((className) => !!extra[className]),
432
+ ];
433
+ }
434
+ function toCrudDrawerResult(value) {
435
+ if (!value || typeof value !== 'object') {
436
+ return undefined;
437
+ }
438
+ const result = value;
439
+ return result.type === 'save' || result.type === 'delete' || result.type === 'close'
440
+ ? result
441
+ : undefined;
442
+ }
395
443
 
396
444
  const DOCUMENT_KIND = 'praxis.crud.editor';
397
445
  const DOCUMENT_VERSION = 1;
@@ -3656,6 +3704,9 @@ class PraxisCrudComponent {
3656
3704
  links: this.resolveCrudRuntimeLinks(effectiveAction.action, row),
3657
3705
  });
3658
3706
  this.afterOpen.emit({ mode, action: effectiveAction.action });
3707
+ if (mode === 'drawer') {
3708
+ return;
3709
+ }
3659
3710
  if (!ref) {
3660
3711
  return;
3661
3712
  }
@@ -3999,7 +4050,7 @@ class PraxisCrudComponent {
3999
4050
  const providedAction = this.resolveProvidedWorkflowAction(normalizedAction, runtimeEvent?.actionConfig);
4000
4051
  const catalog = providedAction ? null : await this.resolveDiscoveredActionCatalog(row);
4001
4052
  const discoveredAction = providedAction || this.selectDiscoveredWorkflowAction(normalizedAction, catalog?.actions || []);
4002
- const resourcePath = String(this.resolveResourcePath(this.resolvedMetadata) || catalog?.resourcePath || '').trim();
4053
+ const resourcePath = String(catalog?.resourcePath || this.resolveResourcePath(this.resolvedMetadata) || '').trim();
4003
4054
  if (!discoveredAction || !resourcePath) {
4004
4055
  return false;
4005
4056
  }
@@ -4657,7 +4708,7 @@ class PraxisCrudComponent {
4657
4708
  />
4658
4709
  }
4659
4710
  }
4660
- `, isInline: true, styles: [":host{display:block;width:100%;min-width:0;max-width:100%}\n"], dependencies: [{ kind: "component", type: PraxisTable, selector: "praxis-table", inputs: ["config", "resourcePath", "data", "tableId", "componentInstanceId", "configPersistenceStrategy", "title", "subtitle", "icon", "autoDelete", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "crudContext", "filterCriteria", "queryContext", "aiContext", "aiAssistantVoiceInputMode", "aiAssistantVoiceLanguage", "horizontalScroll", "enableCustomization", "dense"], outputs: ["rowClick", "widgetEvent", "rowDoubleClick", "rowExpansionChange", "rowAction", "toolbarAction", "bulkAction", "exportAction", "columnReorder", "columnReorderAttempt", "beforeDelete", "afterDelete", "deleteError", "beforeBulkDelete", "afterBulkDelete", "bulkDeleteError", "schemaStatusChange", "configChange", "metadataChange", "loadingStateChange", "collectionLinksChange", "selectionChange"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone"] }] });
4711
+ `, isInline: true, styles: [":host{display:block;width:100%;min-width:0;max-width:100%}\n"], dependencies: [{ kind: "component", type: PraxisTable, selector: "praxis-table", inputs: ["config", "resourcePath", "data", "tableId", "componentInstanceId", "configPersistenceStrategy", "title", "subtitle", "icon", "autoDelete", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "crudContext", "filterCriteria", "queryContext", "aiContext", "aiAssistantVoiceInputMode", "aiAssistantVoiceLanguage", "horizontalScroll", "enableCustomization", "dense"], outputs: ["rowClick", "widgetEvent", "resourceEvent", "rowDoubleClick", "rowExpansionChange", "rowAction", "toolbarAction", "bulkAction", "exportAction", "columnReorder", "columnReorderAttempt", "columnResize", "beforeDelete", "afterDelete", "deleteError", "beforeBulkDelete", "afterBulkDelete", "bulkDeleteError", "schemaStatusChange", "configChange", "metadataChange", "loadingStateChange", "collectionLinksChange", "selectionChange"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone"] }] });
4661
4712
  }
4662
4713
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudComponent, decorators: [{
4663
4714
  type: Component,
@@ -4742,6 +4793,7 @@ class DynamicFormDialogHostComponent {
4742
4793
  configStorage;
4743
4794
  formComp;
4744
4795
  modal = {};
4796
+ presentation = 'modal';
4745
4797
  maximized = false;
4746
4798
  initialSize = {};
4747
4799
  rememberState = false;
@@ -4756,6 +4808,7 @@ class DynamicFormDialogHostComponent {
4756
4808
  submitMethod;
4757
4809
  apiEndpointKey;
4758
4810
  apiUrlEntry;
4811
+ layoutPolicy;
4759
4812
  formActions;
4760
4813
  mode = 'create';
4761
4814
  backConfig;
@@ -4778,6 +4831,7 @@ class DynamicFormDialogHostComponent {
4778
4831
  this.crud = crud;
4779
4832
  this.configStorage = configStorage;
4780
4833
  this.dialogRef.disableClose = true;
4834
+ this.presentation = this.data.presentation === 'drawer' ? 'drawer' : 'modal';
4781
4835
  // i18n
4782
4836
  this.texts = {
4783
4837
  ...this.texts,
@@ -4812,6 +4866,7 @@ class DynamicFormDialogHostComponent {
4812
4866
  this.apiUrlEntry = this.data.inputs?.['apiUrlEntry'] ?? null;
4813
4867
  const act = this.data.action?.action;
4814
4868
  this.mode = act === 'edit' ? 'edit' : act === 'view' ? 'view' : 'create';
4869
+ this.layoutPolicy = this.resolveLayoutPolicy();
4815
4870
  this.formActions = this.resolveFormActions();
4816
4871
  // Back config: defaults from metadata/action, overridden by saved per-form config
4817
4872
  const defaults = (this.data.action?.back || this.data.metadata?.defaults?.back) || {};
@@ -4857,6 +4912,7 @@ class DynamicFormDialogHostComponent {
4857
4912
  'apiEndpointKey',
4858
4913
  'apiUrlEntry',
4859
4914
  'initialValue',
4915
+ 'layoutPolicy',
4860
4916
  ]);
4861
4917
  const explicit = inputs['initialValue'] && typeof inputs['initialValue'] === 'object'
4862
4918
  ? { ...inputs['initialValue'] }
@@ -4890,6 +4946,15 @@ class DynamicFormDialogHostComponent {
4890
4946
  },
4891
4947
  };
4892
4948
  }
4949
+ resolveLayoutPolicy() {
4950
+ const explicit = this.data.inputs?.['layoutPolicy'] ??
4951
+ this.data.action?.form?.layoutPolicy ??
4952
+ this.data.action?.layoutPolicy;
4953
+ if (explicit && typeof explicit === 'object') {
4954
+ return explicit;
4955
+ }
4956
+ return null;
4957
+ }
4893
4958
  resolveSubmitLabel() {
4894
4959
  const action = this.data.action ?? {};
4895
4960
  const explicit = stringOrUndefined(action.form?.submitLabel ??
@@ -4962,10 +5027,10 @@ class DynamicFormDialogHostComponent {
4962
5027
  ref
4963
5028
  .afterClosed()
4964
5029
  .pipe(filter((confirmed) => !!confirmed), takeUntilDestroyed(this.destroyRef))
4965
- .subscribe(() => this.dialogRef.close());
5030
+ .subscribe(() => this.dialogRef.close({ type: 'close' }));
4966
5031
  }
4967
5032
  else {
4968
- this.dialogRef.close();
5033
+ this.dialogRef.close({ type: 'close' });
4969
5034
  }
4970
5035
  }
4971
5036
  toggleMaximize(initial = false) {
@@ -5032,7 +5097,7 @@ class DynamicFormDialogHostComponent {
5032
5097
  }
5033
5098
  }
5034
5099
  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 });
5035
- 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\"" }, classAttribute: "praxis-dialog" }, providers: [GenericCrudService], viewQueries: [{ propertyName: "formComp", first: true, predicate: PraxisDynamicForm, descendants: true }], ngImport: i0, template: `
5100
+ 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: [GenericCrudService], viewQueries: [{ propertyName: "formComp", first: true, predicate: PraxisDynamicForm, descendants: true }], ngImport: i0, template: `
5036
5101
  <div mat-dialog-title class="dialog-header">
5037
5102
  <h2 id="crudDialogTitle" class="dialog-title">
5038
5103
  {{ data.action?.label || texts.title }}
@@ -5074,6 +5139,7 @@ class DynamicFormDialogHostComponent {
5074
5139
  [submitMethod]="submitMethod"
5075
5140
  [apiEndpointKey]="apiEndpointKey"
5076
5141
  [apiUrlEntry]="apiUrlEntry"
5142
+ [layoutPolicy]="layoutPolicy"
5077
5143
  [presentationModeGlobal]="mode === 'view' ? true : null"
5078
5144
  [backConfig]="backConfig"
5079
5145
  [actions]="formActions"
@@ -5081,7 +5147,7 @@ class DynamicFormDialogHostComponent {
5081
5147
  (formCancel)="onCancel()"
5082
5148
  ></praxis-dynamic-form>
5083
5149
  </mat-dialog-content>
5084
- `, isInline: true, styles: [":host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}:host([data-density=\"compact\"]){--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}.dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);padding:0 var(--dlg-pad);height:var(--dlg-header-h);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)}.dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}.spacer{flex:1}.dialog-content{overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}.dialog-header button.mat-icon-button{color:var(--md-sys-color-on-surface-variant)}.dialog-header button.mat-icon-button:hover{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}.dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}\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: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisDynamicForm, selector: "praxis-dynamic-form", inputs: ["resourcePath", "resourceId", "initialValue", "editorialContext", "mode", "config", "actions", "schemaSource", "schemaUrl", "readUrl", "submitUrl", "submitMethod", "responseSchemaUrl", "apiEndpointKey", "apiUrlEntry", "enableCustomization", "showAiAssistant", "formId", "componentInstanceId", "configPersistenceStrategy", "layout", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "visibleGlobal", "domainRules", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "configPatchChange", "formReady", "valueChange", "syncCompleted", "initializationError", "loadingStateChange", "enableCustomizationChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError", "ruleDiagnosticsChange"] }] });
5150
+ `, 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);padding:0 var(--dlg-pad);height:var(--dlg-header-h);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 .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.praxis-drawer{--dlg-header-h: 64px;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.mat-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.mat-icon-button:hover{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}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%;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 .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-drawer-pane .mdc-dialog__surface{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: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisDynamicForm, selector: "praxis-dynamic-form", inputs: ["resourcePath", "resourceId", "initialValue", "editorialContext", "mode", "config", "actions", "schemaSource", "schemaUrl", "readUrl", "submitUrl", "submitMethod", "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 });
5085
5151
  }
5086
5152
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
5087
5153
  type: Component,
@@ -5091,9 +5157,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5091
5157
  MatIconModule,
5092
5158
  PraxisIconDirective,
5093
5159
  PraxisDynamicForm
5094
- ], providers: [GenericCrudService], host: {
5160
+ ], encapsulation: ViewEncapsulation.None, providers: [GenericCrudService], host: {
5095
5161
  class: 'praxis-dialog',
5096
5162
  '[attr.data-density]': 'modal.density || "default"',
5163
+ '[attr.data-presentation]': 'presentation',
5164
+ '[class.praxis-drawer]': 'presentation === "drawer"',
5097
5165
  }, template: `
5098
5166
  <div mat-dialog-title class="dialog-header">
5099
5167
  <h2 id="crudDialogTitle" class="dialog-title">
@@ -5136,6 +5204,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5136
5204
  [submitMethod]="submitMethod"
5137
5205
  [apiEndpointKey]="apiEndpointKey"
5138
5206
  [apiUrlEntry]="apiUrlEntry"
5207
+ [layoutPolicy]="layoutPolicy"
5139
5208
  [presentationModeGlobal]="mode === 'view' ? true : null"
5140
5209
  [backConfig]="backConfig"
5141
5210
  [actions]="formActions"
@@ -5143,7 +5212,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5143
5212
  (formCancel)="onCancel()"
5144
5213
  ></praxis-dynamic-form>
5145
5214
  </mat-dialog-content>
5146
- `, styles: [":host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}:host([data-density=\"compact\"]){--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}.dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);padding:0 var(--dlg-pad);height:var(--dlg-header-h);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)}.dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}.spacer{flex:1}.dialog-content{overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}.dialog-header button.mat-icon-button{color:var(--md-sys-color-on-surface-variant)}.dialog-header button.mat-icon-button:hover{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}.dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}\n"] }]
5215
+ `, 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);padding:0 var(--dlg-pad);height:var(--dlg-header-h);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 .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.praxis-drawer{--dlg-header-h: 64px;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.mat-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.mat-icon-button:hover{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}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%;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 .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-drawer-pane .mdc-dialog__surface{border-radius:0!important}\n"] }]
5147
5216
  }], ctorParameters: () => [{ type: undefined, decorators: [{
5148
5217
  type: Inject,
5149
5218
  args: [MatDialogRef]
@@ -5325,6 +5394,11 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
5325
5394
  type: 'PraxisDataQueryContext | null',
5326
5395
  description: 'Contexto semântico de consulta encaminhado para a tabela interna do CRUD. Preferir este contrato para novo authoring remoto.',
5327
5396
  },
5397
+ {
5398
+ name: 'metadata.resource.schemaPath',
5399
+ type: 'string | null',
5400
+ description: 'Template canonico opcional usado apenas para resolver /schemas/filtered quando resource.path precisa ser um alvo operacional concreto.',
5401
+ },
5328
5402
  {
5329
5403
  name: 'metadata.filterCriteria',
5330
5404
  type: 'Record<string, unknown> | null',
@@ -5699,6 +5773,7 @@ const CRUD_AI_CAPABILITIES = {
5699
5773
  { path: 'actions[].params[].to', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.paramTarget, description: 'Destino do parametro.' },
5700
5774
  { path: 'actions[].params[].name', category: 'actions', valueKind: 'string', description: 'Nome do parametro no destino.' },
5701
5775
  { path: 'actions[].form.initialValue', category: 'actions', valueKind: 'object', description: 'Seed fixo do formulario injetado em inputs.initialValue antes da abertura.' },
5776
+ { path: 'actions[].form.layoutPolicy', category: 'actions', valueKind: 'object', description: 'Politica schema-driven opt-in encaminhada ao Dynamic Form para materializar detalhe ou command form sem FormConfig.sections local; deve acompanhar schemaUrl compativel, especialmente schemaType=request para comandos.' },
5702
5777
  { path: 'actions[].back', category: 'navigation', valueKind: 'object', description: 'BackConfig por acao.' },
5703
5778
  { path: 'actions[].back.strategy', category: 'navigation', valueKind: 'enum', allowedValues: ENUMS.backStrategy, description: 'Estrategia de retorno da acao (auto, close, navigate).' },
5704
5779
  { path: 'actions[].back.returnTo', category: 'navigation', valueKind: 'string', description: 'Rota ou destino usado quando a estrategia de retorno for navigate.' },
@@ -5736,6 +5811,7 @@ const surfaceConfigureSchema = {
5736
5811
  submitMethod: { enum: ['post', 'put', 'patch', 'delete'] },
5737
5812
  apiEndpointKey: { type: 'string' },
5738
5813
  initialValue: { type: 'object' },
5814
+ layoutPolicy: { type: 'object' },
5739
5815
  },
5740
5816
  },
5741
5817
  params: {
@@ -5854,7 +5930,7 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
5854
5930
  { name: 'afterDelete', type: '{ id: string | number }', description: 'Emitted after delete; CRUD refetches the list.' },
5855
5931
  ],
5856
5932
  editableTargets: [
5857
- { kind: 'resourceBinding', resolver: 'crud-resource-by-path-or-key', description: 'Resource path/key, id field, endpoint key and query context owned by CRUD orchestration.' },
5933
+ { kind: 'resourceBinding', resolver: 'crud-resource-by-path-or-key', description: 'Resource path/key, optional schema path template, id field, endpoint key and query context owned by CRUD orchestration.' },
5858
5934
  { kind: 'listSurface', resolver: 'crud-list-surface', description: 'CRUD-hosted list surface and table delegation boundary.' },
5859
5935
  { kind: 'createSurface', resolver: 'crud-action-by-id:create', description: 'Create action open mode, route/form binding and launcher inputs.' },
5860
5936
  { kind: 'editSurface', resolver: 'crud-action-by-id:edit', description: 'Edit action open mode, route/form binding and launcher inputs.' },
@@ -5877,13 +5953,13 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
5877
5953
  effects: [{ kind: 'compile-domain-patch', handler: 'crud-resource-bind', handlerContract: {
5878
5954
  reads: ['CrudMetadata.resource', 'api_metadata', 'ResourceDiscoveryService', 'GET /{resource}/capabilities'],
5879
5955
  writes: ['CrudMetadata.resource', 'CrudMetadata.queryContext', 'PraxisCrudComponent.tableCrudContext'],
5880
- identityKeys: ['resourcePath', 'resourceKey'],
5956
+ identityKeys: ['resourcePath', 'resourceKey', 'schemaPath'],
5881
5957
  inputSchema: resourceBindSchema,
5882
5958
  failureModes: ['resource-not-found', 'schema-url-not-canonical', 'capabilities-unavailable', 'id-field-missing'],
5883
- description: 'Binds CRUD to a canonical resource and validates it against api_metadata/resource capabilities before runtime use.',
5959
+ description: 'Binds CRUD to a canonical resource and validates it against api_metadata/resource capabilities before runtime use. Use CrudMetadata.resource.schemaPath when schema discovery must use a templated path while resource.path targets a concrete nested collection.',
5884
5960
  } }],
5885
5961
  validators: ['resource-exists-in-api-metadata', 'resource-path-canonical', 'resource-key-stable', 'id-field-known', 'resource-capabilities-resolvable'],
5886
- affectedPaths: ['resource.path', 'resource.idField', 'resource.endpointKey', 'queryContext', 'filterCriteria'],
5962
+ affectedPaths: ['resource.path', 'resource.schemaPath', 'resource.idField', 'resource.endpointKey', 'resource.apiUrlEntry', 'queryContext', 'filterCriteria'],
5887
5963
  submissionImpact: 'affects-remote-binding',
5888
5964
  preconditions: ['crud-metadata-loaded', 'api-metadata-available'],
5889
5965
  },
@@ -6017,14 +6093,14 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
6017
6093
  target: { kind: 'dialogHost', resolver: 'crud-dialog-host-defaults', ambiguityPolicy: 'fail', required: true },
6018
6094
  inputSchema: dialogHostSchema,
6019
6095
  effects: [{ kind: 'compile-domain-patch', handler: 'crud-dialog-host-set', handlerContract: {
6020
- reads: ['CrudMetadata.defaults', 'CrudLauncherService.resolveOpenMode', 'DialogService', 'CRUD_DRAWER_ADAPTER'],
6096
+ reads: ['CrudMetadata.defaults', 'CrudLauncherService.resolveOpenMode', 'DialogService', 'DynamicFormDialogHostComponent', 'CRUD_DRAWER_ADAPTER'],
6021
6097
  writes: ['CrudMetadata.defaults.openMode', 'CrudMetadata.defaults.modal', 'CrudMetadata.defaults.back'],
6022
6098
  identityKeys: ['crudId'],
6023
6099
  inputSchema: dialogHostSchema,
6024
- failureModes: ['open-mode-unsupported', 'drawer-adapter-missing', 'modal-size-invalid', 'back-policy-invalid'],
6025
- description: 'Configures CRUD-owned route/modal/drawer defaults consumed by the launcher and dialog host.',
6100
+ failureModes: ['open-mode-unsupported', 'drawer-runtime-unavailable', 'modal-size-invalid', 'back-policy-invalid'],
6101
+ description: 'Configures CRUD-owned route/modal/drawer defaults consumed by the launcher, default dialog-backed drawer runtime and optional host drawer adapter.',
6026
6102
  } }],
6027
- validators: ['open-mode-supported', 'modal-size-valid', 'drawer-adapter-available-when-needed', 'back-policy-valid', 'settings-panel-shell-compatible'],
6103
+ validators: ['open-mode-supported', 'modal-size-valid', 'drawer-runtime-available', 'back-policy-valid', 'settings-panel-shell-compatible'],
6028
6104
  affectedPaths: ['defaults.openMode', 'defaults.modal', 'defaults.back'],
6029
6105
  submissionImpact: 'config-only',
6030
6106
  preconditions: ['crud-metadata-loaded'],
@@ -6100,7 +6176,7 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
6100
6176
  { validatorId: 'permissions-delete-valid', level: 'error', code: 'CRUD_DELETE_PERMISSION_VALID', description: 'Delete permission cannot bypass resource capabilities or confirmation policy.' },
6101
6177
  { validatorId: 'open-mode-supported', level: 'error', code: 'CRUD_OPEN_MODE_SUPPORTED', description: 'Open mode must be route, modal or drawer.' },
6102
6178
  { validatorId: 'modal-size-valid', level: 'error', code: 'CRUD_MODAL_SIZE_VALID', description: 'Modal sizing defaults must be valid DialogConfig values.' },
6103
- { validatorId: 'drawer-adapter-available-when-needed', level: 'error', code: 'CRUD_DRAWER_ADAPTER_AVAILABLE', description: 'Drawer open mode requires a host-provided drawer adapter.' },
6179
+ { validatorId: 'drawer-runtime-available', level: 'error', code: 'CRUD_DRAWER_RUNTIME_AVAILABLE', description: 'Drawer open mode uses the default dialog-backed drawer runtime with CRUD-owned chrome, responsive right-panel sizing, Escape/backdrop handling and semantic close/save results; a host drawer adapter is optional only for shell-specific presentation.' },
6104
6180
  { validatorId: 'back-policy-valid', level: 'error', code: 'CRUD_BACK_POLICY_VALID', description: 'Back policy must be valid for route/modal/drawer behavior.' },
6105
6181
  { validatorId: 'settings-panel-shell-compatible', level: 'warning', code: 'CRUD_SETTINGS_PANEL_COMPATIBLE', description: 'Authoring shell must preserve apply/save/reset semantics.' },
6106
6182
  { validatorId: 'action-permission-supported', level: 'error', code: 'CRUD_ACTION_PERMISSION_SUPPORTED', description: 'Action permissions must map to supported resource capabilities.' },
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@praxisui/crud",
3
- "version": "9.0.0-beta.4",
3
+ "version": "9.0.0-beta.41",
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.0-beta.4",
9
- "@praxisui/table": "^9.0.0-beta.4",
10
- "@praxisui/core": "^9.0.0-beta.4",
11
- "@praxisui/dynamic-fields": "^9.0.0-beta.4",
12
- "@praxisui/settings-panel": "^9.0.0-beta.4",
8
+ "@praxisui/dynamic-form": "^9.0.0-beta.41",
9
+ "@praxisui/table": "^9.0.0-beta.41",
10
+ "@praxisui/core": "^9.0.0-beta.41",
11
+ "@praxisui/dynamic-fields": "^9.0.0-beta.41",
12
+ "@praxisui/settings-panel": "^9.0.0-beta.41",
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.0-beta.4",
17
+ "@praxisui/ai": "^9.0.0-beta.41",
18
18
  "rxjs": "~7.8.0"
19
19
  },
20
20
  "dependencies": {
@@ -28,6 +28,7 @@
28
28
  "keywords": [
29
29
  "angular",
30
30
  "praxisui",
31
+ "praxis",
31
32
  "crud",
32
33
  "admin",
33
34
  "backoffice",
@@ -48,7 +49,10 @@
48
49
  "./drawer-adapter": {
49
50
  "types": "./types/praxisui-crud-drawer-adapter.d.ts",
50
51
  "default": "./fesm2022/praxisui-crud-drawer-adapter.mjs"
52
+ },
53
+ "./ai/component-registry.json": {
54
+ "default": "./ai/component-registry.json"
51
55
  }
52
56
  },
53
57
  "type": "module"
54
- }
58
+ }
@@ -1,5 +1,5 @@
1
1
  import * as _praxisui_core from '@praxisui/core';
2
- import { RowAction, ToolbarAction, ApiEndpoint, ApiUrlEntry, BackConfig, TableConfig, PraxisDataQueryContext, FormConfig, ResourceActionCatalogItem, ResourceSurfaceCatalogItem, LoadingState, RestApiLinks, ResourceCapabilitySnapshot, GenericCrudService, AsyncConfigStorage, ComponentDocMeta, AiCapabilityCatalog, AiCapability, AiCapabilityCategory, AiValueKind, ComponentAuthoringManifest, SettingsValueProvider } from '@praxisui/core';
2
+ import { RowAction, ToolbarAction, ApiEndpoint, ApiUrlEntry, DynamicFormLayoutPolicy, BackConfig, TableConfig, PraxisDataQueryContext, FormConfig, ResourceActionCatalogItem, ResourceSurfaceCatalogItem, LoadingState, RestApiLinks, ResourceCapabilitySnapshot, GenericCrudService, AsyncConfigStorage, ComponentDocMeta, AiCapabilityCatalog, AiCapability, AiCapabilityCategory, AiValueKind, ComponentAuthoringManifest, SettingsValueProvider } from '@praxisui/core';
3
3
  export { BackConfig } from '@praxisui/core';
4
4
  import * as _angular_core from '@angular/core';
5
5
  import { NgZone, OnChanges, EventEmitter, SimpleChanges, OnInit, Provider, AfterViewInit, OnDestroy } from '@angular/core';
@@ -53,6 +53,7 @@ interface CrudActionFormContract {
53
53
  apiEndpointKey?: ApiEndpoint | string | null;
54
54
  apiUrlEntry?: ApiUrlEntry | null;
55
55
  initialValue?: Record<string, unknown> | null;
56
+ layoutPolicy?: DynamicFormLayoutPolicy | null;
56
57
  }
57
58
  type CrudActionResolutionMode = 'inferred' | 'explicit';
58
59
  type CrudAction = (RowAction | ToolbarAction) & {
@@ -72,8 +73,10 @@ interface CrudDefaults {
72
73
  }
73
74
  interface CrudResource {
74
75
  path: string;
76
+ schemaPath?: string | null;
75
77
  idField?: string | number;
76
78
  endpointKey?: ApiEndpoint;
79
+ apiUrlEntry?: ApiUrlEntry | null;
77
80
  }
78
81
  interface CrudMetadata {
79
82
  component: 'praxis-crud';
@@ -316,6 +319,7 @@ declare class CrudLauncherService {
316
319
  submitMethod?: string | null;
317
320
  apiEndpointKey?: unknown;
318
321
  apiUrlEntry?: unknown;
322
+ layoutPolicy?: unknown;
319
323
  };
320
324
  private buildInferredFormId;
321
325
  private isCanonicalCrudAction;
@@ -333,6 +337,7 @@ declare class DynamicFormDialogHostComponent implements OnInit {
333
337
  private configStorage;
334
338
  formComp?: PraxisDynamicForm;
335
339
  modal: any;
340
+ presentation: 'modal' | 'drawer';
336
341
  maximized: boolean;
337
342
  private initialSize;
338
343
  private rememberState;
@@ -347,6 +352,7 @@ declare class DynamicFormDialogHostComponent implements OnInit {
347
352
  submitMethod?: string | null;
348
353
  apiEndpointKey?: ApiEndpoint | string | null;
349
354
  apiUrlEntry?: ApiUrlEntry | null;
355
+ layoutPolicy?: DynamicFormLayoutPolicy | null;
350
356
  formActions?: FormConfig['actions'];
351
357
  mode: 'create' | 'edit' | 'view';
352
358
  backConfig?: BackConfig;
@@ -355,6 +361,7 @@ declare class DynamicFormDialogHostComponent implements OnInit {
355
361
  constructor(dialogRef: DialogRef<DynamicFormDialogHostComponent>, data: any, dialogService: DialogService, crud: GenericCrudService<any>, configStorage: AsyncConfigStorage);
356
362
  private extractInitialValue;
357
363
  private resolveFormActions;
364
+ private resolveLayoutPolicy;
358
365
  private resolveSubmitLabel;
359
366
  ngOnInit(): void;
360
367
  onSave(result: unknown): void;