Tooltips optimization.

This commit is contained in:
gerlofvanek 2025-03-02 14:40:08 +01:00
parent a5c3c692a0
commit 5cb06702ec
2 changed files with 152 additions and 80 deletions

View file

@ -917,7 +917,6 @@ const forceTooltipDOMCleanup = () => {
foundCount += allTooltipElements.length;
allTooltipElements.forEach(element => {
const isDetached = !document.body.contains(element) ||
element.classList.contains('hidden') ||
element.style.display === 'none';
@ -947,7 +946,6 @@ const forceTooltipDOMCleanup = () => {
const tippyRoots = document.querySelectorAll('[data-tippy-root]');
foundCount += tippyRoots.length;
tippyRoots.forEach(element => {
const isOrphan = !element.children.length ||
element.children[0].classList.contains('hidden') ||
@ -975,13 +973,10 @@ const forceTooltipDOMCleanup = () => {
}
}
});
// Handle legacy tooltip elements
document.querySelectorAll('.tooltip').forEach(element => {
const isTrulyDetached = !element.parentElement ||
!document.body.contains(element.parentElement) ||
element.classList.contains('hidden');
if (isTrulyDetached) {
try {
element.remove();
@ -992,14 +987,11 @@ const forceTooltipDOMCleanup = () => {
}
});
if (window.TooltipManager && window.TooltipManager.activeTooltips) {
window.TooltipManager.activeTooltips.forEach((instance, id) => {
const tooltipElement = document.getElementById(id.split('tooltip-trigger-')[1]);
const triggerElement = document.querySelector(`[data-tooltip-trigger-id="${id}"]`);
if (!tooltipElement || !triggerElement ||
!document.body.contains(tooltipElement) ||
!document.body.contains(triggerElement)) {
if (window.TooltipManager && typeof window.TooltipManager.getActiveTooltipInstances === 'function') {
const activeTooltips = window.TooltipManager.getActiveTooltipInstances();
activeTooltips.forEach(([element, instance]) => {
const tooltipId = element.getAttribute('data-tooltip-trigger-id');
if (!document.body.contains(element)) {
if (instance?.[0]) {
try {
instance[0].destroy();
@ -1007,14 +999,13 @@ const forceTooltipDOMCleanup = () => {
console.warn('Error destroying tooltip instance:', e);
}
}
window.TooltipManager.activeTooltips.delete(id);
}
});
}
if (removedCount > 0) {
// console.log(`Tooltip cleanup: found ${foundCount}, removed ${removedCount} detached tooltips`);
}
};
}
const createTableRow = async (bid) => {
const identity = await IdentityManager.getIdentityData(bid.addr_from);

View file

@ -1,9 +1,11 @@
class TooltipManager {
constructor() {
this.activeTooltips = new Map();
this.sizeCheckIntervals = new Map();
this.activeTooltips = new WeakMap();
this.sizeCheckIntervals = new WeakMap();
this.tooltipIdCounter = 0;
this.setupStyles();
this.setupCleanupEvents();
this.initializeMutationObserver();
}
static initialize() {
@ -19,16 +21,26 @@ class TooltipManager {
this.destroy(element);
const checkSize = () => {
if (!document.body.contains(element)) {
return;
}
const rect = element.getBoundingClientRect();
if (rect.width && rect.height) {
clearInterval(this.sizeCheckIntervals.get(element));
this.sizeCheckIntervals.delete(element);
delete element._tooltipRetryCount;
this.createTooltip(element, content, options, rect);
} else {
const retryCount = element._tooltipRetryCount || 0;
if (retryCount < 5) {
element._tooltipRetryCount = retryCount + 1;
requestAnimationFrame(checkSize);
} else {
delete element._tooltipRetryCount;
}
}
};
this.sizeCheckIntervals.set(element, setInterval(checkSize, 50));
checkSize();
requestAnimationFrame(checkSize);
return null;
}
@ -62,6 +74,8 @@ class TooltipManager {
}
}
const tooltipId = `tooltip-${++this.tooltipIdCounter}`;
const instance = tippy(element, {
content,
allowHTML: true,
@ -75,6 +89,28 @@ class TooltipManager {
theme: '',
moveTransition: 'none',
offset: [0, 10],
onShow(instance) {
if (!document.body.contains(element)) {
return false;
}
const rect = element.getBoundingClientRect();
if (!rect.width || !rect.height) {
return false;
}
return true;
},
onMount(instance) {
if (instance.popper.firstElementChild) {
instance.popper.firstElementChild.classList.add(bgClass);
instance.popper.setAttribute('data-for-tooltip-id', tooltipId);
}
const arrow = instance.popper.querySelector('.tippy-arrow');
if (arrow) {
arrow.style.setProperty('color', arrowColor, 'important');
}
},
popperOptions: {
strategy: 'fixed',
modifiers: [
@ -93,45 +129,11 @@ class TooltipManager {
}
}
]
},
onCreate(instance) {
instance._originalPlacement = instance.props.placement;
},
onShow(instance) {
if (!document.body.contains(element)) {
return false;
}
const rect = element.getBoundingClientRect();
if (!rect.width || !rect.height) {
return false;
}
instance.setProps({
placement: instance._originalPlacement
});
if (instance.popper.firstElementChild) {
instance.popper.firstElementChild.classList.add(bgClass);
}
return true;
},
onMount(instance) {
if (instance.popper.firstElementChild) {
instance.popper.firstElementChild.classList.add(bgClass);
}
const arrow = instance.popper.querySelector('.tippy-arrow');
if (arrow) {
arrow.style.setProperty('color', arrowColor, 'important');
}
}
});
const id = element.getAttribute('data-tooltip-trigger-id') ||
`tooltip-${Math.random().toString(36).substring(7)}`;
element.setAttribute('data-tooltip-trigger-id', id);
this.activeTooltips.set(id, instance);
element.setAttribute('data-tooltip-trigger-id', tooltipId);
this.activeTooltips.set(element, instance);
return instance;
}
@ -139,40 +141,33 @@ class TooltipManager {
destroy(element) {
if (!element) return;
if (this.sizeCheckIntervals.has(element)) {
clearInterval(this.sizeCheckIntervals.get(element));
this.sizeCheckIntervals.delete(element);
}
delete element._tooltipRetryCount;
const id = element.getAttribute('data-tooltip-trigger-id');
if (!id) return;
const instance = this.activeTooltips.get(id);
const instance = this.activeTooltips.get(element);
if (instance?.[0]) {
try {
instance[0].destroy();
} catch (e) {
console.warn('Error destroying tooltip:', e);
const tippyRoot = document.querySelector(`[data-for-tooltip-id="${id}"]`);
if (tippyRoot && tippyRoot.parentNode) {
tippyRoot.parentNode.removeChild(tippyRoot);
}
}
}
this.activeTooltips.delete(id);
this.activeTooltips.delete(element);
element.removeAttribute('data-tooltip-trigger-id');
}
cleanup() {
this.sizeCheckIntervals.forEach((interval) => clearInterval(interval));
this.sizeCheckIntervals.clear();
this.activeTooltips.forEach((instance, id) => {
if (instance?.[0]) {
try {
instance[0].destroy();
} catch (e) {
console.warn('Error cleaning up tooltip:', e);
}
}
document.querySelectorAll('[data-tooltip-trigger-id]').forEach(element => {
this.destroy(element);
});
this.activeTooltips.clear();
document.querySelectorAll('[data-tippy-root]').forEach(element => {
if (element.parentNode) {
@ -181,6 +176,63 @@ class TooltipManager {
});
}
getActiveTooltipInstances() {
const result = [];
document.querySelectorAll('[data-tooltip-trigger-id]').forEach(element => {
const instance = this.activeTooltips.get(element);
if (instance) {
result.push([element, instance]);
}
});
return result;
}
initializeMutationObserver() {
if (this.mutationObserver) return;
this.mutationObserver = new MutationObserver(mutations => {
let needsCleanup = false;
mutations.forEach(mutation => {
if (mutation.removedNodes.length) {
Array.from(mutation.removedNodes).forEach(node => {
if (node.nodeType === 1) {
if (node.hasAttribute && node.hasAttribute('data-tooltip-trigger-id')) {
this.destroy(node);
needsCleanup = true;
}
if (node.querySelectorAll) {
node.querySelectorAll('[data-tooltip-trigger-id]').forEach(el => {
this.destroy(el);
needsCleanup = true;
});
}
}
});
}
});
if (needsCleanup) {
document.querySelectorAll('[data-tippy-root]').forEach(element => {
const id = element.getAttribute('data-for-tooltip-id');
if (id && !document.querySelector(`[data-tooltip-trigger-id="${id}"]`)) {
if (element.parentNode) {
element.parentNode.removeChild(element);
}
}
});
}
});
this.mutationObserver.observe(document.body, {
childList: true,
subtree: true
});
}
setupStyles() {
if (document.getElementById('tooltip-styles')) return;
@ -274,13 +326,22 @@ class TooltipManager {
}
setupCleanupEvents() {
window.addEventListener('beforeunload', () => this.cleanup());
window.addEventListener('unload', () => this.cleanup());
document.addEventListener('visibilitychange', () => {
this.boundCleanup = this.cleanup.bind(this);
this.handleVisibilityChange = () => {
if (document.hidden) {
this.cleanup();
}
});
};
window.addEventListener('beforeunload', this.boundCleanup);
window.addEventListener('unload', this.boundCleanup);
document.addEventListener('visibilitychange', this.handleVisibilityChange);
}
removeCleanupEvents() {
window.removeEventListener('beforeunload', this.boundCleanup);
window.removeEventListener('unload', this.boundCleanup);
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
}
initializeTooltips(selector = '[data-tooltip-target]') {
@ -295,6 +356,26 @@ class TooltipManager {
}
});
}
dispose() {
this.cleanup();
if (this.mutationObserver) {
this.mutationObserver.disconnect();
this.mutationObserver = null;
}
this.removeCleanupEvents();
const styleElement = document.getElementById('tooltip-styles');
if (styleElement && styleElement.parentNode) {
styleElement.parentNode.removeChild(styleElement);
}
if (window.TooltipManager === this) {
window.TooltipManager = null;
}
}
}
if (typeof module !== 'undefined' && module.exports) {