# Editor

![Status](https://img.shields.io/badge/status-stable-brightgreen)

Editor de texto rico (WYSIWYG) construído sobre o **Tiptap/ProseMirror** (licença MIT). Substitui o `p-editor` do PrimeNG sem dependências comerciais e implementa `ControlValueAccessor` para integração nativa com Angular Forms.

## Quando usar

- Campos de formulário que exigem formatação de texto (descrições, observações, contratos, e-mails)
- Substituição do `p-editor` do PrimeNG em formulários existentes
- Exibição de conteúdo HTML salvo em modo somente leitura

## Quando não usar

- Entrada de texto simples sem formatação — use `<textarea>` ou `s-text-area`
- Edição colaborativa em tempo real — o componente não possui suporte a operações concorrentes

## Instalação

```typescript
import { EditorComponent } from '@seniorsistemas/angular-components/editor';

@Component({
    standalone: true,
    imports: [EditorComponent],
})
export class MeuComponent {}
```

## Uso básico

```html
<s-editor [(ngModel)]="htmlContent" />
```

```html
<s-editor formControlName="descricao" />
```

## API

### Inputs

| Propriedade | Tipo | Padrão | Obrigatório | Descrição |
|-------------|------|--------|:-----------:|-----------|
| `readonly` | `boolean` | `false` | Não | Desabilita edição e oculta a toolbar |
| `formats` | `EditorFormat[]` | `undefined` | Não | Lista de formatos exibidos na toolbar. Quando omitido, todos os formatos são exibidos |
| `style` | `{ [klass: string]: unknown }` | `undefined` | Não | Estilos inline aplicados ao container do editor (ex: `{ 'min-height': '200px' }`) |
| `styleClass` | `string` | `undefined` | Não | Classes CSS adicionais aplicadas ao container do editor |
| `extensions` | `Extension[]` | `[]` | Não | Extensões Tiptap adicionais para estender o comportamento padrão |

### Outputs

| Evento | Tipo | Descrição |
|--------|------|-----------|
| `editorInit` | `OutputEmitterRef<Editor>` | Emitido quando o editor está inicializado e pronto para uso. Entrega a instância do Tiptap `Editor` |
| `textChange` | `OutputEmitterRef<EditorTextChangeEvent>` | Emitido sempre que o conteúdo é alterado pelo usuário |
| `selectionChange` | `OutputEmitterRef<EditorSelectionChangeEvent>` | Emitido sempre que a seleção de texto no editor muda. Entrega a posição atual da seleção |

### Tipos

```typescript
type EditorFormat =
    | 'header'       // Dropdown de títulos (Normal, Título 1, 2, 3)
    | 'bold'         // Negrito (Ctrl+B)
    | 'italic'       // Itálico (Ctrl+I)
    | 'underline'    // Sublinhado (Ctrl+U)
    | 'strike'       // Tachado
    | 'color'        // Cor do texto (seletor nativo do sistema)
    | 'list'         // Listas com marcadores e numeradas
    | 'align'        // Alinhamento (esquerda, centro, direita)
    | 'blockquote'   // Citação
    | 'code-block'   // Código inline
    | 'link'         // Inserir/editar link
    | 'image'        // Inserir imagem (base64)
    | 'subscript'    // Subscrito
    | 'superscript'  // Sobrescrito
    | 'clean';       // Limpar toda a formatação

interface EditorTextChangeEvent {
    htmlValue: string;  // Conteúdo como HTML
    textValue: string;  // Conteúdo como texto plano
}

interface EditorSelectionChangeEvent {
    from: number;    // Posição inicial da seleção
    to: number;      // Posição final da seleção
    empty: boolean;  // `true` quando não há texto selecionado (apenas o cursor)
}
```

## Exemplos

### Com todos os formatos (padrão)

```html
<s-editor [(ngModel)]="content" />
```

### Toolbar simplificada

Use o input `formats` para exibir apenas os formatos necessários:

```html
<s-editor
    [(ngModel)]="content"
    [formats]="['bold', 'italic', 'underline', 'list']"
/>
```

### Modo somente leitura

```html
<s-editor [(ngModel)]="content" [readonly]="true" />
```

### Com altura personalizada

```html
<s-editor
    [(ngModel)]="content"
    [style]="{ 'min-height': '300px' }"
/>
```

### Reactive Forms

```html
<form [formGroup]="form">
    <s-editor formControlName="descricao" />
</form>
```

```typescript
form = new FormGroup({
    descricao: new FormControl(''),
});
```

### Reagindo à inicialização do editor

```html
<s-editor [(ngModel)]="content" (editorInit)="onEditorReady($event)" />
```

```typescript
onEditorReady(editor: Editor): void {
    // instância Tiptap disponível para operações programáticas
    editor.commands.focus();
}
```

### Reagindo a mudanças de conteúdo

```html
<s-editor [(ngModel)]="content" (textChange)="onTextChange($event)" />
```

```typescript
onTextChange(event: EditorTextChangeEvent): void {
    console.log(event.htmlValue); // '<p>Texto com <strong>negrito</strong></p>'
    console.log(event.textValue); // 'Texto com negrito'
}
```

### Com extensões Tiptap customizadas

```typescript
import CharacterCount from '@tiptap/extension-character-count';

extensions = [CharacterCount.configure({ limit: 500 })];
```

```html
<s-editor [(ngModel)]="content" [extensions]="extensions" />
```

## Toolbar

Os botões de **Desfazer** e **Refazer** são sempre exibidos quando a toolbar está visível, independente do input `formats`.

| Formato | Valor em `formats` | Atalho | Descrição |
|---------|-------------------|--------|-----------|
| Títulos | `'header'` | — | Dropdown: Normal, Título 1, 2, 3 |
| Negrito | `'bold'` | Ctrl+B | |
| Itálico | `'italic'` | Ctrl+I | |
| Sublinhado | `'underline'` | Ctrl+U | |
| Tachado | `'strike'` | — | |
| Cor do texto | `'color'` | — | Abre o seletor de cores nativo do sistema |
| Subscrito | `'subscript'` | — | |
| Sobrescrito | `'superscript'` | — | |
| Listas | `'list'` | — | Com marcadores e numerada |
| Alinhamento | `'align'` | — | Esquerda, centro, direita |
| Citação | `'blockquote'` | — | |
| Código | `'code-block'` | — | Código inline |
| Link | `'link'` | — | Abre dialog para inserir/editar URL |
| Imagem | `'image'` | — | Seleciona arquivo local e insere como base64 |
| Limpar formatação | `'clean'` | — | Remove toda a formatação do texto selecionado |
| Desfazer | sempre visível | Ctrl+Z | |
| Refazer | sempre visível | Ctrl+Y | |

## Formato de dados

O componente produz e consome **HTML**. O valor salvo em `ngModel` ou `FormControl` é a string HTML gerada pelo Tiptap, compatível com o HTML produzido pelo `p-editor` do PrimeNG para os formatos comuns.

## Migração do `p-editor`

### Substituição do import e seletor

```html
<!-- Antes (p-editor) -->
<p-editor formControlName="descricao" [formats]="formats" />

<!-- Depois (s-editor) -->
<s-editor formControlName="descricao" [formats]="formats" />
```

### Diferença semântica do input `formats`

No `p-editor`, `formats` era uma whitelist de formatos permitidos **no conteúdo** (comportamento do Quill). No `s-editor`, `formats` controla a **visibilidade dos botões na toolbar**. O efeito prático é equivalente: o usuário não consegue aplicar formatos que não estão listados.

### Input `modules` removido

O input `modules` do `p-editor` era específico do Quill e não tem equivalente no Tiptap. Use o input `extensions` para adicionar extensões Tiptap customizadas.

## Acessibilidade

- A área editável recebe `role="textbox"` e `aria-multiline="true"` automaticamente
- O `aria-label` da área editável é traduzível via `@ngx-translate`
- A toolbar tem `role="toolbar"` com `aria-label` traduzível
- O controle fica `touched` ao perder o foco (comportamento correto com formulários reativos e validação)
- O seletor de títulos tem `aria-label` traduzível

## Componentes relacionados

- [`DynamicFormComponent`](../dynamic-form/README.md) — use `provideEditorField()` para integrar o editor como campo do formulário dinâmico
- [`DialogComponent`](../dialog/README.md) — usado internamente para o dialog de inserção de links
