# Tipos de campo

Os tipos de campo suportados e sua configuração.

## Tipos de campo

| Tipo | Descrição |
|---|---|
| `text` | Texto de uma linha |
| `textarea` | Texto simples de várias linhas |
| `html` | Texto formatado (editor bubble e barra do campo, veja Botões do editor) |
| `slug` | Campo de slug seguro para URL, calculado a partir dos campos de origem configurados |
| `boolean` | Chave verdadeiro/falso |
| `date` | Seletor de data |
| `email` | Endereço de e-mail |
| `url` | URL |
| `password` | Campo de senha criptografado |
| `image` | Envio de uma imagem |
| `file` | Envio de um arquivo |
| `upload` | Envio genérico |
| `gallery` | Envio de várias imagens |
| `select` | Lista de opções, fixas ou de um recurso |
| `number` | Entrada numérica, com `min` e `max` opcionais |
| `color` | Seletor de cor |
| `location-picker` | Mapa-múndi interativo que guarda a latitude no campo e a longitude no campo associado configurado em `longitude_field` |

Os nomes dos tipos de campo precisam corresponder exatamente.

## Configuração de campos

Opções comuns por campo:

| Chave | Tipo | Descrição |
|---|---|---|
| `name` | string | Rótulo exibido no admin |
| `required` | boolean | Validação; defina em todos os campos de título e nome |
| `admin_col` | boolean | Mostrar na tabela da visão geral do admin (padrão: true) |
| `admin_filter` | boolean ou objeto | Acrescenta um filtro à visão geral do admin para um campo `select` ou `boolean`. Use `{ "default": <stored-value> }` para ativá-lo com uma seleção padrão, e o `"feature": "permission-name"` opcional para mostrá-lo só a usuários com essa permissão. Independente de `admin_col`. |
| `multi` | boolean | Permitir vários valores |
| `i18n` | boolean | Traduzir por idioma |
| `accept` | string | Restrição de tipo de arquivo para campos image e file |

**Campos HTML** devem usar explicitamente uma de duas configurações:

Simples (blocos curtos de texto, como uma introdução):
```json
"buttons": "bold,italic",
"admin_col": false
```

Rica (conteúdo completo, como um corpo de texto):
```json
"buttons": "h2,h3,h4,bold,italic,orderedlist,unorderedlist,quote,anchor",
"media": true,
"media_sizes": "sm-90,md-70,lg-60,xl-50,xxl-40",
"admin_col": false
```

Nunca defina um campo `html` sem escolher uma delas. `media_sizes` é obrigatório quando `media: true`.

**Botões do editor.** `buttons` lista os botões de formatação de um campo `html`, em ordem. Sem `buttons`, um campo recebe `bold,italic,removeFormat`. Integrados:

| Botão | Faz |
|---|---|
| `bold`, `italic` | Formatação no texto (Ctrl/Cmd+B, Ctrl/Cmd+I) |
| `h2`, `h3`, `h4`, `quote`, `pre` | Formato de bloco; repetir volta a ser parágrafo |
| `orderedlist`, `unorderedlist` | Listas |
| `anchor` | Link: adicionar, editar (pré-preenchido) ou remover; Ctrl/Cmd+K |
| `removeFormat` | Limpar formatação |
| `strikethrough`, `subscript`, `superscript`, `underline` | Opcionais, em nenhum conjunto padrão |

Os mesmos botões aparecem em dois lugares: a barra bubble no texto selecionado e a barra do campo. Nos formulários do admin a barra do campo faz parte da caixa do campo; na edição na página ela flutua acima do campo em foco e também tem Media (quando `media: true`) e Save (Ctrl/Cmd+S). Alt+F10 leva o foco do teclado à barra do campo. Colar mantém só texto simples, a menos que o campo defina `"paste_html": true`, que mantém a formatação básica e remove o markup do Word e do Google Docs.

**Botões próprios do editor.** Um projeto declara seus próprios botões como JSON em `ext/tpl/bubble-editor-buttons/index.tpl` (o template pode usar shortcodes, como `[#text ...#]` para rótulos) e os lista no `buttons` de um campo como os integrados:

```json
{
    "highlight": { "kind": "wrap", "tag": "mark", "label": "[#text Highlight#]", "icon": "M" },
    "h5": { "kind": "block", "tag": "h5", "label": "Heading 5", "icon": "H5" },
    "poi": {
        "kind": "event", "event": "nb:insert-point", "insert": true, "bubble": true,
        "active": "[data-poi-link]", "label": "[#text Point of interest#]", "icon": "<svg ...></svg>"
    }
}
```

| Tipo | Faz |
|---|---|
| `command` | Um comando de formatação (`"command": "bold"`) |
| `block` | Formato de bloco (`"tag": "h5"`) |
| `list` | Comando de lista |
| `wrap` | Envolve a seleção num elemento (`"tag"`, `"class"` opcional); repetir desfaz |
| `insert` | Insere um HTML fixo no cursor (`"html"`) |
| `event` | Dispara um evento DOM no campo (`"event"`, com bubbling, `detail: { editor, range }`) para o código próprio do projeto, por exemplo para abrir um seletor |
| `link` | Pede uma URL, como `anchor` |

Opções: `"insert": true` coloca um botão ao lado de Media na barra do campo em vez de junto dos botões de formatação; acrescente `"bubble": true` quando ele também age sobre texto selecionado; `"active"` é um seletor que marca o botão como ativo quando o cursor está dentro de uma correspondência. Links são `a[href]`: um projeto pode usar `<a>` sem `href` para elementos próprios no texto. O código também pode registrar botões com `nb.bubble_editor.register(name, { label, icon, run(ctx), is_active(ctx) })`.

As ferramentas de edição de um projeto (o modal e o script por trás de um botão `event`) ficam em `ext/tpl/edit-extras/index.tpl`, exibido para usuários que podem editar na página, em páginas do site e do admin.

**Campos de slug:**

Slugs são campos explícitos. Não acrescente `"slug": true` a outro campo. Defina um campo separado com `"type": "slug"` e um `source` que aponte para o campo ou os campos de que ele é derivado.

O formulário do admin calcula o slug no navegador a partir dos campos de origem, antes do envio. A API não inventa campos de slug que faltam ao salvar; ela guarda o valor do campo de slug que recebe. Se um código cria ou importa registros fora do formulário do admin, ele também precisa definir o valor do campo de slug.

Defina `"preserve_existing": true` no campo de slug para gerar slugs que faltam, mantendo inalteradas as URLs já salvas quando o formulário abre ou quando o título de origem muda. Os editores ainda podem mudar um slug salvo de propósito.

```json
"url_slug": {
  "name": "URL slug",
  "type": "slug",
  "source": "title",
  "admin_col": false
}
```

Várias origens são separadas por vírgula; os valores são unidos com um espaço antes de virar slug:

```json
"url_slug": {
  "name": "URL slug",
  "type": "slug",
  "source": "title,date",
  "admin_col": false
}
```

Com `"source": "title,date"`, título `"Jazz Night"` e data `"2026-04-09"`, o slug vira `jazz-night-2026-04-09`. Acrescente sempre `url_slug` à lista `index` do recurso, para que ele possa ser buscado no `route.inc`.

Se o campo de origem é i18n, o campo de slug também é tratado como i18n. Por exemplo, `title.nl`, `title.en` e `title.fr` geram valores correspondentes em `title_slug.nl`, `title_slug.en` e `title_slug.fr`. Não traduza slugs à mão; deixe o campo de slug derivá-los dos valores de origem de cada idioma.

**Campos de seleção:**

Opções fixas:
```json
"type": "select",
"options": {
  "new": "New",
  "active": "Active",
  "closed": "Closed"
}
```

Opções de outro recurso:
```json
"type": "select",
"resource": "categories"
```

Os filtros da visão geral do admin são ativados por campo, em campos `select` e `boolean`:

```json
"status": {
  "type": "select",
  "options": { "active": "Active", "closed": "Closed" },
  "admin_filter": { "default": "active" }
},
"published": {
  "type": "boolean",
  "admin_col": false,
  "admin_filter": true
}
```

Os filtros comparam os valores brutos guardados, enquanto exibem as opções configuradas ou os rótulos do recurso relacionado. Seleções com vários valores correspondem quando o registro contém o valor escolhido. Vários filtros se combinam com E e continuam funcionando com busca, ordenação e paginação. As seleções ficam na URL como `filter[field]=value`. Um parâmetro ausente usa o padrão configurado; um parâmetro vazio escolhe explicitamente **All**. Valores inválidos na URL viram **All**.

**Campos de localização:**

Use `location-picker` quando os editores devem escolher coordenadas num mapa em vez de digitá-las. O campo seletor guarda a latitude no próprio campo e atualiza um campo numérico separado de longitude, indicado em `longitude_field`. Oculte o campo associado com `wrapper_class: "hidden"`, para que o editor veja um único controle enquanto o recurso guarda coordenadas simples:

```json
"location_lat": {
  "name": "Location",
  "type": "location-picker",
  "longitude_field": "location_lng",
  "required": true,
  "admin_col": false
},
"location_lng": {
  "name": "Longitude",
  "type": "number",
  "required": true,
  "min": -180,
  "max": 180,
  "wrapper_class": "hidden",
  "admin_col": false
}
```

## Como os campos de formulário são gerados

Esta seção mostra o caminho completo, da definição de um campo num recurso até o campo exibido no formulário. Leia antes de criar um novo tipo de campo.

### Visão geral

```
.meta field definition
  ↓
render_field() - PHP (core/modules/forms/lib/render-field.php)
  ↓
_f.* template variables
  ↓
[#field-{type}#] template (core/modules/forms/tpl/field-{type}/index.tpl)
  ↓
Alpine.js form_data.{field}   ← x-model binding
  ↓
API POST/PUT on submit
```

### `render_field()` e o contexto `_f.*`

`render_field()` é a porta de entrada. Ele:

1. Recebe um array com a definição do campo, a chave do campo e parâmetros opcionais de valor, armazenamento e origem
2. Espalha a definição inteira do campo em `_f.*` via `set_variable_dot('_f', $def)`; todo atributo do `.meta` fica disponível automaticamente no template
3. Define variáveis calculadas por cima: `_f.key`, `_f.title`, `_f.value`, `_f.model`, `_f.required`, `_f.ai`, `_f.bg`
4. Passa para `[#field-{type}#]`, o template desse tipo de campo

`_f.model` é a expressão do Alpine.js para `x-model`, calculada como `{store}.{field}`. O armazenamento padrão é `form_data`. Em campos i18n ela vira `form_data.{field}[{lang}]`.

Variáveis `_f.*` padrão disponíveis em todo template de campo:

| Variável | Conteúdo |
|---|---|
| `_f.key` | Nome do campo / atributo name do HTML |
| `_f.title` | Rótulo (de `name` no .meta) |
| `_f.value` | Valor pré-preenchido |
| `_f.model` | Expressão x-model do Alpine, por exemplo `form_data.email` |
| `_f.required` | Se o campo é obrigatório |
| `_f.bg` | Classe da cor de fundo do rótulo flutuante |
| `_f.*` (qualquer) | Todas as outras chaves da definição do campo: `_f.accept`, `_f.options`, `_f.resource` etc. |

### `form_data`: o store do Alpine.js

`form_data` é o objeto reativo do Alpine.js que guarda todos os valores atuais dos campos. Ele é declarado no elemento `<form>`. Cada campo grava seu valor ali via `x-model="[#_f.model#]"`.

No envio, `form_data` é colocado no payload da API e enviado por POST para `/api/v1/{resource}`. Campos com editor rico (HTML, gallery, image) gravam seus valores por um mecanismo separado (`nb.edit.get_field_values()`), que é combinado automaticamente; os templates de campo não precisam cuidar disso.

### O template de campo mais simples

`field-default/index.tpl` é a base dos campos de texto:

```html
<div class="[#_f.wrapper_class#]">
    <input type="[#_f.type#]" value="[#_f.value#]" name="[#_f.key#]"
        x-init="[#_f.model#]=`[#_f.value#]`"
        x-model="[#_f.model#]"
        [#if _f.required=(not-empty) echo=required#]
        class="input input-bordered w-full" />
    <label class="pointer-events-none absolute left-3 -top-2.5 px-1
            font-bold text-sm leading-tight [#get _f.bg default=bg-neutral-50#]">
        [#_f.title#][#if _f.required=(not-empty) echo=" *"#]
    </label>
</div>
```

### Acrescentando um novo tipo de campo

1. Crie o template:
   ```
   core/modules/forms/tpl/field-{type}/index.tpl   ← core types
   ext/tpl/field-{type}/index.tpl                   ← project-specific types
   ```

2. No template:
   - Ligue ao Alpine via `x-model="[#_f.model#]"`
   - Defina o valor inicial com `x-init="[#_f.model#]='[#_f.value#]'"`
   - Use `[#_f.key#]` como atributo `name` do HTML
   - Acesse opções específicas do tipo via `_f.*` (por exemplo `[#_f.accept#]` para restrições de arquivo)

3. Use o nome do tipo no `.meta`; ele funciona assim que o template existe.

4. Se o campo gerencia um estado complexo (como um seletor de arquivo ou um botão do navegador de mídia), aninhe um `x-data` próprio dentro do `x-data` do formulário. O `form_data` externo continua acessível a partir dos escopos aninhados.

Campos próprios usam a convenção de template `field-{type}` e o contexto `_f.*` (`_f.model`, `_f.key`, `_f.title`, `_f.bg` etc.). Templates de campo de uma geração anterior, com `_fmodel`, `_fid`, `_ftitle`, `_fbg`, `_bf_render_field()` ou atributos `data-te-*` do Tailwind Elements, são refeitos nessa forma em vez de mantidos.

Campos aninhados e repetidores precisam gerenciar explicitamente os caminhos de modelo aninhados do Alpine. Por exemplo, um campo `group` que guarda `locations` deve ligar os campos filhos a caminhos como `form_data.locations[ix].location`, ou passar um modelo explícito ao reutilizar `render_field()`. Não suponha que o renderizador do core consiga deduzir caminhos de arrays aninhados a partir de uma chave de campo plana.
