From 6ea66f32cf0cc279b066836510eb77381b4b2939 Mon Sep 17 00:00:00 2001 From: Julien Maille Date: Sun, 18 Jan 2026 22:41:06 +0100 Subject: [PATCH] FEAT: add tooltip for truncated text on cards and track items --- js/ui-interactions.js | 67 ++++++++++++++++++++++++++++++++++++++++++- styles.css | 26 +++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/js/ui-interactions.js b/js/ui-interactions.js index ff66da4..f8abaeb 100644 --- a/js/ui-interactions.js +++ b/js/ui-interactions.js @@ -379,4 +379,69 @@ export function initializeUIInteractions(player, api) { document.getElementById(contentId)?.classList.add('active'); }); }); -} + + // Tooltip for truncated text + let tooltipEl = document.getElementById('custom-tooltip'); + if (!tooltipEl) { + tooltipEl = document.createElement('div'); + tooltipEl.id = 'custom-tooltip'; + document.body.appendChild(tooltipEl); + } + + const updateTooltipPosition = (e) => { + const x = e.clientX + 15; + const y = e.clientY + 15; + + // Prevent going off-screen + const rect = tooltipEl.getBoundingClientRect(); + const winWidth = window.innerWidth; + const winHeight = window.innerHeight; + + let finalX = x; + let finalY = y; + + if (x + rect.width > winWidth) { + finalX = e.clientX - rect.width - 10; + } + + if (y + rect.height > winHeight) { + finalY = e.clientY - rect.height - 10; + } + + tooltipEl.style.transform = `translate(${finalX}px, ${finalY}px)`; + // Reset top/left to 0 since we use transform + tooltipEl.style.top = '0'; + tooltipEl.style.left = '0'; + }; + + document.body.addEventListener('mouseover', (e) => { + const selector = + '.card-title, .card-subtitle, .track-item-details .title, .track-item-details .artist, .now-playing-bar .title, .now-playing-bar .artist, .now-playing-bar .album'; + const target = e.target.closest(selector); + + if (target) { + // Remove native title if present to avoid double tooltip + if (target.hasAttribute('title')) { + target.removeAttribute('title'); + } + + if (target.scrollWidth > target.clientWidth) { + tooltipEl.innerHTML = target.innerHTML.trim(); + tooltipEl.classList.add('visible'); + updateTooltipPosition(e); + + const moveHandler = (moveEvent) => { + updateTooltipPosition(moveEvent); + }; + + const outHandler = () => { + tooltipEl.classList.remove('visible'); + target.removeEventListener('mousemove', moveHandler); + target.removeEventListener('mouseleave', outHandler); + }; + + target.addEventListener('mousemove', moveHandler); + target.addEventListener('mouseleave', outHandler); + } + } + });} diff --git a/styles.css b/styles.css index e25365f..ef8b530 100644 --- a/styles.css +++ b/styles.css @@ -4114,3 +4114,29 @@ body:has(#fullscreen-cover-overlay:not([style*='display: none'])) .now-playing-b left: 2%; } } + +/* Custom Tooltip */ +#custom-tooltip { + position: fixed; + background: var(--card); + color: var(--card-foreground); + padding: 0.5rem 0.75rem; + border-radius: var(--radius); + border: 1px solid var(--border); + box-shadow: var(--shadow-lg); + font-size: 0.85rem; + pointer-events: none; + z-index: 10000; + opacity: 0; + transition: opacity 0.1s ease; + white-space: nowrap; + max-width: 400px; + overflow: hidden; + text-overflow: ellipsis; + will-change: transform, opacity; +} + +#custom-tooltip.visible { + opacity: 1; +} +