Shadow DOM and events
The idea behind shadow tree is to encapsulate internal implementation details of a component.
Let’s say, a click event happens inside a shadow DOM of
Bubbling, event.composedPath()
For purposes of event bubbling, flattened DOM is used.
So, if we have a slotted element, and an event occurs somewhere inside it, then it bubbles up to the
event.composed
Most events successfully bubble through a shadow DOM boundary. There are few events that do not. This is governed by the composed event object property. If it’s true, then the event does cross the boundary. Otherwise, it only can be caught from inside the shadow DOM. If you take a look at UI Events specification, most events have composed: true: - blur, focus, focusin, focusout, - click, dblclick, - mousedown, mouseup mousemove, mouseout, mouseover, - wheel, - beforeinput, input, keydown, keyup. All touch events and pointer events also have composed: true. There are some events that have composed: false though: - mouseenter, mouseleave (they do not bubble at all), - load, unload, abort, error, - select, - slotchange. These events can be caught only on elements within the same DOM, where the event target resides.
Custom events
When we dispatch custom events, we need to set both bubbles and composed properties to true for it to bubble up and out of the component. For example, here we create div#inner in the shadow DOM of div#outer and trigger two events on it. Only the one with composed: true makes it outside to the document:
Summary
Events only cross shadow DOM boundaries if their composed flag is set to true. Built-in events mostly have composed: true, as described in the relevant specifications: - UI Events https://www.w3.org/TR/uievents. - Touch Events https://w3c.github.io/touch-events. - Pointer Events https://www.w3.org/TR/pointerevents. - …And so on. Some built-in events that have composed: false: - mouseenter, mouseleave (also do not bubble), - load, unload, abort, error, - select, - slotchange. These events can be caught only on elements within the same DOM. If we dispatch a CustomEvent, then we should explicitly set composed: true. Please note that in case of nested components, one shadow DOM may be nested into another. In that case composed events bubble through all shadow DOM boundaries. So, if an event is intended only for the immediate enclosing component, we can also dispatch it on the shadow host and set composed: false. Then it’s out of the component shadow DOM, but won’t bubble up to higher-level DOM.
<user-card></user-card>
<script>
customElements.define('user-card', class extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML = `<p>
<button>Click me</button>
</p>`;
this.shadowRoot.firstElementChild.onclick =
e => alert("Inner target: " + e.target.tagName);
}
});
document.onclick =
e => alert("Outer target: " + e.target.tagName);
</script>
Follow the lesson from Microsoft Web-Dev-For-Beginners course