@praxisui/table 1.0.0-beta.4 → 1.0.0-beta.41

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
@@ -1,11 +1,32 @@
1
1
  # @praxisui/table
2
2
 
3
+ ## 🔰 Exemplos / Quickstart
4
+
5
+ Para ver esta biblioteca em funcionamento em uma aplicação completa, utilize o projeto de exemplo (Quickstart):
6
+
7
+ - Repositório: https://github.com/codexrodrigues/praxis-ui-quickstart
8
+ - O Quickstart demonstra a integração das bibliotecas `@praxisui/*` em um app Angular, incluindo instalação, configuração e uso em telas reais.
9
+
3
10
  > Componente de tabela empresarial avançado com arquitetura unificada
4
11
 
5
12
  ## 🌟 Visão Geral
6
13
 
7
14
  A biblioteca `@praxisui/table` fornece um componente de tabela robusto e altamente configurável para aplicações Angular empresariais. Com a nova arquitetura unificada, oferece uma experiência de desenvolvimento simplificada mantendo todos os recursos avançados.
8
15
 
16
+ ## 🎨 Tema M3 (tokens mínimos)
17
+
18
+ Para garantir que a tabela, filtros e editores reflitam o tema do app host:
19
+
20
+ - Superfícies: `--md-sys-color-surface`, `--md-sys-color-surface-variant`, `--md-sys-color-surface-container-*`
21
+ - Texto/contorno: `--md-sys-color-on-surface`, `--md-sys-color-on-surface-variant`, `--md-sys-color-on-primary`, `--md-sys-color-outline`, `--md-sys-color-outline-variant`
22
+ - Semânticos: `--md-sys-color-primary`, `--md-sys-color-secondary`, `--md-sys-color-tertiary`, `--md-sys-color-error`
23
+ - Containers: `--md-sys-color-primary-container`, `--md-sys-color-secondary-container`, `--md-sys-color-tertiary-container`, `--md-sys-color-error-container`
24
+ - Overlay: `--md-sys-color-scrim`
25
+ - Elevação: `--md-sys-elevation-level1`–`--md-sys-elevation-level3`
26
+
27
+ Observação: tokens `--md-sys-*` e `--mdc-theme-*` continuam aceitos quando presentes.
28
+ Nota: a classe de tema é decisão do host (`.dark-theme` ou `.theme-dark`/`.theme-light`); mantenha tokens e componentes no mesmo escopo.
29
+
9
30
  ## ✨ Características Principais
10
31
 
11
32
  ### 🏗️ Arquitetura Unificada
@@ -34,6 +55,205 @@ A biblioteca `@praxisui/table` fornece um componente de tabela robusto e altamen
34
55
  - **Toolbar Editor**: Personalização de ações
35
56
  - **Messages Editor**: Textos e localização
36
57
 
58
+ Nota (Regras)
59
+ - A antiga aba visual genérica foi removida deste pacote. O editor especializado de regras da Tabela já está disponível no painel de configuração.
60
+
61
+ ### 🧩 Editor de Regras (column-first)
62
+
63
+ O novo editor é "column-first" e usa uma tipagem compartilhada com o Editor de Colunas. Principais pontos:
64
+
65
+ - Escopo: aplique regras na linha inteira (rowConditionalStyles) ou em uma coluna específica (columns[].conditionalStyles).
66
+ - Operadores por tipo: o conjunto de operadores é filtrado por tipo (string/number/date/boolean/enum).
67
+ - Editores de valor dinâmicos: inputs numéricos, listas (CSV/múltipla seleção), datepickers.
68
+ - Grupos lógicos: construa condições com AND/OR/NOT e reordene via arrastar-e-soltar (CDK DragDrop).
69
+ - Preview: execute “Testar” para ver quantas linhas seriam afetadas pelas regras ativas.
70
+ - Import/Export: exporta JSON sem o campo `enabled`; importa com validação de DSL e sanitização de estilos (allowlist).
71
+
72
+ Exemplos de DSL:
73
+
74
+ ```text
75
+ contains(status, 'Ativo')
76
+ not (status in ['A', 'I'])
77
+ (price >= 10 and price <= 20)
78
+ (createdAt >= '2024-10-01' and createdAt <= '2024-10-31')
79
+ (name == null or name == '')
80
+ active == true
81
+ ```
82
+
83
+ Notas sobre enum/CSV:
84
+ - Para campos enumerados, as opções são inferidas de `valueMapping` da coluna ou de `fields?.options`.
85
+ - Para `in/not in` em números/strings, use CSV (ex.: `10, 20, 30` ou `Alice, Bob`).
86
+
87
+ #### Extensões de DSL (runtime nativo + custom)
88
+
89
+ O runtime da tabela já inclui helpers de JSON/tempo/listas/texto sem configuração extra:
90
+
91
+ - JSON: `jsonGet`, `hasJsonKey`, `jsonPathMatches`, `jsonEq`, `jsonIn`, `jsonType`
92
+ - Texto/valor: `isBlank`, `eqIgnoreCase`, `coalesce`
93
+ - Faixas: `between`, `dateBetween`
94
+ - Coleções: `containsAny`, `containsAll`, `len`, `lenEq`, `lenGt`, `lenLt`, `lenAtLeast`
95
+ - Tempo: `today`, `now`, `dateAdd`, `dateFormat`, `isToday`, `inLast`, `isPast`, `isPastOrNow`, `isFuture`, `isFutureOrNow`, `weekdayIn`
96
+
97
+ Exemplos de DSL (canônica):
98
+ - `jsonEq(payload, '$.user.name', 'Alice')`
99
+ - `jsonIn(customer, '$.id', [10, 20])`
100
+ - `eqIgnoreCase(status, 'ATIVO')`
101
+ - `between(amount, 10, 20)`
102
+ - `dateBetween(createdAt, '2025-01-01', '2025-12-31')`
103
+ - `containsAll(tags, ['vip', 'gold'])`
104
+
105
+ Observação de gramática:
106
+ - comparações diretas sobre retorno de função (`fn(...) == 'x'`) não são suportadas pela gramática atual; prefira funções predicativas (booleanas) na `condition`.
107
+
108
+ Guia completo: `projects/praxis-table/docs/DSL-Extensions-Guide.md`
109
+
110
+ #### 🎨 Efeitos (Conteúdo C)
111
+
112
+ O bloco de Efeitos permite aplicar estilo e visuais quando a condição é verdadeira:
113
+
114
+ - Toolbar de texto com ícones (negrito, itálico, sublinhado) e presets rápidos (tachado, maiúsculas/minúsculas/capitalizar, tamanho +/- e reset).
115
+ - Presets de feedback com ícones (sucesso, aviso, erro, info) — mantêm o rótulo via tooltip e `aria-label`.
116
+ - “Posição do valor” como grupo de alternância (antes / entre-1 / entre-2 / depois / oculto).
117
+ - “Ordem dos visuais” com seletor compacto (3 mais comuns) e menu para opções extras.
118
+ - Layout do compose: espaçamento, alinhamento, quebra de linha e reticências.
119
+
120
+ Dicas
121
+ - Use “Resetar texto” para reverter efeitos de texto sem perder cor de fundo/borda.
122
+ - Para acessibilidade, os grupos possuem `role="toolbar"`, tooltips e navegação por teclado consistente.
123
+
124
+ Como reverter efeitos
125
+ - “Reset Text” remove apenas propriedades de tipografia e transformação de texto.
126
+ - “Limpar efeitos” apaga classe e estilo livres do bloco rápido.
127
+
128
+ Observação: capturas de tela “antes/depois” podem ser adicionadas no PR conforme necessário.
129
+
130
+ #### Relacionais (lookup)
131
+
132
+ Campos relacionais (FK/objetos) podem ser editados no Rules Editor com busca assíncrona (typeahead) e seleção single/multiple.
133
+
134
+ Boas práticas
135
+ - Informe `resourcePath` (ou `endpoint`) no `FieldDefinition` para habilitar o lookup via `GenericCrudService` do host.
136
+ - Defina `valueField` (chave estável, tipicamente `id`) e `displayField` (label amigável, como `name`).
137
+
138
+ ## 🔎 Filtro: Atalhos para Período (DateRange)
139
+
140
+ Habilite atalhos de período no filtro dinâmico sem alterar o contrato do `praxis-filter` — apenas via metadata do campo:
141
+
142
+ ```ts
143
+ // Em FilterConfig (ou na metadata de campos do filtro)
144
+ {
145
+ name: 'period',
146
+ label: 'Período',
147
+ controlType: 'dateRange',
148
+ showShortcuts: true,
149
+ shortcuts: ['thisMonth','lastMonth','thisYear'],
150
+ // Opcional:
151
+ // i18nShortcuts: { thisMonth: 'Este mês', lastMonth: 'Mês passado' },
152
+ // applyOnShortcutClick: true
153
+ }
154
+ ```
155
+ - Para múltipla seleção, use `multiple: true` no `FieldDefinition` quando aplicável.
156
+ - No runtime da tabela não é necessário registrar funções JSON/data manualmente; os helpers já são embutidos.
157
+ - APIs com busca: suporte a um parâmetro livre de texto (por ex. `search`) facilita a experiência do autocomplete.
158
+
159
+ Exemplo de FieldDefinition (lookup)
160
+ ```ts
161
+ import type { FieldDefinition } from '@praxisui/core';
162
+
163
+ const fields: FieldDefinition[] = [
164
+ {
165
+ name: 'customer',
166
+ type: 'object',
167
+ // Habilita o lookup via GenericCrudService
168
+ resourcePath: 'customers',
169
+ // Mapeia o id/label que virão do backend
170
+ valueField: 'id',
171
+ displayField: 'name',
172
+ // Se quiser multiseleção em todo o app
173
+ // multiple: true,
174
+ },
175
+ ];
176
+ ```
177
+
178
+ Operadores e DSL gerada (relacional)
179
+ - `id ==` → `jsonEq(customer, '$.id', 10)`
180
+ - `id in` → `jsonIn(customer, '$.id', [10, 20])`
181
+ - `has property` → `hasJsonKey(customer, '$.meta.etag')`
182
+ - Condições por caminho (join simples): use `key ==` (JSON Path + valor)
183
+
184
+ UI (no editor)
185
+ - `id ==` (single): autocomplete + chip “label [id]” com ação “Limpar”.
186
+ - `id in` (multi): autocomplete + chips removíveis por item.
187
+ - `has property`: input do caminho (ex.: `$.meta.etag`).
188
+
189
+ Formato esperado do payload (relacional)
190
+
191
+ Para que o editor/preview e o runtime avaliem corretamente as regras relacionais, o valor do campo deve ser um objeto (ou compatível com JSON) contendo ao menos o identificador e, opcionalmente, propriedades exibidas:
192
+
193
+ ```ts
194
+ // Exemplo de linha (row) com campo relacional "customer"
195
+ const row = {
196
+ id: 123,
197
+ customer: {
198
+ id: 10, // ← usado em id == / id in via jsonEq/jsonIn
199
+ name: 'ACME', // ← usado para exibir label no autocomplete/chip
200
+ meta: { etag: 'v1' },
201
+ },
202
+ };
203
+
204
+ // Regras DSL comuns
205
+ // id ==
206
+ // jsonEq(customer, '$.id', 10)
207
+ // id in
208
+ // jsonIn(customer, '$.id', [10, 20])
209
+ // has property
210
+ // hasJsonKey(customer, '$.meta.etag')
211
+ // caminho/valor (join simples)
212
+ // jsonEq(customer, '$.name', 'ACME')
213
+ ```
214
+
215
+ Notas
216
+ - O editor usa `displayField` (ex.: `name`) para compor o rótulo, e `valueField` (ex.: `id`) para gerar a DSL.
217
+ - Se o backend devolver o relacionamento já "normalizado" (ex.: `customerId` e `customerName` na mesma linha), você ainda pode expressar condições com operadores padrão (`==`, `in`) no campo numérico/literal — a abordagem relacional é útil quando o payload contém o objeto.
218
+
219
+ Onde é consumido (preview e runtime)
220
+
221
+ - Preview/validação no Editor de Regras (usa `DslParser` interno):
222
+ - Validação da expressão (parse): projects/praxis-table/src/lib/rules-editor/table-rules-editor.component.ts:536
223
+ - Preview “Testar” (parse + evaluate): projects/praxis-table/src/lib/rules-editor/table-rules-editor.component.ts:909
224
+ - Runtime na Tabela (aplica `rowConditionalStyles`/`conditionalStyles`):
225
+ - Parser interno do componente: projects/praxis-table/src/lib/praxis-table.ts:282
226
+
227
+ Observação
228
+ - O runtime da tabela já injeta o registry DSL padrão para validação/parse/execução.
229
+ - Para funções realmente custom, injete via input `[dslFunctionRegistry]` no componente.
230
+
231
+ Exemplo de Provider (recomendado)
232
+
233
+ ```ts
234
+ import { Injectable } from '@angular/core';
235
+ import { FunctionRegistry } from '@praxisui/specification';
236
+
237
+ @Injectable({ providedIn: 'root' })
238
+ export class TableDslRegistryFactory {
239
+ private readonly registry = FunctionRegistry.getInstance<any>('orders-table-rules');
240
+
241
+ constructor() {
242
+ this.registry.clear();
243
+ this.registry.register('isStatus', (_row: any, value: any, expected: any) =>
244
+ String(value ?? '').toLowerCase() === String(expected ?? '').toLowerCase(),
245
+ );
246
+ }
247
+
248
+ get(): FunctionRegistry<any> {
249
+ return this.registry;
250
+ }
251
+ }
252
+
253
+ // Uso no host:
254
+ // <praxis-table [dslFunctionRegistry]="tableDslRegistryFactory.get()" ... />
255
+ ```
256
+
37
257
  ### ⚙️ Painel de Configurações
38
258
 
39
259
  Para abrir o painel de configurações, habilite o modo de edição na tabela:
@@ -178,6 +398,46 @@ sequenceDiagram
178
398
  - Virtualização: habilite em Comportamento → Virtualização para listas grandes (requer altura de linha previsível).
179
399
  - Densidade e linhas de grade: ajuste em Comportamento → Aparência rápida (compacta/confortável/espaçosa; bordas horizontais/verticais).
180
400
 
401
+ ## Rolagem Horizontal (owner do scroll)
402
+
403
+ Por padrão, a `<praxis-table>` agora assume a responsabilidade pela rolagem horizontal quando o conteúdo excede a largura do container. Isso evita cortes de conteúdo e comportamentos inconsistentes entre projetos.
404
+
405
+ ### Input: `horizontalScroll`
406
+
407
+ ```
408
+ @Input() horizontalScroll: 'auto' | 'wrap' | 'none' = 'auto';
409
+ ```
410
+
411
+ - `auto` (padrão): a tabela cria um viewport com `overflow-x: auto` e permite que a tabela interna cresça com `width: max-content`. Quando a soma das colunas > container, aparece a barra de rolagem horizontal neste viewport.
412
+ - `wrap`: permite quebra de linha nas células (libera `white-space: normal`), reduzindo a largura necessária; ideal quando você prefere minimizar a rolagem horizontal.
413
+ - `none`: desabilita o comportamento interno; o host (página) deve fornecer o container com `overflow-x: auto` e as regras de largura necessárias.
414
+
415
+ Exemplo:
416
+
417
+ ```html
418
+ <!-- Comportamento padrão (auto) -->
419
+ <praxis-table resourcePath="employees"></praxis-table>
420
+
421
+ <!-- Reduz rolagem: permite quebra de linha nas células -->
422
+ <praxis-table resourcePath="employees" horizontalScroll="wrap"></praxis-table>
423
+
424
+ <!-- Host controla o scroll horizontal -->
425
+ <div class="table-shell" style="overflow-x:auto">
426
+ <praxis-table resourcePath="employees" horizontalScroll="none"></praxis-table>
427
+ <!-- host pode usar width: max-content na tabela interna conforme seu design system -->
428
+ </div>
429
+ ```
430
+
431
+ ### Editor de Configuração (Settings Panel)
432
+
433
+ Na aba “Visão Geral & Comportamento”, há um seletor “Scroll Horizontal” com as opções `Auto`, `Wrap` e `Host` (none). As alterações podem ser aplicadas (Aplicar) ou salvas (Salvar & Fechar). O valor é persistido junto com outras preferências da tabela.
434
+
435
+ ### Notas
436
+
437
+ - O caminho virtual (CDK Virtual Scroll) permanece sendo o owner da rolagem vertical; o viewport horizontal é um contêiner externo que não interfere no scroll vertical.
438
+ - Cabeçalho sticky continua funcional. Teste combinações com colunas sticky/virt para seu caso.
439
+ - Para grids alternativos (ex.: Kendo), as mesmas regras de `max-content`/`min-width: 100%` se aplicam ao elemento de tabela interno.
440
+
181
441
  ## Virtualização (CDK)
182
442
 
183
443
  Quando `behavior.virtualization.enabled` estiver ativo, as linhas da tabela são renderizadas com `cdk-virtual-scroll-viewport` (cabeçalho permanece igual).
@@ -201,6 +461,10 @@ Quando `behavior.virtualization.enabled` estiver ativo, as linhas da tabela são
201
461
  - `behavior.pagination.position`: `top` | `bottom` | `both`.
202
462
  - `behavior.pagination.style`: `default` | `compact` (aplica classe de estilo no paginator).
203
463
 
464
+ Nota sobre estratégia (client vs server)
465
+ - Se `behavior.pagination.strategy` não estiver definido, a tabela assume `server` automaticamente quando há `resourcePath` (dados remotos). Caso contrário, usa `client`.
466
+ - O mesmo vale para `behavior.sorting.strategy`.
467
+
204
468
  ## Duplo clique na linha
205
469
 
206
470
  Ative em Comportamento → Interação.
@@ -262,6 +526,8 @@ Observação: quando informado, o defaultSort é aplicado na carga inicial se n
262
526
 
263
527
  ## Coluna de ações (sticky)
264
528
 
529
+ Nota: por padrão a coluna de ações vem desabilitada. Habilite explicitamente em `actions.row.enabled` e defina as ações desejadas.
530
+
265
531
  - Fixe a coluna de ações no início/fim configurando `actions.row.sticky`:
266
532
 
267
533
  ```ts
@@ -347,8 +613,11 @@ sequenceDiagram
347
613
  - `notifyIfOutdated: 'inline' | 'snackbar' | 'both' | 'none' = 'both'`
348
614
  - `snoozeMs: number = 86400000` (24h)
349
615
  - `autoOpenSettingsOnOutdated: boolean = false`
350
- - Output:
616
+ - Outputs:
351
617
  - `schemaStatusChange: { outdated: boolean; serverHash?: string; lastVerifiedAt?: string; resourcePath?: string }`
618
+ - Emitido tanto na verificação leve (304/200) quanto no bootstrap do schema (primeira carga via `loadSchema()`).
619
+ - `metadataChange: { meta: any; reason: 'bootstrap'|'verification'|'applied' }`
620
+ - Emitido quando `config.meta` é atualizado (ex.: após bootstrap ou verificação ETag). Útil para sincronizar sidebars/bridges de metadados.
352
621
 
353
622
  ### Fallback Global (opcional)
354
623
 
@@ -403,7 +672,7 @@ onSchemaStatus(ev: { outdated: boolean; serverHash?: string; lastVerifiedAt?: st
403
672
  ### Boas práticas para estabilidade
404
673
 
405
674
  - Evite literais em bindings para o carregador dinâmico:
406
- - Não use `[fields]="[quickFieldMeta]"`; prefira um array estável como `quickFieldMetaArray` atualizado apenas quando o metadata mudar.
675
+ - Não use `[fields]="[pinnedFieldMeta]"`; prefira um array estável como `pinnedFieldMetaArray` atualizado apenas quando o metadata mudar.
407
676
  - Reutilize `FormGroup` quando possível. Se precisar trocar o `FormGroup` sem mudar os campos, deixe o `DynamicFieldLoader` reatribuir os controles (rebind-only) em vez de recriar componentes.
408
677
  - Evite recriar arrays/objetos desnecessariamente em `ngOnChanges`/`ngAfterViewInit`. Prefira atualizar valores (`setValue`, `patchValue`) e manter referências estáveis.
409
678
  - Observers (Resize/Mutation): atualize `overlayOrigin` e largura apenas quando houver mudanças reais; isso evita loops de CD.
@@ -488,7 +757,9 @@ sequenceDiagram
488
757
 
489
758
  ### Uso com Dados Locais (Client-Side)
490
759
 
491
- Se você precisar fornecer os dados manualmente (por exemplo, de uma fonte que não é uma API Praxis), pode usar o input `[data]` e omitir o `resourcePath`. Neste modo, todas as operações (paginação, ordenação, filtro) são realizadas no lado do cliente.
760
+ Se você precisar fornecer os dados manualmente (por exemplo, de uma fonte que não é uma API Praxis), pode usar o input `[data]` e omitir o `resourcePath`.
761
+ Importante: o modo local está protegido por feature flag e exige `behavior.localDataMode.enabled = true` (default é `false`).
762
+ Quando a flag está ativa, as operações de paginação, ordenação e filtro são executadas no lado do cliente.
492
763
 
493
764
  ```typescript
494
765
  import { PraxisTable } from "@praxisui/table";
@@ -508,12 +779,13 @@ export class ExampleComponent {
508
779
  { field: "name", header: "Nome", type: "string" },
509
780
  { field: "email", header: "Email", type: "string" },
510
781
  ],
511
- behavior: {
512
- pagination: { enabled: true, pageSize: 10 },
513
- sorting: { enabled: true },
514
- filtering: { enabled: true },
515
- },
516
- };
782
+ behavior: {
783
+ pagination: { enabled: true, pageSize: 10 },
784
+ sorting: { enabled: true },
785
+ filtering: { enabled: true },
786
+ localDataMode: { enabled: true }, // obrigatório para fluxo local oficial
787
+ },
788
+ };
517
789
 
518
790
  employees = [
519
791
  { id: 1, name: "João Silva", email: "joao@empresa.com" },
@@ -527,6 +799,8 @@ export class ExampleComponent {
527
799
 
528
800
  Quando a `<praxis-table>` é conectada a um `resourcePath`, as operações de paginação, ordenação e filtro são delegadas ao backend. Isso garante alta performance, pois apenas os dados visíveis na tela são trafegados pela rede.
529
801
 
802
+ Importante: se você não configurar explicitamente as estratégias de paginação/ordenação no `TableConfig`, a tabela resolve automaticamente como `server` quando há `resourcePath`. Se preferir operar no cliente, defina `behavior.pagination.strategy = 'client'` e/ou `behavior.sorting.strategy = 'client'` conscientemente.
803
+
530
804
  O diagrama abaixo detalha a sequência de eventos, desde a interação do usuário na UI até a construção da consulta JPA no servidor.
531
805
 
532
806
  ```mermaid
@@ -1006,7 +1280,7 @@ Para documentação completa da API, consulte a [documentação da @praxisui/cor
1006
1280
  O `PraxisFilter` pode ser acoplado à barra de ferramentas da tabela. O exemplo abaixo mostra a busca de pessoas por CPF e status.
1007
1281
 
1008
1282
  ```html
1009
- <praxis-filter [resourcePath]="'pessoas'" [formId]="'pessoas-filter'" [persistenceKey]="'pessoas-filter-v1'" [quickField]="'cpf'" [alwaysVisibleFields]="['status']" (submit)="onFilter($event)"></praxis-filter> <praxis-table [data]="tableData"></praxis-table>
1283
+ <praxis-filter [resourcePath]="'pessoas'" [formId]="'pessoas-filter'" [persistenceKey]="'pessoas-filter-v1'" [alwaysVisibleFields]="['status']" (submit)="onFilter($event)"></praxis-filter> <praxis-table [data]="tableData"></praxis-table>
1010
1284
  ```
1011
1285
 
1012
1286
  ```ts
@@ -1018,15 +1292,15 @@ onFilter(dto: any) {
1018
1292
  }
1019
1293
  ```
1020
1294
 
1021
- ### ⚙️ Painel de Configurações do Filtro
1295
+ ### ⚙️ Configuração do Filtro
1022
1296
 
1023
- O `PraxisFilter` possui um painel de configurações acessível pelo ícone de
1024
- engrenagem na barra do filtro ou programaticamente através do método
1297
+ O `PraxisFilter` pode ser configurado por inputs/JSON de configuração. O atalho
1298
+ visual por ícone de engrenagem não é exposto por padrão na tabela/demo.
1299
+ Quando necessário, também é possível abrir o painel programaticamente via
1025
1300
  `openSettings()`. Nesse painel é possível ajustar:
1026
1301
 
1027
- - **quickField** – campo utilizado para a busca rápida
1028
1302
  - **alwaysVisibleFields** – campos que permanecem sempre visíveis
1029
- - **placeholder** – texto exibido no campo de busca
1303
+ - **alwaysVisibleFieldMetadataOverrides** – patch de metadata por campo sempre visível (controle, clearButton, inlineAutoSize etc.)
1030
1304
  - **showAdvanced** – define se a seção avançada inicia aberta
1031
1305
 
1032
1306
  ```ts
@@ -1038,9 +1312,32 @@ abrirConfiguracoes() {
1038
1312
  ```
1039
1313
 
1040
1314
  Ao aplicar ou salvar, as escolhas são validadas contra os metadados
1041
- disponíveis. O componente exibe uma barra de progresso durante o processo
1042
- de persistência e mensagens de sucesso ou erro via _snack bar_, garantindo
1043
- uma experiência consistente.
1315
+ do **DTO de filtro** (schema de `POST /{resource}/filter`). Campos ausentes
1316
+ no DTO não são aplicados em `alwaysVisibleFields`.
1317
+ O componente exibe uma barra de progresso durante o processo de persistência e
1318
+ mensagens de sucesso ou erro via _snack bar_, garantindo uma experiência
1319
+ consistente.
1320
+
1321
+ ### 🔖 Mini‑guia: Atalhos do Filtro (tags)
1322
+
1323
+ Atalhos são “chips” que guardam um conjunto de filtros (DTO) para reuso rápido.
1324
+
1325
+ - Criar/Salvar
1326
+ - Modal (padrão): o host do diálogo exibe o botão quando `allowSaveTags === true`.
1327
+ - Gaveta (Drawer): o Adapter recebe `allowSaveTags?`, `i18nSaveAsShortcut?` e `onSaveShortcut?` (opcionais). O host deve exibir o botão e chamar `onSaveShortcut(clean({ ...initialDto, ...lastValue }))`.
1328
+ - O DTO é limpo antes de persistir (remove `'' | null | undefined`).
1329
+ - Aplicar
1330
+ - Clique/Enter no chip aplica imediatamente `tag.patch` (substitui o estado atual), emite `submit` e persiste.
1331
+ - O chip ativo é destacado (cor/borda + ícone de “check”) quando o DTO atual é igual ao patch do atalho.
1332
+ - Editar/Renomear
1333
+ - Apenas atalhos do usuário exibem ícone de lápis. Predefinidos mostram ícone de “cadeado” (somente leitura).
1334
+ - Excluir (com Undo)
1335
+ - Exclusão remove imediatamente o atalho do usuário e exibe snackbar “Atalho removido” com ação “Desfazer” quando `confirmTagDelete === true`.
1336
+ - Ao clicar em “Desfazer”, o atalho é restaurado na mesma posição e os eventos são reemitidos.
1337
+ - i18n
1338
+ - Chaves úteis: `saveAsShortcut`, `renameShortcut`, `removeShortcut`, `shortcutSaved`, `shortcutRemoved`, `undo`, `readonlyShortcut`.
1339
+
1340
+ Veja também: “Guia de Integração — Hosts (filtro em gaveta)” em `docs/host-crud-integration.md` para detalhes do contrato do Adapter.
1044
1341
 
1045
1342
  ### Novos Inputs/Outputs (PraxisFilter)
1046
1343
 
@@ -1064,6 +1361,51 @@ Notas rápidas (Flow ETag no Filter):
1064
1361
  - 200: atualiza `serverHash/lastVerifiedAt`; marca `outdated=true` apenas quando em customização; não aplica o schema automaticamente.
1065
1362
  - O corpo do schema é baixado apenas quando necessário para renderização (ex.: `alwaysVisibleFields` ou ao abrir o painel Avançado).
1066
1363
 
1364
+ ## Formatação de Colunas (format)
1365
+
1366
+ Cada coluna pode declarar `type` e uma string de formatação `format` consumida pelo `DataFormattingService`. Os tipos suportados são: `string`, `number`, `currency`, `percentage`, `date`, `boolean`, `custom`.
1367
+
1368
+ Regra geral: a formatação só é aplicada quando há `format` não vazio e `type !== 'custom'`.
1369
+
1370
+ Tipos e padrões de `format`:
1371
+ - number (DecimalPipe)
1372
+ - Padrões: `minInt.minFrac-maxFrac`
1373
+ - Exemplos: `1.0-0`, `1.2-2`, `1.0-3|nosep` (remove separador de milhar)
1374
+ - currency (CurrencyPipe)
1375
+ - Sintaxe: `CURRENCY|DISPLAY|DECIMALS[|nosep]`
1376
+ - Exemplos: `BRL|symbol|2`, `USD|code|0`, `EUR|symbol|2|nosep`
1377
+ - percentage (PercentPipe/DecimalPipe)
1378
+ - Sem multiplicador: `1.0-0` (PercentPipe)
1379
+ - Multiplicar por 100: `1.1-1|x100` (DecimalPipe×100 + `%`)
1380
+ - date (DatePipe)
1381
+ - Tokens Angular: `shortDate`, `mediumDate`, `longDate`, `fullDate`, `short`, `shortTime`
1382
+ - Padrões customizados: `dd/MM/yyyy`, `yyyy-MM-dd`, `dd/MM/yyyy HH:mm`
1383
+ - string (transformações + truncamento)
1384
+ - Transform: `uppercase` | `lowercase` | `titlecase` | `capitalize` | `none`
1385
+ - Transform + truncar: `<transform>|truncate|<max>|<suffix>`
1386
+ - Exemplos: `uppercase|truncate|50|...`, `titlecase`
1387
+ - boolean (pré-definidos ou custom)
1388
+ - Presets: `true-false` | `yes-no` | `active-inactive` | `on-off` | `enabled-disabled`
1389
+ - Custom: `custom|Verdadeiro|Falso`
1390
+
1391
+ Exemplos de ColumnDefinition:
1392
+ ```ts
1393
+ { field: 'salario', type: 'currency', format: 'BRL|symbol|2', header: 'Salário' }
1394
+ { field: 'desconto', type: 'percentage', format: '1.1-1|x100', header: 'Desconto' }
1395
+ { field: 'criadoEm', type: 'date', format: 'dd/MM/yyyy', header: 'Criado em' }
1396
+ { field: 'nome', type: 'string', format: 'titlecase|truncate|32|…', header: 'Nome' }
1397
+ { field: 'ativo', type: 'boolean', format: 'yes-no', header: 'Ativo' }
1398
+ ```
1399
+
1400
+ Observações:
1401
+ - `|nosep` remove separadores de milhar da saída formatada.
1402
+ - Para datas inválidas ou valores não numéricos, o serviço retorna o valor original (com aviso no console).
1403
+ - Colunas com renderizador `custom` não passam por formatação automática.
1404
+
1405
+ Exemplos práticos no workspace (rotas):
1406
+ - Regras Visuais (Simples): `/table-rules-simple`
1407
+ - Regras Visuais (Complexas): `/table-rules-complex`
1408
+
1067
1409
  ## 📊 Roadmap
1068
1410
 
1069
1411
  ### Próximas Versões
@@ -0,0 +1,166 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Inject, Component } from '@angular/core';
3
+ import * as i1$1 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+ import * as i1 from '@angular/material/dialog';
6
+ import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
7
+ import * as i3 from '@angular/material/button';
8
+ import { MatButtonModule } from '@angular/material/button';
9
+ import * as i15 from '@angular/material/progress-bar';
10
+ import { MatProgressBarModule } from '@angular/material/progress-bar';
11
+ import * as i5 from '@angular/material/icon';
12
+ import { MatIconModule } from '@angular/material/icon';
13
+ import { PraxisFilterForm } from '@praxisui/dynamic-form';
14
+
15
+ class FilterFormDialogHostComponent {
16
+ data;
17
+ ref;
18
+ valid = true;
19
+ lastValue = {};
20
+ formGroup = null;
21
+ canSave = false;
22
+ constructor(data, ref) {
23
+ this.data = data;
24
+ this.ref = ref;
25
+ }
26
+ onReady(ev) {
27
+ try {
28
+ this.formGroup = ev?.formGroup || null;
29
+ const dto = this.data?.initialDto || {};
30
+ if (ev?.formGroup && dto && Object.keys(dto).length) {
31
+ ev.formGroup.patchValue(dto, { emitEvent: false });
32
+ }
33
+ this.lastValue = this.formGroup?.getRawValue?.() ?? this.lastValue;
34
+ this.updateCanSave();
35
+ }
36
+ catch { }
37
+ }
38
+ onChange(ev) {
39
+ this.lastValue = ev?.formData ?? {};
40
+ this.updateCanSave();
41
+ }
42
+ onValidity(v) {
43
+ this.valid = v;
44
+ this.updateCanSave();
45
+ }
46
+ apply() {
47
+ const formData = this.formGroup?.getRawValue?.() ??
48
+ this.lastValue ??
49
+ this.data?.initialDto ??
50
+ {};
51
+ this.ref.close({ formData });
52
+ }
53
+ close() { this.ref.close(); }
54
+ clean(obj) {
55
+ const out = {};
56
+ Object.keys(obj || {}).forEach((k) => {
57
+ const v = obj[k];
58
+ if (v !== '' && v !== null && v !== undefined)
59
+ out[k] = v;
60
+ });
61
+ return out;
62
+ }
63
+ buildDtoForSave() {
64
+ const base = this.data?.initialDto || {};
65
+ const merged = { ...base, ...(this.lastValue || {}) };
66
+ return this.clean(merged);
67
+ }
68
+ updateCanSave() {
69
+ this.canSave = Object.keys(this.buildDtoForSave() || {}).length > 0;
70
+ }
71
+ saveShortcut() {
72
+ try {
73
+ const dto = this.buildDtoForSave();
74
+ if (!Object.keys(dto).length)
75
+ return;
76
+ this.data?.onSaveShortcut?.(dto);
77
+ }
78
+ catch { }
79
+ }
80
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: FilterFormDialogHostComponent, deps: [{ token: MAT_DIALOG_DATA }, { token: i1.MatDialogRef }], target: i0.ɵɵFactoryTarget.Component });
81
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: FilterFormDialogHostComponent, isStandalone: true, selector: "praxis-filter-form-dialog-host", ngImport: i0, template: `
82
+ <div mat-dialog-title class="pfx-dialog-title" id="filterDialogTitle">
83
+ <div class="pfx-dialog-title-text">
84
+ <mat-icon>tune</mat-icon>
85
+ <span>{{ data.title || 'Filtro avançado' }}</span>
86
+ </div>
87
+ <button mat-icon-button type="button" class="pfx-dialog-close" (click)="close()"
88
+ [attr.aria-label]="data.i18n?.cancel || 'Fechar'">
89
+ <mat-icon>close</mat-icon>
90
+ </button>
91
+ </div>
92
+ <mat-dialog-content class="pfx-filter-dialog-content" aria-labelledby="filterDialogTitle">
93
+ <mat-progress-bar *ngIf="data?.schemaLoading" mode="indeterminate"></mat-progress-bar>
94
+ <praxis-filter-form
95
+ *ngIf="data?.config"
96
+ [formId]="data.formId"
97
+ [resourcePath]="data.resourcePath"
98
+ [mode]="'edit'"
99
+ [config]="data.config"
100
+ (formReady)="onReady($event)"
101
+ (valueChange)="onChange($event)"
102
+ (validityChange)="onValidity($event)"
103
+ ></praxis-filter-form>
104
+ <p *ngIf="!data?.config && !data?.schemaLoading" class="pfx-empty-state">{{ data.i18n?.noData || 'Nenhum dado' }}</p>
105
+ </mat-dialog-content>
106
+ <mat-dialog-actions align="end" class="pfx-dialog-actions">
107
+ <button mat-button type="button"
108
+ *ngIf="data?.allowSaveTags"
109
+ [disabled]="!canSave"
110
+ (click)="saveShortcut()">
111
+ {{ data.i18n?.saveAsShortcut || 'Salvar como atalho' }}
112
+ </button>
113
+ <button mat-stroked-button type="button" (click)="close()">{{ data.i18n?.cancel || 'Cancelar' }}</button>
114
+ <button mat-flat-button color="primary" (click)="apply()" [disabled]="!valid">
115
+ {{ data.i18n?.apply || 'Aplicar' }}
116
+ </button>
117
+ </mat-dialog-actions>
118
+ `, isInline: true, styles: [".pfx-dialog-title{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-right:8px}.pfx-dialog-title-text{display:inline-flex;align-items:center;gap:8px;font-weight:600;color:var(--md-sys-color-on-surface)}.pfx-dialog-close{margin-left:auto}.pfx-filter-dialog-content{display:flex;flex-direction:column;gap:12px;padding-top:8px}.pfx-empty-state{margin:8px 0 0;color:var(--md-sys-color-on-surface-variant)}.pfx-dialog-actions{padding:var(--pdx-dialog-actions-padding, 12px 24px 16px);border-top:1px solid var(--md-sys-color-outline-variant);background:transparent;display:flex;align-items:center;gap:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.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: "component", type: i3.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatProgressBarModule }, { kind: "component", type: i15.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: PraxisFilterForm, selector: "praxis-filter-form", inputs: ["config", "formId", "resourcePath", "mode"], outputs: ["formReady", "valueChange", "submit", "validityChange"] }] });
119
+ }
120
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: FilterFormDialogHostComponent, decorators: [{
121
+ type: Component,
122
+ args: [{ selector: 'praxis-filter-form-dialog-host', standalone: true, imports: [CommonModule, MatDialogModule, MatButtonModule, MatProgressBarModule, MatIconModule, PraxisFilterForm], template: `
123
+ <div mat-dialog-title class="pfx-dialog-title" id="filterDialogTitle">
124
+ <div class="pfx-dialog-title-text">
125
+ <mat-icon>tune</mat-icon>
126
+ <span>{{ data.title || 'Filtro avançado' }}</span>
127
+ </div>
128
+ <button mat-icon-button type="button" class="pfx-dialog-close" (click)="close()"
129
+ [attr.aria-label]="data.i18n?.cancel || 'Fechar'">
130
+ <mat-icon>close</mat-icon>
131
+ </button>
132
+ </div>
133
+ <mat-dialog-content class="pfx-filter-dialog-content" aria-labelledby="filterDialogTitle">
134
+ <mat-progress-bar *ngIf="data?.schemaLoading" mode="indeterminate"></mat-progress-bar>
135
+ <praxis-filter-form
136
+ *ngIf="data?.config"
137
+ [formId]="data.formId"
138
+ [resourcePath]="data.resourcePath"
139
+ [mode]="'edit'"
140
+ [config]="data.config"
141
+ (formReady)="onReady($event)"
142
+ (valueChange)="onChange($event)"
143
+ (validityChange)="onValidity($event)"
144
+ ></praxis-filter-form>
145
+ <p *ngIf="!data?.config && !data?.schemaLoading" class="pfx-empty-state">{{ data.i18n?.noData || 'Nenhum dado' }}</p>
146
+ </mat-dialog-content>
147
+ <mat-dialog-actions align="end" class="pfx-dialog-actions">
148
+ <button mat-button type="button"
149
+ *ngIf="data?.allowSaveTags"
150
+ [disabled]="!canSave"
151
+ (click)="saveShortcut()">
152
+ {{ data.i18n?.saveAsShortcut || 'Salvar como atalho' }}
153
+ </button>
154
+ <button mat-stroked-button type="button" (click)="close()">{{ data.i18n?.cancel || 'Cancelar' }}</button>
155
+ <button mat-flat-button color="primary" (click)="apply()" [disabled]="!valid">
156
+ {{ data.i18n?.apply || 'Aplicar' }}
157
+ </button>
158
+ </mat-dialog-actions>
159
+ `, styles: [".pfx-dialog-title{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-right:8px}.pfx-dialog-title-text{display:inline-flex;align-items:center;gap:8px;font-weight:600;color:var(--md-sys-color-on-surface)}.pfx-dialog-close{margin-left:auto}.pfx-filter-dialog-content{display:flex;flex-direction:column;gap:12px;padding-top:8px}.pfx-empty-state{margin:8px 0 0;color:var(--md-sys-color-on-surface-variant)}.pfx-dialog-actions{padding:var(--pdx-dialog-actions-padding, 12px 24px 16px);border-top:1px solid var(--md-sys-color-outline-variant);background:transparent;display:flex;align-items:center;gap:8px}\n"] }]
160
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
161
+ type: Inject,
162
+ args: [MAT_DIALOG_DATA]
163
+ }] }, { type: i1.MatDialogRef }] });
164
+
165
+ export { FilterFormDialogHostComponent };
166
+ //# sourceMappingURL=praxisui-table-filter-form-dialog-host.component-CLF05-__.mjs.map