@praxisui/table 1.0.0-beta.30 → 1.0.0-beta.40

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
@@ -13,6 +13,20 @@ Para ver esta biblioteca em funcionamento em uma aplicação completa, utilize o
13
13
 
14
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.
15
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
+
16
30
  ## ✨ Características Principais
17
31
 
18
32
  ### 🏗️ Arquitetura Unificada
@@ -42,7 +56,7 @@ A biblioteca `@praxisui/table` fornece um componente de tabela robusto e altamen
42
56
  - **Messages Editor**: Textos e localização
43
57
 
44
58
  Nota (Regras)
45
- - A antiga aba visual genérica foi removida deste pacote. Um editor especializado para regras da Tabela está em desenvolvimento e pode ser usado no painel de configuração.
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.
46
60
 
47
61
  ### 🧩 Editor de Regras (column-first)
48
62
 
@@ -70,24 +84,26 @@ Notas sobre enum/CSV:
70
84
  - Para campos enumerados, as opções são inferidas de `valueMapping` da coluna ou de `fields?.options`.
71
85
  - Para `in/not in` em números/strings, use CSV (ex.: `10, 20, 30` ou `Alice, Bob`).
72
86
 
73
- #### Extensões de DSL (JSON/Tempo)
87
+ #### Extensões de DSL (runtime nativo + custom)
74
88
 
75
- Você pode registrar funções adicionais no `DslParser` do app host para expressões com JSON e data/tempo.
89
+ O runtime da tabela inclui helpers de JSON/tempo/listas/texto sem configuração extra:
76
90
 
77
- Exemplo rápido (registro direto):
78
- ```ts
79
- import { DslParser } from '@praxisui/specification';
80
- import { registerJsonDslFunctions, registerDateDslFunctions } from '@praxisui/table';
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`
81
96
 
82
- const parser = new DslParser<any>();
83
- registerJsonDslFunctions(parser);
84
- registerDateDslFunctions(parser);
85
- ```
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'])`
86
104
 
87
- Exemplos de DSL:
88
- - `jsonGet(payload, '$.user.name') == 'Alice'`
89
- - `hasJsonKey(payload, '$.meta.etag')`
90
- - `jsonPathMatches(payload, '$.roles[0]', '^admin$')`
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`.
91
107
 
92
108
  Guia completo: `projects/praxis-table/docs/DSL-Extensions-Guide.md`
93
109
 
@@ -118,8 +134,26 @@ Campos relacionais (FK/objetos) podem ser editados no Rules Editor com busca ass
118
134
  Boas práticas
119
135
  - Informe `resourcePath` (ou `endpoint`) no `FieldDefinition` para habilitar o lookup via `GenericCrudService` do host.
120
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
+ ```
121
155
  - Para múltipla seleção, use `multiple: true` no `FieldDefinition` quando aplicável.
122
- - No runtime, registre as funções JSON no parser (ver guia) para que as expressões de ID/JSON funcionem: `jsonGet/hasJsonKey/jsonPathMatches`.
156
+ - No runtime da tabela não é necessário registrar funções JSON/data manualmente; os helpers são embutidos.
123
157
  - APIs com busca: suporte a um parâmetro livre de texto (por ex. `search`) facilita a experiência do autocomplete.
124
158
 
125
159
  Exemplo de FieldDefinition (lookup)
@@ -142,8 +176,8 @@ const fields: FieldDefinition[] = [
142
176
  ```
143
177
 
144
178
  Operadores e DSL gerada (relacional)
145
- - `id ==` → `jsonGet(customer, '$.id') == 10`
146
- - `id in` → `jsonGet(customer, '$.id') in [10, 20]`
179
+ - `id ==` → `jsonEq(customer, '$.id', 10)`
180
+ - `id in` → `jsonIn(customer, '$.id', [10, 20])`
147
181
  - `has property` → `hasJsonKey(customer, '$.meta.etag')`
148
182
  - Condições por caminho (join simples): use `key ==` (JSON Path + valor)
149
183
 
@@ -161,7 +195,7 @@ Para que o editor/preview e o runtime avaliem corretamente as regras relacionais
161
195
  const row = {
162
196
  id: 123,
163
197
  customer: {
164
- id: 10, // ← usado em id == / id in via jsonGet(customer, '$.id')
198
+ id: 10, // ← usado em id == / id in via jsonEq/jsonIn
165
199
  name: 'ACME', // ← usado para exibir label no autocomplete/chip
166
200
  meta: { etag: 'v1' },
167
201
  },
@@ -169,13 +203,13 @@ const row = {
169
203
 
170
204
  // Regras DSL comuns
171
205
  // id ==
172
- // jsonGet(customer, '$.id') == 10
206
+ // jsonEq(customer, '$.id', 10)
173
207
  // id in
174
- // jsonGet(customer, '$.id') in [10, 20]
208
+ // jsonIn(customer, '$.id', [10, 20])
175
209
  // has property
176
210
  // hasJsonKey(customer, '$.meta.etag')
177
211
  // caminho/valor (join simples)
178
- // jsonGet(customer, '$.name') == 'ACME'
212
+ // jsonEq(customer, '$.name', 'ACME')
179
213
  ```
180
214
 
181
215
  Notas
@@ -191,36 +225,33 @@ Onde é consumido (preview e runtime)
191
225
  - Parser interno do componente: projects/praxis-table/src/lib/praxis-table.ts:282
192
226
 
193
227
  Observação
194
- - Ao registrar funções via `DslParserFactory` (ex.: `registerJsonDslFunctions`/`registerDateDslFunctions`), use a mesma estratégia para o parser que avalia regras no runtime (Tabela) e para qualquer serviço que precise avaliar DSL. O Editor usa um parser interno para validação/preview; as funções de data já são suportadas por padrão, e as de JSON podem exigir extensão no runtime (ver guia).
228
+ - O runtime da tabela injeta o registry DSL padrão para validação/parse/execução.
229
+ - Para funções realmente custom, injete via input `[dslFunctionRegistry]` no componente.
195
230
 
196
231
  Exemplo de Provider (recomendado)
197
232
 
198
233
  ```ts
199
234
  import { Injectable } from '@angular/core';
200
- import { DslParser } from '@praxisui/specification';
201
- import { registerJsonDslFunctions, registerDateDslFunctions } from '@praxisui/table';
235
+ import { FunctionRegistry } from '@praxisui/specification';
202
236
 
203
237
  @Injectable({ providedIn: 'root' })
204
- export class DslParserFactory {
205
- private parser: DslParser<any>;
238
+ export class TableDslRegistryFactory {
239
+ private readonly registry = FunctionRegistry.getInstance<any>('orders-table-rules');
206
240
 
207
241
  constructor() {
208
- this.parser = new DslParser<any>();
209
- registerJsonDslFunctions(this.parser);
210
- registerDateDslFunctions(this.parser);
242
+ this.registry.clear();
243
+ this.registry.register('isStatus', (_row: any, value: any, expected: any) =>
244
+ String(value ?? '').toLowerCase() === String(expected ?? '').toLowerCase(),
245
+ );
211
246
  }
212
247
 
213
- get(): DslParser<any> {
214
- return this.parser;
248
+ get(): FunctionRegistry<any> {
249
+ return this.registry;
215
250
  }
216
251
  }
217
252
 
218
- // Em um componente/serviço que avalia regras:
219
- // constructor(factory: DslParserFactory) {
220
- // const parser = factory.get();
221
- // const spec = parser.parse("jsonGet(payload, '$.user.name') == 'Alice'");
222
- // const ok = spec.isSatisfiedBy({ payload: { user: { name: 'Alice' } } });
223
- // }
253
+ // Uso no host:
254
+ // <praxis-table [dslFunctionRegistry]="tableDslRegistryFactory.get()" ... />
224
255
  ```
225
256
 
226
257
  ### ⚙️ Painel de Configurações
@@ -641,7 +672,7 @@ onSchemaStatus(ev: { outdated: boolean; serverHash?: string; lastVerifiedAt?: st
641
672
  ### Boas práticas para estabilidade
642
673
 
643
674
  - Evite literais em bindings para o carregador dinâmico:
644
- - 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.
645
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.
646
677
  - Evite recriar arrays/objetos desnecessariamente em `ngOnChanges`/`ngAfterViewInit`. Prefira atualizar valores (`setValue`, `patchValue`) e manter referências estáveis.
647
678
  - Observers (Resize/Mutation): atualize `overlayOrigin` e largura apenas quando houver mudanças reais; isso evita loops de CD.
@@ -726,7 +757,9 @@ sequenceDiagram
726
757
 
727
758
  ### Uso com Dados Locais (Client-Side)
728
759
 
729
- 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.
730
763
 
731
764
  ```typescript
732
765
  import { PraxisTable } from "@praxisui/table";
@@ -746,12 +779,13 @@ export class ExampleComponent {
746
779
  { field: "name", header: "Nome", type: "string" },
747
780
  { field: "email", header: "Email", type: "string" },
748
781
  ],
749
- behavior: {
750
- pagination: { enabled: true, pageSize: 10 },
751
- sorting: { enabled: true },
752
- filtering: { enabled: true },
753
- },
754
- };
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
+ };
755
789
 
756
790
  employees = [
757
791
  { id: 1, name: "João Silva", email: "joao@empresa.com" },
@@ -1246,7 +1280,7 @@ Para documentação completa da API, consulte a [documentação da @praxisui/cor
1246
1280
  O `PraxisFilter` pode ser acoplado à barra de ferramentas da tabela. O exemplo abaixo mostra a busca de pessoas por CPF e status.
1247
1281
 
1248
1282
  ```html
1249
- <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>
1250
1284
  ```
1251
1285
 
1252
1286
  ```ts
@@ -1258,15 +1292,15 @@ onFilter(dto: any) {
1258
1292
  }
1259
1293
  ```
1260
1294
 
1261
- ### ⚙️ Painel de Configurações do Filtro
1295
+ ### ⚙️ Configuração do Filtro
1262
1296
 
1263
- O `PraxisFilter` possui um painel de configurações acessível pelo ícone de
1264
- 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
1265
1300
  `openSettings()`. Nesse painel é possível ajustar:
1266
1301
 
1267
- - **quickField** – campo utilizado para a busca rápida
1268
1302
  - **alwaysVisibleFields** – campos que permanecem sempre visíveis
1269
- - **placeholder** – texto exibido no campo de busca
1303
+ - **alwaysVisibleFieldMetadataOverrides** – patch de metadata por campo sempre visível (controle, clearButton, inlineAutoSize etc.)
1270
1304
  - **showAdvanced** – define se a seção avançada inicia aberta
1271
1305
 
1272
1306
  ```ts
@@ -1278,9 +1312,32 @@ abrirConfiguracoes() {
1278
1312
  ```
1279
1313
 
1280
1314
  Ao aplicar ou salvar, as escolhas são validadas contra os metadados
1281
- disponíveis. O componente exibe uma barra de progresso durante o processo
1282
- de persistência e mensagens de sucesso ou erro via _snack bar_, garantindo
1283
- 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.
1284
1341
 
1285
1342
  ### Novos Inputs/Outputs (PraxisFilter)
1286
1343
 
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"praxisui-table-filter-form-dialog-host.component-CLF05-__.mjs","sources":["../../../projects/praxis-table/src/lib/components/praxis-filter/filter-form-dialog-host.component.ts"],"sourcesContent":["import { Component, Inject } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatProgressBarModule } from '@angular/material/progress-bar';\nimport { MatIconModule } from '@angular/material/icon';\nimport { FormGroup } from '@angular/forms';\nimport { PraxisFilterForm } from '@praxisui/dynamic-form';\n\n@Component({\n selector: 'praxis-filter-form-dialog-host',\n standalone: true,\n imports: [CommonModule, MatDialogModule, MatButtonModule, MatProgressBarModule, MatIconModule, PraxisFilterForm],\n template: `\n <div mat-dialog-title class=\"pfx-dialog-title\" id=\"filterDialogTitle\">\n <div class=\"pfx-dialog-title-text\">\n <mat-icon>tune</mat-icon>\n <span>{{ data.title || 'Filtro avançado' }}</span>\n </div>\n <button mat-icon-button type=\"button\" class=\"pfx-dialog-close\" (click)=\"close()\"\n [attr.aria-label]=\"data.i18n?.cancel || 'Fechar'\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n <mat-dialog-content class=\"pfx-filter-dialog-content\" aria-labelledby=\"filterDialogTitle\">\n <mat-progress-bar *ngIf=\"data?.schemaLoading\" mode=\"indeterminate\"></mat-progress-bar>\n <praxis-filter-form\n *ngIf=\"data?.config\"\n [formId]=\"data.formId\"\n [resourcePath]=\"data.resourcePath\"\n [mode]=\"'edit'\"\n [config]=\"data.config\"\n (formReady)=\"onReady($event)\"\n (valueChange)=\"onChange($event)\"\n (validityChange)=\"onValidity($event)\"\n ></praxis-filter-form>\n <p *ngIf=\"!data?.config && !data?.schemaLoading\" class=\"pfx-empty-state\">{{ data.i18n?.noData || 'Nenhum dado' }}</p>\n </mat-dialog-content>\n <mat-dialog-actions align=\"end\" class=\"pfx-dialog-actions\">\n <button mat-button type=\"button\"\n *ngIf=\"data?.allowSaveTags\"\n [disabled]=\"!canSave\"\n (click)=\"saveShortcut()\">\n {{ data.i18n?.saveAsShortcut || 'Salvar como atalho' }}\n </button>\n <button mat-stroked-button type=\"button\" (click)=\"close()\">{{ data.i18n?.cancel || 'Cancelar' }}</button>\n <button mat-flat-button color=\"primary\" (click)=\"apply()\" [disabled]=\"!valid\">\n {{ data.i18n?.apply || 'Aplicar' }}\n </button>\n </mat-dialog-actions>\n `,\n styles: [`\n .pfx-dialog-title {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding-right: 8px;\n }\n .pfx-dialog-title-text {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n font-weight: 600;\n color: var(--md-sys-color-on-surface);\n }\n .pfx-dialog-close {\n margin-left: auto;\n }\n .pfx-filter-dialog-content {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding-top: 8px;\n }\n .pfx-empty-state {\n margin: 8px 0 0;\n color: var(--md-sys-color-on-surface-variant);\n }\n .pfx-dialog-actions {\n padding: var(--pdx-dialog-actions-padding, 12px 24px 16px);\n border-top: 1px solid var(--md-sys-color-outline-variant);\n background: transparent;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n `],\n})\nexport class FilterFormDialogHostComponent {\n valid = true;\n private lastValue: any = {};\n private formGroup: FormGroup<Record<string, any>> | null = null;\n canSave = false;\n constructor(\n @Inject(MAT_DIALOG_DATA)\n public data: {\n formId: string;\n resourcePath: string;\n config: any;\n title?: string;\n schemaLoading?: boolean;\n initialDto?: Record<string, any>;\n allowSaveTags?: boolean;\n i18n?: { saveAsShortcut?: string; cancel?: string; apply?: string; noData?: string };\n onSaveShortcut?: (dto: Record<string, any>) => void;\n },\n private ref: MatDialogRef<FilterFormDialogHostComponent>,\n ) {}\n\n onReady(ev: { formGroup: any }): void {\n try {\n this.formGroup = (ev?.formGroup as FormGroup<Record<string, any>>) || null;\n const dto = this.data?.initialDto || {};\n if (ev?.formGroup && dto && Object.keys(dto).length) {\n ev.formGroup.patchValue(dto, { emitEvent: false });\n }\n this.lastValue = this.formGroup?.getRawValue?.() ?? this.lastValue;\n this.updateCanSave();\n } catch {}\n }\n onChange(ev: { formData: Record<string, any> }): void {\n this.lastValue = ev?.formData ?? {};\n this.updateCanSave();\n }\n onValidity(v: boolean): void {\n this.valid = v;\n this.updateCanSave();\n }\n apply(): void {\n const formData =\n this.formGroup?.getRawValue?.() ??\n this.lastValue ??\n this.data?.initialDto ??\n {};\n this.ref.close({ formData });\n }\n close(): void { this.ref.close(); }\n\n private clean(obj: Record<string, any> | undefined | null): Record<string, any> {\n const out: Record<string, any> = {};\n Object.keys(obj || {}).forEach((k) => {\n const v = (obj as any)[k];\n if (v !== '' && v !== null && v !== undefined) out[k] = v;\n });\n return out;\n }\n private buildDtoForSave(): Record<string, any> {\n const base = this.data?.initialDto || {};\n const merged = { ...base, ...(this.lastValue || {}) };\n return this.clean(merged);\n }\n private updateCanSave(): void {\n this.canSave = Object.keys(this.buildDtoForSave() || {}).length > 0;\n }\n saveShortcut(): void {\n try {\n const dto = this.buildDtoForSave();\n if (!Object.keys(dto).length) return;\n this.data?.onSaveShortcut?.(dto);\n } catch {}\n }\n}\n"],"names":["i2","i4"],"mappings":"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"}