'How to attach a keyup event to Custom Element shadowRoot

I have searched for some time; but only find Polymer answers;
or answers where EventListeners are put on DOM elements inside the shadowRoot.

The effect I am trying to achieve with native Custom Elements:

  • Only the focussed element should accept (and display) a keypress

It is possible to attach a click event to the shadowRoot, it seems I am doing something wrong for the 'keyup' event.

If I put the EventListener on the window all elements (of course) update with the same key info.

window.customElements.define('game-toes', class extends HTMLElement {
  constructor() {
    super().attachShadow({mode:'open'})
           .innerHTML = this.tabIndex;
    this.addEventListener('keyup',evt=>this.shadowRoot.innerHTML = 'key:'+evt.key);        
  }
});
game-toes{
  display:inline-block;
  height:auto;
  width:100px;
  padding:1em;
  border:10px solid green;
}
game-toes:focus { 
  background-color: lightgreen;
}
<game-toes tabindex=1></game-toes>
<game-toes tabindex=2></game-toes>
<game-toes tabindex=3></game-toes>


Solution 1:[1]

You can do it like you were but you need to add some extra code to make it work:

function on(el, evt, cb) {
  el.addEventListener(evt, cb);
  return () => {
    el.removeEventListener(evt, cb);
  }
}


window.customElements.define('game-toes', class extends HTMLElement {
  constructor() {
    super()
      .attachShadow({mode: 'open'})
      .innerHTML = this.tabIndex;
  }
  
  connectedCallback() {
    this._offKeyup = on(this, 'keyup', evt => {
      this.shadowRoot.innerHTML = evt.key;
      evt.stopPropagation(); // Prevent the event from leaving this element
    });
  }
  
  disconnectedCallback() {
    this._offKeyup();
  }
});
game-toes{
  display:inline-block;
  height:auto;
  width:100px;
  padding:1em;
  border:10px solid green;
}
game-toes:focus { 
  background-color: lightgreen;
}
<game-toes tabindex=1></game-toes>
<game-toes tabindex=2></game-toes>
<game-toes tabindex=3></game-toes>
  1. You may want to use evt.stopPropagation() to stop the event from leaving the component.
  2. You either need to add your eventListener on the component itself OR, create an element in the shadowRoot with the ability to take focus and then set focus on the inner element when the component gets focus. And then you should be able to add the keyup event on that internal element.
  3. It is safest to add the eventListener in connectedCallback and release them in the disconnectedCallback unless you never plan to remove your component.

Solution 2:[2]

In your example, the tabindex attribute is set to the custom element <game-toes>, and not to its Shadow DOM.

As a consequence, you should instead listen to the keyup event on the custom element itself :

this.addEventListener('keyup',evt=>this.shadowRoot.innerHTML = 'key:'+evt.key);

window.customElements.define('game-toes', class extends HTMLElement {
  constructor() {
    super();
    let shadowRoot=this.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = this.tabIndex;
    this.addEventListener('keyup',evt=>this.shadowRoot.innerHTML = 'key:'+evt.key);        
  }
});
game-toes{
  display:inline-block;
  height:auto;
  width:100px;
  padding:1em;
  border:10px solid green;
}
game-toes:focus { 
  background-color: lightgreen;
}
<game-toes tabindex=1></game-toes>
<game-toes tabindex=2></game-toes>
<game-toes tabindex=3></game-toes>

Alternately, if you want to listen the keyup event at the Shadow DOM level, you should set the tabindex attribute in an element inside the Shadow DOM.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Danny '365CSI' Engelman
Solution 2