@praxisui/dynamic-form 1.0.0-beta.7 → 2.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.
- package/README.md +500 -15
- package/fesm2022/praxisui-dynamic-form.mjs +19189 -9948
- package/fesm2022/praxisui-dynamic-form.mjs.map +1 -1
- package/index.d.ts +740 -67
- package/package.json +13 -12
package/README.md
CHANGED
|
@@ -1,7 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Dynamic Form"
|
|
3
|
+
slug: "dynamic-form-overview"
|
|
4
|
+
description: "Visao geral do @praxisui/dynamic-form com schema-driven UI, editor embutido, layout runtime e integracao orientada a contrato."
|
|
5
|
+
doc_type: "reference"
|
|
6
|
+
document_kind: "component-overview"
|
|
7
|
+
component: "dynamic-form"
|
|
8
|
+
category: "components"
|
|
9
|
+
audience:
|
|
10
|
+
- "frontend"
|
|
11
|
+
- "host"
|
|
12
|
+
- "architect"
|
|
13
|
+
level: "intermediate"
|
|
14
|
+
status: "active"
|
|
15
|
+
owner: "praxis-ui"
|
|
16
|
+
tags:
|
|
17
|
+
- "dynamic-form"
|
|
18
|
+
- "schema-driven"
|
|
19
|
+
- "runtime"
|
|
20
|
+
- "layout"
|
|
21
|
+
- "config-editor"
|
|
22
|
+
order: 30
|
|
23
|
+
icon: "dynamic_form"
|
|
24
|
+
toc: true
|
|
25
|
+
sidebar: true
|
|
26
|
+
search_boost: 1.0
|
|
27
|
+
reading_time: 12
|
|
28
|
+
estimated_setup_time: 20
|
|
29
|
+
version: "1.0"
|
|
30
|
+
related_docs:
|
|
31
|
+
- "dynamic-form-hot-metadata-updates"
|
|
32
|
+
- "praxis-dynamic-form-json-api"
|
|
33
|
+
- "host-integration-guide"
|
|
34
|
+
- "consumer-integration-quickstart"
|
|
35
|
+
keywords:
|
|
36
|
+
- "FormConfig"
|
|
37
|
+
- "schema-driven"
|
|
38
|
+
- "layout editor"
|
|
39
|
+
- "settings integration"
|
|
40
|
+
last_updated: "2026-03-22"
|
|
41
|
+
---
|
|
42
|
+
|
|
1
43
|
# @praxisui/dynamic-form
|
|
2
44
|
|
|
3
45
|
> Standalone dynamic form component with schema-driven UI, native field cascades, settings integration, and a built-in configuration editor.
|
|
4
46
|
|
|
47
|
+
## Documentation
|
|
48
|
+
|
|
49
|
+
- Official documentation: https://praxisui.dev
|
|
50
|
+
- Quickstart reference app: https://github.com/codexrodrigues/praxis-ui-quickstart
|
|
51
|
+
- Recommended for: schema-driven screens that need runtime forms, settings integration and contract-first metadata
|
|
52
|
+
|
|
53
|
+
## When to use
|
|
54
|
+
|
|
55
|
+
- Build forms from metadata instead of hand-coding each screen
|
|
56
|
+
- Keep runtime customization and layout governance inside the host application
|
|
57
|
+
- Integrate with other `@praxisui/*` packages through shared contracts and settings flows
|
|
58
|
+
|
|
5
59
|
## Install
|
|
6
60
|
|
|
7
61
|
```bash
|
|
@@ -36,7 +90,7 @@ import type { FormConfig } from '@praxisui/core';
|
|
|
36
90
|
<praxis-dynamic-form
|
|
37
91
|
[config]="config"
|
|
38
92
|
[mode]="'create'"
|
|
39
|
-
[
|
|
93
|
+
[enableCustomization]="true"
|
|
40
94
|
(formSubmit)="onSubmit($event)"
|
|
41
95
|
></praxis-dynamic-form>
|
|
42
96
|
`,
|
|
@@ -46,18 +100,29 @@ export class FormDemoComponent {
|
|
|
46
100
|
sections: [
|
|
47
101
|
{
|
|
48
102
|
id: 'main',
|
|
49
|
-
|
|
103
|
+
title: 'Employee',
|
|
104
|
+
sectionHeader: {
|
|
105
|
+
mode: 'auto',
|
|
106
|
+
sourceField: 'employeePhoto',
|
|
107
|
+
initialsSourceField: 'fullName',
|
|
108
|
+
altField: 'fullName',
|
|
109
|
+
},
|
|
50
110
|
rows: [
|
|
51
111
|
{
|
|
52
112
|
columns: [
|
|
53
|
-
{
|
|
54
|
-
{
|
|
113
|
+
{ id: 'col-fullName', fields: ['fullName'] },
|
|
114
|
+
{ id: 'col-email', fields: ['email'] },
|
|
55
115
|
],
|
|
56
116
|
},
|
|
57
117
|
],
|
|
58
118
|
},
|
|
59
119
|
],
|
|
60
|
-
|
|
120
|
+
fieldMetadata: [
|
|
121
|
+
{ name: 'employeePhoto', label: 'Photo', controlType: 'input' } as any,
|
|
122
|
+
{ name: 'fullName', label: 'Full Name', controlType: 'input', required: true } as any,
|
|
123
|
+
{ name: 'email', label: 'E-mail', controlType: 'email' } as any,
|
|
124
|
+
],
|
|
125
|
+
};
|
|
61
126
|
|
|
62
127
|
onSubmit(evt: any) {
|
|
63
128
|
console.log('Submitted:', evt);
|
|
@@ -65,7 +130,70 @@ export class FormDemoComponent {
|
|
|
65
130
|
}
|
|
66
131
|
```
|
|
67
132
|
|
|
68
|
-
Tip: connect to a backend resource by setting `resourcePath`/`resourceId`. The component can fetch schemas and reconcile local layout with server metadata when `
|
|
133
|
+
Tip: connect to a backend resource by setting `resourcePath`/`resourceId`. The component can fetch schemas and reconcile local layout with server metadata when `enableCustomization` is true.
|
|
134
|
+
When a late `config` hydration rebuilds the surface, the runtime preserves the current form values already mounted in memory only when the logical context remains the same (`resourcePath`, `resourceId`, and mode). If the form was preloaded from `resourceId`, that entity hydration remains visible after the rebuild only for the same entity context; entity switches and create-mode transitions do not restore the previous record snapshot.
|
|
135
|
+
|
|
136
|
+
### Canonical DI contract for hosts
|
|
137
|
+
|
|
138
|
+
`praxis-dynamic-form` does not register `GenericCrudService` internally.
|
|
139
|
+
When the runtime uses `resourcePath`, `resourceId`, schema fetch, CRUD submit, or custom endpoints, the host must provide the service in the effective host scope.
|
|
140
|
+
|
|
141
|
+
Canonical rule:
|
|
142
|
+
- `praxis-table` is self-hosted for `GenericCrudService`.
|
|
143
|
+
- `praxis-dynamic-form` is host-driven for `GenericCrudService`.
|
|
144
|
+
- If the host needs a specific `endpointKey` or pre-configuration, it must provide and configure the same `GenericCrudService` instance before rendering the form.
|
|
145
|
+
|
|
146
|
+
Minimal host example:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { Component } from '@angular/core';
|
|
150
|
+
import { GenericCrudService } from '@praxisui/core';
|
|
151
|
+
import { PraxisDynamicForm } from '@praxisui/dynamic-form';
|
|
152
|
+
|
|
153
|
+
@Component({
|
|
154
|
+
selector: 'app-form-host',
|
|
155
|
+
standalone: true,
|
|
156
|
+
imports: [PraxisDynamicForm],
|
|
157
|
+
providers: [GenericCrudService],
|
|
158
|
+
template: `
|
|
159
|
+
<praxis-dynamic-form
|
|
160
|
+
[formId]="'employees-form'"
|
|
161
|
+
[resourcePath]="'employees'"
|
|
162
|
+
[mode]="'edit'"
|
|
163
|
+
></praxis-dynamic-form>
|
|
164
|
+
`,
|
|
165
|
+
})
|
|
166
|
+
export class FormHostComponent {}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
If the host must force a non-default API endpoint, configure the service in the same host scope:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import { Component } from '@angular/core';
|
|
173
|
+
import { ApiEndpoint, GenericCrudService } from '@praxisui/core';
|
|
174
|
+
import { PraxisDynamicForm } from '@praxisui/dynamic-form';
|
|
175
|
+
|
|
176
|
+
@Component({
|
|
177
|
+
selector: 'app-form-host',
|
|
178
|
+
standalone: true,
|
|
179
|
+
imports: [PraxisDynamicForm],
|
|
180
|
+
providers: [GenericCrudService],
|
|
181
|
+
template: `
|
|
182
|
+
<praxis-dynamic-form
|
|
183
|
+
[formId]="'employees-form'"
|
|
184
|
+
[resourcePath]="'human-resources/employees'"
|
|
185
|
+
[mode]="'edit'"
|
|
186
|
+
></praxis-dynamic-form>
|
|
187
|
+
`,
|
|
188
|
+
})
|
|
189
|
+
export class FormHostComponent {
|
|
190
|
+
constructor(private readonly crud: GenericCrudService<any>) {
|
|
191
|
+
this.crud.configure('human-resources/employees', ApiEndpoint.HumanResources);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Without that provider, a standalone host can fail with `NG0201: No provider found for _GenericCrudService`.
|
|
69
197
|
|
|
70
198
|
### 2) Config Editor component
|
|
71
199
|
|
|
@@ -95,22 +223,373 @@ export class FormEditorLauncherComponent {
|
|
|
95
223
|
}
|
|
96
224
|
```
|
|
97
225
|
|
|
98
|
-
Alternatively, when `
|
|
226
|
+
Alternatively, when `enableCustomization` is true, `praxis-dynamic-form` renders a gear button that opens the editor internally.
|
|
99
227
|
|
|
100
228
|
## API Surface
|
|
101
229
|
|
|
102
230
|
- Components: `PraxisDynamicForm`, `PraxisDynamicFormConfigEditor`, `JsonConfigEditorComponent`, `LayoutEditorComponent`
|
|
103
231
|
- Services: `FormConfigService`, `FormLayoutService`, `DynamicFormLayoutService`, `FormContextService`
|
|
104
|
-
- Utilities: form rule converters, normalize date arrays
|
|
232
|
+
- Utilities: form rule converters, normalize date arrays, **FormRulesService** (aplica regras de propriedades)
|
|
105
233
|
- Metadata helpers: `providePraxisDynamicFormMetadata`
|
|
106
234
|
|
|
235
|
+
## Editorial Hosting (Foundation)
|
|
236
|
+
|
|
237
|
+
O `@praxisui/dynamic-form` agora aceita uma surface fundacional para hospedar blocos editoriais ao redor do formulario, sem misturar esses blocos com `fieldMetadata` nem com `formData`.
|
|
238
|
+
|
|
239
|
+
Inputs relevantes:
|
|
240
|
+
- `config.formBlocksBefore?: WidgetDefinition[]`
|
|
241
|
+
- `config.formBlocksBeforeActions?: WidgetDefinition[]`
|
|
242
|
+
- `config.formBlocksAfter?: WidgetDefinition[]`
|
|
243
|
+
- `config.editorialContext?: Record<string, unknown>`
|
|
244
|
+
- `[editorialContext]?: Record<string, unknown>`
|
|
245
|
+
|
|
246
|
+
Precedencia de contexto editorial:
|
|
247
|
+
1. runtime base do form
|
|
248
|
+
2. `config.editorialContext`
|
|
249
|
+
3. input host `[editorialContext]`
|
|
250
|
+
|
|
251
|
+
As camadas posteriores sobrescrevem as anteriores.
|
|
252
|
+
|
|
253
|
+
Exemplo minimo:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
import type { FormConfig } from '@praxisui/core';
|
|
257
|
+
|
|
258
|
+
const formConfig: FormConfig = {
|
|
259
|
+
editorialContext: {
|
|
260
|
+
accountName: 'Helena Costa',
|
|
261
|
+
accountRole: 'Gestora financeira',
|
|
262
|
+
accountContext: {
|
|
263
|
+
user: {
|
|
264
|
+
name: 'Helena Costa',
|
|
265
|
+
email: 'helena.costa@praxis.demo',
|
|
266
|
+
role: 'Gestora financeira',
|
|
267
|
+
},
|
|
268
|
+
tenant: {
|
|
269
|
+
name: 'Praxis Holding',
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
formBlocksBefore: [
|
|
274
|
+
{
|
|
275
|
+
id: 'widget:hero-banner',
|
|
276
|
+
inputs: {
|
|
277
|
+
instanceId: 'editorial:before:hero:1',
|
|
278
|
+
title: 'Praxis Summit 2026',
|
|
279
|
+
subtitle: 'Inscricao institucional',
|
|
280
|
+
description: 'Experiencia institucional com composicao editorial hospedada antes do formulario.',
|
|
281
|
+
variant: 'event',
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
id: 'widget:rich-text-block',
|
|
286
|
+
inputs: {
|
|
287
|
+
instanceId: 'editorial:before:rich-text:1',
|
|
288
|
+
title: 'Antes de comecar',
|
|
289
|
+
subtitle: 'Contexto editorial',
|
|
290
|
+
icon: 'info',
|
|
291
|
+
contentFormat: 'markdown',
|
|
292
|
+
content: 'Confirme os dados abaixo antes do envio e consulte a [documentacao](https://example.com/docs) se precisar de suporte.',
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
id: 'widget:legal-notice',
|
|
297
|
+
inputs: {
|
|
298
|
+
instanceId: 'editorial:before:legal:1',
|
|
299
|
+
title: 'Uso de dados',
|
|
300
|
+
contentFormat: 'plain',
|
|
301
|
+
content: 'Ao participar, voce reconhece a politica institucional aplicavel.',
|
|
302
|
+
severity: 'info',
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
formBlocksBeforeActions: [
|
|
307
|
+
{
|
|
308
|
+
id: 'form:user-context-summary',
|
|
309
|
+
inputs: {
|
|
310
|
+
instanceId: 'editorial:before-actions:user-context:1',
|
|
311
|
+
title: 'Conta atual',
|
|
312
|
+
source: 'context',
|
|
313
|
+
context: '${accountContext}',
|
|
314
|
+
fields: [
|
|
315
|
+
{ label: 'Nome', valuePath: 'user.name', fallback: '-' },
|
|
316
|
+
{ label: 'E-mail', valuePath: 'user.email', fallback: '-' },
|
|
317
|
+
],
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
],
|
|
321
|
+
formBlocksAfter: [
|
|
322
|
+
{
|
|
323
|
+
id: 'widget:footer-links',
|
|
324
|
+
inputs: {
|
|
325
|
+
instanceId: 'editorial:after:footer-links:1',
|
|
326
|
+
brandText: 'Praxis',
|
|
327
|
+
secondaryText: 'Todos os direitos reservados.',
|
|
328
|
+
links: [
|
|
329
|
+
{ label: 'Docs', href: '/components/dynamic-form/docs/overview' },
|
|
330
|
+
{ label: 'API', href: '/components/dynamic-form/api' },
|
|
331
|
+
],
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
sections: [
|
|
336
|
+
{
|
|
337
|
+
id: 'registration',
|
|
338
|
+
title: 'Dados da inscricao',
|
|
339
|
+
rows: [
|
|
340
|
+
{
|
|
341
|
+
columns: [
|
|
342
|
+
{ fields: ['fullName'] },
|
|
343
|
+
{ fields: ['workEmail'] },
|
|
344
|
+
],
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
},
|
|
348
|
+
],
|
|
349
|
+
fieldMetadata: [
|
|
350
|
+
{ name: 'fullName', label: 'Nome completo', controlType: 'input', required: true },
|
|
351
|
+
{ name: 'workEmail', label: 'E-mail corporativo', controlType: 'email', required: true },
|
|
352
|
+
],
|
|
353
|
+
};
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Uso no host:
|
|
357
|
+
|
|
358
|
+
```html
|
|
359
|
+
<praxis-dynamic-form
|
|
360
|
+
[config]="formConfig"
|
|
361
|
+
[editorialContext]="{ accountName: 'Helena Costa', accountRole: 'Host override' }"
|
|
362
|
+
(widgetEvent)="onWidgetEvent($event)"
|
|
363
|
+
></praxis-dynamic-form>
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Notas:
|
|
367
|
+
- `formBlocksBefore`, `formBlocksBeforeActions` e `formBlocksAfter` nao entram em `formData`.
|
|
368
|
+
- `widgetEvent` reemite eventos dos widgets hospedados com `placement` (`before`, `beforeActions` ou `after`).
|
|
369
|
+
- `editorialContext` deve ser tratado como imutavel; mutacoes in-place nao invalidam o cache do host.
|
|
370
|
+
- A surface editorial ja suporta widgets reais compartilhados para hero, rich text, legal notice, user context summary e footer links.
|
|
371
|
+
- `formBlocksBeforeActions` renderiza depois das secoes e antes da area principal de acoes, sendo o slot recomendado para blocos contextuais como `form:user-context-summary`.
|
|
372
|
+
- `formBlocksAfter` permanece como slot de fechamento e continua renderizando depois do formulario inteiro, incluindo os CTAs.
|
|
373
|
+
- secoes agora suportam `appearance` (`card|plain|step`) e `stepLabel` para cabecalhos mais estruturados.
|
|
374
|
+
|
|
107
375
|
See public exports: `projects/praxis-dynamic-form/src/public-api.ts`.
|
|
108
376
|
|
|
377
|
+
## Documentacao Tecnica da Lib
|
|
378
|
+
|
|
379
|
+
- `projects/praxis-dynamic-form/src/lib/praxis-dynamic-form.json-api.md`
|
|
380
|
+
- `projects/praxis-dynamic-form/docs/hot-metadata-updates.md`
|
|
381
|
+
|
|
382
|
+
## Header de seção com avatar dinâmico
|
|
383
|
+
|
|
384
|
+
- `FormSection.icon` continua suportado como identidade visual estática do header.
|
|
385
|
+
- `FormSection.sectionHeader` amplia o contrato para permitir avatar resolvido a partir do `formData`, sem perder compatibilidade com `icon`.
|
|
386
|
+
- Modos suportados:
|
|
387
|
+
- `icon`: usa o ícone estático da seção.
|
|
388
|
+
- `avatar-image`: tenta resolver uma imagem a partir de `sourceField`.
|
|
389
|
+
- `avatar-initials`: deriva iniciais a partir de `initialsSourceField` e, na ausência dele, usa `altField`.
|
|
390
|
+
- `auto`: tenta imagem, depois iniciais e por fim aplica o fallback configurado.
|
|
391
|
+
- Campos principais de `sectionHeader`:
|
|
392
|
+
- `sourceField`: field cujo valor atual representa foto/avatar.
|
|
393
|
+
- `initialsSourceField`: field textual usado para derivar iniciais.
|
|
394
|
+
- `altField`: field usado como texto acessível do avatar e fallback textual para iniciais.
|
|
395
|
+
- `fallbackIcon`: ícone usado quando a fonte dinâmica estiver vazia ou inválida.
|
|
396
|
+
- `emptyState`: `fallback-icon`, `placeholder-avatar` ou `none`, útil sobretudo em fluxos `create`; o padrão efetivo é `placeholder-avatar`, neutro para qualquer domínio.
|
|
397
|
+
- `initialsMaxLength`: máximo de letras no avatar textual, normalizado para `1..4`.
|
|
398
|
+
- Formatos de imagem aceitos pelo runtime:
|
|
399
|
+
- URL
|
|
400
|
+
- data URL base64
|
|
401
|
+
- `File`/`Blob`
|
|
402
|
+
- objetos com `url`, `src`, `dataUrl`, `base64` ou `bytes`
|
|
403
|
+
- Comportamento típico em cadastro:
|
|
404
|
+
- formulário vazio: mostra placeholder neutro por padrão, ou `fallbackIcon` se `emptyState` pedir isso
|
|
405
|
+
- nome preenchido, sem foto: `auto` pode mostrar iniciais
|
|
406
|
+
- foto preenchida: mostra a imagem
|
|
407
|
+
|
|
408
|
+
## IA — catálogo de capacidades (composição)
|
|
409
|
+
|
|
410
|
+
O assistente usa um catálogo agregado de capabilities para gerar patches seguros:
|
|
411
|
+
|
|
412
|
+
- **Macro (FormConfig)**: layout, regras, ações, hooks e mensagens (`form-ai-capabilities`).
|
|
413
|
+
- **Base (FieldMetadata)**: propriedades comuns de campos (`field-metadata-ai-capabilities`).
|
|
414
|
+
- **Micro (por controlType)**: overlay específico de cada input (`@praxisui/dynamic-fields`).
|
|
415
|
+
|
|
416
|
+
Os paths micro são normalizados para `fieldMetadata[].<prop>` para garantir que os patches apontem para a raiz correta do formulário.
|
|
417
|
+
|
|
418
|
+
## Regras de formulário (novo contrato)
|
|
419
|
+
|
|
420
|
+
- Formato: cada regra tem `targetType` (`field | section | action | row | column`), `targets: string[]` (IDs canônicos, sem prefixo), e `effect` com `condition` (DSL ou Specification), `properties` e `propertiesWhenFalse`.
|
|
421
|
+
- Compatibilidade: regras antigas (`context/targetField`) são migradas para `properties/targets` automaticamente; prefixos `section:/action:/row:/column:` são removidos e `targetType` é inferido se não vier explicitado.
|
|
422
|
+
- Semântica de limpeza: valores `null` em `properties/propertiesWhenFalse` removem o override e retornam ao valor base do layout; ausência mantém o valor base.
|
|
423
|
+
- Whitelist por tipo (somente propriedades a seguir são aplicadas; demais são descartadas e logadas em dev):
|
|
424
|
+
- `field`: `visible`, `required`, `readonly`, `disabled`, `className`, `style`, `label`, `description`, `placeholder`, `hint`, `tooltip`, `prefixIcon`, `suffixIcon`, `prefixText`, `suffixText`, `defaultValue`, `options` (array `{label,value,disabled?}`), `appearance` (`fill|outline`), `color` (`primary|accent|warn`), `floatLabel` (`auto|always|never`), `hintPosition` (`start|end`), `validators` (primitivos por chave).
|
|
425
|
+
- `section`: `visible`, `title`, `description`, `icon`, `sectionHeader` (objeto rico) e tambem subpropriedades tipadas como `sectionHeader.mode`, `sectionHeader.sourceField`, `sectionHeader.initialsSourceField`, `sectionHeader.altField`, `sectionHeader.fallbackIcon`, `sectionHeader.emptyState`, `sectionHeader.initialsMaxLength`, `className`, `style`, `collapsible`, `collapsed`, `headerTooltip`, `headerAlign` (`start|center`), `appearance` (`card|plain|step`), `stepLabel`, gaps (`gapBottom`, `titleGapBottom`, `descriptionGapBottom`), cores/tipografia (`titleColor`, `descriptionColor`, `titleStyle`, `descriptionStyle`).
|
|
426
|
+
- `action`: `visible`, `disabled`, `loading`, `label`, `icon`, `tooltip`, `color` (`primary|accent|warn|basic`), `variant` (`raised|stroked|flat|fab`), `size` (`small|medium|large`), `className`, `style`.
|
|
427
|
+
- `row`: `visible`, `gap`, `rowGap`, `className`, `style`.
|
|
428
|
+
- `column`: `visible`, `span`, `offset`, `order`, `hidden`, `align` (`start|center|end|stretch`), `padding`, `className`, `style`.
|
|
429
|
+
- Runtime (FormRulesService): filtra por whitelist, converte tipos (enum/number/boolean/string), saneia objetos (`options/validators/style`), aplica remoção de chaves com `null` e retorna mapas `fieldProps/sectionProps/actionProps/rowProps/columnProps`.
|
|
430
|
+
- Renderização: `PraxisDynamicForm` aplica overrides em campos/seções/ações/linhas/colunas (visibilidade, gaps, padding, classes, estilos, labels, etc.); colunas respeitam `align/span/offset/order/hidden/padding` vindo das regras.
|
|
431
|
+
|
|
432
|
+
### Builder integrado
|
|
433
|
+
|
|
434
|
+
- No editor visual, use a aba “Propriedades” (integrada ao builder) para selecionar o alvo (`targetType` + autocomplete de IDs de campos/seções/ações/linhas/colunas), escolher propriedades whitelisted e definir valores para `properties` (branch true) e `propertiesWhenFalse` (branch false). Botão “Limpar override” remove a propriedade (equivalente a `null`).
|
|
435
|
+
- A aba de Propriedades usa inputs tipados (enum/number/boolean/string/JSON) conforme o schema injetado; valores inválidos são ignorados.
|
|
436
|
+
- O config editor fornece `targetSchemas` (campos/seções/ações/linhas/colunas) e `targetPropertySchemas` para o builder; `formRules` são salvas no formato canônico (sem `context/targetField`).
|
|
437
|
+
|
|
438
|
+
## Layout padrão (sem FormConfig)
|
|
439
|
+
|
|
440
|
+
Quando o componente não recebe uma `FormConfig` prévia (primeira execução), ele gera um layout padrão a partir do metadata do backend:
|
|
441
|
+
|
|
442
|
+
- Linhas/colunas: 2 campos por linha (padrão). Em telas pequenas (xs/sm) os campos empilham (1 por linha), e a partir de md ficam lado a lado.
|
|
443
|
+
- Responsividade do grid (12 colunas):
|
|
444
|
+
- xs: 12, sm: 12, md: 6, lg: 6, xl: 6 (para 2 por linha). Para outros valores, a regra é `base = floor(12 / fieldsPerRow)`.
|
|
445
|
+
- Largura dos campos: `mat-form-field { width: 100% }` e um **allow‑list** de tipos “input‑like” em `data-field-type` recebem `width: 100%`. Controles compactos (ex.: checkbox/radio/toggle/rating/slider) **não** são forçados a preencher a coluna.
|
|
446
|
+
- Editor de Configuração: ao abrir a aba “Layout”, o editor reflete esse layout padrão; ao aplicar/salvar, persiste a `FormConfig` no storage do host.
|
|
447
|
+
- Personalização: você pode ajustar o layout pelo Editor (arrastar/seções/linhas/colunas, alterar spans) ou fornecer uma `FormConfig` completa via `[config]`.
|
|
448
|
+
|
|
449
|
+
## Form Actions — Layout & Styling
|
|
450
|
+
|
|
451
|
+
A barra de ações (onde ficam "ENVIAR", "Cancelar", etc.) é configurável via `config.actions` e pelo Editor (aba "Ações").
|
|
452
|
+
|
|
453
|
+
Defaults
|
|
454
|
+
- Rótulo do botão principal: `ENVIAR`.
|
|
455
|
+
- Posição estrutural: `afterSections` (renderiza abaixo da última seção).
|
|
456
|
+
- Alinhamento: `right`.
|
|
457
|
+
- Orientação: `horizontal`.
|
|
458
|
+
- Espaçamento: `normal`.
|
|
459
|
+
- Background: sem cor por padrão (herda da superfície). Configure via `containerStyles` ou `containerClassName` se desejar uma superfície própria.
|
|
460
|
+
|
|
461
|
+
Estrutura (parcial)
|
|
462
|
+
```ts
|
|
463
|
+
interface FormActionsLayout {
|
|
464
|
+
submit: FormActionButton; // id, label, color, type, variant, shortcut, etc.
|
|
465
|
+
cancel: FormActionButton;
|
|
466
|
+
reset: FormActionButton;
|
|
467
|
+
custom?: FormActionButton[]; // botões extras
|
|
468
|
+
|
|
469
|
+
// Layout/posicionamento
|
|
470
|
+
placement?: 'afterSections' | 'insideLastSection' | 'top';
|
|
471
|
+
position?: 'left' | 'center' | 'right' | 'justified' | 'split';
|
|
472
|
+
orientation?: 'horizontal' | 'vertical';
|
|
473
|
+
spacing?: 'compact' | 'normal' | 'spacious';
|
|
474
|
+
sticky?: boolean; // fixa a barra (bottom)
|
|
475
|
+
|
|
476
|
+
// Estilização do container
|
|
477
|
+
containerClassName?: string; // adiciona classe ao container
|
|
478
|
+
containerStyles?: { [k: string]: any }; // estilos inline (camelCase)
|
|
479
|
+
|
|
480
|
+
// Mobile
|
|
481
|
+
mobile?: { position?: 'left'|'center'|'right'|'justified'; orientation?: 'horizontal'|'vertical'; collapseToMenu?: boolean };
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Exemplo (config)
|
|
486
|
+
```ts
|
|
487
|
+
config.actions = {
|
|
488
|
+
submit: { visible: true, label: 'ENVIAR', type: 'submit', color: 'primary', variant: 'raised', shortcut: 'ctrl+s' },
|
|
489
|
+
cancel: { visible: true, label: 'Cancelar', type: 'button', color: 'basic' },
|
|
490
|
+
reset: { visible: false, label: 'Reset' },
|
|
491
|
+
placement: 'afterSections',
|
|
492
|
+
position: 'right',
|
|
493
|
+
orientation: 'horizontal',
|
|
494
|
+
spacing: 'normal',
|
|
495
|
+
sticky: false,
|
|
496
|
+
containerClassName: 'my-form-actions',
|
|
497
|
+
containerStyles: {
|
|
498
|
+
background: 'var(--md-sys-color-surface-container)',
|
|
499
|
+
border: '1px solid var(--md-sys-color-outline-variant)',
|
|
500
|
+
borderRadius: '12px',
|
|
501
|
+
padding: '12px 16px'
|
|
502
|
+
},
|
|
503
|
+
mobile: { collapseToMenu: true }
|
|
504
|
+
};
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
CSS por classe (opcional)
|
|
508
|
+
```scss
|
|
509
|
+
.my-form-actions {
|
|
510
|
+
background: var(--md-sys-color-surface-container);
|
|
511
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
512
|
+
border-radius: 12px;
|
|
513
|
+
padding: 12px 16px;
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
Dicas
|
|
518
|
+
- Cores do botão: use `color` = `primary|accent|warn|basic` (respeitam o tema Material).
|
|
519
|
+
- Tokens M3: prefira `--md-sys-*` para cores/superfícies.
|
|
520
|
+
- Mobile: ative `collapseToMenu` para colapsar botões extras em menu nas telas pequenas.
|
|
521
|
+
- A classe de tema é decisão do host (`.dark-theme` ou `.theme-dark`/`.theme-light`); mantenha tokens e componentes no mesmo escopo.
|
|
522
|
+
|
|
523
|
+
### Tokens M3 obrigatórios (host)
|
|
524
|
+
|
|
525
|
+
Para que o builder e os editores respeitem o tema do app host:
|
|
526
|
+
|
|
527
|
+
- Superfícies: `--md-sys-color-surface`, `--md-sys-color-surface-variant`, `--md-sys-color-surface-container-*`
|
|
528
|
+
- Texto/contorno: `--md-sys-color-on-surface`, `--md-sys-color-on-surface-variant`, `--md-sys-color-outline`, `--md-sys-color-outline-variant`
|
|
529
|
+
- Semânticos: `--md-sys-color-primary`, `--md-sys-color-secondary`, `--md-sys-color-tertiary`, `--md-sys-color-error`
|
|
530
|
+
- Containers: `--md-sys-color-primary-container`, `--md-sys-color-secondary-container`, `--md-sys-color-tertiary-container`, `--md-sys-color-error-container`
|
|
531
|
+
- Elevação: `--md-sys-elevation-level1`–`--md-sys-elevation-level3`
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
## Section titles — espaçamento global
|
|
535
|
+
|
|
536
|
+
O título de seção usa por padrão `margin: 0 0 6px 0`. Você pode ajustar globalmente via CSS var:
|
|
537
|
+
|
|
538
|
+
```scss
|
|
539
|
+
/* Global (app host) */
|
|
540
|
+
:root { --pfx-section-title-mb: 10px; } // ex.: 10px abaixo do título
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
Ou por seção, via metadado `titleGapBottom` (em pixels), que aplica inline somente naquela seção.
|
|
544
|
+
|
|
109
545
|
## Compatibility
|
|
110
546
|
|
|
111
547
|
- `@praxisui/dynamic-form` `0.0.x` → Angular `20.x`
|
|
112
548
|
- Module format: `ESM2022`
|
|
113
549
|
|
|
550
|
+
## Crash Recovery Checkpoint
|
|
551
|
+
|
|
552
|
+
Estado registrado em `2026-03-15` para retomada rapida do trabalho de E2E do editor `/form-config-editor`.
|
|
553
|
+
|
|
554
|
+
Suites Playwright ja adicionadas e validadas:
|
|
555
|
+
- `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-smoke.playwright.spec.ts`
|
|
556
|
+
- `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-layout.playwright.spec.ts`
|
|
557
|
+
- `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-rules.playwright.spec.ts`
|
|
558
|
+
- `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-cascades.playwright.spec.ts`
|
|
559
|
+
- `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-json.playwright.spec.ts`
|
|
560
|
+
- `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-hooks.playwright.spec.ts`
|
|
561
|
+
- `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-actions.playwright.spec.ts`
|
|
562
|
+
- `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-actions-custom.playwright.spec.ts`
|
|
563
|
+
|
|
564
|
+
Cobertura ja estabilizada:
|
|
565
|
+
- smoke das tabs principais do editor integrado
|
|
566
|
+
- `Layout`
|
|
567
|
+
- `Regras`
|
|
568
|
+
- `Cascatas`
|
|
569
|
+
- `JSON` com edicao real e bloqueio de payload invalido
|
|
570
|
+
- `Hooks`
|
|
571
|
+
- `Acoes` (`Botoes Padrao` e `Layout`)
|
|
572
|
+
- `Acoes` customizadas profundas (`showAlert` simples e estruturado)
|
|
573
|
+
- `Comportamento`
|
|
574
|
+
- `Mensagens`
|
|
575
|
+
- `Dicas e Tooltips`
|
|
576
|
+
|
|
577
|
+
Ajuste tecnico aplicado durante essa fase:
|
|
578
|
+
- [praxis-dynamic-form-config-editor.ts](/mnt/d/Developer/praxis-plataform/praxis-ui-angular/projects/praxis-dynamic-form/src/lib/config-editor/praxis-dynamic-form-config-editor.ts) agora chama `this.jsonEditor?.updateJsonFromConfig(this.editedConfig)` em `onConfigChange(...)` para manter o editor JSON sincronizado com mudancas vindas de abas como `Hooks`.
|
|
579
|
+
- [actions-editor.component.ts](/mnt/d/Developer/praxis-plataform/praxis-ui-angular/projects/praxis-dynamic-form/src/lib/actions-editor/actions-editor.component.ts) agora mescla catalogo legado + catalogo global do app para o editor de acoes customizadas.
|
|
580
|
+
- [actions-editor.component.ts](/mnt/d/Developer/praxis-plataform/praxis-ui-angular/projects/praxis-dynamic-form/src/lib/actions-editor/actions-editor.component.ts) agora preserva draft local dos campos globais e usa `track` estavel em `actions.custom`.
|
|
581
|
+
|
|
582
|
+
Comando base usado para rerodar suites isoladas:
|
|
583
|
+
|
|
584
|
+
```bash
|
|
585
|
+
node ./node_modules/playwright/cli.js test --config tools/e2e/playwright/praxis-dynamic-form-config-editor.playwright.config.ts <spec>
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
Se houver crash, retomar daqui:
|
|
589
|
+
1. garantir que o app esteja no ar
|
|
590
|
+
2. rerodar a ultima suite alterada
|
|
591
|
+
3. proximo alvo pendente: consolidar a bateria completa ou seguir para outra area residual do editor
|
|
592
|
+
|
|
114
593
|
## License
|
|
115
594
|
|
|
116
595
|
Apache-2.0 – see the `LICENSE` packaged with this library or the repository root.
|
|
@@ -119,9 +598,9 @@ Apache-2.0 – see the `LICENSE` packaged with this library or the repository ro
|
|
|
119
598
|
|
|
120
599
|
### Concept Usage
|
|
121
600
|
|
|
122
|
-
-
|
|
123
|
-
-
|
|
124
|
-
-
|
|
601
|
+
- Data-driven forms
|
|
602
|
+
- Declarative UI
|
|
603
|
+
- Schema-driven UI
|
|
125
604
|
|
|
126
605
|
## Quando usar cada mecanismo
|
|
127
606
|
|
|
@@ -142,13 +621,13 @@ Apache-2.0 – see the `LICENSE` packaged with this library or the repository ro
|
|
|
142
621
|
|
|
143
622
|
## Links úteis
|
|
144
623
|
|
|
145
|
-
- Fluxo de Schema (ETag/304, schemaId, reconciliação): `docs/schemas/fluxo-schema.md`
|
|
624
|
+
- Fluxo de Schema (ETag/304, schemaId, reconciliação): `projects/praxis-core/docs/schema-flow.md` (canônico) e `docs/schemas/fluxo-schema.md` (resumo operacional)
|
|
146
625
|
- Guia de implementação e metadados da cascata: `docs/CASCADE-NATIVA.md`
|
|
147
|
-
- Padrões de endpoints (Options vs Filter) para selects: `docs/DEVS-GENERIC-CRUD-SERVICE.md`
|
|
626
|
+
- Padrões de endpoints (Options vs Filter) para selects: `projects/praxis-dynamic-fields/docs/generic-crud-service.md` (canônico) e `docs/DEVS-GENERIC-CRUD-SERVICE.md` (resumo operacional)
|
|
148
627
|
|
|
149
628
|
## Verificação de Schema (ETag/If-None-Match)
|
|
150
629
|
|
|
151
|
-
- Inputs (opcionais; ativos apenas com `
|
|
630
|
+
- Inputs (opcionais; ativos apenas com `enableCustomization=true`):
|
|
152
631
|
- `notifyIfOutdated: 'inline' | 'snackbar' | 'both' | 'none' = 'both'`
|
|
153
632
|
- `snoozeMs: number = 86400000`
|
|
154
633
|
- `autoOpenSettingsOnOutdated: boolean = false`
|
|
@@ -161,6 +640,12 @@ Apache-2.0 – see the `LICENSE` packaged with this library or the repository ro
|
|
|
161
640
|
- Comportamento:
|
|
162
641
|
- Quando já existe base local (ex.: `config.sections.length > 0`), o componente faz uma verificação leve via `/schemas/filtered` com `If-None-Match`.
|
|
163
642
|
- 304 → apenas atualiza `lastVerifiedAt` e emite `schemaStatusChange(outdated=false)`.
|
|
164
|
-
- 200 → atualiza `serverHash/lastVerifiedAt`, define `schemaOutdated =
|
|
643
|
+
- 200 → atualiza `serverHash/lastVerifiedAt`, define `schemaOutdated = enableCustomization && hadBase`, emite `schemaStatusChange`. Não aplica schema automaticamente.
|
|
165
644
|
- Primeira vez (sem base): baixa o corpo do schema para gerar o layout; persiste `form-schema-meta:{formId}`.
|
|
166
645
|
- Notificações respeitam preferências e são one‑shot por hash; o banner/snackbar oferecem ações para Reconciliar, Lembrar depois (snooze) e Ignorar.
|
|
646
|
+
|
|
647
|
+
### URL da API (absoluto vs relativo)
|
|
648
|
+
|
|
649
|
+
- Quando `API_URL.default.baseUrl` for relativo (ex.: `'/api'`), a lib resolve a origem a partir de `location.origin` no browser. Isso cobre o cenário comum com proxy de dev (`/api`, `/schemas`).
|
|
650
|
+
- Em SSR (sem `location.origin`), configure `baseUrl` absoluto (ex.: `https://api.acme.com/api`) para evitar erros do tipo “Invalid URL” ao construir chamadas de `/schemas/filtered`.
|
|
651
|
+
- O `GenericCrudService.getSchemasFilteredBaseUrl()` retorna sempre uma URL absoluta; o `SchemaMetadataClient` também aceita `baseUrl` relativo quando há origin disponível.
|