Foco no próximo elemento no índice da guia

103

Estou tentando mover o foco para o próximo elemento na sequência de guias com base no elemento atual que tem foco. Até agora não encontrei nada em minhas pesquisas.

function OnFocusOut()
{
    var currentElement = $get(currentElementId); // ID set by OnFocusIn 

    currentElementId = "";
    currentElement.nextElementByTabIndex.focus();
}

Claro que o nextElementByTabIndex é a parte chave para que isso funcione. Como encontro o próximo elemento na sequência de guias? A solução precisaria ser baseada em JScript e não em algo como JQuery.

JadziaMD
fonte
3
por que você tem essa linha currentElementId = "";?
1
Não acho que nenhum navegador exponha as informações de ordem das guias - e o algoritmo usado pelos próprios navegadores é muito complicado para replicar. Talvez você possa restringir seus requisitos, por exemplo, "considerar apenas input, buttone textareamarcar e ignorar o tabindexatributo".
Wladimir Palant
Precisamos ver seu .newElementByTabIndexcódigo porque ele não está funcionando.
0x499602D2
2
Então, novamente, talvez a restrição a tags específicas seja desnecessária - pode-se verificar se o focus()método existe.
Wladimir Palant
1
@David Essa é a função que não existe, por isso minha dúvida. : D
JadziaMD

Respostas:

24

Sem jquery: em primeiro lugar, em seus elementos tabuláveis, adicione class="tabable"isso para que possamos selecioná-los mais tarde. (Não se esqueça do prefixo do seletor de classe "." No código abaixo)

var lastTabIndex = 10;
function OnFocusOut()
{
    var currentElement = $get(currentElementId); // ID set by OnFOcusIn
    var curIndex = currentElement.tabIndex; //get current elements tab index
    if(curIndex == lastTabIndex) { //if we are on the last tabindex, go back to the beginning
        curIndex = 0;
    }
    var tabbables = document.querySelectorAll(".tabable"); //get all tabable elements
    for(var i=0; i<tabbables.length; i++) { //loop through each element
        if(tabbables[i].tabIndex == (curIndex+1)) { //check the tabindex to see if it's the element we want
            tabbables[i].focus(); //if it's the one we want, focus it and exit the loop
            break;
        }
    }
}
Brian Glaz
fonte
16
Uma solução sem ter que adicionar um nome a cada elemento (já que muitos podem fazer isso, se possível) seria o ideal.
JadziaMD
3
ok, isso é para um formulário? Se todos os elementos que você quer são elementos de entrada, você pode substituir a linha var tabbables = document.getElementsByName("tabable");com var tabbables = document.getElementsByTagName("input");em vez
Brian Glaz
var tabbables = document.querySelectorAll("input, textarea, button")// IE8 +, obtém uma referência para todos os tabbables sem modificar seu HTML.
Greg
2
class = "tabbable" em vez de usar o atributo name
Chris F Carroll
4
Observe que, ao usar o flexbox, a ordem dos elementos é diferente no DOM e visualmente no navegador. Apenas escolher o próximo elemento tabulável não funciona quando você altera a ordem dos elementos usando o flexbox.
Haneev
75

Nunca implementei isso, mas já analisei um problema semelhante e aqui está o que eu tentaria.

Tente isto primeiro

Primeiro, veria se você poderia simplesmente disparar um keypressevento para a tecla Tab no elemento que está em foco atualmente. Pode haver uma maneira diferente de fazer isso para navegadores diferentes.

Se isso não funcionar, você terá que trabalhar mais ...

Fazendo referência à implementação do jQuery, você deve:

  1. Ouça Tab e Shift + Tab
  2. Saiba quais elementos podem ser tabulados
  3. Entenda como funciona a ordem das guias

1. Ouça Tab e Shift + Tab

Ouvir Tab e Shift + Tab provavelmente são bem abordadas em outros lugares na web, então vou pular essa parte.

2. Saiba quais elementos podem ser tabulados

Saber quais elementos podem ser tabulados é mais complicado. Basicamente, um elemento pode ser tabulado se puder ser focalizado e não tiver o atributo tabindex="-1"definido. Portanto, devemos perguntar quais elementos são focalizáveis. Os seguintes elementos podem ser focalizados:

  • input, select, textarea, button, E objectelementos que não são desativados.
  • ae areaelementos que possuem um hrefou têm um valor numérico para tabindexconjunto.
  • qualquer elemento que tenha um valor numérico para tabindexdefinir.

Além disso, um elemento pode ser focalizado apenas se:

  • Nenhum de seus ancestrais é display: none.
  • O valor calculado de visibilityé visible. Isso significa que o ancestral mais próximo a ser visibilitydefinido deve ter um valor de visible. Se nenhum ancestral foi visibilitydefinido, o valor calculado é visible.

Mais detalhes estão em outra resposta do Stack Overflow .

3. Entenda como funciona a ordem das guias

A ordem de tabulação dos elementos em um documento é controlada pelo tabindexatributo. Se nenhum valor for definido, o tabindexé eficaz 0.

A tabindexordem do documento é: 1, 2, 3,…, 0.

Inicialmente, quando o bodyelemento (ou nenhum elemento) tem foco, o primeiro elemento na ordem de tabulação é o menor diferente de zero tabindex. Se vários elementos tiverem o mesmo tabindex, então você vai na ordem do documento até chegar ao último elemento com isso tabindex. Então você passa para o próximo nível mais baixo tabindexe o processo continua. Finalmente, termine com esses elementos com um zero (ou vazio) tabindex.

Chris Calo
fonte
37

Aqui está algo que construí para este propósito:

focusNextElement: function () {
    //add all elements we want to include in our selection
    var focussableElements = 'a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])';
    if (document.activeElement && document.activeElement.form) {
        var focussable = Array.prototype.filter.call(document.activeElement.form.querySelectorAll(focussableElements),
        function (element) {
            //check for visibility while always include the current activeElement 
            return element.offsetWidth > 0 || element.offsetHeight > 0 || element === document.activeElement
        });
        var index = focussable.indexOf(document.activeElement);
        if(index > -1) {
           var nextElement = focussable[index + 1] || focussable[0];
           nextElement.focus();
        }                    
    }
}

Recursos:

  • conjunto configurável de elementos focalizáveis
  • não é necessário jQuery
  • funciona em todos os navegadores modernos
  • rápido e leve
Mx.
fonte
2
Esta é a solução mais eficaz e com mais recursos. Obrigado! Aqui está meu script de trabalho completo: stackoverflow.com/a/40686327/1589669
eapo
Eu adicionei um snippet abaixo para incluir a classificação por TabIndex explícito focussable.sort (sort_by_TabIndex)
DavB.cs
1
Ao melhor ! Ele deve ser tão complexo: o nextElementSiblingpodem não ser focusable, o próximo focalizável pode não ser um irmão.
Tinmarino
Boa abordagem, mas deve permitir qualquer entrada que não seja do tipo hiddene também cobrir textareae select.
Lucero
23

Eu criei um plugin jQuery simples que faz exatamente isso. Ele usa o seletor ': tabbable' do jQuery UI para encontrar o próximo elemento 'tabbable' e o seleciona.

Exemplo de uso:

// Simulate tab key when element is clicked 
$('.myElement').bind('click', function(event){
    $.tabNext();
    return false;
});
Mark Lagendijk
fonte
8

O cerne da resposta está em encontrar o próximo elemento:

  function findNextTabStop(el) {
    var universe = document.querySelectorAll('input, button, select, textarea, a[href]');
    var list = Array.prototype.filter.call(universe, function(item) {return item.tabIndex >= "0"});
    var index = list.indexOf(el);
    return list[index + 1] || list[0];
  }

Uso:

var nextEl = findNextTabStop(element);
nextEl.focus();

Observe que não me importo em priorizar tabIndex.

André Werlang
fonte
3
E se a ordem do tabindex for contra a ordem do documento? Acho que a matriz deve ser classificada por número de índice de tabulação e, em seguida, por ordem de documento
Chris F Carroll
Sim, isso seria mais "compatível com as especificações". Não tenho certeza sobre casos extremos, sobre elementos pais, etc
André Werlang
E se um item que não é uma dessas tags tiver um atributo tabindex?
Matt Pennington
1
@MattPennington Seria ignorado. O filtro é (uma tentativa) de agilizar a busca, fique à vontade para se adaptar.
André Werlang
3

Conforme mencionado em um comentário acima, não acho que nenhum navegador exponha informações de ordem de guias. Aqui está uma aproximação simplificada do que o navegador faz para obter o próximo elemento na ordem das guias:

var allowedTags = {input: true, textarea: true, button: true};

var walker = document.createTreeWalker(
  document.body,
  NodeFilter.SHOW_ELEMENT,
  {
    acceptNode: function(node)
    {
      if (node.localName in allowedTags)
        return NodeFilter.FILTER_ACCEPT;
      else
        NodeFilter.FILTER_SKIP;
    }
  },
  false
);
walker.currentNode = currentElement;
if (!walker.nextNode())
{
  // Restart search from the start of the document
  walker.currentNode = walker.root;
  walker.nextNode();
}
if (walker.currentNode && walker.currentNode != walker.root)
  walker.currentNode.focus();

Isso considera apenas algumas tags e ignora o tabindexatributo, mas pode ser o suficiente, dependendo do que você está tentando alcançar.

Wladimir Palant
fonte
3

Parece que você pode verificar a tabIndexpropriedade de um elemento para determinar se ele pode ser focalizado. Um elemento que não pode ser focalizado tem um valor tabindexde "-1".

Então você só precisa conhecer as regras para paradas de tabulação:

  • tabIndex="1" tem o maior sacerdócio.
  • tabIndex="2" tem a próxima prioridade mais alta.
  • tabIndex="3" é o próximo e assim por diante.
  • tabIndex="0" (ou tabulável por padrão) tem a prioridade mais baixa.
  • tabIndex="-1" (ou não tabulável por padrão) não atua como uma parada de tabulação.
  • Para dois elementos que têm o mesmo tabIndex, aquele que aparece primeiro no DOM tem a prioridade mais alta.

Aqui está um exemplo de como construir a lista de paradas de tabulação, em sequência, usando Javascript puro:

function getTabStops(o, a, el) {
    // Check if this element is a tab stop
    if (el.tabIndex > 0) {
        if (o[el.tabIndex]) {
            o[el.tabIndex].push(el);
        } else {
            o[el.tabIndex] = [el];
        }
    } else if (el.tabIndex === 0) {
        // Tab index "0" comes last so we accumulate it seperately
        a.push(el);
    }
    // Check if children are tab stops
    for (var i = 0, l = el.children.length; i < l; i++) {
        getTabStops(o, a, el.children[i]);
    }
}

var o = [],
    a = [],
    stops = [],
    active = document.activeElement;

getTabStops(o, a, document.body);

// Use simple loops for maximum browser support
for (var i = 0, l = o.length; i < l; i++) {
    if (o[i]) {
        for (var j = 0, m = o[i].length; j < m; j++) {
            stops.push(o[i][j]);
        }
    }
}
for (var i = 0, l = a.length; i < l; i++) {
    stops.push(a[i]);
}

Primeiro percorremos o DOM, coletando todas as paradas de tabulação em sequência com seu índice. Em seguida, montamos a lista final. Observe que adicionamos os itens com tabIndex="0"no final da lista, após os itens com tabIndex1, 2, 3, etc.

Para obter um exemplo funcional, onde você pode navegar usando a tecla "enter", verifique este violino .

chowey
fonte
2

Tabbable é um pequeno pacote JS que fornece uma lista de todos os elementos tabuláveis em ordem de tabulação . Assim, você pode encontrar seu elemento dentro dessa lista e, em seguida, focar na próxima entrada da lista.

O pacote trata corretamente os casos extremos complicados mencionados em outras respostas (por exemplo, nenhum ancestral pode ser display: none). E não depende do jQuery!

No momento da redação deste artigo (versão 1.1.1), ele tinha as ressalvas de que não suportava o IE8 e que os erros do navegador o impediam de funcionar contenteditablecorretamente.

Nate Sullivan
fonte
2
function focusNextElement(){
  var focusable = [].slice.call(document.querySelectorAll("a, button, input, select, textarea, [tabindex], [contenteditable]")).filter(function($e){
    if($e.disabled || ($e.getAttribute("tabindex") && parseInt($e.getAttribute("tabindex"))<0)) return false;
    return true;
  }).sort(function($a, $b){
    return (parseFloat($a.getAttribute("tabindex") || 99999) || 99999) - (parseFloat($b.getAttribute("tabindex") || 99999) || 99999);
  });
  var focusIndex = focusable.indexOf(document.activeElement);
  if(focusable[focusIndex+1]) focusable[focusIndex+1].focus();
};
Dustin Poissant
fonte
1

Esta é minha primeira postagem no SO, então não tenho reputação suficiente para comentar a resposta aceita, mas tive que modificar o código para o seguinte:

export function focusNextElement () {
  //add all elements we want to include in our selection
  const focussableElements = 
    'a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled])'
  if (document.activeElement && document.activeElement.form) {
      var focussable = Array.prototype.filter.call(
        document.activeElement.form.querySelectorAll(focussableElements),
      function (element) {
          // if element has tabindex = -1, it is not focussable
          if ( element.hasAttribute('tabindex') && element.tabIndex === -1 ){
            return false
          }
          //check for visibility while always include the current activeElement 
          return (element.offsetWidth > 0 || element.offsetHeight > 0 || 
            element === document.activeElement)
      });
      console.log(focussable)
      var index = focussable.indexOf(document.activeElement);
      if(index > -1) {
         var nextElement = focussable[index + 1] || focussable[0];
         console.log(nextElement)
         nextElement.focus()
      }                    
  }
}

A mudança de var para constante não é crítica. A principal mudança é que nos livramos do seletor que verifica tabindex! = "-1". Posteriormente, se o elemento tiver o atributo tabindex E for definido como "-1", NÃO o consideramos focalizável.

O motivo pelo qual precisei mudar isso foi porque ao adicionar tabindex = "- 1" a um <input>, esse elemento ainda foi considerado focalizável porque corresponde ao seletor "input [type = text]: not ([disabled])". Minha mudança é equivalente a "se formos uma entrada de texto não desabilitada e tivermos um atributo tabIndex, e o valor desse atributo for -1, então não devemos ser considerados focalizáveis.

Eu acredito que quando o autor da resposta aceita editou sua resposta para explicar o atributo tabIndex, ele não o fez corretamente. Por favor me avise se este não for o caso

BrushyAmoeba
fonte
1

Existe a propriedade tabindex que pode ser configurada no componente. Ele especifica em qual ordem os componentes de entrada devem ser iterados ao selecionar um e pressionar a guia. Valores acima de 0 são reservados para navegação personalizada, 0 é "em ordem natural" (portanto, se comportaria de maneira diferente se definido para o primeiro elemento), -1 significa não focalizável no teclado:

<!-- navigate with tab key: -->
<input tabindex="1" type="text"/>
<input tabindex="2" type="text"/>

Ele também pode ser definido para algo diferente dos campos de entrada de texto, mas não é muito óbvio o que faria lá, se é que faria alguma coisa. Mesmo se a navegação funcionar, talvez seja melhor usar "ordem natural" para qualquer outra coisa do que os elementos de entrada do usuário muito óbvios.

Não, você não precisa de JQuery ou qualquer script para oferecer suporte a esse caminho de navegação personalizado. Você pode implementá-lo no lado do servidor sem qualquer suporte a JavaScript. Por outro lado, a propriedade também funciona bem no framework React, mas não a exige.

Audrius Meskauskas
fonte
0

Aqui está uma versão mais completa de como focar no próximo elemento. Ele segue as diretrizes das especificações e classifica a lista de elementos corretamente usando tabindex. Além disso, uma variável reversa é definida se você deseja obter o elemento anterior.

function focusNextElement( reverse, activeElem ) {
  /*check if an element is defined or use activeElement*/
  activeElem = activeElem instanceof HTMLElement ? activeElem : document.activeElement;

  let queryString = [
      'a:not([disabled]):not([tabindex="-1"])',
      'button:not([disabled]):not([tabindex="-1"])',
      'input:not([disabled]):not([tabindex="-1"])',
      'select:not([disabled]):not([tabindex="-1"])',
      '[tabindex]:not([disabled]):not([tabindex="-1"])'
      /* add custom queries here */
    ].join(','),
    queryResult = Array.prototype.filter.call(document.querySelectorAll(queryString), elem => {
      /*check for visibility while always include the current activeElement*/
      return elem.offsetWidth > 0 || elem.offsetHeight > 0 || elem === activeElem;
    }),
    indexedList = queryResult.slice().filter(elem => {
      /* filter out all indexes not greater than 0 */
      return elem.tabIndex == 0 || elem.tabIndex == -1 ? false : true;
    }).sort((a, b) => {
      /* sort the array by index from smallest to largest */
      return a.tabIndex != 0 && b.tabIndex != 0 
        ? (a.tabIndex < b.tabIndex ? -1 : b.tabIndex < a.tabIndex ? 1 : 0) 
        : a.tabIndex != 0 ? -1 : b.tabIndex != 0 ? 1 : 0;
    }),
    focusable = [].concat(indexedList, queryResult.filter(elem => {
      /* filter out all indexes above 0 */
      return elem.tabIndex == 0 || elem.tabIndex == -1 ? true : false;
    }));

  /* if reverse is true return the previous focusable element
     if reverse is false return the next focusable element */
  return reverse ? (focusable[focusable.indexOf(activeElem) - 1] || focusable[focusable.length - 1]) 
    : (focusable[focusable.indexOf(activeElem) + 1] || focusable[0]);
}
Svarlitskiy
fonte
0

Este é um potencial aprimoramento para a grande solução que o @Kano e o @Mx ofereceram. Se você deseja preservar a ordem TabIndex, adicione esta classificação no meio:

// Sort by explicit Tab Index, if any
var sort_by_TabIndex = function (elementA, elementB) {
    let a = elementA.tabIndex || 1;
    let b = elementB.tabIndex || 1;
    if (a < b) { return -1; }
    if (a > b) { return 1; }
    return 0;
}
focussable.sort(sort_by_TabIndex);
DavB.cs
fonte
0

Você pode chamar isso de:

Aba:

$.tabNext();

Shift + Tab:

$.tabPrev();

<!DOCTYPE html>
<html>
<body>
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>
<script>
(function($){
	'use strict';

	/**
	 * Focusses the next :focusable element. Elements with tabindex=-1 are focusable, but not tabable.
	 * Does not take into account that the taborder might be different as the :tabbable elements order
	 * (which happens when using tabindexes which are greater than 0).
	 */
	$.focusNext = function(){
		selectNextTabbableOrFocusable(':focusable');
	};

	/**
	 * Focusses the previous :focusable element. Elements with tabindex=-1 are focusable, but not tabable.
	 * Does not take into account that the taborder might be different as the :tabbable elements order
	 * (which happens when using tabindexes which are greater than 0).
	 */
	$.focusPrev = function(){
		selectPrevTabbableOrFocusable(':focusable');
	};

	/**
	 * Focusses the next :tabable element.
	 * Does not take into account that the taborder might be different as the :tabbable elements order
	 * (which happens when using tabindexes which are greater than 0).
	 */
	$.tabNext = function(){
		selectNextTabbableOrFocusable(':tabbable');
	};

	/**
	 * Focusses the previous :tabbable element
	 * Does not take into account that the taborder might be different as the :tabbable elements order
	 * (which happens when using tabindexes which are greater than 0).
	 */
	$.tabPrev = function(){
		selectPrevTabbableOrFocusable(':tabbable');
	};

    function tabIndexToInt(tabIndex){
        var tabIndexInded = parseInt(tabIndex);
        if(isNaN(tabIndexInded)){
            return 0;
        }else{
            return tabIndexInded;
        }
    }

    function getTabIndexList(elements){
        var list = [];
        for(var i=0; i<elements.length; i++){
            list.push(tabIndexToInt(elements.eq(i).attr("tabIndex")));
        }
        return list;
    }

    function selectNextTabbableOrFocusable(selector){
        var selectables = $(selector);
        var current = $(':focus');

        // Find same TabIndex of remainder element
        var currentIndex = selectables.index(current);
        var currentTabIndex = tabIndexToInt(current.attr("tabIndex"));
        for(var i=currentIndex+1; i<selectables.length; i++){
            if(tabIndexToInt(selectables.eq(i).attr("tabIndex")) === currentTabIndex){
                selectables.eq(i).focus();
                return;
            }
        }

        // Check is last TabIndex
        var tabIndexList = getTabIndexList(selectables).sort(function(a, b){return a-b});
        if(currentTabIndex === tabIndexList[tabIndexList.length-1]){
            currentTabIndex = -1;// Starting from 0
        }

        // Find next TabIndex of all element
        var nextTabIndex = tabIndexList.find(function(element){return currentTabIndex<element;});
        for(var i=0; i<selectables.length; i++){
            if(tabIndexToInt(selectables.eq(i).attr("tabIndex")) === nextTabIndex){
                selectables.eq(i).focus();
                return;
            }
        }
    }

	function selectPrevTabbableOrFocusable(selector){
		var selectables = $(selector);
		var current = $(':focus');

		// Find same TabIndex of remainder element
        var currentIndex = selectables.index(current);
        var currentTabIndex = tabIndexToInt(current.attr("tabIndex"));
        for(var i=currentIndex-1; 0<=i; i--){
            if(tabIndexToInt(selectables.eq(i).attr("tabIndex")) === currentTabIndex){
                selectables.eq(i).focus();
                return;
            }
        }

        // Check is last TabIndex
        var tabIndexList = getTabIndexList(selectables).sort(function(a, b){return b-a});
        if(currentTabIndex <= tabIndexList[tabIndexList.length-1]){
            currentTabIndex = tabIndexList[0]+1;// Starting from max
        }

        // Find prev TabIndex of all element
        var prevTabIndex = tabIndexList.find(function(element){return element<currentTabIndex;});
        for(var i=selectables.length-1; 0<=i; i--){
            if(tabIndexToInt(selectables.eq(i).attr("tabIndex")) === prevTabIndex){
                selectables.eq(i).focus();
                return;
            }
        }
	}

	/**
	 * :focusable and :tabbable, both taken from jQuery UI Core
	 */
	$.extend($.expr[ ':' ], {
		data: $.expr.createPseudo ?
			$.expr.createPseudo(function(dataName){
				return function(elem){
					return !!$.data(elem, dataName);
				};
			}) :
			// support: jQuery <1.8
			function(elem, i, match){
				return !!$.data(elem, match[ 3 ]);
			},

		focusable: function(element){
			return focusable(element, !isNaN($.attr(element, 'tabindex')));
		},

		tabbable: function(element){
			var tabIndex = $.attr(element, 'tabindex'),
				isTabIndexNaN = isNaN(tabIndex);
			return ( isTabIndexNaN || tabIndex >= 0 ) && focusable(element, !isTabIndexNaN);
		}
	});

	/**
	 * focussable function, taken from jQuery UI Core
	 * @param element
	 * @returns {*}
	 */
	function focusable(element){
		var map, mapName, img,
			nodeName = element.nodeName.toLowerCase(),
			isTabIndexNotNaN = !isNaN($.attr(element, 'tabindex'));
		if('area' === nodeName){
			map = element.parentNode;
			mapName = map.name;
			if(!element.href || !mapName || map.nodeName.toLowerCase() !== 'map'){
				return false;
			}
			img = $('img[usemap=#' + mapName + ']')[0];
			return !!img && visible(img);
		}
		return ( /^(input|select|textarea|button|object)$/.test(nodeName) ?
			!element.disabled :
			'a' === nodeName ?
				element.href || isTabIndexNotNaN :
				isTabIndexNotNaN) &&
			// the element and all of its ancestors must be visible
			visible(element);

		function visible(element){
			return $.expr.filters.visible(element) && !$(element).parents().addBack().filter(function(){
				return $.css(this, 'visibility') === 'hidden';
			}).length;
		}
	}
})(jQuery);
</script>

<a tabindex="5">5</a><br>
<a tabindex="20">20</a><br>
<a tabindex="3">3</a><br>
<a tabindex="7">7</a><br>
<a tabindex="20">20</a><br>
<a tabindex="0">0</a><br>

<script>
var timer;
function tab(){
    window.clearTimeout(timer)
    timer = window.setInterval(function(){$.tabNext();}, 1000);
}
function shiftTab(){
    window.clearTimeout(timer)
    timer = window.setInterval(function(){$.tabPrev();}, 1000);
}
</script>
<button tabindex="-1" onclick="tab()">Tab</button>
<button tabindex="-1" onclick="shiftTab()">Shift+Tab</button>

</body>
</html>

I modificar jquery.tabbable plugin para completo.

iHad 169
fonte
Duplicação desta resposta , que foi postada pelo criador daquele plugin jQuery.
mbomb007
0

Necromante.
Eu tenho um buch de 0-tabIndexes, que eu queria navegar pelo teclado.
Como, nesse caso, apenas a ORDEM dos elementos importava, fiz isso usandodocument.createTreeWalker

Portanto, primeiro você cria o filtro (você deseja apenas elementos [visíveis], que têm um atributo "tabIndex" com um valor NUMERICAL.

Em seguida, você define o nó raiz, além do qual não deseja pesquisar. No meu caso, this.m_treeé um elemento ul contendo uma árvore toggable. Se você quiser o documento inteiro, basta substituir this.m_treepordocument.documentElement .

Em seguida, você define o nó atual para o elemento ativo atual:

ni.currentNode = el; // el = document.activeElement

Então você retorna ni.nextNode()ou ni.previousNode().

Observação:
isso NÃO retornará as guias na ordem correta se você tiver tabIndices! = 0 e a ordem do elemento NÃO for a ordem tabIndex. No caso de tabIndex = 0, o tabOrder é sempre a ordem dos elementos, por isso funciona (nesse caso).

protected createFilter(fn?: (node: Node) => number): NodeFilter
{
    // Accept all currently filtered elements.
    function acceptNode(node: Node): number 
    {
        return NodeFilter.FILTER_ACCEPT;
    }

    if (fn == null)
        fn = acceptNode;


    // Work around Internet Explorer wanting a function instead of an object.
    // IE also *requires* this argument where other browsers don't.
    const safeFilter: NodeFilter = <NodeFilter><any>fn;
    (<any>safeFilter).acceptNode = fn;

    return safeFilter;
}



protected createTabbingFilter(): NodeFilter
{
    // Accept all currently filtered elements.
    function acceptNode(node: Node): number 
    {
        if (!node)
            return NodeFilter.FILTER_REJECT;

        if (node.nodeType !== Node.ELEMENT_NODE)
            return NodeFilter.FILTER_REJECT;

        if (window.getComputedStyle(<Element>node).display === "none")
            return NodeFilter.FILTER_REJECT;

        // "tabIndex": "0"
        if (!(<Element>node).hasAttribute("tabIndex"))
            return NodeFilter.FILTER_SKIP;

        let tabIndex = parseInt((<Element>node).getAttribute("tabIndex"), 10);
        if (!tabIndex || isNaN(tabIndex) || !isFinite(tabIndex))
            return NodeFilter.FILTER_SKIP;

        // if ((<Element>node).tagName !== "LI") return NodeFilter.FILTER_SKIP;

        return NodeFilter.FILTER_ACCEPT;
    }

    return this.createFilter(acceptNode);
}


protected getNextTab(el: HTMLElement): HTMLElement
{
    let currentNode: Node;
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/createNodeIterator
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/createTreeWalker

    // let ni = document.createNodeIterator(el, NodeFilter.SHOW_ELEMENT);
    // let ni = document.createTreeWalker(this.m_tree, NodeFilter.SHOW_ELEMENT);
    let ni = document.createTreeWalker(this.m_tree, NodeFilter.SHOW_ELEMENT, this.createTabbingFilter(), false);

    ni.currentNode = el;

    while (currentNode = ni.nextNode())
    {
        return <HTMLElement>currentNode;
    }

    return el;
}


protected getPreviousTab(el: HTMLElement): HTMLElement
{
    let currentNode: Node;
    let ni = document.createTreeWalker(this.m_tree, NodeFilter.SHOW_ELEMENT, this.createTabbingFilter(), false);
    ni.currentNode = el;

    while (currentNode = ni.previousNode())
    {
        return <HTMLElement>currentNode;
    }

    return el;
}

Observe que o loop while

while (currentNode = ni.nextNode())
{
    // Additional checks here
    // if(condition) return currentNode;
    // else the loop continues;
    return <HTMLElement>currentNode; // everything is already filtered down to what we need here
}

está lá apenas se você desejar, se tiver critérios adicionais que não podem ser filtrados no filtro passado para createTreeWalker.

Observe que este é o TypeScript, você precisa remover todos os tokens atrás dos dois pontos (:) e entre colchetes angulares (<>), por exemplo, <Element>ou:(node: Node) => number para obter um JavaScript válido.

Aqui como um serviço, o JS transpilado:

"use strict";
function createFilter(fn) {
    // Accept all currently filtered elements.
    function acceptNode(node) {
        return NodeFilter.FILTER_ACCEPT;
    }
    if (fn == null)
        fn = acceptNode;
    // Work around Internet Explorer wanting a function instead of an object.
    // IE also *requires* this argument where other browsers don't.
    const safeFilter = fn;
    safeFilter.acceptNode = fn;
    return safeFilter;
}
function createTabbingFilter() {
    // Accept all currently filtered elements.
    function acceptNode(node) {
        if (!node)
            return NodeFilter.FILTER_REJECT;
        if (node.nodeType !== Node.ELEMENT_NODE)
            return NodeFilter.FILTER_REJECT;
        if (window.getComputedStyle(node).display === "none")
            return NodeFilter.FILTER_REJECT;
        // "tabIndex": "0"
        if (!node.hasAttribute("tabIndex"))
            return NodeFilter.FILTER_SKIP;
        let tabIndex = parseInt(node.getAttribute("tabIndex"), 10);
        if (!tabIndex || isNaN(tabIndex) || !isFinite(tabIndex))
            return NodeFilter.FILTER_SKIP;
        // if ((<Element>node).tagName !== "LI") return NodeFilter.FILTER_SKIP;
        return NodeFilter.FILTER_ACCEPT;
    }
    return createFilter(acceptNode);
}
function getNextTab(el) {
    let currentNode;
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/createNodeIterator
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/createTreeWalker
    // let ni = document.createNodeIterator(el, NodeFilter.SHOW_ELEMENT);
    // let ni = document.createTreeWalker(this.m_tree, NodeFilter.SHOW_ELEMENT);
    let ni = document.createTreeWalker(document.documentElement, NodeFilter.SHOW_ELEMENT, createTabbingFilter(), false);
    ni.currentNode = el;
    while (currentNode = ni.nextNode()) {
        return currentNode;
    }
    return el;
}
function getPreviousTab(el) {
    let currentNode;
    let ni = document.createTreeWalker(document.documentElement, NodeFilter.SHOW_ELEMENT, createTabbingFilter(), false);
    ni.currentNode = el;
    while (currentNode = ni.previousNode()) {
        return currentNode;
    }
    return el;
}
Stefan Steiger
fonte
-1

Você especificou seus próprios valores tabIndex para cada elemento que deseja percorrer? em caso afirmativo, você pode tentar isto:

var lasTabIndex = 10; //Set this to the highest tabIndex you have
function OnFocusOut()
{
    var currentElement = $get(currentElementId); // ID set by OnFocusIn 

    var curIndex = $(currentElement).attr('tabindex'); //get the tab index of the current element
    if(curIndex == lastTabIndex) { //if we are on the last tabindex, go back to the beginning
        curIndex = 0;
    }
    $('[tabindex=' + (curIndex + 1) + ']').focus(); //set focus on the element that has a tab index one greater than the current tab index
}

Você está usando jquery, certo?

Brian Glaz
fonte
Não estamos usando JQuery, pois ele quebra o aplicativo. : /
JadziaMD
Ok, acho que posso reescrever sem usar jquery, me dê um minuto
Brian Glaz
Cada elemento em que estamos interessados ​​tem seus valores de índice de tabulação definidos.
JadziaMD
-1

Eu verifiquei as soluções acima e as achei bastante extensas. Isso pode ser realizado com apenas uma linha de código:

currentElement.nextElementSibling.focus();

ou

currentElement.previousElementSibling.focus();

aqui, currentElement pode ser qualquer, por exemplo, document.activeElement ou this se o elemento atual estiver no contexto da função.

Acompanhei eventos tab e shift-tab com evento keydown

let cursorDirection = ''
$(document).keydown(function (e) {
    let key = e.which || e.keyCode;
    if (e.shiftKey) {
        //does not matter if user has pressed tab key or not.
        //If it matters for you then compare it with 9
        cursorDirection = 'prev';
    }
    else if (key == 9) {
        //if tab key is pressed then move next.
        cursorDirection = 'next';
    }
    else {
        cursorDirection == '';
    }
});

depois de ter a direção do cursor, você pode usar os métodos nextElementSibling.focusoupreviousElementSibling.focus

Manpreet Singh Dhillon
fonte
1
Infelizmente, a ordem do irmão não está relacionada à ordem da guia, exceto por uma feliz coincidência, e não há garantia de que o irmão anterior / próximo será focalizado.
Lawrence Dol