@praxisui/crud 8.0.0-beta.9 → 8.0.0-beta.91

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, runtimeEvent);
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();
@@ -3727,7 +3764,7 @@ class PraxisCrudComponent {
3727
3764
  }
3728
3765
  let payload;
3729
3766
  try {
3730
- payload = this.surfaceOpenAdapter.toPayload(surface, {
3767
+ payload = this.getSurfaceOpenAdapter().toPayload(surface, {
3731
3768
  resourcePath,
3732
3769
  resourceId: this.resolveRowResourceId(row),
3733
3770
  endpointKey: this.resolvedMetadata?.resource?.endpointKey,
@@ -3777,13 +3814,13 @@ class PraxisCrudComponent {
3777
3814
  if (!this.tableCollectionLinks) {
3778
3815
  return null;
3779
3816
  }
3780
- return await firstValueFrom(this.resourceDiscovery.getSurfaces(this.tableCollectionLinks, this.buildDiscoveryOptions()));
3817
+ return await firstValueFrom(this.getResourceDiscovery().getSurfaces(this.tableCollectionLinks, this.buildDiscoveryOptions()));
3781
3818
  }
3782
3819
  const rowLinks = row?._links;
3783
3820
  if (!rowLinks) {
3784
3821
  return null;
3785
3822
  }
3786
- return await firstValueFrom(this.resourceDiscovery.getSurfaces(rowLinks, this.buildDiscoveryOptions()));
3823
+ return await firstValueFrom(this.getResourceDiscovery().getSurfaces(rowLinks, this.buildDiscoveryOptions()));
3787
3824
  }
3788
3825
  catch {
3789
3826
  return null;
@@ -3807,7 +3844,7 @@ class PraxisCrudComponent {
3807
3844
  }
3808
3845
  let payload;
3809
3846
  try {
3810
- payload = this.actionOpenAdapter.toPayload(discoveredAction, {
3847
+ payload = this.getActionOpenAdapter().toPayload(discoveredAction, {
3811
3848
  resourcePath,
3812
3849
  resourceId: this.resolveRowResourceId(row),
3813
3850
  endpointKey: this.resolvedMetadata?.resource?.endpointKey,
@@ -3858,12 +3895,12 @@ class PraxisCrudComponent {
3858
3895
  if (!rowLinks) {
3859
3896
  return null;
3860
3897
  }
3861
- return await firstValueFrom(this.resourceDiscovery.getActions(rowLinks, this.buildDiscoveryOptions()));
3898
+ return await firstValueFrom(this.getResourceDiscovery().getActions(rowLinks, this.buildDiscoveryOptions()));
3862
3899
  }
3863
3900
  if (!this.tableCollectionLinks) {
3864
3901
  return null;
3865
3902
  }
3866
- return await firstValueFrom(this.resourceDiscovery.getActions(this.tableCollectionLinks, this.buildDiscoveryOptions()));
3903
+ return await firstValueFrom(this.getResourceDiscovery().getActions(this.tableCollectionLinks, this.buildDiscoveryOptions()));
3867
3904
  }
3868
3905
  catch {
3869
3906
  return null;
@@ -4111,13 +4148,27 @@ class PraxisCrudComponent {
4111
4148
  });
4112
4149
  }
4113
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;
4114
4155
  cfg.actions = cfg.actions || {};
4115
4156
  cfg.actions.row = {
4116
4157
  enabled: true,
4117
4158
  position: 'end',
4118
- width: cfg.actions?.row?.width || '120px',
4159
+ width: cfg.actions?.row?.width || crudDefaults.rowActionsWidth || '144px',
4119
4160
  display: crudDefaults.rowActionsDisplay || 'icons',
4120
- 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
+ },
4121
4172
  actions: rowActions,
4122
4173
  header: { label: cfg.actions?.row?.header?.label || '' },
4123
4174
  };
@@ -4327,7 +4378,7 @@ class PraxisCrudComponent {
4327
4378
  }
4328
4379
  resolveCollectionCapabilitiesHref(links) {
4329
4380
  try {
4330
- return this.resourceDiscovery.resolveLinkHref(links || undefined, 'capabilities', this.buildDiscoveryOptions());
4381
+ return this.getResourceDiscovery().resolveLinkHref(links || undefined, 'capabilities', this.buildDiscoveryOptions());
4331
4382
  }
4332
4383
  catch {
4333
4384
  return null;
@@ -4347,7 +4398,7 @@ class PraxisCrudComponent {
4347
4398
  }
4348
4399
  resolveDiscoveryApiEntry() {
4349
4400
  try {
4350
- return this.resourceDiscovery.resolveApiEntry(this.buildDiscoveryOptions());
4401
+ return this.getResourceDiscovery().resolveApiEntry(this.buildDiscoveryOptions());
4351
4402
  }
4352
4403
  catch {
4353
4404
  return null;
@@ -4407,8 +4458,8 @@ class PraxisCrudComponent {
4407
4458
  return fallback;
4408
4459
  }
4409
4460
  }
4410
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisCrudComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4411
- 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: [
4412
4463
  providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
4413
4464
  providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG),
4414
4465
  ], viewQueries: [{ propertyName: "table", first: true, predicate: PraxisTable, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
@@ -4422,6 +4473,8 @@ class PraxisCrudComponent {
4422
4473
  [tableId]="crudId || 'default'"
4423
4474
  [crudContext]="tableCrudContext"
4424
4475
  [enableCustomization]="enableCustomization"
4476
+ (rowClick)="onTableRowClick($event)"
4477
+ (selectionChange)="onTableSelectionChange($event)"
4425
4478
  (rowAction)="onAction($event.action, $event.row, $event)"
4426
4479
  (toolbarAction)="onAction($event.action)"
4427
4480
  (collectionLinksChange)="onCollectionLinksChange($event)"
@@ -4439,9 +4492,9 @@ class PraxisCrudComponent {
4439
4492
  />
4440
4493
  }
4441
4494
  }
4442
- `, 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", "selectionChange"] }, { 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", "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"] }] });
4443
4496
  }
4444
- 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: [{
4445
4498
  type: Component,
4446
4499
  args: [{ selector: 'praxis-crud', standalone: true, imports: [PraxisTable, EmptyStateCardComponent], providers: [
4447
4500
  providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
@@ -4457,6 +4510,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
4457
4510
  [tableId]="crudId || 'default'"
4458
4511
  [crudContext]="tableCrudContext"
4459
4512
  [enableCustomization]="enableCustomization"
4513
+ (rowClick)="onTableRowClick($event)"
4514
+ (selectionChange)="onTableSelectionChange($event)"
4460
4515
  (rowAction)="onAction($event.action, $event.row, $event)"
4461
4516
  (toolbarAction)="onAction($event.action)"
4462
4517
  (collectionLinksChange)="onCollectionLinksChange($event)"
@@ -4499,6 +4554,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
4499
4554
  type: Output
4500
4555
  }], error: [{
4501
4556
  type: Output
4557
+ }], rowClick: [{
4558
+ type: Output
4559
+ }], selectionChange: [{
4560
+ type: Output
4502
4561
  }], tableRuntimeConfigChange: [{
4503
4562
  type: Output
4504
4563
  }], crudAuthoringDocumentApplied: [{
@@ -4807,8 +4866,8 @@ class DynamicFormDialogHostComponent {
4807
4866
  this.dialogRef.updatePosition();
4808
4867
  }
4809
4868
  }
4810
- 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 });
4811
- 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: `
4812
4871
  <div mat-dialog-title class="dialog-header">
4813
4872
  <h2 id="crudDialogTitle" class="dialog-title">
4814
4873
  {{ data.action?.label || texts.title }}
@@ -4857,17 +4916,16 @@ class DynamicFormDialogHostComponent {
4857
4916
  (formCancel)="onCancel()"
4858
4917
  ></praxis-dynamic-form>
4859
4918
  </mat-dialog-content>
4860
- `, 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", "actions", "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"] }] });
4861
4920
  }
4862
- 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: [{
4863
4922
  type: Component,
4864
4923
  args: [{ selector: 'praxis-dynamic-form-dialog-host', standalone: true, imports: [
4865
- CommonModule,
4866
4924
  MatDialogModule,
4867
4925
  MatButtonModule,
4868
4926
  MatIconModule,
4869
4927
  PraxisIconDirective,
4870
- PraxisDynamicForm,
4928
+ PraxisDynamicForm
4871
4929
  ], providers: [GenericCrudService], host: {
4872
4930
  class: 'praxis-dialog',
4873
4931
  '[attr.data-density]': 'modal.density || "default"',
@@ -4963,6 +5021,102 @@ var dynamicFormDialogHost_component = /*#__PURE__*/Object.freeze({
4963
5021
  DynamicFormDialogHostComponent: DynamicFormDialogHostComponent
4964
5022
  });
4965
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
+
4966
5120
  /** Metadata for PraxisCrudComponent */
4967
5121
  const PRAXIS_CRUD_COMPONENT_METADATA = {
4968
5122
  id: 'praxis-crud',
@@ -4971,6 +5125,14 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
4971
5125
  friendlyName: 'Praxis CRUD',
4972
5126
  description: 'Tabela com operações de CRUD via metadados.',
4973
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
+ },
4974
5136
  inputs: [
4975
5137
  {
4976
5138
  name: 'metadata',
@@ -5035,6 +5197,16 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
5035
5197
  type: '{ id: string | number }',
5036
5198
  description: 'Emitido ao deletar.',
5037
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
+ },
5038
5210
  {
5039
5211
  name: 'error',
5040
5212
  type: 'unknown',
@@ -5141,6 +5313,23 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
5141
5313
  },
5142
5314
  scope: 'shell',
5143
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
+ },
5144
5333
  {
5145
5334
  id: 'close',
5146
5335
  label: 'Fechar',
@@ -5181,64 +5370,66 @@ class CrudPageHeaderComponent {
5181
5370
  sticky = true;
5182
5371
  divider = true;
5183
5372
  returnTo;
5184
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudPageHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5185
- 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: `
5186
5375
  <header
5187
5376
  class="crud-header"
5188
5377
  [class.sticky]="sticky"
5189
5378
  [class.with-divider]="divider"
5190
- >
5379
+ >
5191
5380
  <div class="left">
5192
- <a
5193
- *ngIf="showBack && returnTo"
5194
- class="back-btn"
5195
- [class.ghost]="variant === 'ghost'"
5196
- [class.tonal]="variant === 'tonal'"
5197
- [class.outlined]="variant === 'outlined'"
5198
- [routerLink]="returnTo"
5199
- [attr.aria-label]="backLabel || 'Voltar'"
5200
- mat-button
5201
- >
5202
- <mat-icon [praxisIcon]="'arrow_back'"></mat-icon>
5203
- <span class="label" [class.hide-on-narrow]="true">{{ backLabel || 'Voltar' }}</span>
5204
- </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
+ }
5205
5395
  <h2 class="title" [attr.title]="title">{{ title }}</h2>
5206
5396
  </div>
5207
5397
  <div class="right">
5208
5398
  <ng-content></ng-content>
5209
5399
  </div>
5210
5400
  </header>
5211
- `, 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"] }] });
5212
5402
  }
5213
- 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: [{
5214
5404
  type: Component,
5215
- 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: `
5216
5406
  <header
5217
5407
  class="crud-header"
5218
5408
  [class.sticky]="sticky"
5219
5409
  [class.with-divider]="divider"
5220
- >
5410
+ >
5221
5411
  <div class="left">
5222
- <a
5223
- *ngIf="showBack && returnTo"
5224
- class="back-btn"
5225
- [class.ghost]="variant === 'ghost'"
5226
- [class.tonal]="variant === 'tonal'"
5227
- [class.outlined]="variant === 'outlined'"
5228
- [routerLink]="returnTo"
5229
- [attr.aria-label]="backLabel || 'Voltar'"
5230
- mat-button
5231
- >
5232
- <mat-icon [praxisIcon]="'arrow_back'"></mat-icon>
5233
- <span class="label" [class.hide-on-narrow]="true">{{ backLabel || 'Voltar' }}</span>
5234
- </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
+ }
5235
5426
  <h2 class="title" [attr.title]="title">{{ title }}</h2>
5236
5427
  </div>
5237
5428
  <div class="right">
5238
5429
  <ng-content></ng-content>
5239
5430
  </div>
5240
5431
  </header>
5241
- `, 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"] }]
5242
5433
  }], propDecorators: { title: [{
5243
5434
  type: Input
5244
5435
  }], backLabel: [{
@@ -5290,6 +5481,12 @@ const CRUD_AI_CAPABILITIES = {
5290
5481
  { path: 'resource.path', category: 'resource', valueKind: 'string', description: 'Endpoint base do recurso.' },
5291
5482
  { path: 'resource.idField', category: 'resource', valueKind: 'string', description: 'Campo identificador do recurso.' },
5292
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.' },
5293
5490
  // --- Table / Form ---
5294
5491
  { path: 'table', category: 'table', valueKind: 'object', description: 'TableConfig completo (usar catalogo de tabela).' },
5295
5492
  { path: 'form', category: 'form', valueKind: 'object', description: 'FormConfig completo (usar catalogo de formulario).' },
@@ -5346,6 +5543,438 @@ const CRUD_AI_CAPABILITIES = {
5346
5543
  ],
5347
5544
  };
5348
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
+
5349
5978
  /*
5350
5979
  * Public API Surface of praxis-crud
5351
5980
  */
@@ -5354,4 +5983,4 @@ const CRUD_AI_CAPABILITIES = {
5354
5983
  * Generated bundle index. Do not edit.
5355
5984
  */
5356
5985
 
5357
- 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 };