@praxisui/crud 8.0.0-beta.8 → 8.0.0-beta.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,20 +1,18 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, InjectionToken, inject, input, signal, computed, effect, ChangeDetectionStrategy, Component, EventEmitter, DestroyRef, ChangeDetectorRef, ViewChild, Output, Input, Inject, ENVIRONMENT_INITIALIZER } from '@angular/core';
2
+ import { Injectable, InjectionToken, inject, input, signal, computed, effect, ChangeDetectionStrategy, Component, EventEmitter, DestroyRef, ChangeDetectorRef, Injector, ViewChild, Output, Input, Inject, ENVIRONMENT_INITIALIZER } from '@angular/core';
3
3
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
4
  import { HttpClient } from '@angular/common/http';
5
5
  import { Router, ActivatedRoute, RouterLink } from '@angular/router';
6
6
  import { MatSnackBar } from '@angular/material/snack-bar';
7
- import { firstValueFrom, BehaviorSubject } from 'rxjs';
7
+ import { firstValueFrom, BehaviorSubject, Subscription } from 'rxjs';
8
8
  import * as i2$1 from '@praxisui/core';
9
- import { ASYNC_CONFIG_STORAGE, GlobalConfigService, CrudOperationResolutionService, fillUndefined, SETTINGS_PANEL_DATA, PraxisI18nService, providePraxisI18nConfig, createDefaultTableConfig, ResourceDiscoveryService, ResourceActionOpenAdapterService, ResourceSurfaceOpenAdapterService, GLOBAL_SURFACE_SERVICE, ComponentKeyService, translateUnavailableWorkflowMessage, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, PraxisIconDirective, GenericCrudService, ComponentMetadataRegistry } from '@praxisui/core';
9
+ import { ASYNC_CONFIG_STORAGE, GlobalConfigService, CrudOperationResolutionService, fillUndefined, SETTINGS_PANEL_DATA, PraxisI18nService, providePraxisI18nConfig, createDefaultTableConfig, GLOBAL_SURFACE_SERVICE, ComponentKeyService, ResourceDiscoveryService, ResourceActionOpenAdapterService, ResourceSurfaceOpenAdapterService, translateUnavailableWorkflowMessage, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, PraxisIconDirective, GenericCrudService, ComponentMetadataRegistry } from '@praxisui/core';
10
10
  import { SettingsPanelService } from '@praxisui/settings-panel';
11
11
  import { PraxisTableInlineAuthoringEditorComponent, PraxisTable } from '@praxisui/table';
12
12
  import { ConfirmDialogComponent } from '@praxisui/dynamic-fields';
13
13
  import * as i1 from '@angular/material/dialog';
14
14
  import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
15
15
  export { MAT_DIALOG_DATA as DIALOG_DATA } from '@angular/material/dialog';
16
- import * as i1$2 from '@angular/common';
17
- import { CommonModule } from '@angular/common';
18
16
  import * as i1$1 from '@angular/forms';
19
17
  import { FormsModule } from '@angular/forms';
20
18
  import * as i2 from '@angular/material/button';
@@ -50,10 +48,10 @@ class DialogService {
50
48
  const component = await loader();
51
49
  return this.zone.run(() => this.matDialog.open(component, config));
52
50
  }
53
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DialogService, deps: [{ token: i1.MatDialog }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
54
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DialogService, providedIn: 'root' });
51
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DialogService, deps: [{ token: i1.MatDialog }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
52
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DialogService, providedIn: 'root' });
55
53
  }
56
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DialogService, decorators: [{
54
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DialogService, decorators: [{
57
55
  type: Injectable,
58
56
  args: [{ providedIn: 'root' }]
59
57
  }], ctorParameters: () => [{ type: i1.MatDialog }, { type: i0.NgZone }] });
@@ -251,6 +249,9 @@ class CrudLauncherService {
251
249
  if (action.form?.initialValue != null) {
252
250
  inputs['initialValue'] = action.form.initialValue;
253
251
  }
252
+ else if (action.action !== 'create' && row && typeof row === 'object') {
253
+ inputs['initialValue'] = { ...row };
254
+ }
254
255
  return inputs;
255
256
  }
256
257
  resolveActionForLaunch(action, metadata) {
@@ -382,10 +383,10 @@ class CrudLauncherService {
382
383
  return { metadata, action };
383
384
  }
384
385
  }
385
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudLauncherService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
386
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudLauncherService, providedIn: 'root' });
386
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudLauncherService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
387
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudLauncherService, providedIn: 'root' });
387
388
  }
388
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudLauncherService, decorators: [{
389
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudLauncherService, decorators: [{
389
390
  type: Injectable,
390
391
  args: [{ providedIn: 'root' }]
391
392
  }] });
@@ -1235,10 +1236,10 @@ const MODAL_DENSITIES = ['default', 'compact'];
1235
1236
  const BACK_STRATEGIES = ['auto', 'close', 'navigate'];
1236
1237
  const PARAM_TARGETS = ['routeParam', 'query', 'input'];
1237
1238
  class CrudMetadataEditorComponent {
1238
- documentInput = input(null, ...(ngDevMode ? [{ debugName: "documentInput", alias: 'document' }] : [{ alias: 'document' }]));
1239
- metadataInput = input(null, ...(ngDevMode ? [{ debugName: "metadataInput", alias: 'metadata' }] : [{ alias: 'metadata' }]));
1240
- crudIdInput = input(null, ...(ngDevMode ? [{ debugName: "crudIdInput", alias: 'crudId' }] : [{ alias: 'crudId' }]));
1241
- readonlyInput = input(false, ...(ngDevMode ? [{ debugName: "readonlyInput", alias: 'readonly' }] : [{ alias: 'readonly' }]));
1239
+ documentInput = input(null, { ...(ngDevMode ? { debugName: "documentInput" } : /* istanbul ignore next */ {}), alias: 'document' });
1240
+ metadataInput = input(null, { ...(ngDevMode ? { debugName: "metadataInput" } : /* istanbul ignore next */ {}), alias: 'metadata' });
1241
+ crudIdInput = input(null, { ...(ngDevMode ? { debugName: "crudIdInput" } : /* istanbul ignore next */ {}), alias: 'crudId' });
1242
+ readonlyInput = input(false, { ...(ngDevMode ? { debugName: "readonlyInput" } : /* istanbul ignore next */ {}), alias: 'readonly' });
1242
1243
  isDirty$ = new BehaviorSubject(false);
1243
1244
  isValid$ = new BehaviorSubject(true);
1244
1245
  isBusy$ = new BehaviorSubject(false);
@@ -1250,39 +1251,39 @@ class CrudMetadataEditorComponent {
1250
1251
  modalDensities = MODAL_DENSITIES;
1251
1252
  backStrategies = BACK_STRATEGIES;
1252
1253
  paramTargets = PARAM_TARGETS;
1253
- initialValueDrafts = signal({}, ...(ngDevMode ? [{ debugName: "initialValueDrafts" }] : []));
1254
+ initialValueDrafts = signal({}, ...(ngDevMode ? [{ debugName: "initialValueDrafts" }] : /* istanbul ignore next */ []));
1254
1255
  injectedData = inject(SETTINGS_PANEL_DATA, { optional: true });
1255
1256
  i18n = inject(PraxisI18nService);
1256
- currentDocument = signal(createCrudAuthoringDocument({}), ...(ngDevMode ? [{ debugName: "currentDocument" }] : []));
1257
- initialDocument = signal(createCrudAuthoringDocument({}), ...(ngDevMode ? [{ debugName: "initialDocument" }] : []));
1257
+ currentDocument = signal(createCrudAuthoringDocument({}), ...(ngDevMode ? [{ debugName: "currentDocument" }] : /* istanbul ignore next */ []));
1258
+ initialDocument = signal(createCrudAuthoringDocument({}), ...(ngDevMode ? [{ debugName: "initialDocument" }] : /* istanbul ignore next */ []));
1258
1259
  lastExternalSignature = null;
1259
- effectiveCrudId = computed(() => this.crudIdInput() || this.injectedData?.crudId || null, ...(ngDevMode ? [{ debugName: "effectiveCrudId" }] : []));
1260
- isReadonly = computed(() => !!(this.readonlyInput() || this.injectedData?.readonly), ...(ngDevMode ? [{ debugName: "isReadonly" }] : []));
1261
- diagnostics = computed(() => validateCrudAuthoringDocument(this.currentDocument(), { requireCanonicalActions: true }), ...(ngDevMode ? [{ debugName: "diagnostics" }] : []));
1262
- errorCount = computed(() => this.diagnostics().filter((issue) => issue.level === 'error').length, ...(ngDevMode ? [{ debugName: "errorCount" }] : []));
1263
- warningCount = computed(() => this.diagnostics().filter((issue) => issue.level !== 'error').length, ...(ngDevMode ? [{ debugName: "warningCount" }] : []));
1264
- serializedDocument = computed(() => JSON.stringify(serializeCrudAuthoringDocument(this.currentDocument()), null, 2), ...(ngDevMode ? [{ debugName: "serializedDocument" }] : []));
1265
- tableConfig = computed(() => this.currentDocument().metadata.table || { columns: [] }, ...(ngDevMode ? [{ debugName: "tableConfig" }] : []));
1266
- resourcePath = computed(() => this.currentDocument().metadata.resource?.path || '', ...(ngDevMode ? [{ debugName: "resourcePath" }] : []));
1267
- resourceIdField = computed(() => String(this.currentDocument().metadata.resource?.idField ?? ''), ...(ngDevMode ? [{ debugName: "resourceIdField" }] : []));
1268
- resourceEndpointKey = computed(() => String(this.currentDocument().metadata.resource?.endpointKey ?? ''), ...(ngDevMode ? [{ debugName: "resourceEndpointKey" }] : []));
1269
- defaultsOpenMode = computed(() => this.currentDocument().metadata.defaults?.openMode || 'route', ...(ngDevMode ? [{ debugName: "defaultsOpenMode" }] : []));
1270
- headerShowBack = computed(() => !!this.currentDocument().metadata.defaults?.header?.showBack, ...(ngDevMode ? [{ debugName: "headerShowBack" }] : []));
1271
- headerBackLabel = computed(() => this.currentDocument().metadata.defaults?.header?.backLabel || '', ...(ngDevMode ? [{ debugName: "headerBackLabel" }] : []));
1272
- headerVariant = computed(() => this.currentDocument().metadata.defaults?.header?.variant || 'ghost', ...(ngDevMode ? [{ debugName: "headerVariant" }] : []));
1273
- headerSticky = computed(() => !!this.currentDocument().metadata.defaults?.header?.sticky, ...(ngDevMode ? [{ debugName: "headerSticky" }] : []));
1274
- headerBreadcrumbs = computed(() => !!this.currentDocument().metadata.defaults?.header?.breadcrumbs, ...(ngDevMode ? [{ debugName: "headerBreadcrumbs" }] : []));
1275
- headerDivider = computed(() => !!this.currentDocument().metadata.defaults?.header?.divider, ...(ngDevMode ? [{ debugName: "headerDivider" }] : []));
1276
- modalDensity = computed(() => String(this.currentDocument().metadata.defaults?.modal?.density ?? 'default'), ...(ngDevMode ? [{ debugName: "modalDensity" }] : []));
1277
- modalCanMaximize = computed(() => this.currentDocument().metadata.defaults?.modal?.canMaximize ?? true, ...(ngDevMode ? [{ debugName: "modalCanMaximize" }] : []));
1278
- modalStartMaximized = computed(() => !!this.currentDocument().metadata.defaults?.modal?.startMaximized, ...(ngDevMode ? [{ debugName: "modalStartMaximized" }] : []));
1279
- modalRememberLastState = computed(() => !!this.currentDocument().metadata.defaults?.modal?.rememberLastState, ...(ngDevMode ? [{ debugName: "modalRememberLastState" }] : []));
1280
- modalDisableCloseOnEsc = computed(() => !!this.currentDocument().metadata.defaults?.modal?.disableCloseOnEsc, ...(ngDevMode ? [{ debugName: "modalDisableCloseOnEsc" }] : []));
1281
- modalDisableCloseOnBackdrop = computed(() => !!this.currentDocument().metadata.defaults?.modal?.disableCloseOnBackdrop, ...(ngDevMode ? [{ debugName: "modalDisableCloseOnBackdrop" }] : []));
1282
- modalFullscreenBreakpoint = computed(() => String(this.currentDocument().metadata.defaults?.modal?.fullscreenBreakpoint ?? ''), ...(ngDevMode ? [{ debugName: "modalFullscreenBreakpoint" }] : []));
1283
- backStrategy = computed(() => String(this.currentDocument().metadata.defaults?.back?.strategy ?? 'auto'), ...(ngDevMode ? [{ debugName: "backStrategy" }] : []));
1284
- backReturnTo = computed(() => this.currentDocument().metadata.defaults?.back?.returnTo || '', ...(ngDevMode ? [{ debugName: "backReturnTo" }] : []));
1285
- backConfirmOnDirty = computed(() => !!this.currentDocument().metadata.defaults?.back?.confirmOnDirty, ...(ngDevMode ? [{ debugName: "backConfirmOnDirty" }] : []));
1260
+ effectiveCrudId = computed(() => this.crudIdInput() || this.injectedData?.crudId || null, ...(ngDevMode ? [{ debugName: "effectiveCrudId" }] : /* istanbul ignore next */ []));
1261
+ isReadonly = computed(() => !!(this.readonlyInput() || this.injectedData?.readonly), ...(ngDevMode ? [{ debugName: "isReadonly" }] : /* istanbul ignore next */ []));
1262
+ diagnostics = computed(() => validateCrudAuthoringDocument(this.currentDocument(), { requireCanonicalActions: true }), ...(ngDevMode ? [{ debugName: "diagnostics" }] : /* istanbul ignore next */ []));
1263
+ errorCount = computed(() => this.diagnostics().filter((issue) => issue.level === 'error').length, ...(ngDevMode ? [{ debugName: "errorCount" }] : /* istanbul ignore next */ []));
1264
+ warningCount = computed(() => this.diagnostics().filter((issue) => issue.level !== 'error').length, ...(ngDevMode ? [{ debugName: "warningCount" }] : /* istanbul ignore next */ []));
1265
+ serializedDocument = computed(() => JSON.stringify(serializeCrudAuthoringDocument(this.currentDocument()), null, 2), ...(ngDevMode ? [{ debugName: "serializedDocument" }] : /* istanbul ignore next */ []));
1266
+ tableConfig = computed(() => this.currentDocument().metadata.table || { columns: [] }, ...(ngDevMode ? [{ debugName: "tableConfig" }] : /* istanbul ignore next */ []));
1267
+ resourcePath = computed(() => this.currentDocument().metadata.resource?.path || '', ...(ngDevMode ? [{ debugName: "resourcePath" }] : /* istanbul ignore next */ []));
1268
+ resourceIdField = computed(() => String(this.currentDocument().metadata.resource?.idField ?? ''), ...(ngDevMode ? [{ debugName: "resourceIdField" }] : /* istanbul ignore next */ []));
1269
+ resourceEndpointKey = computed(() => String(this.currentDocument().metadata.resource?.endpointKey ?? ''), ...(ngDevMode ? [{ debugName: "resourceEndpointKey" }] : /* istanbul ignore next */ []));
1270
+ defaultsOpenMode = computed(() => this.currentDocument().metadata.defaults?.openMode || 'route', ...(ngDevMode ? [{ debugName: "defaultsOpenMode" }] : /* istanbul ignore next */ []));
1271
+ headerShowBack = computed(() => !!this.currentDocument().metadata.defaults?.header?.showBack, ...(ngDevMode ? [{ debugName: "headerShowBack" }] : /* istanbul ignore next */ []));
1272
+ headerBackLabel = computed(() => this.currentDocument().metadata.defaults?.header?.backLabel || '', ...(ngDevMode ? [{ debugName: "headerBackLabel" }] : /* istanbul ignore next */ []));
1273
+ headerVariant = computed(() => this.currentDocument().metadata.defaults?.header?.variant || 'ghost', ...(ngDevMode ? [{ debugName: "headerVariant" }] : /* istanbul ignore next */ []));
1274
+ headerSticky = computed(() => !!this.currentDocument().metadata.defaults?.header?.sticky, ...(ngDevMode ? [{ debugName: "headerSticky" }] : /* istanbul ignore next */ []));
1275
+ headerBreadcrumbs = computed(() => !!this.currentDocument().metadata.defaults?.header?.breadcrumbs, ...(ngDevMode ? [{ debugName: "headerBreadcrumbs" }] : /* istanbul ignore next */ []));
1276
+ headerDivider = computed(() => !!this.currentDocument().metadata.defaults?.header?.divider, ...(ngDevMode ? [{ debugName: "headerDivider" }] : /* istanbul ignore next */ []));
1277
+ modalDensity = computed(() => String(this.currentDocument().metadata.defaults?.modal?.density ?? 'default'), ...(ngDevMode ? [{ debugName: "modalDensity" }] : /* istanbul ignore next */ []));
1278
+ modalCanMaximize = computed(() => this.currentDocument().metadata.defaults?.modal?.canMaximize ?? true, ...(ngDevMode ? [{ debugName: "modalCanMaximize" }] : /* istanbul ignore next */ []));
1279
+ modalStartMaximized = computed(() => !!this.currentDocument().metadata.defaults?.modal?.startMaximized, ...(ngDevMode ? [{ debugName: "modalStartMaximized" }] : /* istanbul ignore next */ []));
1280
+ modalRememberLastState = computed(() => !!this.currentDocument().metadata.defaults?.modal?.rememberLastState, ...(ngDevMode ? [{ debugName: "modalRememberLastState" }] : /* istanbul ignore next */ []));
1281
+ modalDisableCloseOnEsc = computed(() => !!this.currentDocument().metadata.defaults?.modal?.disableCloseOnEsc, ...(ngDevMode ? [{ debugName: "modalDisableCloseOnEsc" }] : /* istanbul ignore next */ []));
1282
+ modalDisableCloseOnBackdrop = computed(() => !!this.currentDocument().metadata.defaults?.modal?.disableCloseOnBackdrop, ...(ngDevMode ? [{ debugName: "modalDisableCloseOnBackdrop" }] : /* istanbul ignore next */ []));
1283
+ modalFullscreenBreakpoint = computed(() => String(this.currentDocument().metadata.defaults?.modal?.fullscreenBreakpoint ?? ''), ...(ngDevMode ? [{ debugName: "modalFullscreenBreakpoint" }] : /* istanbul ignore next */ []));
1284
+ backStrategy = computed(() => String(this.currentDocument().metadata.defaults?.back?.strategy ?? 'auto'), ...(ngDevMode ? [{ debugName: "backStrategy" }] : /* istanbul ignore next */ []));
1285
+ backReturnTo = computed(() => this.currentDocument().metadata.defaults?.back?.returnTo || '', ...(ngDevMode ? [{ debugName: "backReturnTo" }] : /* istanbul ignore next */ []));
1286
+ backConfirmOnDirty = computed(() => !!this.currentDocument().metadata.defaults?.back?.confirmOnDirty, ...(ngDevMode ? [{ debugName: "backConfirmOnDirty" }] : /* istanbul ignore next */ []));
1286
1287
  nextFocusSection = computed(() => {
1287
1288
  for (const section of ['connection', 'defaults', 'actions', 'table']) {
1288
1289
  if (this.sectionStatus(section) === 'invalid') {
@@ -1295,8 +1296,8 @@ class CrudMetadataEditorComponent {
1295
1296
  }
1296
1297
  }
1297
1298
  return 'connection';
1298
- }, ...(ngDevMode ? [{ debugName: "nextFocusSection" }] : []));
1299
- nextFocusStatus = computed(() => this.sectionStatus(this.nextFocusSection()), ...(ngDevMode ? [{ debugName: "nextFocusStatus" }] : []));
1299
+ }, ...(ngDevMode ? [{ debugName: "nextFocusSection" }] : /* istanbul ignore next */ []));
1300
+ nextFocusStatus = computed(() => this.sectionStatus(this.nextFocusSection()), ...(ngDevMode ? [{ debugName: "nextFocusStatus" }] : /* istanbul ignore next */ []));
1300
1301
  visibleHealthBuckets = computed(() => {
1301
1302
  const attentionBuckets = ['invalid', 'pending']
1302
1303
  .filter((bucket) => this.hasHealthBucketContent(bucket));
@@ -1304,8 +1305,8 @@ class CrudMetadataEditorComponent {
1304
1305
  return attentionBuckets;
1305
1306
  }
1306
1307
  return this.hasHealthBucketContent('ready') ? ['ready'] : [];
1307
- }, ...(ngDevMode ? [{ debugName: "visibleHealthBuckets" }] : []));
1308
- showHealthMap = computed(() => this.visibleHealthBuckets().length > 1, ...(ngDevMode ? [{ debugName: "showHealthMap" }] : []));
1308
+ }, ...(ngDevMode ? [{ debugName: "visibleHealthBuckets" }] : /* istanbul ignore next */ []));
1309
+ showHealthMap = computed(() => this.visibleHealthBuckets().length > 1, ...(ngDevMode ? [{ debugName: "showHealthMap" }] : /* istanbul ignore next */ []));
1309
1310
  groupedDiagnostics = computed(() => ['connection', 'defaults', 'actions', 'table']
1310
1311
  .map((section) => {
1311
1312
  const issues = this.sectionDiagnostics(section);
@@ -1315,7 +1316,7 @@ class CrudMetadataEditorComponent {
1315
1316
  hasError: issues.some((issue) => issue.level === 'error'),
1316
1317
  };
1317
1318
  })
1318
- .filter((group) => group.issues.length > 0), ...(ngDevMode ? [{ debugName: "groupedDiagnostics" }] : []));
1319
+ .filter((group) => group.issues.length > 0), ...(ngDevMode ? [{ debugName: "groupedDiagnostics" }] : /* istanbul ignore next */ []));
1319
1320
  constructor() {
1320
1321
  effect(() => {
1321
1322
  const seed = this.resolveExternalSeed();
@@ -2307,8 +2308,8 @@ class CrudMetadataEditorComponent {
2307
2308
  table: this.tx('crud.authoring.section.table', 'Table'),
2308
2309
  }[section];
2309
2310
  }
2310
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudMetadataEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2311
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: CrudMetadataEditorComponent, isStandalone: true, selector: "praxis-crud-metadata-editor", inputs: { documentInput: { classPropertyName: "documentInput", publicName: "document", isSignal: true, isRequired: false, transformFunction: null }, metadataInput: { classPropertyName: "metadataInput", publicName: "metadata", isSignal: true, isRequired: false, transformFunction: null }, crudIdInput: { classPropertyName: "crudIdInput", publicName: "crudId", isSignal: true, isRequired: false, transformFunction: null }, readonlyInput: { classPropertyName: "readonlyInput", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null } }, providers: [providePraxisI18nConfig(PRAXIS_CRUD_AUTHORING_I18N_CONFIG)], ngImport: i0, template: `
2311
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudMetadataEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2312
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: CrudMetadataEditorComponent, isStandalone: true, selector: "praxis-crud-metadata-editor", inputs: { documentInput: { classPropertyName: "documentInput", publicName: "document", isSignal: true, isRequired: false, transformFunction: null }, metadataInput: { classPropertyName: "metadataInput", publicName: "metadata", isSignal: true, isRequired: false, transformFunction: null }, crudIdInput: { classPropertyName: "crudIdInput", publicName: "crudId", isSignal: true, isRequired: false, transformFunction: null }, readonlyInput: { classPropertyName: "readonlyInput", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null } }, providers: [providePraxisI18nConfig(PRAXIS_CRUD_AUTHORING_I18N_CONFIG)], ngImport: i0, template: `
2312
2313
  <section class="editor-shell" data-testid="crud-metadata-editor">
2313
2314
  <header class="editor-header">
2314
2315
  <div>
@@ -2783,12 +2784,11 @@ class CrudMetadataEditorComponent {
2783
2784
  <pre data-testid="crud-editor-json">{{ serializedDocument() }}</pre>
2784
2785
  </mat-card>
2785
2786
  </section>
2786
- `, isInline: true, styles: [":host{display:block;min-width:0;color:var(--md-sys-color-on-surface,#1a1b20)}.editor-shell{display:grid;gap:16px}.editor-header{display:flex;justify-content:space-between;gap:16px;align-items:start}.editor-overview,.editor-health-map{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.editor-health-card{display:grid;gap:4px;padding:14px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-health-card--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-health-card--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-focus{display:flex;gap:12px;align-items:start;padding:14px 16px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-focus--success{background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent)}.editor-focus-chip{padding:6px 10px;border-radius:999px;font-size:12px;background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent);white-space:nowrap}.editor-focus-chip--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-focus-chip--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-focus-copy{display:grid;gap:4px}.editor-focus-copy p{margin:0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-overview-card{display:grid;gap:4px;padding:14px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-overview-card--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-overview-card--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-overview-label{font-size:12px;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-overview-value{line-height:1.3;overflow-wrap:anywhere}.editor-overview-note{margin:0;font-size:12px;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-header h2,.editor-card h3{margin:0}.editor-header p{margin:6px 0 0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-chip{padding:6px 10px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary,#1263b4) 12%,transparent);font-size:12px}.editor-card{display:grid;gap:14px;padding:16px;border-radius:20px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline,#c5c7ce) 70%,transparent)}.editor-section-header{display:flex;justify-content:space-between;gap:12px;align-items:start}.editor-grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-grid--compact{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.editor-toggles{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-actions,.action-grid{display:grid;gap:14px}.action-summary-strip{display:flex;flex-wrap:wrap;gap:8px}.action-summary-chip{padding:6px 10px;border-radius:999px;font-size:12px;background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent)}.action-summary-chip--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.action-summary-chip--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.action-group{display:grid;gap:10px;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 88%,transparent)}.action-group--advanced{background:color-mix(in srgb,var(--md-sys-color-surface-container,#f5f7fb) 90%,transparent)}.action-group__header{display:grid;gap:4px}.action-subgroup{display:grid;gap:10px;padding:10px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low,#fafbfd) 92%,transparent)}.action-subgroup__header{display:grid;gap:4px}.action-param-list{display:grid;gap:12px}.action-param-row{display:grid;gap:8px}.action-advanced-panel{border:1px solid color-mix(in srgb,var(--md-sys-color-outline,#c5c7ce) 65%,transparent);border-radius:16px}.editor-section-note{margin:0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.diagnostics-groups{display:grid;gap:12px}.diagnostics-group{display:grid;gap:10px;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.diagnostics{display:grid;gap:8px;margin:0;padding-left:18px}.diagnostics .error{color:var(--md-sys-color-error,#b3261e)}pre{margin:0;overflow:auto;max-height:320px;padding:12px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i3.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i4.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i4.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i4.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i4.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i7.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i8.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: PraxisTableInlineAuthoringEditorComponent, selector: "praxis-table-inline-authoring-editor", inputs: ["config", "readonly"], outputs: ["configChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2787
+ `, isInline: true, styles: [":host{display:block;min-width:0;color:var(--md-sys-color-on-surface,#1a1b20)}.editor-shell{display:grid;gap:16px}.editor-header{display:flex;justify-content:space-between;gap:16px;align-items:start}.editor-overview,.editor-health-map{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.editor-health-card{display:grid;gap:4px;padding:14px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-health-card--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-health-card--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-focus{display:flex;gap:12px;align-items:start;padding:14px 16px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-focus--success{background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent)}.editor-focus-chip{padding:6px 10px;border-radius:999px;font-size:12px;background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent);white-space:nowrap}.editor-focus-chip--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-focus-chip--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-focus-copy{display:grid;gap:4px}.editor-focus-copy p{margin:0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-overview-card{display:grid;gap:4px;padding:14px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-overview-card--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-overview-card--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-overview-label{font-size:12px;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-overview-value{line-height:1.3;overflow-wrap:anywhere}.editor-overview-note{margin:0;font-size:12px;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-header h2,.editor-card h3{margin:0}.editor-header p{margin:6px 0 0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-chip{padding:6px 10px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary,#1263b4) 12%,transparent);font-size:12px}.editor-card{display:grid;gap:14px;padding:16px;border-radius:20px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline,#c5c7ce) 70%,transparent)}.editor-section-header{display:flex;justify-content:space-between;gap:12px;align-items:start}.editor-grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-grid--compact{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.editor-toggles{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-actions,.action-grid{display:grid;gap:14px}.action-summary-strip{display:flex;flex-wrap:wrap;gap:8px}.action-summary-chip{padding:6px 10px;border-radius:999px;font-size:12px;background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent)}.action-summary-chip--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.action-summary-chip--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.action-group{display:grid;gap:10px;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 88%,transparent)}.action-group--advanced{background:color-mix(in srgb,var(--md-sys-color-surface-container,#f5f7fb) 90%,transparent)}.action-group__header{display:grid;gap:4px}.action-subgroup{display:grid;gap:10px;padding:10px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low,#fafbfd) 92%,transparent)}.action-subgroup__header{display:grid;gap:4px}.action-param-list{display:grid;gap:12px}.action-param-row{display:grid;gap:8px}.action-advanced-panel{border:1px solid color-mix(in srgb,var(--md-sys-color-outline,#c5c7ce) 65%,transparent);border-radius:16px}.editor-section-note{margin:0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.diagnostics-groups{display:grid;gap:12px}.diagnostics-group{display:grid;gap:10px;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.diagnostics{display:grid;gap:8px;margin:0;padding-left:18px}.diagnostics .error{color:var(--md-sys-color-error,#b3261e)}pre{margin:0;overflow:auto;max-height:320px;padding:12px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i3.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i4.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i4.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i4.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i4.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i7.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i8.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: PraxisTableInlineAuthoringEditorComponent, selector: "praxis-table-inline-authoring-editor", inputs: ["config", "readonly"], outputs: ["configChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2787
2788
  }
2788
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudMetadataEditorComponent, decorators: [{
2789
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudMetadataEditorComponent, decorators: [{
2789
2790
  type: Component,
2790
2791
  args: [{ selector: 'praxis-crud-metadata-editor', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
2791
- CommonModule,
2792
2792
  FormsModule,
2793
2793
  MatButtonModule,
2794
2794
  MatCardModule,
@@ -2797,7 +2797,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2797
2797
  MatInputModule,
2798
2798
  MatSelectModule,
2799
2799
  MatSlideToggleModule,
2800
- PraxisTableInlineAuthoringEditorComponent,
2800
+ PraxisTableInlineAuthoringEditorComponent
2801
2801
  ], providers: [providePraxisI18nConfig(PRAXIS_CRUD_AUTHORING_I18N_CONFIG)], template: `
2802
2802
  <section class="editor-shell" data-testid="crud-metadata-editor">
2803
2803
  <header class="editor-header">
@@ -3308,6 +3308,8 @@ class PraxisCrudComponent {
3308
3308
  afterSave = new EventEmitter();
3309
3309
  afterDelete = new EventEmitter();
3310
3310
  error = new EventEmitter();
3311
+ rowClick = new EventEmitter();
3312
+ selectionChange = new EventEmitter();
3311
3313
  tableRuntimeConfigChange = new EventEmitter();
3312
3314
  crudAuthoringDocumentApplied = new EventEmitter();
3313
3315
  crudAuthoringDocumentSaved = new EventEmitter();
@@ -3327,9 +3329,10 @@ class PraxisCrudComponent {
3327
3329
  snack = inject(MatSnackBar);
3328
3330
  dialog = inject(DialogService);
3329
3331
  i18n = inject(PraxisI18nService);
3330
- resourceDiscovery = inject(ResourceDiscoveryService);
3331
- actionOpenAdapter = inject(ResourceActionOpenAdapterService);
3332
- surfaceOpenAdapter = inject(ResourceSurfaceOpenAdapterService);
3332
+ injector = inject(Injector);
3333
+ resourceDiscoveryInstance;
3334
+ actionOpenAdapterInstance;
3335
+ surfaceOpenAdapterInstance;
3333
3336
  global = (() => {
3334
3337
  try {
3335
3338
  return inject(GlobalConfigService);
@@ -3359,6 +3362,18 @@ class PraxisCrudComponent {
3359
3362
  collectionCapabilitiesResolvedHref = null;
3360
3363
  collectionCapabilitiesRequestSeq = 0;
3361
3364
  currentAuthoringDocument;
3365
+ getResourceDiscovery() {
3366
+ const assigned = this.resourceDiscovery;
3367
+ return assigned ?? (this.resourceDiscoveryInstance ??= this.injector.get(ResourceDiscoveryService));
3368
+ }
3369
+ getActionOpenAdapter() {
3370
+ const assigned = this.actionOpenAdapter;
3371
+ return assigned ?? (this.actionOpenAdapterInstance ??= this.injector.get(ResourceActionOpenAdapterService));
3372
+ }
3373
+ getSurfaceOpenAdapter() {
3374
+ const assigned = this.surfaceOpenAdapter;
3375
+ return assigned ?? (this.surfaceOpenAdapterInstance ??= this.injector.get(ResourceSurfaceOpenAdapterService));
3376
+ }
3362
3377
  onResetPreferences() {
3363
3378
  try {
3364
3379
  const keyId = this.componentKeyId();
@@ -3372,6 +3387,12 @@ class PraxisCrudComponent {
3372
3387
  }
3373
3388
  catch { }
3374
3389
  }
3390
+ onTableRowClick(event) {
3391
+ this.rowClick.emit(event);
3392
+ }
3393
+ onTableSelectionChange(event) {
3394
+ this.selectionChange.emit(event);
3395
+ }
3375
3396
  ngOnChanges(changes) {
3376
3397
  if (!changes['metadata'] && !changes['context']) {
3377
3398
  return;
@@ -3409,14 +3430,6 @@ class PraxisCrudComponent {
3409
3430
  async onAction(action, row, runtimeEvent) {
3410
3431
  try {
3411
3432
  document.activeElement?.blur();
3412
- const openedByDiscovery = await this.tryOpenDiscoveredCrudSurface(action, row);
3413
- if (openedByDiscovery) {
3414
- return;
3415
- }
3416
- const handledByWorkflowAction = await this.tryOpenDiscoveredWorkflowAction(action, row, runtimeEvent);
3417
- if (handledByWorkflowAction) {
3418
- return;
3419
- }
3420
3433
  let actionMeta = this.resolvedMetadata.actions?.find((candidate) => candidate.action === action);
3421
3434
  if (!actionMeta) {
3422
3435
  const ctxAction = this.tableCrudContext?.actions?.find((candidate) => candidate.action === action);
@@ -3433,6 +3446,16 @@ class PraxisCrudComponent {
3433
3446
  }
3434
3447
  }
3435
3448
  const effectiveAction = (actionMeta || { action });
3449
+ if (!this.hasExplicitOpenBinding(effectiveAction)) {
3450
+ const openedByDiscovery = await this.tryOpenDiscoveredCrudSurface(action, row, runtimeEvent);
3451
+ if (openedByDiscovery) {
3452
+ return;
3453
+ }
3454
+ const handledByWorkflowAction = await this.tryOpenDiscoveredWorkflowAction(action, row, runtimeEvent);
3455
+ if (handledByWorkflowAction) {
3456
+ return;
3457
+ }
3458
+ }
3436
3459
  const handledByDelete = await this.tryHandleCanonicalDeleteAction(effectiveAction, row);
3437
3460
  if (handledByDelete) {
3438
3461
  return;
@@ -3495,6 +3518,19 @@ class PraxisCrudComponent {
3495
3518
  this.error.emit(err);
3496
3519
  }
3497
3520
  }
3521
+ hasExplicitOpenBinding(action) {
3522
+ const mode = action.openMode;
3523
+ if (mode === 'route') {
3524
+ return !!String(action.route || '').trim();
3525
+ }
3526
+ if (mode === 'modal' || mode === 'drawer') {
3527
+ return !!String(action.formId || '').trim();
3528
+ }
3529
+ return !!(String(action.route || '').trim() ||
3530
+ String(action.formId || '').trim() ||
3531
+ String(action.form?.schemaUrl || '').trim() ||
3532
+ String(action.form?.submitUrl || '').trim());
3533
+ }
3498
3534
  getCurrentTableConfigSnapshot() {
3499
3535
  const current = this.table?.config || this.tableConfigForBinding || this.effectiveTableConfig;
3500
3536
  if (!current)
@@ -3537,7 +3573,7 @@ class PraxisCrudComponent {
3537
3573
  }
3538
3574
  const requestSeq = ++this.collectionCapabilitiesRequestSeq;
3539
3575
  this.collectionCapabilitiesRequestHref = capabilitiesHref;
3540
- void firstValueFrom(this.resourceDiscovery.getCapabilities(links || {}, this.buildDiscoveryOptions()))
3576
+ void firstValueFrom(this.getResourceDiscovery().getCapabilities(links || {}, this.buildDiscoveryOptions()))
3541
3577
  .then((snapshot) => {
3542
3578
  if (requestSeq !== this.collectionCapabilitiesRequestSeq) {
3543
3579
  return;
@@ -3591,6 +3627,7 @@ class PraxisCrudComponent {
3591
3627
  }
3592
3628
  onConfigureRequested() {
3593
3629
  this.configureRequested.emit();
3630
+ this.openCrudAuthoringFromTable();
3594
3631
  }
3595
3632
  async tryHandleCanonicalDeleteAction(action, row) {
3596
3633
  const normalizedAction = String(action.action || '').trim().toLowerCase();
@@ -3704,25 +3741,35 @@ class PraxisCrudComponent {
3704
3741
  resolveFilterCriteria(meta) {
3705
3742
  return this.isRecord(meta?.filterCriteria) ? { ...meta.filterCriteria } : {};
3706
3743
  }
3707
- async tryOpenDiscoveredCrudSurface(action, row) {
3744
+ async tryOpenDiscoveredCrudSurface(action, row, runtimeEvent) {
3708
3745
  const normalizedAction = String(action || '').trim().toLowerCase();
3709
- if (!this.isDiscoveryManagedCrudAction(normalizedAction) || !this.surfaceService) {
3746
+ if (!this.surfaceService) {
3710
3747
  return false;
3711
3748
  }
3712
- const catalog = await this.resolveDiscoveredSurfaceCatalog(normalizedAction, row);
3713
- const surface = this.selectSurfaceForCrudAction(normalizedAction, catalog?.surfaces || []);
3749
+ const providedSurface = this.resolveProvidedSurface(normalizedAction, runtimeEvent?.actionConfig);
3750
+ if (!providedSurface && !this.isDiscoveryManagedCrudAction(normalizedAction)) {
3751
+ return false;
3752
+ }
3753
+ const catalog = providedSurface
3754
+ ? null
3755
+ : await this.resolveDiscoveredSurfaceCatalog(normalizedAction, row);
3756
+ const surface = providedSurface || this.selectSurfaceForCrudAction(normalizedAction, catalog?.surfaces || []);
3714
3757
  const resourcePath = String(catalog?.resourcePath || this.resolveResourcePath(this.resolvedMetadata) || '').trim();
3715
- if (!catalog || !surface || !resourcePath) {
3758
+ if (!surface || !resourcePath) {
3716
3759
  return false;
3717
3760
  }
3761
+ if (surface.availability?.allowed === false) {
3762
+ this.snack.open(translateUnavailableWorkflowMessage(this.i18n, surface.availability), undefined, { duration: 2500 });
3763
+ return true;
3764
+ }
3718
3765
  let payload;
3719
3766
  try {
3720
- payload = this.surfaceOpenAdapter.toPayload(surface, {
3767
+ payload = this.getSurfaceOpenAdapter().toPayload(surface, {
3721
3768
  resourcePath,
3722
3769
  resourceId: this.resolveRowResourceId(row),
3723
3770
  endpointKey: this.resolvedMetadata?.resource?.endpointKey,
3724
3771
  apiUrlEntry: this.resolveDiscoveryApiEntry(),
3725
- group: catalog.group ?? null,
3772
+ group: catalog?.group ?? null,
3726
3773
  });
3727
3774
  }
3728
3775
  catch {
@@ -3767,13 +3814,13 @@ class PraxisCrudComponent {
3767
3814
  if (!this.tableCollectionLinks) {
3768
3815
  return null;
3769
3816
  }
3770
- return await firstValueFrom(this.resourceDiscovery.getSurfaces(this.tableCollectionLinks, this.buildDiscoveryOptions()));
3817
+ return await firstValueFrom(this.getResourceDiscovery().getSurfaces(this.tableCollectionLinks, this.buildDiscoveryOptions()));
3771
3818
  }
3772
3819
  const rowLinks = row?._links;
3773
3820
  if (!rowLinks) {
3774
3821
  return null;
3775
3822
  }
3776
- return await firstValueFrom(this.resourceDiscovery.getSurfaces(rowLinks, this.buildDiscoveryOptions()));
3823
+ return await firstValueFrom(this.getResourceDiscovery().getSurfaces(rowLinks, this.buildDiscoveryOptions()));
3777
3824
  }
3778
3825
  catch {
3779
3826
  return null;
@@ -3797,7 +3844,7 @@ class PraxisCrudComponent {
3797
3844
  }
3798
3845
  let payload;
3799
3846
  try {
3800
- payload = this.actionOpenAdapter.toPayload(discoveredAction, {
3847
+ payload = this.getActionOpenAdapter().toPayload(discoveredAction, {
3801
3848
  resourcePath,
3802
3849
  resourceId: this.resolveRowResourceId(row),
3803
3850
  endpointKey: this.resolvedMetadata?.resource?.endpointKey,
@@ -3848,12 +3895,12 @@ class PraxisCrudComponent {
3848
3895
  if (!rowLinks) {
3849
3896
  return null;
3850
3897
  }
3851
- return await firstValueFrom(this.resourceDiscovery.getActions(rowLinks, this.buildDiscoveryOptions()));
3898
+ return await firstValueFrom(this.getResourceDiscovery().getActions(rowLinks, this.buildDiscoveryOptions()));
3852
3899
  }
3853
3900
  if (!this.tableCollectionLinks) {
3854
3901
  return null;
3855
3902
  }
3856
- return await firstValueFrom(this.resourceDiscovery.getActions(this.tableCollectionLinks, this.buildDiscoveryOptions()));
3903
+ return await firstValueFrom(this.getResourceDiscovery().getActions(this.tableCollectionLinks, this.buildDiscoveryOptions()));
3857
3904
  }
3858
3905
  catch {
3859
3906
  return null;
@@ -3880,6 +3927,23 @@ class PraxisCrudComponent {
3880
3927
  }
3881
3928
  return candidate;
3882
3929
  }
3930
+ resolveProvidedSurface(action, candidate) {
3931
+ if (!candidate || typeof candidate !== 'object') {
3932
+ return null;
3933
+ }
3934
+ const normalizedId = String(candidate.id || '').trim().toLowerCase();
3935
+ if (!normalizedId || normalizedId !== action) {
3936
+ return null;
3937
+ }
3938
+ const surface = candidate;
3939
+ if (!surface.kind || !surface.scope || !surface.path || !surface.method) {
3940
+ return null;
3941
+ }
3942
+ if (!this.isWritableCrudSurface(surface) && !this.isReadableCrudSurface(surface)) {
3943
+ return null;
3944
+ }
3945
+ return surface;
3946
+ }
3883
3947
  selectSurfaceForCrudAction(action, surfaces) {
3884
3948
  const candidates = surfaces
3885
3949
  .filter((surface) => surface.availability?.allowed !== false)
@@ -4300,7 +4364,7 @@ class PraxisCrudComponent {
4300
4364
  }
4301
4365
  resolveCollectionCapabilitiesHref(links) {
4302
4366
  try {
4303
- return this.resourceDiscovery.resolveLinkHref(links || undefined, 'capabilities', this.buildDiscoveryOptions());
4367
+ return this.getResourceDiscovery().resolveLinkHref(links || undefined, 'capabilities', this.buildDiscoveryOptions());
4304
4368
  }
4305
4369
  catch {
4306
4370
  return null;
@@ -4320,7 +4384,7 @@ class PraxisCrudComponent {
4320
4384
  }
4321
4385
  resolveDiscoveryApiEntry() {
4322
4386
  try {
4323
- return this.resourceDiscovery.resolveApiEntry(this.buildDiscoveryOptions());
4387
+ return this.getResourceDiscovery().resolveApiEntry(this.buildDiscoveryOptions());
4324
4388
  }
4325
4389
  catch {
4326
4390
  return null;
@@ -4380,8 +4444,8 @@ class PraxisCrudComponent {
4380
4444
  return fallback;
4381
4445
  }
4382
4446
  }
4383
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisCrudComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4384
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisCrudComponent, isStandalone: true, selector: "praxis-crud", inputs: { metadata: "metadata", crudId: "crudId", componentInstanceId: "componentInstanceId", context: "context", enableCustomization: "enableCustomization" }, outputs: { configureRequested: "configureRequested", afterOpen: "afterOpen", afterClose: "afterClose", afterSave: "afterSave", afterDelete: "afterDelete", error: "error", tableRuntimeConfigChange: "tableRuntimeConfigChange", crudAuthoringDocumentApplied: "crudAuthoringDocumentApplied", crudAuthoringDocumentSaved: "crudAuthoringDocumentSaved" }, providers: [
4447
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4448
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisCrudComponent, isStandalone: true, selector: "praxis-crud", inputs: { metadata: "metadata", crudId: "crudId", componentInstanceId: "componentInstanceId", context: "context", enableCustomization: "enableCustomization" }, outputs: { configureRequested: "configureRequested", afterOpen: "afterOpen", afterClose: "afterClose", afterSave: "afterSave", afterDelete: "afterDelete", error: "error", rowClick: "rowClick", selectionChange: "selectionChange", tableRuntimeConfigChange: "tableRuntimeConfigChange", crudAuthoringDocumentApplied: "crudAuthoringDocumentApplied", crudAuthoringDocumentSaved: "crudAuthoringDocumentSaved" }, providers: [
4385
4449
  providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
4386
4450
  providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG),
4387
4451
  ], viewQueries: [{ propertyName: "table", first: true, predicate: PraxisTable, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
@@ -4395,6 +4459,8 @@ class PraxisCrudComponent {
4395
4459
  [tableId]="crudId || 'default'"
4396
4460
  [crudContext]="tableCrudContext"
4397
4461
  [enableCustomization]="enableCustomization"
4462
+ (rowClick)="onTableRowClick($event)"
4463
+ (selectionChange)="onTableSelectionChange($event)"
4398
4464
  (rowAction)="onAction($event.action, $event.row, $event)"
4399
4465
  (toolbarAction)="onAction($event.action)"
4400
4466
  (collectionLinksChange)="onCollectionLinksChange($event)"
@@ -4412,9 +4478,9 @@ class PraxisCrudComponent {
4412
4478
  />
4413
4479
  }
4414
4480
  }
4415
- `, isInline: true, styles: [":host{display:block;width:100%;min-width:0;max-width:100%}\n"], dependencies: [{ kind: "component", type: PraxisTable, selector: "praxis-table", inputs: ["config", "resourcePath", "data", "tableId", "componentInstanceId", "title", "subtitle", "icon", "autoDelete", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "crudContext", "filterCriteria", "queryContext", "enableCustomization", "dense"], outputs: ["rowClick", "rowDoubleClick", "rowExpansionChange", "rowAction", "toolbarAction", "bulkAction", "columnReorder", "columnReorderAttempt", "beforeDelete", "afterDelete", "deleteError", "beforeBulkDelete", "afterBulkDelete", "bulkDeleteError", "schemaStatusChange", "metadataChange", "loadingStateChange", "collectionLinksChange", "selectionChange"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone"] }] });
4481
+ `, 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"] }] });
4416
4482
  }
4417
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisCrudComponent, decorators: [{
4483
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudComponent, decorators: [{
4418
4484
  type: Component,
4419
4485
  args: [{ selector: 'praxis-crud', standalone: true, imports: [PraxisTable, EmptyStateCardComponent], providers: [
4420
4486
  providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
@@ -4430,6 +4496,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
4430
4496
  [tableId]="crudId || 'default'"
4431
4497
  [crudContext]="tableCrudContext"
4432
4498
  [enableCustomization]="enableCustomization"
4499
+ (rowClick)="onTableRowClick($event)"
4500
+ (selectionChange)="onTableSelectionChange($event)"
4433
4501
  (rowAction)="onAction($event.action, $event.row, $event)"
4434
4502
  (toolbarAction)="onAction($event.action)"
4435
4503
  (collectionLinksChange)="onCollectionLinksChange($event)"
@@ -4472,6 +4540,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
4472
4540
  type: Output
4473
4541
  }], error: [{
4474
4542
  type: Output
4543
+ }], rowClick: [{
4544
+ type: Output
4545
+ }], selectionChange: [{
4546
+ type: Output
4475
4547
  }], tableRuntimeConfigChange: [{
4476
4548
  type: Output
4477
4549
  }], crudAuthoringDocumentApplied: [{
@@ -4780,8 +4852,8 @@ class DynamicFormDialogHostComponent {
4780
4852
  this.dialogRef.updatePosition();
4781
4853
  }
4782
4854
  }
4783
- 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 });
4784
- 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: `
4855
+ 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 });
4856
+ 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: `
4785
4857
  <div mat-dialog-title class="dialog-header">
4786
4858
  <h2 id="crudDialogTitle" class="dialog-title">
4787
4859
  {{ data.action?.label || texts.title }}
@@ -4830,17 +4902,16 @@ class DynamicFormDialogHostComponent {
4830
4902
  (formCancel)="onCancel()"
4831
4903
  ></praxis-dynamic-form>
4832
4904
  </mat-dialog-content>
4833
- `, 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"] }] });
4905
+ `, 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", "submitUrl", "submitMethod", "responseSchemaUrl", "apiEndpointKey", "apiUrlEntry", "enableCustomization", "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"] }] });
4834
4906
  }
4835
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
4907
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
4836
4908
  type: Component,
4837
4909
  args: [{ selector: 'praxis-dynamic-form-dialog-host', standalone: true, imports: [
4838
- CommonModule,
4839
4910
  MatDialogModule,
4840
4911
  MatButtonModule,
4841
4912
  MatIconModule,
4842
4913
  PraxisIconDirective,
4843
- PraxisDynamicForm,
4914
+ PraxisDynamicForm
4844
4915
  ], providers: [GenericCrudService], host: {
4845
4916
  class: 'praxis-dialog',
4846
4917
  '[attr.data-density]': 'modal.density || "default"',
@@ -4936,6 +5007,102 @@ var dynamicFormDialogHost_component = /*#__PURE__*/Object.freeze({
4936
5007
  DynamicFormDialogHostComponent: DynamicFormDialogHostComponent
4937
5008
  });
4938
5009
 
5010
+ class PraxisCrudWidgetConfigEditor {
5011
+ inputs = null;
5012
+ widgetKey;
5013
+ crudEditor;
5014
+ isDirty$ = new BehaviorSubject(false);
5015
+ isValid$ = new BehaviorSubject(true);
5016
+ isBusy$ = new BehaviorSubject(false);
5017
+ subscription = new Subscription();
5018
+ ngAfterViewInit() {
5019
+ if (!this.crudEditor) {
5020
+ return;
5021
+ }
5022
+ this.subscription.add(this.crudEditor.isDirty$.subscribe((value) => this.isDirty$.next(value)));
5023
+ this.subscription.add(this.crudEditor.isValid$.subscribe((value) => this.isValid$.next(value)));
5024
+ this.subscription.add(this.crudEditor.isBusy$.subscribe((value) => this.isBusy$.next(value)));
5025
+ }
5026
+ ngOnDestroy() {
5027
+ this.subscription.unsubscribe();
5028
+ }
5029
+ get metadata() {
5030
+ return this.createDocumentFromInputs().metadata;
5031
+ }
5032
+ get effectiveCrudId() {
5033
+ return this.inputs?.crudId ?? this.widgetKey ?? null;
5034
+ }
5035
+ getSettingsValue() {
5036
+ return this.buildValue(this.crudEditor?.getSettingsValue());
5037
+ }
5038
+ onSave() {
5039
+ return this.buildValue(this.crudEditor?.onSave?.() ?? this.crudEditor?.getSettingsValue());
5040
+ }
5041
+ reset() {
5042
+ this.crudEditor?.reset?.();
5043
+ }
5044
+ buildValue(rawPayload) {
5045
+ const document = this.extractDocument(rawPayload);
5046
+ return {
5047
+ inputs: {
5048
+ ...(this.inputs ?? {}),
5049
+ metadata: document.metadata,
5050
+ crudId: this.inputs?.crudId ?? this.widgetKey,
5051
+ componentInstanceId: this.inputs?.componentInstanceId ?? this.widgetKey,
5052
+ context: this.inputs?.context,
5053
+ enableCustomization: this.inputs?.enableCustomization ?? false,
5054
+ },
5055
+ };
5056
+ }
5057
+ createDocumentFromInputs() {
5058
+ return parseLegacyOrCrudDocument(this.inputs?.metadata ?? {
5059
+ component: 'praxis-crud',
5060
+ table: { columns: [] },
5061
+ });
5062
+ }
5063
+ extractDocument(rawPayload) {
5064
+ const payload = rawPayload;
5065
+ return parseLegacyOrCrudDocument(payload?.document ??
5066
+ payload?.metadata ??
5067
+ serializeCrudAuthoringDocument(this.createDocumentFromInputs()));
5068
+ }
5069
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudWidgetConfigEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
5070
+ 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: `
5071
+ <section data-testid="crud-widget-config-editor">
5072
+ <praxis-crud-metadata-editor
5073
+ #crudEditor
5074
+ [metadata]="metadata"
5075
+ [crudId]="effectiveCrudId"
5076
+ />
5077
+ </section>
5078
+ `, isInline: true, dependencies: [{ kind: "component", type: CrudMetadataEditorComponent, selector: "praxis-crud-metadata-editor", inputs: ["document", "metadata", "crudId", "readonly"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5079
+ }
5080
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudWidgetConfigEditor, decorators: [{
5081
+ type: Component,
5082
+ args: [{
5083
+ selector: 'praxis-crud-widget-config-editor',
5084
+ standalone: true,
5085
+ imports: [CrudMetadataEditorComponent],
5086
+ template: `
5087
+ <section data-testid="crud-widget-config-editor">
5088
+ <praxis-crud-metadata-editor
5089
+ #crudEditor
5090
+ [metadata]="metadata"
5091
+ [crudId]="effectiveCrudId"
5092
+ />
5093
+ </section>
5094
+ `,
5095
+ changeDetection: ChangeDetectionStrategy.OnPush,
5096
+ }]
5097
+ }], propDecorators: { inputs: [{
5098
+ type: Input
5099
+ }], widgetKey: [{
5100
+ type: Input
5101
+ }], crudEditor: [{
5102
+ type: ViewChild,
5103
+ args: ['crudEditor']
5104
+ }] } });
5105
+
4939
5106
  /** Metadata for PraxisCrudComponent */
4940
5107
  const PRAXIS_CRUD_COMPONENT_METADATA = {
4941
5108
  id: 'praxis-crud',
@@ -4944,6 +5111,14 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
4944
5111
  friendlyName: 'Praxis CRUD',
4945
5112
  description: 'Tabela com operações de CRUD via metadados.',
4946
5113
  icon: 'table_chart',
5114
+ authoringManifestRef: {
5115
+ componentId: 'praxis-crud',
5116
+ source: 'PRAXIS_CRUD_AUTHORING_MANIFEST',
5117
+ },
5118
+ configEditor: {
5119
+ component: PraxisCrudWidgetConfigEditor,
5120
+ title: 'Configurar CRUD',
5121
+ },
4947
5122
  inputs: [
4948
5123
  {
4949
5124
  name: 'metadata',
@@ -5008,6 +5183,16 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
5008
5183
  type: '{ id: string | number }',
5009
5184
  description: 'Emitido ao deletar.',
5010
5185
  },
5186
+ {
5187
+ name: 'rowClick',
5188
+ type: '{ row: unknown; index: number }',
5189
+ description: 'Encaminha o clique de linha da tabela interna para composição master-detail e seleção local.',
5190
+ },
5191
+ {
5192
+ name: 'selectionChange',
5193
+ type: 'unknown',
5194
+ description: 'Encaminha a mudança de seleção da tabela interna quando o CRUD é usado como widget composto.',
5195
+ },
5011
5196
  {
5012
5197
  name: 'error',
5013
5198
  type: 'unknown',
@@ -5114,6 +5299,23 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
5114
5299
  },
5115
5300
  scope: 'shell',
5116
5301
  },
5302
+ {
5303
+ id: 'row-click',
5304
+ label: 'Clique em linha',
5305
+ icon: 'ads_click',
5306
+ description: 'Emite evento ao clicar em uma linha da tabela interna do CRUD.',
5307
+ emit: 'rowClick',
5308
+ payloadSchema: {
5309
+ type: 'object',
5310
+ properties: {
5311
+ row: { type: 'object', description: 'Registro clicado.' },
5312
+ index: { type: 'number', description: 'Índice da linha.' },
5313
+ },
5314
+ required: ['row'],
5315
+ example: { row: {}, index: 0 },
5316
+ },
5317
+ scope: 'context',
5318
+ },
5117
5319
  {
5118
5320
  id: 'close',
5119
5321
  label: 'Fechar',
@@ -5154,64 +5356,66 @@ class CrudPageHeaderComponent {
5154
5356
  sticky = true;
5155
5357
  divider = true;
5156
5358
  returnTo;
5157
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudPageHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5158
- 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: `
5359
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudPageHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5360
+ 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: `
5159
5361
  <header
5160
5362
  class="crud-header"
5161
5363
  [class.sticky]="sticky"
5162
5364
  [class.with-divider]="divider"
5163
- >
5365
+ >
5164
5366
  <div class="left">
5165
- <a
5166
- *ngIf="showBack && returnTo"
5167
- class="back-btn"
5168
- [class.ghost]="variant === 'ghost'"
5169
- [class.tonal]="variant === 'tonal'"
5170
- [class.outlined]="variant === 'outlined'"
5171
- [routerLink]="returnTo"
5172
- [attr.aria-label]="backLabel || 'Voltar'"
5173
- mat-button
5174
- >
5175
- <mat-icon [praxisIcon]="'arrow_back'"></mat-icon>
5176
- <span class="label" [class.hide-on-narrow]="true">{{ backLabel || 'Voltar' }}</span>
5177
- </a>
5367
+ @if (showBack && returnTo) {
5368
+ <a
5369
+ class="back-btn"
5370
+ [class.ghost]="variant === 'ghost'"
5371
+ [class.tonal]="variant === 'tonal'"
5372
+ [class.outlined]="variant === 'outlined'"
5373
+ [routerLink]="returnTo"
5374
+ [attr.aria-label]="backLabel || 'Voltar'"
5375
+ mat-button
5376
+ >
5377
+ <mat-icon [praxisIcon]="'arrow_back'"></mat-icon>
5378
+ <span class="label" [class.hide-on-narrow]="true">{{ backLabel || 'Voltar' }}</span>
5379
+ </a>
5380
+ }
5178
5381
  <h2 class="title" [attr.title]="title">{{ title }}</h2>
5179
5382
  </div>
5180
5383
  <div class="right">
5181
5384
  <ng-content></ng-content>
5182
5385
  </div>
5183
5386
  </header>
5184
- `, 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"] }] });
5387
+ `, 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"] }] });
5185
5388
  }
5186
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudPageHeaderComponent, decorators: [{
5389
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudPageHeaderComponent, decorators: [{
5187
5390
  type: Component,
5188
- args: [{ selector: 'praxis-crud-page-header', standalone: true, imports: [CommonModule, RouterLink, MatButtonModule, MatIconModule, PraxisIconDirective], template: `
5391
+ args: [{ selector: 'praxis-crud-page-header', standalone: true, imports: [RouterLink, MatButtonModule, MatIconModule, PraxisIconDirective], template: `
5189
5392
  <header
5190
5393
  class="crud-header"
5191
5394
  [class.sticky]="sticky"
5192
5395
  [class.with-divider]="divider"
5193
- >
5396
+ >
5194
5397
  <div class="left">
5195
- <a
5196
- *ngIf="showBack && returnTo"
5197
- class="back-btn"
5198
- [class.ghost]="variant === 'ghost'"
5199
- [class.tonal]="variant === 'tonal'"
5200
- [class.outlined]="variant === 'outlined'"
5201
- [routerLink]="returnTo"
5202
- [attr.aria-label]="backLabel || 'Voltar'"
5203
- mat-button
5204
- >
5205
- <mat-icon [praxisIcon]="'arrow_back'"></mat-icon>
5206
- <span class="label" [class.hide-on-narrow]="true">{{ backLabel || 'Voltar' }}</span>
5207
- </a>
5398
+ @if (showBack && returnTo) {
5399
+ <a
5400
+ class="back-btn"
5401
+ [class.ghost]="variant === 'ghost'"
5402
+ [class.tonal]="variant === 'tonal'"
5403
+ [class.outlined]="variant === 'outlined'"
5404
+ [routerLink]="returnTo"
5405
+ [attr.aria-label]="backLabel || 'Voltar'"
5406
+ mat-button
5407
+ >
5408
+ <mat-icon [praxisIcon]="'arrow_back'"></mat-icon>
5409
+ <span class="label" [class.hide-on-narrow]="true">{{ backLabel || 'Voltar' }}</span>
5410
+ </a>
5411
+ }
5208
5412
  <h2 class="title" [attr.title]="title">{{ title }}</h2>
5209
5413
  </div>
5210
5414
  <div class="right">
5211
5415
  <ng-content></ng-content>
5212
5416
  </div>
5213
5417
  </header>
5214
- `, 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"] }]
5418
+ `, 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"] }]
5215
5419
  }], propDecorators: { title: [{
5216
5420
  type: Input
5217
5421
  }], backLabel: [{
@@ -5263,6 +5467,12 @@ const CRUD_AI_CAPABILITIES = {
5263
5467
  { path: 'resource.path', category: 'resource', valueKind: 'string', description: 'Endpoint base do recurso.' },
5264
5468
  { path: 'resource.idField', category: 'resource', valueKind: 'string', description: 'Campo identificador do recurso.' },
5265
5469
  { path: 'resource.endpointKey', category: 'resource', valueKind: 'string', description: 'Chave de endpoint (ApiEndpoint).' },
5470
+ { path: 'queryContext.meta.domainCatalog', category: 'resource', valueKind: 'object', description: 'Referencia leve para contexto semantico/governanca resolvido via Domain Catalog; nao materializa regras em FormConfig.' },
5471
+ { path: 'queryContext.meta.domainCatalog.schemaVersion', category: 'resource', valueKind: 'string', description: 'Versao do contrato DomainCatalogContextHint usado em runtime e prompts.' },
5472
+ { path: 'queryContext.meta.domainCatalog.resourceKey', category: 'resource', valueKind: 'string', description: 'ResourceKey semantico estavel usado para resolver vocabulario/governanca de dominio.' },
5473
+ { path: 'queryContext.meta.domainCatalog.releaseId', category: 'resource', valueKind: 'string', description: 'Release opcional do Domain Catalog usada como fonte do contexto.' },
5474
+ { path: 'queryContext.meta.domainCatalog.query', category: 'resource', valueKind: 'string', description: 'Probe de campo/conceito usado para buscar itens relevantes, como cpf, salario ou status.' },
5475
+ { 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.' },
5266
5476
  // --- Table / Form ---
5267
5477
  { path: 'table', category: 'table', valueKind: 'object', description: 'TableConfig completo (usar catalogo de tabela).' },
5268
5478
  { path: 'form', category: 'form', valueKind: 'object', description: 'FormConfig completo (usar catalogo de formulario).' },
@@ -5319,6 +5529,438 @@ const CRUD_AI_CAPABILITIES = {
5319
5529
  ],
5320
5530
  };
5321
5531
 
5532
+ const resourceBindSchema = {
5533
+ type: 'object',
5534
+ required: ['resourcePath'],
5535
+ properties: {
5536
+ resourcePath: { type: 'string' },
5537
+ resourceKey: { type: 'string' },
5538
+ idField: { type: ['string', 'number'] },
5539
+ endpointKey: { type: 'string' },
5540
+ queryContext: { type: 'object' },
5541
+ },
5542
+ };
5543
+ const surfaceConfigureSchema = {
5544
+ type: 'object',
5545
+ required: ['actionId', 'openMode'],
5546
+ properties: {
5547
+ actionId: { enum: ['create', 'edit', 'view'] },
5548
+ openMode: { enum: ['route', 'modal', 'drawer'] },
5549
+ route: { type: 'string' },
5550
+ formId: { type: 'string' },
5551
+ form: {
5552
+ type: 'object',
5553
+ properties: {
5554
+ schemaUrl: { type: 'string' },
5555
+ submitUrl: { type: 'string' },
5556
+ submitMethod: { enum: ['post', 'put', 'patch', 'delete'] },
5557
+ apiEndpointKey: { type: 'string' },
5558
+ initialValue: { type: 'object' },
5559
+ },
5560
+ },
5561
+ params: {
5562
+ type: 'array',
5563
+ items: {
5564
+ type: 'object',
5565
+ required: ['from', 'to', 'name'],
5566
+ properties: {
5567
+ from: { type: 'string' },
5568
+ to: { enum: ['routeParam', 'query', 'input'] },
5569
+ name: { type: 'string' },
5570
+ },
5571
+ },
5572
+ },
5573
+ back: { type: 'object' },
5574
+ },
5575
+ };
5576
+ const listSurfaceSchema = {
5577
+ type: 'object',
5578
+ properties: {
5579
+ tablePatch: { type: 'object' },
5580
+ queryContext: { type: 'object' },
5581
+ filterCriteria: { type: 'object' },
5582
+ },
5583
+ };
5584
+ const deleteBehaviorSchema = {
5585
+ type: 'object',
5586
+ required: ['enabled'],
5587
+ properties: {
5588
+ enabled: { type: 'boolean' },
5589
+ actionId: { type: 'string' },
5590
+ requiresConfirmation: { type: 'boolean' },
5591
+ autoDelete: { type: 'boolean' },
5592
+ form: {
5593
+ type: 'object',
5594
+ properties: {
5595
+ submitUrl: { type: 'string' },
5596
+ submitMethod: { enum: ['delete'] },
5597
+ apiEndpointKey: { type: 'string' },
5598
+ },
5599
+ },
5600
+ },
5601
+ };
5602
+ const dialogHostSchema = {
5603
+ type: 'object',
5604
+ properties: {
5605
+ defaultOpenMode: { enum: ['route', 'modal', 'drawer'] },
5606
+ modal: {
5607
+ type: 'object',
5608
+ properties: {
5609
+ width: { type: 'string' },
5610
+ height: { type: 'string' },
5611
+ minWidth: { type: 'string' },
5612
+ maxWidth: { type: 'string' },
5613
+ density: { type: 'string' },
5614
+ canMaximize: { type: 'boolean' },
5615
+ rememberLastState: { type: 'boolean' },
5616
+ startMaximized: { type: 'boolean' },
5617
+ disableCloseOnEsc: { type: 'boolean' },
5618
+ disableCloseOnBackdrop: { type: 'boolean' },
5619
+ fullscreenBreakpoint: { type: 'string' },
5620
+ },
5621
+ },
5622
+ back: { type: 'object' },
5623
+ },
5624
+ };
5625
+ const permissionsSchema = {
5626
+ type: 'object',
5627
+ properties: {
5628
+ requiredCapabilities: {
5629
+ type: 'array',
5630
+ items: { enum: ['create', 'view', 'edit', 'delete'] },
5631
+ },
5632
+ actionPermissions: { type: 'object' },
5633
+ denyWhenMissingCapability: { type: 'boolean' },
5634
+ },
5635
+ };
5636
+ const domainGovernanceContextSchema = {
5637
+ type: 'object',
5638
+ required: ['resourceKey', 'query'],
5639
+ properties: {
5640
+ resourceKey: { type: 'string' },
5641
+ releaseId: { type: 'string' },
5642
+ query: { type: 'string' },
5643
+ itemTypes: {
5644
+ type: 'array',
5645
+ items: { enum: ['governance', 'vocabulary', 'relationship'] },
5646
+ },
5647
+ intent: { enum: ['authoring', 'explain', 'validate', 'ai-access-control'] },
5648
+ },
5649
+ };
5650
+ const childDelegateSchema = {
5651
+ type: 'object',
5652
+ required: ['childComponentId', 'childOperationId', 'reason'],
5653
+ properties: {
5654
+ childComponentId: { enum: ['praxis-dynamic-form', 'praxis-table', 'praxis-dialog', 'praxis-settings-panel'] },
5655
+ childOperationId: { type: 'string' },
5656
+ reason: { type: 'string' },
5657
+ childTarget: { type: 'object' },
5658
+ childParams: { type: 'object' },
5659
+ },
5660
+ };
5661
+ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
5662
+ schemaVersion: '1.0.0',
5663
+ componentId: 'praxis-crud',
5664
+ ownerPackage: '@praxisui/crud',
5665
+ configSchemaId: 'CrudMetadata',
5666
+ manifestVersion: '1.0.0',
5667
+ runtimeInputs: [
5668
+ { name: 'metadata', type: 'CrudMetadata | string', description: 'Canonical CRUD metadata or serialized metadata document.' },
5669
+ { name: 'crudId', type: 'string', description: 'Stable CRUD instance id used for table/form identity and persistence.' },
5670
+ { name: 'componentInstanceId', type: 'string', description: 'Optional stable host instance id for multiple CRUD widgets on the same route.' },
5671
+ { name: 'context', type: 'Record<string, unknown>', description: 'Opaque host context used for authoring seeds and launcher inputs.' },
5672
+ { name: 'afterOpen', type: '{ mode: FormOpenMode; action: string }', description: 'Emitted after a CRUD action opens.' },
5673
+ { name: 'afterSave', type: '{ id: string | number; data: unknown }', description: 'Emitted after save; CRUD refetches the list.' },
5674
+ { name: 'afterDelete', type: '{ id: string | number }', description: 'Emitted after delete; CRUD refetches the list.' },
5675
+ ],
5676
+ editableTargets: [
5677
+ { kind: 'resourceBinding', resolver: 'crud-resource-by-path-or-key', description: 'Resource path/key, id field, endpoint key and query context owned by CRUD orchestration.' },
5678
+ { kind: 'listSurface', resolver: 'crud-list-surface', description: 'CRUD-hosted list surface and table delegation boundary.' },
5679
+ { kind: 'createSurface', resolver: 'crud-action-by-id:create', description: 'Create action open mode, route/form binding and launcher inputs.' },
5680
+ { kind: 'editSurface', resolver: 'crud-action-by-id:edit', description: 'Edit action open mode, route/form binding and launcher inputs.' },
5681
+ { kind: 'viewSurface', resolver: 'crud-action-by-id:view', description: 'View action open mode, route/form binding and launcher inputs.' },
5682
+ { kind: 'deleteBehavior', resolver: 'crud-action-by-id:delete', description: 'Delete action enablement, confirmation, endpoint and capability policy.' },
5683
+ { kind: 'dialogHost', resolver: 'crud-dialog-host-defaults', description: 'Route/modal/drawer defaults consumed by CrudLauncherService and DynamicFormDialogHostComponent.' },
5684
+ { kind: 'formBinding', resolver: 'crud-action-form-contract-by-action-id', description: 'CRUD-owned form binding fields: formId, schemaUrl, submitUrl, submitMethod, params and initialValue.' },
5685
+ { kind: 'permissions', resolver: 'crud-resource-capabilities', description: 'CRUD action availability derived from resource capabilities and action permissions.' },
5686
+ { 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.' },
5687
+ { kind: 'childOperation', resolver: 'child-authoring-manifest-operation', description: 'Delegated form/table/dialog/settings-panel operation owned by the child component manifest.' },
5688
+ ],
5689
+ operations: [
5690
+ {
5691
+ operationId: 'resource.bind',
5692
+ title: 'Bind CRUD resource',
5693
+ scope: 'dataBinding',
5694
+ targetKind: 'resourceBinding',
5695
+ target: { kind: 'resourceBinding', resolver: 'crud-resource-by-path-or-key', ambiguityPolicy: 'fail', required: true },
5696
+ inputSchema: resourceBindSchema,
5697
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-resource-bind', handlerContract: {
5698
+ reads: ['CrudMetadata.resource', 'api_metadata', 'ResourceDiscoveryService', 'GET /{resource}/capabilities'],
5699
+ writes: ['CrudMetadata.resource', 'CrudMetadata.queryContext', 'PraxisCrudComponent.tableCrudContext'],
5700
+ identityKeys: ['resourcePath', 'resourceKey'],
5701
+ inputSchema: resourceBindSchema,
5702
+ failureModes: ['resource-not-found', 'schema-url-not-canonical', 'capabilities-unavailable', 'id-field-missing'],
5703
+ description: 'Binds CRUD to a canonical resource and validates it against api_metadata/resource capabilities before runtime use.',
5704
+ } }],
5705
+ validators: ['resource-exists-in-api-metadata', 'resource-path-canonical', 'resource-key-stable', 'id-field-known', 'resource-capabilities-resolvable'],
5706
+ affectedPaths: ['resource.path', 'resource.idField', 'resource.endpointKey', 'queryContext', 'filterCriteria'],
5707
+ submissionImpact: 'affects-remote-binding',
5708
+ preconditions: ['crud-metadata-loaded', 'api-metadata-available'],
5709
+ },
5710
+ {
5711
+ operationId: 'domain.governanceContext.attach',
5712
+ title: 'Attach Domain Catalog governance context',
5713
+ scope: 'dataBinding',
5714
+ targetKind: 'domainGovernanceContext',
5715
+ target: { kind: 'domainGovernanceContext', resolver: 'domain-catalog-context-by-resource-key', ambiguityPolicy: 'fail', required: true },
5716
+ inputSchema: domainGovernanceContextSchema,
5717
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-domain-governance-context-attach', handlerContract: {
5718
+ reads: ['CrudMetadata.resource', 'CrudMetadata.queryContext', 'DomainCatalogService.getGovernanceContext', 'DOMAIN_CATALOG_COMPONENT_CONTEXT_PACK', 'api_metadata'],
5719
+ writes: ['CrudMetadata.queryContext.meta.domainCatalog'],
5720
+ identityKeys: ['resourceKey', 'releaseId', 'query'],
5721
+ inputSchema: domainGovernanceContextSchema,
5722
+ failureModes: ['resource-key-missing', 'domain-catalog-release-not-found', 'governance-context-empty', 'ai-usage-forbidden'],
5723
+ description: 'Resolves read-only domain vocabulary/governance context and stores only a lightweight reference/probe under queryContext.meta.domainCatalog.',
5724
+ } }],
5725
+ validators: ['resource-key-stable', 'domain-catalog-context-resolvable', 'domain-catalog-read-only', 'ai-usage-visibility-respected', 'no-form-config-rule-materialization'],
5726
+ affectedPaths: ['queryContext.meta.domainCatalog'],
5727
+ submissionImpact: 'config-only',
5728
+ preconditions: ['crud-metadata-loaded', 'api-metadata-available', 'domain-catalog-service-available'],
5729
+ },
5730
+ {
5731
+ operationId: 'list.surface.configure',
5732
+ title: 'Configure CRUD list surface',
5733
+ scope: 'interaction',
5734
+ targetKind: 'listSurface',
5735
+ target: { kind: 'listSurface', resolver: 'crud-list-surface', ambiguityPolicy: 'fail', required: true },
5736
+ inputSchema: listSurfaceSchema,
5737
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-list-surface-configure', handlerContract: {
5738
+ reads: ['CrudMetadata.table', 'CrudMetadata.queryContext', 'PRAXIS_TABLE_AUTHORING_MANIFEST', 'CRUD_AI_CAPABILITIES'],
5739
+ writes: ['CrudMetadata.table', 'CrudMetadata.queryContext', 'CrudMetadata.filterCriteria'],
5740
+ identityKeys: ['crudId'],
5741
+ inputSchema: listSurfaceSchema,
5742
+ failureModes: ['table-operation-not-delegated', 'table-patch-not-supported', 'query-context-invalid'],
5743
+ description: 'Configures CRUD-owned list orchestration while routing table semantics to the praxis-table manifest.',
5744
+ } }],
5745
+ validators: ['table-child-operation-delegated', 'query-context-valid', 'filter-criteria-bridge-valid', 'crud-context-stable'],
5746
+ affectedPaths: ['table', 'queryContext', 'filterCriteria'],
5747
+ submissionImpact: 'config-only',
5748
+ preconditions: ['crud-metadata-loaded', 'praxis-table-manifest-available'],
5749
+ },
5750
+ {
5751
+ operationId: 'surface.create.configure',
5752
+ title: 'Configure create surface',
5753
+ scope: 'interaction',
5754
+ targetKind: 'createSurface',
5755
+ target: { kind: 'createSurface', resolver: 'crud-action-by-id:create', ambiguityPolicy: 'fail', required: true },
5756
+ inputSchema: surfaceConfigureSchema,
5757
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-create-surface-configure', handlerContract: {
5758
+ reads: ['CrudMetadata.actions', 'CrudLauncherService.resolveOpenMode', 'DynamicFormDialogHostComponent', 'api_metadata'],
5759
+ writes: ['CrudMetadata.actions[]', 'CrudMetadata.actions[].form', 'CrudMetadata.actions[].params', 'CrudMetadata.actions[].back'],
5760
+ identityKeys: ['actionId'],
5761
+ inputSchema: surfaceConfigureSchema,
5762
+ failureModes: ['action-not-found', 'open-mode-binding-incomplete', 'schema-url-not-canonical', 'submit-url-not-canonical', 'resource-create-not-supported'],
5763
+ description: 'Configures create action binding and launcher inputs without editing the child FormConfig.',
5764
+ } }],
5765
+ validators: ['action-exists', 'open-mode-binding-complete', 'schema-url-canonical', 'submit-url-canonical', 'resource-create-supported', 'form-child-operation-delegated'],
5766
+ affectedPaths: ['actions[].openMode', 'actions[].route', 'actions[].formId', 'actions[].form', 'actions[].params', 'actions[].back'],
5767
+ submissionImpact: 'affects-schema-backed-data',
5768
+ preconditions: ['crud-metadata-loaded', 'resource-capabilities-resolved'],
5769
+ },
5770
+ {
5771
+ operationId: 'surface.edit.configure',
5772
+ title: 'Configure edit surface',
5773
+ scope: 'interaction',
5774
+ targetKind: 'editSurface',
5775
+ target: { kind: 'editSurface', resolver: 'crud-action-by-id:edit', ambiguityPolicy: 'fail', required: true },
5776
+ inputSchema: surfaceConfigureSchema,
5777
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-edit-surface-configure', handlerContract: {
5778
+ reads: ['CrudMetadata.actions', 'CrudMetadata.resource.idField', 'CrudLauncherService.resolveOpenMode', 'api_metadata'],
5779
+ writes: ['CrudMetadata.actions[]', 'CrudMetadata.actions[].form', 'CrudMetadata.actions[].params', 'CrudMetadata.actions[].back'],
5780
+ identityKeys: ['actionId'],
5781
+ inputSchema: surfaceConfigureSchema,
5782
+ failureModes: ['action-not-found', 'id-param-missing', 'open-mode-binding-incomplete', 'resource-edit-not-supported', 'submit-url-not-canonical'],
5783
+ description: 'Configures edit action routing/form binding and ensures id parameter mapping is available to the launcher.',
5784
+ } }],
5785
+ 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'],
5786
+ affectedPaths: ['actions[].openMode', 'actions[].route', 'actions[].formId', 'actions[].form', 'actions[].params', 'actions[].back'],
5787
+ submissionImpact: 'affects-schema-backed-data',
5788
+ preconditions: ['crud-metadata-loaded', 'resource-capabilities-resolved'],
5789
+ },
5790
+ {
5791
+ operationId: 'surface.view.configure',
5792
+ title: 'Configure view surface',
5793
+ scope: 'interaction',
5794
+ targetKind: 'viewSurface',
5795
+ target: { kind: 'viewSurface', resolver: 'crud-action-by-id:view', ambiguityPolicy: 'fail', required: true },
5796
+ inputSchema: surfaceConfigureSchema,
5797
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-view-surface-configure', handlerContract: {
5798
+ reads: ['CrudMetadata.actions', 'CrudLauncherService.resolveOpenMode', 'DynamicFormDialogHostComponent', 'api_metadata'],
5799
+ writes: ['CrudMetadata.actions[]', 'CrudMetadata.actions[].form', 'CrudMetadata.actions[].params', 'CrudMetadata.actions[].back'],
5800
+ identityKeys: ['actionId'],
5801
+ inputSchema: surfaceConfigureSchema,
5802
+ failureModes: ['action-not-found', 'open-mode-binding-incomplete', 'resource-view-not-supported', 'readonly-form-delegation-missing'],
5803
+ description: 'Configures view action binding and delegates readonly form behavior to the dynamic-form manifest.',
5804
+ } }],
5805
+ validators: ['action-exists', 'open-mode-binding-complete', 'schema-url-canonical', 'resource-view-supported', 'form-child-operation-delegated', 'readonly-form-delegation-valid'],
5806
+ affectedPaths: ['actions[].openMode', 'actions[].route', 'actions[].formId', 'actions[].form', 'actions[].params', 'actions[].back'],
5807
+ submissionImpact: 'config-only',
5808
+ preconditions: ['crud-metadata-loaded', 'resource-capabilities-resolved'],
5809
+ },
5810
+ {
5811
+ operationId: 'delete.enabled.set',
5812
+ title: 'Configure delete behavior',
5813
+ scope: 'interaction',
5814
+ targetKind: 'deleteBehavior',
5815
+ target: { kind: 'deleteBehavior', resolver: 'crud-action-by-id:delete', ambiguityPolicy: 'fail', required: true },
5816
+ inputSchema: deleteBehaviorSchema,
5817
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-delete-behavior-set', handlerContract: {
5818
+ reads: ['CrudMetadata.actions', 'CrudMetadata.resource', 'GET /{resource}/{id}/capabilities'],
5819
+ writes: ['CrudMetadata.actions[]', 'CrudMetadata.actions[].requiresConfirmation', 'CrudMetadata.actions[].autoDelete', 'CrudMetadata.actions[].form'],
5820
+ identityKeys: ['actionId'],
5821
+ inputSchema: deleteBehaviorSchema,
5822
+ failureModes: ['delete-action-not-found', 'resource-delete-not-supported', 'destructive-delete-not-confirmed', 'delete-submit-url-not-canonical'],
5823
+ description: 'Enables or disables delete behavior with capability checks and explicit confirmation for destructive changes.',
5824
+ } }],
5825
+ destructive: true,
5826
+ requiresConfirmation: true,
5827
+ validators: ['delete-action-exists', 'resource-delete-supported', 'destructive-delete-confirmed', 'submit-url-canonical', 'permissions-delete-valid'],
5828
+ affectedPaths: ['actions[].disabled', 'actions[].requiresConfirmation', 'actions[].autoDelete', 'actions[].form', 'actions[].form.submitUrl', 'actions[].form.submitMethod', 'actions[].form.apiEndpointKey'],
5829
+ submissionImpact: 'affects-schema-backed-data',
5830
+ preconditions: ['crud-metadata-loaded', 'resource-capabilities-resolved', 'explicit-confirmation-provided'],
5831
+ },
5832
+ {
5833
+ operationId: 'dialog.size.set',
5834
+ title: 'Configure CRUD dialog host defaults',
5835
+ scope: 'interaction',
5836
+ targetKind: 'dialogHost',
5837
+ target: { kind: 'dialogHost', resolver: 'crud-dialog-host-defaults', ambiguityPolicy: 'fail', required: true },
5838
+ inputSchema: dialogHostSchema,
5839
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-dialog-host-set', handlerContract: {
5840
+ reads: ['CrudMetadata.defaults', 'CrudLauncherService.resolveOpenMode', 'DialogService', 'CRUD_DRAWER_ADAPTER'],
5841
+ writes: ['CrudMetadata.defaults.openMode', 'CrudMetadata.defaults.modal', 'CrudMetadata.defaults.back'],
5842
+ identityKeys: ['crudId'],
5843
+ inputSchema: dialogHostSchema,
5844
+ failureModes: ['open-mode-unsupported', 'drawer-adapter-missing', 'modal-size-invalid', 'back-policy-invalid'],
5845
+ description: 'Configures CRUD-owned route/modal/drawer defaults consumed by the launcher and dialog host.',
5846
+ } }],
5847
+ validators: ['open-mode-supported', 'modal-size-valid', 'drawer-adapter-available-when-needed', 'back-policy-valid', 'settings-panel-shell-compatible'],
5848
+ affectedPaths: ['defaults.openMode', 'defaults.modal', 'defaults.back'],
5849
+ submissionImpact: 'config-only',
5850
+ preconditions: ['crud-metadata-loaded'],
5851
+ },
5852
+ {
5853
+ operationId: 'permissions.set',
5854
+ title: 'Configure CRUD permissions',
5855
+ scope: 'interaction',
5856
+ targetKind: 'permissions',
5857
+ target: { kind: 'permissions', resolver: 'crud-resource-capabilities', ambiguityPolicy: 'fail', required: true },
5858
+ inputSchema: permissionsSchema,
5859
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-permissions-set', handlerContract: {
5860
+ reads: ['GET /{resource}/capabilities', 'GET /{resource}/{id}/capabilities', 'CrudMetadata.actions'],
5861
+ writes: ['CrudMetadata.actions[].disabled', 'CrudMetadata.actions[].visibleWhen', 'CrudMetadata.actions[].requiresConfirmation'],
5862
+ identityKeys: ['resourcePath'],
5863
+ inputSchema: permissionsSchema,
5864
+ failureModes: ['capability-not-found', 'action-permission-conflict', 'delete-permission-without-confirmation'],
5865
+ description: 'Aligns CRUD action visibility/disablement with resource capabilities without inventing a second permission source.',
5866
+ } }],
5867
+ validators: ['resource-capabilities-resolvable', 'action-permission-supported', 'delete-permission-requires-confirmation', 'permissions-do-not-shadow-backend'],
5868
+ affectedPaths: ['actions[].disabled', 'actions[].visibleWhen', 'actions[].requiresConfirmation'],
5869
+ submissionImpact: 'config-only',
5870
+ preconditions: ['crud-metadata-loaded', 'resource-capabilities-resolved'],
5871
+ },
5872
+ {
5873
+ operationId: 'form.childOperation.delegate',
5874
+ title: 'Delegate child form/table/dialog authoring operation',
5875
+ scope: 'global',
5876
+ targetKind: 'childOperation',
5877
+ target: { kind: 'childOperation', resolver: 'child-authoring-manifest-operation', ambiguityPolicy: 'fail', required: false },
5878
+ inputSchema: childDelegateSchema,
5879
+ effects: [{ kind: 'compile-domain-patch', handler: 'crud-child-operation-delegate', handlerContract: {
5880
+ reads: ['PRAXIS_DYNAMIC_FORM_AUTHORING_MANIFEST', 'PRAXIS_TABLE_AUTHORING_MANIFEST', 'PRAXIS_DIALOG_AUTHORING_MANIFEST', 'PRAXIS_SETTINGS_PANEL_AUTHORING_MANIFEST'],
5881
+ writes: ['delegatedAuthoringOperations[]'],
5882
+ identityKeys: ['childComponentId', 'childOperationId'],
5883
+ inputSchema: childDelegateSchema,
5884
+ failureModes: ['child-manifest-missing', 'child-operation-not-found', 'attempted-local-child-config-write'],
5885
+ description: 'Records explicit delegation when requested edits belong to child manifests instead of CRUD orchestration.',
5886
+ } }],
5887
+ validators: ['child-manifest-available', 'child-operation-known', 'no-local-form-config-write', 'no-local-table-config-write', 'delegation-target-valid'],
5888
+ affectedPaths: ['delegatedAuthoringOperations'],
5889
+ submissionImpact: 'none',
5890
+ preconditions: ['child-manifest-available'],
5891
+ },
5892
+ ],
5893
+ validators: [
5894
+ { validatorId: 'resource-exists-in-api-metadata', level: 'error', code: 'CRUD_RESOURCE_EXISTS', description: 'Resource must exist in api_metadata or resource discovery.' },
5895
+ { validatorId: 'resource-path-canonical', level: 'error', code: 'CRUD_RESOURCE_PATH_CANONICAL', description: 'Resource path must be canonical and not a local alias.' },
5896
+ { validatorId: 'resource-key-stable', level: 'error', code: 'CRUD_RESOURCE_KEY_STABLE', description: 'Resource key must remain stable for surfaces/actions/capabilities.' },
5897
+ { validatorId: 'id-field-known', level: 'error', code: 'CRUD_ID_FIELD_KNOWN', description: 'CRUD id field must exist for edit/view/delete flows.' },
5898
+ { validatorId: 'resource-capabilities-resolvable', level: 'error', code: 'CRUD_CAPABILITIES_RESOLVABLE', description: 'Resource capabilities must be resolvable before action enablement is authored.' },
5899
+ { 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.' },
5900
+ { 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.' },
5901
+ { 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.' },
5902
+ { 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.' },
5903
+ { validatorId: 'table-child-operation-delegated', level: 'error', code: 'CRUD_TABLE_CHILD_DELEGATED', description: 'Table semantics must be delegated to praxis-table.' },
5904
+ { validatorId: 'query-context-valid', level: 'error', code: 'CRUD_QUERY_CONTEXT_VALID', description: 'Query context must be valid for the bound resource.' },
5905
+ { validatorId: 'filter-criteria-bridge-valid', level: 'warning', code: 'CRUD_FILTER_CRITERIA_BRIDGE_VALID', description: 'filterCriteria is a bridge; prefer queryContext for new remote authoring.' },
5906
+ { validatorId: 'crud-context-stable', level: 'error', code: 'CRUD_CONTEXT_STABLE', description: 'Authoring must not break stable crudContext references.' },
5907
+ { validatorId: 'action-exists', level: 'error', code: 'CRUD_ACTION_EXISTS', description: 'Target action must exist or be created through CRUD action orchestration.' },
5908
+ { 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.' },
5909
+ { validatorId: 'schema-url-canonical', level: 'error', code: 'CRUD_SCHEMA_URL_CANONICAL', description: 'Schema URLs must be canonical for the bound resource/action.' },
5910
+ { validatorId: 'submit-url-canonical', level: 'error', code: 'CRUD_SUBMIT_URL_CANONICAL', description: 'Submit URL and method must be canonical and declared together.' },
5911
+ { validatorId: 'resource-create-supported', level: 'error', code: 'CRUD_CREATE_SUPPORTED', description: 'Create surface requires resource create capability.' },
5912
+ { validatorId: 'resource-edit-supported', level: 'error', code: 'CRUD_EDIT_SUPPORTED', description: 'Edit surface requires resource edit capability.' },
5913
+ { validatorId: 'resource-view-supported', level: 'error', code: 'CRUD_VIEW_SUPPORTED', description: 'View surface requires resource view capability.' },
5914
+ { 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.' },
5915
+ { 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.' },
5916
+ { validatorId: 'readonly-form-delegation-valid', level: 'error', code: 'CRUD_READONLY_FORM_DELEGATED', description: 'Readonly form behavior belongs to the dynamic-form manifest.' },
5917
+ { validatorId: 'delete-action-exists', level: 'error', code: 'CRUD_DELETE_ACTION_EXISTS', description: 'Delete behavior requires a delete action target.' },
5918
+ { validatorId: 'resource-delete-supported', level: 'error', code: 'CRUD_DELETE_SUPPORTED', description: 'Delete behavior requires resource delete capability.' },
5919
+ { validatorId: 'destructive-delete-confirmed', level: 'error', code: 'CRUD_DELETE_CONFIRMED', description: 'Destructive delete behavior requires explicit confirmation.' },
5920
+ { validatorId: 'permissions-delete-valid', level: 'error', code: 'CRUD_DELETE_PERMISSION_VALID', description: 'Delete permission cannot bypass resource capabilities or confirmation policy.' },
5921
+ { validatorId: 'open-mode-supported', level: 'error', code: 'CRUD_OPEN_MODE_SUPPORTED', description: 'Open mode must be route, modal or drawer.' },
5922
+ { validatorId: 'modal-size-valid', level: 'error', code: 'CRUD_MODAL_SIZE_VALID', description: 'Modal sizing defaults must be valid DialogConfig values.' },
5923
+ { validatorId: 'drawer-adapter-available-when-needed', level: 'error', code: 'CRUD_DRAWER_ADAPTER_AVAILABLE', description: 'Drawer open mode requires a host-provided drawer adapter.' },
5924
+ { validatorId: 'back-policy-valid', level: 'error', code: 'CRUD_BACK_POLICY_VALID', description: 'Back policy must be valid for route/modal/drawer behavior.' },
5925
+ { validatorId: 'settings-panel-shell-compatible', level: 'warning', code: 'CRUD_SETTINGS_PANEL_COMPATIBLE', description: 'Authoring shell must preserve apply/save/reset semantics.' },
5926
+ { validatorId: 'action-permission-supported', level: 'error', code: 'CRUD_ACTION_PERMISSION_SUPPORTED', description: 'Action permissions must map to supported resource capabilities.' },
5927
+ { validatorId: 'delete-permission-requires-confirmation', level: 'error', code: 'CRUD_DELETE_PERMISSION_CONFIRMATION', description: 'Delete permission enablement requires confirmation policy.' },
5928
+ { validatorId: 'permissions-do-not-shadow-backend', level: 'error', code: 'CRUD_PERMISSIONS_NO_BACKEND_SHADOW', description: 'UI permissions must not shadow backend capability denial.' },
5929
+ { validatorId: 'child-manifest-available', level: 'error', code: 'CRUD_CHILD_MANIFEST_AVAILABLE', description: 'Delegated child manifest must be available.' },
5930
+ { validatorId: 'child-operation-known', level: 'error', code: 'CRUD_CHILD_OPERATION_KNOWN', description: 'Delegated operation must exist in the child manifest.' },
5931
+ { validatorId: 'no-local-form-config-write', level: 'error', code: 'CRUD_NO_LOCAL_FORM_CONFIG_WRITE', description: 'CRUD must not locally redefine FormConfig semantics.' },
5932
+ { validatorId: 'no-local-table-config-write', level: 'error', code: 'CRUD_NO_LOCAL_TABLE_CONFIG_WRITE', description: 'CRUD must not locally redefine TableConfig semantics.' },
5933
+ { validatorId: 'delegation-target-valid', level: 'error', code: 'CRUD_DELEGATION_TARGET_VALID', description: 'Delegation target must be resolvable by the child manifest.' },
5934
+ ],
5935
+ roundTripRequirements: [
5936
+ 'CrudMetadata is the canonical CRUD document shape.',
5937
+ 'CrudAuthoringDocument wraps CrudMetadata without introducing host-local aliases.',
5938
+ 'Resource path and resource key have separate semantics: path is operational, key is discovery/capability identity.',
5939
+ '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.',
5940
+ 'Open mode round-trip must preserve route, formId, form contract, params, initialValue and back policy.',
5941
+ 'FormConfig and FieldMetadata edits must delegate to dynamic-form or metadata-editor manifests.',
5942
+ 'TableConfig edits must delegate to praxis-table; CRUD owns only shell orchestration and list surface binding.',
5943
+ 'Delete behavior is destructive and requires explicit confirmation plus backend capability support.',
5944
+ 'crudContext must remain reference-stable across change detection cycles.',
5945
+ ],
5946
+ examples: [
5947
+ { 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 },
5948
+ { 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 },
5949
+ { 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 },
5950
+ { 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 },
5951
+ { 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 },
5952
+ { 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 },
5953
+ { 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 },
5954
+ { 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 },
5955
+ { 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 },
5956
+ { 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 },
5957
+ { 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 },
5958
+ { 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 },
5959
+ { 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 },
5960
+ { 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 },
5961
+ ],
5962
+ };
5963
+
5322
5964
  /*
5323
5965
  * Public API Surface of praxis-crud
5324
5966
  */
@@ -5327,4 +5969,4 @@ const CRUD_AI_CAPABILITIES = {
5327
5969
  * Generated bundle index. Do not edit.
5328
5970
  */
5329
5971
 
5330
- export { CRUD_AI_CAPABILITIES, CrudLauncherService, CrudMetadataEditorComponent, CrudPageHeaderComponent, DialogService, DynamicFormDialogHostComponent, PRAXIS_CRUD_COMPONENT_METADATA, PraxisCrudComponent, assertCrudMetadata, createCrudAuthoringDocument, findCrudAction, normalizeCrudAuthoringDocument, openCrudMetadataEditor, parseLegacyOrCrudDocument, providePraxisCrudMetadata, serializeCrudAuthoringDocument, validateCrudAuthoringDocument };
5972
+ 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 };