I have an Angular web component installed on my site. It uses Shadow DOM so it's super fast (which it has to be in my case).
On my site I also have a shortcut on h which opens up a popup that displays some helpful information. It's a must that this h keybinding stays as it is. Example code of how it was implemented can be seen here: https://jsfiddle.net/js1edv37/
It's a simple event listener that listens on document
:
$(document).on("keyup", function(e) {
}
However, this also gets triggered when my web component has focused textarea
or input
elements. This happens because it uses Shadow DOM, which a script from the outside cannot access.
You can test it by pressing h on the keyboard inside and outside of the input
and textarea
elements.
Is there a way to let my script from outside of the Shadow DOM web component, still listen for the keyup
event, but make it listen for all elements on the page? Even the ones inside the Shadow DOM.
But scripts in the main document have no idea about the shadow DOM internals, especially if the component comes from a 3rd-party library. So, to keep the details encapsulated, the browser retargets the event. Events that happen in shadow DOM have the host element as the target, when caught outside of the component.
That’s an element from the light DOM, so no retargeting. On the other hand, if the click occurs on an element originating from shadow DOM, e.g. on <b>Name</b>, then, as it bubbles out of the shadow DOM, its event.target is reset to <user-card>. For purposes of event bubbling, flattened DOM is used.
Events that happen in shadow DOM have the host element as the target, when caught outside of the component. If you click on the button, the messages are: Inner target: BUTTON – internal event handler gets the correct target, the element inside shadow DOM.
Once you click the button, the following messages will be shown: Inner target: BUTTON - the internal event handler receives the correct target, the element within the shadow DOM. Outer target: USER-CARD - the document event handler receives the shadow host as a target.
In the Web Component, get the input element with a querySelector()
call on the shadowRoot
property:
let textareainshadow = div.shadowRoot.querySelector( 'textarea' )
Then listen to the keyup
event and stop its propagation with the help of the stopImmediatePropagation()
method.
textareainshadow.addEventListener( 'keyup' , ev => {
console.log( 'caught', ev.type )
ev.stopImmediatePropagation()
})
https://jsfiddle.net/7mkrxh25/1/
If you save the reference to the shadow root you can always access it's children as search on those
$(document).on("keyup", function(e) {
let focusedInputs = $("input:focus, textarea:focus").length + $(shadow).children("input:focus, textarea:focus").length;
if (focusedInputs > 0) {
return true;
}
if (e.keyCode === 72) {
trigger();
}
});
function trigger() {
alert("If this was triggered, everything is perfectly fine");
}
let div = document.querySelector("div");
let shadow = div.createShadowRoot();
shadow.innerHTML = "<textarea>This shouldn't fail</textarea>";
textarea {
width: 500px;
height: 100px;
}
input {
width: 250px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<textarea>Some stuff here</textarea>
<br />
<input type="text" value="Some more text here" />
<br />
<br />
<h1>Shadow DOM element WON'T fail now :)</h1>
<div></div>
Fiddle
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With