javascripthtmleventsweb-componentshadow-dom

How to add keydown listener to shadow dom


I've been trying to implement an event listener for the keydown event for a specific web component. The event listener should be placed within the web component, so it can be used in other projects, too. I implemented an onclick listener which works fine: both methods (for the document and for the shadow dom) are being called. However, this does not work for the keydown event.

I already did some research, however, did not find fitting solutions, e.g. this one. According to this source, the keydown event should be "useable" as is has the composed = true property.

Below is my sample code. This problems seems to appear also for the keyup and keypress event. I feel like I'm missing an essential point. Is there any possibility to handle keydown events within a shadow dom?

class CustomElement extends HTMLElement {
  constructor() {
    super();
    let template = document.getElementById("custom-template");

    this.attachShadow({ mode: "open" });
    this.shadowRoot.appendChild(template.content.cloneNode(true));
  }

  connectedCallback() {
    this.shadowRoot.addEventListener("click", (e) => {
      alert("click from shadow dom");
    });
    this.shadowRoot.addEventListener("keydown", (e) => {
      e.stopPropagation(); // doesn't seem to change anything 
      alert("keydown from shadow dom"); // won't run
    });
  }
}

window.customElements.define("custom-template", CustomElement);

document.addEventListener("click", (e) => {
  alert(`click from document; composed: ${e.composed}`);
});

document.addEventListener("keydown", (e) => {
  alert(`keydown from document; composed: ${e.composed}`);
});
<custom-template></custom-template>

<template id="custom-template">
  <button>Click me</button>
  <div>This is a div</div>
</template>


Solution

  • source: How to make keypress event have the target equal to a "selected" element?

    Your element is not receiving keyboard events because it is not focused when clicked. All elements are not able to recieve focus when clicked as they don't have tabindex focus flag set except for the following elements. [source:www.w3.org]

    • <a> elements that have an href attribute
    • <link> elements that have an href attribute
    • <button> elements
    • <input> elements whose type attribute are not in the Hidden state
    • <select> elements
    • <textarea> elements
    • Editing hosts
    • Browsing context containers

    If an element is not in focus keyboard events are recieved by the body element.

    When an element is focused, key events received by the document must be targeted at that element. There may be no element focused; when no element is focused, key events received by the document must be targeted at the body element. [source:www.w3.org]

    You can make any HTML element able to receive focus and thereby receive keyboard events by adding a tabindex property.

    Web Component shadowDOM example:

    Best played with in the JSFiddle: https://jsfiddle.net/WebComponents/1muf82nj/

    <script>
      customElements.define("my-element", class extends HTMLElement {
        constructor() {
          super().attachShadow({ mode:"open" })
                 .innerHTML = `<style>
                                 *:focus-within { background:green }
                               </style>
                               <button>button</button>
                               <input>
                               <div>DIV without tabindex</div>
                               <div tabindex="0">DIV with tabindex</div>`;
        }
        connectedCallback() {
          const BR = _ => document.createElement("br");
          const log = ( e, t=e.target, n=t.nodeName ) => 
                        this.shadowRoot.append(
                                  BR(),
                                  e.type, 
                                  e.type=="keydown" ?" key:"+e.key:"" ," in ",  
                                  n=="BODY" ? "e.target=SCRIPT tag!" : t.innerHTML || n
                                ); // append
                                
          this.shadowRoot.addEventListener("click", log); // can't do this.shadowRoot.onclick = ...
          this.shadowRoot.addEventListener("keydown", log);
          document.onkeydown = log;
        }
      });
    </script>
    <div><h4>Focus an element, then hit a key</h4></div>
    <my-element></my-element>