@praxisui/table 10.0.0-rc.40 → 10.0.0-rc.42

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
@@ -814,6 +814,20 @@ das abas. Acima desse limite, reaparece a navegação por abas. O Settings Panel
814
814
  organiza título, expandir e fechar em uma grade até 599px; a prévia, quando
815
815
  suportada, ocupa a linha seguinte. Nenhum documento ou protocolo de Save é alterado.
816
816
 
817
+ ### Filhos de páginas governadas
818
+
819
+ Consulte [o adapter nativo de página](docs/governed-page-widget-adapter.md) para reconstrução sem defaults, presença exata, identidade derivada e execução sem preferências concorrentes.
820
+
821
+ ### Shared assistant voice policy
822
+
823
+ Configure `{ provide: PRAXIS_ASSISTANT_VOICE_INPUT_MODE, useValue: 'governed-transcription' }` once in the host application (token from `@praxisui/ai`). The shell, Page Builder and Table inherit it; explicit component inputs can override it, including `disabled`. The default remains disabled when the host does not opt in.
824
+
825
+ The shared composer checks `GET /api/praxis/config/ai/transcriptions/capabilities` before requesting microphone permission. Readiness means local provider/credential configuration, not quota or network health. Recording, transcription and cancellation preserve the editable draft and never auto-submit. Deploy the backend capability before enabling the updated client.
826
+
827
+ Voice language also inherits `PRAXIS_ASSISTANT_VOICE_LANGUAGE`; explicit component inputs override it. Governed capture is bound to confirmed enterprise context (user/tenant/profile/version) and is cancelled when that authority is invalidated. Page Builder session resets and Table owner changes discard the old shell capture; editing the current draft does not cancel it. The host must keep its session and runtime authority coherent and invalidate it on session-generation changes.
828
+
829
+ Corporate adoption requires a ready context and current context-version headers, a host resolver bound to the server session/membership, and the correct cookie transport. The quickstart enterprise resolver applies this admission to all its AI callers. Local header-only integration cannot detect cookie changes that the host does not report. Focused/mock tests are not proof of durable session persistence, provider connectivity or published package adoption.
830
+
817
831
  <!-- praxis-npm-catalog-links:start -->
818
832
  ## Official documentation and examples
819
833
 
@@ -821,7 +835,3 @@ suportada, ocupa a linha seguinte. Nenhum documento ou protocolo de Save é alte
821
835
  - [Praxis Angular public catalog](https://praxisui.dev/angular)
822
836
  - [Public example: Examples](https://praxisui.dev/docs/components/praxis-table/examples)
823
837
  <!-- praxis-npm-catalog-links:end -->
824
-
825
- ### Filhos de páginas governadas
826
-
827
- Consulte [o adapter nativo de página](docs/governed-page-widget-adapter.md) para reconstrução sem defaults, presença exata, identidade derivada e execução sem preferências concorrentes.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-10-26T18:09:20.136Z",
3
+ "generatedAt": "2026-11-01T13:59:56.156Z",
4
4
  "packageName": "@praxisui/table",
5
- "packageVersion": "10.0.0-rc.40",
5
+ "packageVersion": "10.0.0-rc.42",
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:
@@ -307,7 +307,7 @@ function tableDetailAuthoringIssues(config) {
307
307
  || (detail.presentation?.initiallyCollapsed !== undefined && typeof detail.presentation.initiallyCollapsed !== 'boolean')
308
308
  || (detail.presentation?.bottomStackPosition !== undefined && !['beforeControls', 'afterControls'].includes(detail.presentation.bottomStackPosition))
309
309
  || (detail.presentation?.emptyBehavior !== undefined && !['message', 'hide'].includes(detail.presentation.emptyBehavior))
310
- || (detail.source?.mode !== undefined && !['inline', 'resource', 'resourcePath', 'hypermedia'].includes(detail.source.mode))
310
+ || (detail.source?.mode !== undefined && !['inline', 'resource', 'resourcePath', 'hypermedia', 'schema'].includes(detail.source.mode))
311
311
  || (detail.source?.inlineSchema !== undefined && !isObject(detail.source.inlineSchema))
312
312
  || (detail.source?.inlineSchema?.items !== undefined && !Array.isArray(detail.source.inlineSchema.items))
313
313
  || (resource !== undefined && !isObject(resource))
@@ -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-DQZySZjy.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 };