Why does arrow key navigation returns the wrong value when reversing direction?

Viewed 86

I am trying to create a list navigation controlled by the arrow keys, using the keydown event. Selection works as expected until the user reverses direction. For example, if the user is on item 3 and hits the down arrow, it goes to item 4. However, if the user then hits the up arrow, instead of going back to item 3, it proceeds to item 5. Subsequent hits of the up arrow behave as expected. Only when changing direction does the first key press produces incorrect results.

Can anyone tell me why this is?

function keyListener() {
  "use strict";
  let index = 1;

  document
    .querySelector(".select__element")
    .addEventListener("keydown", (e) => {
      let keyValue = e.key;
      // ****************************** ARROW UP
      if (keyValue === "ArrowUp") {
        console.log("Arrow Up Pressed");

        const selectItem = document.querySelectorAll(".select__item");
        const length = selectItem.length - 1;

        // dropdown the list
        document
          .querySelector(".select__list")
          .classList.add("select__list--visible");

        let indexPrev = index - 1;
        if (indexPrev < 0) {
          indexPrev = length;
        }

        let indexNext = index + 1;
        if (indexNext > length) {
          indexNext = 0;
        }
        // test the bounds
        if (index < 0) {
          index = length;
          indexPrev = length - 1;
          indexNext = 0;
        }

        if (index > length) {
          index = 0;
          indexPrev = index + 1;
          indexNext = length;
        }

        console.log(
          "Up Arrow: ",
          indexPrev,
          index,
          indexNext,
          "Values should decrease"
        );
        // select the item
        selectItem[index].classList.add("select__item--selected");

        selectItem[indexPrev].classList.remove("select__item--selected");
        selectItem[indexNext].classList.remove("select__item--selected");

        index--;
      }

      // ****************************** ARROW DOWN
      if (keyValue === "ArrowDown") {
        console.log("Arrow Down Pressed");

        const selectItem = document.querySelectorAll(".select__item");
        const length = selectItem.length - 1;

        // dropdown the list
        document
          .querySelector(".select__list")
          .classList.add("select__list--visible");

        let indexPrev = index - 1;
        if (indexPrev < 0) {
          indexPrev = length;
        }

        let indexNext = index + 1;
        if (indexNext > length) {
          indexNext = 0;
        }

        // test the bounds
        if (index < 0) {
          index = length;
          indexPrev = 0;
          indexNext = length - 1;
        }

        if (index > length) {
          index = 0;
          indexPrev = length;
          indexNext = index + 1;
        }

        console.log(
          "Down Arrow: ",
          indexPrev,
          index,
          indexNext,
          "Values should increase"
        );

        //select the item
        selectItem[index].classList.add("select__item--selected");

        selectItem[indexPrev].classList.remove("select__item--selected");
        selectItem[indexNext].classList.remove("select__item--selected");

        index++;
      }

    });


}

keyListener();
.select__item--selected,
.select__item:hover {
  background: #25A0DA;
  color: #fff;
}

.select__root {
  background-color: lightpink;
}
<div class="m-wrapper">
  <div id="Select-Pages" class="select">
    <div class="select__element" tabindex="0">
      <div class="select__root">Select an item...</div>
      <ul class="select__list">
        <li class="select__item select__item--selected">Item-1</li>
        <li class="select__item">Item-2</li>
        <li class="select__item">Item-3</li>
        <li class="select__item">Item-4</li>
        <li class="select__item">Item-5</li>
        <li class="select__item">Item-6</li>
      </ul>
    </div>
  </div>

1 Answers

The index variable is incremented and decremented after class lists are modified. As a result, highlighting appears out of sync with the selected index. I suggest updating the variable before the class lists, as shown below.

Also, since selectItem array indices start with zero, I recommend defining index = 0.

function keyListener() {
  "use strict";

  const selectItem = document.querySelectorAll(".select__item");
  const length = selectItem.length;
  let index = 0;

  document
    .querySelector(".select__element")
    .addEventListener("keydown", (e) => {

      let keyValue = e.key;

      // store last index
      let lastIndex = index;

      // decrement / increment index
      if (keyValue === "ArrowUp") {
        index--;
      }
      if (keyValue === "ArrowDown") {
        index++;
      }

      // keep index within range
      index = (index + length) % length;

      // deselect last item
      selectItem[lastIndex].classList.remove("select__item--selected");

      // select current item
      selectItem[index].classList.add("select__item--selected");

    });

}

keyListener();
.select__item--selected,
.select__item:hover {
  background: #25A0DA;
  color: #fff;
}

.select__root {
  background-color: lightpink;
}
<div id="Select-Pages" class="select">
  <div class="select__element" tabindex="0">
    <div class="select__root">Select an item...</div>
    <ul class="select__list">
      <li class="select__item select__item--selected">Item-1</li>
      <li class="select__item">Item-2</li>
      <li class="select__item">Item-3</li>
      <li class="select__item">Item-4</li>
      <li class="select__item">Item-5</li>
      <li class="select__item">Item-6</li>
    </ul>
  </div>
</div>

Related