---
title: "Table"
slug: "table-overview"
description: "Visao geral do @praxisui/table com TableConfig unificada, filtros, renderers, performance e integracao enterprise."
doc_type: "reference"
document_kind: "component-overview"
component: "table"
category: "components"
audience:
  - "frontend"
  - "host"
  - "architect"
level: "intermediate"
status: "active"
owner: "praxis-ui"
tags:
  - "table"
  - "tableconfig"
  - "filters"
  - "renderers"
  - "enterprise"
order: 25
icon: "table_chart"
toc: true
sidebar: true
search_boost: 1.0
reading_time: 14
estimated_setup_time: 25
version: "1.0"
related_docs:
  - "dynamic-filter-architecture-overview"
  - "dynamic-filter-host-integration-guide"
  - "dynamic-filter-payload-contract"
  - "dynamic-filter-range-filters-guide"
  - "dynamic-filter-editor-settings-guide"
  - "dynamic-inline-filter-catalog"
  - "dynamic-filter-troubleshooting-guide"
  - "dynamic-filter-backend-contract-cheatsheet"
  - "adr-dynamic-filter-cross-lib-coupling-2026-03"
  - "host-integration-guide"
  - "consumer-integration-quickstart"
keywords:
  - "TableConfig"
  - "filters"
  - "renderers"
  - "performance"
  - "dynamic filter"
last_updated: "2026-03-07"
---

# @praxisui/table

## Documentation

- Documentação oficial: https://praxisui.dev
- Aplicação de referência: https://github.com/codexrodrigues/praxis-ui-quickstart
- Indicado para: data grids enterprise com filtros, edicao, virtualizacao, renderers e governanca de configuracao

## When to use

- Exibir grandes volumes de dados com filtros e operacoes ricas
- Governar configuracao de colunas, toolbar e comportamento em runtime
- Integrar tabelas a fluxos CRUD, IA e editores visuais do ecossistema Praxis UI

> Componente de tabela empresarial avançado com arquitetura unificada

## Customization Mode Contract

- `enableCustomization` is the canonical public input for runtime customization mode.
- The default is `false`; hosts opt in when they want settings, authoring affordances and schema-drift UX.
- Customization mode does not change the table data mode. It only gates configuration/editorial surfaces.

## Migration Note

- This release treats `enableCustomization=false` as the canonical default.
- Hosts that relied on the previous implicit authoring behavior must now declare `[enableCustomization]="true"` explicitly.
- This is an intentional breaking change for enterprise governance: authoring is opt-in, not ambient.

## 🌟 Visão Geral

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.

## 🎨 Tema M3 (tokens mínimos)

Para garantir que a tabela, filtros e editores reflitam o tema do app host:

- Superfícies: `--md-sys-color-surface`, `--md-sys-color-surface-variant`, `--md-sys-color-surface-container-*`
- 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`
- Semânticos: `--md-sys-color-primary`, `--md-sys-color-secondary`, `--md-sys-color-tertiary`, `--md-sys-color-error`
- Containers: `--md-sys-color-primary-container`, `--md-sys-color-secondary-container`, `--md-sys-color-tertiary-container`, `--md-sys-color-error-container`
- Overlay: `--md-sys-color-scrim`
- Elevação: `--md-sys-elevation-level1`–`--md-sys-elevation-level3`

Observação: tokens `--md-sys-*` e `--mdc-theme-*` continuam aceitos quando presentes.
Nota: a classe de tema é decisão do host (`.dark-theme` ou `.theme-dark`/`.theme-light`); mantenha tokens e componentes no mesmo escopo.

## ✨ Características Principais

### 🏗️ Arquitetura Unificada

- **Interface única**: `TableConfig` consolidada
- **Type Safety**: Tipagem forte em toda a API
- **Performance otimizada**: Eliminação de overhead de adaptação
- **API simplificada**: Menos confusão, mais produtividade

### 📊 Recursos Avançados

- **Paginação inteligente**: Client-side e server-side
- **Ordenação múltipla**: Suporte a multi-sort
- **Filtros dinâmicos**: Global e por coluna
- **Seleção de linhas**: Single, multiple e bulk actions
- **Redimensionamento**: Colunas redimensionáveis
- **Virtualização**: Para grandes volumes de dados
- **Exportação**: CSV, Excel, PDF
- **Acessibilidade**: WCAG 2.1 AA compliant
- **Verificação de Schema**: ETag/If-None-Match com notificações (somente em customização)

### 📄 Contrato Inline do Filtro

- Referencia canonica: `projects/praxis-dynamic-fields/docs/dynamic-fields-inline-components-guide.md` (slug publicado: `dynamic-fields-inline-components-guide`)
- No contexto da tabela, use este contrato para definir `controlType` inline e politica de compatibilidade (canonica vs legado).

### 🧭 Decisoes de Arquitetura (ADR)

- Bundle/adapters: `projects/praxis-table/docs/adr/2026-03-filter-drawer-adapter-light-entrypoint.md`

### 🎨 Editores Visuais

- **Behavior Editor**: Configuração de comportamentos
- **Columns Editor**: Gestão avançada de colunas
- **Toolbar Editor**: Personalização de ações
- **Messages Editor**: Textos e localização

Nota (Regras)
- 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.

### 🧩 Editor de Regras (column-first)

O novo editor é "column-first" e usa uma tipagem compartilhada com o Editor de Colunas. Principais pontos:

- Escopo: aplique regras na linha inteira (rowConditionalStyles) ou em uma coluna específica (columns[].conditionalStyles).
- Operadores por tipo: o conjunto de operadores é filtrado por tipo (string/number/date/boolean/enum).
- Editores de valor dinâmicos: inputs numéricos, listas (CSV/múltipla seleção), datepickers.
- Grupos lógicos: construa condições com AND/OR/NOT e reordene via arrastar-e-soltar (CDK DragDrop).
- Preview: execute “Testar” para ver quantas linhas seriam afetadas pelas regras ativas.
- Import/Export: exporta JSON sem o campo `enabled`; importa com validação de DSL e sanitização de estilos (allowlist).

Exemplos de DSL:

```text
contains(status, 'Ativo')
not (status in ['A', 'I'])
(price >= 10 and price <= 20)
(createdAt >= '2024-10-01' and createdAt <= '2024-10-31')
(name == null or name == '')
active == true
```

Notas sobre enum/CSV:
- Para campos enumerados, as opções são inferidas de `valueMapping` da coluna ou de `fields?.options`.
- Para `in/not in` em números/strings, use CSV (ex.: `10, 20, 30` ou `Alice, Bob`).

#### Extensões de DSL (runtime nativo + custom)

O runtime da tabela já inclui helpers de JSON/tempo/listas/texto sem configuração extra:

- JSON: `jsonGet`, `hasJsonKey`, `jsonPathMatches`, `jsonEq`, `jsonIn`, `jsonType`
- Texto/valor: `isBlank`, `eqIgnoreCase`, `coalesce`
- Faixas: `between`, `dateBetween`
- Coleções: `containsAny`, `containsAll`, `len`, `lenEq`, `lenGt`, `lenLt`, `lenAtLeast`
- Tempo: `today`, `now`, `dateAdd`, `dateFormat`, `isToday`, `inLast`, `isPast`, `isPastOrNow`, `isFuture`, `isFutureOrNow`, `weekdayIn`

Exemplos de DSL (canônica):
- `jsonEq(payload, '$.user.name', 'Alice')`
- `jsonIn(customer, '$.id', [10, 20])`
- `eqIgnoreCase(status, 'ATIVO')`
- `between(amount, 10, 20)`
- `dateBetween(createdAt, '2025-01-01', '2025-12-31')`
- `containsAll(tags, ['vip', 'gold'])`

Observação de gramática:
- 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`.

Guia completo: `projects/praxis-table/docs/DSL-Extensions-Guide.md`

#### 🎨 Efeitos (Conteúdo C)

O bloco de Efeitos permite aplicar estilo e visuais quando a condição é verdadeira:

- Toolbar de texto com ícones (negrito, itálico, sublinhado) e presets rápidos (tachado, maiúsculas/minúsculas/capitalizar, tamanho +/- e reset).
- Presets de feedback com ícones (sucesso, aviso, erro, info) — mantêm o rótulo via tooltip e `aria-label`.
- “Posição do valor” como grupo de alternância (antes / entre-1 / entre-2 / depois / oculto).
- “Ordem dos visuais” com seletor compacto (3 mais comuns) e menu para opções extras.
- Layout do compose: espaçamento, alinhamento, quebra de linha e reticências.

Dicas
- Use “Resetar texto” para reverter efeitos de texto sem perder cor de fundo/borda.
- Para acessibilidade, os grupos possuem `role="toolbar"`, tooltips e navegação por teclado consistente.

Como reverter efeitos
- “Reset Text” remove apenas propriedades de tipografia e transformação de texto.
- “Limpar efeitos” apaga classe e estilo livres do bloco rápido.

Observação: capturas de tela “antes/depois” podem ser adicionadas no PR conforme necessário.

#### Relacionais (lookup)

Campos relacionais (FK/objetos) podem ser editados no Rules Editor com busca assíncrona (typeahead) e seleção single/multiple.

Boas práticas
- Informe `resourcePath` (ou `endpoint`) no `FieldDefinition` para habilitar o lookup via `GenericCrudService` do host.
- Defina `valueField` (chave estável, tipicamente `id`) e `displayField` (label amigável, como `name`).

## 🔎 Filtro: Atalhos para Período (DateRange)

Habilite atalhos de período no filtro dinâmico sem alterar o contrato do `praxis-filter` — apenas via metadata do campo:

```ts
// Em FilterConfig (ou na metadata de campos do filtro)
{
  name: 'period',
  label: 'Período',
  controlType: 'dateRange',
  showShortcuts: true,
  shortcuts: ['thisMonth','lastMonth','thisYear'],
  // Opcional:
  // i18nShortcuts: { thisMonth: 'Este mês', lastMonth: 'Mês passado' },
  // applyOnShortcutClick: true
}
```
- Para múltipla seleção, use `multiple: true` no `FieldDefinition` quando aplicável.
- No runtime da tabela não é necessário registrar funções JSON/data manualmente; os helpers já são embutidos.
- APIs com busca: suporte a um parâmetro livre de texto (por ex. `search`) facilita a experiência do autocomplete.

Exemplo de FieldDefinition (lookup)
```ts
import type { FieldDefinition } from '@praxisui/core';

const fields: FieldDefinition[] = [
  {
    name: 'customer',
    type: 'object',
    // Habilita o lookup via GenericCrudService
    resourcePath: 'customers',
    // Mapeia o id/label que virão do backend
    valueField: 'id',
    displayField: 'name',
    // Se quiser multiseleção em todo o app
    // multiple: true,
  },
];
```

Operadores e DSL gerada (relacional)
- `id ==` → `jsonEq(customer, '$.id', 10)`
- `id in` → `jsonIn(customer, '$.id', [10, 20])`
- `has property` → `hasJsonKey(customer, '$.meta.etag')`
- Condições por caminho (join simples): use `key ==` (JSON Path + valor)

UI (no editor)
- `id ==` (single): autocomplete + chip “label [id]” com ação “Limpar”.
- `id in` (multi): autocomplete + chips removíveis por item.
- `has property`: input do caminho (ex.: `$.meta.etag`).

Formato esperado do payload (relacional)

Para que o editor/preview e o runtime avaliem corretamente as regras relacionais, o valor do campo deve ser um objeto (ou compatível com JSON) contendo ao menos o identificador e, opcionalmente, propriedades exibidas:

```ts
// Exemplo de linha (row) com campo relacional "customer"
const row = {
  id: 123,
  customer: {
    id: 10,          // ← usado em id == / id in via jsonEq/jsonIn
    name: 'ACME',    // ← usado para exibir label no autocomplete/chip
    meta: { etag: 'v1' },
  },
};

// Regras DSL comuns
// id ==
//   jsonEq(customer, '$.id', 10)
// id in
//   jsonIn(customer, '$.id', [10, 20])
// has property
//   hasJsonKey(customer, '$.meta.etag')
// caminho/valor (join simples)
//   jsonEq(customer, '$.name', 'ACME')
```

Notas
- O editor usa `displayField` (ex.: `name`) para compor o rótulo, e `valueField` (ex.: `id`) para gerar a DSL.
- Se o backend devolver o relacionamento já "normalizado" (ex.: `customerId` e `customerName` na mesma linha), você ainda pode expressar condições com operadores padrão (`==`, `in`) no campo numérico/literal — a abordagem relacional é útil quando o payload contém o objeto.

Onde é consumido (preview e runtime)

- Preview/validação no Editor de Regras (usa `DslParser` interno):
  - Validação da expressão (parse): projects/praxis-table/src/lib/rules-editor/table-rules-editor.component.ts:536
  - Preview “Testar” (parse + evaluate): projects/praxis-table/src/lib/rules-editor/table-rules-editor.component.ts:909
- Runtime na Tabela (aplica `rowConditionalStyles`/`conditionalStyles`):
  - Parser interno do componente: projects/praxis-table/src/lib/praxis-table.ts:282

Observação
- O runtime da tabela já injeta o registry DSL padrão para validação/parse/execução.
- Para funções realmente custom, injete via input `[dslFunctionRegistry]` no componente.

Exemplo de Provider (recomendado)

```ts
import { Injectable } from '@angular/core';
import { FunctionRegistry } from '@praxisui/specification';

@Injectable({ providedIn: 'root' })
export class TableDslRegistryFactory {
  private readonly registry = FunctionRegistry.getInstance<any>('orders-table-rules');

  constructor() {
    this.registry.clear();
    this.registry.register('isStatus', (_row: any, value: any, expected: any) =>
      String(value ?? '').toLowerCase() === String(expected ?? '').toLowerCase(),
    );
  }

  get(): FunctionRegistry<any> {
    return this.registry;
  }
}

// Uso no host:
// <praxis-table [dslFunctionRegistry]="tableDslRegistryFactory.get()" ... />
```

### ⚙️ Painel de Configurações

Para abrir o painel de configurações, habilite o modo de edição na tabela:

```html
<praxis-table [enableCustomization]="true"></praxis-table>
```

Um botão de engrenagem será exibido no canto superior direito. Ao clicar nele, o `SettingsPanel` é aberto permitindo ajustar:

- **Comportamento**: paginação, ordenação, filtros e recursos avançados.
- **Colunas**: visibilidade, ordem, largura e estilo.
- **Toolbar**: ações e botões da barra de ferramentas.
- **Mensagens**: textos e rótulos exibidos na interface.

As alterações podem ser aplicadas temporariamente com **Aplicar** ou salvas de forma persistente com **Salvar & Fechar**.

## 🚀 Instalação

```bash
npm install @praxisui/core @praxisui/table
```

Peers necessários (instale no app host):
- `@angular/core` `^20.0.0`, `@angular/common` `^20.0.0`
- `@praxisui/core` `^0.0.1`
- `@praxisui/dynamic-fields` `^0.0.1` (quando usar editores/inputs dinâmicos)
- `@praxisui/dynamic-form` `^0.0.1` (quando integrar com formulários dinâmicos)
- `@praxisui/settings-panel` `^0.0.1` (para painel de configuração embutido)

## 📝 Uso Básico

### Conectando ao Backend com `resourcePath`

A forma mais poderosa de usar a `<praxis-table>` é conectá-la diretamente a um endpoint de API compatível com o ecossistema Praxis. Isso é feito através do input `resourcePath`.

Quando `resourcePath` é fornecido, a tabela se torna "inteligente":

1.  **Busca automática de dados**: A tabela gerencia a paginação, ordenação e filtros, fazendo as requisições necessárias ao backend.
2.  **Geração dinâmica de colunas**: A tabela busca os metadados (schema) do backend para gerar as colunas automaticamente, respeitando as configurações definidas no `praxis-metadata-core` (via anotação `@UISchema`).

```html
<!-- Exemplo no template do seu componente -->
<praxis-table resourcePath="human-resources/departamentos" [enableCustomization]="true"> </praxis-table>
```

Neste exemplo:

- `resourcePath="human-resources/departamentos"` instrui a tabela a se comunicar com o endpoint `/api/human-resources/departamentos`.
- A tabela fará requisições como `POST /api/human-resources/departamentos/filter` para obter os dados e `GET /schemas/filtered?path=/api/human-resources/departamentos/all&operation=get&schemaType=response` para obter o schema estrutural usado no bootstrap das colunas.
- `enableCustomization` controla explicitamente o gate de edição visual. O default canônico agora é `false`; declare `[enableCustomization]="true"` quando o host quiser expor customização e surfaces editoriais.

### Consulta Declarativa com `queryContext`

Para orquestração entre widgets em `praxis-dynamic-page`, o contrato semântico primário da tabela passa a ser `queryContext`.

```html
<praxis-table
  resourcePath="human-resources/departamentos"
  [queryContext]="{
    filters: { status: 'ACTIVE' },
    sort: ['nome,asc'],
    page: { index: 0, size: 25 }
  }"
></praxis-table>
```

Regras atuais:

- `queryContext.filters` já participa do pipeline efetivo da tabela
- `filterCriteria` continua aceito como bridge legada de compatibilidade
- em novo authoring, examples e bindings, prefira `queryContext`

### Empty State + Quick Connect (sem `resourcePath`)

Se a tabela for renderizada sem `resourcePath`, um cartão de "Empty State" é exibido convidando a conectar o componente a um recurso. Basta clicar em "Conectar a recurso" para abrir um painel rápido com um único campo:

```text
resourcePath: ex.: human-resources/departamentos
```

Ao aplicar/salvar, a `<praxis-table>` é automaticamente configurada para buscar o schema e os dados do backend. Esse fluxo evita telas em branco e simplifica o onboarding do componente em páginas novas.

### Fluxo de Comunicação do `resourcePath`

O diagrama abaixo ilustra como a propriedade `resourcePath` conecta o componente frontend ao backend no runtime atual da tabela. Antes de iniciar o fluxo remoto, a lib resolve o modo de dados com precedência **`resourcePath` explícito > `data` local > conexão persistida**. Quando o modo efetivo é `remote`, o bootstrap ocorre em três etapas principais: **Configurar**, **Carregar Schema estrutural** e **Buscar Dados**.

```mermaid
sequenceDiagram
    participant FE_Component as Componente Angular<br>(departamentos.html)
    participant Praxis_Table as @praxisui/table<br>(praxis-table.ts)
    participant Crud_Service as @praxisui/core<br>(generic-crud.service.ts)
    participant Docs_Controller as ApiDocsController<br>(/schemas/filtered)
    participant BE_Controller as Backend Controller<br>(DepartamentoController.java)
    participant Abstract_Controller as AbstractCrudController<br>(/filter)

    FE_Component->>Praxis_Table: Usa o componente com <br> <praxis-table resourcePath="human-resources/departamentos">

    activate Praxis_Table
    Praxis_Table->>Praxis_Table: ngOnInit()/ngOnChanges() resolve dataMode
    Note over Praxis_Table: remote quando há resourcePath efetivo
    Praxis_Table->>Crud_Service: 1. Chama crudService.configure("human-resources/departamentos")

    activate Crud_Service
    Crud_Service->>Crud_Service: Armazena o resourcePath
    deactivate Crud_Service

    Praxis_Table->>Praxis_Table: 2. Chama this.loadSchema()
    Praxis_Table->>Crud_Service: Chama crudService.getSchema()

    activate Crud_Service
    Crud_Service->>Crud_Service: schemaUrl() preserva o base path canônico
    Crud_Service->>Crud_Service: Deriva path="/api/human-resources/departamentos/all"<br>operation="get", schemaType="response"
    Crud_Service->>Docs_Controller: GET /schemas/filtered?path=.../all&operation=get&schemaType=response
    deactivate Crud_Service

    activate Docs_Controller
    Docs_Controller->>Docs_Controller: Resolve grupo OpenAPI a partir do path
    Docs_Controller->>Docs_Controller: Filtra payload estrutural + x-ui
    Docs_Controller-->>Crud_Service: 200/304 com schema + ETag/X-Schema-Hash
    deactivate Docs_Controller

    activate Crud_Service
    Crud_Service-->>Praxis_Table: Normaliza schema e retorna FieldDefinition[]
    deactivate Crud_Service

    Praxis_Table->>Praxis_Table: Constrói as colunas da tabela<br>a partir do schema recebido

    Praxis_Table->>Praxis_Table: 3. Chama this.fetchData()
    Praxis_Table->>Crud_Service: Chama crudService.filter(...) para buscar dados

    activate Crud_Service
    Crud_Service->>Crud_Service: getEndpointUrl('filter') constrói a URL: <br> "/api/human-resources/departamentos/filter"
    Crud_Service->>BE_Controller: Requisição HTTP POST para .../filter
    deactivate Crud_Service

    activate BE_Controller
    BE_Controller->>Abstract_Controller: Herda o método que lida com @PostMapping("/filter")

    activate Abstract_Controller
    Abstract_Controller->>Abstract_Controller: Processa a requisição e busca os dados
    Abstract_Controller-->>BE_Controller: Retorna os dados
    deactivate Abstract_Controller

    BE_Controller-->>Crud_Service: Resposta HTTP com os dados (Page<DepartamentoDTO>)
    deactivate BE_Controller

    activate Crud_Service
    Crud_Service-->>Praxis_Table: Retorna Page<T> com os dados paginados
    deactivate Crud_Service

    Praxis_Table->>Praxis_Table: Atualiza o dataSource da tabela com os dados recebidos
    deactivate Praxis_Table
```

Notas importantes:

- O host informa `resourcePath` sem o prefixo `/api`; o `GenericCrudService` resolve as URLs reais do backend a partir da configuração de API do host.
- O bootstrap de schema não depende mais de uma chamada frontend para `GET /{resource}/schemas`; o runtime consome diretamente `/schemas/filtered` com `ETag`/`X-Schema-Hash`.
- O `praxis-filter` acoplado acompanha o mesmo `resourcePath` em modo remoto e deriva seu schema via `/schemas/filtered?path=.../filter&operation=post&schemaType=request`.
- Sem `resourcePath` efetivo, a tabela não entra nesse fluxo remoto; ela cai em `local` ou `empty` conforme a precedência canônica.

## ℹ️ Dicas de UX (Tabela)

- Multi‑sort: habilite em Comportamento → Ordenação. No uso, mantenha Ctrl/Cmd pressionado ao clicar em múltiplas colunas.
- Duplo clique na linha: configure em Comportamento → Interação (habilitar e escolha a ação: editar/visualizar/personalizada).
- Virtualização: habilite em Comportamento → Virtualização para listas grandes (requer altura de linha previsível).
- Densidade e linhas de grade: ajuste em Comportamento → Aparência rápida (compacta/confortável/espaçosa; bordas horizontais/verticais).

## Rolagem Horizontal (owner do scroll)

Por padrão, a `<praxis-table>` agora assume a responsabilidade pela rolagem horizontal quando o conteúdo excede a largura do container. Isso evita cortes de conteúdo e comportamentos inconsistentes entre projetos.

### Input: `horizontalScroll`

```
@Input() horizontalScroll: 'auto' | 'wrap' | 'none' = 'auto';
```

- `auto` (padrão): a tabela cria um viewport com `overflow-x: auto` e permite que a tabela interna cresça com `width: max-content`. Quando a soma das colunas > container, aparece a barra de rolagem horizontal neste viewport.
- `wrap`: permite quebra de linha nas células (libera `white-space: normal`), reduzindo a largura necessária; ideal quando você prefere minimizar a rolagem horizontal.
- `none`: desabilita o comportamento interno; o host (página) deve fornecer o container com `overflow-x: auto` e as regras de largura necessárias.

Exemplo:

```html
<!-- Comportamento padrão (auto) -->
<praxis-table resourcePath="employees"></praxis-table>

<!-- Reduz rolagem: permite quebra de linha nas células -->
<praxis-table resourcePath="employees" horizontalScroll="wrap"></praxis-table>

<!-- Host controla o scroll horizontal -->
<div class="table-shell" style="overflow-x:auto">
  <praxis-table resourcePath="employees" horizontalScroll="none"></praxis-table>
  <!-- host pode usar width: max-content na tabela interna conforme seu design system -->
</div>
```

### Editor de Configuração (Settings Panel)

Na aba “Visão Geral & Comportamento”, há um seletor “Scroll Horizontal” com as opções `Auto`, `Wrap` e `Host` (none). As alterações podem ser aplicadas (Aplicar) ou salvas (Salvar & Fechar). O valor é persistido junto com outras preferências da tabela.

### Notas

- O caminho virtual (CDK Virtual Scroll) permanece sendo o owner da rolagem vertical; o viewport horizontal é um contêiner externo que não interfere no scroll vertical.
- Cabeçalho sticky continua funcional. Teste combinações com colunas sticky/virt para seu caso.
- Para grids alternativos (ex.: Kendo), as mesmas regras de `max-content`/`min-width: 100%` se aplicam ao elemento de tabela interno.

## Virtualização (CDK)

Quando `behavior.virtualization.enabled` estiver ativo, as linhas da tabela são renderizadas com `cdk-virtual-scroll-viewport` (cabeçalho permanece igual).

- Propriedades:
  - `itemHeight`: altura da linha (px); padrão 44.
  - `bufferSize`: itens adicionais de buffer.
  - `minContainerHeight`: altura mínima do viewport (px ou CSS, ex.: `320` ou `50vh`).
  - `strategy`: `fixed` | `dynamic` (atual uso visual não altera lógica de medição).
- Observação: sticky/pinned em colunas pode ter limitações em conjunto com virtual scroll (mantido sob feature flag; caminho não‑virtual preservado como fallback).

## Paginação (posição/estilo)

### Concept Usage

- Schema-driven UI
- Self-describing APIs
- Rules engines and specifications
- Configuration-driven development

- `behavior.pagination.position`: `top` | `bottom` | `both`.
- `behavior.pagination.style`: `default` | `compact` (aplica classe de estilo no paginator).

Nota sobre estratégia (client vs server)
- Se `behavior.pagination.strategy` não estiver definido, a tabela assume `server` automaticamente quando há `resourcePath` (dados remotos). Caso contrário, usa `client`.
- O mesmo vale para `behavior.sorting.strategy`.

## Duplo clique na linha

Ative em Comportamento → Interação.

- Output: `rowDoubleClick` com payload `{ action: string; row: any }`.
- Ação: `edit` | `view` | `custom` (quando `custom`, use `customAction` no Behavior para definir o identificador).

Exemplo de uso (template do host):

```html
<praxis-table
  [config]="tableConfig"
  [resourcePath]="'human-resources/departamentos'"
  (rowDoubleClick)="onRowDoubleClick($event)">
</praxis-table>
```

```ts
onRowDoubleClick(evt: { action: string; row: any }) {
  if (evt.action === 'edit') {
    // abrir editor
  } else if (evt.action === 'view') {
    // abrir detalhe somente leitura
  } else {
    // ação customizada
  }
}
```

## Ordenação inicial (defaultSort)

- Configure em `behavior.sorting.defaultSort`.
- Aceita objeto único (coluna/direção) ou array para multi‑sort.
- Status runtime atual: `behavior.sorting.multiSort` está **schema-only** (apenas 1 critério efetivo em runtime).

Exemplos:

```ts
sorting: {
  enabled: true,
  strategy: 'server',
  multiSort: false,
  defaultSort: { column: 'nome', direction: 'asc' },
}
```

```ts
sorting: {
  enabled: true,
  strategy: 'server',
  multiSort: true,
  defaultSort: [
    { column: 'status', direction: 'desc' },
    { column: 'nome', direction: 'asc' },
  ],
}
```

Observação: quando informado, o defaultSort é aplicado na carga inicial se não houver estado de ordenação ativo.
Observação enterprise: para evitar ambiguidade de contrato, mantenha `multiSort: false` no runtime atual. Se um array for informado em `defaultSort`, trate como payload de compatibilidade/roadmap e valide no host como single-sort efetivo.

### Roadmap / migração de multiSort

- Curto prazo: `multiSort` segue schema-only no runtime.
- Recomendação de migração: desenhar integrações com fallback para ordenação única (`column` + `direction`).
- Planejamento: quando multi-sort runtime for implementado, publicar release note com guia de ativação gradual (feature-flag + validação de payload legado).

## Coluna de ações (sticky)

Nota: por padrão a coluna de ações vem desabilitada. Habilite explicitamente em `actions.row.enabled` e defina as ações desejadas.

- Fixe a coluna de ações no início/fim configurando `actions.row.sticky`:

```ts
actions: {
  row: {
    enabled: true,
    sticky: 'end', // true | 'start' | 'end'
    width: '120px',
    actions: [ /* ... */ ],
  },
}
```

Observação: em modo virtualizado, sticky pode ter limitações dependendo do layout.

## Colunas de dados (sticky)

Fixe colunas específicas no início/fim usando `columns[].sticky`:

```ts
columns: [
  { field: 'id', header: 'ID', width: '80px', sticky: 'start' },
  { field: 'nome', header: 'Nome' },
  { field: 'status', header: 'Status', sticky: 'end' },
]
```

- Valores aceitos: `true` | `'start'` | `'end'`.
- `true` equivale a `'start'` (fixa no início).
- Dica: combine com `width` para evitar jitter de layout.

Onde configurar no Editor Visual:

- Aba “Colunas” → grupo “Posição Fixa”: escolha entre Nenhum / Início / Fim.
- Para a coluna de ações, use a aba “Barra de Ferramentas & Ações” → grupo “Coluna de Ações” → “Fixar coluna de ações”.

## 🔒 Verificação de Schema (ETag) e Notificações (somente em customização)

Quando já existe uma configuração salva (colunas presentes), a tabela não baixa o schema bruto do servidor para montar as colunas. Em vez disso, valida se há uma nova versão do schema usando ETag/If-None-Match. Notificações são exibidas somente quando o modo de customização está ativo (`enableCustomization = true`).

### Comportamento

- Primeira vez (sem colunas):
  - Baixa o schema (200), gera as colunas e persiste `config.meta` (incluindo `schemaId`, `serverHash`, `lastVerifiedAt`).
- Próximas vezes (colunas já existentes):
  - Faz uma verificação leve em `/schemas/filtered` com `If-None-Match: "<serverHash>"`.
  - 304 Not Modified: atualiza `config.meta.lastVerifiedAt` e segue usando a configuração local.
  - 200 OK (hash mudou): atualiza `config.meta.serverHash/lastVerifiedAt`, marca estado `schemaOutdated=true` e (em modo de customização) exibe aviso e CTA para reconciliar. O schema bruto não é usado/armazenado nesta etapa.

### Diagrama: Verificação de Schema via ETag

```mermaid
sequenceDiagram
  autonumber
  participant PT as PraxisTable
  participant GS as GenericCrudService (@praxisui/core)
  participant API as Backend (/schemas/filtered)

  opt Primeira vez (sem colunas)
    PT->>GS: getSchema() (gera colunas)
    GS->>API: GET /schemas/filtered (sem If-None-Match)
    API-->>GS: 200 + ETag/X-Schema-Hash
    GS-->>PT: FieldDefinition[] (normalized)
    PT->>PT: Persiste config + meta (serverHash, lastVerifiedAt)
  end

  opt Próximas vezes (colunas existentes)
    PT->>API: GET /schemas/filtered?path=... (If-None-Match: "<serverHash>")
    alt Igual (sem mudanças)
      API-->>PT: 304 Not Modified (sem body)
      PT->>PT: Atualiza lastVerifiedAt; segue usando config
    else Diferente (mudou)
      API-->>PT: 200 OK (com ETag/X-Schema-Hash)
      PT->>PT: Atualiza serverHash + lastVerifiedAt; schemaOutdated=true
      Note over PT: Em customização: mostrar banner/snackbar + badge e CTA “Reconciliar”
    end
  end
```

### Inputs/Outputs

- Inputs (efetivos apenas quando `enableCustomization = true`):
  - `notifyIfOutdated: 'inline' | 'snackbar' | 'both' | 'none' = 'both'`
  - `snoozeMs: number = 86400000`
  - `autoOpenSettingsOnOutdated: boolean = false`
  - `dslFunctionRegistry: FunctionRegistry<any> | null = null`
- Outputs:
  - `schemaStatusChange: { outdated: boolean; serverHash?: string; lastVerifiedAt?: string; trigger?: string; tableId?: string }`
    - Emitido tanto na verificação leve (304/200) quanto no bootstrap do schema (primeira carga via `loadSchema()`).
  - `metadataChange: { trigger: string; meta: any; tableId?: string }`
    - Emitido quando `config.meta` é atualizado (ex.: após bootstrap ou verificação ETag). Útil para sincronizar sidebars/bridges de metadados.

### Fallback Global (opcional)

- Quando os inputs do widget permanecem com os valores padrão da biblioteca e não há overrides locais equivalentes, a Tabela aplica como último fallback as preferências globais lidas via `GlobalConfigService.getSchemaPrefsGlobal()`.
- Cadeia de precedência (inalterada): `@Inputs (widget)` → Prefs do widget → Prefs da página → Prefs globais → Defaults.
- Não há persistência no widget a partir das globais; o fallback é aplicado somente em memória.

### Persistência por hash (ConfigStorage)

- `schemaIgnore:{tableId}:{serverHash}` → ignora avisos para este hash.
- `schemaSnooze:{tableId}:{serverHash}` → ISODate até quando não avisar (lembrar depois).
- `schemaNotified:{tableId}:{serverHash}` → evita snackbar duplicado.

### UX

- Banner inline (acima da tabela) quando `schemaOutdated=true`, com ações:
  - Reconciliar (abre Configurações)
  - Lembrar depois (snooze)
  - Ignorar (silenciar para este hash)
- Snackbar opcional (uma vez por hash) com ação “Reconciliar”.
- Badge “!” na engrenagem com tooltip contextual: “Schema do servidor mudou — Reconciliar”.

### Exemplo de uso

```html
<praxis-table
  [enableCustomization]="true"
  [notifyIfOutdated]="'both'"
  [snoozeMs]="12 * 60 * 60 * 1000"
  [autoOpenSettingsOnOutdated]="false"
  (schemaStatusChange)="onSchemaStatus($event)"
  resourcePath="employees"
  [config]="tableConfig">
</praxis-table>
```

```ts
onSchemaStatus(ev: { outdated: boolean; serverHash?: string; lastVerifiedAt?: string }) {
  if (ev.outdated) {
    console.log('Schema mudou. Hash:', ev.serverHash);
  }
}
```

### Notas

- A verificação leve não grava nem usa o schema bruto quando `config.columns` já existe.
- Em ambientes sem customização (`enableCustomization=false`), não há notificações visuais; ainda assim `schemaStatusChange` é emitido e `config.meta.lastVerifiedAt` atualizado.

## Filtro Avançado (PraxisFilter)

### Boas práticas para estabilidade

- Evite literais em bindings para o carregador dinâmico:
  - Não use `[fields]="[pinnedFieldMeta]"`; prefira um array estável como `pinnedFieldMetaArray` atualizado apenas quando o metadata mudar.
- 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.
- Evite recriar arrays/objetos desnecessariamente em `ngOnChanges`/`ngAfterViewInit`. Prefira atualizar valores (`setValue`, `patchValue`) e manter referências estáveis.
- Observers (Resize/Mutation): atualize `overlayOrigin` e largura apenas quando houver mudanças reais; isso evita loops de CD.

### Diagnóstico e Logs

- O `DynamicFieldLoader` possui logs de diagnóstico que podem ser habilitados em tempo de execução definindo `window.__PRAXIS_DEBUG_DFL__ = true` no console do navegador.
- Quando desabilitado (default), o carregador reduz a verbosidade de logs.

### Comportamento do carregador (v20+)

- Guardas internas evitam re-renderizações desnecessárias:
  - Se o snapshot do conteúdo dos campos (nome + controlType) for idêntico e o `FormGroup` não tiver mudado, a renderização é ignorada.
  - Se apenas o `FormGroup` mudou (mesmo snapshot de campos), os controles são reatribuídos aos componentes existentes (rebind-only), preservando o estado visual e evitando recriações.

### Testes

- Há testes cobrindo:
  - Reatribuição de `FormControl` em troca de `FormGroup` (select/multiselect/autocomplete).
  - Ignorar re-render quando apenas a referência do array muda mas o conteúdo é igual.
  - Recriar componentes quando `controlType` muda.

### Fluxo de Schema e Metadados

- O `PraxisFilter` busca schema do DTO de filtro via `/schemas/filtered` com ETag/If-None-Match
  e emite `metaChanged` com `{ schemaId, serverHash, context }`.
- O schema é normalizado e `advancedConfig.metadata` é preenchido para auditoria/telemetria.
- Detalhes: `projects/praxis-core/docs/schema-flow.md` (canônico) e `docs/schemas/fluxo-schema.md` (resumo operacional) para cliente/caching, 200/304 e reconciliadores Form/Filter.

### Resolução da chave primária (idField)

- O backend anota o schema com `x-ui.resource.idField` (e `idFieldValid`) via `/schemas/filtered`.
- A tabela adota o campo identificador automaticamente com a seguinte precedência:
  - `@Input() idField` → `crudContext.idField` → `config.meta.idField` (persistido) → `GenericCrudService.getResourceIdField()` (derivado do schema) → `'id'`.
  - Se `config.meta.idField` divergir do servidor, o componente alerta o usuário e mantém o valor do TableConfig até reconciliação.
- A resolução ocorre no `loadSchema()` e também é considerada em tempo de execução para evitar corridas.
- Para recursos cuja PK não é `id`, defina `getIdFieldName()` no controller backend correspondente.

### Diagramas

Fluxo (Grid): adoção de schema e idField

```mermaid
sequenceDiagram
  autonumber
  participant PT as PraxisTable
  participant GS as GenericCrudService
  participant Docs as ApiDocsController (/schemas/filtered)

  PT->>GS: configure(resourcePath)
  PT->>GS: getSchema()
  GS->>GS: deriva path=/api/.../all + schemaType=response
  GS->>Docs: GET /schemas/filtered?path=.../all&operation=get&schemaType=response
  Docs-->>GS: 200/304 schema + x-ui.resource.idField
  GS->>GS: cache + lastResourceMeta.idField
  GS-->>PT: FieldDefinition[] (normalizado)
  Note over PT: idField = input || context || GS.getResourceIdField() || 'id'
  PT->>PT: construir colunas e renderizar
```

Fluxo (Delete): resolução do identificador

```mermaid
sequenceDiagram
  autonumber
  participant PT as PraxisTable
  participant GS as GenericCrudService
  participant API as Backend

  PT->>PT: key = getIdField() (precedência)
  PT->>PT: id = getNestedPropertyValue(row, key)
  PT->>GS: delete(id)
  GS->>API: DELETE {resource}/{id}
  API-->>GS: 204 No Content
  GS-->>PT: sucesso
  PT->>PT: fetchData() em modo remoto
```

### Troubleshooting rápido (idField)

- A ação delete falhou por ID ausente: verifique se o schema contém `x-ui.resource.idField` e se a coluna correspondente existe no dataset.
- O ID está em outra propriedade: defina `@Input() idField` ou `crudContext.idField` temporariamente; ajuste o backend com `getIdFieldName()` para persistir o comportamento.
- Cache 304 sem idField aplicado: confirme que o serviço recebeu o body pelo menos uma vez (200) e que `GenericCrudService.getResourceIdField()` retorna o valor esperado.

### Uso com Dados Locais (Client-Side)

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`.
Importante: o modo local está protegido por feature flag e exige `behavior.localDataMode.enabled = true` (default é `false`).
Quando a flag está ativa, as operações de paginação, ordenação e filtro são executadas no lado do cliente.

```typescript
import { PraxisTable } from "@praxisui/table";
import { TableConfig } from "@praxisui/core";

@Component({
  selector: "app-example",
  standalone: true,
  imports: [PraxisTable],
  template: ` <praxis-table [config]="tableConfig" [data]="employees"> </praxis-table> `,
})
export class ExampleComponent {
  // Configuração de colunas e comportamento é obrigatória neste modo
  tableConfig: TableConfig = {
    columns: [
      { field: "id", header: "ID", type: "number" },
      { field: "name", header: "Nome", type: "string" },
      { field: "email", header: "Email", type: "string" },
    ],
	    behavior: {
	      pagination: { enabled: true, pageSize: 10 },
	      sorting: { enabled: true },
	      filtering: { enabled: true },
	      localDataMode: { enabled: true }, // obrigatório para fluxo local oficial
	    },
	  };

  employees = [
    { id: 1, name: "João Silva", email: "joao@empresa.com" },
    { id: 2, name: "Maria Santos", email: "maria@empresa.com" },
    // ... mais dados
  ];
}
```

## ⚙️ Fluxo de Paginação e Filtros (Server-Side)

Quando a `<praxis-table>` opera com `resourcePath`, o runtime resolve o modo remoto e passa a delegar paginação, ordenação e filtros ao backend. Isso reduz tráfego, preserva a semântica do DTO de filtro e mantém o banco como fonte de verdade para `WHERE`, `ORDER BY`, `LIMIT` e `OFFSET`.

Importante: se você não configurar explicitamente as estratégias de paginação/ordenação no `TableConfig`, a tabela resolve automaticamente como `server` quando há `resourcePath`. Se preferir operar no cliente, defina `behavior.pagination.strategy = 'client'` e/ou `behavior.sorting.strategy = 'client'` conscientemente.

### Versão Pedagógica de Alto Nível

Esta é a leitura adequada para documentação pública e landing pages.

```mermaid
sequenceDiagram
    participant Usuario as Usuário
    participant Tabela as praxis-table
    participant Frontend as GenericCrudService
    participant Backend as API Praxis
    participant Banco as Banco de Dados

    Usuario->>Tabela: muda página, ordena ou aplica filtro
    Tabela->>Tabela: consolida filterCriteria + paginação + sort
    Tabela->>Frontend: filter(criteria, pageable)
    Frontend->>Backend: POST /api/.../filter?page&size&sort
    Backend->>Banco: executa consulta filtrada e paginada
    Banco-->>Backend: retorna página de resultados
    Backend-->>Frontend: responde com página de DTOs
    Frontend-->>Tabela: entrega Page<T>
    Tabela->>Tabela: atualiza linhas, paginação e estado visual
```

Leitura pedagógica:

1. **A tabela captura a intenção do usuário**: página, ordenação e filtros viram estado de consulta.
2. **O frontend envia o DTO de filtro para o endpoint canônico**: corpo JSON para filtro, query params para paginação e sort.
3. **O backend aplica o filtro no conjunto remoto**: não é a UI que filtra localmente em modo remoto.
4. **O resultado volta já paginado**: a tabela apenas renderiza a página retornada pela API.

### Versão Detalhada e Fiel ao Runtime Atual

Esta versão documenta o fluxo real do `praxis-table`, do `praxis-filter`, do `GenericCrudService` e do `praxis-metadata-starter`.

```mermaid
sequenceDiagram
    participant Host as Host / bindings
    participant Tabela as PraxisTable
    participant Filtro as PraxisFilter
    participant Crud as GenericCrudService
    participant Controller as AbstractCrudController
    participant Service as BaseCrudService
    participant Builder as GenericSpecificationsBuilder
    participant Repo as JpaSpecificationExecutor

    Host->>Tabela: fornece resourcePath
    Tabela->>Crud: configure(resourcePath)
    alt sem colunas prontas
        Tabela->>Crud: getSchema()
        Crud-->>Tabela: schema normalizado
    else com colunas prontas
        Tabela->>Tabela: verifyServerSchemaVersion()
    end

    alt advancedFilters habilitado
        Tabela->>Filtro: monta praxis-filter
        Filtro->>Crud: getFilteredSchema(...) ou getSchema()
        Crud-->>Filtro: metadata de filtro
    end

    alt paginação
        Tabela->>Tabela: onPageChange(pageIndex, pageSize)
    else ordenação
        Tabela->>Tabela: onSortChange(active, direction)
        Tabela->>Tabela: reseta pageIndex = 0
    else filtro avançado
        Filtro-->>Tabela: submit/change/clear
        Tabela->>Tabela: onAdvancedFilterSubmit(criteria)
    end

    Tabela->>Tabela: fetchData()
    Tabela->>Tabela: monta PageableRequest com pageIndex, pageSize e sortField efetivo
    Tabela->>Crud: filter(filterCriteria, pageable)

    Crud->>Crud: normalizeRangeCriteria(...)
    Crud->>Controller: POST /filter?page&size&sort
    Note over Crud,Controller: body = filterCriteria JSON

    Controller->>Controller: lê page,size,sort de query params
    Controller->>Controller: PageableBuilder.from(page,size,sort,...)
    Controller->>Service: filterMappedWithIncludeIds(filterDTO, pageable, includeIds, toDto)

    Service->>Builder: buildSpecification(filterDTO, pageable)
    Builder->>Builder: inspeciona campos @Filterable do DTO
    Builder-->>Service: Specification pronta
    Service->>Repo: findAll(specification, pageable)
    Repo-->>Service: Page<Entity>
    Service-->>Controller: Page<DTO>

    Controller->>Controller: mapeia para EntityModel<DTO> e monta RestApiResponse.success(...)
    Controller-->>Crud: HTTP 200
    Crud->>Crud: unwrapPageResponse(...)
    Crud-->>Tabela: Page<T>
    Tabela->>Tabela: atualiza dataSubject, totalElements e paginator
```

### Pontos-Chave do Fluxo Real

1. **`resourcePath` não dispara só fetch**: em modo remoto, a tabela primeiro configura o `GenericCrudService` e pode carregar schema antes da primeira busca.
2. **O filtro avançado vive em `praxis-filter`**: os eventos reais são `submit`, `change` e `clear`, que a tabela recebe como `onAdvancedFilterSubmit`, `onAdvancedFilterChange` e `onAdvancedFilterClear`.
3. **O controller não recebe `Pageable` pronto do Spring nesse endpoint**: ele reconstrói o `Pageable` a partir de `page`, `size` e `sort`.
4. **O backend do starter responde com `RestApiResponse<Page<...>>`**: o `GenericCrudService` faz `unwrapPageResponse(...)` para entregar `Page<T>` ao runtime da tabela.
5. **A coluna ordenada pode mapear para `sortField`**: o `active` da UI não é necessariamente o campo final enviado ao backend.

## 🎨 Edição Visual da Tabela: O Poder do Low-Code

A `<praxis-table>` vem com um poderoso editor de configuração visual que permite personalizar quase todos os aspectos da sua tabela em tempo real, sem escrever uma única linha de código. O runtime expõe esse editor somente quando o host ativa `[enableCustomization]="true"`.

A seguir, veja os principais recursos que você pode configurar visualmente:

### 1. Gerenciamento de Colunas

Controle total sobre as colunas da sua tabela. Dentro do editor, você pode:

- **Reordenar com Arrastar e Soltar:** Simplesmente clique e arraste uma coluna para a posição desejada.
- **Alterar Visibilidade:** Use a caixa de seleção ao lado de cada coluna para mostrá-la ou ocultá-la instantaneamente.
- **Editar Títulos e Largura:** Clique em uma coluna para abrir suas propriedades e altere o texto do cabeçalho, defina uma largura fixa (ex: `150px`) ou deixe-a automática.

### 2. Transformação de Dados Sem Código

Converta dados brutos em informações claras e formatadas para o usuário.

- **Formatação Automática:** Selecione uma coluna e escolha seu "Tipo de Dado". Se escolher `Moeda`, os valores serão formatados como `R$ 1.234,56`. Se escolher `Data`, você pode selecionar formatos como `dd/MM/yyyy` ou `25 de janeiro de 2025`.
- **Mapeamento de Valores:** Transforme códigos e valores brutos em texto legível. Na seção "Mapeamento de Valores", você pode definir visualmente que o valor `true` deve ser exibido como "Ativo" e `false` como "Inativo", ou que o código `1` significa "Pendente" e `2` significa "Aprovado".

### 3. Colunas Calculadas com Fórmulas Visuais

Crie novas colunas dinamicamente a partir de dados existentes, sem precisar programar.

- **Concatenar Texto:** Crie uma "Coluna Calculada", escolha a fórmula "Concatenar" e selecione os campos `nome` e `sobrenome` para criar uma coluna "Nome Completo".
- **Realizar Operações Matemáticas:** Use a fórmula "Operação Matemática" para criar uma coluna que calcula `preço * quantidade`.
- **Criar Valores Condicionais (IF/ELSE):** Com a fórmula "Condicional", você pode criar uma coluna "Nível de Risco" que exibe "Alto" se o `valor` for maior que 1000, e "Baixo" caso contrário.

### 4. Formatação Condicional (Regras de Estilo)

Destaque informações importantes aplicando estilos que mudam com base nos dados da linha.

- **Crie Regras Visuais:** Na seção de "Formatação Condicional", crie uma nova regra.
- **Defina a Condição:** Estabeleça a condição, por exemplo: "Quando a coluna `status` tiver o valor igual a 'Urgente'".
- **Aplique o Estilo:** Use seletores de cor para definir que, quando a condição for verdadeira, a cor de fundo da célula ou da linha inteira deve se tornar vermelha e o texto, branco.

### 5. Comportamentos da Tabela

Habilite e configure as funcionalidades centrais da tabela com um clique. Na aba "Comportamento", você pode:

- **Ativar/Desativar Paginação:** Com um único interruptor, ative a paginação para tabelas com muitos dados e defina quantos itens exibir por página.
- **Controlar Ordenação e Filtros:** Habilite a capacidade dos usuários de ordenar colunas e filtrar os dados com simples caixas de seleção.
- **Gerenciar Seleção de Linhas:** Permita que os usuários selecionem uma ou várias linhas para realizar ações em lote.

### Editores Especializados

#### Behavior Editor

```typescript
import { BehaviorConfigEditorComponent } from '@praxisui/table';

// Usar como componente standalone para edição específica
<behavior-config-editor
  [config]="tableConfig"
  (configChange)="onBehaviorChange($event)">
</behavior-config-editor>
```

#### Columns Editor

```typescript
import { ColumnsConfigEditorComponent } from '@praxisui/table';

<columns-config-editor
  [config]="tableConfig"
  (configChange)="onColumnsChange($event)"
  (columnChange)="onColumnChange($event)">
</columns-config-editor>
```

## 🔧 Configuração Avançada

### Performance com Virtualização

```typescript
const highVolumeConfig: TableConfig = {
  columns: [...],
  performance: {
    virtualization: {
      enabled: true,
      itemHeight: 48,
      bufferSize: 10,
      minContainerHeight: 400,
      strategy: 'fixed'
    },
    lazyLoading: {
      threshold: 100,
      images: true,
      components: true
    }
  }
};
```

### Acessibilidade Personalizada

```typescript
const accessibleConfig: TableConfig = {
  columns: [...],
  accessibility: {
    enabled: true,
    announcements: {
      dataChanges: true,
      userActions: true,
      loadingStates: true,
      liveRegion: 'polite'
    },
    keyboard: {
      shortcuts: true,
      tabNavigation: true,
      arrowNavigation: true,
      skipLinks: true,
      focusTrap: false
    },
    highContrast: false,
    reduceMotion: false
  }
};
```

### Aparência Customizada

```typescript
const styledConfig: TableConfig = {
  columns: [...],
  appearance: {
    density: 'compact',
    borders: {
      showRowBorders: true,
      showColumnBorders: false,
      showOuterBorder: true,
      style: 'solid',
      width: 1,
      color: '#e0e0e0'
    },
    elevation: {
      level: 2,
      shadowColor: 'rgba(0, 0, 0, 0.1)'
    },
    spacing: {
      cellPadding: '8px 16px',
      headerPadding: '12px 16px'
    },
    typography: {
      fontWeight: '400',
      fontSize: '14px',
      headerFontWeight: '500',
      headerFontSize: '14px'
    }
  }
};
```

## 🎯 Event Handling

### Eventos da Tabela

```typescript
<praxis-table
  [config]="tableConfig"
  [data]="data"
  (rowClick)="onRowClick($event)"
  (rowSelect)="onRowSelect($event)"
  (bulkAction)="onBulkAction($event)"
  (configChange)="onConfigChange($event)"
  (dataFilter)="onDataFilter($event)"
  (dataSort)="onDataSort($event)"
  (pageChange)="onPageChange($event)">
</praxis-table>
```

### Implementação dos Handlers

```typescript
export class MyComponent {
  onRowClick(event: { row: any; index: number }) {
    console.log("Row clicked:", event.row);
  }

  onRowSelect(event: { selectedRows: any[]; isSelectAll: boolean }) {
    console.log("Selection changed:", event.selectedRows);
  }

  onBulkAction(event: { action: string; selectedRows: any[] }) {
    switch (event.action) {
      case "deleteSelected":
        this.deleteMultiple(event.selectedRows);
        break;
      // Handle other bulk actions
    }
  }

  onConfigChange(newConfig: TableConfig) {
    this.tableConfig = newConfig;
  }
}
```

## 🛠️ Utilitários e Helpers

### Helper Functions

```typescript
import { createDefaultTableConfig, isValidTableConfig, cloneTableConfig, mergeTableConfigs } from "@praxisui/core";

// Criar configuração padrão
const defaultConfig = createDefaultTableConfig();

// Validar configuração
if (isValidTableConfig(myConfig)) {
  // Configuração válida
}

// Clonar configuração
const clonedConfig = cloneTableConfig(originalConfig);

// Merge configurações
const mergedConfig = mergeTableConfigs(baseConfig, overrides);
```

### Service Integration

```typescript
import { TableConfigService } from '@praxisui/core';

@Component({...})
export class MyComponent {
  constructor(private configService: TableConfigService) {}

  ngOnInit() {
    // Usar serviço para gerenciar configuração
    this.configService.setConfig(this.tableConfig);

    // Verificar recursos disponíveis
    // No runtime atual, multiSort permanece schema-only.
    const hasMultiSort = false;
    const hasBulkActions = this.configService.isFeatureEnabled('bulkActions');
  }
}
```

## 🧪 Testes

### Unit Tests

```typescript
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { PraxisTable } from "@praxisui/table";
import { TableConfig } from "@praxisui/core";

describe("PraxisTable", () => {
  let component: PraxisTable;
  let fixture: ComponentFixture<PraxisTable>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [PraxisTable],
    });

    fixture = TestBed.createComponent(PraxisTable);
    component = fixture.componentInstance;
  });

  it("should create", () => {
    expect(component).toBeTruthy();
  });

  it("should handle configuration changes", () => {
    const config: TableConfig = {
      columns: [{ field: "test", header: "Test" }],
    };

    component.config = config;
    fixture.detectChanges();

    expect(component.config).toEqual(config);
  });
});
```

## 📋 Migration Guide

### Migração da Arquitetura V1/V2

Se você estava usando as versões anteriores com dual architecture, aqui estão as principais mudanças:

#### Imports Atualizados

```typescript
// Antes
import { TableConfigV1, TableConfigV2, TableConfigUnified } from "@praxisui/core";

// Depois
import { TableConfig } from "@praxisui/core";
```

#### Serviços Removidos

```typescript
// Antes
import { TableConfigAdapterService } from "@praxisui/table";

// Depois - Não mais necessário
// Uso direto da configuração
```

#### Tipos Simplificados

```typescript
// Antes
config: TableConfigUnified;

// Depois
config: TableConfig;
```

### Breaking Changes

1. **TableConfigAdapterService**: Removido - uso direto da configuração
2. **TableConfigMigrationService**: Simplificado - funcionalidade integrada
3. **TableConfigUnified**: Renomeado para `TableConfig`

## 🔍 Troubleshooting

### Problemas Comuns

#### Configuração não está funcionando

```typescript
// Verificar se a configuração é válida
import { isValidTableConfig } from "@praxisui/core";

if (!isValidTableConfig(myConfig)) {
  console.error("Configuração inválida:", myConfig);
}
```

#### Performance Issues

```typescript
// Habilitar virtualização para grandes datasets
const config: TableConfig = {
  // ...
  performance: {
    virtualization: {
      enabled: true,
      itemHeight: 48,
      bufferSize: 20,
    },
  },
};
```

#### Acessibilidade

```typescript
// Garantir que acessibilidade está habilitada
const config: TableConfig = {
  // ...
  accessibility: {
    enabled: true,
    announcements: { dataChanges: true, userActions: true, loadingStates: true, liveRegion: "polite" },
  },
};
```

## 📚 API Reference

### Interfaces Principais

#### TableConfig

Interface principal para configuração da tabela.

#### ColumnDefinition

Define configuração individual de colunas.

#### TableBehaviorConfig

Configurações de comportamento (paginação, ordenação, etc.).

#### TableAppearanceConfig

Configurações de aparência visual.

Para documentação completa da API, consulte `projects/praxis-core/README.md`.

## 🤝 Contribuição

### Como Contribuir

1. Fork o projeto
2. Crie branch para feature (`git checkout -b feature/nova-funcionalidade`)
3. Commit mudanças (`git commit -m 'Add: nova funcionalidade'`)
4. Push para branch (`git push origin feature/nova-funcionalidade`)
5. Abra Pull Request

### Guidelines

- Seguir Angular Style Guide
- Adicionar testes para novas features
- Manter documentação atualizada
- Usar TypeScript strict mode

## 🔍 Exemplo de Integração com PraxisFilter

O `PraxisFilter` pode ser acoplado à barra de ferramentas da tabela. O exemplo abaixo mostra a busca de pessoas por CPF e status.

```html
<praxis-filter [resourcePath]="'pessoas'" [formId]="'pessoas-filter'" [persistenceKey]="'pessoas-filter-v1'" [alwaysVisibleFields]="['status']" (requestSearch)="onFilter($event)"></praxis-filter> <praxis-table [data]="tableData"></praxis-table>
```

```ts
onFilter(dto: any) {
  this.crud.configure('pessoas', ApiEndpoint.HumanResources);
  this.crud.filter(dto, this.pageable).subscribe(page => {
    this.tableData = page.content;
  });
}
```

### ⚙️ Configuração do Filtro

O `PraxisFilter` pode ser configurado por inputs/JSON de configuração. O atalho
visual por ícone de engrenagem não é exposto por padrão na tabela/demo.
Quando necessário, também é possível abrir o painel programaticamente via
`openSettings()`. Nesse painel é possível ajustar:

- **alwaysVisibleFields** – campos que permanecem sempre visíveis
- **alwaysVisibleFieldMetadataOverrides** – patch de metadata por campo sempre visível (controle, clearButton, inlineAutoSize etc.)
- **showAdvanced** – define se a seção avançada inicia aberta

```ts
@ViewChild(PraxisFilter) filter!: PraxisFilter;

abrirConfiguracoes() {
  this.filter.openSettings();
}
```

Ao aplicar ou salvar, as escolhas são validadas contra os metadados
do **DTO de filtro** (schema de `POST /{resource}/filter`). Campos ausentes
no DTO não são aplicados em `alwaysVisibleFields`.
O componente exibe uma barra de progresso durante o processo de persistência e
mensagens de sucesso ou erro via _snack bar_, garantindo uma experiência
consistente.

### 🔖 Mini‑guia: Atalhos do Filtro (tags)

Atalhos são “chips” que guardam um conjunto de filtros (DTO) para reuso rápido.

- Criar/Salvar
  - Modal (padrão): o host do diálogo exibe o botão quando `allowSaveTags === true`.
  - Gaveta (Drawer): o Adapter recebe `allowSaveTags?`, `i18nSaveAsShortcut?` e `onSaveShortcut?` (opcionais). O host deve exibir o botão e chamar `onSaveShortcut(clean({ ...initialDto, ...lastValue }))`.
  - O DTO é limpo antes de persistir (remove `'' | null | undefined`).
- Aplicar
  - Clique/Enter no chip aplica imediatamente `tag.patch` (substitui o estado atual), emite `submit` e persiste.
  - O chip ativo é destacado (cor/borda + ícone de “check”) quando o DTO atual é igual ao patch do atalho.
- Editar/Renomear
  - Apenas atalhos do usuário exibem ícone de lápis. Predefinidos mostram ícone de “cadeado” (somente leitura).
- Excluir (com Undo)
  - Exclusão remove imediatamente o atalho do usuário e exibe snackbar “Atalho removido” com ação “Desfazer” quando `confirmTagDelete === true`.
  - Ao clicar em “Desfazer”, o atalho é restaurado na mesma posição e os eventos são reemitidos.
- i18n
  - Chaves úteis: `saveAsShortcut`, `renameShortcut`, `removeShortcut`, `shortcutSaved`, `shortcutRemoved`, `undo`, `readonlyShortcut`.

Veja também: `docs/host-crud-integration.md` (guia operacional host) e `projects/praxis-crud/docs/host-crud-runtime-and-openmode.md` (contrato técnico canônico) para detalhes do contrato do Adapter.

### Novos Inputs/Outputs (PraxisFilter)

- Inputs (efetivos apenas quando `enableCustomization = true`):
  - `enableCustomization: boolean` — habilita o gate de customização para notificações de schema.
  - `notifyIfOutdated: 'inline' | 'snackbar' | 'both' | 'none' = 'both'` — seleciona como o runtime publica avisos de drift quando a política operacional estiver habilitada.
  - `snoozeMs: number = 86400000` — tempo de soneca para avisos (ms).
  - `autoOpenSettingsOnOutdated: boolean = false` — abre Configurações ao detectar schema desatualizado.
  - `dslFunctionRegistry: FunctionRegistry<any> | null = null` — adiciona funções DSL custom sem substituir o registry nativo.
- Output:
  - `schemaStatusChange: { outdated: boolean; serverHash?: string; lastVerifiedAt?: string; tableId?: string }` — emitido após verificação leve (304/200).

#### Fallback Global (opcional)

- Quando os inputs do componente permanecem com os valores padrão e não há overrides locais, o Filter utiliza como último fallback as preferências globais via `GlobalConfigService.getSchemaPrefsGlobal()`.
- Cadeia de precedência (inalterada): `@Inputs (widget)` → Prefs do widget → Prefs da página → Prefs globais → Defaults.
- O fallback global não é persistido no componente; serve apenas para defaults em memória.

Notas rápidas (Flow ETag no Filter):
- Verificação leve sempre no init (sem baixar o corpo): usa ETag/If-None-Match em `/schemas/filtered` (path=`.../filter`, operation=`post`, schemaType=`request`, `includeInternalSchemas=true`).
- 304: atualiza `lastVerifiedAt`; emite `schemaStatusChange(outdated=false)`.
- 200: atualiza `serverHash/lastVerifiedAt`; marca `outdated=true` apenas quando em customização; não aplica o schema automaticamente.
- O corpo do schema é baixado apenas quando necessário para renderização (ex.: `alwaysVisibleFields` ou ao abrir o painel Avançado).

## Formatação de Colunas (format)

Cada coluna pode declarar `type` e uma string de formatação `format` consumida pelo `DataFormattingService`. Os tipos suportados são: `string`, `number`, `currency`, `percentage`, `date`, `datetime`, `time`, `boolean`, `custom`.

Regra geral:
- `format` explícito continua com precedência máxima.
- Para `number`, `currency`, `percentage`, `date`, `datetime` e `time`, a tabela também consegue derivar formatação básica a partir de `type + config.localization`, mesmo quando `format` estiver ausente.
- `string`, `boolean` e `custom` continuam conservadores; nesses casos, use `format` explícito quando precisar de transformação.

Nota de plataforma:
- o contrato público da tabela continua sendo `column.type + config.localization + format`.
- a tabela converge internamente para a semântica canônica horizontal do core, mas não expõe `valuePresentation` como bloco público de configuração de coluna.

Tipos e padrões de `format`:
- number (DecimalPipe)
  - Padrões: `minInt.minFrac-maxFrac`
  - Exemplos: `1.0-0`, `1.2-2`, `1.0-3|nosep` (remove separador de milhar)
- currency (CurrencyPipe)
  - Sintaxe: `CURRENCY|DISPLAY|DECIMALS[|nosep]`
  - Exemplos: `BRL|symbol|2`, `USD|code|0`, `EUR|symbol|2|nosep`
- percentage (PercentPipe/DecimalPipe)
  - Sem multiplicador: `1.0-0` (PercentPipe)
  - Multiplicar por 100: `1.1-1|x100` (DecimalPipe×100 + `%`)
- date (DatePipe)
  - Tokens Angular: `shortDate`, `mediumDate`, `longDate`, `fullDate`, `short`, `shortTime`
  - Padrões customizados: `dd/MM/yyyy`, `yyyy-MM-dd`, `dd/MM/yyyy HH:mm`
- string (transformações + truncamento)
  - Transform: `uppercase` | `lowercase` | `titlecase` | `capitalize` | `none`
  - Transform + truncar: `<transform>|truncate|<max>|<suffix>`
  - Exemplos: `uppercase|truncate|50|...`, `titlecase`
- boolean (pré-definidos ou custom)
  - Presets: `true-false` | `yes-no` | `active-inactive` | `on-off` | `enabled-disabled`
  - Custom: `custom|Verdadeiro|Falso`

Exemplos de ColumnDefinition:
```ts
{ field: 'salario', type: 'currency', format: 'BRL|symbol|2', header: 'Salário' }
{ field: 'desconto', type: 'percentage', format: '1.1-1|x100', header: 'Desconto' }
{ field: 'criadoEm', type: 'date', format: 'dd/MM/yyyy', header: 'Criado em' }
{ field: 'nome', type: 'string', format: 'titlecase|truncate|32|…', header: 'Nome' }
{ field: 'ativo', type: 'boolean', format: 'yes-no', header: 'Ativo' }
```

Exemplos com defaults implícitos por `type + localization`:
```ts
{
  localization: {
    locale: 'pt-BR',
    currency: { code: 'BRL', symbol: 'R$', position: 'before', spacing: true, precision: 2 },
    dateTime: { dateFormat: 'dd/MM/yyyy', timeFormat: 'HH:mm', dateTimeFormat: 'dd/MM/yyyy HH:mm', firstDayOfWeek: 1 },
    number: { decimalSeparator: ',', thousandsSeparator: '.', defaultPrecision: 2, negativeSign: '-', negativeSignPosition: 'before' },
  },
  columns: [
    { field: 'salario', type: 'currency', header: 'Salário' },
    { field: 'criadoEm', type: 'date', header: 'Criado em' },
    { field: 'taxa', type: 'percentage', header: 'Taxa' },
  ],
}
```

Observações:
- `|nosep` remove separadores de milhar da saída formatada.
- Para datas inválidas ou valores não numéricos, o serviço retorna o valor original (com aviso no console).
- Colunas com renderizador `custom` não passam por formatação automática.
- Precedência de locale para formatação: `config.localization.locale` -> `LOCALE_ID` do host.
- Quando `config.localization.currency` estiver presente, a tabela usa esse bloco como override para código, símbolo, posição, espaçamento e precisão da moeda.
- Quando `config.localization.number` estiver presente, a tabela respeita `decimalSeparator`, `thousandsSeparator`, `defaultPrecision`, `negativeSign` e `negativeSignPosition` em number/currency/percentage.

Exemplos práticos no workspace (rotas):
- Regras Visuais (Simples): `/table-rules-simple`
- Regras Visuais (Complexas): `/table-rules-complex`

## Auto-render de Avatar (strict mode)

No bootstrap de schema (quando `config.columns` esta vazio), a tabela detecta automaticamente colunas de avatar e aplica `renderer.type = 'avatar'`.

Deteccao:
- somente quando `FieldDefinition.controlType === 'avatar'`

Renderer aplicado:
- `renderer: { type: 'avatar', avatar: { srcField, initialsExpr, shape: 'circle', size: 28 } }`
- alinhamento central e largura compacta (~56px)
- `initialsExpr` com fallback para `nomeCompleto|nome|fullName|name|title`

Avatar explicito:
- `renderer: { type: 'avatar', avatar: { srcField, initialsExpr, initialsField, shape: 'circle', size: 28 } }`
- `initialsField` deriva automaticamente as iniciais de um campo textual quando `srcField` nao resolve imagem e `initialsExpr` nao foi informado
- `initialsExpr` deve retornar as iniciais prontas; ele nao transforma automaticamente um campo bruto em iniciais
- quando quiser derivar iniciais a partir de um campo textual, prefira `initialsField`
- exemplo recomendado: `avatar: { srcField: 'photoUrl', initialsField: 'name' }`
- exemplo de `initialsExpr` explicito: `avatar: { initialsExpr: \"= (row.name || '').trim().split(/\\s+/).slice(0,2).map(p => p[0] || '').join('').toUpperCase()\" }`

Comportamento importante:
- a autodeteccao ocorre apenas no bootstrap (nao sobrescreve configuracoes de colunas persistidas)
- se quiser desabilitar, defina renderer explicito na coluna (`image` ou `text`)
- para cenarios sem `x-ui`, exponha renderer no config ou ajuste backend para enviar `x-ui.controlType`

Ajustes comuns:
- `renderer.avatar.shape`: `'square' | 'rounded' | 'circle'`
- `renderer.avatar.size`: numero em px
- `renderer.avatar.alt` / `altField`
- use `conditionalRenderers` para variar renderer por linha

A11y, seguranca e performance:
- `alt`/`altField` sao respeitados
- fallback para iniciais quando imagem falha/nao existe
- URL da imagem sanitizada (bloqueia esquemas perigosos)
- imagem com `loading=\"lazy\"`

## 📊 Roadmap

### Próximas Versões

- ✅ Arquitetura unificada (v2.0.0)
- 🔄 Enhanced mobile support (v2.1.0)
- 📋 Advanced export options (v2.2.0)
- 🎨 Theme customization (v2.3.0)

## 📄 Licença

Apache-2.0 — consulte `LICENSE` na raiz do workspace para detalhes.

---

**Parte do Praxis UI Workspace**
**Versão**: 2.0.0 (Unified Architecture)
**Compatibilidade**: Angular 18+
