@praxisui/crud 1.0.0-beta.8 → 3.0.0-beta.0

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,13 +291,15 @@ 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
- editModeEnabled = false;
302
+ enableCustomization = false;
273
303
  /** CTA: usado pelo Builder para abrir configuração de metadados quando vazio */
274
304
  configureRequested = new EventEmitter();
275
305
  afterOpen = new EventEmitter();
@@ -277,13 +307,38 @@ class PraxisCrudComponent {
277
307
  afterSave = new EventEmitter();
278
308
  afterDelete = new EventEmitter();
279
309
  error = new EventEmitter();
310
+ /**
311
+ * Emits the live PraxisTable configuration snapshot after runtime hydration or
312
+ * metadata refreshes. This reflects the effective table state in memory,
313
+ * deduplicated by structural JSON equality, and is intended for external hosts
314
+ * that need to mirror the table editor/runtime state.
315
+ */
316
+ tableRuntimeConfigChange = new EventEmitter();
280
317
  resolvedMetadata;
281
318
  /** Configuração efetiva da tabela com melhorias automáticas (ex.: botão Adicionar). */
282
319
  effectiveTableConfig;
320
+ /** Config passado ao PraxisTable — sempre definido (fallback seguro). */
321
+ tableConfigForBinding = createDefaultTableConfig();
283
322
  launcher = inject(CrudLauncherService);
323
+ destroyRef = inject(DestroyRef);
284
324
  table;
285
- storage = inject(CONFIG_STORAGE);
325
+ storage = inject(ASYNC_CONFIG_STORAGE);
286
326
  snack = inject(MatSnackBar);
327
+ global = (() => { try {
328
+ return inject(GlobalConfigService);
329
+ }
330
+ catch {
331
+ return undefined;
332
+ } })();
333
+ componentKeys = inject(ComponentKeyService);
334
+ route = (() => { try {
335
+ return inject(ActivatedRoute);
336
+ }
337
+ catch {
338
+ return undefined;
339
+ } })();
340
+ warnedMissingId = false;
341
+ lastEmittedTableRuntimeConfigJson = '';
287
342
  /**
288
343
  * Stable CRUD context passed to PraxisTable.
289
344
  * Previously this was created via a getter, producing a new object each CD tick
@@ -292,9 +347,11 @@ class PraxisCrudComponent {
292
347
  tableCrudContext;
293
348
  onResetPreferences() {
294
349
  try {
295
- const id = this.resolvedMetadata?.resource?.path || 'default';
296
- const key = `crud-overrides:${id}`;
297
- this.storage.clearConfig(key);
350
+ const keyId = this.componentKeyId();
351
+ if (!keyId)
352
+ return;
353
+ const key = `crud-overrides:${keyId}`;
354
+ void firstValueFrom(this.storage.clearConfig(key)).catch(() => { });
298
355
  this.snack.open('Overrides de CRUD redefinidos', undefined, { duration: 2000 });
299
356
  }
300
357
  catch { }
@@ -302,12 +359,21 @@ class PraxisCrudComponent {
302
359
  ngOnChanges(changes) {
303
360
  if (changes['metadata']) {
304
361
  try {
305
- this.resolvedMetadata =
306
- typeof this.metadata === 'string'
307
- ? JSON.parse(this.metadata)
308
- : this.metadata;
309
- assertCrudMetadata(this.resolvedMetadata);
362
+ const parsed = typeof this.metadata === 'string'
363
+ ? JSON.parse(this.metadata)
364
+ : this.metadata;
365
+ this.resolvedMetadata = parsed;
366
+ // Runtime metadata must stay structurally valid, but route/formId can be
367
+ // completed later by launcher overrides, defaults or host policy.
368
+ assertCrudMetadata(this.resolvedMetadata, {
369
+ allowDeferredActionBindings: true,
370
+ });
310
371
  this.effectiveTableConfig = this.buildEffectiveTableConfig(this.resolvedMetadata);
372
+ // Evitar passar undefined para [config], o que sobrescreve o default do PraxisTable
373
+ this.tableConfigForBinding =
374
+ this.effectiveTableConfig ||
375
+ (this.resolvedMetadata.table ??
376
+ createDefaultTableConfig());
311
377
  // Build a stable table context when metadata changes
312
378
  this.tableCrudContext = this.buildTableCrudContext(this.resolvedMetadata);
313
379
  }
@@ -338,13 +404,39 @@ class PraxisCrudComponent {
338
404
  }
339
405
  }
340
406
  const effectiveAction = (actionMeta || { action });
341
- const { mode, ref } = await this.launcher.launch(effectiveAction, row, this.resolvedMetadata);
407
+ let drawerCloseEmitted = false;
408
+ const emitDrawerClose = () => {
409
+ if (drawerCloseEmitted)
410
+ return;
411
+ drawerCloseEmitted = true;
412
+ this.afterClose.emit();
413
+ };
414
+ const { mode, ref } = await this.launcher.launch(effectiveAction, row, this.resolvedMetadata, this.componentKeyId(), {
415
+ onClose: () => {
416
+ emitDrawerClose();
417
+ },
418
+ onResult: (result) => {
419
+ emitDrawerClose();
420
+ const data = (result?.data || {});
421
+ const idField = this.getIdField();
422
+ if (result?.type === 'save') {
423
+ const id = data?.[idField];
424
+ this.afterSave.emit({ id, data });
425
+ this.refreshTable();
426
+ }
427
+ if (result?.type === 'delete') {
428
+ const id = data?.[idField];
429
+ this.afterDelete.emit({ id });
430
+ this.refreshTable();
431
+ }
432
+ },
433
+ });
342
434
  this.afterOpen.emit({ mode, action: effectiveAction.action });
343
435
  if (ref) {
344
436
  const idField = this.getIdField();
345
437
  ref
346
438
  .afterClosed()
347
- .pipe(takeUntilDestroyed())
439
+ .pipe(takeUntilDestroyed(this.destroyRef))
348
440
  .subscribe((result) => {
349
441
  this.afterClose.emit();
350
442
  if (result?.type === 'save') {
@@ -369,6 +461,61 @@ class PraxisCrudComponent {
369
461
  refreshTable() {
370
462
  this.table.refetch();
371
463
  }
464
+ getCurrentTableConfigSnapshot() {
465
+ const current = this.table?.config || this.tableConfigForBinding || this.effectiveTableConfig;
466
+ if (!current)
467
+ return null;
468
+ try {
469
+ return JSON.parse(JSON.stringify(current));
470
+ }
471
+ catch {
472
+ return current;
473
+ }
474
+ }
475
+ onTableMetadataChange() {
476
+ this.emitTableRuntimeConfigSnapshot();
477
+ }
478
+ onTableLoadingStateChange(state) {
479
+ if (!state || state.status !== 'success')
480
+ return;
481
+ if (state.phase !== 'render' && state.phase !== 'data' && state.phase !== 'schema') {
482
+ return;
483
+ }
484
+ this.emitTableRuntimeConfigSnapshot();
485
+ }
486
+ emitTableRuntimeConfigSnapshot() {
487
+ const snapshot = this.getCurrentTableConfigSnapshot();
488
+ if (!snapshot)
489
+ return;
490
+ try {
491
+ const nextJson = JSON.stringify(snapshot);
492
+ if (nextJson === this.lastEmittedTableRuntimeConfigJson)
493
+ return;
494
+ this.lastEmittedTableRuntimeConfigJson = nextJson;
495
+ }
496
+ catch {
497
+ // Fallback to emitting when serialization is not possible.
498
+ }
499
+ this.tableRuntimeConfigChange.emit(snapshot);
500
+ }
501
+ resolveResourcePath(meta) {
502
+ return (meta?.resource?.path ||
503
+ meta?.table?.resourcePath ||
504
+ '').trim();
505
+ }
506
+ resolveLocalData(meta) {
507
+ const localData = meta?.data;
508
+ return Array.isArray(localData) ? localData : null;
509
+ }
510
+ resolveTableData(meta) {
511
+ if (this.resolveResourcePath(meta).length > 0) {
512
+ return null;
513
+ }
514
+ return this.resolveLocalData(meta);
515
+ }
516
+ shouldRenderTable(meta) {
517
+ return this.resolveResourcePath(meta).length > 0 || Array.isArray(meta?.data);
518
+ }
372
519
  getIdField() {
373
520
  return this.resolvedMetadata?.resource?.idField || 'id';
374
521
  }
@@ -383,45 +530,77 @@ class PraxisCrudComponent {
383
530
  */
384
531
  buildEffectiveTableConfig(meta) {
385
532
  const base = meta.table;
386
- if (!base)
387
- return undefined;
388
- // Clonar para evitar mutações
389
- const cfg = JSON.parse(JSON.stringify(base));
533
+ // Clonar base ou criar default para permitir injeção de melhorias
534
+ const cfg = base
535
+ ? JSON.parse(JSON.stringify(base))
536
+ : createDefaultTableConfig();
537
+ let changed = false;
390
538
  const ensureToolbar = () => {
391
539
  if (!cfg.toolbar) {
392
540
  cfg.toolbar = { visible: true, position: 'top' };
393
541
  }
394
542
  return cfg.toolbar;
395
543
  };
396
- // Detectar se existe ação de adicionar na toolbar
544
+ // 1) Toolbar: injetar ação "Adicionar" se metadata declara create/add e toolbar não tiver
397
545
  const hasToolbarAdd = (cfg.toolbar?.actions || []).some((a) => this.isAddLike(a));
398
- // Procurar mapeamento de ação "adicionar/criar" nos metadados do CRUD
399
546
  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;
547
+ if (addAction) {
548
+ if (hasToolbarAdd) {
549
+ const tb = ensureToolbar();
550
+ tb.visible = true; // garantir visibilidade
551
+ }
552
+ else {
553
+ const tb = ensureToolbar();
554
+ tb.visible = true;
555
+ if (!tb.actions)
556
+ tb.actions = [];
557
+ const injected = {
558
+ id: addAction.id || 'add',
559
+ label: addAction.label || 'Adicionar',
560
+ icon: addAction.icon || 'add',
561
+ type: 'button',
562
+ color: 'primary',
563
+ position: 'end',
564
+ action: addAction.action || 'add',
565
+ };
566
+ tb.actions.push(injected);
567
+ }
568
+ changed = true;
403
569
  }
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;
570
+ // 2) Row actions automáticas (apenas quando host não definiu explicitamente)
571
+ const hostDefinedRow = !!(base && base.actions && base.actions.row);
572
+ const crudDefaults = this.global.get('crud.defaults') || {};
573
+ const autoRow = crudDefaults.autoRowActions !== false; // default true
574
+ if (!hostDefinedRow && autoRow) {
575
+ const acts = (meta.actions || []);
576
+ const hasView = acts.some((a) => String(a.action).toLowerCase() === 'view');
577
+ const hasEdit = acts.some((a) => String(a.action).toLowerCase() === 'edit');
578
+ const includeDelete = !!crudDefaults.includeDeleteInRow && acts.some((a) => String(a.action).toLowerCase() === 'delete');
579
+ const rowActions = [];
580
+ if (hasView)
581
+ rowActions.push({ id: 'view', label: 'Ver', icon: 'visibility', action: 'view', tooltip: 'Ver' });
582
+ if (hasEdit)
583
+ rowActions.push({ id: 'edit', label: 'Editar', icon: 'edit', action: 'edit', tooltip: 'Editar' });
584
+ if (includeDelete)
585
+ rowActions.push({ id: 'delete', label: 'Excluir', icon: 'delete', action: 'delete', tooltip: 'Excluir' });
586
+ if (rowActions.length) {
587
+ cfg.actions = cfg.actions || {};
588
+ cfg.actions.row = {
589
+ enabled: true,
590
+ position: 'end',
591
+ width: cfg.actions?.row?.width || '120px',
592
+ display: crudDefaults.rowActionsDisplay || 'icons',
593
+ trigger: cfg.actions?.row?.trigger || 'hover',
594
+ actions: rowActions,
595
+ header: { label: cfg.actions?.row?.header?.label || '' },
596
+ };
597
+ changed = true;
598
+ }
599
+ }
600
+ // Se nada foi alterado e havia base, não retornar para preservar semântica anterior
601
+ if (!changed) {
602
+ return base ? undefined : cfg;
409
603
  }
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
604
  return cfg;
426
605
  }
427
606
  /** Heurística leve para identificar ações do tipo "adicionar/criar" */
@@ -451,8 +630,9 @@ class PraxisCrudComponent {
451
630
  ? meta.actions
452
631
  : [{ action: 'create' }, { action: 'view' }, { action: 'edit' }];
453
632
  return {
454
- tableId: meta.resource?.path || meta.table?.resourcePath || 'default',
455
- resourcePath: meta.resource?.path || meta.table?.resourcePath,
633
+ tableId: this.crudId || 'default',
634
+ componentKeyId: this.componentKeyId() || undefined,
635
+ resourcePath: this.resolveResourcePath(meta) || undefined,
456
636
  defaults: meta.defaults,
457
637
  actions: baseActions.map((a) => ({
458
638
  action: a.action,
@@ -464,71 +644,97 @@ class PraxisCrudComponent {
464
644
  idField: meta.resource?.idField || 'id',
465
645
  };
466
646
  }
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) {
647
+ componentKeyId() {
648
+ const key = this.componentKeys.buildComponentId({
649
+ componentType: 'praxis-crud',
650
+ componentId: this.crudId,
651
+ instanceKey: this.componentInstanceId,
652
+ componentRef: this,
653
+ route: this.route,
654
+ requireComponentId: true,
655
+ });
656
+ if (!key)
657
+ this.warnMissingId();
658
+ return key;
659
+ }
660
+ warnMissingId() {
661
+ if (this.warnedMissingId)
662
+ return;
663
+ this.warnedMissingId = true;
664
+ console.warn('[PraxisCrud] crudId is required for config persistence.');
665
+ }
666
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisCrudComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
667
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisCrudComponent, isStandalone: true, selector: "praxis-crud", inputs: { metadata: "metadata", crudId: "crudId", componentInstanceId: "componentInstanceId", context: "context", enableCustomization: "enableCustomization" }, outputs: { configureRequested: "configureRequested", afterOpen: "afterOpen", afterClose: "afterClose", afterSave: "afterSave", afterDelete: "afterDelete", error: "error", tableRuntimeConfigChange: "tableRuntimeConfigChange" }, viewQueries: [{ propertyName: "table", first: true, predicate: PraxisTable, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
668
+ @if (shouldRenderTable(resolvedMetadata)) {
470
669
  <praxis-table
471
- [config]="effectiveTableConfig || resolvedMetadata.table"
472
- [resourcePath]="resolvedMetadata.resource?.path"
473
- [tableId]="resolvedMetadata.resource?.path || 'default'"
670
+ [config]="tableConfigForBinding"
671
+ [resourcePath]="resolveResourcePath(resolvedMetadata)"
672
+ [data]="resolveTableData(resolvedMetadata)"
673
+ [tableId]="crudId || 'default'"
474
674
  [crudContext]="tableCrudContext"
475
- [editModeEnabled]="editModeEnabled"
675
+ [enableCustomization]="enableCustomization"
476
676
  (rowAction)="onAction($event.action, $event.row)"
477
677
  (toolbarAction)="onAction($event.action)"
478
678
  (reset)="onResetPreferences()"
479
- [debugLayout]="debugLayout"
679
+ (metadataChange)="onTableMetadataChange()"
680
+ (loadingStateChange)="onTableLoadingStateChange($event)"
480
681
  ></praxis-table>
481
682
  } @else {
482
- @if (editModeEnabled) {
683
+ @if (enableCustomization) {
483
684
  <praxis-empty-state-card
484
685
  icon="table_rows"
485
686
  [title]="'Conecte o CRUD a um recurso'"
486
- [description]="'Informe os metadados (resourcePath / schema) para habilitar a tabela e ações.'"
687
+ [description]="'Informe os metadados (resourcePath / schema) ou forneça metadata.data para habilitar a tabela e ações.'"
487
688
  [primaryAction]="{ label: 'Configurar metadados', icon: 'bolt', action: onConfigureRequested.bind(this) }"
488
689
  />
489
690
  }
490
691
  }
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", "horizontalScroll", "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"] }] });
692
+ `, 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", "enableCustomization", "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
693
  }
493
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PraxisCrudComponent, decorators: [{
694
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisCrudComponent, decorators: [{
494
695
  type: Component,
495
696
  args: [{
496
697
  selector: 'praxis-crud',
497
698
  standalone: true,
498
699
  imports: [PraxisTable, EmptyStateCardComponent],
499
700
  template: `
500
- @if ($any(resolvedMetadata)?.resource?.path) {
701
+ @if (shouldRenderTable(resolvedMetadata)) {
501
702
  <praxis-table
502
- [config]="effectiveTableConfig || resolvedMetadata.table"
503
- [resourcePath]="resolvedMetadata.resource?.path"
504
- [tableId]="resolvedMetadata.resource?.path || 'default'"
703
+ [config]="tableConfigForBinding"
704
+ [resourcePath]="resolveResourcePath(resolvedMetadata)"
705
+ [data]="resolveTableData(resolvedMetadata)"
706
+ [tableId]="crudId || 'default'"
505
707
  [crudContext]="tableCrudContext"
506
- [editModeEnabled]="editModeEnabled"
708
+ [enableCustomization]="enableCustomization"
507
709
  (rowAction)="onAction($event.action, $event.row)"
508
710
  (toolbarAction)="onAction($event.action)"
509
711
  (reset)="onResetPreferences()"
510
- [debugLayout]="debugLayout"
712
+ (metadataChange)="onTableMetadataChange()"
713
+ (loadingStateChange)="onTableLoadingStateChange($event)"
511
714
  ></praxis-table>
512
715
  } @else {
513
- @if (editModeEnabled) {
716
+ @if (enableCustomization) {
514
717
  <praxis-empty-state-card
515
718
  icon="table_rows"
516
719
  [title]="'Conecte o CRUD a um recurso'"
517
- [description]="'Informe os metadados (resourcePath / schema) para habilitar a tabela e ações.'"
720
+ [description]="'Informe os metadados (resourcePath / schema) ou forneça metadata.data para habilitar a tabela e ações.'"
518
721
  [primaryAction]="{ label: 'Configurar metadados', icon: 'bolt', action: onConfigureRequested.bind(this) }"
519
722
  />
520
723
  }
521
724
  }
522
725
  `,
523
726
  }]
524
- }], propDecorators: { debugLayout: [{
525
- type: Input
526
- }], metadata: [{
727
+ }], propDecorators: { metadata: [{
728
+ type: Input,
729
+ args: [{ required: true }]
730
+ }], crudId: [{
527
731
  type: Input,
528
732
  args: [{ required: true }]
733
+ }], componentInstanceId: [{
734
+ type: Input
529
735
  }], context: [{
530
736
  type: Input
531
- }], editModeEnabled: [{
737
+ }], enableCustomization: [{
532
738
  type: Input
533
739
  }], configureRequested: [{
534
740
  type: Output
@@ -542,6 +748,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
542
748
  type: Output
543
749
  }], error: [{
544
750
  type: Output
751
+ }], tableRuntimeConfigChange: [{
752
+ type: Output
545
753
  }], table: [{
546
754
  type: ViewChild,
547
755
  args: [PraxisTable]
@@ -559,6 +767,7 @@ class DynamicFormDialogHostComponent {
559
767
  initialSize = {};
560
768
  rememberState = false;
561
769
  stateKey;
770
+ backDefaults = {};
562
771
  destroyRef = inject(DestroyRef);
563
772
  resourcePath;
564
773
  resourceId;
@@ -610,10 +819,8 @@ class DynamicFormDialogHostComponent {
610
819
  this.mode = act === 'edit' ? 'edit' : act === 'view' ? 'view' : 'create';
611
820
  // Back config: defaults from metadata/action, overridden by saved per-form config
612
821
  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 || {}) };
822
+ this.backDefaults = defaults;
823
+ this.backConfig = { ...defaults };
617
824
  console.debug('[CRUD:Host] constructed', {
618
825
  action: this.data?.action,
619
826
  resourcePath: this.resourcePath,
@@ -644,34 +851,25 @@ class DynamicFormDialogHostComponent {
644
851
  }
645
852
  ngOnInit() {
646
853
  // 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;
854
+ if (this.rememberState && this.stateKey) {
855
+ this.configStorage
856
+ .loadConfig(this.stateKey)
857
+ .pipe(take(1))
858
+ .subscribe((saved) => this.applySavedState(saved || null));
662
859
  }
663
860
  else {
664
- shouldMax =
665
- this.modal.startMaximized ||
666
- (this.modal.fullscreenBreakpoint &&
667
- window.innerWidth <= this.modal.fullscreenBreakpoint);
861
+ this.applySavedState(null);
668
862
  }
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();
863
+ // Carregar override de back config se houver formId
864
+ if (this.data.action?.formId) {
865
+ this.configStorage
866
+ .loadConfig(`crud-back:${this.data.action.formId}`)
867
+ .pipe(take(1))
868
+ .subscribe((saved) => {
869
+ if (saved && typeof saved === 'object') {
870
+ this.backConfig = { ...this.backDefaults, ...saved };
871
+ }
872
+ });
675
873
  }
676
874
  }
677
875
  onSave(result) {
@@ -695,6 +893,7 @@ class DynamicFormDialogHostComponent {
695
893
  cancelText: this.texts.discardCancel,
696
894
  type: 'warning',
697
895
  },
896
+ autoFocus: false,
698
897
  });
699
898
  ref
700
899
  .afterClosed()
@@ -731,15 +930,46 @@ class DynamicFormDialogHostComponent {
731
930
  const style = pane ? getComputedStyle(pane) : null;
732
931
  const currentWidth = style?.width || this.initialSize.width;
733
932
  const currentHeight = style?.height || this.initialSize.height;
734
- this.configStorage.saveConfig(this.stateKey, {
933
+ this.configStorage
934
+ .saveConfig(this.stateKey, {
735
935
  maximized: this.maximized,
736
936
  width: currentWidth,
737
937
  height: currentHeight,
938
+ })
939
+ .pipe(take(1))
940
+ .subscribe({ error: () => { } });
941
+ }
942
+ applySavedState(saved) {
943
+ this.initialSize = {
944
+ width: saved?.width ?? this.modal.width,
945
+ height: saved?.height ?? this.modal.height,
946
+ };
947
+ console.debug('[CRUD:Host] ngOnInit', {
948
+ initialSize: this.initialSize,
949
+ startMaximized: this.modal.startMaximized,
950
+ fullscreenBreakpoint: this.modal.fullscreenBreakpoint,
738
951
  });
952
+ let shouldMax = false;
953
+ if (saved && typeof saved.maximized === 'boolean') {
954
+ shouldMax = !!saved.maximized;
955
+ }
956
+ else {
957
+ shouldMax =
958
+ this.modal.startMaximized ||
959
+ (this.modal.fullscreenBreakpoint &&
960
+ window.innerWidth <= this.modal.fullscreenBreakpoint);
961
+ }
962
+ if (shouldMax) {
963
+ this.toggleMaximize(true);
964
+ }
965
+ else if (this.initialSize.width || this.initialSize.height) {
966
+ this.dialogRef.updateSize(this.initialSize.width, this.initialSize.height);
967
+ this.dialogRef.updatePosition();
968
+ }
739
969
  }
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">
970
+ 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 });
971
+ 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: `
972
+ <div mat-dialog-title class="dialog-header">
743
973
  <h2 id="crudDialogTitle" class="dialog-title">
744
974
  {{ data.action?.label || texts.title }}
745
975
  </h2>
@@ -749,13 +979,9 @@ class DynamicFormDialogHostComponent {
749
979
  mat-icon-button
750
980
  type="button"
751
981
  (click)="toggleMaximize()"
752
- [attr.aria-label]="
753
- maximized ? texts.restoreLabel : texts.maximizeLabel
754
- "
982
+ [attr.aria-label]="maximized ? texts.restoreLabel : texts.maximizeLabel"
755
983
  >
756
- <mat-icon>{{
757
- maximized ? 'close_fullscreen' : 'open_in_full'
758
- }}</mat-icon>
984
+ <mat-icon [praxisIcon]="maximized ? 'close_fullscreen' : 'open_in_full'"></mat-icon>
759
985
  </button>
760
986
  }
761
987
  <button
@@ -778,14 +1004,15 @@ class DynamicFormDialogHostComponent {
778
1004
  [resourcePath]="resourcePath"
779
1005
  [resourceId]="resourceId"
780
1006
  [mode]="mode"
1007
+ [presentationModeGlobal]="mode === 'view' ? true : null"
781
1008
  [backConfig]="backConfig"
782
1009
  (formSubmit)="onSave($event)"
783
1010
  (formCancel)="onCancel()"
784
1011
  ></praxis-dynamic-form>
785
1012
  </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"] }] });
1013
+ `, 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", "editorialContext", "mode", "config", "schemaSource", "enableCustomization", "formId", "componentInstanceId", "layout", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "visibleGlobal", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "formReady", "valueChange", "syncCompleted", "initializationError", "loadingStateChange", "enableCustomizationChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError", "widgetEvent"] }] });
787
1014
  }
788
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
1015
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
789
1016
  type: Component,
790
1017
  args: [{ selector: 'praxis-dynamic-form-dialog-host', standalone: true, imports: [
791
1018
  CommonModule,
@@ -798,7 +1025,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
798
1025
  class: 'praxis-dialog',
799
1026
  '[attr.data-density]': 'modal.density || "default"',
800
1027
  }, template: `
801
- <div class="dialog-header">
1028
+ <div mat-dialog-title class="dialog-header">
802
1029
  <h2 id="crudDialogTitle" class="dialog-title">
803
1030
  {{ data.action?.label || texts.title }}
804
1031
  </h2>
@@ -808,13 +1035,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
808
1035
  mat-icon-button
809
1036
  type="button"
810
1037
  (click)="toggleMaximize()"
811
- [attr.aria-label]="
812
- maximized ? texts.restoreLabel : texts.maximizeLabel
813
- "
1038
+ [attr.aria-label]="maximized ? texts.restoreLabel : texts.maximizeLabel"
814
1039
  >
815
- <mat-icon>{{
816
- maximized ? 'close_fullscreen' : 'open_in_full'
817
- }}</mat-icon>
1040
+ <mat-icon [praxisIcon]="maximized ? 'close_fullscreen' : 'open_in_full'"></mat-icon>
818
1041
  </button>
819
1042
  }
820
1043
  <button
@@ -837,12 +1060,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
837
1060
  [resourcePath]="resourcePath"
838
1061
  [resourceId]="resourceId"
839
1062
  [mode]="mode"
1063
+ [presentationModeGlobal]="mode === 'view' ? true : null"
840
1064
  [backConfig]="backConfig"
841
1065
  (formSubmit)="onSave($event)"
842
1066
  (formCancel)="onCancel()"
843
1067
  ></praxis-dynamic-form>
844
1068
  </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"] }]
1069
+ `, 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
1070
  }], ctorParameters: () => [{ type: undefined, decorators: [{
847
1071
  type: Inject,
848
1072
  args: [MatDialogRef]
@@ -851,7 +1075,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
851
1075
  args: [MAT_DIALOG_DATA]
852
1076
  }] }, { type: DialogService }, { type: i2.GenericCrudService }, { type: undefined, decorators: [{
853
1077
  type: Inject,
854
- args: [CONFIG_STORAGE]
1078
+ args: [ASYNC_CONFIG_STORAGE]
855
1079
  }] }], propDecorators: { formComp: [{
856
1080
  type: ViewChild,
857
1081
  args: [PraxisDynamicForm]
@@ -874,18 +1098,28 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
874
1098
  {
875
1099
  name: 'metadata',
876
1100
  type: 'CrudMetadata | string',
877
- description: 'Metadados de configuração do CRUD',
1101
+ description: 'Metadados de configuração do CRUD (fonte remota via resource.path ou local via metadata.data).',
1102
+ },
1103
+ {
1104
+ name: 'crudId',
1105
+ type: 'string',
1106
+ description: 'Identificador do CRUD (base para tabela/formulário e persistência)',
1107
+ },
1108
+ {
1109
+ name: 'componentInstanceId',
1110
+ type: 'string',
1111
+ description: 'Identificador opcional para múltiplas instâncias na mesma rota',
878
1112
  },
879
1113
  {
880
1114
  name: 'context',
881
1115
  type: 'Record<string, unknown>',
882
- description: 'Contexto adicional para resoluções',
1116
+ description: 'Contexto opaco do host. A implementacao atual o expõe como Input, mas nao o consome no launcher nem no tableCrudContext.',
883
1117
  },
884
1118
  {
885
- name: 'editModeEnabled',
1119
+ name: 'enableCustomization',
886
1120
  type: 'boolean',
887
1121
  default: false,
888
- description: 'Habilita modo de edição do layout',
1122
+ description: 'Habilita modo de customização do layout',
889
1123
  },
890
1124
  ],
891
1125
  outputs: [
@@ -916,6 +1150,108 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
916
1150
  description: 'Emitido quando CTA de configuração é acionado em modo edição',
917
1151
  },
918
1152
  ],
1153
+ actions: [
1154
+ {
1155
+ id: 'create',
1156
+ label: 'Criar',
1157
+ icon: 'add',
1158
+ description: 'Emite evento ao abrir fluxo de criação',
1159
+ emit: 'afterOpen',
1160
+ payloadSchema: {
1161
+ type: 'object',
1162
+ properties: {
1163
+ action: { type: 'string', description: 'Ação executada' },
1164
+ mode: { type: 'string', description: 'Modo de abertura' },
1165
+ },
1166
+ required: ['action', 'mode'],
1167
+ example: { action: 'create', mode: 'route' },
1168
+ },
1169
+ scope: 'toolbar',
1170
+ },
1171
+ {
1172
+ id: 'view',
1173
+ label: 'Visualizar',
1174
+ icon: 'visibility',
1175
+ description: 'Emite evento ao abrir fluxo de visualização',
1176
+ emit: 'afterOpen',
1177
+ payloadSchema: {
1178
+ type: 'object',
1179
+ properties: {
1180
+ action: { type: 'string', description: 'Ação executada' },
1181
+ mode: { type: 'string', description: 'Modo de abertura' },
1182
+ },
1183
+ required: ['action', 'mode'],
1184
+ example: { action: 'view', mode: 'modal' },
1185
+ },
1186
+ scope: 'context',
1187
+ },
1188
+ {
1189
+ id: 'edit',
1190
+ label: 'Editar',
1191
+ icon: 'edit',
1192
+ description: 'Emite evento ao abrir fluxo de edição',
1193
+ emit: 'afterOpen',
1194
+ payloadSchema: {
1195
+ type: 'object',
1196
+ properties: {
1197
+ action: { type: 'string', description: 'Ação executada' },
1198
+ mode: { type: 'string', description: 'Modo de abertura' },
1199
+ },
1200
+ required: ['action', 'mode'],
1201
+ example: { action: 'edit', mode: 'drawer' },
1202
+ },
1203
+ scope: 'context',
1204
+ },
1205
+ {
1206
+ id: 'delete',
1207
+ label: 'Excluir',
1208
+ icon: 'delete',
1209
+ description: 'Emite evento após exclusão',
1210
+ emit: 'afterDelete',
1211
+ payloadSchema: {
1212
+ type: 'object',
1213
+ properties: {
1214
+ id: { type: 'string | number', description: 'ID do registro (string ou number)' },
1215
+ },
1216
+ required: ['id'],
1217
+ example: { id: '123' },
1218
+ },
1219
+ scope: 'context',
1220
+ },
1221
+ {
1222
+ id: 'save',
1223
+ label: 'Salvar',
1224
+ icon: 'save',
1225
+ description: 'Emite evento após salvar',
1226
+ emit: 'afterSave',
1227
+ payloadSchema: {
1228
+ type: 'object',
1229
+ properties: {
1230
+ id: { type: 'string | number', description: 'ID do registro (string ou number)' },
1231
+ data: { type: 'object', description: 'Dados salvos' },
1232
+ },
1233
+ required: ['id', 'data'],
1234
+ example: { id: '123', data: {} },
1235
+ },
1236
+ scope: 'shell',
1237
+ },
1238
+ {
1239
+ id: 'close',
1240
+ label: 'Fechar',
1241
+ icon: 'close',
1242
+ description: 'Emite evento ao fechar o diálogo',
1243
+ emit: 'afterClose',
1244
+ scope: 'shell',
1245
+ },
1246
+ {
1247
+ id: 'configure',
1248
+ label: 'Configurar',
1249
+ icon: 'tune',
1250
+ description: 'Emite evento ao abrir configuração',
1251
+ emit: 'configureRequested',
1252
+ scope: 'shell',
1253
+ },
1254
+ ],
919
1255
  tags: ['widget', 'crud', 'configurable', 'hasWizard', 'stable'],
920
1256
  lib: '@praxisui/crud',
921
1257
  };
@@ -939,8 +1275,8 @@ class CrudPageHeaderComponent {
939
1275
  sticky = true;
940
1276
  divider = true;
941
1277
  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: `
1278
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudPageHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1279
+ 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
1280
  <header
945
1281
  class="crud-header"
946
1282
  [class.sticky]="sticky"
@@ -966,9 +1302,9 @@ class CrudPageHeaderComponent {
966
1302
  <ng-content></ng-content>
967
1303
  </div>
968
1304
  </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"] }] });
1305
+ `, 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
1306
  }
971
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CrudPageHeaderComponent, decorators: [{
1307
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudPageHeaderComponent, decorators: [{
972
1308
  type: Component,
973
1309
  args: [{ selector: 'praxis-crud-page-header', standalone: true, imports: [CommonModule, RouterLink, MatButtonModule, MatIconModule, PraxisIconDirective], template: `
974
1310
  <header
@@ -996,7 +1332,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
996
1332
  <ng-content></ng-content>
997
1333
  </div>
998
1334
  </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"] }]
1335
+ `, 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
1336
  }], propDecorators: { title: [{
1001
1337
  type: Input
1002
1338
  }], backLabel: [{
@@ -1013,6 +1349,96 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1013
1349
  type: Input
1014
1350
  }] } });
1015
1351
 
1352
+ /**
1353
+ * Capabilities catalog for CrudMetadata (Praxis CRUD).
1354
+ */
1355
+ const ENUMS = {
1356
+ openMode: ['route', 'modal', 'drawer'],
1357
+ paramTarget: ['routeParam', 'query', 'input'],
1358
+ headerVariant: ['ghost', 'tonal', 'outlined'],
1359
+ toolbarActionType: ['button', 'icon', 'fab', 'menu'],
1360
+ toolbarActionAppearance: ['filled', 'outlined', 'elevated', 'text', 'tonal'],
1361
+ toolbarActionPosition: ['start', 'end'],
1362
+ backStrategy: ['auto', 'close', 'navigate'],
1363
+ };
1364
+ const CRUD_AI_CAPABILITIES = {
1365
+ version: 'v1.0',
1366
+ enums: ENUMS,
1367
+ targets: ['praxis-crud', 'crud-view', 'generic-crud'],
1368
+ notes: [
1369
+ 'CrudMetadata agrega TableConfig e FormConfig; use os catalogos de tabela e formulario para detalhes.',
1370
+ 'actions[] herda campos de RowAction e ToolbarAction; combine com openMode/route/formId quando aplicavel.',
1371
+ ],
1372
+ capabilities: [
1373
+ // --- Metadata root ---
1374
+ {
1375
+ path: 'component',
1376
+ category: 'meta',
1377
+ valueKind: 'enum',
1378
+ allowedValues: ['praxis-crud'],
1379
+ description: 'Identificador fixo do componente.',
1380
+ critical: true,
1381
+ },
1382
+ // --- Resource ---
1383
+ { path: 'resource', category: 'resource', valueKind: 'object', description: 'Recurso principal do CRUD.' },
1384
+ { path: 'resource.path', category: 'resource', valueKind: 'string', description: 'Endpoint base do recurso.' },
1385
+ { path: 'resource.idField', category: 'resource', valueKind: 'string', description: 'Campo identificador do recurso.' },
1386
+ { path: 'resource.endpointKey', category: 'resource', valueKind: 'string', description: 'Chave de endpoint (ApiEndpoint).' },
1387
+ // --- Table / Form ---
1388
+ { path: 'table', category: 'table', valueKind: 'object', description: 'TableConfig completo (usar catalogo de tabela).' },
1389
+ { path: 'form', category: 'form', valueKind: 'object', description: 'FormConfig completo (usar catalogo de formulario).' },
1390
+ // --- Defaults ---
1391
+ { path: 'defaults', category: 'defaults', valueKind: 'object', description: 'Defaults aplicados a acoes e UI.' },
1392
+ { path: 'defaults.openMode', category: 'defaults', valueKind: 'enum', allowedValues: ENUMS.openMode, description: 'Modo padrao de abertura (route/modal/drawer).' },
1393
+ { path: 'defaults.modal', category: 'defaults', valueKind: 'object', description: 'Config de dialog (DialogConfig).' },
1394
+ { path: 'defaults.back', category: 'navigation', valueKind: 'object', description: 'Config de retorno (BackConfig).' },
1395
+ { path: 'defaults.back.strategy', category: 'navigation', valueKind: 'enum', allowedValues: ENUMS.backStrategy, description: 'Estrategia de retorno.' },
1396
+ { path: 'defaults.back.returnTo', category: 'navigation', valueKind: 'string', description: 'Rota de retorno quando strategy=navigate.' },
1397
+ { path: 'defaults.back.confirmOnDirty', category: 'navigation', valueKind: 'boolean', description: 'Confirmar ao sair com alteracoes.' },
1398
+ { path: 'defaults.header', category: 'defaults', valueKind: 'object', description: 'Config do header do CRUD.' },
1399
+ { path: 'defaults.header.showBack', category: 'defaults', valueKind: 'boolean', description: 'Exibir botao voltar.' },
1400
+ { path: 'defaults.header.backLabel', category: 'defaults', valueKind: 'string', description: 'Label do botao voltar.' },
1401
+ { path: 'defaults.header.variant', category: 'defaults', valueKind: 'enum', allowedValues: ENUMS.headerVariant, description: 'Variante visual do header.' },
1402
+ { path: 'defaults.header.sticky', category: 'defaults', valueKind: 'boolean', description: 'Header fixo.' },
1403
+ { path: 'defaults.header.breadcrumbs', category: 'defaults', valueKind: 'boolean', description: 'Exibir breadcrumbs.' },
1404
+ { path: 'defaults.header.divider', category: 'defaults', valueKind: 'boolean', description: 'Exibir divisor no header.' },
1405
+ // --- Actions ---
1406
+ { path: 'actions', category: 'actions', valueKind: 'array', description: 'Acoes disponiveis no CRUD.' },
1407
+ { path: 'actions[].id', category: 'actions', valueKind: 'string', description: 'ID da acao (RowAction/ToolbarAction).' },
1408
+ { path: 'actions[].label', category: 'actions', valueKind: 'string', description: 'Label da acao.' },
1409
+ { path: 'actions[].icon', category: 'actions', valueKind: 'string', description: 'Icone da acao.' },
1410
+ { path: 'actions[].color', category: 'actions', valueKind: 'string', description: 'Cor da acao.' },
1411
+ { path: 'actions[].disabled', category: 'actions', valueKind: 'boolean', description: 'Acao desabilitada.' },
1412
+ { path: 'actions[].action', category: 'actions', valueKind: 'string', description: 'Nome da acao emitida.' },
1413
+ { path: 'actions[].tooltip', category: 'actions', valueKind: 'string', description: 'Tooltip da acao.' },
1414
+ { path: 'actions[].requiresConfirmation', category: 'actions', valueKind: 'boolean', description: 'Exige confirmacao.' },
1415
+ { path: 'actions[].visibleWhen', category: 'actions', valueKind: 'expression', description: 'Condicao de visibilidade (RowAction/ToolbarAction).' },
1416
+ { path: 'actions[].separator', category: 'actions', valueKind: 'boolean', description: 'Separador apos a acao (RowAction).' },
1417
+ { path: 'actions[].autoDelete', category: 'actions', valueKind: 'boolean', description: 'Habilita auto delete (RowAction).' },
1418
+ { path: 'actions[].deleteEndpoint', category: 'actions', valueKind: 'string', description: 'Endpoint custom para delete (RowAction).' },
1419
+ { path: 'actions[].type', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.toolbarActionType, description: 'Tipo de acao (ToolbarAction).' },
1420
+ { path: 'actions[].appearance', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.toolbarActionAppearance, description: 'Aparencia do botao (ToolbarAction).' },
1421
+ { path: 'actions[].position', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.toolbarActionPosition, description: 'Posicao da acao (ToolbarAction).' },
1422
+ { path: 'actions[].order', category: 'actions', valueKind: 'number', description: 'Ordem de exibicao (ToolbarAction).' },
1423
+ { path: 'actions[].shortcut', category: 'actions', valueKind: 'string', description: 'Atalho de teclado (ToolbarAction).' },
1424
+ { path: 'actions[].children', category: 'actions', valueKind: 'array', description: 'Sub-acoes (menu).' },
1425
+ { path: 'actions[].openMode', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.openMode, description: 'Modo de abertura da acao.' },
1426
+ { path: 'actions[].route', category: 'actions', valueKind: 'string', description: 'Rota para openMode=route.' },
1427
+ { 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.' },
1428
+ { path: 'actions[].params', category: 'actions', valueKind: 'array', description: 'Mapeamento de parametros.' },
1429
+ { path: 'actions[].params[].from', category: 'actions', valueKind: 'string', description: 'Origem do parametro.' },
1430
+ { path: 'actions[].params[].to', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.paramTarget, description: 'Destino do parametro.' },
1431
+ { path: 'actions[].params[].name', category: 'actions', valueKind: 'string', description: 'Nome do parametro no destino.' },
1432
+ { path: 'actions[].back', category: 'navigation', valueKind: 'object', description: 'BackConfig por acao.' },
1433
+ { path: 'actions[].back.strategy', category: 'navigation', valueKind: 'enum', allowedValues: ENUMS.backStrategy, description: 'Estrategia de retorno da acao (auto, close, navigate).' },
1434
+ { path: 'actions[].back.returnTo', category: 'navigation', valueKind: 'string', description: 'Rota ou destino usado quando a estrategia de retorno for navigate.' },
1435
+ { path: 'actions[].back.confirmOnDirty', category: 'navigation', valueKind: 'boolean', description: 'Solicita confirmacao antes de sair com alteracoes nao salvas.' },
1436
+ // --- i18n ---
1437
+ { path: 'i18n', category: 'i18n', valueKind: 'object', description: 'Mensagens i18n do CRUD.' },
1438
+ { path: 'i18n.crudDialog', category: 'i18n', valueKind: 'object', description: 'Mapa de textos do dialog.' },
1439
+ ],
1440
+ };
1441
+
1016
1442
  /*
1017
1443
  * Public API Surface of praxis-crud
1018
1444
  */
@@ -1021,5 +1447,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1021
1447
  * Generated bundle index. Do not edit.
1022
1448
  */
1023
1449
 
1024
- export { CRUD_DRAWER_ADAPTER, CrudLauncherService, CrudPageHeaderComponent, DialogService, DynamicFormDialogHostComponent, PRAXIS_CRUD_COMPONENT_METADATA, PraxisCrudComponent, assertCrudMetadata, providePraxisCrudMetadata };
1450
+ export { CRUD_AI_CAPABILITIES, CrudLauncherService, CrudPageHeaderComponent, DialogService, DynamicFormDialogHostComponent, PRAXIS_CRUD_COMPONENT_METADATA, PraxisCrudComponent, assertCrudMetadata, providePraxisCrudMetadata };
1025
1451
  //# sourceMappingURL=praxisui-crud.mjs.map