I am trying to get the coordinates of a shadow DOM table cell that may (or may-not) have overlay elements displayed in front of it. The clickable overlay elements may span over multiple cells so they will not necessarily be children of the cell I need the coordinates from.
I can't get the event to bubble through the table like I expect. I believe event re-targeting of the shadow DOM is preventing the event from ever hitting the cells when the event originates in the light DOM (I kinda think that this is the wrong approach anyways as the overlay element is not a child of the cell I want the coordinates from to begin with)
The "pointer-events: none;"property pretty much does what I want, however, I need the event to also trigger on the light DOM overlay element so I don't know if that is an option.
I am thinking it is a good idea to avoid attaching a listener to every cell for performance issues (in the final application there will be hundreds of cells)
I am using vanilla JavaScript
In the provided example, is it possible to get the correct coordinates of the underlying table cell when the overlying div grey is clicked?
class Custom_Grid extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: 'open'
});
this.shadowRoot.innerHTML = `
<style>
td {
height:50px; width:50px; border: 1px solid grey;
}
</style>
<table>
<tr>
<td id='x0y0'><slot name='x0y0'></slot></td>
<td id='x0y1'><slot name='x0y1'></slot></td>
<td id='x0y2'><slot name='x0y2'></slot></td>
</tr>
<tr>
<td id='x1y0'><slot name='x1y0'></slot></td>
<td id='x1y1'><slot name='x1y1'></slot></td>
<td id='x1y2'><slot name='x1y2'></slot></td>
</tr>
<tr>
<td id='x2y0'><slot name='x2y0'></slot></td>
<td id='x2y1'><slot name='x2y1'></slot></td>
<td id='x2y2'><slot name='x2y2'></slot></td>
</tr>
`;
}
connectedCallback() {
this.shadowRoot.addEventListener("click", (e) => {
if (e.target.matches('td'))
console.log(`Grid Cell: ${e.target.id}`)
});
}
}
window.customElements.define('custom-grid', Custom_Grid);
document.getElementById('grid_overlay').addEventListener("click", (e) => {
console.log("#overGrid Click")
});
#grid_overlay {
width: 100px;
height: 100px;
background-color: grey;
position: absolute;
/*pointer-events: none; <-- not an option?*/
}
<custom-grid>
<div slot='x1y0' id='grid_overlay'></div>
</custom-grid>
A solution is to hide the overlay element temporarily in order to get the covered element thanks to the elementFromPoint()
method.
if ( e.target.id === 'grid_overlay' ) {
console.log( 'overlay click' )
e.target.hidden = true
var el = this.shadowRoot.elementFromPoint( e.x, e.y )
if (el.id)
console.log( 'under: ', el.id )
e.target.hidden = false
}
class Custom_Grid extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
td {
height:50px; width:50px; border: 1px solid grey;
}
</style>
<table>
<tr>
<td id='x0y0'><slot name='x0y0'></slot></td>
<td id='x0y1'><slot name='x0y1'></slot></td>
<td id='x0y2'><slot name='x0y2'></slot></td>
</tr>
<tr>
<td id='x1y0'><slot name='x1y0'></slot></td>
<td id='x1y1'><slot name='x1y1'></slot></td>
<td id='x1y2'><slot name='x1y2'></slot></td>
</tr>
<tr>
<td id='x2y0'><slot name='x2y0'></slot></td>
<td id='x2y1'><slot name='x2y1'></slot></td>
<td id='x2y2'><slot name='x2y2'></slot></td>
</tr>
`;
}
connectedCallback() {
this.shadowRoot.addEventListener("click", (e) => {
if (e.target.matches('td'))
console.log(`Grid Cell: ${e.target.id}`)
else if (e.target.id === 'grid_overlay' ) {
console.log( 'overlay click ' )
e.target.hidden = true
var el = this.shadowRoot.elementFromPoint( e.x, e.y )
if (el.id)
console.log( 'under: ', el.id )
e.target.hidden = false
}
});
}
}
window.customElements.define('custom-grid', Custom_Grid);
#grid_overlay {
width: 100px;
height: 100px;
background-color: grey;
position: absolute;
}
<custom-grid>
<div slot='x1y0' id='grid_overlay'></div>
</custom-grid>