@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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-10-26T18:09:20.136Z",
3
+ "generatedAt": "2026-10-27T22:14:23.237Z",
4
4
  "packageName": "@praxisui/table",
5
- "packageVersion": "10.0.0-rc.40",
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-03-07"
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
- - "../praxis-metadata-starter/src/main/java/org/praxisplatform/uischema/controller/base/AbstractResourceQueryController.java"
46
- - "../praxis-metadata-starter/src/main/java/org/praxisplatform/uischema/filter/web/FilterRequestBodyAdvice.java"
47
- - "../praxis-metadata-starter/docs/examples/filter-dto.md"
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
- return {
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.ref.close({ formData });
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
- return this.clean(merged);
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
- const dto = this.buildDtoForSave();
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 };