Improve tooltips by using mutation observers

This commit is contained in:
Antoine Bartuccio 2025-05-14 12:38:13 +02:00
parent 19aac8f302
commit d1e5c93a08
Signed by: klmp200
GPG Key ID: E7245548C53F904B

View File

@ -18,7 +18,7 @@ import {
* Note: placement are suggestions and the position could change if the popup gets * Note: placement are suggestions and the position could change if the popup gets
* outside of the screen. * outside of the screen.
* *
* You can customize your tooltip by passing additionnal classes or ids to it * You can customize your tooltip by passing additional classes or ids to it
* You can use `tooltip-class` and `tooltip-id` to add additional elements to the * You can use `tooltip-class` and `tooltip-id` to add additional elements to the
* `class` and `id` attribute of the generated tooltip * `class` and `id` attribute of the generated tooltip
* *
@ -32,7 +32,7 @@ import {
type Status = "open" | "close"; type Status = "open" | "close";
const tooltips = new Map(); const tooltips: Map<HTMLElement, HTMLElement> = new Map();
function getPosition(element: HTMLElement): Placement | "auto" { function getPosition(element: HTMLElement): Placement | "auto" {
const position = element.getAttribute("tooltip-position"); const position = element.getAttribute("tooltip-position");
@ -93,12 +93,10 @@ function getTooltip(element: HTMLElement) {
return tooltip; return tooltip;
} }
addEventListener("mouseover", (event: MouseEvent) => { function tooltipMouseover(event: MouseEvent) {
const target = event.target as HTMLElement; // We get the closest tooltip to have a consistent behavior
if (!target.hasAttribute("tooltip")) { // when hovering over a child element of a tooltip marked element
return; const target = (event.target as HTMLElement).closest("[tooltip]") as HTMLElement;
}
const tooltip = getTooltip(target); const tooltip = getTooltip(target);
updateTooltip(target, tooltip, "open"); updateTooltip(target, tooltip, "open");
@ -113,13 +111,57 @@ addEventListener("mouseover", (event: MouseEvent) => {
}); });
document.body.append(tooltip); document.body.append(tooltip);
}); }
addEventListener("mouseout", (event: MouseEvent) => {
const target = event.target as HTMLElement;
if (!target.hasAttribute("tooltip")) {
return;
}
function tooltipMouseout(event: MouseEvent) {
// We get the closest tooltip to have a consistent behavior
// when hovering over a child element of a tooltip marked element
const target = (event.target as HTMLElement).closest("[tooltip]") as HTMLElement;
updateTooltip(target, getTooltip(target), "close"); updateTooltip(target, getTooltip(target), "close");
}
window.addEventListener("DOMContentLoaded", () => {
for (const el of document.querySelectorAll("[tooltip]")) {
el.addEventListener("mouseover", tooltipMouseover);
el.addEventListener("mouseout", tooltipMouseout);
}
});
// Add / remove callback when tooltip attribute is added / removed
new MutationObserver((mutations: MutationRecord[]) => {
for (const mutation of mutations) {
const target = mutation.target as HTMLElement;
target.removeEventListener("mouseover", tooltipMouseover);
target.removeEventListener("mouseout", tooltipMouseout);
if (target.hasAttribute("tooltip")) {
target.addEventListener("mouseover", tooltipMouseover);
target.addEventListener("mouseout", tooltipMouseout);
}
}
}).observe(document.body, {
attributes: true,
attributeFilter: ["tooltip"],
subtree: true,
});
// Remove orphan tooltips
new MutationObserver((mutations: MutationRecord[]) => {
for (const mutation of mutations) {
for (const node of mutation.removedNodes) {
if (node.nodeType !== node.ELEMENT_NODE) {
continue;
}
const target = node as HTMLElement;
if (!target.hasAttribute("tooltip")) {
continue;
}
if (tooltips.has(target)) {
tooltips.get(target).remove();
tooltips.delete(target);
}
}
}
}).observe(document.body, {
subtree: true,
childList: true,
}); });