@praxisui/crud 10.0.0-rc.16 → 10.0.0-rc.18

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.
package/README.md CHANGED
@@ -365,7 +365,9 @@ and materializes them in the contextual bulk selection toolbar:
365
365
  executions until terminal state (`COMPLETED`, `COMPLETED_WITH_ERRORS`,
366
366
  `CANCELLED`, `STOPPED`, `RECONCILIATION_REQUIRED`), aggregates all rejection
367
367
  categories (`denied`, `invalid`, `conflict`, `notProcessed`, `unknown`),
368
- clears table selection, refreshes the collection, and re-fetches capabilities
368
+ clears table selection on full success or deselects only confirmed items on
369
+ partial outcomes so rejected/unprocessed rows remain selected across
370
+ `persistOnDataUpdate: true` table refreshes, and re-fetches capabilities
369
371
  when `refresh.capabilities === true`.
370
372
 
371
373
  Unavailable bulk operations (`availability.allowed === false`) remain visible
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-10-14T20:20:10.826Z",
3
+ "generatedAt": "2026-10-14T22:59:57.823Z",
4
4
  "packageName": "@praxisui/crud",
5
- "packageVersion": "10.0.0-rc.16",
5
+ "packageVersion": "10.0.0-rc.18",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 2,
@@ -7,7 +7,7 @@ import { MatSnackBar } from '@angular/material/snack-bar';
7
7
  import { firstValueFrom, BehaviorSubject, Subscription } from 'rxjs';
8
8
  import { take, filter } from 'rxjs/operators';
9
9
  import * as i2$1 from '@praxisui/core';
10
- import { PraxisBulkClientService, generateBulkIdempotencyKey, API_URL, SafeExpressionEvaluator, buildApiUrl, ASYNC_CONFIG_STORAGE, GlobalConfigService, CrudOperationResolutionService, LoggerService, SurfaceNavigationError, fillUndefined, SETTINGS_PANEL_DATA, PraxisI18nService, PraxisIconDirective, providePraxisI18nConfig, createDefaultTableConfig, PraxisWidgetEventBus, EnterpriseRuntimeContextService, GLOBAL_SURFACE_SERVICE, SurfaceOutletRegistryService, ComponentKeyService, ResourceDiscoveryService, ResourceActionOpenAdapterService, ResourceSurfaceOpenAdapterService, composeHeadersWithVersion, normalizeSurfaceOperationContext, translateUnavailableWorkflowMessage, isSurfaceNavigationError, translateSurfaceNavigationRejected, normalizeUnknownError, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, SURFACE_DRAWER_REF, SURFACE_DRAWER_CONTENT_DATA, PraxisIconButtonComponent, PraxisResourceIdentityComponent, GenericCrudService, ComponentMetadataRegistry } from '@praxisui/core';
10
+ import { PraxisBulkClientService, generateBulkIdempotencyKey, API_URL, SafeExpressionEvaluator, buildApiUrl, ASYNC_CONFIG_STORAGE, GlobalConfigService, CrudOperationResolutionService, LoggerService, SurfaceNavigationError, fillUndefined, SETTINGS_PANEL_DATA, PraxisI18nService, PraxisIconDirective, providePraxisI18nConfig, createDefaultTableConfig, PraxisWidgetEventBus, EnterpriseRuntimeContextService, GLOBAL_SURFACE_SERVICE, SurfaceOutletRegistryService, ComponentKeyService, ResourceDiscoveryService, ResourceActionOpenAdapterService, ResourceSurfaceOpenAdapterService, composeHeadersWithVersion, normalizeSurfaceOperationContext, translateUnavailableWorkflowMessage, isSurfaceNavigationError, translateSurfaceNavigationRejected, normalizeUnknownError, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, SURFACE_DRAWER_REF, SURFACE_DRAWER_CONTENT_DATA, PraxisIconButtonComponent, PraxisResourceIdentityComponent, GenericCrudService, ComponentMetadataRegistry, PraxisCustomizationModeService } from '@praxisui/core';
11
11
  export { createDynamicRecord, extractRecordId, isDynamicDataRecord, resolveDataField } from '@praxisui/core';
12
12
  import * as i1 from '@angular/common';
13
13
  import { CommonModule } from '@angular/common';
@@ -522,6 +522,7 @@ class PraxisBulkOperationDialogComponent {
522
522
  this.dialogRef.close({
523
523
  success: isSuccess,
524
524
  execution: exec,
525
+ results: this.executionItems(),
525
526
  });
526
527
  }
527
528
  onCancel() {
@@ -1771,6 +1772,7 @@ class PraxisBulkEditDialogComponent {
1771
1772
  this.dialogRef.close({
1772
1773
  success: isSuccess,
1773
1774
  execution: exec,
1775
+ results: this.executionItems(),
1774
1776
  });
1775
1777
  }
1776
1778
  onCancel() {
@@ -7089,6 +7091,15 @@ class PraxisCrudComponent {
7089
7091
  collectionCapabilities = null;
7090
7092
  activeAuthoringPanelRef;
7091
7093
  constructor() {
7094
+ this.destroyRef.onDestroy(() => {
7095
+ if (this.activeAuthoringPanelRef) {
7096
+ try {
7097
+ this.activeAuthoringPanelRef.close();
7098
+ }
7099
+ catch { }
7100
+ this.activeAuthoringPanelRef = undefined;
7101
+ }
7102
+ });
7092
7103
  this.enterpriseRuntimeContext?.contextChanges$
7093
7104
  .pipe(takeUntilDestroyed(this.destroyRef))
7094
7105
  .subscribe(() => {
@@ -7227,8 +7238,21 @@ class PraxisCrudComponent {
7227
7238
  this.openingActionLabel.set(this.resolveOpeningActionLabel(action, event));
7228
7239
  try {
7229
7240
  let resolvedBulk = this.resolveSyncBulkExecutionContractForAction(action, normalizedAction, configAction, crudAction, tableBulkAction);
7230
- if (!resolvedBulk.contract && this.tableCollectionLinks && (!crudAction || !this.hasExplicitOpenBinding(crudAction))) {
7231
- resolvedBulk = await this.resolveAsyncDiscoveredBulkContractForAction(action, normalizedAction);
7241
+ if ((!resolvedBulk.contract ||
7242
+ (!resolvedBulk.catalogAction &&
7243
+ resolvedBulk.contract.mode === 'DOMAIN_COMMAND' &&
7244
+ !configAction?.requestSchemaId &&
7245
+ !configAction?.commandParameterDescriptors &&
7246
+ !crudAction?.requestSchemaId)) &&
7247
+ this.tableCollectionLinks &&
7248
+ (!crudAction || !this.hasExplicitOpenBinding(crudAction))) {
7249
+ const asyncBulk = await this.resolveAsyncDiscoveredBulkContractForAction(action, normalizedAction);
7250
+ if (asyncBulk.contract || asyncBulk.catalogAction) {
7251
+ resolvedBulk = {
7252
+ contract: resolvedBulk.contract ?? asyncBulk.contract,
7253
+ catalogAction: resolvedBulk.catalogAction ?? asyncBulk.catalogAction,
7254
+ };
7255
+ }
7232
7256
  }
7233
7257
  const bulkContract = resolvedBulk.contract;
7234
7258
  const discoveredCatalogAction = resolvedBulk.catalogAction;
@@ -7239,14 +7263,22 @@ class PraxisCrudComponent {
7239
7263
  this.snack.open(`A seleção atual (${selectedCount} itens) excede o limite máximo permitido (${maxTargets} itens por lote).`, undefined, { duration: 4000 });
7240
7264
  return;
7241
7265
  }
7242
- const requiresResourceVersion = discoveredCatalogAction?.execution?.preconditions?.resourceVersion !== 'NONE';
7266
+ const resourceVersionPrecondition = discoveredCatalogAction?.execution?.preconditions?.resourceVersion ??
7267
+ configAction?.execution?.preconditions?.resourceVersion ??
7268
+ crudAction?.execution?.preconditions?.resourceVersion ??
7269
+ bulkContract?.preconditions?.resourceVersion;
7270
+ const requiresResourceVersion = resourceVersionPrecondition !== 'NONE';
7243
7271
  const targets = Array.isArray(rawTargets) && rawTargets.length > 0
7244
7272
  ? rawTargets
7245
- : await this.resolveBulkTargetsFromRows(rows, requiresResourceVersion);
7273
+ : await this.resolveBulkTargetsFromRows(rows, requiresResourceVersion, resourcePath);
7246
7274
  if (!targets.length) {
7247
7275
  this.snack.open('Nenhum registro selecionado para a operação em lote.', undefined, { duration: 3000 });
7248
7276
  return;
7249
7277
  }
7278
+ const refreshPolicy = discoveredCatalogAction?.execution?.refresh ??
7279
+ configAction?.execution?.refresh ??
7280
+ crudAction?.execution?.refresh ??
7281
+ bulkContract?.refresh;
7250
7282
  const columns = this.table?.config?.columns?.length
7251
7283
  ? this.table.config.columns
7252
7284
  : this.effectiveTableConfig?.columns;
@@ -7277,7 +7309,7 @@ class PraxisCrudComponent {
7277
7309
  .afterClosed()
7278
7310
  .pipe(takeUntilDestroyed(this.destroyRef))
7279
7311
  .subscribe((res) => {
7280
- this.handleBulkDialogResult(res, discoveredCatalogAction?.execution?.refresh, 'Atualização em lote concluída com sucesso.');
7312
+ this.handleBulkDialogResult(res, refreshPolicy, 'Atualização em lote concluída com sucesso.');
7281
7313
  });
7282
7314
  return;
7283
7315
  }
@@ -7314,7 +7346,7 @@ class PraxisCrudComponent {
7314
7346
  .afterClosed()
7315
7347
  .pipe(takeUntilDestroyed(this.destroyRef))
7316
7348
  .subscribe((res) => {
7317
- this.handleBulkDialogResult(res, discoveredCatalogAction?.execution?.refresh, 'Operação em lote executada com sucesso.');
7349
+ this.handleBulkDialogResult(res, refreshPolicy, 'Operação em lote executada com sucesso.');
7318
7350
  });
7319
7351
  return;
7320
7352
  }
@@ -7331,6 +7363,15 @@ class PraxisCrudComponent {
7331
7363
  }
7332
7364
  handleBulkDialogResult(res, refreshPolicy, defaultSuccessMessage) {
7333
7365
  const exec = res?.execution;
7366
+ const confirmedIds = Array.isArray(res?.results)
7367
+ ? new Set(res.results
7368
+ .filter((item) => (item?.status === 'CONFIRMED' ||
7369
+ item?.status === 'UNCHANGED' ||
7370
+ item?.outcome === 'CONFIRMED' ||
7371
+ item?.outcome === 'UNCHANGED') &&
7372
+ item?.id != null)
7373
+ .map((item) => String(item.id)))
7374
+ : undefined;
7334
7375
  if (exec && exec.status) {
7335
7376
  const status = exec.status;
7336
7377
  const confirmed = exec.totals?.['confirmed'] ?? 0;
@@ -7345,13 +7386,13 @@ class PraxisCrudComponent {
7345
7386
  return;
7346
7387
  }
7347
7388
  if (status === 'COMPLETED_WITH_ERRORS' || (status === 'COMPLETED' && rejected > 0)) {
7348
- this.handleBulkExecutionCompleted(refreshPolicy, false);
7389
+ this.handleBulkExecutionCompleted(refreshPolicy, false, confirmedIds);
7349
7390
  this.snack.open(`Operação em lote concluída parcialmente (${confirmed} efetivado(s), ${rejected} com restrição).`, undefined, { duration: 4000 });
7350
7391
  return;
7351
7392
  }
7352
7393
  if (status === 'CANCELLED') {
7353
7394
  if (confirmed > 0) {
7354
- this.handleBulkExecutionCompleted(refreshPolicy, false);
7395
+ this.handleBulkExecutionCompleted(refreshPolicy, false, confirmedIds);
7355
7396
  }
7356
7397
  this.snack.open(confirmed > 0
7357
7398
  ? `Execução em lote cancelada após efetivar ${confirmed} registro(s).`
@@ -7360,7 +7401,7 @@ class PraxisCrudComponent {
7360
7401
  }
7361
7402
  if (status === 'STOPPED' || status === 'RECONCILIATION_REQUIRED') {
7362
7403
  if (confirmed > 0 || status === 'RECONCILIATION_REQUIRED') {
7363
- this.handleBulkExecutionCompleted(refreshPolicy, false);
7404
+ this.handleBulkExecutionCompleted(refreshPolicy, false, confirmedIds);
7364
7405
  }
7365
7406
  this.snack.open(status === 'RECONCILIATION_REQUIRED'
7366
7407
  ? 'Execução em lote exige reconciliação. Verifique os diagnósticos.'
@@ -7373,7 +7414,7 @@ class PraxisCrudComponent {
7373
7414
  this.snack.open(defaultSuccessMessage, undefined, { duration: 3000 });
7374
7415
  }
7375
7416
  }
7376
- handleBulkExecutionCompleted(refreshPolicy, clearSelection = true) {
7417
+ handleBulkExecutionCompleted(refreshPolicy, clearSelection = true, confirmedIds) {
7377
7418
  if (clearSelection) {
7378
7419
  try {
7379
7420
  this.table?.clearSelection?.();
@@ -7383,6 +7424,23 @@ class PraxisCrudComponent {
7383
7424
  // Best-effort table selection cleanup
7384
7425
  }
7385
7426
  }
7427
+ else if (confirmedIds && confirmedIds.size > 0) {
7428
+ try {
7429
+ const selectionModel = this.table?.selection;
7430
+ if (selectionModel && Array.isArray(selectionModel.selected)) {
7431
+ const rowsToDeselect = selectionModel.selected.filter((row) => {
7432
+ const id = this.resolveRowResourceId(row);
7433
+ return id != null && confirmedIds.has(String(id));
7434
+ });
7435
+ if (rowsToDeselect.length > 0 && typeof selectionModel.deselect === 'function') {
7436
+ selectionModel.deselect(...rowsToDeselect);
7437
+ }
7438
+ }
7439
+ }
7440
+ catch {
7441
+ // Best-effort partial selection reconciliation
7442
+ }
7443
+ }
7386
7444
  if (refreshPolicy?.collection !== false) {
7387
7445
  this.refreshTable();
7388
7446
  }
@@ -7447,12 +7505,6 @@ class PraxisCrudComponent {
7447
7505
  return results;
7448
7506
  }
7449
7507
  resolveSyncBulkExecutionContractForAction(action, normalizedAction, configAction, crudAction, tableBulkAction) {
7450
- const direct = configAction?.execution?.bulk ||
7451
- crudAction?.execution?.bulk ||
7452
- tableBulkAction?.execution?.bulk;
7453
- if (direct) {
7454
- return { contract: direct };
7455
- }
7456
7508
  const matchKey = (candidate) => {
7457
7509
  const id = String(candidate?.id || candidate?.action || candidate?.operationId || '').trim();
7458
7510
  const norm = this.normalizeCrudActionName(id).toLowerCase();
@@ -7461,8 +7513,12 @@ class PraxisCrudComponent {
7461
7513
  String(candidate?.operationId || '').trim() === action);
7462
7514
  };
7463
7515
  const capAction = (this.collectionCapabilities?.actions || []).find((a) => matchKey(a) && a.execution?.bulk);
7464
- if (capAction?.execution?.bulk) {
7465
- return { contract: capAction.execution.bulk, catalogAction: capAction };
7516
+ const direct = configAction?.execution?.bulk ||
7517
+ crudAction?.execution?.bulk ||
7518
+ tableBulkAction?.execution?.bulk ||
7519
+ capAction?.execution?.bulk;
7520
+ if (direct) {
7521
+ return { contract: direct, ...(capAction ? { catalogAction: capAction } : {}) };
7466
7522
  }
7467
7523
  const capOp = Object.values(this.collectionCapabilities?.operations || {}).find((op) => matchKey(op) && op?.bulk);
7468
7524
  if (capOp?.bulk) {
@@ -7511,6 +7567,16 @@ class PraxisCrudComponent {
7511
7567
  await this.onAction(action, row ?? undefined, event);
7512
7568
  }
7513
7569
  ngOnChanges(changes) {
7570
+ if (changes['enableCustomization'] || changes['authoringCapability']) {
7571
+ if (!this.isCustomizationAvailable() && this.activeAuthoringPanelRef) {
7572
+ try {
7573
+ this.activeAuthoringPanelRef.close();
7574
+ }
7575
+ catch { }
7576
+ this.activeAuthoringPanelRef = undefined;
7577
+ }
7578
+ this.cdr.markForCheck();
7579
+ }
7514
7580
  if (!changes['metadata'] && !changes['context'] && !changes['resource']) {
7515
7581
  if (changes['filterCriteria'] && !changes['filterCriteria'].firstChange) {
7516
7582
  this.applyFilterCriteria(this.filterCriteria);
@@ -9022,7 +9088,7 @@ class PraxisCrudComponent {
9022
9088
  allowSelectAll: prevSelection?.allowSelectAll ?? true,
9023
9089
  checkboxPosition: prevSelection?.checkboxPosition || 'start',
9024
9090
  persistSelection: prevSelection?.persistSelection ?? true,
9025
- persistOnDataUpdate: prevSelection?.persistOnDataUpdate ?? false,
9091
+ persistOnDataUpdate: prevSelection?.persistOnDataUpdate ?? true,
9026
9092
  };
9027
9093
  }
9028
9094
  changed = true;
@@ -9339,6 +9405,12 @@ class PraxisCrudComponent {
9339
9405
  items.push({
9340
9406
  id: action.id,
9341
9407
  label: action.title || action.id,
9408
+ title: action.title || action.id,
9409
+ description: action.description,
9410
+ operationId: action.operationId,
9411
+ requestSchemaId: action.requestSchemaId,
9412
+ requestSchemaUrl: action.requestSchemaUrl,
9413
+ commandParameterDescriptors: action.commandParameterDescriptors,
9342
9414
  icon: action.icon || (action.execution?.bulk?.mode === 'UNIFORM_UPDATE' ? 'edit_note' : 'playlist_add_check'),
9343
9415
  action: action.id,
9344
9416
  requiresSelection: true,
@@ -9346,7 +9418,7 @@ class PraxisCrudComponent {
9346
9418
  tooltip: !available
9347
9419
  ? translateUnavailableWorkflowMessage(this.i18n, capabilities.operations?.[action.id]?.availability || action.availability) || action.description
9348
9420
  : action.description,
9349
- execution: { bulk: action.execution.bulk },
9421
+ execution: action.execution,
9350
9422
  });
9351
9423
  }
9352
9424
  for (const [opKey, op] of Object.entries(capabilities.operations || {})) {
@@ -12054,6 +12126,8 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
12054
12126
  class PraxisResourcePage {
12055
12127
  route = inject(ActivatedRoute, { optional: true });
12056
12128
  runtimeContext = inject(EnterpriseRuntimeContextService, { optional: true });
12129
+ customizationMode = inject(PraxisCustomizationModeService, { optional: true });
12130
+ crudComponent;
12057
12131
  routeSub;
12058
12132
  contextSub;
12059
12133
  observedContext = false;
@@ -12069,7 +12143,7 @@ class PraxisResourcePage {
12069
12143
  scopeBar;
12070
12144
  scopeItems;
12071
12145
  defaultScopeId;
12072
- enableCustomization = false;
12146
+ enableCustomization;
12073
12147
  tableConfigPersistenceStrategy = 'local-first';
12074
12148
  context;
12075
12149
  actions;
@@ -12119,8 +12193,18 @@ class PraxisResourcePage {
12119
12193
  }, ...(ngDevMode ? [{ debugName: "effectiveDomain" }] : /* istanbul ignore next */ []));
12120
12194
  effectiveEnableCustomization = computed(() => {
12121
12195
  this.inputsVersion();
12122
- return this.enableCustomization || this.routeCustomizationSignal() || false;
12196
+ if (typeof this.enableCustomization === 'boolean') {
12197
+ return this.enableCustomization;
12198
+ }
12199
+ const routeCustomization = this.routeCustomizationSignal();
12200
+ if (typeof routeCustomization === 'boolean') {
12201
+ return routeCustomization;
12202
+ }
12203
+ return this.customizationMode?.isCustomizing() ?? false;
12123
12204
  }, ...(ngDevMode ? [{ debugName: "effectiveEnableCustomization" }] : /* istanbul ignore next */ []));
12205
+ openPageCustomization() {
12206
+ this.crudComponent?.onConfigureRequested();
12207
+ }
12124
12208
  effectiveScopeBar = computed(() => {
12125
12209
  this.inputsVersion();
12126
12210
  if (this.scopeBar)
@@ -12490,9 +12574,12 @@ class PraxisResourcePage {
12490
12574
  if (typeof data['enableCustomization'] === 'boolean') {
12491
12575
  this.routeCustomizationSignal.set(data['enableCustomization']);
12492
12576
  }
12577
+ else {
12578
+ this.routeCustomizationSignal.set(null);
12579
+ }
12493
12580
  }
12494
12581
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisResourcePage, deps: [], target: i0.ɵɵFactoryTarget.Component });
12495
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisResourcePage, isStandalone: true, selector: "praxis-resource-page", inputs: { resource: "resource", resourcePath: "resourcePath", crudId: "crudId", metadata: "metadata", title: "title", subtitle: "subtitle", domain: "domain", scopeBar: "scopeBar", scopeItems: "scopeItems", defaultScopeId: "defaultScopeId", enableCustomization: "enableCustomization", tableConfigPersistenceStrategy: "tableConfigPersistenceStrategy", context: "context", actions: "actions", filterCriteria: "filterCriteria" }, outputs: { rowClick: "rowClick", rowAction: "rowAction", rowDoubleClick: "rowDoubleClick", selectionChange: "selectionChange", drawerOpen: "drawerOpen", drawerClose: "drawerClose", afterOpen: "afterOpen", afterClose: "afterClose", kpiCardClick: "kpiCardClick", afterSave: "afterSave", afterDelete: "afterDelete", error: "error" }, usesOnChanges: true, ngImport: i0, template: `
12582
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisResourcePage, isStandalone: true, selector: "praxis-resource-page", inputs: { resource: "resource", resourcePath: "resourcePath", crudId: "crudId", metadata: "metadata", title: "title", subtitle: "subtitle", domain: "domain", scopeBar: "scopeBar", scopeItems: "scopeItems", defaultScopeId: "defaultScopeId", enableCustomization: "enableCustomization", tableConfigPersistenceStrategy: "tableConfigPersistenceStrategy", context: "context", actions: "actions", filterCriteria: "filterCriteria" }, outputs: { rowClick: "rowClick", rowAction: "rowAction", rowDoubleClick: "rowDoubleClick", selectionChange: "selectionChange", drawerOpen: "drawerOpen", drawerClose: "drawerClose", afterOpen: "afterOpen", afterClose: "afterClose", kpiCardClick: "kpiCardClick", afterSave: "afterSave", afterDelete: "afterDelete", error: "error" }, viewQueries: [{ propertyName: "crudComponent", first: true, predicate: PraxisCrudComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
12496
12583
  <div class="praxis-resource-page page-container">
12497
12584
  <!-- Auto-Header Governado -->
12498
12585
  @if (effectiveTitle() || effectiveDomain()) {
@@ -12518,6 +12605,17 @@ class PraxisResourcePage {
12518
12605
  </div>
12519
12606
 
12520
12607
  <div class="praxis-resource-page__header-actions">
12608
+ @if (effectiveEnableCustomization()) {
12609
+ <button
12610
+ type="button"
12611
+ class="praxis-resource-page__configure-btn"
12612
+ data-testid="resource-page-configure-btn"
12613
+ (click)="openPageCustomization()"
12614
+ >
12615
+ <span class="material-symbols-outlined">tune</span>
12616
+ <span>Configurar Página</span>
12617
+ </button>
12618
+ }
12521
12619
  <ng-content select="[page-header-actions]"></ng-content>
12522
12620
  </div>
12523
12621
  </header>
@@ -12564,7 +12662,7 @@ class PraxisResourcePage {
12564
12662
  }
12565
12663
  }
12566
12664
  </div>
12567
- `, isInline: true, styles: [":host{display:block;width:100%;min-height:100%;box-sizing:border-box}.praxis-resource-page{width:100%;display:flex;flex-direction:column;gap:20px;padding:var(--praxis-page-padding, 24px);box-sizing:border-box;max-width:1540px;margin:0 auto}.praxis-resource-page__header{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;flex-wrap:wrap}.praxis-resource-page__header-main{min-width:0;display:flex;flex-direction:column}.praxis-resource-page__domain-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:9999px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px;align-self:flex-start;background:var(--praxis-domain-badge-bg, rgba(255, 255, 255, .08));color:var(--praxis-domain-badge-color, inherit);border:1px solid var(--praxis-domain-badge-border, rgba(255, 255, 255, .1))}.praxis-resource-page__domain-badge.tone-primary,.praxis-resource-page__domain-badge.tone-info{background:color-mix(in srgb,var(--praxis-info, #0284c7) 14%,transparent);color:var(--praxis-info, #38bdf8);border-color:color-mix(in srgb,var(--praxis-info, #0284c7) 30%,transparent)}.praxis-resource-page__domain-badge.tone-success{background:color-mix(in srgb,var(--praxis-success, #059669) 14%,transparent);color:var(--praxis-success, #34d399);border-color:color-mix(in srgb,var(--praxis-success, #059669) 30%,transparent)}.praxis-resource-page__domain-badge.tone-warning{background:color-mix(in srgb,var(--praxis-warning, #d97706) 14%,transparent);color:var(--praxis-warning, #fbbf24);border-color:color-mix(in srgb,var(--praxis-warning, #d97706) 30%,transparent)}.praxis-resource-page__domain-badge.tone-danger,.praxis-resource-page__domain-badge.tone-error{background:color-mix(in srgb,var(--praxis-error, #dc2626) 14%,transparent);color:var(--praxis-error, #f87171);border-color:color-mix(in srgb,var(--praxis-error, #dc2626) 30%,transparent)}.praxis-resource-page__domain-badge span.material-symbols-outlined{font-size:14px}.praxis-resource-page__title{margin:0 0 6px;font-size:var(--praxis-page-title-size, 2rem);font-weight:800;letter-spacing:-.02em;color:var(--praxis-page-title-color, var(--foreground, inherit));line-height:1.2}.praxis-resource-page__subtitle{margin:0;font-size:var(--praxis-page-subtitle-size, .9rem);color:var(--muted-foreground, #94a3b8);max-width:800px;line-height:1.5}.praxis-resource-page__crud-surface{width:100%;border-radius:var(--praxis-surface-radius, 16px);overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: PraxisCrudComponent, selector: "praxis-crud", inputs: ["metadata", "crudId", "resource", "componentInstanceId", "tableConfigPersistenceStrategy", "context", "enableCustomization", "showResourceIdentityKey", "authoringCapability", "filterCriteria", "filterTeleport", "disableAutoDrawer"], outputs: ["configureRequested", "afterOpen", "afterClose", "afterSave", "afterDelete", "reactiveDeterminationExecuted", "reactiveDeterminationPendingChange", "reactiveDeterminationStabilityChange", "error", "rowClick", "rowAction", "selectionChange", "resourceEvent", "tableRuntimeConfigChange", "drawerOpen", "drawerClose", "crudAuthoringDocumentApplied", "crudAuthoringDocumentSaved", "kpiCardClick"] }, { kind: "component", type: PraxisScopeBarComponent, selector: "praxis-scope-bar", inputs: ["items", "activeId", "showOmnibox", "omniboxPlaceholder", "omniboxValue", "omniboxDebounceMs", "leadIcon", "leadLabel", "showClearButton", "clearLabel", "target", "targetWidgetId", "scopeBarId", "searchField"], outputs: ["activeIdChange", "omniboxValueChange", "scopeChange", "searchChange", "filterCriteriaChange", "clear"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
12665
+ `, isInline: true, styles: [":host{display:block;width:100%;min-height:100%;box-sizing:border-box}.praxis-resource-page{width:100%;display:flex;flex-direction:column;gap:20px;padding:var(--praxis-page-padding, 24px);box-sizing:border-box;max-width:1540px;margin:0 auto}.praxis-resource-page__header{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;flex-wrap:wrap}.praxis-resource-page__header-main{min-width:0;display:flex;flex-direction:column}.praxis-resource-page__domain-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:9999px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px;align-self:flex-start;background:var(--praxis-domain-badge-bg, rgba(255, 255, 255, .08));color:var(--praxis-domain-badge-color, inherit);border:1px solid var(--praxis-domain-badge-border, rgba(255, 255, 255, .1))}.praxis-resource-page__domain-badge.tone-primary,.praxis-resource-page__domain-badge.tone-info{background:color-mix(in srgb,var(--praxis-info, #0284c7) 14%,transparent);color:var(--praxis-info, #38bdf8);border-color:color-mix(in srgb,var(--praxis-info, #0284c7) 30%,transparent)}.praxis-resource-page__domain-badge.tone-success{background:color-mix(in srgb,var(--praxis-success, #059669) 14%,transparent);color:var(--praxis-success, #34d399);border-color:color-mix(in srgb,var(--praxis-success, #059669) 30%,transparent)}.praxis-resource-page__domain-badge.tone-warning{background:color-mix(in srgb,var(--praxis-warning, #d97706) 14%,transparent);color:var(--praxis-warning, #fbbf24);border-color:color-mix(in srgb,var(--praxis-warning, #d97706) 30%,transparent)}.praxis-resource-page__domain-badge.tone-danger,.praxis-resource-page__domain-badge.tone-error{background:color-mix(in srgb,var(--praxis-error, #dc2626) 14%,transparent);color:var(--praxis-error, #f87171);border-color:color-mix(in srgb,var(--praxis-error, #dc2626) 30%,transparent)}.praxis-resource-page__domain-badge span.material-symbols-outlined{font-size:14px}.praxis-resource-page__title{margin:0 0 6px;font-size:var(--praxis-page-title-size, 2rem);font-weight:800;letter-spacing:-.02em;color:var(--praxis-page-title-color, var(--foreground, inherit));line-height:1.2}.praxis-resource-page__subtitle{margin:0;font-size:var(--praxis-page-subtitle-size, .9rem);color:var(--muted-foreground, #94a3b8);max-width:800px;line-height:1.5}.praxis-resource-page__header-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.praxis-resource-page__configure-btn{display:inline-flex;align-items:center;gap:6px;height:36px;padding:0 14px;border-radius:10px;border:1px solid color-mix(in srgb,var(--praxis-primary, #6366f1) 35%,transparent);background:color-mix(in srgb,var(--praxis-primary, #6366f1) 14%,transparent);color:var(--praxis-primary, #818cf8);font-size:13px;font-weight:600;cursor:pointer;transition:all .16s ease}.praxis-resource-page__configure-btn:hover{background:color-mix(in srgb,var(--praxis-primary, #6366f1) 22%,transparent)}.praxis-resource-page__configure-btn .material-symbols-outlined{font-size:18px}.praxis-resource-page__crud-surface{width:100%;border-radius:var(--praxis-surface-radius, 16px);overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: PraxisCrudComponent, selector: "praxis-crud", inputs: ["metadata", "crudId", "resource", "componentInstanceId", "tableConfigPersistenceStrategy", "context", "enableCustomization", "showResourceIdentityKey", "authoringCapability", "filterCriteria", "filterTeleport", "disableAutoDrawer"], outputs: ["configureRequested", "afterOpen", "afterClose", "afterSave", "afterDelete", "reactiveDeterminationExecuted", "reactiveDeterminationPendingChange", "reactiveDeterminationStabilityChange", "error", "rowClick", "rowAction", "selectionChange", "resourceEvent", "tableRuntimeConfigChange", "drawerOpen", "drawerClose", "crudAuthoringDocumentApplied", "crudAuthoringDocumentSaved", "kpiCardClick"] }, { kind: "component", type: PraxisScopeBarComponent, selector: "praxis-scope-bar", inputs: ["items", "activeId", "showOmnibox", "omniboxPlaceholder", "omniboxValue", "omniboxDebounceMs", "leadIcon", "leadLabel", "showClearButton", "clearLabel", "target", "targetWidgetId", "scopeBarId", "searchField"], outputs: ["activeIdChange", "omniboxValueChange", "scopeChange", "searchChange", "filterCriteriaChange", "clear"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
12568
12666
  }
12569
12667
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisResourcePage, decorators: [{
12570
12668
  type: Component,
@@ -12594,6 +12692,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
12594
12692
  </div>
12595
12693
 
12596
12694
  <div class="praxis-resource-page__header-actions">
12695
+ @if (effectiveEnableCustomization()) {
12696
+ <button
12697
+ type="button"
12698
+ class="praxis-resource-page__configure-btn"
12699
+ data-testid="resource-page-configure-btn"
12700
+ (click)="openPageCustomization()"
12701
+ >
12702
+ <span class="material-symbols-outlined">tune</span>
12703
+ <span>Configurar Página</span>
12704
+ </button>
12705
+ }
12597
12706
  <ng-content select="[page-header-actions]"></ng-content>
12598
12707
  </div>
12599
12708
  </header>
@@ -12640,8 +12749,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
12640
12749
  }
12641
12750
  }
12642
12751
  </div>
12643
- `, styles: [":host{display:block;width:100%;min-height:100%;box-sizing:border-box}.praxis-resource-page{width:100%;display:flex;flex-direction:column;gap:20px;padding:var(--praxis-page-padding, 24px);box-sizing:border-box;max-width:1540px;margin:0 auto}.praxis-resource-page__header{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;flex-wrap:wrap}.praxis-resource-page__header-main{min-width:0;display:flex;flex-direction:column}.praxis-resource-page__domain-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:9999px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px;align-self:flex-start;background:var(--praxis-domain-badge-bg, rgba(255, 255, 255, .08));color:var(--praxis-domain-badge-color, inherit);border:1px solid var(--praxis-domain-badge-border, rgba(255, 255, 255, .1))}.praxis-resource-page__domain-badge.tone-primary,.praxis-resource-page__domain-badge.tone-info{background:color-mix(in srgb,var(--praxis-info, #0284c7) 14%,transparent);color:var(--praxis-info, #38bdf8);border-color:color-mix(in srgb,var(--praxis-info, #0284c7) 30%,transparent)}.praxis-resource-page__domain-badge.tone-success{background:color-mix(in srgb,var(--praxis-success, #059669) 14%,transparent);color:var(--praxis-success, #34d399);border-color:color-mix(in srgb,var(--praxis-success, #059669) 30%,transparent)}.praxis-resource-page__domain-badge.tone-warning{background:color-mix(in srgb,var(--praxis-warning, #d97706) 14%,transparent);color:var(--praxis-warning, #fbbf24);border-color:color-mix(in srgb,var(--praxis-warning, #d97706) 30%,transparent)}.praxis-resource-page__domain-badge.tone-danger,.praxis-resource-page__domain-badge.tone-error{background:color-mix(in srgb,var(--praxis-error, #dc2626) 14%,transparent);color:var(--praxis-error, #f87171);border-color:color-mix(in srgb,var(--praxis-error, #dc2626) 30%,transparent)}.praxis-resource-page__domain-badge span.material-symbols-outlined{font-size:14px}.praxis-resource-page__title{margin:0 0 6px;font-size:var(--praxis-page-title-size, 2rem);font-weight:800;letter-spacing:-.02em;color:var(--praxis-page-title-color, var(--foreground, inherit));line-height:1.2}.praxis-resource-page__subtitle{margin:0;font-size:var(--praxis-page-subtitle-size, .9rem);color:var(--muted-foreground, #94a3b8);max-width:800px;line-height:1.5}.praxis-resource-page__crud-surface{width:100%;border-radius:var(--praxis-surface-radius, 16px);overflow:hidden}\n"] }]
12644
- }], propDecorators: { resource: [{
12752
+ `, styles: [":host{display:block;width:100%;min-height:100%;box-sizing:border-box}.praxis-resource-page{width:100%;display:flex;flex-direction:column;gap:20px;padding:var(--praxis-page-padding, 24px);box-sizing:border-box;max-width:1540px;margin:0 auto}.praxis-resource-page__header{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;flex-wrap:wrap}.praxis-resource-page__header-main{min-width:0;display:flex;flex-direction:column}.praxis-resource-page__domain-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:9999px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px;align-self:flex-start;background:var(--praxis-domain-badge-bg, rgba(255, 255, 255, .08));color:var(--praxis-domain-badge-color, inherit);border:1px solid var(--praxis-domain-badge-border, rgba(255, 255, 255, .1))}.praxis-resource-page__domain-badge.tone-primary,.praxis-resource-page__domain-badge.tone-info{background:color-mix(in srgb,var(--praxis-info, #0284c7) 14%,transparent);color:var(--praxis-info, #38bdf8);border-color:color-mix(in srgb,var(--praxis-info, #0284c7) 30%,transparent)}.praxis-resource-page__domain-badge.tone-success{background:color-mix(in srgb,var(--praxis-success, #059669) 14%,transparent);color:var(--praxis-success, #34d399);border-color:color-mix(in srgb,var(--praxis-success, #059669) 30%,transparent)}.praxis-resource-page__domain-badge.tone-warning{background:color-mix(in srgb,var(--praxis-warning, #d97706) 14%,transparent);color:var(--praxis-warning, #fbbf24);border-color:color-mix(in srgb,var(--praxis-warning, #d97706) 30%,transparent)}.praxis-resource-page__domain-badge.tone-danger,.praxis-resource-page__domain-badge.tone-error{background:color-mix(in srgb,var(--praxis-error, #dc2626) 14%,transparent);color:var(--praxis-error, #f87171);border-color:color-mix(in srgb,var(--praxis-error, #dc2626) 30%,transparent)}.praxis-resource-page__domain-badge span.material-symbols-outlined{font-size:14px}.praxis-resource-page__title{margin:0 0 6px;font-size:var(--praxis-page-title-size, 2rem);font-weight:800;letter-spacing:-.02em;color:var(--praxis-page-title-color, var(--foreground, inherit));line-height:1.2}.praxis-resource-page__subtitle{margin:0;font-size:var(--praxis-page-subtitle-size, .9rem);color:var(--muted-foreground, #94a3b8);max-width:800px;line-height:1.5}.praxis-resource-page__header-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.praxis-resource-page__configure-btn{display:inline-flex;align-items:center;gap:6px;height:36px;padding:0 14px;border-radius:10px;border:1px solid color-mix(in srgb,var(--praxis-primary, #6366f1) 35%,transparent);background:color-mix(in srgb,var(--praxis-primary, #6366f1) 14%,transparent);color:var(--praxis-primary, #818cf8);font-size:13px;font-weight:600;cursor:pointer;transition:all .16s ease}.praxis-resource-page__configure-btn:hover{background:color-mix(in srgb,var(--praxis-primary, #6366f1) 22%,transparent)}.praxis-resource-page__configure-btn .material-symbols-outlined{font-size:18px}.praxis-resource-page__crud-surface{width:100%;border-radius:var(--praxis-surface-radius, 16px);overflow:hidden}\n"] }]
12753
+ }], propDecorators: { crudComponent: [{
12754
+ type: ViewChild,
12755
+ args: [PraxisCrudComponent]
12756
+ }], resource: [{
12645
12757
  type: Input
12646
12758
  }], resourcePath: [{
12647
12759
  type: Input
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@praxisui/crud",
3
- "version": "10.0.0-rc.16",
3
+ "version": "10.0.0-rc.18",
4
4
  "description": "CRUD building blocks for Praxis UI: integrates dynamic forms and tables with unified configuration and services.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.0.0",
7
7
  "@angular/core": "^21.0.0",
8
- "@praxisui/dynamic-form": "^10.0.0-rc.16",
9
- "@praxisui/table": "^10.0.0-rc.16",
10
- "@praxisui/core": "^10.0.0-rc.16",
11
- "@praxisui/dynamic-fields": "^10.0.0-rc.16",
12
- "@praxisui/settings-panel": "^10.0.0-rc.16",
8
+ "@praxisui/dynamic-form": "^10.0.0-rc.18",
9
+ "@praxisui/table": "^10.0.0-rc.18",
10
+ "@praxisui/core": "^10.0.0-rc.18",
11
+ "@praxisui/dynamic-fields": "^10.0.0-rc.18",
12
+ "@praxisui/settings-panel": "^10.0.0-rc.18",
13
13
  "@angular/cdk": "^21.0.0",
14
14
  "@angular/forms": "^21.0.0",
15
15
  "@angular/material": "^21.0.0",
16
16
  "@angular/router": "^21.0.0",
17
- "@praxisui/ai": "^10.0.0-rc.16",
17
+ "@praxisui/ai": "^10.0.0-rc.18",
18
18
  "rxjs": "~7.8.0"
19
19
  },
20
20
  "dependencies": {
@@ -1071,6 +1071,8 @@ interface PraxisResourceScopeBarConfig {
1071
1071
  declare class PraxisResourcePage implements OnInit, OnChanges, OnDestroy {
1072
1072
  private readonly route;
1073
1073
  private readonly runtimeContext;
1074
+ private readonly customizationMode;
1075
+ private crudComponent?;
1074
1076
  private routeSub?;
1075
1077
  private contextSub?;
1076
1078
  private observedContext;
@@ -1086,7 +1088,7 @@ declare class PraxisResourcePage implements OnInit, OnChanges, OnDestroy {
1086
1088
  scopeBar?: PraxisResourceScopeBarConfig;
1087
1089
  scopeItems?: PraxisScopeBarItem[];
1088
1090
  defaultScopeId?: string;
1089
- enableCustomization: boolean;
1091
+ enableCustomization?: boolean;
1090
1092
  tableConfigPersistenceStrategy: TableConfigPersistenceStrategy;
1091
1093
  context?: Record<string, unknown>;
1092
1094
  actions?: CrudAction[];
@@ -1132,6 +1134,7 @@ declare class PraxisResourcePage implements OnInit, OnChanges, OnDestroy {
1132
1134
  readonly effectiveSubtitle: _angular_core.Signal<string | null>;
1133
1135
  readonly effectiveDomain: _angular_core.Signal<PraxisResourceDomainBadge | null>;
1134
1136
  readonly effectiveEnableCustomization: _angular_core.Signal<boolean>;
1137
+ openPageCustomization(): void;
1135
1138
  readonly effectiveScopeBar: _angular_core.Signal<PraxisResourceScopeBarConfig | null>;
1136
1139
  readonly resolvedCrudId: _angular_core.Signal<string>;
1137
1140
  readonly effectiveMetadata: _angular_core.Signal<CrudMetadata>;