IndigoWhiteGun

by SimonCurtis

HTML

<div>
  <button id="toggle-run" tabindex="-1">Start</button>
  <input id="element-5" type="text">
  <input id="element-1" type="text" tabindex="1">
  <input id="element-2" type="text" tabindex="2">
  <input id="element-4" type="text">
</div>

<!-- The focus order should be [element-2, element-1, element-5, element-4] -->

CSS

input:focus {
  background: red;
}

JavaScript

// Find all the elements that have a tabindex set, I would keep this file scoped for performance sake
const tabElements = Array.from(document
  .querySelectorAll('a, button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])'))
  .reduce((prev, next) => {  
      return next.tabIndex > 0
        ? [[...prev[0], next], prev[1]]
        : [prev[0],[...prev[1], next]];
      }, [[], []])
  .reduce((prev, next) => ([...prev, ...next.sort((a, b) => a.tabIndex === 0 ? -1 : a.tabIndex > b.tabIndex ? -1 : 1)]), []);

const expectedOrder = ["element-2", "element-1", "element-5", "element-4"];
console.log("Expected order correct?", tabElements.every((element, index) => expectedOrder[index] === element.id))

// Method to find the next element to focus and change the focus to that element
const DispatchTab = () => {
  const currentIndex = tabElements.findIndex(e => e === document.activeElement);

  // get the next element in the list, "%" will loop around to 0
  const nextIndex = (currentIndex + 1) % tabElements.length;
  tabElements[nextIndex].focus();
}

// Interval just to call above method
let interval = {};

document.getElementById("toggle-run").addEventListener("click", e => {
  if (e.target.innerText === "Stop"){
  	clearInterval(interval);
    e.target.innerText = "Start";
  } 
  else {
  	interval = setInterval(DispatchTab, 1000);
    e.target.innerText = "Stop";
  }
});