I started with Stencil some time ago and today I did an experiment with event bubbling.
Basically, I clicked on a child element and listened on its parent for mousedown
. What I expected was that event.target
to be my child component. This is super simple, for example if I run this test with regular DOM elements (not Stencil) it all works as expected
But in Stencil event.target
is the parent element. Here is what I did. My parent component looks like this:
@Component({
tag: 'app-root',
styleUrl: 'app-root.scss',
shadow: true
})
export class MyApp {
@Listen('mousedown')
onMousedown(event) {
console.log(event.target, event.currentTarget);
}
render() {
return (
<div>
<my-element></my-element>
</div> );
}
}
And the child
@Component({
tag: 'my-element',
styleUrl: './my-element.scss',
shadow: true
})
export class MyElement {
@Listen('mousedown')
onMouseDonw(e) {
console.log(e.target);
}
render() {
return ( <div>YO</div> );
}
}
Now, as I said, when I click my-element
, I first see the console.log
from my-element
, which confirms that event.target
is my-element
. But then the event arrives at its parent and event.target
(but also event.currentTarget
) contains app-root
. I noticed that when I set shadow
to false
of my app-root
it all seems to work, but I don't understand why, and is this the only solution to my problem?
It's considered a feature of the encapsulation of the Shadow DOM. The original target is hidden as "internal implementation", and instead the event is re-targeted to the host element.
When you attach the listener with the Listen
decorator within MyApp
, it gets attached to the component's host element, which is app-root
in this case.
You can use event.composedPath()
to get the real target though (not supported in IE and old Edge).
Or you can catch the event from within the component and re-emit it as a custom event and forward the real event or target element:
export class MyApp {
@Event() myMousedown: EventEmitter<MouseEvent>;
handleElementMousedown = (event: MouseEvent) => {
this.myMousedown.emit(event);
}
render() {
return (
<div>
<my-element onMousedown={handleElementMousedown} />
</div> );
}
}
document
.querySelector('app-root')
.attachEventListener('my-mousedown', console.log);
(disclaimer: this code was not tested)