Ir ao conteúdo
SEED engenhariaDesign System

Componentes

68. Editor de texto rico — toolbar mínima

estávelseed-componentes.md v1.43 · §68seção 74 de 9868-editor-de-texto-rico-toolbar-minima-estavel-f7.md · MD5 e4f9203a

Título completo no canon: Editor de texto rico — toolbar mínima — estável (F7.5, 2026-08-16 · promovido em 2026-08-17 pelo gate visual do Rafael — verbatim: "aprovado") · ET1–ET14 · fecha C7 · bancada banco-texto-rico.html v0.2 · suite-texto-rico.mjs 25 · 0 · 0

O que é, e o que explicitamente NÃO é. Este §68 é o editor de CAMPO: a descrição de uma ordem de serviço, a observação de um laudo, o corpo de um comentário. Toolbar mínima, marcas fechadas, registro tipográfico de interface.

Ele não é o editor de DOCUMENTO (arquétipo A13), e essa fronteira não é preferência — ela está medida. O dispositivo C25 do estudo-clickup-completo.md registra, no editor de documento do produto de referência: coluna de 660px, texto 16px / line-height 24px (=1,5) e uma stack de fonte diferente da do resto do aplicativo. Interface quer 14px/1,15 e densidade; leitura corrida quer 16px/1,5 e medida de linha controlada — usar o mesmo registro nos dois piora os dois. Um componente só para os dois casos não serviria bem a nenhum. A13 continua AUSENTE do mapa de cobertura, e é assim que fica dito.

68.0 Rodada zero — leitura visual MEDIDA, e ela decidiu a fronteira#

Veredito: MEDIDO — e é o caso raro em que a leitura visual entrou na spec decidindo o que fica de fora. Os dispositivos lidos: C25 (registro tipográfico próprio da coluna de leitura), C101 (o documento abre como overlay com ×, não como rota), C103 (rail de ferramentas vertical flutuante à direita), C105 (toolbar flutuante de bloco, que aparece ao passar o mouse), C106 (callout como bloco tingido).

Todos os cinco pertencem ao editor de DOCUMENTO. Nenhum deles é do editor de campo — e é por isso que eles aparecem aqui como fronteira, não como contrato: importar a toolbar flutuante de bloco (C105) para um campo de descrição de OS traria um controle que só aparece com o mouse por cima, sobre um campo de três linhas. Leitura visual bem lida também serve para dizer o que não se copia.

68.1 RITO — três rodadas encadeadas#

Rodada Fonte O que trouxe
R1 — canon ARIA APG — Toolbar Pattern · SC 2.1.2 No Keyboard Trap A toolbar é composite: um único tab stop, setas navegando dentro, Home/End nas pontas (ET2). E o 2.1.2 é o que decide o Tab (ET6): editor que insere tabulação prende quem navega por teclado
R2 — mercado/stack PatternFly · Semrush Intergalactic · prática de editores (ProseMirror/Lexical/TipTap) Do mercado vem a confirmação de que o estado do botão precisa seguir o cursor, não o último clique (ET3), e a lição estrutural: o modelo do documento é da biblioteca de edição; o contrato é do DS. Daí a fronteira ET-P1
R3 — normas WCAG 2.2: SC 1.3.1 Info and Relationships · SC 4.1.2 Name, Role, Value · SC 4.1.3 Status Messages O 1.3.1 é o que transforma "usar <strong> e não <b>" de gosto em norma: a marcação tem de carregar a relação, não só a aparência. O 4.1.2 exige role/nome/valor na área editável (ET4) e nos alternadores (ET3). O 4.1.3 põe o contador em região de status (ET13)

68.2 A decisão estruturante: o conjunto de marcas é FECHADO em seis#

Negrito · itálico · lista com marcadores · lista numerada · link · limpar formatação.

O motivo não é estético, é de custo em quatro lugares. O valor deste campo não fica na tela: ele é reimpresso na proposta (seed-ds-proposta), no e-mail (seed-email.md) e no PDF. Toda marca que o editor aceita é uma marca que os outros três meios precisam saber desenhar — e o e-mail é o mais restrito dos quatro. Uma marca nova custa quatro implementações e quatro testes, não um.

Descartado, com o porquê: títulos e tabelas dentro do campo (são estrutura de documento, e o documento é o A13) · cor de texto e realce (não invertem no tema escuro, e cor não é informação que sobrevive à reimpressão) · imagem embutida (vira anexo, que é o §14) · Markdown como formato de entrada (exigiria que a pessoa soubesse a sintaxe — o oposto de toolbar mínima).

68.3 Os contratos ET1–ET14#

# Contrato Por quê Descartado
ET1 Conjunto de marcas fechado em seis Ver 68.2. O gerador aborta acima de oito — acima disso a toolbar deixa de ser mínima e o custo deixa de ser visível Toolbar “completa” estilo processador de texto (quatro implementações por marca)
ET2 A toolbar é role="toolbar", nomeada, com aria-controls e um tab stop; setas andam dentro APG. Seis botões na ordem de tabulação obrigariam seis Tab para chegar ao texto Botões soltos (seis paradas antes do campo)
ET3 Cada alternador tem aria-pressed que segue o cursor Botão que mostra o estado do último clique mente sobre o texto onde o cursor está — e botão que mente é pior que botão sem estado aria-pressed alternando no clique (descasa do conteúdo na primeira vez que se move o cursor)
ET4 A área é textbox multilinha, com nome ligado (aria-labelledby) e descrição (aria-describedby) contenteditable puro não se anuncia como campo. SC 4.1.2 div editável sem papel (a AT não sabe que é campo)
ET5 Os atalhos existem e estão escritos na tela — e também no nome acessível de cada botão Atalho que ninguém descobre não existe. E atalho escrito e inoperante é pior: promete e falha Atalhos só na documentação
ET6 Tab SAI do editor e nunca insere tabulação SC 2.1.2. Indentar lista com Tab é a convenção dos editores — e é exatamente o que prende quem navega por teclado. A indentação, quando for preciso, entra pela toolbar Tab indenta (armadilha de teclado); Esc para sair (exige saber que existe)
ET7 A saída é HTML semântico de lista branca: p, br, strong, em, ul, ol, li, a SC 1.3.1. <b> é aparência, <strong> é relação — e é a relação que sobrevive à reimpressão em três meios <b>/<i> (não viajam); HTML livre (superfície de injeção e de drift)
ET8 O que se cola passa pela mesma higienização Uma função só, usada nos dois caminhos: duas implementações seriam duas verdades, e a que ninguém testa é a que deixa passar Limpar depois de colar (há uma janela com o conteúdo sujo já no DOM)
ET9 Link sem texto não nasce: a seleção vira o rótulo; e o rel entra sempre URL crua lida letra a letra por leitor de tela; “clique aqui” não diz para onde vai Pedir a URL primeiro e o texto depois (produz link sem rótulo quando a pessoa desiste no meio)
ET10 A dica do vazio não é o rótulo Regra do §13: placeholder como rótulo some ao digitar e leva o nome do campo junto Dica no lugar do rótulo (o padrão que o §13 já proibiu)
ET11 Cor fixa colada não sobrevive — nem como style, nem como atributo color:#333333 não inverte no tema escuro: é o BT3 do §64 entrando pelo conteúdo em vez do CSS. E a higienização remove a tag, mantendo o textonada do que a pessoa escreveu se perde; perde-se só o que o outro programa impôs Manter a cor “porque o usuário escolheu” (ele escolheu no outro programa, para o tema claro dele)
ET12 Alvo dos botões ≥ 44×44px Piso do projeto. Toolbar é onde a tentação de encolher aparece primeiro 32px por densidade
ET13 Limite, se houver, é anunciado e não trunca em silêncio Herda a doutrina do CD4 (§66): truncar sem avisar entrega um texto que a pessoa não escreveu Corte silencioso no envio
ET14 O valor tem leitura em texto puro derivada em fonte única A lista, a busca e o e-mail curto consomem texto sem marcas. Sem uma derivação canônica, cada consumidor inventa a sua, e nascem três versões do mesmo texto Cada consumidor removendo tags do seu jeito

68.4 Os números medidos#

Par Claro Escuro Piso
Texto do campo sobre a superfície 13,72 12,49 4,50
Botão da toolbar em repouso 4,60 7,39 4,50
Botão pressionado (sobre action-primary) 4,60 7,39 4,50
Barra inferior (contador e dica) 7,13 9,10 4,50
Borda do editor (não-textual) 3,38 4,50 3,00

Alvo dos seis botões: 44×44px.

A prova da higienização, medida — colado o HTML que um editor de escritório produz (style="font-family:Calibri;color:#333333", <b>, <font color="#FF0000">, <div>, <span>):

Resultado
Tags que sobraram p · strong · em
style= no resultado nenhum
color no resultado nenhum
Texto perdido nenhum — “Escopo”, “três quadros de distribuição”, “5 dias úteis” e “Relatório em PDF ao fim” continuam lá

68.4-b O gate achou o que a lista branca não cobria: comentário também é conteúdo#

O Rafael colou de um editor real e o resultado trouxe <!--StartFragment--> para dentro do campo. A higienização varria no.querySelectorAll('*')e comentário não é elemento. Ele sobrevivia inteiro, e a suíte não via, porque ela media tags e comentário não é tag.

Não é enfeite: <!--StartFragment--> e <!--EndFragment--> são marcadores que o clipboard do Windows adiciona a todo text/html; e o Word emite comentários condicionais (<!--[if gte mso 9]><xml>…<![endif]-->) com folhas de estilo inteiras dentro deles. Uma lista branca de tags não vê nada disso.

Correção: a higienização passa a varrer os nós de comentário com TreeWalker antes de olhar os elementos, e removê-los. Guarda nova ET8-b, que cola um text/html com os dois marcadores e um bloco condicional do Word e cobra zero nós de comentário — conferindo, na mesma medida, que nenhum texto se perdeu. Provada capaz de reprovar: desfeito o conserto, o placar vai a 20 · 1.

Regra nova: guarda que varre por TIPO DE NÓ erra em todos os tipos que não listou. É a mesma família dos defeitos de recorte (8º, 14º, 17º) — o escopo errado, de novo, agora escolhido por tipo em vez de por posição ou por texto.

E um achado de leitura, não de código: o bloco "o que se cola" mostra o código-fonte escapado. Selecioná-lo com o mouse e dar Ctrl+C leva texto, e o editor insere texto — corretamente, mas o resultado parece quebrado. A bancada agora diz isso em uma linha, e o botão "Colar isto no editor" é o caminho declarado: ele entrega o mesmo conteúdo como text/html, que é o que o editor de escritório de fato coloca na área de transferência.

68.4-c A lista branca responde QUAIS tags — ela não responde EM QUE ORDEM#

Segunda volta do mesmo gate, e o achado é de outra natureza. Colando dentro de um parágrafo existente, a saída era:

<p>Texto que ja<p>Escopo…</p><p>Prazo…</p> estava no campo.</p>

Bloco dentro de bloco. HTML inválido — um <p> não pode conter um <p>. E a guarda ET7 passava, porque o conjunto de tags estava certo: p, strong, em. O que estava errado era o arranjo.

Por que aconteceu: a colagem insere os nós com range.insertNode, e a API do DOM não impõe modelo de conteúdo — só o parser de HTML impõe. Inserir programaticamente permite o que o parser jamais produziria.

Correção, usando o próprio parser: reatribuir innerHTML faz o conteúdo ser reanalisado, e o parser fecha o <p> de fora ao encontrar o de dentro. Depois disso, parágrafo vazio sai e texto solto no topo é embrulhado — sem bloco, o consumidor (proposta, e-mail, PDF) não sabe onde o parágrafo começa. A normalização roda só na colagem, nunca no input: o round-trip recria os nós, e recriar nós enquanto alguém digita mataria o cursor a cada tecla.

Regra nova: lista branca é vocabulário; estrutura é outra medição. Toda guarda que valida marcação por conjunto de tags precisa de uma irmã que valide aninhamento.

Guarda nova ET7-b: cola com o cursor no meio de um parágrafo — a posição que produzia o defeito — e mede três coisas que só existem na estrutura (bloco aninhado, parágrafo vazio, nó de texto solto no topo), conferindo também que o texto antes e o texto depois do cursor sobrevivem. Provada capaz de reprovar: sem a normalização, ela acusa 2 aninhamentos e o placar vai a 21 · 1.

68.5 Uma decisão de render, e por que ela está escrita#

Os quatro primeiros botões usam glifo tipográficoB, I, •—, 1—. Os dois últimos usam palavra: “Link” e “Limpar”.

O motivo é um achado do render, não gosto: (link) e (limpar) caem em fonte de fallback e saem como retângulo vazio em parte das máquinas. O set canônico do §45 não tem glifo de link nem de limpar formatação, e a árvore NM4 manda usar Lucide nesse caso — mas importar dois SVGs para dois botões de uma toolbar de seis é peso sem retorno. Palavra curta é legível em qualquer fonte, em qualquer tema, e não precisa de legenda.

68.6 A suíte#

suite-texto-rico.mjs — 22 PASS · 0 FAIL · 1 medida. A guarda central mede o que sai, não o que aparece: um editor pode parecer perfeito e produzir <b style="color:#333">.

Detalhe de método que vale para as próximas suítes: a lista branca está escrita nas duas pontas — no gerador e na suíte — de propósito. Guarda que importa a régua do medido não mede nada: mede a opinião dele sobre si mesmo. Se alguém afrouxar a lista no gerador, a suíte reprova.

Guarda O que ela faz de fato
ET7 seleciona um trecho, clica em negrito e compara as tags da saída com a lista branca
ET3 move o cursor para dentro e para fora do negrito e confere o aria-pressed nos dois
ET8 dispara um paste real com text/html sujo e confere o que sobrou
ET11 confere que nenhuma cor sobreviveu e que nenhum texto se perdeu
ET6 aperta Tab e confere que o foco saiu e que nada foi inserido
ET5-OP aperta Ctrl+B e confere que produziu <strong>
ET2-OP aperta e confere que o foco andou e que o tab stop acompanhou
ET9 clica em link sem seleção (não nasce) e com seleção (o texto vira rótulo, com rel)

68.7 Pendências#

# Pendência Por que fica aberta
ET-P1 A bancada usa document.execCommand, que é API depreciada O que se avalia nesta página são os contratos — o que a toolbar promete, o que o estado reflete, o que a higienização deixa passar —, não a engenharia do editor. Em produção o §68 é implementado sobre biblioteca de edição de verdade. A fronteira está dita no código e aqui, para não virar surpresa
ET-P2 FECHADA — gate visual APROVADO em 2026-08-17. O olho achou o <!--StartFragment--> atravessando a higienização, e a reprodução do caso revelou <p> dentro de <p>; guardas ET8-b e ET7-b. A seção é promovida a estável
ET-P3 O editor de DOCUMENTO (A13) segue AUSENTE Fronteira declarada em 68.0. Ele precisa do registro tipográfico de leitura (C25: 660px, 16px/1,5, outra stack) e de estrutura de blocos — é outro componente, e provavelmente outro arquétipo de tela
ET-P4 A higienização não foi auditada como fronteira de segurança Ela existe para proteger a integridade visual e semântica do valor. Sanitização contra injeção é responsabilidade do produto, no servidor, e isso precisa estar escrito onde o produto lê — não só aqui
ET-P5 Os três consumidores (proposta, e-mail, PDF) ainda não declararam que desenham as seis marcas O ET1 assume que eles sabem. Assumir não é medir — a auditoria dos três é o que fecha o argumento de custo do 68.2

BancadaSEED Design System v2 · banco-texto-rico (§68 · editor de texto rico) — preview v0.2abrir em página própria ↗
Esc