Ao desenvolver aplicativos, os fornecedores front-end raramente prestam atenção em como o usuário usará as funções do teclado fornecidas pelo navegador. Não sou uma exceção, mas um dia recebi uma tarefa referente a UX e transições pressionando “Tab” e “Shift + Tab”.
A essência da tarefa é transparente e limpa: existe uma interface cujo layout é exibido abaixo. Conceitualmente, uma página pode conter 2 formulários diferentes e o requisito era que “executar com guias não vá de 1 formulário para outro”.

Tudo ficaria bem se o navegador soubesse "bloquear" nativamente o foco nos formulários. Um exemplo é apresentado na figura abaixo, onde a “borda” laranja marca o elemento atual e cinza - o anterior.


Como você pode ver, o comportamento "nativo" não atende aos requisitos. Então, vamos resolver esse problema. A solução não é complicada, então considere-a.
Seria ideal se houvesse alguns "portões" que impediriam o "pulo" de foco do último (com "Tab") ou do primeiro (com "Shift + Tab") elemento com
"tabindex" ou suporte ao foco por padrão. Portanto, a essência é simples: nossos “portões” são “elementos de entrada” ocultos, que recebem um evento de “evento” como argumento durante o evento “onFocus” e retornam o foco ao elemento de origem. Ilustração abaixo.

É possível obter o elemento anterior usando a propriedade "relatedTarget" do objeto "event". Visualização da solução abaixo.


E aqui está o próprio código. Vale ressaltar que não há sintaxe "ES6 +", pois a idéia é oferecer suporte ao código com diferentes navegadores sem conectar diferentes "transpilers" como o Babel.
function getGateInput(handleTabOut) { var input = document.createElement("input");
Não há nada complicado: é criada uma "entrada", são definidos estilos que "escondem" nossos "portões". Aqui, "display: none" não é usado, pois o navegador não focaliza "Guias" nesses elementos. Devido a esse comportamento, é necessário tornar o elemento transparente e movê-lo para fora da janela do navegador.
function getTabOutHandler(element, GATES) { return function(event) { var relatedTarget = event.relatedTarget || event.fromElement; var target = event.target; var gatesTrapped = target === GATES[0] || target === GATES[1]; if (gatesTrapped && isChild(relatedTarget, element)) { event.preventDefault(); relatedTarget.focus(); } }; }
Para retornar o foco ao item anterior, use getTabOutHandler. Este é
o HOC . Seu primeiro argumento é o nosso contêiner (aquele em torno do qual definimos o "portão"), e o segundo espera uma matriz de "portão" que criamos usando getGateInput. Essa função retorna um manipulador de eventos que funciona de acordo com o princípio descrito acima.
Para que o foco entre no contêiner, precisamos abrir e fechar o “portão”. Faremos isso configurando o atributo
"tabindex" . (-1 - não foque com Guias, 0 - foque de acordo com o fluxo)
function moveGates(open, GATES) { GATES[0].setAttribute("tabindex", open ? -1 : 0); GATES[1].setAttribute("tabindex", open ? -1 : 0); }
Para controlar os portões, configuraremos um manipulador que "ouvirá" pressionar Tab (código 9) e se o elemento em foco (activeElement) estiver dentro do contêiner, feche o "portão", caso contrário, abra-o.
window.addEventListener("keydown", function(event) { if (event.keyCode === 9) { if (isChild(document.activeElement, element)) { moveGates(false, GATES); } else { moveGates(true, GATES); } } });
Total
Foi considerado um método para bloquear o foco em um formulário, que consiste em retornar o foco a um elemento focalizado anterior. Para "capturar" o foco, usamos "elementos de entrada" ocultos, cujo foco foi controlado usando o
"tabindex" . O código acima faz parte da biblioteca
tab-out-catcher que escrevi para resolver meu problema. Exemplos de uso podem ser encontrados
aqui . Há também uma
solução para aplicativos React.