@praxisui/crud 8.0.0-beta.0 → 8.0.0-beta.100

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,20 +1,18 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, InjectionToken, inject, input, signal, computed, effect, ChangeDetectionStrategy, Component, EventEmitter, DestroyRef, ChangeDetectorRef, 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, 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
- import { firstValueFrom, BehaviorSubject } from 'rxjs';
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, providePraxisI18nConfig, createDefaultTableConfig, ResourceDiscoveryService, ResourceActionOpenAdapterService, ResourceSurfaceOpenAdapterService, GLOBAL_SURFACE_SERVICE, ComponentKeyService, translateUnavailableWorkflowMessage, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, PraxisIconDirective, GenericCrudService, ComponentMetadataRegistry } from '@praxisui/core';
9
+ import { ASYNC_CONFIG_STORAGE, GlobalConfigService, CrudOperationResolutionService, fillUndefined, SETTINGS_PANEL_DATA, PraxisI18nService, providePraxisI18nConfig, createDefaultTableConfig, GLOBAL_SURFACE_SERVICE, ComponentKeyService, ResourceDiscoveryService, ResourceActionOpenAdapterService, ResourceSurfaceOpenAdapterService, translateUnavailableWorkflowMessage, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, PraxisIconDirective, 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';
13
13
  import * as i1 from '@angular/material/dialog';
14
14
  import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
15
15
  export { MAT_DIALOG_DATA as DIALOG_DATA } from '@angular/material/dialog';
16
- import * as i1$2 from '@angular/common';
17
- import { CommonModule } from '@angular/common';
18
16
  import * as i1$1 from '@angular/forms';
19
17
  import { FormsModule } from '@angular/forms';
20
18
  import * as i2 from '@angular/material/button';
@@ -50,10 +48,10 @@ class DialogService {
50
48
  const component = await loader();
51
49
  return this.zone.run(() => this.matDialog.open(component, config));
52
50
  }
53
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DialogService, deps: [{ token: i1.MatDialog }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
54
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DialogService, providedIn: 'root' });
51
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DialogService, deps: [{ token: i1.MatDialog }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
52
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DialogService, providedIn: 'root' });
55
53
  }
56
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DialogService, decorators: [{
54
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DialogService, decorators: [{
57
55
  type: Injectable,
58
56
  args: [{ providedIn: 'root' }]
59
57
  }], ctorParameters: () => [{ type: i1.MatDialog }, { type: i0.NgZone }] });
@@ -251,6 +249,9 @@ class CrudLauncherService {
251
249
  if (action.form?.initialValue != null) {
252
250
  inputs['initialValue'] = action.form.initialValue;
253
251
  }
252
+ else if (action.action !== 'create' && row && typeof row === 'object') {
253
+ inputs['initialValue'] = { ...row };
254
+ }
254
255
  return inputs;
255
256
  }
256
257
  resolveActionForLaunch(action, metadata) {
@@ -382,10 +383,10 @@ class CrudLauncherService {
382
383
  return { metadata, action };
383
384
  }
384
385
  }
385
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudLauncherService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
386
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudLauncherService, providedIn: 'root' });
386
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudLauncherService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
387
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudLauncherService, providedIn: 'root' });
387
388
  }
388
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudLauncherService, decorators: [{
389
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudLauncherService, decorators: [{
389
390
  type: Injectable,
390
391
  args: [{ providedIn: 'root' }]
391
392
  }] });
@@ -1235,10 +1236,10 @@ const MODAL_DENSITIES = ['default', 'compact'];
1235
1236
  const BACK_STRATEGIES = ['auto', 'close', 'navigate'];
1236
1237
  const PARAM_TARGETS = ['routeParam', 'query', 'input'];
1237
1238
  class CrudMetadataEditorComponent {
1238
- documentInput = input(null, ...(ngDevMode ? [{ debugName: "documentInput", alias: 'document' }] : [{ alias: 'document' }]));
1239
- metadataInput = input(null, ...(ngDevMode ? [{ debugName: "metadataInput", alias: 'metadata' }] : [{ alias: 'metadata' }]));
1240
- crudIdInput = input(null, ...(ngDevMode ? [{ debugName: "crudIdInput", alias: 'crudId' }] : [{ alias: 'crudId' }]));
1241
- readonlyInput = input(false, ...(ngDevMode ? [{ debugName: "readonlyInput", alias: 'readonly' }] : [{ alias: 'readonly' }]));
1239
+ documentInput = input(null, { ...(ngDevMode ? { debugName: "documentInput" } : /* istanbul ignore next */ {}), alias: 'document' });
1240
+ metadataInput = input(null, { ...(ngDevMode ? { debugName: "metadataInput" } : /* istanbul ignore next */ {}), alias: 'metadata' });
1241
+ crudIdInput = input(null, { ...(ngDevMode ? { debugName: "crudIdInput" } : /* istanbul ignore next */ {}), alias: 'crudId' });
1242
+ readonlyInput = input(false, { ...(ngDevMode ? { debugName: "readonlyInput" } : /* istanbul ignore next */ {}), alias: 'readonly' });
1242
1243
  isDirty$ = new BehaviorSubject(false);
1243
1244
  isValid$ = new BehaviorSubject(true);
1244
1245
  isBusy$ = new BehaviorSubject(false);
@@ -1250,39 +1251,39 @@ class CrudMetadataEditorComponent {
1250
1251
  modalDensities = MODAL_DENSITIES;
1251
1252
  backStrategies = BACK_STRATEGIES;
1252
1253
  paramTargets = PARAM_TARGETS;
1253
- initialValueDrafts = signal({}, ...(ngDevMode ? [{ debugName: "initialValueDrafts" }] : []));
1254
+ initialValueDrafts = signal({}, ...(ngDevMode ? [{ debugName: "initialValueDrafts" }] : /* istanbul ignore next */ []));
1254
1255
  injectedData = inject(SETTINGS_PANEL_DATA, { optional: true });
1255
1256
  i18n = inject(PraxisI18nService);
1256
- currentDocument = signal(createCrudAuthoringDocument({}), ...(ngDevMode ? [{ debugName: "currentDocument" }] : []));
1257
- initialDocument = signal(createCrudAuthoringDocument({}), ...(ngDevMode ? [{ debugName: "initialDocument" }] : []));
1257
+ currentDocument = signal(createCrudAuthoringDocument({}), ...(ngDevMode ? [{ debugName: "currentDocument" }] : /* istanbul ignore next */ []));
1258
+ initialDocument = signal(createCrudAuthoringDocument({}), ...(ngDevMode ? [{ debugName: "initialDocument" }] : /* istanbul ignore next */ []));
1258
1259
  lastExternalSignature = null;
1259
- effectiveCrudId = computed(() => this.crudIdInput() || this.injectedData?.crudId || null, ...(ngDevMode ? [{ debugName: "effectiveCrudId" }] : []));
1260
- isReadonly = computed(() => !!(this.readonlyInput() || this.injectedData?.readonly), ...(ngDevMode ? [{ debugName: "isReadonly" }] : []));
1261
- diagnostics = computed(() => validateCrudAuthoringDocument(this.currentDocument(), { requireCanonicalActions: true }), ...(ngDevMode ? [{ debugName: "diagnostics" }] : []));
1262
- errorCount = computed(() => this.diagnostics().filter((issue) => issue.level === 'error').length, ...(ngDevMode ? [{ debugName: "errorCount" }] : []));
1263
- warningCount = computed(() => this.diagnostics().filter((issue) => issue.level !== 'error').length, ...(ngDevMode ? [{ debugName: "warningCount" }] : []));
1264
- serializedDocument = computed(() => JSON.stringify(serializeCrudAuthoringDocument(this.currentDocument()), null, 2), ...(ngDevMode ? [{ debugName: "serializedDocument" }] : []));
1265
- tableConfig = computed(() => this.currentDocument().metadata.table || { columns: [] }, ...(ngDevMode ? [{ debugName: "tableConfig" }] : []));
1266
- resourcePath = computed(() => this.currentDocument().metadata.resource?.path || '', ...(ngDevMode ? [{ debugName: "resourcePath" }] : []));
1267
- resourceIdField = computed(() => String(this.currentDocument().metadata.resource?.idField ?? ''), ...(ngDevMode ? [{ debugName: "resourceIdField" }] : []));
1268
- resourceEndpointKey = computed(() => String(this.currentDocument().metadata.resource?.endpointKey ?? ''), ...(ngDevMode ? [{ debugName: "resourceEndpointKey" }] : []));
1269
- defaultsOpenMode = computed(() => this.currentDocument().metadata.defaults?.openMode || 'route', ...(ngDevMode ? [{ debugName: "defaultsOpenMode" }] : []));
1270
- headerShowBack = computed(() => !!this.currentDocument().metadata.defaults?.header?.showBack, ...(ngDevMode ? [{ debugName: "headerShowBack" }] : []));
1271
- headerBackLabel = computed(() => this.currentDocument().metadata.defaults?.header?.backLabel || '', ...(ngDevMode ? [{ debugName: "headerBackLabel" }] : []));
1272
- headerVariant = computed(() => this.currentDocument().metadata.defaults?.header?.variant || 'ghost', ...(ngDevMode ? [{ debugName: "headerVariant" }] : []));
1273
- headerSticky = computed(() => !!this.currentDocument().metadata.defaults?.header?.sticky, ...(ngDevMode ? [{ debugName: "headerSticky" }] : []));
1274
- headerBreadcrumbs = computed(() => !!this.currentDocument().metadata.defaults?.header?.breadcrumbs, ...(ngDevMode ? [{ debugName: "headerBreadcrumbs" }] : []));
1275
- headerDivider = computed(() => !!this.currentDocument().metadata.defaults?.header?.divider, ...(ngDevMode ? [{ debugName: "headerDivider" }] : []));
1276
- modalDensity = computed(() => String(this.currentDocument().metadata.defaults?.modal?.density ?? 'default'), ...(ngDevMode ? [{ debugName: "modalDensity" }] : []));
1277
- modalCanMaximize = computed(() => this.currentDocument().metadata.defaults?.modal?.canMaximize ?? true, ...(ngDevMode ? [{ debugName: "modalCanMaximize" }] : []));
1278
- modalStartMaximized = computed(() => !!this.currentDocument().metadata.defaults?.modal?.startMaximized, ...(ngDevMode ? [{ debugName: "modalStartMaximized" }] : []));
1279
- modalRememberLastState = computed(() => !!this.currentDocument().metadata.defaults?.modal?.rememberLastState, ...(ngDevMode ? [{ debugName: "modalRememberLastState" }] : []));
1280
- modalDisableCloseOnEsc = computed(() => !!this.currentDocument().metadata.defaults?.modal?.disableCloseOnEsc, ...(ngDevMode ? [{ debugName: "modalDisableCloseOnEsc" }] : []));
1281
- modalDisableCloseOnBackdrop = computed(() => !!this.currentDocument().metadata.defaults?.modal?.disableCloseOnBackdrop, ...(ngDevMode ? [{ debugName: "modalDisableCloseOnBackdrop" }] : []));
1282
- modalFullscreenBreakpoint = computed(() => String(this.currentDocument().metadata.defaults?.modal?.fullscreenBreakpoint ?? ''), ...(ngDevMode ? [{ debugName: "modalFullscreenBreakpoint" }] : []));
1283
- backStrategy = computed(() => String(this.currentDocument().metadata.defaults?.back?.strategy ?? 'auto'), ...(ngDevMode ? [{ debugName: "backStrategy" }] : []));
1284
- backReturnTo = computed(() => this.currentDocument().metadata.defaults?.back?.returnTo || '', ...(ngDevMode ? [{ debugName: "backReturnTo" }] : []));
1285
- backConfirmOnDirty = computed(() => !!this.currentDocument().metadata.defaults?.back?.confirmOnDirty, ...(ngDevMode ? [{ debugName: "backConfirmOnDirty" }] : []));
1260
+ effectiveCrudId = computed(() => this.crudIdInput() || this.injectedData?.crudId || null, ...(ngDevMode ? [{ debugName: "effectiveCrudId" }] : /* istanbul ignore next */ []));
1261
+ isReadonly = computed(() => !!(this.readonlyInput() || this.injectedData?.readonly), ...(ngDevMode ? [{ debugName: "isReadonly" }] : /* istanbul ignore next */ []));
1262
+ diagnostics = computed(() => validateCrudAuthoringDocument(this.currentDocument(), { requireCanonicalActions: true }), ...(ngDevMode ? [{ debugName: "diagnostics" }] : /* istanbul ignore next */ []));
1263
+ errorCount = computed(() => this.diagnostics().filter((issue) => issue.level === 'error').length, ...(ngDevMode ? [{ debugName: "errorCount" }] : /* istanbul ignore next */ []));
1264
+ warningCount = computed(() => this.diagnostics().filter((issue) => issue.level !== 'error').length, ...(ngDevMode ? [{ debugName: "warningCount" }] : /* istanbul ignore next */ []));
1265
+ serializedDocument = computed(() => JSON.stringify(serializeCrudAuthoringDocument(this.currentDocument()), null, 2), ...(ngDevMode ? [{ debugName: "serializedDocument" }] : /* istanbul ignore next */ []));
1266
+ tableConfig = computed(() => this.currentDocument().metadata.table || { columns: [] }, ...(ngDevMode ? [{ debugName: "tableConfig" }] : /* istanbul ignore next */ []));
1267
+ resourcePath = computed(() => this.currentDocument().metadata.resource?.path || '', ...(ngDevMode ? [{ debugName: "resourcePath" }] : /* istanbul ignore next */ []));
1268
+ resourceIdField = computed(() => String(this.currentDocument().metadata.resource?.idField ?? ''), ...(ngDevMode ? [{ debugName: "resourceIdField" }] : /* istanbul ignore next */ []));
1269
+ resourceEndpointKey = computed(() => String(this.currentDocument().metadata.resource?.endpointKey ?? ''), ...(ngDevMode ? [{ debugName: "resourceEndpointKey" }] : /* istanbul ignore next */ []));
1270
+ defaultsOpenMode = computed(() => this.currentDocument().metadata.defaults?.openMode || 'route', ...(ngDevMode ? [{ debugName: "defaultsOpenMode" }] : /* istanbul ignore next */ []));
1271
+ headerShowBack = computed(() => !!this.currentDocument().metadata.defaults?.header?.showBack, ...(ngDevMode ? [{ debugName: "headerShowBack" }] : /* istanbul ignore next */ []));
1272
+ headerBackLabel = computed(() => this.currentDocument().metadata.defaults?.header?.backLabel || '', ...(ngDevMode ? [{ debugName: "headerBackLabel" }] : /* istanbul ignore next */ []));
1273
+ headerVariant = computed(() => this.currentDocument().metadata.defaults?.header?.variant || 'ghost', ...(ngDevMode ? [{ debugName: "headerVariant" }] : /* istanbul ignore next */ []));
1274
+ headerSticky = computed(() => !!this.currentDocument().metadata.defaults?.header?.sticky, ...(ngDevMode ? [{ debugName: "headerSticky" }] : /* istanbul ignore next */ []));
1275
+ headerBreadcrumbs = computed(() => !!this.currentDocument().metadata.defaults?.header?.breadcrumbs, ...(ngDevMode ? [{ debugName: "headerBreadcrumbs" }] : /* istanbul ignore next */ []));
1276
+ headerDivider = computed(() => !!this.currentDocument().metadata.defaults?.header?.divider, ...(ngDevMode ? [{ debugName: "headerDivider" }] : /* istanbul ignore next */ []));
1277
+ modalDensity = computed(() => String(this.currentDocument().metadata.defaults?.modal?.density ?? 'default'), ...(ngDevMode ? [{ debugName: "modalDensity" }] : /* istanbul ignore next */ []));
1278
+ modalCanMaximize = computed(() => this.currentDocument().metadata.defaults?.modal?.canMaximize ?? true, ...(ngDevMode ? [{ debugName: "modalCanMaximize" }] : /* istanbul ignore next */ []));
1279
+ modalStartMaximized = computed(() => !!this.currentDocument().metadata.defaults?.modal?.startMaximized, ...(ngDevMode ? [{ debugName: "modalStartMaximized" }] : /* istanbul ignore next */ []));
1280
+ modalRememberLastState = computed(() => !!this.currentDocument().metadata.defaults?.modal?.rememberLastState, ...(ngDevMode ? [{ debugName: "modalRememberLastState" }] : /* istanbul ignore next */ []));
1281
+ modalDisableCloseOnEsc = computed(() => !!this.currentDocument().metadata.defaults?.modal?.disableCloseOnEsc, ...(ngDevMode ? [{ debugName: "modalDisableCloseOnEsc" }] : /* istanbul ignore next */ []));
1282
+ modalDisableCloseOnBackdrop = computed(() => !!this.currentDocument().metadata.defaults?.modal?.disableCloseOnBackdrop, ...(ngDevMode ? [{ debugName: "modalDisableCloseOnBackdrop" }] : /* istanbul ignore next */ []));
1283
+ modalFullscreenBreakpoint = computed(() => String(this.currentDocument().metadata.defaults?.modal?.fullscreenBreakpoint ?? ''), ...(ngDevMode ? [{ debugName: "modalFullscreenBreakpoint" }] : /* istanbul ignore next */ []));
1284
+ backStrategy = computed(() => String(this.currentDocument().metadata.defaults?.back?.strategy ?? 'auto'), ...(ngDevMode ? [{ debugName: "backStrategy" }] : /* istanbul ignore next */ []));
1285
+ backReturnTo = computed(() => this.currentDocument().metadata.defaults?.back?.returnTo || '', ...(ngDevMode ? [{ debugName: "backReturnTo" }] : /* istanbul ignore next */ []));
1286
+ backConfirmOnDirty = computed(() => !!this.currentDocument().metadata.defaults?.back?.confirmOnDirty, ...(ngDevMode ? [{ debugName: "backConfirmOnDirty" }] : /* istanbul ignore next */ []));
1286
1287
  nextFocusSection = computed(() => {
1287
1288
  for (const section of ['connection', 'defaults', 'actions', 'table']) {
1288
1289
  if (this.sectionStatus(section) === 'invalid') {
@@ -1295,8 +1296,8 @@ class CrudMetadataEditorComponent {
1295
1296
  }
1296
1297
  }
1297
1298
  return 'connection';
1298
- }, ...(ngDevMode ? [{ debugName: "nextFocusSection" }] : []));
1299
- nextFocusStatus = computed(() => this.sectionStatus(this.nextFocusSection()), ...(ngDevMode ? [{ debugName: "nextFocusStatus" }] : []));
1299
+ }, ...(ngDevMode ? [{ debugName: "nextFocusSection" }] : /* istanbul ignore next */ []));
1300
+ nextFocusStatus = computed(() => this.sectionStatus(this.nextFocusSection()), ...(ngDevMode ? [{ debugName: "nextFocusStatus" }] : /* istanbul ignore next */ []));
1300
1301
  visibleHealthBuckets = computed(() => {
1301
1302
  const attentionBuckets = ['invalid', 'pending']
1302
1303
  .filter((bucket) => this.hasHealthBucketContent(bucket));
@@ -1304,8 +1305,8 @@ class CrudMetadataEditorComponent {
1304
1305
  return attentionBuckets;
1305
1306
  }
1306
1307
  return this.hasHealthBucketContent('ready') ? ['ready'] : [];
1307
- }, ...(ngDevMode ? [{ debugName: "visibleHealthBuckets" }] : []));
1308
- showHealthMap = computed(() => this.visibleHealthBuckets().length > 1, ...(ngDevMode ? [{ debugName: "showHealthMap" }] : []));
1308
+ }, ...(ngDevMode ? [{ debugName: "visibleHealthBuckets" }] : /* istanbul ignore next */ []));
1309
+ showHealthMap = computed(() => this.visibleHealthBuckets().length > 1, ...(ngDevMode ? [{ debugName: "showHealthMap" }] : /* istanbul ignore next */ []));
1309
1310
  groupedDiagnostics = computed(() => ['connection', 'defaults', 'actions', 'table']
1310
1311
  .map((section) => {
1311
1312
  const issues = this.sectionDiagnostics(section);
@@ -1315,7 +1316,7 @@ class CrudMetadataEditorComponent {
1315
1316
  hasError: issues.some((issue) => issue.level === 'error'),
1316
1317
  };
1317
1318
  })
1318
- .filter((group) => group.issues.length > 0), ...(ngDevMode ? [{ debugName: "groupedDiagnostics" }] : []));
1319
+ .filter((group) => group.issues.length > 0), ...(ngDevMode ? [{ debugName: "groupedDiagnostics" }] : /* istanbul ignore next */ []));
1319
1320
  constructor() {
1320
1321
  effect(() => {
1321
1322
  const seed = this.resolveExternalSeed();
@@ -2307,8 +2308,8 @@ class CrudMetadataEditorComponent {
2307
2308
  table: this.tx('crud.authoring.section.table', 'Table'),
2308
2309
  }[section];
2309
2310
  }
2310
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudMetadataEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2311
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: CrudMetadataEditorComponent, isStandalone: true, selector: "praxis-crud-metadata-editor", inputs: { documentInput: { classPropertyName: "documentInput", publicName: "document", isSignal: true, isRequired: false, transformFunction: null }, metadataInput: { classPropertyName: "metadataInput", publicName: "metadata", isSignal: true, isRequired: false, transformFunction: null }, crudIdInput: { classPropertyName: "crudIdInput", publicName: "crudId", isSignal: true, isRequired: false, transformFunction: null }, readonlyInput: { classPropertyName: "readonlyInput", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null } }, providers: [providePraxisI18nConfig(PRAXIS_CRUD_AUTHORING_I18N_CONFIG)], ngImport: i0, template: `
2311
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudMetadataEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2312
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: CrudMetadataEditorComponent, isStandalone: true, selector: "praxis-crud-metadata-editor", inputs: { documentInput: { classPropertyName: "documentInput", publicName: "document", isSignal: true, isRequired: false, transformFunction: null }, metadataInput: { classPropertyName: "metadataInput", publicName: "metadata", isSignal: true, isRequired: false, transformFunction: null }, crudIdInput: { classPropertyName: "crudIdInput", publicName: "crudId", isSignal: true, isRequired: false, transformFunction: null }, readonlyInput: { classPropertyName: "readonlyInput", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null } }, providers: [providePraxisI18nConfig(PRAXIS_CRUD_AUTHORING_I18N_CONFIG)], ngImport: i0, template: `
2312
2313
  <section class="editor-shell" data-testid="crud-metadata-editor">
2313
2314
  <header class="editor-header">
2314
2315
  <div>
@@ -2783,12 +2784,11 @@ class CrudMetadataEditorComponent {
2783
2784
  <pre data-testid="crud-editor-json">{{ serializedDocument() }}</pre>
2784
2785
  </mat-card>
2785
2786
  </section>
2786
- `, isInline: true, styles: [":host{display:block;min-width:0;color:var(--md-sys-color-on-surface,#1a1b20)}.editor-shell{display:grid;gap:16px}.editor-header{display:flex;justify-content:space-between;gap:16px;align-items:start}.editor-overview,.editor-health-map{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.editor-health-card{display:grid;gap:4px;padding:14px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-health-card--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-health-card--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-focus{display:flex;gap:12px;align-items:start;padding:14px 16px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-focus--success{background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent)}.editor-focus-chip{padding:6px 10px;border-radius:999px;font-size:12px;background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent);white-space:nowrap}.editor-focus-chip--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-focus-chip--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-focus-copy{display:grid;gap:4px}.editor-focus-copy p{margin:0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-overview-card{display:grid;gap:4px;padding:14px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-overview-card--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-overview-card--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-overview-label{font-size:12px;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-overview-value{line-height:1.3;overflow-wrap:anywhere}.editor-overview-note{margin:0;font-size:12px;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-header h2,.editor-card h3{margin:0}.editor-header p{margin:6px 0 0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-chip{padding:6px 10px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary,#1263b4) 12%,transparent);font-size:12px}.editor-card{display:grid;gap:14px;padding:16px;border-radius:20px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline,#c5c7ce) 70%,transparent)}.editor-section-header{display:flex;justify-content:space-between;gap:12px;align-items:start}.editor-grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-grid--compact{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.editor-toggles{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-actions,.action-grid{display:grid;gap:14px}.action-summary-strip{display:flex;flex-wrap:wrap;gap:8px}.action-summary-chip{padding:6px 10px;border-radius:999px;font-size:12px;background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent)}.action-summary-chip--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.action-summary-chip--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.action-group{display:grid;gap:10px;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 88%,transparent)}.action-group--advanced{background:color-mix(in srgb,var(--md-sys-color-surface-container,#f5f7fb) 90%,transparent)}.action-group__header{display:grid;gap:4px}.action-subgroup{display:grid;gap:10px;padding:10px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low,#fafbfd) 92%,transparent)}.action-subgroup__header{display:grid;gap:4px}.action-param-list{display:grid;gap:12px}.action-param-row{display:grid;gap:8px}.action-advanced-panel{border:1px solid color-mix(in srgb,var(--md-sys-color-outline,#c5c7ce) 65%,transparent);border-radius:16px}.editor-section-note{margin:0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.diagnostics-groups{display:grid;gap:12px}.diagnostics-group{display:grid;gap:10px;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.diagnostics{display:grid;gap:8px;margin:0;padding-left:18px}.diagnostics .error{color:var(--md-sys-color-error,#b3261e)}pre{margin:0;overflow:auto;max-height:320px;padding:12px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i3.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i4.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i4.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i4.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i4.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i7.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i8.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: PraxisTableInlineAuthoringEditorComponent, selector: "praxis-table-inline-authoring-editor", inputs: ["config", "readonly"], outputs: ["configChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2787
+ `, isInline: true, styles: [":host{display:block;min-width:0;color:var(--md-sys-color-on-surface,#1a1b20)}.editor-shell{display:grid;gap:16px}.editor-header{display:flex;justify-content:space-between;gap:16px;align-items:start}.editor-overview,.editor-health-map{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.editor-health-card{display:grid;gap:4px;padding:14px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-health-card--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-health-card--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-focus{display:flex;gap:12px;align-items:start;padding:14px 16px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-focus--success{background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent)}.editor-focus-chip{padding:6px 10px;border-radius:999px;font-size:12px;background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent);white-space:nowrap}.editor-focus-chip--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-focus-chip--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-focus-copy{display:grid;gap:4px}.editor-focus-copy p{margin:0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-overview-card{display:grid;gap:4px;padding:14px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-overview-card--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-overview-card--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-overview-label{font-size:12px;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-overview-value{line-height:1.3;overflow-wrap:anywhere}.editor-overview-note{margin:0;font-size:12px;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-header h2,.editor-card h3{margin:0}.editor-header p{margin:6px 0 0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-chip{padding:6px 10px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary,#1263b4) 12%,transparent);font-size:12px}.editor-card{display:grid;gap:14px;padding:16px;border-radius:20px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline,#c5c7ce) 70%,transparent)}.editor-section-header{display:flex;justify-content:space-between;gap:12px;align-items:start}.editor-grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-grid--compact{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.editor-toggles{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-actions,.action-grid{display:grid;gap:14px}.action-summary-strip{display:flex;flex-wrap:wrap;gap:8px}.action-summary-chip{padding:6px 10px;border-radius:999px;font-size:12px;background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent)}.action-summary-chip--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.action-summary-chip--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.action-group{display:grid;gap:10px;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 88%,transparent)}.action-group--advanced{background:color-mix(in srgb,var(--md-sys-color-surface-container,#f5f7fb) 90%,transparent)}.action-group__header{display:grid;gap:4px}.action-subgroup{display:grid;gap:10px;padding:10px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low,#fafbfd) 92%,transparent)}.action-subgroup__header{display:grid;gap:4px}.action-param-list{display:grid;gap:12px}.action-param-row{display:grid;gap:8px}.action-advanced-panel{border:1px solid color-mix(in srgb,var(--md-sys-color-outline,#c5c7ce) 65%,transparent);border-radius:16px}.editor-section-note{margin:0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.diagnostics-groups{display:grid;gap:12px}.diagnostics-group{display:grid;gap:10px;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.diagnostics{display:grid;gap:8px;margin:0;padding-left:18px}.diagnostics .error{color:var(--md-sys-color-error,#b3261e)}pre{margin:0;overflow:auto;max-height:320px;padding:12px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i3.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i4.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i4.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i4.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i4.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i7.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i8.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: PraxisTableInlineAuthoringEditorComponent, selector: "praxis-table-inline-authoring-editor", inputs: ["config", "readonly"], outputs: ["configChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2787
2788
  }
2788
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudMetadataEditorComponent, decorators: [{
2789
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudMetadataEditorComponent, decorators: [{
2789
2790
  type: Component,
2790
2791
  args: [{ selector: 'praxis-crud-metadata-editor', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
2791
- CommonModule,
2792
2792
  FormsModule,
2793
2793
  MatButtonModule,
2794
2794
  MatCardModule,
@@ -2797,7 +2797,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2797
2797
  MatInputModule,
2798
2798
  MatSelectModule,
2799
2799
  MatSlideToggleModule,
2800
- PraxisTableInlineAuthoringEditorComponent,
2800
+ PraxisTableInlineAuthoringEditorComponent
2801
2801
  ], providers: [providePraxisI18nConfig(PRAXIS_CRUD_AUTHORING_I18N_CONFIG)], template: `
2802
2802
  <section class="editor-shell" data-testid="crud-metadata-editor">
2803
2803
  <header class="editor-header">
@@ -3308,6 +3308,8 @@ class PraxisCrudComponent {
3308
3308
  afterSave = new EventEmitter();
3309
3309
  afterDelete = new EventEmitter();
3310
3310
  error = new EventEmitter();
3311
+ rowClick = new EventEmitter();
3312
+ selectionChange = new EventEmitter();
3311
3313
  tableRuntimeConfigChange = new EventEmitter();
3312
3314
  crudAuthoringDocumentApplied = new EventEmitter();
3313
3315
  crudAuthoringDocumentSaved = new EventEmitter();
@@ -3327,9 +3329,10 @@ class PraxisCrudComponent {
3327
3329
  snack = inject(MatSnackBar);
3328
3330
  dialog = inject(DialogService);
3329
3331
  i18n = inject(PraxisI18nService);
3330
- resourceDiscovery = inject(ResourceDiscoveryService);
3331
- actionOpenAdapter = inject(ResourceActionOpenAdapterService);
3332
- surfaceOpenAdapter = inject(ResourceSurfaceOpenAdapterService);
3332
+ injector = inject(Injector);
3333
+ resourceDiscoveryInstance;
3334
+ actionOpenAdapterInstance;
3335
+ surfaceOpenAdapterInstance;
3333
3336
  global = (() => {
3334
3337
  try {
3335
3338
  return inject(GlobalConfigService);
@@ -3359,6 +3362,18 @@ class PraxisCrudComponent {
3359
3362
  collectionCapabilitiesResolvedHref = null;
3360
3363
  collectionCapabilitiesRequestSeq = 0;
3361
3364
  currentAuthoringDocument;
3365
+ getResourceDiscovery() {
3366
+ const assigned = this.resourceDiscovery;
3367
+ return assigned ?? (this.resourceDiscoveryInstance ??= this.injector.get(ResourceDiscoveryService));
3368
+ }
3369
+ getActionOpenAdapter() {
3370
+ const assigned = this.actionOpenAdapter;
3371
+ return assigned ?? (this.actionOpenAdapterInstance ??= this.injector.get(ResourceActionOpenAdapterService));
3372
+ }
3373
+ getSurfaceOpenAdapter() {
3374
+ const assigned = this.surfaceOpenAdapter;
3375
+ return assigned ?? (this.surfaceOpenAdapterInstance ??= this.injector.get(ResourceSurfaceOpenAdapterService));
3376
+ }
3362
3377
  onResetPreferences() {
3363
3378
  try {
3364
3379
  const keyId = this.componentKeyId();
@@ -3372,6 +3387,12 @@ class PraxisCrudComponent {
3372
3387
  }
3373
3388
  catch { }
3374
3389
  }
3390
+ onTableRowClick(event) {
3391
+ this.rowClick.emit(event);
3392
+ }
3393
+ onTableSelectionChange(event) {
3394
+ this.selectionChange.emit(event);
3395
+ }
3375
3396
  ngOnChanges(changes) {
3376
3397
  if (!changes['metadata'] && !changes['context']) {
3377
3398
  return;
@@ -3409,14 +3430,6 @@ class PraxisCrudComponent {
3409
3430
  async onAction(action, row, runtimeEvent) {
3410
3431
  try {
3411
3432
  document.activeElement?.blur();
3412
- const openedByDiscovery = await this.tryOpenDiscoveredCrudSurface(action, row);
3413
- if (openedByDiscovery) {
3414
- return;
3415
- }
3416
- const handledByWorkflowAction = await this.tryOpenDiscoveredWorkflowAction(action, row, runtimeEvent);
3417
- if (handledByWorkflowAction) {
3418
- return;
3419
- }
3420
3433
  let actionMeta = this.resolvedMetadata.actions?.find((candidate) => candidate.action === action);
3421
3434
  if (!actionMeta) {
3422
3435
  const ctxAction = this.tableCrudContext?.actions?.find((candidate) => candidate.action === action);
@@ -3433,6 +3446,16 @@ class PraxisCrudComponent {
3433
3446
  }
3434
3447
  }
3435
3448
  const effectiveAction = (actionMeta || { action });
3449
+ if (!this.hasExplicitOpenBinding(effectiveAction)) {
3450
+ const openedByDiscovery = await this.tryOpenDiscoveredCrudSurface(action, row, runtimeEvent);
3451
+ if (openedByDiscovery) {
3452
+ return;
3453
+ }
3454
+ const handledByWorkflowAction = await this.tryOpenDiscoveredWorkflowAction(action, row, runtimeEvent);
3455
+ if (handledByWorkflowAction) {
3456
+ return;
3457
+ }
3458
+ }
3436
3459
  const handledByDelete = await this.tryHandleCanonicalDeleteAction(effectiveAction, row);
3437
3460
  if (handledByDelete) {
3438
3461
  return;
@@ -3495,6 +3518,19 @@ class PraxisCrudComponent {
3495
3518
  this.error.emit(err);
3496
3519
  }
3497
3520
  }
3521
+ hasExplicitOpenBinding(action) {
3522
+ const mode = action.openMode;
3523
+ if (mode === 'route') {
3524
+ return !!String(action.route || '').trim();
3525
+ }
3526
+ if (mode === 'modal' || mode === 'drawer') {
3527
+ return !!String(action.formId || '').trim();
3528
+ }
3529
+ return !!(String(action.route || '').trim() ||
3530
+ String(action.formId || '').trim() ||
3531
+ String(action.form?.schemaUrl || '').trim() ||
3532
+ String(action.form?.submitUrl || '').trim());
3533
+ }
3498
3534
  getCurrentTableConfigSnapshot() {
3499
3535
  const current = this.table?.config || this.tableConfigForBinding || this.effectiveTableConfig;
3500
3536
  if (!current)
@@ -3537,7 +3573,7 @@ class PraxisCrudComponent {
3537
3573
  }
3538
3574
  const requestSeq = ++this.collectionCapabilitiesRequestSeq;
3539
3575
  this.collectionCapabilitiesRequestHref = capabilitiesHref;
3540
- void firstValueFrom(this.resourceDiscovery.getCapabilities(links || {}, this.buildDiscoveryOptions()))
3576
+ void firstValueFrom(this.getResourceDiscovery().getCapabilities(links || {}, this.buildDiscoveryOptions()))
3541
3577
  .then((snapshot) => {
3542
3578
  if (requestSeq !== this.collectionCapabilitiesRequestSeq) {
3543
3579
  return;
@@ -3591,6 +3627,7 @@ class PraxisCrudComponent {
3591
3627
  }
3592
3628
  onConfigureRequested() {
3593
3629
  this.configureRequested.emit();
3630
+ this.openCrudAuthoringFromTable();
3594
3631
  }
3595
3632
  async tryHandleCanonicalDeleteAction(action, row) {
3596
3633
  const normalizedAction = String(action.action || '').trim().toLowerCase();
@@ -3704,25 +3741,35 @@ class PraxisCrudComponent {
3704
3741
  resolveFilterCriteria(meta) {
3705
3742
  return this.isRecord(meta?.filterCriteria) ? { ...meta.filterCriteria } : {};
3706
3743
  }
3707
- async tryOpenDiscoveredCrudSurface(action, row) {
3744
+ async tryOpenDiscoveredCrudSurface(action, row, runtimeEvent) {
3708
3745
  const normalizedAction = String(action || '').trim().toLowerCase();
3709
- if (!this.isDiscoveryManagedCrudAction(normalizedAction) || !this.surfaceService) {
3746
+ if (!this.surfaceService) {
3747
+ return false;
3748
+ }
3749
+ const providedSurface = this.resolveProvidedSurface(normalizedAction, runtimeEvent?.actionConfig);
3750
+ if (!providedSurface && !this.isDiscoveryManagedCrudAction(normalizedAction)) {
3710
3751
  return false;
3711
3752
  }
3712
- const catalog = await this.resolveDiscoveredSurfaceCatalog(normalizedAction, row);
3713
- const surface = this.selectSurfaceForCrudAction(normalizedAction, catalog?.surfaces || []);
3753
+ const catalog = providedSurface
3754
+ ? null
3755
+ : await this.resolveDiscoveredSurfaceCatalog(normalizedAction, row);
3756
+ const surface = providedSurface || this.selectSurfaceForCrudAction(normalizedAction, catalog?.surfaces || []);
3714
3757
  const resourcePath = String(catalog?.resourcePath || this.resolveResourcePath(this.resolvedMetadata) || '').trim();
3715
- if (!catalog || !surface || !resourcePath) {
3758
+ if (!surface || !resourcePath) {
3716
3759
  return false;
3717
3760
  }
3761
+ if (surface.availability?.allowed === false) {
3762
+ this.snack.open(translateUnavailableWorkflowMessage(this.i18n, surface.availability), undefined, { duration: 2500 });
3763
+ return true;
3764
+ }
3718
3765
  let payload;
3719
3766
  try {
3720
- payload = this.surfaceOpenAdapter.toPayload(surface, {
3767
+ payload = this.getSurfaceOpenAdapter().toPayload(surface, {
3721
3768
  resourcePath,
3722
3769
  resourceId: this.resolveRowResourceId(row),
3723
3770
  endpointKey: this.resolvedMetadata?.resource?.endpointKey,
3724
3771
  apiUrlEntry: this.resolveDiscoveryApiEntry(),
3725
- group: catalog.group ?? null,
3772
+ group: catalog?.group ?? null,
3726
3773
  });
3727
3774
  }
3728
3775
  catch {
@@ -3767,13 +3814,13 @@ class PraxisCrudComponent {
3767
3814
  if (!this.tableCollectionLinks) {
3768
3815
  return null;
3769
3816
  }
3770
- return await firstValueFrom(this.resourceDiscovery.getSurfaces(this.tableCollectionLinks, this.buildDiscoveryOptions()));
3817
+ return await firstValueFrom(this.getResourceDiscovery().getSurfaces(this.tableCollectionLinks, this.buildDiscoveryOptions()));
3771
3818
  }
3772
3819
  const rowLinks = row?._links;
3773
3820
  if (!rowLinks) {
3774
3821
  return null;
3775
3822
  }
3776
- return await firstValueFrom(this.resourceDiscovery.getSurfaces(rowLinks, this.buildDiscoveryOptions()));
3823
+ return await firstValueFrom(this.getResourceDiscovery().getSurfaces(rowLinks, this.buildDiscoveryOptions()));
3777
3824
  }
3778
3825
  catch {
3779
3826
  return null;
@@ -3797,7 +3844,7 @@ class PraxisCrudComponent {
3797
3844
  }
3798
3845
  let payload;
3799
3846
  try {
3800
- payload = this.actionOpenAdapter.toPayload(discoveredAction, {
3847
+ payload = this.getActionOpenAdapter().toPayload(discoveredAction, {
3801
3848
  resourcePath,
3802
3849
  resourceId: this.resolveRowResourceId(row),
3803
3850
  endpointKey: this.resolvedMetadata?.resource?.endpointKey,
@@ -3848,12 +3895,12 @@ class PraxisCrudComponent {
3848
3895
  if (!rowLinks) {
3849
3896
  return null;
3850
3897
  }
3851
- return await firstValueFrom(this.resourceDiscovery.getActions(rowLinks, this.buildDiscoveryOptions()));
3898
+ return await firstValueFrom(this.getResourceDiscovery().getActions(rowLinks, this.buildDiscoveryOptions()));
3852
3899
  }
3853
3900
  if (!this.tableCollectionLinks) {
3854
3901
  return null;
3855
3902
  }
3856
- return await firstValueFrom(this.resourceDiscovery.getActions(this.tableCollectionLinks, this.buildDiscoveryOptions()));
3903
+ return await firstValueFrom(this.getResourceDiscovery().getActions(this.tableCollectionLinks, this.buildDiscoveryOptions()));
3857
3904
  }
3858
3905
  catch {
3859
3906
  return null;
@@ -3880,6 +3927,23 @@ class PraxisCrudComponent {
3880
3927
  }
3881
3928
  return candidate;
3882
3929
  }
3930
+ resolveProvidedSurface(action, candidate) {
3931
+ if (!candidate || typeof candidate !== 'object') {
3932
+ return null;
3933
+ }
3934
+ const normalizedId = String(candidate.id || '').trim().toLowerCase();
3935
+ if (!normalizedId || normalizedId !== action) {
3936
+ return null;
3937
+ }
3938
+ const surface = candidate;
3939
+ if (!surface.kind || !surface.scope || !surface.path || !surface.method) {
3940
+ return null;
3941
+ }
3942
+ if (!this.isWritableCrudSurface(surface) && !this.isReadableCrudSurface(surface)) {
3943
+ return null;
3944
+ }
3945
+ return surface;
3946
+ }
3883
3947
  selectSurfaceForCrudAction(action, surfaces) {
3884
3948
  const candidates = surfaces
3885
3949
  .filter((surface) => surface.availability?.allowed !== false)
@@ -4084,13 +4148,27 @@ class PraxisCrudComponent {
4084
4148
  });
4085
4149
  }
4086
4150
  if (rowActions.length) {
4151
+ const rawMaxInline = Number(crudDefaults.rowActionsMaxInline);
4152
+ const maxInline = Number.isFinite(rawMaxInline)
4153
+ ? Math.max(0, Math.floor(rawMaxInline))
4154
+ : 2;
4087
4155
  cfg.actions = cfg.actions || {};
4088
4156
  cfg.actions.row = {
4089
4157
  enabled: true,
4090
4158
  position: 'end',
4091
- width: cfg.actions?.row?.width || '120px',
4159
+ width: cfg.actions?.row?.width || crudDefaults.rowActionsWidth || '144px',
4092
4160
  display: crudDefaults.rowActionsDisplay || 'icons',
4093
- trigger: cfg.actions?.row?.trigger || 'hover',
4161
+ trigger: cfg.actions?.row?.trigger || crudDefaults.rowActionsTrigger || 'always',
4162
+ menuIcon: crudDefaults.rowActionsMenuIcon || 'more_vert',
4163
+ maxVisibleActions: maxInline,
4164
+ behavior: {
4165
+ enabled: true,
4166
+ maxInline,
4167
+ autoStrategy: 'breakpoints',
4168
+ },
4169
+ discovery: {
4170
+ enabled: crudDefaults.rowActionDiscovery !== false,
4171
+ },
4094
4172
  actions: rowActions,
4095
4173
  header: { label: cfg.actions?.row?.header?.label || '' },
4096
4174
  };
@@ -4300,7 +4378,7 @@ class PraxisCrudComponent {
4300
4378
  }
4301
4379
  resolveCollectionCapabilitiesHref(links) {
4302
4380
  try {
4303
- return this.resourceDiscovery.resolveLinkHref(links || undefined, 'capabilities', this.buildDiscoveryOptions());
4381
+ return this.getResourceDiscovery().resolveLinkHref(links || undefined, 'capabilities', this.buildDiscoveryOptions());
4304
4382
  }
4305
4383
  catch {
4306
4384
  return null;
@@ -4320,7 +4398,7 @@ class PraxisCrudComponent {
4320
4398
  }
4321
4399
  resolveDiscoveryApiEntry() {
4322
4400
  try {
4323
- return this.resourceDiscovery.resolveApiEntry(this.buildDiscoveryOptions());
4401
+ return this.getResourceDiscovery().resolveApiEntry(this.buildDiscoveryOptions());
4324
4402
  }
4325
4403
  catch {
4326
4404
  return null;
@@ -4380,8 +4458,8 @@ class PraxisCrudComponent {
4380
4458
  return fallback;
4381
4459
  }
4382
4460
  }
4383
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisCrudComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4384
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisCrudComponent, isStandalone: true, selector: "praxis-crud", inputs: { metadata: "metadata", crudId: "crudId", componentInstanceId: "componentInstanceId", context: "context", enableCustomization: "enableCustomization" }, outputs: { configureRequested: "configureRequested", afterOpen: "afterOpen", afterClose: "afterClose", afterSave: "afterSave", afterDelete: "afterDelete", error: "error", tableRuntimeConfigChange: "tableRuntimeConfigChange", crudAuthoringDocumentApplied: "crudAuthoringDocumentApplied", crudAuthoringDocumentSaved: "crudAuthoringDocumentSaved" }, providers: [
4461
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4462
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisCrudComponent, isStandalone: true, selector: "praxis-crud", inputs: { metadata: "metadata", crudId: "crudId", componentInstanceId: "componentInstanceId", context: "context", enableCustomization: "enableCustomization" }, outputs: { configureRequested: "configureRequested", afterOpen: "afterOpen", afterClose: "afterClose", afterSave: "afterSave", afterDelete: "afterDelete", error: "error", rowClick: "rowClick", selectionChange: "selectionChange", tableRuntimeConfigChange: "tableRuntimeConfigChange", crudAuthoringDocumentApplied: "crudAuthoringDocumentApplied", crudAuthoringDocumentSaved: "crudAuthoringDocumentSaved" }, providers: [
4385
4463
  providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
4386
4464
  providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG),
4387
4465
  ], viewQueries: [{ propertyName: "table", first: true, predicate: PraxisTable, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
@@ -4395,6 +4473,8 @@ class PraxisCrudComponent {
4395
4473
  [tableId]="crudId || 'default'"
4396
4474
  [crudContext]="tableCrudContext"
4397
4475
  [enableCustomization]="enableCustomization"
4476
+ (rowClick)="onTableRowClick($event)"
4477
+ (selectionChange)="onTableSelectionChange($event)"
4398
4478
  (rowAction)="onAction($event.action, $event.row, $event)"
4399
4479
  (toolbarAction)="onAction($event.action)"
4400
4480
  (collectionLinksChange)="onCollectionLinksChange($event)"
@@ -4412,9 +4492,9 @@ class PraxisCrudComponent {
4412
4492
  />
4413
4493
  }
4414
4494
  }
4415
- `, 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", "title", "subtitle", "icon", "autoDelete", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "crudContext", "filterCriteria", "queryContext", "enableCustomization", "dense"], outputs: ["rowClick", "rowDoubleClick", "rowExpansionChange", "rowAction", "toolbarAction", "bulkAction", "columnReorder", "columnReorderAttempt", "beforeDelete", "afterDelete", "deleteError", "beforeBulkDelete", "afterBulkDelete", "bulkDeleteError", "schemaStatusChange", "metadataChange", "loadingStateChange", "collectionLinksChange"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone"] }] });
4495
+ `, 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", "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"] }] });
4416
4496
  }
4417
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisCrudComponent, decorators: [{
4497
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudComponent, decorators: [{
4418
4498
  type: Component,
4419
4499
  args: [{ selector: 'praxis-crud', standalone: true, imports: [PraxisTable, EmptyStateCardComponent], providers: [
4420
4500
  providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
@@ -4430,6 +4510,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
4430
4510
  [tableId]="crudId || 'default'"
4431
4511
  [crudContext]="tableCrudContext"
4432
4512
  [enableCustomization]="enableCustomization"
4513
+ (rowClick)="onTableRowClick($event)"
4514
+ (selectionChange)="onTableSelectionChange($event)"
4433
4515
  (rowAction)="onAction($event.action, $event.row, $event)"
4434
4516
  (toolbarAction)="onAction($event.action)"
4435
4517
  (collectionLinksChange)="onCollectionLinksChange($event)"
@@ -4472,6 +4554,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
4472
4554
  type: Output
4473
4555
  }], error: [{
4474
4556
  type: Output
4557
+ }], rowClick: [{
4558
+ type: Output
4559
+ }], selectionChange: [{
4560
+ type: Output
4475
4561
  }], tableRuntimeConfigChange: [{
4476
4562
  type: Output
4477
4563
  }], crudAuthoringDocumentApplied: [{
@@ -4505,6 +4591,7 @@ class DynamicFormDialogHostComponent {
4505
4591
  submitMethod;
4506
4592
  apiEndpointKey;
4507
4593
  apiUrlEntry;
4594
+ formActions;
4508
4595
  mode = 'create';
4509
4596
  backConfig;
4510
4597
  idField = 'id';
@@ -4560,6 +4647,7 @@ class DynamicFormDialogHostComponent {
4560
4647
  this.apiUrlEntry = this.data.inputs?.['apiUrlEntry'] ?? null;
4561
4648
  const act = this.data.action?.action;
4562
4649
  this.mode = act === 'edit' ? 'edit' : act === 'view' ? 'view' : 'create';
4650
+ this.formActions = this.resolveFormActions();
4563
4651
  // Back config: defaults from metadata/action, overridden by saved per-form config
4564
4652
  const defaults = (this.data.action?.back || this.data.metadata?.defaults?.back) || {};
4565
4653
  this.backDefaults = defaults;
@@ -4616,6 +4704,47 @@ class DynamicFormDialogHostComponent {
4616
4704
  }
4617
4705
  return Object.keys(explicit).length ? explicit : null;
4618
4706
  }
4707
+ resolveFormActions() {
4708
+ if (this.mode === 'view') {
4709
+ return undefined;
4710
+ }
4711
+ const submitLabel = this.resolveSubmitLabel();
4712
+ if (!submitLabel) {
4713
+ return undefined;
4714
+ }
4715
+ return {
4716
+ showSaveButton: true,
4717
+ showCancelButton: false,
4718
+ showResetButton: false,
4719
+ submit: {
4720
+ id: 'submit',
4721
+ type: 'submit',
4722
+ color: 'primary',
4723
+ visible: true,
4724
+ label: submitLabel,
4725
+ },
4726
+ };
4727
+ }
4728
+ resolveSubmitLabel() {
4729
+ const action = this.data.action ?? {};
4730
+ const explicit = stringOrUndefined(action.form?.submitLabel ??
4731
+ action.submitLabel ??
4732
+ action.form?.actions?.submit?.label ??
4733
+ action.form?.actions?.submitButtonLabel);
4734
+ if (explicit) {
4735
+ return explicit;
4736
+ }
4737
+ const actionLabel = stringOrUndefined(action.label);
4738
+ if (this.mode === 'create') {
4739
+ return deriveCreateSubmitLabel(actionLabel);
4740
+ }
4741
+ if (this.mode === 'edit') {
4742
+ return actionLabel && !/^editar\b/i.test(actionLabel)
4743
+ ? actionLabel
4744
+ : 'Salvar alterações';
4745
+ }
4746
+ return undefined;
4747
+ }
4619
4748
  ngOnInit() {
4620
4749
  // Carregar estado salvo (se habilitado)
4621
4750
  if (this.rememberState && this.stateKey) {
@@ -4640,8 +4769,11 @@ class DynamicFormDialogHostComponent {
4640
4769
  }
4641
4770
  }
4642
4771
  onSave(result) {
4772
+ const stage = getFormSubmitStage(result);
4773
+ if (stage === 'before' || stage === 'error') {
4774
+ return;
4775
+ }
4643
4776
  if (this.modal.closeOnSave === false) {
4644
- // Não fechar: manter aberto e opcionalmente salvar estado atual
4645
4777
  this.saveState();
4646
4778
  return;
4647
4779
  }
@@ -4734,8 +4866,8 @@ class DynamicFormDialogHostComponent {
4734
4866
  this.dialogRef.updatePosition();
4735
4867
  }
4736
4868
  }
4737
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", 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 });
4738
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", 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: `
4869
+ 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 });
4870
+ 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: `
4739
4871
  <div mat-dialog-title class="dialog-header">
4740
4872
  <h2 id="crudDialogTitle" class="dialog-title">
4741
4873
  {{ data.action?.label || texts.title }}
@@ -4779,21 +4911,21 @@ class DynamicFormDialogHostComponent {
4779
4911
  [apiUrlEntry]="apiUrlEntry"
4780
4912
  [presentationModeGlobal]="mode === 'view' ? true : null"
4781
4913
  [backConfig]="backConfig"
4914
+ [actions]="formActions"
4782
4915
  (formSubmit)="onSave($event)"
4783
4916
  (formCancel)="onCancel()"
4784
4917
  ></praxis-dynamic-form>
4785
4918
  </mat-dialog-content>
4786
- `, 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: CommonModule }, { 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: i5$1.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", "schemaSource", "schemaUrl", "submitUrl", "submitMethod", "responseSchemaUrl", "apiEndpointKey", "apiUrlEntry", "enableCustomization", "formId", "componentInstanceId", "layout", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "visibleGlobal", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "formReady", "valueChange", "syncCompleted", "initializationError", "loadingStateChange", "enableCustomizationChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError"] }] });
4919
+ `, 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: i5$1.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"] }] });
4787
4920
  }
4788
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
4921
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
4789
4922
  type: Component,
4790
4923
  args: [{ selector: 'praxis-dynamic-form-dialog-host', standalone: true, imports: [
4791
- CommonModule,
4792
4924
  MatDialogModule,
4793
4925
  MatButtonModule,
4794
4926
  MatIconModule,
4795
4927
  PraxisIconDirective,
4796
- PraxisDynamicForm,
4928
+ PraxisDynamicForm
4797
4929
  ], providers: [GenericCrudService], host: {
4798
4930
  class: 'praxis-dialog',
4799
4931
  '[attr.data-density]': 'modal.density || "default"',
@@ -4841,6 +4973,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
4841
4973
  [apiUrlEntry]="apiUrlEntry"
4842
4974
  [presentationModeGlobal]="mode === 'view' ? true : null"
4843
4975
  [backConfig]="backConfig"
4976
+ [actions]="formActions"
4844
4977
  (formSubmit)="onSave($event)"
4845
4978
  (formCancel)="onCancel()"
4846
4979
  ></praxis-dynamic-form>
@@ -4859,12 +4992,131 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
4859
4992
  type: ViewChild,
4860
4993
  args: [PraxisDynamicForm]
4861
4994
  }] } });
4995
+ function getFormSubmitStage(result) {
4996
+ if (!result || typeof result !== 'object' || !('stage' in result)) {
4997
+ return null;
4998
+ }
4999
+ const stage = result.stage;
5000
+ return stage === 'before' || stage === 'after' || stage === 'error'
5001
+ ? stage
5002
+ : null;
5003
+ }
5004
+ function stringOrUndefined(value) {
5005
+ const text = String(value ?? '').trim();
5006
+ return text || undefined;
5007
+ }
5008
+ function deriveCreateSubmitLabel(actionLabel) {
5009
+ if (!actionLabel) {
5010
+ return 'Criar';
5011
+ }
5012
+ const match = actionLabel.match(/^(novo|nova|adicionar|incluir|criar)\s+(.+)$/i);
5013
+ if (match?.[2]) {
5014
+ return `Criar ${match[2].trim()}`;
5015
+ }
5016
+ return actionLabel;
5017
+ }
4862
5018
 
4863
5019
  var dynamicFormDialogHost_component = /*#__PURE__*/Object.freeze({
4864
5020
  __proto__: null,
4865
5021
  DynamicFormDialogHostComponent: DynamicFormDialogHostComponent
4866
5022
  });
4867
5023
 
5024
+ class PraxisCrudWidgetConfigEditor {
5025
+ inputs = null;
5026
+ widgetKey;
5027
+ crudEditor;
5028
+ isDirty$ = new BehaviorSubject(false);
5029
+ isValid$ = new BehaviorSubject(true);
5030
+ isBusy$ = new BehaviorSubject(false);
5031
+ subscription = new Subscription();
5032
+ ngAfterViewInit() {
5033
+ if (!this.crudEditor) {
5034
+ return;
5035
+ }
5036
+ this.subscription.add(this.crudEditor.isDirty$.subscribe((value) => this.isDirty$.next(value)));
5037
+ this.subscription.add(this.crudEditor.isValid$.subscribe((value) => this.isValid$.next(value)));
5038
+ this.subscription.add(this.crudEditor.isBusy$.subscribe((value) => this.isBusy$.next(value)));
5039
+ }
5040
+ ngOnDestroy() {
5041
+ this.subscription.unsubscribe();
5042
+ }
5043
+ get metadata() {
5044
+ return this.createDocumentFromInputs().metadata;
5045
+ }
5046
+ get effectiveCrudId() {
5047
+ return this.inputs?.crudId ?? this.widgetKey ?? null;
5048
+ }
5049
+ getSettingsValue() {
5050
+ return this.buildValue(this.crudEditor?.getSettingsValue());
5051
+ }
5052
+ onSave() {
5053
+ return this.buildValue(this.crudEditor?.onSave?.() ?? this.crudEditor?.getSettingsValue());
5054
+ }
5055
+ reset() {
5056
+ this.crudEditor?.reset?.();
5057
+ }
5058
+ buildValue(rawPayload) {
5059
+ const document = this.extractDocument(rawPayload);
5060
+ return {
5061
+ inputs: {
5062
+ ...(this.inputs ?? {}),
5063
+ metadata: document.metadata,
5064
+ crudId: this.inputs?.crudId ?? this.widgetKey,
5065
+ componentInstanceId: this.inputs?.componentInstanceId ?? this.widgetKey,
5066
+ context: this.inputs?.context,
5067
+ enableCustomization: this.inputs?.enableCustomization ?? false,
5068
+ },
5069
+ };
5070
+ }
5071
+ createDocumentFromInputs() {
5072
+ return parseLegacyOrCrudDocument(this.inputs?.metadata ?? {
5073
+ component: 'praxis-crud',
5074
+ table: { columns: [] },
5075
+ });
5076
+ }
5077
+ extractDocument(rawPayload) {
5078
+ const payload = rawPayload;
5079
+ return parseLegacyOrCrudDocument(payload?.document ??
5080
+ payload?.metadata ??
5081
+ serializeCrudAuthoringDocument(this.createDocumentFromInputs()));
5082
+ }
5083
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudWidgetConfigEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
5084
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.14", type: PraxisCrudWidgetConfigEditor, isStandalone: true, selector: "praxis-crud-widget-config-editor", inputs: { inputs: "inputs", widgetKey: "widgetKey" }, viewQueries: [{ propertyName: "crudEditor", first: true, predicate: ["crudEditor"], descendants: true }], ngImport: i0, template: `
5085
+ <section data-testid="crud-widget-config-editor">
5086
+ <praxis-crud-metadata-editor
5087
+ #crudEditor
5088
+ [metadata]="metadata"
5089
+ [crudId]="effectiveCrudId"
5090
+ />
5091
+ </section>
5092
+ `, isInline: true, dependencies: [{ kind: "component", type: CrudMetadataEditorComponent, selector: "praxis-crud-metadata-editor", inputs: ["document", "metadata", "crudId", "readonly"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5093
+ }
5094
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudWidgetConfigEditor, decorators: [{
5095
+ type: Component,
5096
+ args: [{
5097
+ selector: 'praxis-crud-widget-config-editor',
5098
+ standalone: true,
5099
+ imports: [CrudMetadataEditorComponent],
5100
+ template: `
5101
+ <section data-testid="crud-widget-config-editor">
5102
+ <praxis-crud-metadata-editor
5103
+ #crudEditor
5104
+ [metadata]="metadata"
5105
+ [crudId]="effectiveCrudId"
5106
+ />
5107
+ </section>
5108
+ `,
5109
+ changeDetection: ChangeDetectionStrategy.OnPush,
5110
+ }]
5111
+ }], propDecorators: { inputs: [{
5112
+ type: Input
5113
+ }], widgetKey: [{
5114
+ type: Input
5115
+ }], crudEditor: [{
5116
+ type: ViewChild,
5117
+ args: ['crudEditor']
5118
+ }] } });
5119
+
4868
5120
  /** Metadata for PraxisCrudComponent */
4869
5121
  const PRAXIS_CRUD_COMPONENT_METADATA = {
4870
5122
  id: 'praxis-crud',
@@ -4873,6 +5125,14 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
4873
5125
  friendlyName: 'Praxis CRUD',
4874
5126
  description: 'Tabela com operações de CRUD via metadados.',
4875
5127
  icon: 'table_chart',
5128
+ authoringManifestRef: {
5129
+ componentId: 'praxis-crud',
5130
+ source: 'PRAXIS_CRUD_AUTHORING_MANIFEST',
5131
+ },
5132
+ configEditor: {
5133
+ component: PraxisCrudWidgetConfigEditor,
5134
+ title: 'Configurar CRUD',
5135
+ },
4876
5136
  inputs: [
4877
5137
  {
4878
5138
  name: 'metadata',
@@ -4937,6 +5197,16 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
4937
5197
  type: '{ id: string | number }',
4938
5198
  description: 'Emitido ao deletar.',
4939
5199
  },
5200
+ {
5201
+ name: 'rowClick',
5202
+ type: '{ row: unknown; index: number }',
5203
+ description: 'Encaminha o clique de linha da tabela interna para composição master-detail e seleção local.',
5204
+ },
5205
+ {
5206
+ name: 'selectionChange',
5207
+ type: 'unknown',
5208
+ description: 'Encaminha a mudança de seleção da tabela interna quando o CRUD é usado como widget composto.',
5209
+ },
4940
5210
  {
4941
5211
  name: 'error',
4942
5212
  type: 'unknown',
@@ -5043,6 +5313,23 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
5043
5313
  },
5044
5314
  scope: 'shell',
5045
5315
  },
5316
+ {
5317
+ id: 'row-click',
5318
+ label: 'Clique em linha',
5319
+ icon: 'ads_click',
5320
+ description: 'Emite evento ao clicar em uma linha da tabela interna do CRUD.',
5321
+ emit: 'rowClick',
5322
+ payloadSchema: {
5323
+ type: 'object',
5324
+ properties: {
5325
+ row: { type: 'object', description: 'Registro clicado.' },
5326
+ index: { type: 'number', description: 'Índice da linha.' },
5327
+ },
5328
+ required: ['row'],
5329
+ example: { row: {}, index: 0 },
5330
+ },
5331
+ scope: 'context',
5332
+ },
5046
5333
  {
5047
5334
  id: 'close',
5048
5335
  label: 'Fechar',
@@ -5083,64 +5370,66 @@ class CrudPageHeaderComponent {
5083
5370
  sticky = true;
5084
5371
  divider = true;
5085
5372
  returnTo;
5086
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudPageHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5087
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: CrudPageHeaderComponent, isStandalone: true, selector: "praxis-crud-page-header", inputs: { title: "title", backLabel: "backLabel", showBack: "showBack", variant: "variant", sticky: "sticky", divider: "divider", returnTo: "returnTo" }, ngImport: i0, template: `
5373
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudPageHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5374
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: CrudPageHeaderComponent, isStandalone: true, selector: "praxis-crud-page-header", inputs: { title: "title", backLabel: "backLabel", showBack: "showBack", variant: "variant", sticky: "sticky", divider: "divider", returnTo: "returnTo" }, ngImport: i0, template: `
5088
5375
  <header
5089
5376
  class="crud-header"
5090
5377
  [class.sticky]="sticky"
5091
5378
  [class.with-divider]="divider"
5092
- >
5379
+ >
5093
5380
  <div class="left">
5094
- <a
5095
- *ngIf="showBack && returnTo"
5096
- class="back-btn"
5097
- [class.ghost]="variant === 'ghost'"
5098
- [class.tonal]="variant === 'tonal'"
5099
- [class.outlined]="variant === 'outlined'"
5100
- [routerLink]="returnTo"
5101
- [attr.aria-label]="backLabel || 'Voltar'"
5102
- mat-button
5103
- >
5104
- <mat-icon [praxisIcon]="'arrow_back'"></mat-icon>
5105
- <span class="label" [class.hide-on-narrow]="true">{{ backLabel || 'Voltar' }}</span>
5106
- </a>
5381
+ @if (showBack && returnTo) {
5382
+ <a
5383
+ class="back-btn"
5384
+ [class.ghost]="variant === 'ghost'"
5385
+ [class.tonal]="variant === 'tonal'"
5386
+ [class.outlined]="variant === 'outlined'"
5387
+ [routerLink]="returnTo"
5388
+ [attr.aria-label]="backLabel || 'Voltar'"
5389
+ mat-button
5390
+ >
5391
+ <mat-icon [praxisIcon]="'arrow_back'"></mat-icon>
5392
+ <span class="label" [class.hide-on-narrow]="true">{{ backLabel || 'Voltar' }}</span>
5393
+ </a>
5394
+ }
5107
5395
  <h2 class="title" [attr.title]="title">{{ title }}</h2>
5108
5396
  </div>
5109
5397
  <div class="right">
5110
5398
  <ng-content></ng-content>
5111
5399
  </div>
5112
5400
  </header>
5113
- `, isInline: true, styles: [".crud-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 0;background:var(--md-sys-color-surface)}.crud-header.sticky{position:sticky;top:0;z-index:10;-webkit-backdrop-filter:saturate(110%);backdrop-filter:saturate(110%)}.crud-header.with-divider{border-bottom:1px solid var(--md-sys-color-outline-variant)}.left{display:flex;align-items:center;gap:8px;min-width:0}.right{display:flex;align-items:center;gap:8px}.title{margin:0;font-weight:600;color:var(--md-sys-color-on-surface, currentColor);font:var(--mdc-typography-title-large, 600 20px/28px system-ui);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.back-btn{display:inline-flex;align-items:center;gap:6px;text-decoration:none}.back-btn .mat-mdc-button{padding-left:0}.back-btn mat-icon{color:var(--md-sys-color-on-surface-variant, currentColor)}.back-btn .label{color:var(--md-sys-color-on-surface-variant, currentColor)}.back-btn.ghost{border-radius:20px;padding:4px 8px}.back-btn.ghost:hover{background:var(--md-sys-color-primary-container)}.back-btn.tonal{border-radius:20px;padding:4px 10px;background:var(--md-sys-color-surface-container);box-shadow:inset 0 0 0 1px var(--md-sys-color-outline-variant)}.back-btn.tonal:hover{background:var(--md-sys-color-primary-container)}.back-btn.outlined{border-radius:20px;padding:4px 10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface)}.back-btn.outlined:hover{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}@media(max-width:599px){.label.hide-on-narrow{display:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }] });
5401
+ `, isInline: true, styles: [".crud-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 0;background:var(--md-sys-color-surface)}.crud-header.sticky{position:sticky;top:0;z-index:10;-webkit-backdrop-filter:saturate(110%);backdrop-filter:saturate(110%)}.crud-header.with-divider{border-bottom:1px solid var(--md-sys-color-outline-variant)}.left{display:flex;align-items:center;gap:8px;min-width:0}.right{display:flex;align-items:center;gap:8px}.title{margin:0;font-weight:600;color:var(--md-sys-color-on-surface, currentColor);font:var(--mdc-typography-title-large, 600 20px/28px system-ui);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.back-btn{display:inline-flex;align-items:center;gap:6px;text-decoration:none}.back-btn .mat-mdc-button{padding-left:0}.back-btn mat-icon{color:var(--md-sys-color-on-surface-variant, currentColor)}.back-btn .label{color:var(--md-sys-color-on-surface-variant, currentColor)}.back-btn.ghost{border-radius:20px;padding:4px 8px}.back-btn.ghost:hover{background:var(--md-sys-color-primary-container)}.back-btn.tonal{border-radius:20px;padding:4px 10px;background:var(--md-sys-color-surface-container);box-shadow:inset 0 0 0 1px var(--md-sys-color-outline-variant)}.back-btn.tonal:hover{background:var(--md-sys-color-primary-container)}.back-btn.outlined{border-radius:20px;padding:4px 10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface)}.back-btn.outlined:hover{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}@media(max-width:599px){.label.hide-on-narrow{display:none}}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }] });
5114
5402
  }
5115
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudPageHeaderComponent, decorators: [{
5403
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudPageHeaderComponent, decorators: [{
5116
5404
  type: Component,
5117
- args: [{ selector: 'praxis-crud-page-header', standalone: true, imports: [CommonModule, RouterLink, MatButtonModule, MatIconModule, PraxisIconDirective], template: `
5405
+ args: [{ selector: 'praxis-crud-page-header', standalone: true, imports: [RouterLink, MatButtonModule, MatIconModule, PraxisIconDirective], template: `
5118
5406
  <header
5119
5407
  class="crud-header"
5120
5408
  [class.sticky]="sticky"
5121
5409
  [class.with-divider]="divider"
5122
- >
5410
+ >
5123
5411
  <div class="left">
5124
- <a
5125
- *ngIf="showBack && returnTo"
5126
- class="back-btn"
5127
- [class.ghost]="variant === 'ghost'"
5128
- [class.tonal]="variant === 'tonal'"
5129
- [class.outlined]="variant === 'outlined'"
5130
- [routerLink]="returnTo"
5131
- [attr.aria-label]="backLabel || 'Voltar'"
5132
- mat-button
5133
- >
5134
- <mat-icon [praxisIcon]="'arrow_back'"></mat-icon>
5135
- <span class="label" [class.hide-on-narrow]="true">{{ backLabel || 'Voltar' }}</span>
5136
- </a>
5412
+ @if (showBack && returnTo) {
5413
+ <a
5414
+ class="back-btn"
5415
+ [class.ghost]="variant === 'ghost'"
5416
+ [class.tonal]="variant === 'tonal'"
5417
+ [class.outlined]="variant === 'outlined'"
5418
+ [routerLink]="returnTo"
5419
+ [attr.aria-label]="backLabel || 'Voltar'"
5420
+ mat-button
5421
+ >
5422
+ <mat-icon [praxisIcon]="'arrow_back'"></mat-icon>
5423
+ <span class="label" [class.hide-on-narrow]="true">{{ backLabel || 'Voltar' }}</span>
5424
+ </a>
5425
+ }
5137
5426
  <h2 class="title" [attr.title]="title">{{ title }}</h2>
5138
5427
  </div>
5139
5428
  <div class="right">
5140
5429
  <ng-content></ng-content>
5141
5430
  </div>
5142
5431
  </header>
5143
- `, styles: [".crud-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 0;background:var(--md-sys-color-surface)}.crud-header.sticky{position:sticky;top:0;z-index:10;-webkit-backdrop-filter:saturate(110%);backdrop-filter:saturate(110%)}.crud-header.with-divider{border-bottom:1px solid var(--md-sys-color-outline-variant)}.left{display:flex;align-items:center;gap:8px;min-width:0}.right{display:flex;align-items:center;gap:8px}.title{margin:0;font-weight:600;color:var(--md-sys-color-on-surface, currentColor);font:var(--mdc-typography-title-large, 600 20px/28px system-ui);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.back-btn{display:inline-flex;align-items:center;gap:6px;text-decoration:none}.back-btn .mat-mdc-button{padding-left:0}.back-btn mat-icon{color:var(--md-sys-color-on-surface-variant, currentColor)}.back-btn .label{color:var(--md-sys-color-on-surface-variant, currentColor)}.back-btn.ghost{border-radius:20px;padding:4px 8px}.back-btn.ghost:hover{background:var(--md-sys-color-primary-container)}.back-btn.tonal{border-radius:20px;padding:4px 10px;background:var(--md-sys-color-surface-container);box-shadow:inset 0 0 0 1px var(--md-sys-color-outline-variant)}.back-btn.tonal:hover{background:var(--md-sys-color-primary-container)}.back-btn.outlined{border-radius:20px;padding:4px 10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface)}.back-btn.outlined:hover{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}@media(max-width:599px){.label.hide-on-narrow{display:none}}\n"] }]
5432
+ `, styles: [".crud-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 0;background:var(--md-sys-color-surface)}.crud-header.sticky{position:sticky;top:0;z-index:10;-webkit-backdrop-filter:saturate(110%);backdrop-filter:saturate(110%)}.crud-header.with-divider{border-bottom:1px solid var(--md-sys-color-outline-variant)}.left{display:flex;align-items:center;gap:8px;min-width:0}.right{display:flex;align-items:center;gap:8px}.title{margin:0;font-weight:600;color:var(--md-sys-color-on-surface, currentColor);font:var(--mdc-typography-title-large, 600 20px/28px system-ui);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.back-btn{display:inline-flex;align-items:center;gap:6px;text-decoration:none}.back-btn .mat-mdc-button{padding-left:0}.back-btn mat-icon{color:var(--md-sys-color-on-surface-variant, currentColor)}.back-btn .label{color:var(--md-sys-color-on-surface-variant, currentColor)}.back-btn.ghost{border-radius:20px;padding:4px 8px}.back-btn.ghost:hover{background:var(--md-sys-color-primary-container)}.back-btn.tonal{border-radius:20px;padding:4px 10px;background:var(--md-sys-color-surface-container);box-shadow:inset 0 0 0 1px var(--md-sys-color-outline-variant)}.back-btn.tonal:hover{background:var(--md-sys-color-primary-container)}.back-btn.outlined{border-radius:20px;padding:4px 10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface)}.back-btn.outlined:hover{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}@media(max-width:599px){.label.hide-on-narrow{display:none}}\n"] }]
5144
5433
  }], propDecorators: { title: [{
5145
5434
  type: Input
5146
5435
  }], backLabel: [{
@@ -5192,6 +5481,12 @@ const CRUD_AI_CAPABILITIES = {
5192
5481
  { path: 'resource.path', category: 'resource', valueKind: 'string', description: 'Endpoint base do recurso.' },
5193
5482
  { path: 'resource.idField', category: 'resource', valueKind: 'string', description: 'Campo identificador do recurso.' },
5194
5483
  { path: 'resource.endpointKey', category: 'resource', valueKind: 'string', description: 'Chave de endpoint (ApiEndpoint).' },
5484
+ { path: 'queryContext.meta.domainCatalog', category: 'resource', valueKind: 'object', description: 'Referencia leve para contexto semantico/governanca resolvido via Domain Catalog; nao materializa regras em FormConfig.' },
5485
+ { path: 'queryContext.meta.domainCatalog.schemaVersion', category: 'resource', valueKind: 'string', description: 'Versao do contrato DomainCatalogContextHint usado em runtime e prompts.' },
5486
+ { path: 'queryContext.meta.domainCatalog.resourceKey', category: 'resource', valueKind: 'string', description: 'ResourceKey semantico estavel usado para resolver vocabulario/governanca de dominio.' },
5487
+ { path: 'queryContext.meta.domainCatalog.releaseId', category: 'resource', valueKind: 'string', description: 'Release opcional do Domain Catalog usada como fonte do contexto.' },
5488
+ { path: 'queryContext.meta.domainCatalog.query', category: 'resource', valueKind: 'string', description: 'Probe de campo/conceito usado para buscar itens relevantes, como cpf, salario ou status.' },
5489
+ { path: 'queryContext.meta.domainCatalog.intent', category: 'resource', valueKind: 'enum', allowedValues: ['authoring', 'explain', 'validate', 'ai-access-control'], description: 'Intencao de uso do contexto para orientar LLMs e validadores sem expor dados alem do necessario.' },
5195
5490
  // --- Table / Form ---
5196
5491
  { path: 'table', category: 'table', valueKind: 'object', description: 'TableConfig completo (usar catalogo de tabela).' },
5197
5492
  { path: 'form', category: 'form', valueKind: 'object', description: 'FormConfig completo (usar catalogo de formulario).' },
@@ -5248,6 +5543,438 @@ const CRUD_AI_CAPABILITIES = {
5248
5543
  ],
5249
5544
  };
5250
5545
 
5546
+ const resourceBindSchema = {
5547
+ type: 'object',
5548
+ required: ['resourcePath'],
5549
+ properties: {
5550
+ resourcePath: { type: 'string' },
5551
+ resourceKey: { type: 'string' },
5552
+ idField: { type: ['string', 'number'] },
5553
+ endpointKey: { type: 'string' },
5554
+ queryContext: { type: 'object' },
5555
+ },
5556
+ };
5557
+ const surfaceConfigureSchema = {
5558
+ type: 'object',
5559
+ required: ['actionId', 'openMode'],
5560
+ properties: {
5561
+ actionId: { enum: ['create', 'edit', 'view'] },
5562
+ openMode: { enum: ['route', 'modal', 'drawer'] },
5563
+ route: { type: 'string' },
5564
+ formId: { type: 'string' },
5565
+ form: {
5566
+ type: 'object',
5567
+ properties: {
5568
+ schemaUrl: { type: 'string' },
5569
+ submitUrl: { type: 'string' },
5570
+ submitMethod: { enum: ['post', 'put', 'patch', 'delete'] },
5571
+ apiEndpointKey: { type: 'string' },
5572
+ initialValue: { type: 'object' },
5573
+ },
5574
+ },
5575
+ params: {
5576
+ type: 'array',
5577
+ items: {
5578
+ type: 'object',
5579
+ required: ['from', 'to', 'name'],
5580
+ properties: {
5581
+ from: { type: 'string' },
5582
+ to: { enum: ['routeParam', 'query', 'input'] },
5583
+ name: { type: 'string' },
5584
+ },
5585
+ },
5586
+ },
5587
+ back: { type: 'object' },
5588
+ },
5589
+ };
5590
+ const listSurfaceSchema = {
5591
+ type: 'object',
5592
+ properties: {
5593
+ tablePatch: { type: 'object' },
5594
+ queryContext: { type: 'object' },
5595
+ filterCriteria: { type: 'object' },
5596
+ },
5597
+ };
5598
+ const deleteBehaviorSchema = {
5599
+ type: 'object',
5600
+ required: ['enabled'],
5601
+ properties: {
5602
+ enabled: { type: 'boolean' },
5603
+ actionId: { type: 'string' },
5604
+ requiresConfirmation: { type: 'boolean' },
5605
+ autoDelete: { type: 'boolean' },
5606
+ form: {
5607
+ type: 'object',
5608
+ properties: {
5609
+ submitUrl: { type: 'string' },
5610
+ submitMethod: { enum: ['delete'] },
5611
+ apiEndpointKey: { type: 'string' },
5612
+ },
5613
+ },
5614
+ },
5615
+ };
5616
+ const dialogHostSchema = {
5617
+ type: 'object',
5618
+ properties: {
5619
+ defaultOpenMode: { enum: ['route', 'modal', 'drawer'] },
5620
+ modal: {
5621
+ type: 'object',
5622
+ properties: {
5623
+ width: { type: 'string' },
5624
+ height: { type: 'string' },
5625
+ minWidth: { type: 'string' },
5626
+ maxWidth: { type: 'string' },
5627
+ density: { type: 'string' },
5628
+ canMaximize: { type: 'boolean' },
5629
+ rememberLastState: { type: 'boolean' },
5630
+ startMaximized: { type: 'boolean' },
5631
+ disableCloseOnEsc: { type: 'boolean' },
5632
+ disableCloseOnBackdrop: { type: 'boolean' },
5633
+ fullscreenBreakpoint: { type: 'string' },
5634
+ },
5635
+ },
5636
+ back: { type: 'object' },
5637
+ },
5638
+ };
5639
+ const permissionsSchema = {
5640
+ type: 'object',
5641
+ properties: {
5642
+ requiredCapabilities: {
5643
+ type: 'array',
5644
+ items: { enum: ['create', 'view', 'edit', 'delete'] },
5645
+ },
5646
+ actionPermissions: { type: 'object' },
5647
+ denyWhenMissingCapability: { type: 'boolean' },
5648
+ },
5649
+ };
5650
+ const domainGovernanceContextSchema = {
5651
+ type: 'object',
5652
+ required: ['resourceKey', 'query'],
5653
+ properties: {
5654
+ resourceKey: { type: 'string' },
5655
+ releaseId: { type: 'string' },
5656
+ query: { type: 'string' },
5657
+ itemTypes: {
5658
+ type: 'array',
5659
+ items: { enum: ['governance', 'vocabulary', 'relationship'] },
5660
+ },
5661
+ intent: { enum: ['authoring', 'explain', 'validate', 'ai-access-control'] },
5662
+ },
5663
+ };
5664
+ const childDelegateSchema = {
5665
+ type: 'object',
5666
+ required: ['childComponentId', 'childOperationId', 'reason'],
5667
+ properties: {
5668
+ childComponentId: { enum: ['praxis-dynamic-form', 'praxis-table', 'praxis-dialog', 'praxis-settings-panel'] },
5669
+ childOperationId: { type: 'string' },
5670
+ reason: { type: 'string' },
5671
+ childTarget: { type: 'object' },
5672
+ childParams: { type: 'object' },
5673
+ },
5674
+ };
5675
+ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
5676
+ schemaVersion: '1.0.0',
5677
+ componentId: 'praxis-crud',
5678
+ ownerPackage: '@praxisui/crud',
5679
+ configSchemaId: 'CrudMetadata',
5680
+ manifestVersion: '1.0.0',
5681
+ runtimeInputs: [
5682
+ { name: 'metadata', type: 'CrudMetadata | string', description: 'Canonical CRUD metadata or serialized metadata document.' },
5683
+ { name: 'crudId', type: 'string', description: 'Stable CRUD instance id used for table/form identity and persistence.' },
5684
+ { name: 'componentInstanceId', type: 'string', description: 'Optional stable host instance id for multiple CRUD widgets on the same route.' },
5685
+ { name: 'context', type: 'Record<string, unknown>', description: 'Opaque host context used for authoring seeds and launcher inputs.' },
5686
+ { name: 'afterOpen', type: '{ mode: FormOpenMode; action: string }', description: 'Emitted after a CRUD action opens.' },
5687
+ { name: 'afterSave', type: '{ id: string | number; data: unknown }', description: 'Emitted after save; CRUD refetches the list.' },
5688
+ { name: 'afterDelete', type: '{ id: string | number }', description: 'Emitted after delete; CRUD refetches the list.' },
5689
+ ],
5690
+ editableTargets: [
5691
+ { kind: 'resourceBinding', resolver: 'crud-resource-by-path-or-key', description: 'Resource path/key, id field, endpoint key and query context owned by CRUD orchestration.' },
5692
+ { kind: 'listSurface', resolver: 'crud-list-surface', description: 'CRUD-hosted list surface and table delegation boundary.' },
5693
+ { kind: 'createSurface', resolver: 'crud-action-by-id:create', description: 'Create action open mode, route/form binding and launcher inputs.' },
5694
+ { kind: 'editSurface', resolver: 'crud-action-by-id:edit', description: 'Edit action open mode, route/form binding and launcher inputs.' },
5695
+ { kind: 'viewSurface', resolver: 'crud-action-by-id:view', description: 'View action open mode, route/form binding and launcher inputs.' },
5696
+ { kind: 'deleteBehavior', resolver: 'crud-action-by-id:delete', description: 'Delete action enablement, confirmation, endpoint and capability policy.' },
5697
+ { kind: 'dialogHost', resolver: 'crud-dialog-host-defaults', description: 'Route/modal/drawer defaults consumed by CrudLauncherService and DynamicFormDialogHostComponent.' },
5698
+ { kind: 'formBinding', resolver: 'crud-action-form-contract-by-action-id', description: 'CRUD-owned form binding fields: formId, schemaUrl, submitUrl, submitMethod, params and initialValue.' },
5699
+ { kind: 'permissions', resolver: 'crud-resource-capabilities', description: 'CRUD action availability derived from resource capabilities and action permissions.' },
5700
+ { kind: 'domainGovernanceContext', resolver: 'domain-catalog-context-by-resource-key', description: 'Read-only semantic/governance context resolved from Domain Catalog and referenced from CRUD queryContext.meta.' },
5701
+ { kind: 'childOperation', resolver: 'child-authoring-manifest-operation', description: 'Delegated form/table/dialog/settings-panel operation owned by the child component manifest.' },
5702
+ ],
5703
+ operations: [
5704
+ {
5705
+ operationId: 'resource.bind',
5706
+ title: 'Bind CRUD resource',
5707
+ scope: 'dataBinding',
5708
+ targetKind: 'resourceBinding',
5709
+ target: { kind: 'resourceBinding', resolver: 'crud-resource-by-path-or-key', ambiguityPolicy: 'fail', required: true },
5710
+ inputSchema: resourceBindSchema,
5711
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-resource-bind', handlerContract: {
5712
+ reads: ['CrudMetadata.resource', 'api_metadata', 'ResourceDiscoveryService', 'GET /{resource}/capabilities'],
5713
+ writes: ['CrudMetadata.resource', 'CrudMetadata.queryContext', 'PraxisCrudComponent.tableCrudContext'],
5714
+ identityKeys: ['resourcePath', 'resourceKey'],
5715
+ inputSchema: resourceBindSchema,
5716
+ failureModes: ['resource-not-found', 'schema-url-not-canonical', 'capabilities-unavailable', 'id-field-missing'],
5717
+ description: 'Binds CRUD to a canonical resource and validates it against api_metadata/resource capabilities before runtime use.',
5718
+ } }],
5719
+ validators: ['resource-exists-in-api-metadata', 'resource-path-canonical', 'resource-key-stable', 'id-field-known', 'resource-capabilities-resolvable'],
5720
+ affectedPaths: ['resource.path', 'resource.idField', 'resource.endpointKey', 'queryContext', 'filterCriteria'],
5721
+ submissionImpact: 'affects-remote-binding',
5722
+ preconditions: ['crud-metadata-loaded', 'api-metadata-available'],
5723
+ },
5724
+ {
5725
+ operationId: 'domain.governanceContext.attach',
5726
+ title: 'Attach Domain Catalog governance context',
5727
+ scope: 'dataBinding',
5728
+ targetKind: 'domainGovernanceContext',
5729
+ target: { kind: 'domainGovernanceContext', resolver: 'domain-catalog-context-by-resource-key', ambiguityPolicy: 'fail', required: true },
5730
+ inputSchema: domainGovernanceContextSchema,
5731
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-domain-governance-context-attach', handlerContract: {
5732
+ reads: ['CrudMetadata.resource', 'CrudMetadata.queryContext', 'DomainCatalogService.getGovernanceContext', 'DOMAIN_CATALOG_COMPONENT_CONTEXT_PACK', 'api_metadata'],
5733
+ writes: ['CrudMetadata.queryContext.meta.domainCatalog'],
5734
+ identityKeys: ['resourceKey', 'releaseId', 'query'],
5735
+ inputSchema: domainGovernanceContextSchema,
5736
+ failureModes: ['resource-key-missing', 'domain-catalog-release-not-found', 'governance-context-empty', 'ai-usage-forbidden'],
5737
+ description: 'Resolves read-only domain vocabulary/governance context and stores only a lightweight reference/probe under queryContext.meta.domainCatalog.',
5738
+ } }],
5739
+ validators: ['resource-key-stable', 'domain-catalog-context-resolvable', 'domain-catalog-read-only', 'ai-usage-visibility-respected', 'no-form-config-rule-materialization'],
5740
+ affectedPaths: ['queryContext.meta.domainCatalog'],
5741
+ submissionImpact: 'config-only',
5742
+ preconditions: ['crud-metadata-loaded', 'api-metadata-available', 'domain-catalog-service-available'],
5743
+ },
5744
+ {
5745
+ operationId: 'list.surface.configure',
5746
+ title: 'Configure CRUD list surface',
5747
+ scope: 'interaction',
5748
+ targetKind: 'listSurface',
5749
+ target: { kind: 'listSurface', resolver: 'crud-list-surface', ambiguityPolicy: 'fail', required: true },
5750
+ inputSchema: listSurfaceSchema,
5751
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-list-surface-configure', handlerContract: {
5752
+ reads: ['CrudMetadata.table', 'CrudMetadata.queryContext', 'PRAXIS_TABLE_AUTHORING_MANIFEST', 'CRUD_AI_CAPABILITIES'],
5753
+ writes: ['CrudMetadata.table', 'CrudMetadata.queryContext', 'CrudMetadata.filterCriteria'],
5754
+ identityKeys: ['crudId'],
5755
+ inputSchema: listSurfaceSchema,
5756
+ failureModes: ['table-operation-not-delegated', 'table-patch-not-supported', 'query-context-invalid'],
5757
+ description: 'Configures CRUD-owned list orchestration while routing table semantics to the praxis-table manifest.',
5758
+ } }],
5759
+ validators: ['table-child-operation-delegated', 'query-context-valid', 'filter-criteria-bridge-valid', 'crud-context-stable'],
5760
+ affectedPaths: ['table', 'queryContext', 'filterCriteria'],
5761
+ submissionImpact: 'config-only',
5762
+ preconditions: ['crud-metadata-loaded', 'praxis-table-manifest-available'],
5763
+ },
5764
+ {
5765
+ operationId: 'surface.create.configure',
5766
+ title: 'Configure create surface',
5767
+ scope: 'interaction',
5768
+ targetKind: 'createSurface',
5769
+ target: { kind: 'createSurface', resolver: 'crud-action-by-id:create', ambiguityPolicy: 'fail', required: true },
5770
+ inputSchema: surfaceConfigureSchema,
5771
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-create-surface-configure', handlerContract: {
5772
+ reads: ['CrudMetadata.actions', 'CrudLauncherService.resolveOpenMode', 'DynamicFormDialogHostComponent', 'api_metadata'],
5773
+ writes: ['CrudMetadata.actions[]', 'CrudMetadata.actions[].form', 'CrudMetadata.actions[].params', 'CrudMetadata.actions[].back'],
5774
+ identityKeys: ['actionId'],
5775
+ inputSchema: surfaceConfigureSchema,
5776
+ failureModes: ['action-not-found', 'open-mode-binding-incomplete', 'schema-url-not-canonical', 'submit-url-not-canonical', 'resource-create-not-supported'],
5777
+ description: 'Configures create action binding and launcher inputs without editing the child FormConfig.',
5778
+ } }],
5779
+ validators: ['action-exists', 'open-mode-binding-complete', 'schema-url-canonical', 'submit-url-canonical', 'resource-create-supported', 'form-child-operation-delegated'],
5780
+ affectedPaths: ['actions[].openMode', 'actions[].route', 'actions[].formId', 'actions[].form', 'actions[].params', 'actions[].back'],
5781
+ submissionImpact: 'affects-schema-backed-data',
5782
+ preconditions: ['crud-metadata-loaded', 'resource-capabilities-resolved'],
5783
+ },
5784
+ {
5785
+ operationId: 'surface.edit.configure',
5786
+ title: 'Configure edit surface',
5787
+ scope: 'interaction',
5788
+ targetKind: 'editSurface',
5789
+ target: { kind: 'editSurface', resolver: 'crud-action-by-id:edit', ambiguityPolicy: 'fail', required: true },
5790
+ inputSchema: surfaceConfigureSchema,
5791
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-edit-surface-configure', handlerContract: {
5792
+ reads: ['CrudMetadata.actions', 'CrudMetadata.resource.idField', 'CrudLauncherService.resolveOpenMode', 'api_metadata'],
5793
+ writes: ['CrudMetadata.actions[]', 'CrudMetadata.actions[].form', 'CrudMetadata.actions[].params', 'CrudMetadata.actions[].back'],
5794
+ identityKeys: ['actionId'],
5795
+ inputSchema: surfaceConfigureSchema,
5796
+ failureModes: ['action-not-found', 'id-param-missing', 'open-mode-binding-incomplete', 'resource-edit-not-supported', 'submit-url-not-canonical'],
5797
+ description: 'Configures edit action routing/form binding and ensures id parameter mapping is available to the launcher.',
5798
+ } }],
5799
+ validators: ['action-exists', 'id-field-known', 'id-param-mapping-valid', 'open-mode-binding-complete', 'schema-url-canonical', 'submit-url-canonical', 'resource-edit-supported', 'form-child-operation-delegated'],
5800
+ affectedPaths: ['actions[].openMode', 'actions[].route', 'actions[].formId', 'actions[].form', 'actions[].params', 'actions[].back'],
5801
+ submissionImpact: 'affects-schema-backed-data',
5802
+ preconditions: ['crud-metadata-loaded', 'resource-capabilities-resolved'],
5803
+ },
5804
+ {
5805
+ operationId: 'surface.view.configure',
5806
+ title: 'Configure view surface',
5807
+ scope: 'interaction',
5808
+ targetKind: 'viewSurface',
5809
+ target: { kind: 'viewSurface', resolver: 'crud-action-by-id:view', ambiguityPolicy: 'fail', required: true },
5810
+ inputSchema: surfaceConfigureSchema,
5811
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-view-surface-configure', handlerContract: {
5812
+ reads: ['CrudMetadata.actions', 'CrudLauncherService.resolveOpenMode', 'DynamicFormDialogHostComponent', 'api_metadata'],
5813
+ writes: ['CrudMetadata.actions[]', 'CrudMetadata.actions[].form', 'CrudMetadata.actions[].params', 'CrudMetadata.actions[].back'],
5814
+ identityKeys: ['actionId'],
5815
+ inputSchema: surfaceConfigureSchema,
5816
+ failureModes: ['action-not-found', 'open-mode-binding-incomplete', 'resource-view-not-supported', 'readonly-form-delegation-missing'],
5817
+ description: 'Configures view action binding and delegates readonly form behavior to the dynamic-form manifest.',
5818
+ } }],
5819
+ validators: ['action-exists', 'open-mode-binding-complete', 'schema-url-canonical', 'resource-view-supported', 'form-child-operation-delegated', 'readonly-form-delegation-valid'],
5820
+ affectedPaths: ['actions[].openMode', 'actions[].route', 'actions[].formId', 'actions[].form', 'actions[].params', 'actions[].back'],
5821
+ submissionImpact: 'config-only',
5822
+ preconditions: ['crud-metadata-loaded', 'resource-capabilities-resolved'],
5823
+ },
5824
+ {
5825
+ operationId: 'delete.enabled.set',
5826
+ title: 'Configure delete behavior',
5827
+ scope: 'interaction',
5828
+ targetKind: 'deleteBehavior',
5829
+ target: { kind: 'deleteBehavior', resolver: 'crud-action-by-id:delete', ambiguityPolicy: 'fail', required: true },
5830
+ inputSchema: deleteBehaviorSchema,
5831
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-delete-behavior-set', handlerContract: {
5832
+ reads: ['CrudMetadata.actions', 'CrudMetadata.resource', 'GET /{resource}/{id}/capabilities'],
5833
+ writes: ['CrudMetadata.actions[]', 'CrudMetadata.actions[].requiresConfirmation', 'CrudMetadata.actions[].autoDelete', 'CrudMetadata.actions[].form'],
5834
+ identityKeys: ['actionId'],
5835
+ inputSchema: deleteBehaviorSchema,
5836
+ failureModes: ['delete-action-not-found', 'resource-delete-not-supported', 'destructive-delete-not-confirmed', 'delete-submit-url-not-canonical'],
5837
+ description: 'Enables or disables delete behavior with capability checks and explicit confirmation for destructive changes.',
5838
+ } }],
5839
+ destructive: true,
5840
+ requiresConfirmation: true,
5841
+ validators: ['delete-action-exists', 'resource-delete-supported', 'destructive-delete-confirmed', 'submit-url-canonical', 'permissions-delete-valid'],
5842
+ affectedPaths: ['actions[].disabled', 'actions[].requiresConfirmation', 'actions[].autoDelete', 'actions[].form', 'actions[].form.submitUrl', 'actions[].form.submitMethod', 'actions[].form.apiEndpointKey'],
5843
+ submissionImpact: 'affects-schema-backed-data',
5844
+ preconditions: ['crud-metadata-loaded', 'resource-capabilities-resolved', 'explicit-confirmation-provided'],
5845
+ },
5846
+ {
5847
+ operationId: 'dialog.size.set',
5848
+ title: 'Configure CRUD dialog host defaults',
5849
+ scope: 'interaction',
5850
+ targetKind: 'dialogHost',
5851
+ target: { kind: 'dialogHost', resolver: 'crud-dialog-host-defaults', ambiguityPolicy: 'fail', required: true },
5852
+ inputSchema: dialogHostSchema,
5853
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-dialog-host-set', handlerContract: {
5854
+ reads: ['CrudMetadata.defaults', 'CrudLauncherService.resolveOpenMode', 'DialogService', 'CRUD_DRAWER_ADAPTER'],
5855
+ writes: ['CrudMetadata.defaults.openMode', 'CrudMetadata.defaults.modal', 'CrudMetadata.defaults.back'],
5856
+ identityKeys: ['crudId'],
5857
+ inputSchema: dialogHostSchema,
5858
+ failureModes: ['open-mode-unsupported', 'drawer-adapter-missing', 'modal-size-invalid', 'back-policy-invalid'],
5859
+ description: 'Configures CRUD-owned route/modal/drawer defaults consumed by the launcher and dialog host.',
5860
+ } }],
5861
+ validators: ['open-mode-supported', 'modal-size-valid', 'drawer-adapter-available-when-needed', 'back-policy-valid', 'settings-panel-shell-compatible'],
5862
+ affectedPaths: ['defaults.openMode', 'defaults.modal', 'defaults.back'],
5863
+ submissionImpact: 'config-only',
5864
+ preconditions: ['crud-metadata-loaded'],
5865
+ },
5866
+ {
5867
+ operationId: 'permissions.set',
5868
+ title: 'Configure CRUD permissions',
5869
+ scope: 'interaction',
5870
+ targetKind: 'permissions',
5871
+ target: { kind: 'permissions', resolver: 'crud-resource-capabilities', ambiguityPolicy: 'fail', required: true },
5872
+ inputSchema: permissionsSchema,
5873
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-permissions-set', handlerContract: {
5874
+ reads: ['GET /{resource}/capabilities', 'GET /{resource}/{id}/capabilities', 'CrudMetadata.actions'],
5875
+ writes: ['CrudMetadata.actions[].disabled', 'CrudMetadata.actions[].visibleWhen', 'CrudMetadata.actions[].requiresConfirmation'],
5876
+ identityKeys: ['resourcePath'],
5877
+ inputSchema: permissionsSchema,
5878
+ failureModes: ['capability-not-found', 'action-permission-conflict', 'delete-permission-without-confirmation'],
5879
+ description: 'Aligns CRUD action visibility/disablement with resource capabilities without inventing a second permission source.',
5880
+ } }],
5881
+ validators: ['resource-capabilities-resolvable', 'action-permission-supported', 'delete-permission-requires-confirmation', 'permissions-do-not-shadow-backend'],
5882
+ affectedPaths: ['actions[].disabled', 'actions[].visibleWhen', 'actions[].requiresConfirmation'],
5883
+ submissionImpact: 'config-only',
5884
+ preconditions: ['crud-metadata-loaded', 'resource-capabilities-resolved'],
5885
+ },
5886
+ {
5887
+ operationId: 'form.childOperation.delegate',
5888
+ title: 'Delegate child form/table/dialog authoring operation',
5889
+ scope: 'global',
5890
+ targetKind: 'childOperation',
5891
+ target: { kind: 'childOperation', resolver: 'child-authoring-manifest-operation', ambiguityPolicy: 'fail', required: false },
5892
+ inputSchema: childDelegateSchema,
5893
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-child-operation-delegate', handlerContract: {
5894
+ reads: ['PRAXIS_DYNAMIC_FORM_AUTHORING_MANIFEST', 'PRAXIS_TABLE_AUTHORING_MANIFEST', 'PRAXIS_DIALOG_AUTHORING_MANIFEST', 'PRAXIS_SETTINGS_PANEL_AUTHORING_MANIFEST'],
5895
+ writes: ['delegatedAuthoringOperations[]'],
5896
+ identityKeys: ['childComponentId', 'childOperationId'],
5897
+ inputSchema: childDelegateSchema,
5898
+ failureModes: ['child-manifest-missing', 'child-operation-not-found', 'attempted-local-child-config-write'],
5899
+ description: 'Records explicit delegation when requested edits belong to child manifests instead of CRUD orchestration.',
5900
+ } }],
5901
+ validators: ['child-manifest-available', 'child-operation-known', 'no-local-form-config-write', 'no-local-table-config-write', 'delegation-target-valid'],
5902
+ affectedPaths: ['delegatedAuthoringOperations'],
5903
+ submissionImpact: 'none',
5904
+ preconditions: ['child-manifest-available'],
5905
+ },
5906
+ ],
5907
+ validators: [
5908
+ { validatorId: 'resource-exists-in-api-metadata', level: 'error', code: 'CRUD_RESOURCE_EXISTS', description: 'Resource must exist in api_metadata or resource discovery.' },
5909
+ { validatorId: 'resource-path-canonical', level: 'error', code: 'CRUD_RESOURCE_PATH_CANONICAL', description: 'Resource path must be canonical and not a local alias.' },
5910
+ { validatorId: 'resource-key-stable', level: 'error', code: 'CRUD_RESOURCE_KEY_STABLE', description: 'Resource key must remain stable for surfaces/actions/capabilities.' },
5911
+ { validatorId: 'id-field-known', level: 'error', code: 'CRUD_ID_FIELD_KNOWN', description: 'CRUD id field must exist for edit/view/delete flows.' },
5912
+ { validatorId: 'resource-capabilities-resolvable', level: 'error', code: 'CRUD_CAPABILITIES_RESOLVABLE', description: 'Resource capabilities must be resolvable before action enablement is authored.' },
5913
+ { validatorId: 'domain-catalog-context-resolvable', level: 'error', code: 'CRUD_DOMAIN_CATALOG_CONTEXT_RESOLVABLE', description: 'Domain Catalog governance context must resolve for the resource key and requested probe before authoring uses it.' },
5914
+ { validatorId: 'domain-catalog-read-only', level: 'error', code: 'CRUD_DOMAIN_CATALOG_READ_ONLY', description: 'Domain Catalog is read-only semantic context; CRUD may reference a probe but must not persist copied catalog rules.' },
5915
+ { validatorId: 'ai-usage-visibility-respected', level: 'error', code: 'CRUD_AI_USAGE_VISIBILITY_RESPECTED', description: 'Authoring must respect Domain Catalog aiUsage.visibility before exposing field data, prompts or generated patches.' },
5916
+ { validatorId: 'no-form-config-rule-materialization', level: 'error', code: 'CRUD_NO_FORM_RULE_MATERIALIZATION', description: 'Shared domain governance must not be materialized into FormConfig rules unless a backend policy explicitly authorizes it.' },
5917
+ { validatorId: 'table-child-operation-delegated', level: 'error', code: 'CRUD_TABLE_CHILD_DELEGATED', description: 'Table semantics must be delegated to praxis-table.' },
5918
+ { validatorId: 'query-context-valid', level: 'error', code: 'CRUD_QUERY_CONTEXT_VALID', description: 'Query context must be valid for the bound resource.' },
5919
+ { validatorId: 'filter-criteria-bridge-valid', level: 'warning', code: 'CRUD_FILTER_CRITERIA_BRIDGE_VALID', description: 'filterCriteria is a bridge; prefer queryContext for new remote authoring.' },
5920
+ { validatorId: 'crud-context-stable', level: 'error', code: 'CRUD_CONTEXT_STABLE', description: 'Authoring must not break stable crudContext references.' },
5921
+ { validatorId: 'action-exists', level: 'error', code: 'CRUD_ACTION_EXISTS', description: 'Target action must exist or be created through CRUD action orchestration.' },
5922
+ { validatorId: 'open-mode-binding-complete', level: 'error', code: 'CRUD_OPEN_MODE_BINDING_COMPLETE', description: 'route requires route; modal/drawer require formId unless resolved by governed overrides.' },
5923
+ { validatorId: 'schema-url-canonical', level: 'error', code: 'CRUD_SCHEMA_URL_CANONICAL', description: 'Schema URLs must be canonical for the bound resource/action.' },
5924
+ { validatorId: 'submit-url-canonical', level: 'error', code: 'CRUD_SUBMIT_URL_CANONICAL', description: 'Submit URL and method must be canonical and declared together.' },
5925
+ { validatorId: 'resource-create-supported', level: 'error', code: 'CRUD_CREATE_SUPPORTED', description: 'Create surface requires resource create capability.' },
5926
+ { validatorId: 'resource-edit-supported', level: 'error', code: 'CRUD_EDIT_SUPPORTED', description: 'Edit surface requires resource edit capability.' },
5927
+ { validatorId: 'resource-view-supported', level: 'error', code: 'CRUD_VIEW_SUPPORTED', description: 'View surface requires resource view capability.' },
5928
+ { validatorId: 'form-child-operation-delegated', level: 'error', code: 'CRUD_FORM_CHILD_DELEGATED', description: 'FormConfig and FieldMetadata edits must delegate to dynamic-form/metadata-editor manifests.' },
5929
+ { validatorId: 'id-param-mapping-valid', level: 'error', code: 'CRUD_ID_PARAM_MAPPING_VALID', description: 'Edit/view actions must map resource id into route/query/input as required.' },
5930
+ { validatorId: 'readonly-form-delegation-valid', level: 'error', code: 'CRUD_READONLY_FORM_DELEGATED', description: 'Readonly form behavior belongs to the dynamic-form manifest.' },
5931
+ { validatorId: 'delete-action-exists', level: 'error', code: 'CRUD_DELETE_ACTION_EXISTS', description: 'Delete behavior requires a delete action target.' },
5932
+ { validatorId: 'resource-delete-supported', level: 'error', code: 'CRUD_DELETE_SUPPORTED', description: 'Delete behavior requires resource delete capability.' },
5933
+ { validatorId: 'destructive-delete-confirmed', level: 'error', code: 'CRUD_DELETE_CONFIRMED', description: 'Destructive delete behavior requires explicit confirmation.' },
5934
+ { validatorId: 'permissions-delete-valid', level: 'error', code: 'CRUD_DELETE_PERMISSION_VALID', description: 'Delete permission cannot bypass resource capabilities or confirmation policy.' },
5935
+ { validatorId: 'open-mode-supported', level: 'error', code: 'CRUD_OPEN_MODE_SUPPORTED', description: 'Open mode must be route, modal or drawer.' },
5936
+ { validatorId: 'modal-size-valid', level: 'error', code: 'CRUD_MODAL_SIZE_VALID', description: 'Modal sizing defaults must be valid DialogConfig values.' },
5937
+ { validatorId: 'drawer-adapter-available-when-needed', level: 'error', code: 'CRUD_DRAWER_ADAPTER_AVAILABLE', description: 'Drawer open mode requires a host-provided drawer adapter.' },
5938
+ { validatorId: 'back-policy-valid', level: 'error', code: 'CRUD_BACK_POLICY_VALID', description: 'Back policy must be valid for route/modal/drawer behavior.' },
5939
+ { validatorId: 'settings-panel-shell-compatible', level: 'warning', code: 'CRUD_SETTINGS_PANEL_COMPATIBLE', description: 'Authoring shell must preserve apply/save/reset semantics.' },
5940
+ { validatorId: 'action-permission-supported', level: 'error', code: 'CRUD_ACTION_PERMISSION_SUPPORTED', description: 'Action permissions must map to supported resource capabilities.' },
5941
+ { validatorId: 'delete-permission-requires-confirmation', level: 'error', code: 'CRUD_DELETE_PERMISSION_CONFIRMATION', description: 'Delete permission enablement requires confirmation policy.' },
5942
+ { validatorId: 'permissions-do-not-shadow-backend', level: 'error', code: 'CRUD_PERMISSIONS_NO_BACKEND_SHADOW', description: 'UI permissions must not shadow backend capability denial.' },
5943
+ { validatorId: 'child-manifest-available', level: 'error', code: 'CRUD_CHILD_MANIFEST_AVAILABLE', description: 'Delegated child manifest must be available.' },
5944
+ { validatorId: 'child-operation-known', level: 'error', code: 'CRUD_CHILD_OPERATION_KNOWN', description: 'Delegated operation must exist in the child manifest.' },
5945
+ { validatorId: 'no-local-form-config-write', level: 'error', code: 'CRUD_NO_LOCAL_FORM_CONFIG_WRITE', description: 'CRUD must not locally redefine FormConfig semantics.' },
5946
+ { validatorId: 'no-local-table-config-write', level: 'error', code: 'CRUD_NO_LOCAL_TABLE_CONFIG_WRITE', description: 'CRUD must not locally redefine TableConfig semantics.' },
5947
+ { validatorId: 'delegation-target-valid', level: 'error', code: 'CRUD_DELEGATION_TARGET_VALID', description: 'Delegation target must be resolvable by the child manifest.' },
5948
+ ],
5949
+ roundTripRequirements: [
5950
+ 'CrudMetadata is the canonical CRUD document shape.',
5951
+ 'CrudAuthoringDocument wraps CrudMetadata without introducing host-local aliases.',
5952
+ 'Resource path and resource key have separate semantics: path is operational, key is discovery/capability identity.',
5953
+ 'Domain Catalog context is referenced through resourceKey/release/probe and stays read-only; CRUD may attach queryContext.meta.domainCatalog but must not copy shared governance rules into FormConfig.',
5954
+ 'Open mode round-trip must preserve route, formId, form contract, params, initialValue and back policy.',
5955
+ 'FormConfig and FieldMetadata edits must delegate to dynamic-form or metadata-editor manifests.',
5956
+ 'TableConfig edits must delegate to praxis-table; CRUD owns only shell orchestration and list surface binding.',
5957
+ 'Delete behavior is destructive and requires explicit confirmation plus backend capability support.',
5958
+ 'crudContext must remain reference-stable across change detection cycles.',
5959
+ ],
5960
+ examples: [
5961
+ { id: 'crud-bind-funcionarios-resource', request: 'Bind this CRUD to funcionarios using id as the identifier.', operationId: 'resource.bind', target: 'resource:funcionarios', params: { resourcePath: '/funcionarios', resourceKey: 'funcionarios', idField: 'id' }, isPositive: true },
5962
+ { id: 'crud-attach-lgpd-cpf-context', request: 'Use the Domain Catalog LGPD context for cpf before changing the employee CRUD.', operationId: 'domain.governanceContext.attach', target: 'domainCatalog:human-resources.funcionarios:cpf', params: { resourceKey: 'human-resources.funcionarios', query: 'cpf', itemTypes: ['governance'], intent: 'authoring' }, isPositive: true },
5963
+ { id: 'crud-list-query-context', request: 'Filter the list by active employees using the canonical query context.', operationId: 'list.surface.configure', target: 'listSurface', params: { queryContext: { filters: [{ field: 'active', operator: 'eq', value: true }] } }, isPositive: true },
5964
+ { id: 'crud-create-modal', request: 'Open create in a modal with the employee form.', operationId: 'surface.create.configure', target: 'action:create', params: { actionId: 'create', openMode: 'modal', formId: 'employee-create' }, isPositive: true },
5965
+ { id: 'crud-edit-route-with-id', request: 'Edit should navigate to /employees/:id and map the row id.', operationId: 'surface.edit.configure', target: 'action:edit', params: { actionId: 'edit', openMode: 'route', route: '/employees/:id/edit', params: [{ from: 'id', to: 'routeParam', name: 'id' }] }, isPositive: true },
5966
+ { id: 'crud-view-drawer-readonly', request: 'Open view in a drawer and keep the form readonly.', operationId: 'surface.view.configure', target: 'action:view', params: { actionId: 'view', openMode: 'drawer', formId: 'employee-view' }, isPositive: true },
5967
+ { id: 'crud-delete-enable-confirmed', request: 'Enable delete but require confirmation.', operationId: 'delete.enabled.set', target: 'action:delete', params: { enabled: true, actionId: 'delete', requiresConfirmation: true, autoDelete: true }, isPositive: true },
5968
+ { id: 'crud-dialog-size-medium', request: 'Make CRUD modals 900px wide and remember their last state.', operationId: 'dialog.size.set', target: 'dialogHost', params: { defaultOpenMode: 'modal', modal: { width: '900px', rememberLastState: true } }, isPositive: true },
5969
+ { id: 'crud-permissions-no-delete', request: 'Hide delete when the resource capabilities do not allow it.', operationId: 'permissions.set', target: 'permissions:delete', params: { requiredCapabilities: ['delete'], denyWhenMissingCapability: true }, isPositive: true },
5970
+ { id: 'crud-delegate-form-layout', request: 'Add a new field to the create form.', operationId: 'form.childOperation.delegate', target: 'praxis-dynamic-form:field.add', params: { childComponentId: 'praxis-dynamic-form', childOperationId: 'field.add', reason: 'Form field semantics belong to dynamic-form' }, isPositive: true },
5971
+ { id: 'crud-delegate-table-column', request: 'Add a salary column to the list table.', operationId: 'form.childOperation.delegate', target: 'praxis-table:column.add', params: { childComponentId: 'praxis-table', childOperationId: 'column.add', reason: 'Table column semantics belong to praxis-table' }, isPositive: true },
5972
+ { id: 'crud-reject-local-form-rules', request: 'Put this dynamic-form validation rule directly inside CRUD.', operationId: 'form.childOperation.delegate', target: 'praxis-dynamic-form:rules.add', params: { childComponentId: 'praxis-dynamic-form', childOperationId: 'rules.add', reason: 'CRUD must not redefine FormConfig rules' }, isPositive: false },
5973
+ { id: 'crud-reject-copy-domain-rule-to-formconfig', request: 'Copy the LGPD rule from Domain Catalog directly into the create form JSON.', operationId: 'domain.governanceContext.attach', target: 'domainCatalog:human-resources.funcionarios:cpf', params: { resourceKey: 'human-resources.funcionarios', query: 'cpf', itemTypes: ['governance'], intent: 'authoring' }, isPositive: false },
5974
+ { id: 'crud-reject-delete-without-capability', request: 'Force delete even though the backend does not expose delete capability.', operationId: 'delete.enabled.set', target: 'action:delete', params: { enabled: true, actionId: 'delete', requiresConfirmation: false }, isPositive: false },
5975
+ ],
5976
+ };
5977
+
5251
5978
  /*
5252
5979
  * Public API Surface of praxis-crud
5253
5980
  */
@@ -5256,4 +5983,4 @@ const CRUD_AI_CAPABILITIES = {
5256
5983
  * Generated bundle index. Do not edit.
5257
5984
  */
5258
5985
 
5259
- export { CRUD_AI_CAPABILITIES, CrudLauncherService, CrudMetadataEditorComponent, CrudPageHeaderComponent, DialogService, DynamicFormDialogHostComponent, PRAXIS_CRUD_COMPONENT_METADATA, PraxisCrudComponent, assertCrudMetadata, createCrudAuthoringDocument, findCrudAction, normalizeCrudAuthoringDocument, openCrudMetadataEditor, parseLegacyOrCrudDocument, providePraxisCrudMetadata, serializeCrudAuthoringDocument, validateCrudAuthoringDocument };
5986
+ export { CRUD_AI_CAPABILITIES, CrudLauncherService, CrudMetadataEditorComponent, CrudPageHeaderComponent, DialogService, DynamicFormDialogHostComponent, PRAXIS_CRUD_AUTHORING_MANIFEST, PRAXIS_CRUD_COMPONENT_METADATA, PraxisCrudComponent, PraxisCrudWidgetConfigEditor, assertCrudMetadata, createCrudAuthoringDocument, findCrudAction, normalizeCrudAuthoringDocument, openCrudMetadataEditor, parseLegacyOrCrudDocument, providePraxisCrudMetadata, serializeCrudAuthoringDocument, validateCrudAuthoringDocument };