@praxisui/crud 1.0.0-beta.5 → 1.0.0-beta.52

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,12 +1,13 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, InjectionToken, inject, EventEmitter, ViewChild, Output, Input, Component, DestroyRef, Inject, ENVIRONMENT_INITIALIZER } from '@angular/core';
2
+ import { Injectable, InjectionToken, inject, EventEmitter, DestroyRef, ViewChild, Output, Input, Component, Inject, ENVIRONMENT_INITIALIZER } from '@angular/core';
3
+ import { Router, ActivatedRoute, RouterLink } from '@angular/router';
3
4
  import { PraxisTable } from '@praxisui/table';
4
- import { Router, RouterLink } from '@angular/router';
5
+ import { firstValueFrom } from 'rxjs';
5
6
  import * as i1 from '@angular/material/dialog';
6
7
  import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
7
8
  export { MAT_DIALOG_DATA as DIALOG_DATA } from '@angular/material/dialog';
8
9
  import * as i2 from '@praxisui/core';
9
- import { CONFIG_STORAGE, GlobalConfigService, fillUndefined, EmptyStateCardComponent, PraxisIconDirective, GenericCrudService, ComponentMetadataRegistry } from '@praxisui/core';
10
+ import { ASYNC_CONFIG_STORAGE, GlobalConfigService, fillUndefined, createDefaultTableConfig, ComponentKeyService, EmptyStateCardComponent, PraxisIconDirective, GenericCrudService, ComponentMetadataRegistry } from '@praxisui/core';
10
11
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
11
12
  import { MatSnackBar } from '@angular/material/snack-bar';
12
13
  import * as i1$1 from '@angular/common';
@@ -17,7 +18,7 @@ import * as i5 from '@angular/material/icon';
17
18
  import { MatIconModule } from '@angular/material/icon';
18
19
  import { PraxisDynamicForm } from '@praxisui/dynamic-form';
19
20
  import { ConfirmDialogComponent } from '@praxisui/dynamic-fields';
20
- import { filter } from 'rxjs/operators';
21
+ import { filter, take } from 'rxjs/operators';
21
22
 
22
23
  class DialogService {
23
24
  matDialog;
@@ -33,20 +34,28 @@ class DialogService {
33
34
  const component = await loader();
34
35
  return this.zone.run(() => this.matDialog.open(component, config));
35
36
  }
36
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DialogService, deps: [{ token: i1.MatDialog }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
37
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DialogService, providedIn: 'root' });
37
+ 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 });
38
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DialogService, providedIn: 'root' });
38
39
  }
39
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DialogService, decorators: [{
40
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DialogService, decorators: [{
40
41
  type: Injectable,
41
42
  args: [{ providedIn: 'root' }]
42
43
  }], ctorParameters: () => [{ type: i1.MatDialog }, { type: i0.NgZone }] });
43
44
 
44
- const CRUD_DRAWER_ADAPTER = new InjectionToken('CRUD_DRAWER_ADAPTER');
45
+ function getCrudDrawerAdapterToken() {
46
+ const registryKey = '__PAX_CRUD_DRAWER_ADAPTER_TOKEN__';
47
+ const globalRegistry = globalThis;
48
+ if (!globalRegistry[registryKey]) {
49
+ globalRegistry[registryKey] = new InjectionToken('CRUD_DRAWER_ADAPTER');
50
+ }
51
+ return globalRegistry[registryKey];
52
+ }
53
+ const CRUD_DRAWER_ADAPTER = getCrudDrawerAdapterToken();
45
54
 
46
55
  class CrudLauncherService {
47
56
  router = inject(Router);
48
57
  dialog = inject(DialogService);
49
- storage = inject(CONFIG_STORAGE);
58
+ storage = inject(ASYNC_CONFIG_STORAGE);
50
59
  global = inject(GlobalConfigService);
51
60
  drawerAdapter = (() => {
52
61
  try {
@@ -56,9 +65,9 @@ class CrudLauncherService {
56
65
  return undefined;
57
66
  }
58
67
  })();
59
- async launch(action, row, metadata) {
68
+ async launch(action, row, metadata, componentKeyId, drawerCallbacks) {
60
69
  // Carregar overrides de CRUD (se houver) e mesclar em uma cópia local
61
- const merged = this.mergeCrudOverrides(metadata, action);
70
+ const merged = await this.mergeCrudOverrides(metadata, action, componentKeyId || undefined);
62
71
  const mode = this.resolveOpenMode(merged.action, merged.metadata);
63
72
  console.debug('[CRUD:Launcher] mode=', mode, 'action=', action);
64
73
  if (mode === 'route') {
@@ -75,7 +84,13 @@ class CrudLauncherService {
75
84
  if (row && inputs[idField] === undefined && row[idField] !== undefined) {
76
85
  inputs[idField] = row[idField];
77
86
  }
78
- await Promise.resolve(this.drawerAdapter.open({ action: merged.action, metadata: merged.metadata, inputs }));
87
+ await Promise.resolve(this.drawerAdapter.open({
88
+ action: merged.action,
89
+ metadata: merged.metadata,
90
+ inputs,
91
+ onClose: drawerCallbacks?.onClose,
92
+ onResult: drawerCallbacks?.onResult,
93
+ }));
79
94
  return { mode };
80
95
  }
81
96
  if (!merged.action.formId) {
@@ -195,13 +210,18 @@ class CrudLauncherService {
195
210
  });
196
211
  return inputs;
197
212
  }
198
- mergeCrudOverrides(metadata, action) {
213
+ async mergeCrudOverrides(metadata, action, componentKeyId) {
199
214
  try {
200
- const tableId = metadata.resource?.path || metadata.table?.resourcePath || 'default';
201
- const key = `crud-overrides:${tableId}`;
202
- const saved = this.storage.loadConfig(key) || {};
203
- const overDefaults = saved.defaults || {};
204
- const overActions = saved.actions || {};
215
+ if (!componentKeyId)
216
+ return { metadata, action };
217
+ const key = `crud-overrides:${componentKeyId}`;
218
+ let saved = null;
219
+ try {
220
+ saved = await firstValueFrom(this.storage.loadConfig(key));
221
+ }
222
+ catch { }
223
+ const overDefaults = saved?.defaults || {};
224
+ const overActions = saved?.actions || {};
205
225
  // Merge defaults (shallow for defaults, shallow for modal/back)
206
226
  let mergedDefaults = {
207
227
  ...(metadata.defaults || {}),
@@ -237,21 +257,29 @@ class CrudLauncherService {
237
257
  return { metadata, action };
238
258
  }
239
259
  }
240
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CrudLauncherService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
241
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CrudLauncherService, providedIn: 'root' });
260
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudLauncherService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
261
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudLauncherService, providedIn: 'root' });
242
262
  }
243
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CrudLauncherService, decorators: [{
263
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudLauncherService, decorators: [{
244
264
  type: Injectable,
245
265
  args: [{ providedIn: 'root' }]
246
266
  }] });
247
267
 
248
- function assertCrudMetadata(meta) {
268
+ function assertCrudMetadata(meta, options = {}) {
269
+ if (meta.component !== 'praxis-crud') {
270
+ throw new Error('Invalid component type for CRUD metadata');
271
+ }
272
+ if (!meta.table) {
273
+ throw new Error('Table config not provided for CRUD metadata');
274
+ }
249
275
  meta.actions?.forEach((action) => {
250
276
  const mode = action.openMode ?? meta.defaults?.openMode ?? 'route';
251
- if (mode === 'route' && !action.route) {
277
+ if (!options.allowDeferredActionBindings && mode === 'route' && !action.route) {
252
278
  throw new Error(`Route not provided for action ${action.action}`);
253
279
  }
254
- if ((mode === 'modal' || mode === 'drawer') && !action.formId) {
280
+ if (!options.allowDeferredActionBindings &&
281
+ (mode === 'modal' || mode === 'drawer') &&
282
+ !action.formId) {
255
283
  throw new Error(`formId not provided for action ${action.action}`);
256
284
  }
257
285
  action.params?.forEach((p) => {
@@ -263,10 +291,12 @@ function assertCrudMetadata(meta) {
263
291
  }
264
292
 
265
293
  class PraxisCrudComponent {
266
- /** Habilita visual de debug para alinhamento/layouot */
267
- debugLayout = false;
268
294
  /** JSON inline ou chave/URL resolvida pelo MetadataResolver */
269
295
  metadata;
296
+ /** Identificador obrigatório do CRUD (base para tabela/formulário) */
297
+ crudId;
298
+ /** Identificador opcional para instâncias múltiplas */
299
+ componentInstanceId;
270
300
  context;
271
301
  /** Encaminha o modo de edição de layout para a tabela interna */
272
302
  editModeEnabled = false;
@@ -280,10 +310,27 @@ class PraxisCrudComponent {
280
310
  resolvedMetadata;
281
311
  /** Configuração efetiva da tabela com melhorias automáticas (ex.: botão Adicionar). */
282
312
  effectiveTableConfig;
313
+ /** Config passado ao PraxisTable — sempre definido (fallback seguro). */
314
+ tableConfigForBinding = createDefaultTableConfig();
283
315
  launcher = inject(CrudLauncherService);
316
+ destroyRef = inject(DestroyRef);
284
317
  table;
285
- storage = inject(CONFIG_STORAGE);
318
+ storage = inject(ASYNC_CONFIG_STORAGE);
286
319
  snack = inject(MatSnackBar);
320
+ global = (() => { try {
321
+ return inject(GlobalConfigService);
322
+ }
323
+ catch {
324
+ return undefined;
325
+ } })();
326
+ componentKeys = inject(ComponentKeyService);
327
+ route = (() => { try {
328
+ return inject(ActivatedRoute);
329
+ }
330
+ catch {
331
+ return undefined;
332
+ } })();
333
+ warnedMissingId = false;
287
334
  /**
288
335
  * Stable CRUD context passed to PraxisTable.
289
336
  * Previously this was created via a getter, producing a new object each CD tick
@@ -292,9 +339,11 @@ class PraxisCrudComponent {
292
339
  tableCrudContext;
293
340
  onResetPreferences() {
294
341
  try {
295
- const id = this.resolvedMetadata?.resource?.path || 'default';
296
- const key = `crud-overrides:${id}`;
297
- this.storage.clearConfig(key);
342
+ const keyId = this.componentKeyId();
343
+ if (!keyId)
344
+ return;
345
+ const key = `crud-overrides:${keyId}`;
346
+ void firstValueFrom(this.storage.clearConfig(key)).catch(() => { });
298
347
  this.snack.open('Overrides de CRUD redefinidos', undefined, { duration: 2000 });
299
348
  }
300
349
  catch { }
@@ -302,12 +351,21 @@ class PraxisCrudComponent {
302
351
  ngOnChanges(changes) {
303
352
  if (changes['metadata']) {
304
353
  try {
305
- this.resolvedMetadata =
306
- typeof this.metadata === 'string'
307
- ? JSON.parse(this.metadata)
308
- : this.metadata;
309
- assertCrudMetadata(this.resolvedMetadata);
354
+ const parsed = typeof this.metadata === 'string'
355
+ ? JSON.parse(this.metadata)
356
+ : this.metadata;
357
+ this.resolvedMetadata = parsed;
358
+ // Runtime metadata must stay structurally valid, but route/formId can be
359
+ // completed later by launcher overrides, defaults or host policy.
360
+ assertCrudMetadata(this.resolvedMetadata, {
361
+ allowDeferredActionBindings: true,
362
+ });
310
363
  this.effectiveTableConfig = this.buildEffectiveTableConfig(this.resolvedMetadata);
364
+ // Evitar passar undefined para [config], o que sobrescreve o default do PraxisTable
365
+ this.tableConfigForBinding =
366
+ this.effectiveTableConfig ||
367
+ (this.resolvedMetadata.table ??
368
+ createDefaultTableConfig());
311
369
  // Build a stable table context when metadata changes
312
370
  this.tableCrudContext = this.buildTableCrudContext(this.resolvedMetadata);
313
371
  }
@@ -338,13 +396,39 @@ class PraxisCrudComponent {
338
396
  }
339
397
  }
340
398
  const effectiveAction = (actionMeta || { action });
341
- const { mode, ref } = await this.launcher.launch(effectiveAction, row, this.resolvedMetadata);
399
+ let drawerCloseEmitted = false;
400
+ const emitDrawerClose = () => {
401
+ if (drawerCloseEmitted)
402
+ return;
403
+ drawerCloseEmitted = true;
404
+ this.afterClose.emit();
405
+ };
406
+ const { mode, ref } = await this.launcher.launch(effectiveAction, row, this.resolvedMetadata, this.componentKeyId(), {
407
+ onClose: () => {
408
+ emitDrawerClose();
409
+ },
410
+ onResult: (result) => {
411
+ emitDrawerClose();
412
+ const data = (result?.data || {});
413
+ const idField = this.getIdField();
414
+ if (result?.type === 'save') {
415
+ const id = data?.[idField];
416
+ this.afterSave.emit({ id, data });
417
+ this.refreshTable();
418
+ }
419
+ if (result?.type === 'delete') {
420
+ const id = data?.[idField];
421
+ this.afterDelete.emit({ id });
422
+ this.refreshTable();
423
+ }
424
+ },
425
+ });
342
426
  this.afterOpen.emit({ mode, action: effectiveAction.action });
343
427
  if (ref) {
344
428
  const idField = this.getIdField();
345
429
  ref
346
430
  .afterClosed()
347
- .pipe(takeUntilDestroyed())
431
+ .pipe(takeUntilDestroyed(this.destroyRef))
348
432
  .subscribe((result) => {
349
433
  this.afterClose.emit();
350
434
  if (result?.type === 'save') {
@@ -369,6 +453,24 @@ class PraxisCrudComponent {
369
453
  refreshTable() {
370
454
  this.table.refetch();
371
455
  }
456
+ resolveResourcePath(meta) {
457
+ return (meta?.resource?.path ||
458
+ meta?.table?.resourcePath ||
459
+ '').trim();
460
+ }
461
+ resolveLocalData(meta) {
462
+ const localData = meta?.data;
463
+ return Array.isArray(localData) ? localData : null;
464
+ }
465
+ resolveTableData(meta) {
466
+ if (this.resolveResourcePath(meta).length > 0) {
467
+ return null;
468
+ }
469
+ return this.resolveLocalData(meta);
470
+ }
471
+ shouldRenderTable(meta) {
472
+ return this.resolveResourcePath(meta).length > 0 || Array.isArray(meta?.data);
473
+ }
372
474
  getIdField() {
373
475
  return this.resolvedMetadata?.resource?.idField || 'id';
374
476
  }
@@ -383,45 +485,77 @@ class PraxisCrudComponent {
383
485
  */
384
486
  buildEffectiveTableConfig(meta) {
385
487
  const base = meta.table;
386
- if (!base)
387
- return undefined;
388
- // Clonar para evitar mutações
389
- const cfg = JSON.parse(JSON.stringify(base));
488
+ // Clonar base ou criar default para permitir injeção de melhorias
489
+ const cfg = base
490
+ ? JSON.parse(JSON.stringify(base))
491
+ : createDefaultTableConfig();
492
+ let changed = false;
390
493
  const ensureToolbar = () => {
391
494
  if (!cfg.toolbar) {
392
495
  cfg.toolbar = { visible: true, position: 'top' };
393
496
  }
394
497
  return cfg.toolbar;
395
498
  };
396
- // Detectar se existe ação de adicionar na toolbar
499
+ // 1) Toolbar: injetar ação "Adicionar" se metadata declara create/add e toolbar não tiver
397
500
  const hasToolbarAdd = (cfg.toolbar?.actions || []).some((a) => this.isAddLike(a));
398
- // Procurar mapeamento de ação "adicionar/criar" nos metadados do CRUD
399
501
  const addAction = (meta.actions || []).find((a) => this.isAddLike(a));
400
- // Se não houver mapeamento "add/create", não vamos injetar nada
401
- if (!addAction) {
402
- return hasToolbarAdd ? cfg : undefined;
502
+ if (addAction) {
503
+ if (hasToolbarAdd) {
504
+ const tb = ensureToolbar();
505
+ tb.visible = true; // garantir visibilidade
506
+ }
507
+ else {
508
+ const tb = ensureToolbar();
509
+ tb.visible = true;
510
+ if (!tb.actions)
511
+ tb.actions = [];
512
+ const injected = {
513
+ id: addAction.id || 'add',
514
+ label: addAction.label || 'Adicionar',
515
+ icon: addAction.icon || 'add',
516
+ type: 'button',
517
+ color: 'primary',
518
+ position: 'end',
519
+ action: addAction.action || 'add',
520
+ };
521
+ tb.actions.push(injected);
522
+ }
523
+ changed = true;
403
524
  }
404
- // Se existe na toolbar, apenas garantir visibilidade se necessário
405
- if (hasToolbarAdd) {
406
- const tb = ensureToolbar();
407
- tb.visible = true;
408
- return cfg;
525
+ // 2) Row actions automáticas (apenas quando host não definiu explicitamente)
526
+ const hostDefinedRow = !!(base && base.actions && base.actions.row);
527
+ const crudDefaults = this.global.get('crud.defaults') || {};
528
+ const autoRow = crudDefaults.autoRowActions !== false; // default true
529
+ if (!hostDefinedRow && autoRow) {
530
+ const acts = (meta.actions || []);
531
+ const hasView = acts.some((a) => String(a.action).toLowerCase() === 'view');
532
+ const hasEdit = acts.some((a) => String(a.action).toLowerCase() === 'edit');
533
+ const includeDelete = !!crudDefaults.includeDeleteInRow && acts.some((a) => String(a.action).toLowerCase() === 'delete');
534
+ const rowActions = [];
535
+ if (hasView)
536
+ rowActions.push({ id: 'view', label: 'Ver', icon: 'visibility', action: 'view', tooltip: 'Ver' });
537
+ if (hasEdit)
538
+ rowActions.push({ id: 'edit', label: 'Editar', icon: 'edit', action: 'edit', tooltip: 'Editar' });
539
+ if (includeDelete)
540
+ rowActions.push({ id: 'delete', label: 'Excluir', icon: 'delete', action: 'delete', tooltip: 'Excluir' });
541
+ if (rowActions.length) {
542
+ cfg.actions = cfg.actions || {};
543
+ cfg.actions.row = {
544
+ enabled: true,
545
+ position: 'end',
546
+ width: cfg.actions?.row?.width || '120px',
547
+ display: crudDefaults.rowActionsDisplay || 'icons',
548
+ trigger: cfg.actions?.row?.trigger || 'hover',
549
+ actions: rowActions,
550
+ header: { label: cfg.actions?.row?.header?.label || '' },
551
+ };
552
+ changed = true;
553
+ }
554
+ }
555
+ // Se nada foi alterado e havia base, não retornar para preservar semântica anterior
556
+ if (!changed) {
557
+ return base ? undefined : cfg;
409
558
  }
410
- // Injetar ação na toolbar
411
- const tb = ensureToolbar();
412
- tb.visible = true;
413
- if (!tb.actions)
414
- tb.actions = [];
415
- const injected = {
416
- id: addAction.id || 'add',
417
- label: addAction.label || 'Adicionar',
418
- icon: addAction.icon || 'add',
419
- type: 'button',
420
- color: 'primary',
421
- position: 'end',
422
- action: addAction.action || 'add',
423
- };
424
- tb.actions.push(injected);
425
559
  return cfg;
426
560
  }
427
561
  /** Heurística leve para identificar ações do tipo "adicionar/criar" */
@@ -451,8 +585,9 @@ class PraxisCrudComponent {
451
585
  ? meta.actions
452
586
  : [{ action: 'create' }, { action: 'view' }, { action: 'edit' }];
453
587
  return {
454
- tableId: meta.resource?.path || meta.table?.resourcePath || 'default',
455
- resourcePath: meta.resource?.path || meta.table?.resourcePath,
588
+ tableId: this.crudId || 'default',
589
+ componentKeyId: this.componentKeyId() || undefined,
590
+ resourcePath: this.resolveResourcePath(meta) || undefined,
456
591
  defaults: meta.defaults,
457
592
  actions: baseActions.map((a) => ({
458
593
  action: a.action,
@@ -464,68 +599,90 @@ class PraxisCrudComponent {
464
599
  idField: meta.resource?.idField || 'id',
465
600
  };
466
601
  }
467
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PraxisCrudComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
468
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: PraxisCrudComponent, isStandalone: true, selector: "praxis-crud", inputs: { debugLayout: "debugLayout", metadata: "metadata", context: "context", editModeEnabled: "editModeEnabled" }, outputs: { configureRequested: "configureRequested", afterOpen: "afterOpen", afterClose: "afterClose", afterSave: "afterSave", afterDelete: "afterDelete", error: "error" }, viewQueries: [{ propertyName: "table", first: true, predicate: PraxisTable, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
469
- @if ($any(resolvedMetadata)?.resource?.path) {
602
+ componentKeyId() {
603
+ const key = this.componentKeys.buildComponentId({
604
+ componentType: 'praxis-crud',
605
+ componentId: this.crudId,
606
+ instanceKey: this.componentInstanceId,
607
+ componentRef: this,
608
+ route: this.route,
609
+ requireComponentId: true,
610
+ });
611
+ if (!key)
612
+ this.warnMissingId();
613
+ return key;
614
+ }
615
+ warnMissingId() {
616
+ if (this.warnedMissingId)
617
+ return;
618
+ this.warnedMissingId = true;
619
+ console.warn('[PraxisCrud] crudId is required for config persistence.');
620
+ }
621
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisCrudComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
622
+ 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", editModeEnabled: "editModeEnabled" }, outputs: { configureRequested: "configureRequested", afterOpen: "afterOpen", afterClose: "afterClose", afterSave: "afterSave", afterDelete: "afterDelete", error: "error" }, viewQueries: [{ propertyName: "table", first: true, predicate: PraxisTable, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
623
+ @if (shouldRenderTable(resolvedMetadata)) {
470
624
  <praxis-table
471
- [config]="effectiveTableConfig || resolvedMetadata.table"
472
- [resourcePath]="resolvedMetadata.resource?.path"
473
- [tableId]="resolvedMetadata.resource?.path || 'default'"
625
+ [config]="tableConfigForBinding"
626
+ [resourcePath]="resolveResourcePath(resolvedMetadata)"
627
+ [data]="resolveTableData(resolvedMetadata)"
628
+ [tableId]="crudId || 'default'"
474
629
  [crudContext]="tableCrudContext"
475
630
  [editModeEnabled]="editModeEnabled"
476
631
  (rowAction)="onAction($event.action, $event.row)"
477
632
  (toolbarAction)="onAction($event.action)"
478
633
  (reset)="onResetPreferences()"
479
- [debugLayout]="debugLayout"
480
634
  ></praxis-table>
481
635
  } @else {
482
636
  @if (editModeEnabled) {
483
637
  <praxis-empty-state-card
484
638
  icon="table_rows"
485
639
  [title]="'Conecte o CRUD a um recurso'"
486
- [description]="'Informe os metadados (resourcePath / schema) para habilitar a tabela e ações.'"
640
+ [description]="'Informe os metadados (resourcePath / schema) ou forneça metadata.data para habilitar a tabela e ações.'"
487
641
  [primaryAction]="{ label: 'Configurar metadados', icon: 'bolt', action: onConfigureRequested.bind(this) }"
488
642
  />
489
643
  }
490
644
  }
491
- `, isInline: true, dependencies: [{ kind: "component", type: PraxisTable, selector: "praxis-table", inputs: ["config", "resourcePath", "filterCriteria", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "showToolbar", "toolbarV2", "autoDelete", "editModeEnabled", "dense", "tableId", "debugLayout", "crudContext", "idField"], outputs: ["rowClick", "rowAction", "toolbarAction", "bulkAction", "rowDoubleClick", "schemaStatusChange", "beforeDelete", "afterDelete", "deleteError", "beforeBulkDelete", "afterBulkDelete", "bulkDeleteError"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline"] }] });
645
+ `, isInline: true, dependencies: [{ kind: "component", type: PraxisTable, selector: "praxis-table", inputs: ["config", "resourcePath", "data", "tableId", "componentInstanceId", "title", "subtitle", "icon", "autoDelete", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "crudContext", "dslFunctionRegistry", "editModeEnabled", "dense"], outputs: ["rowClick", "rowDoubleClick", "rowExpansionChange", "rowAction", "toolbarAction", "bulkAction", "columnReorder", "columnReorderAttempt", "beforeDelete", "afterDelete", "deleteError", "beforeBulkDelete", "afterBulkDelete", "bulkDeleteError", "schemaStatusChange", "metadataChange", "loadingStateChange"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone"] }] });
492
646
  }
493
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PraxisCrudComponent, decorators: [{
647
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisCrudComponent, decorators: [{
494
648
  type: Component,
495
649
  args: [{
496
650
  selector: 'praxis-crud',
497
651
  standalone: true,
498
652
  imports: [PraxisTable, EmptyStateCardComponent],
499
653
  template: `
500
- @if ($any(resolvedMetadata)?.resource?.path) {
654
+ @if (shouldRenderTable(resolvedMetadata)) {
501
655
  <praxis-table
502
- [config]="effectiveTableConfig || resolvedMetadata.table"
503
- [resourcePath]="resolvedMetadata.resource?.path"
504
- [tableId]="resolvedMetadata.resource?.path || 'default'"
656
+ [config]="tableConfigForBinding"
657
+ [resourcePath]="resolveResourcePath(resolvedMetadata)"
658
+ [data]="resolveTableData(resolvedMetadata)"
659
+ [tableId]="crudId || 'default'"
505
660
  [crudContext]="tableCrudContext"
506
661
  [editModeEnabled]="editModeEnabled"
507
662
  (rowAction)="onAction($event.action, $event.row)"
508
663
  (toolbarAction)="onAction($event.action)"
509
664
  (reset)="onResetPreferences()"
510
- [debugLayout]="debugLayout"
511
665
  ></praxis-table>
512
666
  } @else {
513
667
  @if (editModeEnabled) {
514
668
  <praxis-empty-state-card
515
669
  icon="table_rows"
516
670
  [title]="'Conecte o CRUD a um recurso'"
517
- [description]="'Informe os metadados (resourcePath / schema) para habilitar a tabela e ações.'"
671
+ [description]="'Informe os metadados (resourcePath / schema) ou forneça metadata.data para habilitar a tabela e ações.'"
518
672
  [primaryAction]="{ label: 'Configurar metadados', icon: 'bolt', action: onConfigureRequested.bind(this) }"
519
673
  />
520
674
  }
521
675
  }
522
676
  `,
523
677
  }]
524
- }], propDecorators: { debugLayout: [{
525
- type: Input
526
- }], metadata: [{
678
+ }], propDecorators: { metadata: [{
527
679
  type: Input,
528
680
  args: [{ required: true }]
681
+ }], crudId: [{
682
+ type: Input,
683
+ args: [{ required: true }]
684
+ }], componentInstanceId: [{
685
+ type: Input
529
686
  }], context: [{
530
687
  type: Input
531
688
  }], editModeEnabled: [{
@@ -559,6 +716,7 @@ class DynamicFormDialogHostComponent {
559
716
  initialSize = {};
560
717
  rememberState = false;
561
718
  stateKey;
719
+ backDefaults = {};
562
720
  destroyRef = inject(DestroyRef);
563
721
  resourcePath;
564
722
  resourceId;
@@ -610,10 +768,8 @@ class DynamicFormDialogHostComponent {
610
768
  this.mode = act === 'edit' ? 'edit' : act === 'view' ? 'view' : 'create';
611
769
  // Back config: defaults from metadata/action, overridden by saved per-form config
612
770
  const defaults = (this.data.action?.back || this.data.metadata?.defaults?.back) || {};
613
- const saved = this.data.action?.formId
614
- ? this.configStorage.loadConfig(`crud-back:${this.data.action.formId}`)
615
- : null;
616
- this.backConfig = { ...defaults, ...(saved || {}) };
771
+ this.backDefaults = defaults;
772
+ this.backConfig = { ...defaults };
617
773
  console.debug('[CRUD:Host] constructed', {
618
774
  action: this.data?.action,
619
775
  resourcePath: this.resourcePath,
@@ -644,34 +800,25 @@ class DynamicFormDialogHostComponent {
644
800
  }
645
801
  ngOnInit() {
646
802
  // Carregar estado salvo (se habilitado)
647
- const saved = this.rememberState && this.stateKey
648
- ? this.configStorage.loadConfig(this.stateKey)
649
- : null;
650
- this.initialSize = {
651
- width: saved?.width ?? this.modal.width,
652
- height: saved?.height ?? this.modal.height,
653
- };
654
- console.debug('[CRUD:Host] ngOnInit', {
655
- initialSize: this.initialSize,
656
- startMaximized: this.modal.startMaximized,
657
- fullscreenBreakpoint: this.modal.fullscreenBreakpoint,
658
- });
659
- let shouldMax = false;
660
- if (saved && typeof saved.maximized === 'boolean') {
661
- shouldMax = !!saved.maximized;
803
+ if (this.rememberState && this.stateKey) {
804
+ this.configStorage
805
+ .loadConfig(this.stateKey)
806
+ .pipe(take(1))
807
+ .subscribe((saved) => this.applySavedState(saved || null));
662
808
  }
663
809
  else {
664
- shouldMax =
665
- this.modal.startMaximized ||
666
- (this.modal.fullscreenBreakpoint &&
667
- window.innerWidth <= this.modal.fullscreenBreakpoint);
810
+ this.applySavedState(null);
668
811
  }
669
- if (shouldMax) {
670
- this.toggleMaximize(true);
671
- }
672
- else if (this.initialSize.width || this.initialSize.height) {
673
- this.dialogRef.updateSize(this.initialSize.width, this.initialSize.height);
674
- this.dialogRef.updatePosition();
812
+ // Carregar override de back config se houver formId
813
+ if (this.data.action?.formId) {
814
+ this.configStorage
815
+ .loadConfig(`crud-back:${this.data.action.formId}`)
816
+ .pipe(take(1))
817
+ .subscribe((saved) => {
818
+ if (saved && typeof saved === 'object') {
819
+ this.backConfig = { ...this.backDefaults, ...saved };
820
+ }
821
+ });
675
822
  }
676
823
  }
677
824
  onSave(result) {
@@ -695,6 +842,7 @@ class DynamicFormDialogHostComponent {
695
842
  cancelText: this.texts.discardCancel,
696
843
  type: 'warning',
697
844
  },
845
+ autoFocus: false,
698
846
  });
699
847
  ref
700
848
  .afterClosed()
@@ -731,15 +879,46 @@ class DynamicFormDialogHostComponent {
731
879
  const style = pane ? getComputedStyle(pane) : null;
732
880
  const currentWidth = style?.width || this.initialSize.width;
733
881
  const currentHeight = style?.height || this.initialSize.height;
734
- this.configStorage.saveConfig(this.stateKey, {
882
+ this.configStorage
883
+ .saveConfig(this.stateKey, {
735
884
  maximized: this.maximized,
736
885
  width: currentWidth,
737
886
  height: currentHeight,
887
+ })
888
+ .pipe(take(1))
889
+ .subscribe({ error: () => { } });
890
+ }
891
+ applySavedState(saved) {
892
+ this.initialSize = {
893
+ width: saved?.width ?? this.modal.width,
894
+ height: saved?.height ?? this.modal.height,
895
+ };
896
+ console.debug('[CRUD:Host] ngOnInit', {
897
+ initialSize: this.initialSize,
898
+ startMaximized: this.modal.startMaximized,
899
+ fullscreenBreakpoint: this.modal.fullscreenBreakpoint,
738
900
  });
901
+ let shouldMax = false;
902
+ if (saved && typeof saved.maximized === 'boolean') {
903
+ shouldMax = !!saved.maximized;
904
+ }
905
+ else {
906
+ shouldMax =
907
+ this.modal.startMaximized ||
908
+ (this.modal.fullscreenBreakpoint &&
909
+ window.innerWidth <= this.modal.fullscreenBreakpoint);
910
+ }
911
+ if (shouldMax) {
912
+ this.toggleMaximize(true);
913
+ }
914
+ else if (this.initialSize.width || this.initialSize.height) {
915
+ this.dialogRef.updateSize(this.initialSize.width, this.initialSize.height);
916
+ this.dialogRef.updatePosition();
917
+ }
739
918
  }
740
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DynamicFormDialogHostComponent, deps: [{ token: MatDialogRef }, { token: MAT_DIALOG_DATA }, { token: DialogService }, { token: i2.GenericCrudService }, { token: CONFIG_STORAGE }], target: i0.ɵɵFactoryTarget.Component });
741
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", 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: `
742
- <div class="dialog-header">
919
+ 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.GenericCrudService }, { token: ASYNC_CONFIG_STORAGE }], target: i0.ɵɵFactoryTarget.Component });
920
+ 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: `
921
+ <div mat-dialog-title class="dialog-header">
743
922
  <h2 id="crudDialogTitle" class="dialog-title">
744
923
  {{ data.action?.label || texts.title }}
745
924
  </h2>
@@ -749,13 +928,9 @@ class DynamicFormDialogHostComponent {
749
928
  mat-icon-button
750
929
  type="button"
751
930
  (click)="toggleMaximize()"
752
- [attr.aria-label]="
753
- maximized ? texts.restoreLabel : texts.maximizeLabel
754
- "
931
+ [attr.aria-label]="maximized ? texts.restoreLabel : texts.maximizeLabel"
755
932
  >
756
- <mat-icon>{{
757
- maximized ? 'close_fullscreen' : 'open_in_full'
758
- }}</mat-icon>
933
+ <mat-icon [praxisIcon]="maximized ? 'close_fullscreen' : 'open_in_full'"></mat-icon>
759
934
  </button>
760
935
  }
761
936
  <button
@@ -778,14 +953,15 @@ class DynamicFormDialogHostComponent {
778
953
  [resourcePath]="resourcePath"
779
954
  [resourceId]="resourceId"
780
955
  [mode]="mode"
956
+ [presentationModeGlobal]="mode === 'view' ? true : null"
781
957
  [backConfig]="backConfig"
782
958
  (formSubmit)="onSave($event)"
783
959
  (formCancel)="onCancel()"
784
960
  ></praxis-dynamic-form>
785
961
  </mat-dialog-content>
786
- `, 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);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:color-mix(in srgb,var(--md-sys-color-primary),transparent 90%)}.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.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.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.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", "mode", "config", "schemaSource", "editModeEnabled", "formId", "layout", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "visibleGlobal", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "formReady", "valueChange", "syncCompleted", "initializationError", "editModeEnabledChange", "customAction", "actionConfirmation", "schemaStatusChange"] }] });
962
+ `, 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: i4.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.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", "mode", "config", "schemaSource", "editModeEnabled", "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", "editModeEnabledChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError"] }] });
787
963
  }
788
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
964
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
789
965
  type: Component,
790
966
  args: [{ selector: 'praxis-dynamic-form-dialog-host', standalone: true, imports: [
791
967
  CommonModule,
@@ -798,7 +974,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
798
974
  class: 'praxis-dialog',
799
975
  '[attr.data-density]': 'modal.density || "default"',
800
976
  }, template: `
801
- <div class="dialog-header">
977
+ <div mat-dialog-title class="dialog-header">
802
978
  <h2 id="crudDialogTitle" class="dialog-title">
803
979
  {{ data.action?.label || texts.title }}
804
980
  </h2>
@@ -808,13 +984,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
808
984
  mat-icon-button
809
985
  type="button"
810
986
  (click)="toggleMaximize()"
811
- [attr.aria-label]="
812
- maximized ? texts.restoreLabel : texts.maximizeLabel
813
- "
987
+ [attr.aria-label]="maximized ? texts.restoreLabel : texts.maximizeLabel"
814
988
  >
815
- <mat-icon>{{
816
- maximized ? 'close_fullscreen' : 'open_in_full'
817
- }}</mat-icon>
989
+ <mat-icon [praxisIcon]="maximized ? 'close_fullscreen' : 'open_in_full'"></mat-icon>
818
990
  </button>
819
991
  }
820
992
  <button
@@ -837,12 +1009,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
837
1009
  [resourcePath]="resourcePath"
838
1010
  [resourceId]="resourceId"
839
1011
  [mode]="mode"
1012
+ [presentationModeGlobal]="mode === 'view' ? true : null"
840
1013
  [backConfig]="backConfig"
841
1014
  (formSubmit)="onSave($event)"
842
1015
  (formCancel)="onCancel()"
843
1016
  ></praxis-dynamic-form>
844
1017
  </mat-dialog-content>
845
- `, 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);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:color-mix(in srgb,var(--md-sys-color-primary),transparent 90%)}.dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}\n"] }]
1018
+ `, 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"] }]
846
1019
  }], ctorParameters: () => [{ type: undefined, decorators: [{
847
1020
  type: Inject,
848
1021
  args: [MatDialogRef]
@@ -851,7 +1024,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
851
1024
  args: [MAT_DIALOG_DATA]
852
1025
  }] }, { type: DialogService }, { type: i2.GenericCrudService }, { type: undefined, decorators: [{
853
1026
  type: Inject,
854
- args: [CONFIG_STORAGE]
1027
+ args: [ASYNC_CONFIG_STORAGE]
855
1028
  }] }], propDecorators: { formComp: [{
856
1029
  type: ViewChild,
857
1030
  args: [PraxisDynamicForm]
@@ -874,12 +1047,22 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
874
1047
  {
875
1048
  name: 'metadata',
876
1049
  type: 'CrudMetadata | string',
877
- description: 'Metadados de configuração do CRUD',
1050
+ description: 'Metadados de configuração do CRUD (fonte remota via resource.path ou local via metadata.data).',
1051
+ },
1052
+ {
1053
+ name: 'crudId',
1054
+ type: 'string',
1055
+ description: 'Identificador do CRUD (base para tabela/formulário e persistência)',
1056
+ },
1057
+ {
1058
+ name: 'componentInstanceId',
1059
+ type: 'string',
1060
+ description: 'Identificador opcional para múltiplas instâncias na mesma rota',
878
1061
  },
879
1062
  {
880
1063
  name: 'context',
881
1064
  type: 'Record<string, unknown>',
882
- description: 'Contexto adicional para resoluções',
1065
+ description: 'Contexto opaco do host. A implementacao atual o expõe como Input, mas nao o consome no launcher nem no tableCrudContext.',
883
1066
  },
884
1067
  {
885
1068
  name: 'editModeEnabled',
@@ -916,6 +1099,108 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
916
1099
  description: 'Emitido quando CTA de configuração é acionado em modo edição',
917
1100
  },
918
1101
  ],
1102
+ actions: [
1103
+ {
1104
+ id: 'create',
1105
+ label: 'Criar',
1106
+ icon: 'add',
1107
+ description: 'Emite evento ao abrir fluxo de criação',
1108
+ emit: 'afterOpen',
1109
+ payloadSchema: {
1110
+ type: 'object',
1111
+ properties: {
1112
+ action: { type: 'string', description: 'Ação executada' },
1113
+ mode: { type: 'string', description: 'Modo de abertura' },
1114
+ },
1115
+ required: ['action', 'mode'],
1116
+ example: { action: 'create', mode: 'route' },
1117
+ },
1118
+ scope: 'toolbar',
1119
+ },
1120
+ {
1121
+ id: 'view',
1122
+ label: 'Visualizar',
1123
+ icon: 'visibility',
1124
+ description: 'Emite evento ao abrir fluxo de visualização',
1125
+ emit: 'afterOpen',
1126
+ payloadSchema: {
1127
+ type: 'object',
1128
+ properties: {
1129
+ action: { type: 'string', description: 'Ação executada' },
1130
+ mode: { type: 'string', description: 'Modo de abertura' },
1131
+ },
1132
+ required: ['action', 'mode'],
1133
+ example: { action: 'view', mode: 'modal' },
1134
+ },
1135
+ scope: 'context',
1136
+ },
1137
+ {
1138
+ id: 'edit',
1139
+ label: 'Editar',
1140
+ icon: 'edit',
1141
+ description: 'Emite evento ao abrir fluxo de edição',
1142
+ emit: 'afterOpen',
1143
+ payloadSchema: {
1144
+ type: 'object',
1145
+ properties: {
1146
+ action: { type: 'string', description: 'Ação executada' },
1147
+ mode: { type: 'string', description: 'Modo de abertura' },
1148
+ },
1149
+ required: ['action', 'mode'],
1150
+ example: { action: 'edit', mode: 'drawer' },
1151
+ },
1152
+ scope: 'context',
1153
+ },
1154
+ {
1155
+ id: 'delete',
1156
+ label: 'Excluir',
1157
+ icon: 'delete',
1158
+ description: 'Emite evento após exclusão',
1159
+ emit: 'afterDelete',
1160
+ payloadSchema: {
1161
+ type: 'object',
1162
+ properties: {
1163
+ id: { type: 'string | number', description: 'ID do registro (string ou number)' },
1164
+ },
1165
+ required: ['id'],
1166
+ example: { id: '123' },
1167
+ },
1168
+ scope: 'context',
1169
+ },
1170
+ {
1171
+ id: 'save',
1172
+ label: 'Salvar',
1173
+ icon: 'save',
1174
+ description: 'Emite evento após salvar',
1175
+ emit: 'afterSave',
1176
+ payloadSchema: {
1177
+ type: 'object',
1178
+ properties: {
1179
+ id: { type: 'string | number', description: 'ID do registro (string ou number)' },
1180
+ data: { type: 'object', description: 'Dados salvos' },
1181
+ },
1182
+ required: ['id', 'data'],
1183
+ example: { id: '123', data: {} },
1184
+ },
1185
+ scope: 'shell',
1186
+ },
1187
+ {
1188
+ id: 'close',
1189
+ label: 'Fechar',
1190
+ icon: 'close',
1191
+ description: 'Emite evento ao fechar o diálogo',
1192
+ emit: 'afterClose',
1193
+ scope: 'shell',
1194
+ },
1195
+ {
1196
+ id: 'configure',
1197
+ label: 'Configurar',
1198
+ icon: 'tune',
1199
+ description: 'Emite evento ao abrir configuração',
1200
+ emit: 'configureRequested',
1201
+ scope: 'shell',
1202
+ },
1203
+ ],
919
1204
  tags: ['widget', 'crud', 'configurable', 'hasWizard', 'stable'],
920
1205
  lib: '@praxisui/crud',
921
1206
  };
@@ -939,8 +1224,8 @@ class CrudPageHeaderComponent {
939
1224
  sticky = true;
940
1225
  divider = true;
941
1226
  returnTo;
942
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CrudPageHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
943
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", 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: `
1227
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudPageHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1228
+ 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: `
944
1229
  <header
945
1230
  class="crud-header"
946
1231
  [class.sticky]="sticky"
@@ -966,9 +1251,9 @@ class CrudPageHeaderComponent {
966
1251
  <ng-content></ng-content>
967
1252
  </div>
968
1253
  </header>
969
- `, 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, transparent)}.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, rgba(0,0,0,.08))}.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:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}.back-btn.tonal{border-radius:20px;padding:4px 10px;background:var(--md-sys-color-surface-container, rgba(0,0,0,.02));box-shadow:inset 0 0 0 1px var(--md-sys-color-outline-variant, rgba(0,0,0,.06))}.back-btn.tonal:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 10%,var(--md-sys-color-surface-container))}.back-btn.outlined{border-radius:20px;padding:4px 10px;border:1px solid var(--md-sys-color-outline-variant, rgba(0,0,0,.12));background:var(--md-sys-color-surface, transparent)}.back-btn.outlined:hover{border-color:color-mix(in srgb,var(--md-sys-color-primary) 40%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary) 6%,transparent)}@media (max-width: 599px){.label.hide-on-narrow{display:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.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: i4.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.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }] });
1254
+ `, 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$1.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: i4.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.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }] });
970
1255
  }
971
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CrudPageHeaderComponent, decorators: [{
1256
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudPageHeaderComponent, decorators: [{
972
1257
  type: Component,
973
1258
  args: [{ selector: 'praxis-crud-page-header', standalone: true, imports: [CommonModule, RouterLink, MatButtonModule, MatIconModule, PraxisIconDirective], template: `
974
1259
  <header
@@ -996,7 +1281,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
996
1281
  <ng-content></ng-content>
997
1282
  </div>
998
1283
  </header>
999
- `, styles: [".crud-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 0;background:var(--md-sys-color-surface, transparent)}.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, rgba(0,0,0,.08))}.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:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}.back-btn.tonal{border-radius:20px;padding:4px 10px;background:var(--md-sys-color-surface-container, rgba(0,0,0,.02));box-shadow:inset 0 0 0 1px var(--md-sys-color-outline-variant, rgba(0,0,0,.06))}.back-btn.tonal:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 10%,var(--md-sys-color-surface-container))}.back-btn.outlined{border-radius:20px;padding:4px 10px;border:1px solid var(--md-sys-color-outline-variant, rgba(0,0,0,.12));background:var(--md-sys-color-surface, transparent)}.back-btn.outlined:hover{border-color:color-mix(in srgb,var(--md-sys-color-primary) 40%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary) 6%,transparent)}@media (max-width: 599px){.label.hide-on-narrow{display:none}}\n"] }]
1284
+ `, 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"] }]
1000
1285
  }], propDecorators: { title: [{
1001
1286
  type: Input
1002
1287
  }], backLabel: [{
@@ -1013,6 +1298,96 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1013
1298
  type: Input
1014
1299
  }] } });
1015
1300
 
1301
+ /**
1302
+ * Capabilities catalog for CrudMetadata (Praxis CRUD).
1303
+ */
1304
+ const ENUMS = {
1305
+ openMode: ['route', 'modal', 'drawer'],
1306
+ paramTarget: ['routeParam', 'query', 'input'],
1307
+ headerVariant: ['ghost', 'tonal', 'outlined'],
1308
+ toolbarActionType: ['button', 'icon', 'fab', 'menu'],
1309
+ toolbarActionAppearance: ['filled', 'outlined', 'elevated', 'text', 'tonal'],
1310
+ toolbarActionPosition: ['start', 'end'],
1311
+ backStrategy: ['auto', 'close', 'navigate'],
1312
+ };
1313
+ const CRUD_AI_CAPABILITIES = {
1314
+ version: 'v1.0',
1315
+ enums: ENUMS,
1316
+ targets: ['praxis-crud', 'crud-view', 'generic-crud'],
1317
+ notes: [
1318
+ 'CrudMetadata agrega TableConfig e FormConfig; use os catalogos de tabela e formulario para detalhes.',
1319
+ 'actions[] herda campos de RowAction e ToolbarAction; combine com openMode/route/formId quando aplicavel.',
1320
+ ],
1321
+ capabilities: [
1322
+ // --- Metadata root ---
1323
+ {
1324
+ path: 'component',
1325
+ category: 'meta',
1326
+ valueKind: 'enum',
1327
+ allowedValues: ['praxis-crud'],
1328
+ description: 'Identificador fixo do componente.',
1329
+ critical: true,
1330
+ },
1331
+ // --- Resource ---
1332
+ { path: 'resource', category: 'resource', valueKind: 'object', description: 'Recurso principal do CRUD.' },
1333
+ { path: 'resource.path', category: 'resource', valueKind: 'string', description: 'Endpoint base do recurso.' },
1334
+ { path: 'resource.idField', category: 'resource', valueKind: 'string', description: 'Campo identificador do recurso.' },
1335
+ { path: 'resource.endpointKey', category: 'resource', valueKind: 'string', description: 'Chave de endpoint (ApiEndpoint).' },
1336
+ // --- Table / Form ---
1337
+ { path: 'table', category: 'table', valueKind: 'object', description: 'TableConfig completo (usar catalogo de tabela).' },
1338
+ { path: 'form', category: 'form', valueKind: 'object', description: 'FormConfig completo (usar catalogo de formulario).' },
1339
+ // --- Defaults ---
1340
+ { path: 'defaults', category: 'defaults', valueKind: 'object', description: 'Defaults aplicados a acoes e UI.' },
1341
+ { path: 'defaults.openMode', category: 'defaults', valueKind: 'enum', allowedValues: ENUMS.openMode, description: 'Modo padrao de abertura (route/modal/drawer).' },
1342
+ { path: 'defaults.modal', category: 'defaults', valueKind: 'object', description: 'Config de dialog (DialogConfig).' },
1343
+ { path: 'defaults.back', category: 'navigation', valueKind: 'object', description: 'Config de retorno (BackConfig).' },
1344
+ { path: 'defaults.back.strategy', category: 'navigation', valueKind: 'enum', allowedValues: ENUMS.backStrategy, description: 'Estrategia de retorno.' },
1345
+ { path: 'defaults.back.returnTo', category: 'navigation', valueKind: 'string', description: 'Rota de retorno quando strategy=navigate.' },
1346
+ { path: 'defaults.back.confirmOnDirty', category: 'navigation', valueKind: 'boolean', description: 'Confirmar ao sair com alteracoes.' },
1347
+ { path: 'defaults.header', category: 'defaults', valueKind: 'object', description: 'Config do header do CRUD.' },
1348
+ { path: 'defaults.header.showBack', category: 'defaults', valueKind: 'boolean', description: 'Exibir botao voltar.' },
1349
+ { path: 'defaults.header.backLabel', category: 'defaults', valueKind: 'string', description: 'Label do botao voltar.' },
1350
+ { path: 'defaults.header.variant', category: 'defaults', valueKind: 'enum', allowedValues: ENUMS.headerVariant, description: 'Variante visual do header.' },
1351
+ { path: 'defaults.header.sticky', category: 'defaults', valueKind: 'boolean', description: 'Header fixo.' },
1352
+ { path: 'defaults.header.breadcrumbs', category: 'defaults', valueKind: 'boolean', description: 'Exibir breadcrumbs.' },
1353
+ { path: 'defaults.header.divider', category: 'defaults', valueKind: 'boolean', description: 'Exibir divisor no header.' },
1354
+ // --- Actions ---
1355
+ { path: 'actions', category: 'actions', valueKind: 'array', description: 'Acoes disponiveis no CRUD.' },
1356
+ { path: 'actions[].id', category: 'actions', valueKind: 'string', description: 'ID da acao (RowAction/ToolbarAction).' },
1357
+ { path: 'actions[].label', category: 'actions', valueKind: 'string', description: 'Label da acao.' },
1358
+ { path: 'actions[].icon', category: 'actions', valueKind: 'string', description: 'Icone da acao.' },
1359
+ { path: 'actions[].color', category: 'actions', valueKind: 'string', description: 'Cor da acao.' },
1360
+ { path: 'actions[].disabled', category: 'actions', valueKind: 'boolean', description: 'Acao desabilitada.' },
1361
+ { path: 'actions[].action', category: 'actions', valueKind: 'string', description: 'Nome da acao emitida.' },
1362
+ { path: 'actions[].tooltip', category: 'actions', valueKind: 'string', description: 'Tooltip da acao.' },
1363
+ { path: 'actions[].requiresConfirmation', category: 'actions', valueKind: 'boolean', description: 'Exige confirmacao.' },
1364
+ { path: 'actions[].visibleWhen', category: 'actions', valueKind: 'expression', description: 'Condicao de visibilidade (RowAction/ToolbarAction).' },
1365
+ { path: 'actions[].separator', category: 'actions', valueKind: 'boolean', description: 'Separador apos a acao (RowAction).' },
1366
+ { path: 'actions[].autoDelete', category: 'actions', valueKind: 'boolean', description: 'Habilita auto delete (RowAction).' },
1367
+ { path: 'actions[].deleteEndpoint', category: 'actions', valueKind: 'string', description: 'Endpoint custom para delete (RowAction).' },
1368
+ { path: 'actions[].type', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.toolbarActionType, description: 'Tipo de acao (ToolbarAction).' },
1369
+ { path: 'actions[].appearance', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.toolbarActionAppearance, description: 'Aparencia do botao (ToolbarAction).' },
1370
+ { path: 'actions[].position', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.toolbarActionPosition, description: 'Posicao da acao (ToolbarAction).' },
1371
+ { path: 'actions[].order', category: 'actions', valueKind: 'number', description: 'Ordem de exibicao (ToolbarAction).' },
1372
+ { path: 'actions[].shortcut', category: 'actions', valueKind: 'string', description: 'Atalho de teclado (ToolbarAction).' },
1373
+ { path: 'actions[].children', category: 'actions', valueKind: 'array', description: 'Sub-acoes (menu).' },
1374
+ { path: 'actions[].openMode', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.openMode, description: 'Modo de abertura da acao.' },
1375
+ { path: 'actions[].route', category: 'actions', valueKind: 'string', description: 'Rota para openMode=route.' },
1376
+ { path: 'actions[].formId', category: 'actions', valueKind: 'string', description: 'FormId canonico para modal/drawer; se ausente, o host precisa resolve-lo por override por acao antes da abertura.' },
1377
+ { path: 'actions[].params', category: 'actions', valueKind: 'array', description: 'Mapeamento de parametros.' },
1378
+ { path: 'actions[].params[].from', category: 'actions', valueKind: 'string', description: 'Origem do parametro.' },
1379
+ { path: 'actions[].params[].to', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.paramTarget, description: 'Destino do parametro.' },
1380
+ { path: 'actions[].params[].name', category: 'actions', valueKind: 'string', description: 'Nome do parametro no destino.' },
1381
+ { path: 'actions[].back', category: 'navigation', valueKind: 'object', description: 'BackConfig por acao.' },
1382
+ { path: 'actions[].back.strategy', category: 'navigation', valueKind: 'enum', allowedValues: ENUMS.backStrategy },
1383
+ { path: 'actions[].back.returnTo', category: 'navigation', valueKind: 'string' },
1384
+ { path: 'actions[].back.confirmOnDirty', category: 'navigation', valueKind: 'boolean' },
1385
+ // --- i18n ---
1386
+ { path: 'i18n', category: 'i18n', valueKind: 'object', description: 'Mensagens i18n do CRUD.' },
1387
+ { path: 'i18n.crudDialog', category: 'i18n', valueKind: 'object', description: 'Mapa de textos do dialog.' },
1388
+ ],
1389
+ };
1390
+
1016
1391
  /*
1017
1392
  * Public API Surface of praxis-crud
1018
1393
  */
@@ -1021,5 +1396,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1021
1396
  * Generated bundle index. Do not edit.
1022
1397
  */
1023
1398
 
1024
- export { CRUD_DRAWER_ADAPTER, CrudLauncherService, CrudPageHeaderComponent, DialogService, DynamicFormDialogHostComponent, PRAXIS_CRUD_COMPONENT_METADATA, PraxisCrudComponent, assertCrudMetadata, providePraxisCrudMetadata };
1399
+ export { CRUD_AI_CAPABILITIES, CrudLauncherService, CrudPageHeaderComponent, DialogService, DynamicFormDialogHostComponent, PRAXIS_CRUD_COMPONENT_METADATA, PraxisCrudComponent, assertCrudMetadata, providePraxisCrudMetadata };
1025
1400
  //# sourceMappingURL=praxisui-crud.mjs.map