Forward mouse event

by mnk

HTML

<p>We're buttons. Hover over the dummy to unleash its power.</p>
<div class="btn">
  button
</div>

<div class="dummy">
  dummy
</div>

CSS

body {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.btn, .dummy {
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 30px;
  width: 140px;
  height: 76px;
  margin-bottom: 1rem;
  border: 4px solid transparent;
  cursor: pointer;
  -webkit-transition: all .5s ease-in-out;
  -moz-transition: all .5s ease-in-out;
  -ms-transition: all .5s ease-in-out;
  -o-transition: all .5s ease-in-out;
  transition: all .5s ease-in-out;
}
.btn {
  color: blueviolet;
  border-color:blueviolet;
}
.btn:hover {
  background-color: violet;
}

.dummy {
  background-color: #f0f0f0;
}
.dummy:hover {
  background-color: #aaaaaa;
}

JavaScript

const btn = document.querySelector('.btn')
const dummy = document.querySelector('.dummy')

function collectHoverRules() {
  const rules = [];
  for (const sheet of document.styleSheets) {
    try {
      for (const r of sheet.cssRules) {
        if (r.selectorText && r.selectorText.includes(':hover')) rules.push(r);
      }
    } catch (err) {
      // ignore cross-origin stylesheets
    }
  }
  return rules;
}

function applyHoverStylesFromRules(target, hoverRules) {
  // save inline style so we can restore later
  if (target.__hover_savedStyle === undefined) {
    target.__hover_savedStyle = target.getAttribute('style') || '';
  }

  let addedCss = '';
  for (const rule of hoverRules) {
    // a rule may have multiple selectors comma-separated
    const selectors = rule.selectorText.split(',');
    for (let sel of selectors) {
      sel = sel.trim();
      if (!sel.includes(':hover')) continue;
      const baseSel = sel.replace(/:hover/g, '').trim();
      // check if target would match the selector without :hover
      try {
        if (baseSel && target.matches(baseSel)) {
          addedCss += rule.style.cssText + ';';
        }
      } catch (e) {
        // some selectors may be invalid after stripping :hover; ignore them
      }
    }
  }

  if (addedCss) {
    target.style.cssText += ';' + addedCss;
    target.__hover_applied = true;
  }
}

function restoreHoverStyles(target) {
  if (target.__hover_savedStyle !== undefined) {
    target.setAttribute('style', target.__hover_savedStyle);
    delete target.__hover_savedStyle;
    delete target.__hover_applied;
  }
}

// usage:
const hoverRules = collectHoverRules();
dummy.addEventListener('mouseenter', () => applyHoverStylesFromRules(btn, hoverRules));
dummy.addEventListener('mouseleave', () => restoreHoverStyles(btn));
dummy.addEventListener('click', function(e) {
  console.log('dummy clicked')
  btn.click()
})
btn.addEventListener('click',...