Skip to content

Commit 0e4b299

Browse files
committed
Implement custom tooltips with delay and theme support
1 parent 473f1c3 commit 0e4b299

3 files changed

Lines changed: 125 additions & 1 deletion

File tree

popup.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@
198198
</div>
199199
</div>
200200

201+
<div id="tooltip" class="tooltip"></div>
201202
<script src="popup.js"></script>
202203
</body>
203204

popup.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,8 @@ document.addEventListener('DOMContentLoaded', () => {
496496
loadStoredFile();
497497
}
498498
});
499+
500+
setupTooltips();
499501
}
500502

501503
// ==================
@@ -1019,4 +1021,98 @@ document.addEventListener('DOMContentLoaded', () => {
10191021
elements.sendBtn.disabled = false;
10201022
}
10211023
}
1024+
function setupTooltips() {
1025+
const tooltip = document.getElementById('tooltip');
1026+
let activeElement = null;
1027+
let tooltipTimeout = null;
1028+
1029+
document.addEventListener('mouseover', (e) => {
1030+
// Find closest element with a title attribute or data-tooltip
1031+
const target = e.target.closest('[title], [data-tooltip]');
1032+
1033+
// If we are already tracking this element, do nothing (prevents timer reset on mouse move inside element)
1034+
if (activeElement && target === activeElement) {
1035+
return;
1036+
}
1037+
1038+
// If we moved to a new element (or no element), clear any pending tooltip
1039+
if (activeElement) {
1040+
clearTimeout(tooltipTimeout);
1041+
hideTooltip();
1042+
}
1043+
1044+
if (!target) {
1045+
return;
1046+
}
1047+
1048+
// If it has a title, move it to data-tooltip to suppress native tooltip
1049+
if (target.hasAttribute('title')) {
1050+
const title = target.getAttribute('title');
1051+
target.setAttribute('data-tooltip', title);
1052+
target.removeAttribute('title');
1053+
}
1054+
1055+
const text = target.getAttribute('data-tooltip');
1056+
if (!text) return;
1057+
1058+
activeElement = target;
1059+
1060+
// Delay showing the tooltip
1061+
tooltipTimeout = setTimeout(() => {
1062+
// Double-check we are still active on this element
1063+
if (activeElement === target) {
1064+
showTooltip(target, text);
1065+
}
1066+
}, 1000); // 1000ms delay
1067+
});
1068+
1069+
document.addEventListener('mouseout', (e) => {
1070+
if (activeElement && (e.target === activeElement || e.target.closest('[data-tooltip]') === activeElement)) {
1071+
// check if we moved to child of the active element
1072+
if (e.relatedTarget && activeElement.contains(e.relatedTarget)) {
1073+
return;
1074+
}
1075+
1076+
// otherwise, we left the element entirely
1077+
clearTimeout(tooltipTimeout);
1078+
hideTooltip();
1079+
}
1080+
});
1081+
1082+
function showTooltip(element, text) {
1083+
if (!element.isConnected) return; // Verify element is still in DOM
1084+
1085+
tooltip.textContent = text;
1086+
tooltip.classList.add('visible');
1087+
1088+
const rect = element.getBoundingClientRect();
1089+
const tooltipRect = tooltip.getBoundingClientRect();
1090+
const margin = 8;
1091+
1092+
// Default: Top center
1093+
let top = rect.top - tooltipRect.height - margin;
1094+
let left = rect.left + (rect.width - tooltipRect.width) / 2;
1095+
1096+
// prevent overflow top
1097+
if (top < 0) {
1098+
top = rect.bottom + margin;
1099+
}
1100+
1101+
// prevent overflow left/right
1102+
if (left < margin) {
1103+
left = margin;
1104+
} else if (left + tooltipRect.width > window.innerWidth - margin) {
1105+
left = window.innerWidth - tooltipRect.width - margin;
1106+
}
1107+
1108+
tooltip.style.top = `${top}px`;
1109+
tooltip.style.left = `${left}px`;
1110+
}
1111+
1112+
function hideTooltip() {
1113+
tooltip.classList.remove('visible');
1114+
activeElement = null;
1115+
}
1116+
}
1117+
10221118
});

styles.css

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -799,9 +799,36 @@ body {
799799
.divider {
800800
height: 1px;
801801
background-color: var(--border-color);
802-
margin: 10px 0;
802+
margin: 16px 0;
803803
}
804804

805+
/* ========================================
806+
Tooltip
807+
======================================== */
808+
809+
.tooltip {
810+
position: fixed;
811+
padding: 6px 10px;
812+
background-color: var(--bg-card);
813+
color: var(--text-primary);
814+
border: 1px solid var(--border-color);
815+
border-radius: 6px;
816+
font-size: 12px;
817+
font-weight: 500;
818+
pointer-events: none;
819+
opacity: 0;
820+
transition: opacity 0.15s;
821+
box-shadow: var(--shadow);
822+
z-index: 1000;
823+
white-space: nowrap;
824+
}
825+
826+
.tooltip.visible {
827+
opacity: 1;
828+
}
829+
830+
831+
805832
/* ========================================
806833
Filepicker-specific Styles
807834
======================================== */

0 commit comments

Comments
 (0)