@praxisui/table 8.0.0-beta.21 → 8.0.0-beta.23

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.
@@ -118,6 +118,55 @@ Essas chaves são as mais importantes na prática:
118
118
  - `allowSaveTags`: habilita tags persistidas/visíveis;
119
119
  - `changeDebounceMs`: regula a cadência de emissão de `change` e `submit`.
120
120
 
121
+ #### Padronizar `materialDesign` dos campos de filtro
122
+
123
+ O `praxis-filter` normaliza a metadata efetiva de filtros com uma politica
124
+ visual consistente para toolbar compacta e formulario avancado. Quando o host
125
+ fornece metadata propria ou overrides em `alwaysVisibleFieldMetadataOverrides`,
126
+ ele deve preservar essa politica para campos com icones, prefixos, sufixos,
127
+ datepicker toggle, simbolo de moeda, seletor de cor ou clear button.
128
+
129
+ Configuracao recomendada:
130
+
131
+ ```ts
132
+ const filterFieldMaterialDesign = {
133
+ floatLabel: 'always',
134
+ subscriptSizing: 'dynamic',
135
+ } as const;
136
+ ```
137
+
138
+ Exemplo de override:
139
+
140
+ ```ts
141
+ advancedFilters: {
142
+ settings: {
143
+ alwaysVisibleFieldMetadataOverrides: {
144
+ cpf: {
145
+ prefixIcon: 'fingerprint',
146
+ materialDesign: filterFieldMaterialDesign,
147
+ },
148
+ dataNascimento: {
149
+ materialDesign: filterFieldMaterialDesign,
150
+ },
151
+ },
152
+ },
153
+ }
154
+ ```
155
+
156
+ Nao trate isso como ajuste cosmetico local. O Angular Material recomenda
157
+ `floatLabel="always"` para campos `fill`/`outline` com prefixos/sufixos porque o
158
+ label em repouso nao compartilha o mesmo alinhamento do valor do input. A regra
159
+ de plataforma e aplicar essa politica na metadata efetiva de filtro antes de
160
+ recorrer a CSS; o runtime ja faz essa normalizacao para a barra compacta e para
161
+ o formulario avancado.
162
+
163
+ Campos compostos de faixa (`priceRange`, `dateRange`, `dateTimeRange` e
164
+ `timeRange`) devem ocupar uma linha completa no formulario avancado. Eles
165
+ materializam mais de um controle interno e, quando competem lado a lado com um
166
+ campo simples, perdem alinhamento vertical e tornam o hint ambíguo. O
167
+ `praxis-filter` aplica essa classe de layout automaticamente ao montar o
168
+ `FormConfig` avancado.
169
+
121
170
  ### 3. Definir política do painel avançado
122
171
 
123
172
  Os principais knobs são:
@@ -103,6 +103,13 @@ O `PraxisFilter` converte `controlType` genérico em variante inline por padrão
103
103
 
104
104
  Para monetário, `priceRange` já aponta para a experiência compacta específica do filtro.
105
105
 
106
+ ### Formulario avancado
107
+
108
+ No formulario avancado, ranges compostos devem ocupar a linha completa. A regra
109
+ evita comparar verticalmente um controle com duas entradas internas contra um
110
+ campo simples, preserva leitura de label/hint e mantem espaco suficiente para
111
+ `Min`/`Max`, icones e sufixos.
112
+
106
113
  ### Filter settings
107
114
 
108
115
  `filter-settings.component.ts` participa da história porque ele controla:
@@ -118,6 +118,43 @@ O shape enviado não respeita o contrato ou foi interpretado de maneira diferent
118
118
 
119
119
  Compare o payload com o guia de ranges antes de tentar “corrigir no controller”.
120
120
 
121
+ ## Sintoma: label conflita com icone, prefixo ou sufixo
122
+
123
+ ### Causa provável
124
+
125
+ Campos Material `fill`/`outline` com `prefixIcon`, `suffixIcon`, `clearButton`,
126
+ datepicker toggle, simbolo de moeda, seletor de cor ou outros
127
+ `matPrefix`/`matSuffix` podem exibir o label em repouso sobreposto ou desalinhado.
128
+
129
+ Esse comportamento vem da propria geometria do `mat-form-field`: em `fill` e
130
+ `outline`, o label em repouso e o valor do input nao usam o mesmo alinhamento. O
131
+ Angular Material recomenda `floatLabel="always"` nesses casos.
132
+
133
+ ### Onde olhar
134
+
135
+ - metadata efetiva do campo no filtro avancado;
136
+ - `alwaysVisibleFieldMetadataOverrides`;
137
+ - politica global aplicada pelo `praxis-filter` para materializar campos de filtro;
138
+ - `metadata.materialDesign.floatLabel`;
139
+ - `metadata.materialDesign.subscriptSizing`.
140
+
141
+ ### Ação
142
+
143
+ Configure a metadata do campo ou a politica global do filtro:
144
+
145
+ ```json
146
+ {
147
+ "materialDesign": {
148
+ "floatLabel": "always",
149
+ "subscriptSizing": "dynamic"
150
+ }
151
+ }
152
+ ```
153
+
154
+ Nao corrija deslocando label, prefixo, sufixo ou notch por CSS local. Esse tipo
155
+ de patch acopla o host a detalhes internos do Angular Material e tende a quebrar
156
+ em upgrades.
157
+
121
158
  ## Sintoma: campo inline não aparece na barra compacta
122
159
 
123
160
  ### Causa provável
@@ -5679,7 +5679,10 @@ class DataFormattingService {
5679
5679
  if (value instanceof Date)
5680
5680
  return value;
5681
5681
  if (typeof value === 'string' || typeof value === 'number') {
5682
- let date = new Date(this.normalizeNumericDateInput(value));
5682
+ let date = this.parseDateOnlyString(value);
5683
+ if (!date) {
5684
+ date = new Date(this.normalizeNumericDateInput(value));
5685
+ }
5683
5686
  if (typeof value === 'string' && isNaN(date.getTime())) {
5684
5687
  const parts = value.split(',');
5685
5688
  if (parts.length === 3) {
@@ -5722,6 +5725,22 @@ class DataFormattingService {
5722
5725
  return value;
5723
5726
  }
5724
5727
  }
5728
+ parseDateOnlyString(value) {
5729
+ if (typeof value !== 'string') {
5730
+ return null;
5731
+ }
5732
+ const match = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(value.trim());
5733
+ if (!match) {
5734
+ return null;
5735
+ }
5736
+ const year = Number(match[1]);
5737
+ const month = Number(match[2]) - 1;
5738
+ const day = Number(match[3]);
5739
+ const date = new Date(year, month, day);
5740
+ return date.getFullYear() === year && date.getMonth() === month && date.getDate() === day
5741
+ ? date
5742
+ : null;
5743
+ }
5725
5744
  normalizeNumericDateInput(value) {
5726
5745
  if (typeof value !== 'number' || !Number.isFinite(value)) {
5727
5746
  return value;
@@ -34636,7 +34655,7 @@ class PraxisFilter {
34636
34655
  const shortcuts = shouldUseInlineDateRange
34637
34656
  ? (existingShortcuts.length ? existingShortcuts : [...INLINE_DATE_RANGE_CORPORATE_SHORTCUTS])
34638
34657
  : meta.shortcuts;
34639
- return {
34658
+ return this.withFilterMaterialDesignPolicy({
34640
34659
  ...meta,
34641
34660
  controlType: inlineControlType,
34642
34661
  granularity: inlineControlType === INLINE_PERIOD_RANGE_CONTROL_TYPE
@@ -34651,15 +34670,10 @@ class PraxisFilter {
34651
34670
  shortcuts,
34652
34671
  updateOn: 'change',
34653
34672
  density: 'compact',
34654
- materialDesign: {
34655
- ...(meta.materialDesign || {}),
34656
- floatLabel: 'always',
34657
- subscriptSizing: 'dynamic',
34658
- },
34659
- };
34673
+ });
34660
34674
  };
34661
34675
  const alwaysWithDensity = initialAlways
34662
- .map((meta) => this.mergeAlwaysVisibleMetadata(withToolbarMaterialDesign(meta), alwaysVisibleOverrides[meta.name]));
34676
+ .map((meta) => this.withFilterMaterialDesignPolicy(this.mergeAlwaysVisibleMetadata(withToolbarMaterialDesign(meta), alwaysVisibleOverrides[meta.name])));
34663
34677
  const selectedWithDensity = initialSelected.map(withToolbarMaterialDesign);
34664
34678
  // Separar toggles (booleans simples) para a faixa de ações, se habilitado
34665
34679
  const isToggle = (m) => {
@@ -34744,16 +34758,9 @@ class PraxisFilter {
34744
34758
  .filter((m) => !this.alwaysVisibleFields?.includes(m.name))
34745
34759
  .filter((m) => !this.selectedFieldIds?.includes(m.name))
34746
34760
  .map((m) => ({ ...m, updateOn: 'change' }));
34747
- // Quando o painel avançado abre em modal/gaveta, habilitar touchUi nos campos de data
34748
- if (this.advancedOpenMode === 'modal' || this.advancedOpenMode === 'drawer') {
34749
- advancedMetas = advancedMetas.map((m) => {
34750
- const ct = m.controlType;
34751
- if (ct === 'date' || ct === 'dateRange' || ct === 'dateTime' || ct === 'dateTimeRange') {
34752
- return { ...m, touchUi: true };
34753
- }
34754
- return m;
34755
- });
34756
- }
34761
+ // Material's compact datepicker popup is unreliable inside the advanced dialog/drawer overlay.
34762
+ // Use touch UI by default there, while preserving explicit metadata overrides.
34763
+ advancedMetas = advancedMetas.map((m) => this.withAdvancedDatePickerTouchPolicy(m));
34757
34764
  const hasClearOverride = this.advancedClearButtonsEnabled !== undefined;
34758
34765
  const clearButtonsEnabled = this.advancedClearButtonsEnabled !== false;
34759
34766
  if (advancedMetas.length) {
@@ -34775,6 +34782,7 @@ class PraxisFilter {
34775
34782
  return m;
34776
34783
  });
34777
34784
  }
34785
+ advancedMetas = advancedMetas.map((m) => this.withFilterMaterialDesignPolicy(m));
34778
34786
  this.logFilterWarn('[PFILTER] applySchemaMetas: advancedMetas', {
34779
34787
  count: advancedMetas.length,
34780
34788
  names: advancedMetas.map((m) => m.name),
@@ -34787,7 +34795,7 @@ class PraxisFilter {
34787
34795
  id: 'advanced',
34788
34796
  rows: [
34789
34797
  {
34790
- columns: advancedMetas.map((m) => ({ fields: [m.name] })),
34798
+ columns: advancedMetas.map((m) => this.buildAdvancedFilterColumn(m)),
34791
34799
  },
34792
34800
  ],
34793
34801
  },
@@ -34820,6 +34828,57 @@ class PraxisFilter {
34820
34828
  catch { }
34821
34829
  });
34822
34830
  }
34831
+ withFilterMaterialDesignPolicy(meta) {
34832
+ return {
34833
+ ...meta,
34834
+ materialDesign: {
34835
+ ...(meta.materialDesign || {}),
34836
+ floatLabel: 'always',
34837
+ subscriptSizing: 'dynamic',
34838
+ },
34839
+ };
34840
+ }
34841
+ withAdvancedDatePickerTouchPolicy(meta) {
34842
+ if (!this.isAdvancedDatePickerControl(meta)) {
34843
+ return meta;
34844
+ }
34845
+ if (Object.prototype.hasOwnProperty.call(meta, 'touchUi')) {
34846
+ return meta;
34847
+ }
34848
+ if (this.advancedOpenMode !== 'modal' && this.advancedOpenMode !== 'drawer') {
34849
+ return meta;
34850
+ }
34851
+ return { ...meta, touchUi: true };
34852
+ }
34853
+ isAdvancedDatePickerControl(meta) {
34854
+ const token = normalizeControlTypeToken(meta?.controlType || '');
34855
+ return (token === 'date' ||
34856
+ token === 'daterange' ||
34857
+ token === 'datetime' ||
34858
+ token === 'datetimerange' ||
34859
+ token === 'materialdatepicker' ||
34860
+ token === 'materialdaterange');
34861
+ }
34862
+ buildAdvancedFilterColumn(meta) {
34863
+ const column = {
34864
+ id: `advanced-${meta.name}`,
34865
+ fields: [meta.name],
34866
+ };
34867
+ if (this.isCompositeRangeFilterField(meta)) {
34868
+ column.className = 'filter-column--full filter-column--range';
34869
+ }
34870
+ return column;
34871
+ }
34872
+ isCompositeRangeFilterField(meta) {
34873
+ const token = normalizeControlTypeToken(meta?.controlType || '');
34874
+ return (token === 'pricerange' ||
34875
+ token === 'daterange' ||
34876
+ token === 'datetimerange' ||
34877
+ token === 'timerange' ||
34878
+ token === 'materialpricerange' ||
34879
+ token === 'materialdaterange' ||
34880
+ token === 'materialtimerange');
34881
+ }
34823
34882
  pickMetasByOrder(metas, orderedNames) {
34824
34883
  if (!orderedNames?.length)
34825
34884
  return [];
package/index.d.ts CHANGED
@@ -217,6 +217,7 @@ declare class DataFormattingService {
217
217
  */
218
218
  formatValue(value: any, columnType: ColumnDataType$1, formatString: string, options?: DataFormattingOptions): any;
219
219
  private coerceValueToType;
220
+ private parseDateOnlyString;
220
221
  private normalizeNumericDateInput;
221
222
  private formatDate;
222
223
  private formatNumber;
@@ -647,6 +648,11 @@ declare class PraxisFilter implements OnInit, OnChanges, AfterViewInit, OnDestro
647
648
  retrySchemaLoad(): void;
648
649
  private applySchemaMetas;
649
650
  private applySchemaMetasNow;
651
+ private withFilterMaterialDesignPolicy;
652
+ private withAdvancedDatePickerTouchPolicy;
653
+ private isAdvancedDatePickerControl;
654
+ private buildAdvancedFilterColumn;
655
+ private isCompositeRangeFilterField;
650
656
  private pickMetasByOrder;
651
657
  private hasRemoteOptionSource;
652
658
  private withInferredFilterControlType;
package/package.json CHANGED
@@ -1,23 +1,23 @@
1
1
  {
2
2
  "name": "@praxisui/table",
3
- "version": "8.0.0-beta.21",
3
+ "version": "8.0.0-beta.23",
4
4
  "description": "Advanced data table for Angular (Praxis UI) with editing, filtering, sorting, virtualization, and settings panel integration.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^20.0.0",
7
7
  "@angular/core": "^20.0.0",
8
- "@praxisui/ai": "^8.0.0-beta.21",
9
- "@praxisui/core": "^8.0.0-beta.21",
10
- "@praxisui/dynamic-fields": "^8.0.0-beta.21",
11
- "@praxisui/dynamic-form": "^8.0.0-beta.21",
12
- "@praxisui/metadata-editor": "^8.0.0-beta.21",
13
- "@praxisui/rich-content": "^8.0.0-beta.21",
14
- "@praxisui/settings-panel": "^8.0.0-beta.21",
15
- "@praxisui/table-rule-builder": "^8.0.0-beta.21",
8
+ "@praxisui/ai": "^8.0.0-beta.23",
9
+ "@praxisui/core": "^8.0.0-beta.23",
10
+ "@praxisui/dynamic-fields": "^8.0.0-beta.23",
11
+ "@praxisui/dynamic-form": "^8.0.0-beta.23",
12
+ "@praxisui/metadata-editor": "^8.0.0-beta.23",
13
+ "@praxisui/rich-content": "^8.0.0-beta.23",
14
+ "@praxisui/settings-panel": "^8.0.0-beta.23",
15
+ "@praxisui/table-rule-builder": "^8.0.0-beta.23",
16
16
  "@angular/cdk": "^20.0.0",
17
17
  "@angular/forms": "^20.0.0",
18
18
  "@angular/material": "^20.0.0",
19
19
  "@angular/router": "^20.0.0",
20
- "@praxisui/dialog": "^8.0.0-beta.21",
20
+ "@praxisui/dialog": "^8.0.0-beta.23",
21
21
  "rxjs": "~7.8.0"
22
22
  },
23
23
  "dependencies": {