Elsewhere on this site a question was posted about using Selenium accessing the shadow DOM. Not knowing much about the subject, I read the MDN article Using shadow DOM and duplicated the code on my desktop and it seemed to work fine. For the heck of it I decided to add to the form a second element for the credit card number with its own "popup info." This was added as the first form element since one normally enters the credit card number and then the CVC number. I discovered that not only is no information displayed when you hover over the info icon (since the PNG file implementing this does not exist on this website, I provided the alt="Info" attribute on the IMG tag) for the credit card number, but when you click on the credit card number input element, it is the CVC input element that gets highlighted (i.e. gets the focus). I can, however, get the focus on the credit card number input element by either tabbing through all the elements in the form or by clicking on its label. Also, when the info icon for the credit card element gets the focus via tabbing, then the popup info is displayed.
As an experiment I added a second form to the HTML with the two input fields reversed (the two forms are separated by a horizontal line in the presentation for clarity). This works as far as displaying the pop-up info for both input elements, but clicking in the CVC element of the second form still highlights the wrong element.
Can anyone explain why the order seems to matter?
Update
After Keith provided his answer below it has become clear that the problem arises because the invisible popup info for the second element (the CVC) is overlapping with the first input element (the credit card number). So I have added a third form that leaves enough vertical separation between the two input elements demonstrating that the problem disappears. But Keith's solution of modifying the style sheet for the popup info by adding pointer-events: none;
is something you would want even if you had adequate element spacing because without this addition clicking above an input element where the hidden popup info is results in bringing the focus to the input element and that can be confusing to the user (it was to me).
class PopupInfo extends HTMLElement {
constructor() {
super();
// Create a shadow root
const shadow = this.attachShadow({mode: 'open'});
// Create spans
const wrapper = document.createElement('span');
wrapper.setAttribute('class', 'wrapper');
const icon = document.createElement('span');
icon.setAttribute('class', 'icon');
icon.setAttribute('tabindex', 0);
const info = document.createElement('span');
info.setAttribute('class', 'info');
// Take attribute content and put it inside the info span
const text = this.getAttribute('data-text');
info.textContent = text;
// Insert icon
let imgUrl;
if(this.hasAttribute('img')) {
imgUrl = this.getAttribute('img');
} else {
imgUrl = 'img/default.png';
}
const img = document.createElement('img');
img.src = imgUrl;
img.alt = 'Info';
icon.appendChild(img);
// Create some CSS to apply to the shadow dom
const style = document.createElement('style');
//console.log(style.isConnected);
style.textContent = `
.wrapper {
position: relative;
}
.info {
font-size: 0.8rem;
width: 200px;
display: inline-block;
border: 1px solid black;
padding: 10px;
background: white;
border-radius: 10px;
opacity: 0;
transition: 0.6s all;
position: absolute;
bottom: 20px;
left: 10px;
z-index: 3;
}
img {
width: 1.2rem;
}
.icon:hover + .info, .icon:focus + .info {
opacity: 1;
}
`;
// Attach the created elements to the shadow dom
shadow.appendChild(style);
//console.log(style.isConnected);
shadow.appendChild(wrapper);
wrapper.appendChild(icon);
wrapper.appendChild(info);
}
}
customElements.define('popup-info', PopupInfo);
<!doctype html>
<html>
<head>
<title>Custom Component</title>
<meta name=viewport content="width=device-width,initial-scale=1">
<meta charset="utf-8">
</head>
<body>
<h1>Pop-up info widget - web components</h1>
<form>
<div>
<label for="cc_number">Enter your credit card number <popup-info data-text="You may include spaces or hyphens."></popup-info></label>
<input type="text" id="cc_number">
</div>
<div>
<label for="cvc">Enter your CVC <popup-info data-text="Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card."></popup-info></label>
<input type="text" id="cvc">
</div>
</form>
<hr style="margin: 20px 0px;">
<form>
<div>
<label for="cvc2">Enter your CVC <popup-info data-text="Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card."></popup-info></label>
<input type="text" id="cvc2">
</div>
<div>
<label for="cc_number2">Enter your credit card number <popup-info data-text="You may include spaces or hyphens."></popup-info></label>
<input type="text" id="cc_number2">
</div>
<hr style="margin: 20px 0px;">
<form>
<div>
<label for="cc_number3">Enter your credit card number <popup-info data-text="You may include spaces or hyphens."></popup-info></label>
<input type="text" id="cc_number3">
</div>
<div style="margin-top: 100px;">
<label for="cvc3">Enter your CVC <popup-info data-text="Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card."></popup-info></label>
<input type="text" id="cvc3">
</div>
</form>
</form>
</body>
</html>
An element with Opacity 0, will still receive click events. This then causes the focus to move to this hidden info element.
I assume opacity was used here so that you get nice transition effects.
A simple solution here is to make it so that the info element doesn't receive pointer events, using pointer-events: none
.info {
pointer-events: none;
font-size: 0.8rem;
....
eg.
class PopupInfo extends HTMLElement {
constructor() {
super();
// Create a shadow root
const shadow = this.attachShadow({mode: 'open'});
// Create spans
const wrapper = document.createElement('span');
wrapper.setAttribute('class', 'wrapper');
const icon = document.createElement('span');
icon.setAttribute('class', 'icon');
icon.setAttribute('tabindex', 0);
const info = document.createElement('span');
info.setAttribute('class', 'info');
// Take attribute content and put it inside the info span
const text = this.getAttribute('data-text');
info.textContent = text;
// Insert icon
let imgUrl;
if(this.hasAttribute('img')) {
imgUrl = this.getAttribute('img');
} else {
imgUrl = 'img/default.png';
}
const img = document.createElement('img');
img.src = imgUrl;
img.alt = 'Info';
icon.appendChild(img);
// Create some CSS to apply to the shadow dom
const style = document.createElement('style');
//console.log(style.isConnected);
style.textContent = `
.wrapper {
position: relative;
}
.info {
pointer-events: none;
font-size: 0.8rem;
width: 200px;
display: inline-block;
border: 1px solid black;
padding: 10px;
background: white;
border-radius: 10px;
opacity: 0.1;
transition: 0.6s all;
position: absolute;
bottom: 20px;
left: 10px;
z-index: 3;
}
img {
width: 1.2rem;
}
.icon:hover + .info, .icon:focus + .info {
opacity: 1;
}
`;
// Attach the created elements to the shadow dom
shadow.appendChild(style);
//console.log(style.isConnected);
shadow.appendChild(wrapper);
wrapper.appendChild(icon);
wrapper.appendChild(info);
}
}
customElements.define('popup-info', PopupInfo);
<!doctype html>
<html>
<head>
<title>Custom Component</title>
<meta name=viewport content="width=device-width,initial-scale=1">
<meta charset="utf-8">
</head>
<body>
<h1>Pop-up info widget - web components</h1>
<form>
<div>
<label for="cc_number">Enter your credit card number <popup-info data-text="You may include spaces or hyphens."></popup-info></label>
<input type="text" id="cc_number">
</div>
<div>
<label for="cvc">Enter your CVC <popup-info data-text="Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card."></popup-info></label>
<input type="text" id="cvc">
</div>
</form>
<hr style="margin: 20px 0px;">
<form>
<div>
<label for="cvc2">Enter your CVC <popup-info data-text="Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card."></popup-info></label>
<input type="text" id="cvc2">
</div>
<div>
<label for="cc_number2">Enter your credit card number <popup-info data-text="You may include spaces or hyphens."></popup-info></label>
<input type="text" id="cc_number2">
</div>
</form>
</body>
</html>