@praxisui/crud 7.0.0-beta.0 → 8.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -246,6 +246,13 @@ sequenceDiagram
246
246
  5. **Modal e drawer nao sao equivalentes internamente**: o ramo modal usa `DynamicFormDialogHostComponent`; o ramo drawer depende do adapter provido pelo host.
247
247
  6. **Save e delete geram refetch automatico da lista**: esse side effect faz parte do contrato atual do shell CRUD.
248
248
 
249
+ Nota de fronteira para formularios:
250
+
251
+ - `@praxisui/crud` nao redefine payload de formulario nem filtra campos locais.
252
+ - Campos locais/transientes pertencem ao contrato de `@praxisui/dynamic-form` via `fieldMetadata[].source`, `fieldMetadata[].transient` e `fieldMetadata[].submitPolicy`.
253
+ - No fluxo modal, `DynamicFormDialogHostComponent` recebe `formSubmit.formData` ja filtrado para persistencia. Valores completos de UI ficam em `formSubmit.rawFormData`.
254
+ - `actions[].form.initialValue` e `inputs` continuam sendo seed/contexto de abertura, nao campos automaticamente persistiveis.
255
+
249
256
  ## Documentacao Tecnica da Lib
250
257
 
251
258
  - `projects/praxis-crud/docs/host-crud-runtime-and-openmode.md`
@@ -4505,6 +4505,7 @@ class DynamicFormDialogHostComponent {
4505
4505
  submitMethod;
4506
4506
  apiEndpointKey;
4507
4507
  apiUrlEntry;
4508
+ formActions;
4508
4509
  mode = 'create';
4509
4510
  backConfig;
4510
4511
  idField = 'id';
@@ -4560,6 +4561,7 @@ class DynamicFormDialogHostComponent {
4560
4561
  this.apiUrlEntry = this.data.inputs?.['apiUrlEntry'] ?? null;
4561
4562
  const act = this.data.action?.action;
4562
4563
  this.mode = act === 'edit' ? 'edit' : act === 'view' ? 'view' : 'create';
4564
+ this.formActions = this.resolveFormActions();
4563
4565
  // Back config: defaults from metadata/action, overridden by saved per-form config
4564
4566
  const defaults = (this.data.action?.back || this.data.metadata?.defaults?.back) || {};
4565
4567
  this.backDefaults = defaults;
@@ -4616,6 +4618,47 @@ class DynamicFormDialogHostComponent {
4616
4618
  }
4617
4619
  return Object.keys(explicit).length ? explicit : null;
4618
4620
  }
4621
+ resolveFormActions() {
4622
+ if (this.mode === 'view') {
4623
+ return undefined;
4624
+ }
4625
+ const submitLabel = this.resolveSubmitLabel();
4626
+ if (!submitLabel) {
4627
+ return undefined;
4628
+ }
4629
+ return {
4630
+ showSaveButton: true,
4631
+ showCancelButton: false,
4632
+ showResetButton: false,
4633
+ submit: {
4634
+ id: 'submit',
4635
+ type: 'submit',
4636
+ color: 'primary',
4637
+ visible: true,
4638
+ label: submitLabel,
4639
+ },
4640
+ };
4641
+ }
4642
+ resolveSubmitLabel() {
4643
+ const action = this.data.action ?? {};
4644
+ const explicit = stringOrUndefined(action.form?.submitLabel ??
4645
+ action.submitLabel ??
4646
+ action.form?.actions?.submit?.label ??
4647
+ action.form?.actions?.submitButtonLabel);
4648
+ if (explicit) {
4649
+ return explicit;
4650
+ }
4651
+ const actionLabel = stringOrUndefined(action.label);
4652
+ if (this.mode === 'create') {
4653
+ return deriveCreateSubmitLabel(actionLabel);
4654
+ }
4655
+ if (this.mode === 'edit') {
4656
+ return actionLabel && !/^editar\b/i.test(actionLabel)
4657
+ ? actionLabel
4658
+ : 'Salvar alterações';
4659
+ }
4660
+ return undefined;
4661
+ }
4619
4662
  ngOnInit() {
4620
4663
  // Carregar estado salvo (se habilitado)
4621
4664
  if (this.rememberState && this.stateKey) {
@@ -4640,8 +4683,11 @@ class DynamicFormDialogHostComponent {
4640
4683
  }
4641
4684
  }
4642
4685
  onSave(result) {
4686
+ const stage = getFormSubmitStage(result);
4687
+ if (stage === 'before' || stage === 'error') {
4688
+ return;
4689
+ }
4643
4690
  if (this.modal.closeOnSave === false) {
4644
- // Não fechar: manter aberto e opcionalmente salvar estado atual
4645
4691
  this.saveState();
4646
4692
  return;
4647
4693
  }
@@ -4779,11 +4825,12 @@ class DynamicFormDialogHostComponent {
4779
4825
  [apiUrlEntry]="apiUrlEntry"
4780
4826
  [presentationModeGlobal]="mode === 'view' ? true : null"
4781
4827
  [backConfig]="backConfig"
4828
+ [actions]="formActions"
4782
4829
  (formSubmit)="onSave($event)"
4783
4830
  (formCancel)="onCancel()"
4784
4831
  ></praxis-dynamic-form>
4785
4832
  </mat-dialog-content>
4786
- `, isInline: true, styles: [":host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}:host([data-density=\"compact\"]){--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}.dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);padding:0 var(--dlg-pad);height:var(--dlg-header-h);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}.dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}.spacer{flex:1}.dialog-content{overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}.dialog-header button.mat-icon-button{color:var(--md-sys-color-on-surface-variant)}.dialog-header button.mat-icon-button:hover{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}.dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisDynamicForm, selector: "praxis-dynamic-form", inputs: ["resourcePath", "resourceId", "initialValue", "editorialContext", "mode", "config", "schemaSource", "schemaUrl", "submitUrl", "submitMethod", "responseSchemaUrl", "apiEndpointKey", "apiUrlEntry", "enableCustomization", "formId", "componentInstanceId", "layout", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "visibleGlobal", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "formReady", "valueChange", "syncCompleted", "initializationError", "loadingStateChange", "enableCustomizationChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError"] }] });
4833
+ `, isInline: true, styles: [":host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}:host([data-density=\"compact\"]){--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}.dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);padding:0 var(--dlg-pad);height:var(--dlg-header-h);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}.dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}.spacer{flex:1}.dialog-content{overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}.dialog-header button.mat-icon-button{color:var(--md-sys-color-on-surface-variant)}.dialog-header button.mat-icon-button:hover{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}.dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisDynamicForm, selector: "praxis-dynamic-form", inputs: ["resourcePath", "resourceId", "initialValue", "editorialContext", "mode", "config", "actions", "schemaSource", "schemaUrl", "submitUrl", "submitMethod", "responseSchemaUrl", "apiEndpointKey", "apiUrlEntry", "enableCustomization", "formId", "componentInstanceId", "layout", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "visibleGlobal", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "formReady", "valueChange", "syncCompleted", "initializationError", "loadingStateChange", "enableCustomizationChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError"] }] });
4787
4834
  }
4788
4835
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
4789
4836
  type: Component,
@@ -4841,6 +4888,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
4841
4888
  [apiUrlEntry]="apiUrlEntry"
4842
4889
  [presentationModeGlobal]="mode === 'view' ? true : null"
4843
4890
  [backConfig]="backConfig"
4891
+ [actions]="formActions"
4844
4892
  (formSubmit)="onSave($event)"
4845
4893
  (formCancel)="onCancel()"
4846
4894
  ></praxis-dynamic-form>
@@ -4859,6 +4907,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
4859
4907
  type: ViewChild,
4860
4908
  args: [PraxisDynamicForm]
4861
4909
  }] } });
4910
+ function getFormSubmitStage(result) {
4911
+ if (!result || typeof result !== 'object' || !('stage' in result)) {
4912
+ return null;
4913
+ }
4914
+ const stage = result.stage;
4915
+ return stage === 'before' || stage === 'after' || stage === 'error'
4916
+ ? stage
4917
+ : null;
4918
+ }
4919
+ function stringOrUndefined(value) {
4920
+ const text = String(value ?? '').trim();
4921
+ return text || undefined;
4922
+ }
4923
+ function deriveCreateSubmitLabel(actionLabel) {
4924
+ if (!actionLabel) {
4925
+ return 'Criar';
4926
+ }
4927
+ const match = actionLabel.match(/^(novo|nova|adicionar|incluir|criar)\s+(.+)$/i);
4928
+ if (match?.[2]) {
4929
+ return `Criar ${match[2].trim()}`;
4930
+ }
4931
+ return actionLabel;
4932
+ }
4862
4933
 
4863
4934
  var dynamicFormDialogHost_component = /*#__PURE__*/Object.freeze({
4864
4935
  __proto__: null,
package/index.d.ts CHANGED
@@ -336,12 +336,15 @@ declare class DynamicFormDialogHostComponent implements OnInit {
336
336
  submitMethod?: string | null;
337
337
  apiEndpointKey?: ApiEndpoint | string | null;
338
338
  apiUrlEntry?: ApiUrlEntry | null;
339
+ formActions?: FormConfig['actions'];
339
340
  mode: 'create' | 'edit' | 'view';
340
341
  backConfig?: BackConfig;
341
342
  private idField;
342
343
  texts: Record<string, string>;
343
344
  constructor(dialogRef: DialogRef<DynamicFormDialogHostComponent>, data: any, dialogService: DialogService, crud: GenericCrudService<any>, configStorage: AsyncConfigStorage);
344
345
  private extractInitialValue;
346
+ private resolveFormActions;
347
+ private resolveSubmitLabel;
345
348
  ngOnInit(): void;
346
349
  onSave(result: unknown): void;
347
350
  onCancel(): void;
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@praxisui/crud",
3
- "version": "7.0.0-beta.0",
3
+ "version": "8.0.0-beta.1",
4
4
  "description": "CRUD building blocks for Praxis UI: integrates dynamic forms and tables with unified configuration and services.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^20.1.0",
7
7
  "@angular/core": "^20.1.0",
8
- "@praxisui/dynamic-form": "^7.0.0-beta.0",
9
- "@praxisui/table": "^7.0.0-beta.0",
10
- "@praxisui/core": "^7.0.0-beta.0",
11
- "@praxisui/dynamic-fields": "^7.0.0-beta.0",
12
- "@praxisui/settings-panel": "^7.0.0-beta.0"
8
+ "@praxisui/dynamic-form": "^8.0.0-beta.1",
9
+ "@praxisui/table": "^8.0.0-beta.1",
10
+ "@praxisui/core": "^8.0.0-beta.1",
11
+ "@praxisui/dynamic-fields": "^8.0.0-beta.1",
12
+ "@praxisui/settings-panel": "^8.0.0-beta.1"
13
13
  },
14
14
  "dependencies": {
15
15
  "tslib": "^2.3.0"