@praxisui/table 10.0.0-rc.40 → 10.0.0-rc.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/ai/component-registry.json +2 -2
- package/docs/dynamic-filter-host-integration-guide.md +23 -1
- package/docs/dynamic-filter-payload-contract.md +29 -6
- package/fesm2022/praxisui-table-authoring.mjs +33 -1
- package/fesm2022/{praxisui-table-filter-form-dialog-host.component-DlGomgO3.mjs → praxisui-table-filter-form-dialog-host.component-Cst6rkAK.mjs} +49 -6
- package/fesm2022/{praxisui-table-praxisui-table-vjpONc0b.mjs → praxisui-table-praxisui-table-CdEy3SYM.mjs} +308 -31
- package/fesm2022/{praxisui-table-table-agentic-authoring-turn-flow-D0489HPn.mjs → praxisui-table-table-agentic-authoring-turn-flow-sQz0BpSY.mjs} +1 -1
- package/fesm2022/{praxisui-table-table-ai.adapter-2CXgwI8n.mjs → praxisui-table-table-ai.adapter-DEetBMv9.mjs} +1 -1
- package/fesm2022/praxisui-table.mjs +1 -1
- package/package.json +10 -10
- package/src/lib/praxis-table.json-api.md +10 -0
- package/types/praxisui-table.d.ts +6 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"generatedAt": "2026-10-27T22:14:23.237Z",
|
|
4
4
|
"packageName": "@praxisui/table",
|
|
5
|
-
"packageVersion": "10.0.0-rc.
|
|
5
|
+
"packageVersion": "10.0.0-rc.41",
|
|
6
6
|
"sourceRegistry": "praxis-component-registry-ingestion",
|
|
7
7
|
"sourceRegistryVersion": "1.0.0",
|
|
8
8
|
"componentCount": 3,
|
|
@@ -42,7 +42,7 @@ source_of_truth:
|
|
|
42
42
|
- "projects/praxis-table/src/lib/praxis-table.html"
|
|
43
43
|
- "projects/praxis-table/src/lib/components/praxis-filter/praxis-filter.component.ts"
|
|
44
44
|
- "projects/praxis-table/src/lib/services/filter-config.service.ts"
|
|
45
|
-
last_updated: "2026-
|
|
45
|
+
last_updated: "2026-10-27"
|
|
46
46
|
---
|
|
47
47
|
|
|
48
48
|
# Dynamic Filter Host Integration Guide
|
|
@@ -108,6 +108,28 @@ O host precisa garantir que a fonte de metadata do filtro e o backend estejam al
|
|
|
108
108
|
|
|
109
109
|
Sem isso, o runtime até renderiza o formulário, mas o payload não será previsível.
|
|
110
110
|
|
|
111
|
+
### Projetar tipos e validar antes de buscar/salvar
|
|
112
|
+
|
|
113
|
+
Para filtros escalares em array, mantenha `items`/`enumValues` do schema de operação
|
|
114
|
+
nos metadados dos campos. `items` descreve tipo, formato e enum dos itens; não
|
|
115
|
+
substitui o schema de uma coleção de objetos. O normalizador compartilhado de Core
|
|
116
|
+
interpreta somente os tokens admitidos por esse contrato, sem parser específico no
|
|
117
|
+
host ou no controle de filtro.
|
|
118
|
+
|
|
119
|
+
Use os payloads de `change`/`submit` do componente. O caminho avançado/modal usa a
|
|
120
|
+
projeção do `PraxisFilterForm` filho ao aplicar e salvar, em vez de ler valores raw
|
|
121
|
+
que podem conter strings de UI ou campos com política de omissão. O merge com
|
|
122
|
+
`initialDto` para um atalho é novamente projetado para não restaurar campos
|
|
123
|
+
omitidos. Campos sempre visíveis e avançados seguem a mesma regra; salvar um
|
|
124
|
+
atalho não altera o contrato do backend.
|
|
125
|
+
|
|
126
|
+
Grupos inválidos/pendentes ou tokens fora do tipo/enum não emitem busca nem atalho,
|
|
127
|
+
e um debounce anterior também precisa respeitar o estado atual. Preserve o
|
|
128
|
+
rascunho para correção. Sem filtros ativos, o DTO vazio pode representar limpeza;
|
|
129
|
+
não transformar o status `DISABLED` desse grupo vazio em bloqueio de clear. Essas
|
|
130
|
+
regras não tornam `contextChange` um DTO: somente sua propriedade `filter` contém
|
|
131
|
+
a consulta; labels e metadata permanecem evidência de apresentação.
|
|
132
|
+
|
|
111
133
|
### 2. Configurar `advancedFilters.settings`
|
|
112
134
|
|
|
113
135
|
Essas chaves são as mais importantes na prática:
|
|
@@ -33,7 +33,6 @@ related_docs:
|
|
|
33
33
|
- "dynamic-filter-range-filters-guide"
|
|
34
34
|
- "dynamic-fields-inline-components-guide"
|
|
35
35
|
- "dynamic-fields-inline-filter-runtime-contract"
|
|
36
|
-
- "table-overview"
|
|
37
36
|
keywords:
|
|
38
37
|
- "submit"
|
|
39
38
|
- "change"
|
|
@@ -42,11 +41,9 @@ keywords:
|
|
|
42
41
|
source_of_truth:
|
|
43
42
|
- "projects/praxis-table/src/lib/components/praxis-filter/praxis-filter.component.ts"
|
|
44
43
|
- "projects/praxis-table/src/lib/components/praxis-filter/praxis-filter.component.spec.ts"
|
|
45
|
-
- "
|
|
46
|
-
- "
|
|
47
|
-
|
|
48
|
-
- "../praxis-metadata-starter/docs/guides/FILTROS-E-PAGINACAO.md"
|
|
49
|
-
last_updated: "2026-03-07"
|
|
44
|
+
- "projects/praxis-table/src/lib/components/praxis-filter/filter-payload-projection.ts"
|
|
45
|
+
- "projects/praxis-core/src/lib/helpers/scalar-array-value.ts"
|
|
46
|
+
last_updated: "2026-10-27"
|
|
50
47
|
---
|
|
51
48
|
|
|
52
49
|
# Dynamic Filter Payload Contract
|
|
@@ -131,6 +128,32 @@ Aspectos relevantes do runtime:
|
|
|
131
128
|
- estado avançado e preferências visuais podem ser persistidos localmente;
|
|
132
129
|
- aliases inline e metadata overrides influenciam a UI, mas não devem gerar um contrato HTTP arbitrário.
|
|
133
130
|
|
|
131
|
+
### Arrays escalares, enum e campos omitidos
|
|
132
|
+
|
|
133
|
+
O schema de operação pode publicar um array escalar com `items`, por exemplo
|
|
134
|
+
`{ "type": ["array", "null"], "items": { "type": "integer" } }` para um operando
|
|
135
|
+
`IN`. Core conserva `FieldScalarItemsSchema` em `FieldMetadata.items`; o mesmo
|
|
136
|
+
`normalizeScalarArrayValue` valida e converte tokens na projeção do payload.
|
|
137
|
+
`array.itemSchema.fields`, usado para coleções de objetos, é um contrato distinto.
|
|
138
|
+
|
|
139
|
+
A projeção interna de Table alcança os campos sempre visíveis, o painel avançado,
|
|
140
|
+
`change`, `submit` e atalhos salvos. Ela conserva o shape flat ou aninhado do DTO,
|
|
141
|
+
aplica `submitPolicy: "omit"` e as constraints `items`/`enumValues` publicadas, sem
|
|
142
|
+
modificar o rascunho. Para um request com container, o payload pode ser
|
|
143
|
+
`{ "customFields": { "years": [2024, 2025, 2026] } }`; números não viram datas por
|
|
144
|
+
terem três posições e labels não viram valores de consulta.
|
|
145
|
+
|
|
146
|
+
Tokens inválidos, valor fora do enum e grupos inválidos/pendentes impedem a emissão,
|
|
147
|
+
inclusive de um debounce agendado antes da invalidez. Não há descarte parcial de
|
|
148
|
+
itens inválidos. `null`/ausência do container e `[]` opcionais não significam que um
|
|
149
|
+
item `null` seja válido. Um grupo sem filtros ativos e `DISABLED` pode produzir a
|
|
150
|
+
limpeza intencional do filtro; isso não equivale a autorizar emissão de um grupo
|
|
151
|
+
com campos ativos inválidos.
|
|
152
|
+
|
|
153
|
+
O host deve consumir a projeção emitida, não reconstruir o DTO a partir de
|
|
154
|
+
`getRawValue()`, chips ou labels. O backend continua validando o DTO e a operação;
|
|
155
|
+
validação no browser não concede autorização nem certifica execução da consulta.
|
|
156
|
+
|
|
134
157
|
### 2. Envio HTTP
|
|
135
158
|
|
|
136
159
|
O host é responsável por:
|
|
@@ -1201,6 +1201,12 @@ class TableSchemaColumnProjector {
|
|
|
1201
1201
|
const includeOrder = new Map(include.map((field, index) => [field, index]));
|
|
1202
1202
|
const order = Array.isArray(projection?.order) ? projection.order : include;
|
|
1203
1203
|
const presentationOrder = new Map(order.map((field, index) => [field, index]));
|
|
1204
|
+
const fieldsByName = new Map();
|
|
1205
|
+
for (const field of Array.isArray(fields) ? fields : []) {
|
|
1206
|
+
const name = typeof field?.name === 'string' ? field.name.trim() : '';
|
|
1207
|
+
if (name)
|
|
1208
|
+
fieldsByName.set(name, field);
|
|
1209
|
+
}
|
|
1204
1210
|
const schemaColumns = (Array.isArray(fields) ? fields : [])
|
|
1205
1211
|
.filter((field) => !field.tableHidden && !field.hidden)
|
|
1206
1212
|
.filter((field) => include.length === 0 || includeOrder.has(field.name))
|
|
@@ -1212,15 +1218,18 @@ class TableSchemaColumnProjector {
|
|
|
1212
1218
|
const base = this.convertFieldToColumn(field);
|
|
1213
1219
|
const override = overrides[field.name];
|
|
1214
1220
|
if (!override || typeof override !== 'object' || Array.isArray(override)) {
|
|
1221
|
+
this.applySchemaComposeItemsQuickView(base, fieldsByName);
|
|
1215
1222
|
return base;
|
|
1216
1223
|
}
|
|
1217
1224
|
const merged = deepMerge(base, override);
|
|
1218
|
-
|
|
1225
|
+
const projected = {
|
|
1219
1226
|
...merged,
|
|
1220
1227
|
field: base.field,
|
|
1221
1228
|
_isApiField: base._isApiField,
|
|
1222
1229
|
_originalApiType: base._originalApiType,
|
|
1223
1230
|
};
|
|
1231
|
+
this.applySchemaComposeItemsQuickView(projected, fieldsByName);
|
|
1232
|
+
return projected;
|
|
1224
1233
|
});
|
|
1225
1234
|
const canonicalFields = new Set((Array.isArray(fields) ? fields : [])
|
|
1226
1235
|
.map((field) => String(field?.name || '').trim())
|
|
@@ -1367,9 +1376,32 @@ class TableSchemaColumnProjector {
|
|
|
1367
1376
|
// Default to string for ambiguous cases
|
|
1368
1377
|
return 'string';
|
|
1369
1378
|
}
|
|
1379
|
+
applySchemaComposeItemsQuickView(col, fieldsByName) {
|
|
1380
|
+
const renderer = col?.renderer;
|
|
1381
|
+
if (renderer?.type !== 'compose' || !Array.isArray(renderer?.compose?.items)) {
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
for (const item of renderer.compose.items) {
|
|
1385
|
+
if (!item || typeof item !== 'object')
|
|
1386
|
+
continue;
|
|
1387
|
+
const itemType = typeof item.type === 'string' ? item.type.trim() : 'value';
|
|
1388
|
+
if (itemType !== 'value' || item.quickView !== undefined)
|
|
1389
|
+
continue;
|
|
1390
|
+
const itemFieldName = typeof item.field === 'string' ? item.field.trim() : '';
|
|
1391
|
+
if (!itemFieldName)
|
|
1392
|
+
continue;
|
|
1393
|
+
const itemField = fieldsByName.get(itemFieldName);
|
|
1394
|
+
if (itemField?.quickView) {
|
|
1395
|
+
item.quickView = this.clonePlainObject(itemField.quickView);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1370
1399
|
applySchemaFieldPresentation(field, col) {
|
|
1371
1400
|
if (!field || !col)
|
|
1372
1401
|
return;
|
|
1402
|
+
if (field.quickView && !col.quickView) {
|
|
1403
|
+
col.quickView = this.clonePlainObject(field.quickView);
|
|
1404
|
+
}
|
|
1373
1405
|
const controlType = this.normalizePresentationToken(field.controlType);
|
|
1374
1406
|
const numericFormat = this.normalizePresentationToken(field.numericFormat ?? field.format);
|
|
1375
1407
|
const valuePresentationType = this.normalizePresentationToken(field.valuePresentation?.type);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Inject, Component } from '@angular/core';
|
|
2
|
+
import { ViewChild, Inject, Component } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/material/dialog';
|
|
4
4
|
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
|
5
5
|
import * as i3 from '@angular/material/button';
|
|
@@ -12,6 +12,7 @@ import * as i14 from '@angular/material/tooltip';
|
|
|
12
12
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
13
13
|
import { PraxisFilterForm } from '@praxisui/dynamic-form';
|
|
14
14
|
import { PraxisIconButtonComponent } from '@praxisui/core';
|
|
15
|
+
import { aq as projectFilterPayload } from './praxisui-table-praxisui-table-CdEy3SYM.mjs';
|
|
15
16
|
|
|
16
17
|
class FilterFormDialogHostComponent {
|
|
17
18
|
data;
|
|
@@ -20,6 +21,8 @@ class FilterFormDialogHostComponent {
|
|
|
20
21
|
ready = false;
|
|
21
22
|
lastValue = {};
|
|
22
23
|
formGroup = null;
|
|
24
|
+
filterForm;
|
|
25
|
+
pendingAction = null;
|
|
23
26
|
canSave = false;
|
|
24
27
|
saving = false;
|
|
25
28
|
constructor(data, ref) {
|
|
@@ -48,11 +51,39 @@ class FilterFormDialogHostComponent {
|
|
|
48
51
|
this.updateCanSave();
|
|
49
52
|
}
|
|
50
53
|
apply() {
|
|
54
|
+
this.requestProjectedValue('apply');
|
|
55
|
+
}
|
|
56
|
+
requestProjectedValue(action) {
|
|
57
|
+
if (!this.valid || this.formGroup?.invalid || this.formGroup?.pending)
|
|
58
|
+
return;
|
|
59
|
+
if (this.filterForm) {
|
|
60
|
+
this.pendingAction = action;
|
|
61
|
+
try {
|
|
62
|
+
this.filterForm.onFormSubmit();
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
this.pendingAction = null;
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// A host without a rendered form has no schema projection to apply.
|
|
70
|
+
if (this.data?.config)
|
|
71
|
+
return;
|
|
51
72
|
const formData = this.formGroup?.getRawValue?.() ??
|
|
52
73
|
this.lastValue ??
|
|
53
74
|
this.data?.initialDto ??
|
|
54
75
|
{};
|
|
55
|
-
this.
|
|
76
|
+
this.completeAction(action, formData);
|
|
77
|
+
}
|
|
78
|
+
onProjectedSubmit(event) {
|
|
79
|
+
this.completeAction(this.pendingAction ?? 'apply', event.formData);
|
|
80
|
+
}
|
|
81
|
+
completeAction(action, formData) {
|
|
82
|
+
this.lastValue = formData;
|
|
83
|
+
if (action === 'apply')
|
|
84
|
+
this.ref.close({ formData });
|
|
85
|
+
else
|
|
86
|
+
this.saveProjectedShortcut(formData);
|
|
56
87
|
}
|
|
57
88
|
close() { this.ref.close(); }
|
|
58
89
|
clean(obj) {
|
|
@@ -68,7 +99,8 @@ class FilterFormDialogHostComponent {
|
|
|
68
99
|
const base = this.data?.initialDto || {};
|
|
69
100
|
const current = this.formGroup?.getRawValue?.() ?? this.lastValue ?? {};
|
|
70
101
|
const merged = { ...base, ...current };
|
|
71
|
-
|
|
102
|
+
const projected = projectFilterPayload(merged, this.data?.config?.fieldMetadata ?? []);
|
|
103
|
+
return projected === null ? {} : this.clean(projected);
|
|
72
104
|
}
|
|
73
105
|
updateCanSave() {
|
|
74
106
|
this.canSave =
|
|
@@ -91,7 +123,13 @@ class FilterFormDialogHostComponent {
|
|
|
91
123
|
saveShortcut() {
|
|
92
124
|
if (!this.canSave || this.saving)
|
|
93
125
|
return;
|
|
94
|
-
|
|
126
|
+
this.requestProjectedValue('save');
|
|
127
|
+
}
|
|
128
|
+
saveProjectedShortcut(projected) {
|
|
129
|
+
const merged = projectFilterPayload({ ...(this.data?.initialDto || {}), ...projected }, this.data?.config?.fieldMetadata ?? []);
|
|
130
|
+
if (merged === null)
|
|
131
|
+
return;
|
|
132
|
+
const dto = this.clean(merged);
|
|
95
133
|
if (!Object.keys(dto).length)
|
|
96
134
|
return;
|
|
97
135
|
this.saving = true;
|
|
@@ -104,7 +142,7 @@ class FilterFormDialogHostComponent {
|
|
|
104
142
|
}
|
|
105
143
|
}
|
|
106
144
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: FilterFormDialogHostComponent, deps: [{ token: MAT_DIALOG_DATA }, { token: i1.MatDialogRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
107
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: FilterFormDialogHostComponent, isStandalone: true, selector: "praxis-filter-form-dialog-host", ngImport: i0, template: `
|
|
145
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: FilterFormDialogHostComponent, isStandalone: true, selector: "praxis-filter-form-dialog-host", viewQueries: [{ propertyName: "filterForm", first: true, predicate: PraxisFilterForm, descendants: true }], ngImport: i0, template: `
|
|
108
146
|
<div class="pfx-dialog-title">
|
|
109
147
|
<h2 mat-dialog-title class="pfx-dialog-title-text">
|
|
110
148
|
<mat-icon>tune</mat-icon>
|
|
@@ -137,6 +175,7 @@ class FilterFormDialogHostComponent {
|
|
|
137
175
|
(formReady)="onReady($event)"
|
|
138
176
|
(valueChange)="onChange($event)"
|
|
139
177
|
(validityChange)="onValidity($event)"
|
|
178
|
+
(submit)="onProjectedSubmit($event)"
|
|
140
179
|
></praxis-filter-form>
|
|
141
180
|
}
|
|
142
181
|
@if (!data?.config && !data?.schemaLoading) {
|
|
@@ -197,6 +236,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
197
236
|
(formReady)="onReady($event)"
|
|
198
237
|
(valueChange)="onChange($event)"
|
|
199
238
|
(validityChange)="onValidity($event)"
|
|
239
|
+
(submit)="onProjectedSubmit($event)"
|
|
200
240
|
></praxis-filter-form>
|
|
201
241
|
}
|
|
202
242
|
@if (!data?.config && !data?.schemaLoading) {
|
|
@@ -224,6 +264,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
224
264
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
225
265
|
type: Inject,
|
|
226
266
|
args: [MAT_DIALOG_DATA]
|
|
227
|
-
}] }, { type: i1.MatDialogRef }]
|
|
267
|
+
}] }, { type: i1.MatDialogRef }], propDecorators: { filterForm: [{
|
|
268
|
+
type: ViewChild,
|
|
269
|
+
args: [PraxisFilterForm]
|
|
270
|
+
}] } });
|
|
228
271
|
|
|
229
272
|
export { FilterFormDialogHostComponent };
|