The event object in JavaScript has a target property which is the actual element that triggered the event. The currentTarget property is the element that the event listener is attached to.

<div id="container">
<button>Click Me</button>
</div>
document
.getElementById("container")
.addEventListener("click", (event) => {
console.log("target", event.target); // => button
console.log("currentTarget", event.currentTarget); // => div
});

This works different when using Shadow DOM. When the event was triggered inside an element with Shadow DOM, the target property points the Shadow DOM element, not the element that triggered the event:

<div id="container">
<!-- Web Component with Shadow DOM -->
<my-button>Click Me</my-button>
</div>
document
.getElementById("container")
.addEventListener("click", (event) => {
console.log("target", event.target); // => my-button
console.log("currentTarget", event.currentTarget); // => div
});

While this is good in terms of encapsulation, there are use cases where you need the actual target. In these cases you can use the composedPath method to get the actual target:

document
.getElementById("container")
.addEventListener("click", (event) => {
console.log("path", event.composedPath());
// => [slot, button, document-fragment, my-button,
// div#container, body, html, document, Window]
});

The method returns all the elements that are in the path of the event. The first element is the one that triggered the event.

Note: this works only if the Shadow DOM uses encapsulation mode open.