Componentes
67. Edição em linha
seed-componentes.md v1.43 · §67seção 73 de 9867-edicao-em-linha-estavel-f7-5-2026-08-16.md · MD5 98711697Título completo no canon: Edição em linha — estável (F7.5, 2026-08-16 · promovido em 2026-08-17 pelo gate visual do Rafael — verbatim: "aprovado") · IL1–IL14 · fecha C15 · bancada banco-edicao-linha.html v0.2 · suite-edicao-linha.mjs 23 · 0 · 0
O que é. O valor exibido vira campo no próprio lugar, sem abrir formulário nem modal. O §48 (painel) já tinha o caso da grade — PN37: "a lente Tabela é planilha: setas navegam entre células, Enter abre editor na própria célula". O que faltava era o componente que a grade consome, e que também serve fora dela: título de registro, campo do painel de detalhe (§49), item de lista (§43).
Fronteira declarada, e ela é o coração do §67: esta seção especifica a passagem do estado de leitura para o de edição e de volta. Qual controle aparece na edição não é decisão dela (IL13) — monetário abre o §59, data abre o §61, escolha abre o §60, pessoa abre o §62, prioridade abre o §63. A bancada exercita texto e número, e declara os demais em vez de simulá-los: simular componente que já existe cria uma segunda verdade que ninguém mantém.
67.0 Rodada zero — SEM INSTÂNCIA ISOLÁVEL, e a diferença importa#
O veredito aqui não é "não visitado". A edição direta foi lida no produto de referência — ela é a origem do bloco PN22–PN41 do §48, que nasceu de leitura visual em 2026-08-13. O que não existe é uma instância do componente isolado: no produto ele aparece sempre dentro da grade, e nunca como controle avulso com estados próprios.
Veredito: SEM INSTÂNCIA ISOLÁVEL. Consequência de método: o insumo de leitura visual desta seção já está gasto no §48 — e é por isso que o §67 nasce citando PN27, PN28, PN29 e PN37 em vez de reabrir a leitura. Herdar decisão medida é mais barato e mais honesto que remedir mal.
67.1 RITO — três rodadas encadeadas#
| Rodada | Fonte | O que trouxe |
|---|---|---|
| R1 — canon | ARIA APG — Button Pattern · SC 2.1.1 Keyboard | Não existe padrão APG de "inline edit". O que existe é o padrão do botão: o valor em repouso precisa ser um controle de verdade para ser alcançável e anunciável (IL1). E o SC 2.1.1 é o que elimina o duplo clique como caminho único — duplo clique não tem equivalente de teclado |
| R2 — mercado/stack | PatternFly — Inline edit · Semrush Intergalactic — InlineEdit a11y | PatternFly: existe um action-group explícito de salvar/cancelar que só aparece na edição, e o padrão pede preservar o contexto visual — "within the context of their current view", sem modal, sem transição destrutiva. Semrush: Enter abre e, dentro da edição, salva; Esc volta descartando; aria-describedby liga a mensagem de erro. Nenhum dos dois define o comportamento do blur — a decisão ficou em aberto no mercado, e por isso ela é decidida aqui com o porquê escrito |
| R3 — normas | WCAG 2.2: SC 3.2.2 On Input · SC 2.5.8 Target Size · SC 4.1.3 Status Messages | O SC 3.2.2 responde a pergunta que mais confunde: salvar ao sair do campo NÃO é mudança de contexto — contexto é viewport, foco ou conteúdo que muda o significado da página; "uma mudança de conteúdo nem sempre é uma mudança de contexto". O que o critério proíbe é a mudança automática sem aviso prévio: submeter o formulário ao preencher um campo, ou mover o foco sozinho sem estar descrito antes. Daí o IL11 |
67.2 A decisão que mais custa: clicar fora CONFIRMA#
O mercado não decide isso (R2), então a decisão é da casa, e ela tem lado.
Clicar fora é o acidente mais comum num campo em linha. As duas escolhas erram de formas assimétricas:
| Escolha | O que o acidente causa | Recuperável? |
|---|---|---|
blur cancela |
destrói o que a pessoa digitou | Não. O texto nunca existiu em lugar nenhum |
blur confirma |
grava um valor que a pessoa talvez não quisesse | Sim — desfazer de 8 segundos, já contratado no PN28 |
Entre um erro reversível e um irreversível, o sistema escolhe o reversível. E o
Esccontinua existindo para quem quer descartar de propósito — a diferença entre descartar por decisão e descartar por acidente é a decisão.
E há um efeito colateral que só aparece implementando: o blur do campo dispara antes do
clique no botão ✕. Sem tratamento, cancelar seria precedido de um salvamento — a ação faria o oposto
do que promete. O contrato IL5 tem guarda própria para isso, e ela clica no ✕ de verdade.
67.3 Os contratos IL1–IL14#
| # | Contrato | Por quê | Descartado |
|---|---|---|---|
| IL1 | O valor em repouso é um botão, e o nome diz a ação e o valor — “Editar Descrição da ordem: Inspeção termográfica do QGBT” | Texto com onclick não é alcançável por Tab nem anunciado como acionável. E “Inspeção termográfica…” sozinho não diz que dá para editar |
<div onclick> (invisível ao teclado); contenteditable no próprio texto (estado de edição sem fronteira visível, e sem lugar para confirmar/cancelar) |
| IL2 | Entra por clique simples e por Enter/Espaço | SC 2.1.1. O botão nativo já dispara click nas duas teclas — não se acrescenta keydown paralelo, que abriria duas vezes |
Duplo clique como caminho único (sem equivalente de teclado); ícone de lápis separado como único gatilho (alvo minúsculo ao lado de um alvo grande inerte) |
| IL3 | Ao entrar, o foco vai ao campo e o texto fica selecionado | Substituir é o caso comum em campo de lista. Cursor no fim cobra uma seleção extra a cada edição | Cursor no fim (mais teclas para o caso comum) |
| IL4 | Enter confirma · Esc cancela e restaura · clicar fora CONFIRMA | Ver 67.2 | blur cancela (perda irreversível); pedir confirmação em diálogo (mata a vantagem de editar em linha) |
| IL5 | Confirmar e cancelar também existem como botões visíveis durante a edição | Toque não tem Esc. Teclado tem Enter/Esc; ponteiro e toque têm ✓ e ✕. Dois caminhos, a mesma mutação — a mesma lei do CP30 | Só teclado (exclui toque); botões sempre visíveis mesmo em repouso (numa lista de 20 linhas, 40 botões de ruído) |
| IL6 | Sem salto de layout: a caixa de edição mantém a altura da linha (tolerância 2px, declarada) | Numa lista longa, abrir a edição empurraria tudo o que está abaixo e a pessoa perde o lugar na tela. Medido: 49,0 → 49,0px, deslocamento 0,0px | Campo maior que o texto “para caber melhor” (empurra a lista) |
| IL7 | Confirmar devolve o foco ao gatilho e anuncia o resultado | Componente que troca o próprio DOM sem devolver o foco larga o usuário no body — defeito clássico. E o anúncio é "Responsável alterada para X": resultado, não percurso (PN28) |
Foco no próximo campo (decide pelo usuário); nenhum anúncio |
| IL8 | Erro de validação não sai do modo de edição | Fechar a edição no erro descarta o que a pessoa digitou — o mesmo dano que o blur cancela, agora causado pelo sistema. A mensagem se liga por aria-describedby (R2/Semrush) |
Fechar e mostrar toast (o texto se perde) |
| IL9 | “Salvando” e “salvo” declarados, e o valor não pisca de volta ao antigo | Mutação otimista, herdada do PN27: o gesto aplica na hora, na tela. Ver o valor voltar e avançar de novo lê como falha | Esperar o servidor para desenhar (o campo trava); nenhum estado (a pessoa não sabe se gravou) |
| IL10 | A affordance aparece no hover E no foco — nunca só no hover | Quem navega por teclado não passa o mouse. Medido: borda #788F9D, 3,38 no claro e 4,50 no escuro contra o fundo |
Só hover (exclui teclado); borda permanente (a lista vira grade de caixas) |
| IL11 | Digitar não salva a cada tecla e não move o foco sozinho | SC 3.2.2: mudança automática exige aviso prévio descrito. Auto-avanço sem aviso é o mesmo defeito das caixinhas de OTP do §66 | Salvar a cada tecla (grava lixo intermediário e enche o histórico) |
| IL12 | Alvo do gatilho ≥ 44px de altura | Piso do projeto desde o §1, acima do SC 2.5.8 (24px). Medido: 784×44px | 32px “por densidade” (o alvo é o que se erra, não o que se vê) |
| IL13 | O controle da edição é o do domínio — §59 · §60 · §61 · §62 · §63 | Sem isso o componente vira campo de texto universal, e a data volta a ser string livre. A tabela de delegação está impressa na bancada | Texto livre para tudo (perde máscara, teclado e validação de cada domínio) |
| IL14 | Campo que não se edita não vira gatilho — e diz por quê | Herda o PN29: alça que existe e não obedece é botão morto; trava invisível vira bug aos olhos de quem usa. Sem button, sem tabindex, com o motivo ao lado |
disabled mudo (a pessoa clica e nada acontece, e ela não sabe se é bug) |
67.4 A galeria de estados, e por que ela é contrato#
O catálogo operante mostra um estado por vez — é assim que o componente funciona. Mas a lei CP31 diz que bancada imprime os estados lado a lado, e ela existe por um motivo prático: avaliar o estado de erro não pode exigir que o avaliador reproduza o erro — gate que depende de reprodução não acontece.
Os seis estados — repouso · hover/foco · em edição · erro · salvando · bloqueado — são impressos
juntos, e são cópias estáticas. Isso não é detalhe de implementação: um <button> de verdade
impresso ali seria um botão morto, a classe de defeito que o projeto persegue desde a v1.4. A
galeria não tem button, não tem input e não tem tabindex — e a suíte mede essa ausência
(guarda CP31), assim como o gerador aborta se a marcação da galeria contiver qualquer um dos três.
67.5 A suíte#
suite-edicao-linha.mjs — 20 PASS · 0 FAIL · 1 medida. Doze das vinte guardas agem:
| Guarda | O que ela faz de fato |
|---|---|
IL6 |
mede o retângulo da linha antes e depois de abrir, e o deslocamento da linha seguinte |
IL3 |
confere que o foco está no input e que o texto está selecionado (selectionStart/End) |
IL4-esc |
digita, aperta Esc e confere valor restaurado, foco de volta e anúncio |
IL4-enter |
digita, aperta Enter e confere valor, foco, nome acessível atualizado e anúncio de resultado |
IL4-blur |
digita e clica fora: prova que grava |
IL5 |
digita e clica no ✕: prova que cancela mesmo com o blur disparando antes |
IL5-b |
digita e clica no ✓: prova que confirma |
IL8 |
esvazia e confirma: a edição continua aberta, com aria-invalid e aria-describedby |
IL9 |
espera as duas fases e confere salvando → salvo sem o valor piscar |
IL11 |
digita e confere que o gatilho ainda mostra o valor antigo e o foco não fugiu |
IL2 |
foca e aperta Enter no gatilho |
CP31 |
conta os estados impressos e procura controles mortos entre eles |
67.6 Pendências#
| # | Pendência | Por que fica aberta |
|---|---|---|
| IL-P1 | ✅ FECHADA — gate visual APROVADO em 2026-08-17, sem achado próprio. A seção é promovida a estável |
|
| IL-P2 | ✅ FECHADA — EXECUTADA em 2026-08-19. E os defeitos eram SETE, não três: o pior — a grade era INALCANÇÁVEL POR TECLADO (56 células em tabindex="-1", nenhuma em "0"; 40 Tabs sem entrar) — não estava escrito na pendência. A grade passa a cumprir o padrão grid da ARIA e o §67; o contrato IL15 e a guarda que o mede por COMPORTAMENTO estão no §67.7 |
|
| IL-P3 | O desfazer de 8 segundos que sustenta a decisão do blur não existe nesta bancada |
Ele é contrato do §48 (PN28) e vive no painel. A decisão do 67.2 depende dele: sem desfazer, blur confirma deixa de ser o lado seguro. Isso está dito aqui para não virar suposição — quem consumir o §67 fora do painel precisa levar o desfazer junto |
| IL-P4 | Edição em linha de valor de domínio não foi exercida | O IL13 delega, e delegação declarada não é delegação medida. Exercitar exigiria montar §59/§60/§61/§62 dentro da bancada — trabalho de composição, não de spec |
67.7 IL15 — a grade do §48 em USO: o padrão grid da ARIA, e o defeito que ninguém tinha medido#
Escrito em 2026-08-19, ao executar a IL-P2. Seção autossuficiente: presume um leitor que nunca viu a conversa que a gerou.
67.7.1 O que é a grade, e o que estava errado#
O §48 (tela-painel, dispositivo PN37) desenha uma planilha: tabela com célula editável,
setas que navegam e alça que preenche para baixo. O §67 já especifica como se edita um valor em
linha — e a grade tinha editor próprio. Essa é a IL-P2, decidida pelo Rafael em 2026-08-17 junto
com a SI-P4 ("unificar é melhor… precisamos manter o mesmo padrão, isso é inegociável").
A pendência listava TRÊS divergências. Medido em 2026-08-19, eram SETE — e a mais grave não estava escrita:
| # | Defeito medido | Norma |
|---|---|---|
| 1 | A GRADE ERA INALCANÇÁVEL POR TECLADO. As 56 células tinham tabindex="-1" e nenhuma tinha "0". Partindo do seletor "selecionar todas" da própria tabela e teclando Tab 40 vezes, o foco nunca entrava numa célula. Só o clique abria a grade |
SC 2.1.1 |
| 2 | a célula era <td> sem papel nem nome, e não <button> com o nome do IL1 |
SC 4.1.2 |
| 3 | sem nome acessível na forma "Editar <rótulo>: <valor>" |
SC 4.1.2 · 2.4.6 |
| 4 | sem a affordance de lápis do IL10 | IL10 |
| 5 | sem role="grid" — tabindex em <td> solto não é o padrão ARIA |
SC 1.3.1 |
| 6 | o title da alça de preencher entrava no nome acessível da célula. Medido: "Ana Lima arraste para copiar para baixo" |
SC 4.1.2 |
| 7 | faltavam Home/End, Ctrl+Home/Ctrl+End e F2 | padrão grid |
A lição, e ela é a mesma da SI-P4 vista de outro ângulo: PENDÊNCIA ESCRITA É HIPÓTESE, NÃO CENSO. A IL-P2 estava escrita desde 2026-08-17 com três itens de SEMÂNTICA. O defeito que realmente impedia uma pessoa de usar a grade era de TECLADO, e ele só apareceu quando alguém teclou Tab de verdade. Guarda que pergunta "tem este atributo?" nunca teria achado: foi preciso PRESSIONAR a tecla.
67.7.2 RITO — três rodadas encadeadas#
- R1 — canon (padrão
gridda ARIA / APG). O padrão admite duas formas: "a célula contém UM widget cuja operação não exige as setas, e as teclas de navegação da grade põem o foco NESSE widget" ou "a célula contém texto ou um único gráfico e as teclas de navegação põem o foco na CÉLULA". As duas exigem os papéisgrid/row/gridcell. Teclado nomeado pelo padrão: setas · Home/End · Ctrl+Home/Ctrl+End · Enter ou F2 para editar · Esc para cancelar ·tabindexrotativo. - R2 — mercado/stack. Partir de uma
<table>semântica e acrescentarrole="grid"preserva as relações cabeçalho↔célula pelos<th scope>; quando a célula tem um controle, otabindexrotativo mora no controle, não na célula; e o editor precisa de nome acessível próprio, porque o cabeçalho de coluna deixa de bastar no instante em que o texto da célula vira um campo nu. - R3 — normas. SC 2.1.1 (teclado) · 1.3.1 (relações) · 4.1.2 (nome, papel, valor) · 2.4.6 (rótulos) · 3.2.2 (sem mudança automática) · 2.5.8 (tamanho de alvo).
VEREDITO: o IL1 do §67 é exatamente a forma (a) do padrão, e fica CONFIRMADO. O RITO não revogou
o que estava escrito — corrigiu o peso: a pendência tratava role="grid" como "agravante", e ele é a
PRECONDIÇÃO; sem o papel, nenhuma das duas formas existe.
ALTERNATIVA DESCARTADA: a forma (b) — célula focável com texto dentro. É legítima na ARIA e seria menos código, mas contradiz o IL1, que o Rafael já aprovou, e deixaria a célula sem nome de ação: "Ana Lima" não diz que dá para editar.
67.7.3 O contrato IL15#
| # | Cláusula | O que a guarda faz de fato |
|---|---|---|
| IL15-a | a tabela declara role="grid" e tem cabeçalhos de coluna |
lê o papel e conta th[scope="col"] |
| IL15-b | toda célula editável carrega UM gatilho (forma (a)) | conta gatilhos contra células |
| IL15-c | tabindex rotativo: exatamente um gatilho na sequência de Tab |
conta os tabindex="0" |
| IL15-d | a grade é ALCANÇÁVEL por Tab | pressiona Tab até 25 vezes a partir do seletor "selecionar todas" e vê se o foco entra |
| IL15-e/f | seta direita anda uma coluna · seta baixo anda uma linha | tecla e lê a coordenada do foco |
| IL15-g/h | End e Home vão às pontas da LINHA | tecla e lê |
| IL15-i/j | Ctrl+End e Ctrl+Home vão às pontas da GRADE | tecla e lê |
| IL15-k | o tabindex="0" acompanha o foco |
conta depois de navegar |
| IL15-l | nome do gatilho na forma "Editar <rótulo>: <valor>" |
lê o nome acessível |
| IL15-m/o | F2 e Enter entram na edição | tecla e confere que o foco caiu num campo |
| IL15-n | Esc cancela e devolve o foco ao gatilho (IL7) | tecla e lê onde o foco parou |
| IL15-p | a alça de preencher é aria-hidden — o title dela não entra no nome |
lê o atributo |
| IL15-q | a affordance de lápis aparece no foco, não só no hover (IL10) | foca e lê a opacidade computada |
| IL15-r | célula travada não tem gatilho e diz o motivo (IL14) | conta gatilhos e motivos nas travadas |
validacao/guarda-il15.mjs — ALVO DE PASTA: varre todo *.html da raiz e mede todo artefato com
table.planilha; arquivo sem grade sai [n/a] nomeado. Ela troca a lente para "tabela" antes de
medir, porque a grade só existe nessa lente.
PROVA DE REPROVAÇÃO: rodada sobre a cópia anterior do tela-painel.html, reprova 6 cláusulas —
IL15-a (sem papel), IL15-b (0 gatilhos em 56 células), IL15-c (0 de 0), IL15-d (NUNCA em 25
pressionamentos), IL15-p (alça visível ao leitor de tela) e IL15-q (sem gatilho, logo sem lápis).
PLACAR: 1 PASS · 0 FAIL · 50 [n/a], em seis execuções idênticas.
DEFEITO DE MEDIÇÃO CONSERTADO NA PRÓPRIA SESSÃO EM QUE A GUARDA NASCEU. A primeira versão lia a opacidade do lápis logo após dar o foco, e a transição ainda estava correndo: o valor voltava
0.999599e a guarda reprovava um artefato correto em 3 de 6 execuções. Guarda intermitente é pior que guarda ausente: ela ensina a ignorar o placar. A transição é desligada antes da leitura, e o que se mede passa a ser o valor de DESTINO.
67.7.4 O que mudou no artefato, e o que ficou aberto#
O tela-painel é gerado: a mudança foi no molde validacao/painel-template.html e o artefato foi
regerado. A suite-painel reprovou o bloco novo no primeiro segundo — transition: opacity .12s
tinha duração solta, e a guarda PN21-11 exige token. Trocado por var(--seed-dur-productive-fast).
Placares depois: suite-painel 146 PASS · 0 FAIL · guarda-il15 1 · 0 · 50 ·
contraste 2 · 0 a 1440px e 2 · 0 a 390px · forced-colors FC1/FC2 2 · 0 cada ·
reflow BT7 3 · 0 · 1.
| # | Pendência que nasce | Por que fica aberta |
|---|---|---|
| PN-P6 | ✅ FECHADA em 2026-08-19 pelo gate. Decisão do Rafael, verbatim: "sobe para 44". A altura mínima da célula (.planilha .cel-edit) foi de 40px para 44px no molde validacao/painel-template.html, e o painel foi regerado. Medido depois: a guarda-il15 imprime o alvo em 234×44px — a mesma régua que produziu a pendência. Custo medido, e é o único: no mesmo recorte de 360px de altura cabe uma linha a menos (recorte B2 da folha render-audit/gate-v134/fechamento.html). ALTERNATIVA DESCARTADA: manter 40px e declarar a planilha como exceção de densidade — descartada porque toda exclusão declarada é uma dívida com juros, e porque a linha inteira é o alvo, então os 4px não compram densidade real. Aplicado à célula inteira, não só à coluna medida: altura por coluna quebraria o alinhamento horizontal da planilha. Placar depois: suite-painel 146 · 0, guarda-il15 1 · 0 · 50, contraste 2 · 0 nos dois temas. |
|
| CC-P7 | O contraste do tela-painel é medido na lente padrão (lista). Nenhum instrumento troca a lente antes de medir, então as outras sete lentes — inclusive a grade — seguem não medidas por contraste. Ausência de medição não é aprovação |
Também cita o §67: tela-painel.