@praxisui/crud 9.0.0-beta.3 → 9.0.0-beta.31

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.
@@ -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,17 +139,19 @@ 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
156
  data: { action: actionForLaunch, row, metadata: merged.metadata, inputs },
163
157
  });
@@ -248,6 +242,9 @@ class CrudLauncherService {
248
242
  if (resolved.apiUrlEntry != null) {
249
243
  inputs['apiUrlEntry'] = resolved.apiUrlEntry;
250
244
  }
245
+ if (resolved.layoutPolicy != null) {
246
+ inputs['layoutPolicy'] = resolved.layoutPolicy;
247
+ }
251
248
  if (action.form?.initialValue != null) {
252
249
  inputs['initialValue'] = action.form.initialValue;
253
250
  }
@@ -274,12 +271,14 @@ class CrudLauncherService {
274
271
  submitMethod: action.form?.submitMethod ?? null,
275
272
  apiEndpointKey: action.form?.apiEndpointKey ?? null,
276
273
  apiUrlEntry: action.form?.apiUrlEntry ?? null,
274
+ layoutPolicy: action.form?.layoutPolicy ?? null,
277
275
  };
278
276
  }
279
277
  if (!this.isCanonicalCrudAction(action.action)) {
280
278
  return {
281
279
  apiEndpointKey: action.form?.apiEndpointKey ?? null,
282
280
  apiUrlEntry: action.form?.apiUrlEntry ?? null,
281
+ layoutPolicy: action.form?.layoutPolicy ?? null,
283
282
  };
284
283
  }
285
284
  const resourcePath = String(metadata.resource?.path ?? metadata.table?.resourcePath ?? '').trim();
@@ -309,6 +308,7 @@ class CrudLauncherService {
309
308
  submitMethod: resolved?.submitMethod ?? null,
310
309
  apiEndpointKey: action.form?.apiEndpointKey ?? metadata.resource?.endpointKey ?? null,
311
310
  apiUrlEntry: action.form?.apiUrlEntry ?? null,
311
+ layoutPolicy: action.form?.layoutPolicy ?? null,
312
312
  };
313
313
  }
314
314
  resolveRuntimeContract(action, row, metadata, runtime) {
@@ -392,6 +392,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
392
392
  type: Injectable,
393
393
  args: [{ providedIn: 'root' }]
394
394
  }] });
395
+ function mergeClassList(base, extra) {
396
+ if (!extra) {
397
+ return base;
398
+ }
399
+ if (typeof extra === 'string') {
400
+ return [...base, extra];
401
+ }
402
+ if (Array.isArray(extra)) {
403
+ return [...base, ...extra];
404
+ }
405
+ return [
406
+ ...base,
407
+ ...Object.keys(extra).filter((className) => !!extra[className]),
408
+ ];
409
+ }
395
410
 
396
411
  const DOCUMENT_KIND = 'praxis.crud.editor';
397
412
  const DOCUMENT_VERSION = 1;
@@ -4657,7 +4672,7 @@ class PraxisCrudComponent {
4657
4672
  />
4658
4673
  }
4659
4674
  }
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"] }] });
4675
+ `, 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", "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
4676
  }
4662
4677
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudComponent, decorators: [{
4663
4678
  type: Component,
@@ -4756,6 +4771,7 @@ class DynamicFormDialogHostComponent {
4756
4771
  submitMethod;
4757
4772
  apiEndpointKey;
4758
4773
  apiUrlEntry;
4774
+ layoutPolicy;
4759
4775
  formActions;
4760
4776
  mode = 'create';
4761
4777
  backConfig;
@@ -4812,6 +4828,7 @@ class DynamicFormDialogHostComponent {
4812
4828
  this.apiUrlEntry = this.data.inputs?.['apiUrlEntry'] ?? null;
4813
4829
  const act = this.data.action?.action;
4814
4830
  this.mode = act === 'edit' ? 'edit' : act === 'view' ? 'view' : 'create';
4831
+ this.layoutPolicy = this.resolveLayoutPolicy();
4815
4832
  this.formActions = this.resolveFormActions();
4816
4833
  // Back config: defaults from metadata/action, overridden by saved per-form config
4817
4834
  const defaults = (this.data.action?.back || this.data.metadata?.defaults?.back) || {};
@@ -4857,6 +4874,7 @@ class DynamicFormDialogHostComponent {
4857
4874
  'apiEndpointKey',
4858
4875
  'apiUrlEntry',
4859
4876
  'initialValue',
4877
+ 'layoutPolicy',
4860
4878
  ]);
4861
4879
  const explicit = inputs['initialValue'] && typeof inputs['initialValue'] === 'object'
4862
4880
  ? { ...inputs['initialValue'] }
@@ -4890,6 +4908,15 @@ class DynamicFormDialogHostComponent {
4890
4908
  },
4891
4909
  };
4892
4910
  }
4911
+ resolveLayoutPolicy() {
4912
+ const explicit = this.data.inputs?.['layoutPolicy'] ??
4913
+ this.data.action?.form?.layoutPolicy ??
4914
+ this.data.action?.layoutPolicy;
4915
+ if (explicit && typeof explicit === 'object') {
4916
+ return explicit;
4917
+ }
4918
+ return null;
4919
+ }
4893
4920
  resolveSubmitLabel() {
4894
4921
  const action = this.data.action ?? {};
4895
4922
  const explicit = stringOrUndefined(action.form?.submitLabel ??
@@ -5074,6 +5101,7 @@ class DynamicFormDialogHostComponent {
5074
5101
  [submitMethod]="submitMethod"
5075
5102
  [apiEndpointKey]="apiEndpointKey"
5076
5103
  [apiUrlEntry]="apiUrlEntry"
5104
+ [layoutPolicy]="layoutPolicy"
5077
5105
  [presentationModeGlobal]="mode === 'view' ? true : null"
5078
5106
  [backConfig]="backConfig"
5079
5107
  [actions]="formActions"
@@ -5081,7 +5109,7 @@ class DynamicFormDialogHostComponent {
5081
5109
  (formCancel)="onCancel()"
5082
5110
  ></praxis-dynamic-form>
5083
5111
  </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"] }] });
5112
+ `, 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", "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"] }] });
5085
5113
  }
5086
5114
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
5087
5115
  type: Component,
@@ -5136,6 +5164,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5136
5164
  [submitMethod]="submitMethod"
5137
5165
  [apiEndpointKey]="apiEndpointKey"
5138
5166
  [apiUrlEntry]="apiUrlEntry"
5167
+ [layoutPolicy]="layoutPolicy"
5139
5168
  [presentationModeGlobal]="mode === 'view' ? true : null"
5140
5169
  [backConfig]="backConfig"
5141
5170
  [actions]="formActions"
@@ -5699,6 +5728,7 @@ const CRUD_AI_CAPABILITIES = {
5699
5728
  { path: 'actions[].params[].to', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.paramTarget, description: 'Destino do parametro.' },
5700
5729
  { path: 'actions[].params[].name', category: 'actions', valueKind: 'string', description: 'Nome do parametro no destino.' },
5701
5730
  { path: 'actions[].form.initialValue', category: 'actions', valueKind: 'object', description: 'Seed fixo do formulario injetado em inputs.initialValue antes da abertura.' },
5731
+ { 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
5732
  { path: 'actions[].back', category: 'navigation', valueKind: 'object', description: 'BackConfig por acao.' },
5703
5733
  { path: 'actions[].back.strategy', category: 'navigation', valueKind: 'enum', allowedValues: ENUMS.backStrategy, description: 'Estrategia de retorno da acao (auto, close, navigate).' },
5704
5734
  { path: 'actions[].back.returnTo', category: 'navigation', valueKind: 'string', description: 'Rota ou destino usado quando a estrategia de retorno for navigate.' },
@@ -5736,6 +5766,7 @@ const surfaceConfigureSchema = {
5736
5766
  submitMethod: { enum: ['post', 'put', 'patch', 'delete'] },
5737
5767
  apiEndpointKey: { type: 'string' },
5738
5768
  initialValue: { type: 'object' },
5769
+ layoutPolicy: { type: 'object' },
5739
5770
  },
5740
5771
  },
5741
5772
  params: {
@@ -6017,14 +6048,14 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
6017
6048
  target: { kind: 'dialogHost', resolver: 'crud-dialog-host-defaults', ambiguityPolicy: 'fail', required: true },
6018
6049
  inputSchema: dialogHostSchema,
6019
6050
  effects: [{ kind: 'compile-domain-patch', handler: 'crud-dialog-host-set', handlerContract: {
6020
- reads: ['CrudMetadata.defaults', 'CrudLauncherService.resolveOpenMode', 'DialogService', 'CRUD_DRAWER_ADAPTER'],
6051
+ reads: ['CrudMetadata.defaults', 'CrudLauncherService.resolveOpenMode', 'DialogService', 'DynamicFormDialogHostComponent', 'CRUD_DRAWER_ADAPTER'],
6021
6052
  writes: ['CrudMetadata.defaults.openMode', 'CrudMetadata.defaults.modal', 'CrudMetadata.defaults.back'],
6022
6053
  identityKeys: ['crudId'],
6023
6054
  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.',
6055
+ failureModes: ['open-mode-unsupported', 'drawer-runtime-unavailable', 'modal-size-invalid', 'back-policy-invalid'],
6056
+ description: 'Configures CRUD-owned route/modal/drawer defaults consumed by the launcher, default dialog-backed drawer runtime and optional host drawer adapter.',
6026
6057
  } }],
6027
- validators: ['open-mode-supported', 'modal-size-valid', 'drawer-adapter-available-when-needed', 'back-policy-valid', 'settings-panel-shell-compatible'],
6058
+ validators: ['open-mode-supported', 'modal-size-valid', 'drawer-runtime-available', 'back-policy-valid', 'settings-panel-shell-compatible'],
6028
6059
  affectedPaths: ['defaults.openMode', 'defaults.modal', 'defaults.back'],
6029
6060
  submissionImpact: 'config-only',
6030
6061
  preconditions: ['crud-metadata-loaded'],
@@ -6100,7 +6131,7 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
6100
6131
  { validatorId: 'permissions-delete-valid', level: 'error', code: 'CRUD_DELETE_PERMISSION_VALID', description: 'Delete permission cannot bypass resource capabilities or confirmation policy.' },
6101
6132
  { validatorId: 'open-mode-supported', level: 'error', code: 'CRUD_OPEN_MODE_SUPPORTED', description: 'Open mode must be route, modal or drawer.' },
6102
6133
  { 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.' },
6134
+ { validatorId: 'drawer-runtime-available', level: 'error', code: 'CRUD_DRAWER_RUNTIME_AVAILABLE', description: 'Drawer open mode uses the default dialog-backed drawer runtime; a host drawer adapter is optional for shell-specific presentation.' },
6104
6135
  { validatorId: 'back-policy-valid', level: 'error', code: 'CRUD_BACK_POLICY_VALID', description: 'Back policy must be valid for route/modal/drawer behavior.' },
6105
6136
  { validatorId: 'settings-panel-shell-compatible', level: 'warning', code: 'CRUD_SETTINGS_PANEL_COMPATIBLE', description: 'Authoring shell must preserve apply/save/reset semantics.' },
6106
6137
  { 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.3",
3
+ "version": "9.0.0-beta.31",
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.3",
9
- "@praxisui/table": "^9.0.0-beta.3",
10
- "@praxisui/core": "^9.0.0-beta.3",
11
- "@praxisui/dynamic-fields": "^9.0.0-beta.3",
12
- "@praxisui/settings-panel": "^9.0.0-beta.3",
8
+ "@praxisui/dynamic-form": "^9.0.0-beta.31",
9
+ "@praxisui/table": "^9.0.0-beta.31",
10
+ "@praxisui/core": "^9.0.0-beta.31",
11
+ "@praxisui/dynamic-fields": "^9.0.0-beta.31",
12
+ "@praxisui/settings-panel": "^9.0.0-beta.31",
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.3",
17
+ "@praxisui/ai": "^9.0.0-beta.31",
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) & {
@@ -316,6 +317,7 @@ declare class CrudLauncherService {
316
317
  submitMethod?: string | null;
317
318
  apiEndpointKey?: unknown;
318
319
  apiUrlEntry?: unknown;
320
+ layoutPolicy?: unknown;
319
321
  };
320
322
  private buildInferredFormId;
321
323
  private isCanonicalCrudAction;
@@ -347,6 +349,7 @@ declare class DynamicFormDialogHostComponent implements OnInit {
347
349
  submitMethod?: string | null;
348
350
  apiEndpointKey?: ApiEndpoint | string | null;
349
351
  apiUrlEntry?: ApiUrlEntry | null;
352
+ layoutPolicy?: DynamicFormLayoutPolicy | null;
350
353
  formActions?: FormConfig['actions'];
351
354
  mode: 'create' | 'edit' | 'view';
352
355
  backConfig?: BackConfig;
@@ -355,6 +358,7 @@ declare class DynamicFormDialogHostComponent implements OnInit {
355
358
  constructor(dialogRef: DialogRef<DynamicFormDialogHostComponent>, data: any, dialogService: DialogService, crud: GenericCrudService<any>, configStorage: AsyncConfigStorage);
356
359
  private extractInitialValue;
357
360
  private resolveFormActions;
361
+ private resolveLayoutPolicy;
358
362
  private resolveSubmitLabel;
359
363
  ngOnInit(): void;
360
364
  onSave(result: unknown): void;