Simulating a Tab Key Press using Plain JavaScript Supporting Shadow DOM
Asked Answered
H

1

15

Note: Existing questions exists here and elsewhere but they are all jQuery specific and there is no canonical answer covering plain JavaScript, including support for web components.

I'd like to simulate a tab key press so that the focus is shifted to the next element in the tab order. The next element could be any HTML focusable HTML element or an element with tabindex="0". This also needs to work where some descendant HTML elements could be custom web components with shadow DOM's.

The potential solution could be to fire a key down event or iterating over descendant nodes looking for one that is focusable.

Hessney answered 30/9, 2022 at 11:2 Comment(1)
I've tried the solutions in the post linked in particular and some derivatives. Also tried selecting the first descendant with a tabindex greater than -1. There is no canonical answer for this on StackOverflow which seems like a glaring omission for what seems a common technical question. The whole point of this site is to answer dev questions and help me "do my job". Nothing wrong with that.Hessney
I
5

I've tried to make the keyboard events work but sadly I can't get it to work nicely. Based on the docs I believe it the correct way to do it would be:

document.activeElement.dispatchEvent(new KeyboardEvent("keypress", { 
    key: "Tab" 
}));
// -----------------------------------------------------
// Find all the elements that have a tabindex set, 
// I would keep this file scoped for performance sake
// ------------------------------------------------------
const tabElements = Array.from(document
    // Get all elements that can be focusable
  .querySelectorAll('a, button, input, textarea, select, details, [tabindex]'))
  
  // remove any that have a tabIndex of -1
  .filter(element => element.tabIndex > -1)
  
  // reverse, then sort by tabIndex descending to put 0s last but maintain original order
  .reverse().sort((a, b) => a.tabIndex > b.tabIndex ? -1 : 1)

then selecting the next element:

// ------------------------------------------------------------------------------
// Method to find the next element to focus and change the focus to that element
// ------------------------------------------------------------------------------
const DispatchTab = () => {

    // If the current focused element is a -1 then we wont find the index in the elements list, got to the start
    if (document.activeElement.tabIndex === -1) {
    tabElements[0].focus();
    return;
  }

    // find the current index in the tab list of the currently focused element
    const currentIndex = tabElements.findIndex(e => e === document.activeElement);

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

I have created a JS fiddle with an approach that doesn't use the KeyboardEvent at all. It might need tweaking to your liking but should do the job. I feel Tab Example

Ingather answered 30/9, 2022 at 15:58 Comment(2)
Additional info regarding order of elements of "tabindex:0" --->>> querySelectorAll returns elements in "document order", seems this is what we need for elements of "tabindex:0". -> w3.org/TR/selectors-api/#findelementsFossorial
tabElements can contain "invisible" elements. When detecting next element, additional check can be added (nextElement.clientHeight !== 0). Using window.getComputedStyle(nextElement) is more expensive but styleOfNext.display !== "none" / styleOfNext.visibility !== "hidden" is also important.Fossorial

© 2022 - 2024 — McMap. All rights reserved.