import { modifier } from 'ember-modifier'; export default modifier((element) => { let tooltipEl = null; const portal = document.getElementById('modal-portal') || document.body; const show = () => { const text = element.getAttribute('aria-description'); if (!text || tooltipEl) return; tooltipEl = document.createElement('div'); tooltipEl.className = 'tooltip'; tooltipEl.setAttribute('role', 'tooltip'); tooltipEl.textContent = text; portal.appendChild(tooltipEl); const rect = element.getBoundingClientRect(); const tipRect = tooltipEl.getBoundingClientRect(); const arrowSize = 6; // Vertical placement: above trigger, flip below if no room let placement = 'top'; let top = rect.top - tipRect.height - arrowSize; if (top < arrowSize) { placement = 'bottom'; top = rect.bottom + arrowSize; } // Horizontal: center on trigger, clamp to viewport let left = rect.left + rect.width / 2 - tipRect.width / 2; left = Math.max( arrowSize, Math.min(left, window.innerWidth - tipRect.width - arrowSize) ); // Arrow always points at trigger center, even when clamped const arrowLeft = rect.left + rect.width / 2 - left; tooltipEl.dataset.placement = placement; tooltipEl.style.top = `${top}px`; tooltipEl.style.left = `${left}px`; tooltipEl.style.setProperty('--arrow-left', `${arrowLeft}px`); }; const hide = () => { tooltipEl?.remove(); tooltipEl = null; }; element.addEventListener('mouseenter', show); element.addEventListener('mouseleave', hide); element.addEventListener('focus', show); element.addEventListener('blur', hide); return () => { element.removeEventListener('mouseenter', show); element.removeEventListener('mouseleave', hide); element.removeEventListener('focus', show); element.removeEventListener('blur', hide); hide(); }; });