mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-05 18:38:09 +01:00
900 lines
33 KiB
JavaScript
900 lines
33 KiB
JavaScript
const TooltipManager = (function() {
|
|
let instance = null;
|
|
const tooltipInstanceMap = new WeakMap();
|
|
|
|
class TooltipManagerImpl {
|
|
constructor() {
|
|
if (instance) {
|
|
return instance;
|
|
}
|
|
|
|
this.tooltipIdCounter = 0;
|
|
this.maxTooltips = 200;
|
|
this.cleanupThreshold = 1.2;
|
|
this.debug = false;
|
|
this.tooltipData = new WeakMap();
|
|
this.resources = {};
|
|
|
|
if (window.CleanupManager) {
|
|
CleanupManager.registerResource(
|
|
'tooltipManager',
|
|
this,
|
|
(manager) => manager.dispose()
|
|
);
|
|
}
|
|
|
|
instance = this;
|
|
}
|
|
|
|
log(message, ...args) {
|
|
if (this.debug) {
|
|
console.log(`[TooltipManager] ${message}`, ...args);
|
|
}
|
|
}
|
|
|
|
create(element, content, options = {}) {
|
|
if (!element || !document.body.contains(element)) return null;
|
|
|
|
if (!document.contains(element)) {
|
|
this.log('Tried to create tooltip for detached element');
|
|
return null;
|
|
}
|
|
|
|
this.destroy(element);
|
|
|
|
const currentTooltipCount = document.querySelectorAll('[data-tooltip-trigger-id]').length;
|
|
if (currentTooltipCount > this.maxTooltips * this.cleanupThreshold) {
|
|
this.cleanupOrphanedTooltips();
|
|
this.performPeriodicCleanup(true);
|
|
}
|
|
|
|
const createTooltip = () => {
|
|
if (!document.body.contains(element)) return;
|
|
|
|
const rect = element.getBoundingClientRect();
|
|
if (rect.width > 0 && rect.height > 0) {
|
|
this.createTooltipInstance(element, content, options);
|
|
} else {
|
|
let retryCount = 0;
|
|
const maxRetries = 3;
|
|
|
|
const retryCreate = () => {
|
|
const newRect = element.getBoundingClientRect();
|
|
if ((newRect.width > 0 && newRect.height > 0) || retryCount >= maxRetries) {
|
|
if (newRect.width > 0 && newRect.height > 0) {
|
|
this.createTooltipInstance(element, content, options);
|
|
}
|
|
} else {
|
|
retryCount++;
|
|
CleanupManager.setTimeout(() => {
|
|
CleanupManager.requestAnimationFrame(retryCreate);
|
|
}, 100);
|
|
}
|
|
};
|
|
|
|
CleanupManager.setTimeout(() => {
|
|
CleanupManager.requestAnimationFrame(retryCreate);
|
|
}, 100);
|
|
}
|
|
};
|
|
|
|
CleanupManager.requestAnimationFrame(createTooltip);
|
|
return null;
|
|
}
|
|
|
|
createTooltipInstance(element, content, options = {}) {
|
|
if (!element || !document.body.contains(element)) {
|
|
return null;
|
|
}
|
|
|
|
if (typeof window.tippy !== 'function') {
|
|
console.error('Tippy.js is not available.');
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const tooltipId = `tooltip-${++this.tooltipIdCounter}`;
|
|
|
|
const tooltipOptions = {
|
|
content: content,
|
|
allowHTML: true,
|
|
placement: options.placement || 'top',
|
|
appendTo: document.body,
|
|
animation: false,
|
|
duration: 0,
|
|
delay: 0,
|
|
interactive: true,
|
|
arrow: false,
|
|
theme: '',
|
|
moveTransition: 'none',
|
|
offset: [0, 10],
|
|
onShow(instance) {
|
|
if (!document.body.contains(element)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
onMount(instance) {
|
|
if (instance.popper && instance.popper.firstElementChild) {
|
|
const bgClass = options.bgClass || 'bg-gray-400';
|
|
instance.popper.firstElementChild.classList.add(bgClass);
|
|
instance.popper.setAttribute('data-for-tooltip-id', tooltipId);
|
|
}
|
|
const arrow = instance.popper.querySelector('.tippy-arrow');
|
|
if (arrow) {
|
|
const arrowColor = options.arrowColor || 'rgb(156 163 175)';
|
|
arrow.style.setProperty('color', arrowColor, 'important');
|
|
}
|
|
},
|
|
onHidden(instance) {
|
|
if (!document.body.contains(element)) {
|
|
CleanupManager.setTimeout(() => {
|
|
if (instance && instance.destroy) {
|
|
instance.destroy();
|
|
}
|
|
}, 100);
|
|
}
|
|
},
|
|
popperOptions: {
|
|
strategy: 'fixed',
|
|
modifiers: [
|
|
{
|
|
name: 'preventOverflow',
|
|
options: {
|
|
boundary: 'viewport',
|
|
padding: 10
|
|
}
|
|
},
|
|
{
|
|
name: 'flip',
|
|
options: {
|
|
padding: 10,
|
|
fallbackPlacements: ['top', 'bottom', 'right', 'left']
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
const tippyInstance = window.tippy(element, tooltipOptions);
|
|
|
|
if (tippyInstance && Array.isArray(tippyInstance) && tippyInstance[0]) {
|
|
this.tooltipData.set(element, {
|
|
id: tooltipId,
|
|
instance: tippyInstance[0],
|
|
timestamp: Date.now()
|
|
});
|
|
|
|
element.setAttribute('data-tooltip-trigger-id', tooltipId);
|
|
tooltipInstanceMap.set(element, tippyInstance[0]);
|
|
|
|
const resourceId = CleanupManager.registerResource(
|
|
'tooltip',
|
|
{ element, instance: tippyInstance[0] },
|
|
(resource) => {
|
|
try {
|
|
if (resource.instance && resource.instance.destroy) {
|
|
resource.instance.destroy();
|
|
}
|
|
if (resource.element) {
|
|
resource.element.removeAttribute('data-tooltip-trigger-id');
|
|
resource.element.removeAttribute('aria-describedby');
|
|
}
|
|
} catch (e) {
|
|
console.warn('Error destroying tooltip during cleanup:', e);
|
|
}
|
|
}
|
|
);
|
|
|
|
return tippyInstance[0];
|
|
}
|
|
|
|
return null;
|
|
} catch (error) {
|
|
console.error('Error creating tooltip:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
destroy(element) {
|
|
if (!element) return;
|
|
|
|
try {
|
|
const tooltipId = element.getAttribute('data-tooltip-trigger-id');
|
|
if (!tooltipId) return;
|
|
|
|
const tooltipData = this.tooltipData.get(element);
|
|
const instance = tooltipData?.instance || tooltipInstanceMap.get(element);
|
|
|
|
if (instance) {
|
|
try {
|
|
instance.destroy();
|
|
} catch (e) {
|
|
console.warn('Error destroying tooltip instance:', e);
|
|
}
|
|
}
|
|
|
|
element.removeAttribute('data-tooltip-trigger-id');
|
|
element.removeAttribute('aria-describedby');
|
|
|
|
const tippyRoot = document.querySelector(`[data-for-tooltip-id="${tooltipId}"]`);
|
|
if (tippyRoot && tippyRoot.parentNode) {
|
|
tippyRoot.parentNode.removeChild(tippyRoot);
|
|
}
|
|
|
|
this.tooltipData.delete(element);
|
|
tooltipInstanceMap.delete(element);
|
|
} catch (error) {
|
|
console.error('Error destroying tooltip:', error);
|
|
}
|
|
}
|
|
|
|
getActiveTooltipInstances() {
|
|
const result = [];
|
|
try {
|
|
document.querySelectorAll('[data-tooltip-trigger-id]').forEach(element => {
|
|
const instance = element._tippy ? [element._tippy] : null;
|
|
if (instance) {
|
|
result.push([element, instance]);
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Error getting active tooltip instances:', error);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
cleanup() {
|
|
this.log('Running tooltip cleanup');
|
|
|
|
try {
|
|
if ((window.location.pathname.includes('/offers') || window.location.pathname.includes('/bids')) &&
|
|
(document.querySelector('[data-tippy-root]:hover') || document.querySelector('[data-tooltip-trigger-id]:hover'))) {
|
|
console.log('Skipping tooltip cleanup - tooltip is being hovered');
|
|
return;
|
|
}
|
|
|
|
const elements = document.querySelectorAll('[data-tooltip-trigger-id]:not(:hover)');
|
|
const batchSize = 20;
|
|
|
|
const processElementsBatch = (startIdx) => {
|
|
const endIdx = Math.min(startIdx + batchSize, elements.length);
|
|
|
|
for (let i = startIdx; i < endIdx; i++) {
|
|
this.destroy(elements[i]);
|
|
}
|
|
|
|
if (endIdx < elements.length) {
|
|
CleanupManager.requestAnimationFrame(() => {
|
|
processElementsBatch(endIdx);
|
|
});
|
|
} else {
|
|
this.cleanupOrphanedTooltips();
|
|
}
|
|
};
|
|
|
|
if (elements.length > 0) {
|
|
processElementsBatch(0);
|
|
} else {
|
|
this.cleanupOrphanedTooltips();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error during cleanup:', error);
|
|
}
|
|
}
|
|
|
|
thoroughCleanup() {
|
|
this.log('Running thorough tooltip cleanup');
|
|
|
|
try {
|
|
this.cleanup();
|
|
this.cleanupAllTooltips();
|
|
this.log('Thorough tooltip cleanup completed');
|
|
} catch (error) {
|
|
console.error('Error in thorough tooltip cleanup:', error);
|
|
}
|
|
}
|
|
|
|
cleanupAllTooltips() {
|
|
this.log('Cleaning up all tooltips');
|
|
|
|
try {
|
|
if ((window.location.pathname.includes('/offers') || window.location.pathname.includes('/bids')) &&
|
|
document.querySelector('#offers-body tr:hover')) {
|
|
this.log('Skipping all tooltips cleanup on offers/bids page with row hover');
|
|
return;
|
|
}
|
|
|
|
const tooltipRoots = document.querySelectorAll('[data-tippy-root]');
|
|
const tooltipTriggers = document.querySelectorAll('[data-tooltip-trigger-id]');
|
|
const tooltipElements = document.querySelectorAll('.tooltip');
|
|
|
|
const isHovered = element => {
|
|
try {
|
|
return element.matches && element.matches(':hover');
|
|
} catch (e) {
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
tooltipRoots.forEach(root => {
|
|
if (!isHovered(root) && root.parentNode) {
|
|
root.parentNode.removeChild(root);
|
|
}
|
|
});
|
|
|
|
tooltipTriggers.forEach(trigger => {
|
|
if (!isHovered(trigger)) {
|
|
trigger.removeAttribute('data-tooltip-trigger-id');
|
|
trigger.removeAttribute('aria-describedby');
|
|
|
|
if (trigger._tippy) {
|
|
try {
|
|
trigger._tippy.destroy();
|
|
trigger._tippy = null;
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
});
|
|
|
|
tooltipElements.forEach(tooltip => {
|
|
if (!isHovered(tooltip) && tooltip.parentNode) {
|
|
let closestHoveredRow = false;
|
|
|
|
try {
|
|
if (tooltip.closest && tooltip.closest('tr') && isHovered(tooltip.closest('tr'))) {
|
|
closestHoveredRow = true;
|
|
}
|
|
} catch (e) {}
|
|
|
|
if (!closestHoveredRow) {
|
|
const style = window.getComputedStyle(tooltip);
|
|
const isVisible = style.display !== 'none' &&
|
|
style.visibility !== 'hidden' &&
|
|
style.opacity !== '0';
|
|
|
|
if (!isVisible) {
|
|
tooltip.parentNode.removeChild(tooltip);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Error cleaning up all tooltips:', error);
|
|
}
|
|
}
|
|
|
|
cleanupOrphanedTooltips() {
|
|
try {
|
|
const tippyElements = document.querySelectorAll('[data-tippy-root]');
|
|
let removed = 0;
|
|
|
|
tippyElements.forEach(element => {
|
|
const tooltipId = element.getAttribute('data-for-tooltip-id');
|
|
const trigger = tooltipId ?
|
|
document.querySelector(`[data-tooltip-trigger-id="${tooltipId}"]`) :
|
|
null;
|
|
|
|
if (!trigger || !document.body.contains(trigger)) {
|
|
if (element.parentNode) {
|
|
element.parentNode.removeChild(element);
|
|
removed++;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (removed > 0) {
|
|
this.log(`Removed ${removed} orphaned tooltip elements`);
|
|
}
|
|
|
|
return removed;
|
|
} catch (error) {
|
|
console.error('Error cleaning up orphaned tooltips:', error);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
setupMutationObserver() {
|
|
try {
|
|
const mutationObserver = new MutationObserver(mutations => {
|
|
let needsCleanup = false;
|
|
|
|
mutations.forEach(mutation => {
|
|
if (mutation.removedNodes.length) {
|
|
Array.from(mutation.removedNodes).forEach(node => {
|
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
if (node.hasAttribute && node.hasAttribute('data-tooltip-trigger-id')) {
|
|
this.destroy(node);
|
|
needsCleanup = true;
|
|
}
|
|
|
|
if (node.querySelectorAll) {
|
|
const tooltipTriggers = node.querySelectorAll('[data-tooltip-trigger-id]');
|
|
if (tooltipTriggers.length > 0) {
|
|
tooltipTriggers.forEach(trigger => {
|
|
this.destroy(trigger);
|
|
});
|
|
needsCleanup = true;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
if (needsCleanup) {
|
|
this.cleanupOrphanedTooltips();
|
|
}
|
|
});
|
|
|
|
mutationObserver.observe(document.body, {
|
|
childList: true,
|
|
subtree: true
|
|
});
|
|
|
|
this.resources.mutationObserver = CleanupManager.registerResource(
|
|
'mutationObserver',
|
|
mutationObserver,
|
|
(observer) => observer.disconnect()
|
|
);
|
|
|
|
return mutationObserver;
|
|
} catch (error) {
|
|
console.error('Error setting up mutation observer:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
startDisconnectedElementsCheck() {
|
|
try {
|
|
this.resources.disconnectedCheckInterval = CleanupManager.setInterval(() => {
|
|
this.checkForDisconnectedElements();
|
|
}, 60000);
|
|
} catch (error) {
|
|
console.error('Error starting disconnected elements check:', error);
|
|
}
|
|
}
|
|
|
|
checkForDisconnectedElements() {
|
|
try {
|
|
const elements = document.querySelectorAll('[data-tooltip-trigger-id]');
|
|
let removedCount = 0;
|
|
|
|
elements.forEach(element => {
|
|
if (!document.body.contains(element)) {
|
|
this.destroy(element);
|
|
removedCount++;
|
|
}
|
|
});
|
|
|
|
if (removedCount > 0) {
|
|
this.log(`Removed ${removedCount} tooltips for disconnected elements`);
|
|
this.cleanupOrphanedTooltips();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error checking for disconnected elements:', error);
|
|
}
|
|
}
|
|
|
|
startPeriodicCleanup() {
|
|
try {
|
|
this.resources.cleanupInterval = CleanupManager.setInterval(() => {
|
|
this.performPeriodicCleanup();
|
|
}, 120000);
|
|
} catch (error) {
|
|
console.error('Error starting periodic cleanup:', error);
|
|
}
|
|
}
|
|
|
|
performPeriodicCleanup(force = false) {
|
|
try {
|
|
if ((window.location.pathname.includes('/offers') || window.location.pathname.includes('/bids')) &&
|
|
!force) {
|
|
return;
|
|
}
|
|
|
|
this.cleanupOrphanedTooltips();
|
|
this.checkForDisconnectedElements();
|
|
|
|
const tooltipCount = document.querySelectorAll('[data-tippy-root]').length;
|
|
|
|
if (force || tooltipCount > this.maxTooltips) {
|
|
this.log(`Performing aggressive cleanup (${tooltipCount} tooltips)`);
|
|
this.cleanup();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error performing periodic cleanup:', error);
|
|
}
|
|
}
|
|
|
|
setupStyles() {
|
|
if (document.getElementById('tooltip-styles')) return;
|
|
|
|
try {
|
|
const style = document.createElement('style');
|
|
style.id = 'tooltip-styles';
|
|
style.textContent = `
|
|
[data-tippy-root] {
|
|
position: fixed !important;
|
|
z-index: 9999 !important;
|
|
pointer-events: none !important;
|
|
}
|
|
|
|
.tippy-box {
|
|
font-size: 0.875rem;
|
|
line-height: 1.25rem;
|
|
font-weight: 500;
|
|
border-radius: 0.5rem;
|
|
color: white;
|
|
position: relative !important;
|
|
pointer-events: auto !important;
|
|
}
|
|
|
|
.tippy-content {
|
|
padding: 0.5rem 0.75rem !important;
|
|
}
|
|
|
|
.tippy-box .bg-gray-400 {
|
|
background-color: rgb(156 163 175);
|
|
padding: 0.5rem 0.75rem;
|
|
}
|
|
.tippy-box:has(.bg-gray-400) .tippy-arrow {
|
|
color: rgb(156 163 175);
|
|
}
|
|
|
|
.tippy-box .bg-red-500 {
|
|
background-color: rgb(239 68 68);
|
|
padding: 0.5rem 0.75rem;
|
|
}
|
|
.tippy-box:has(.bg-red-500) .tippy-arrow {
|
|
color: rgb(239 68 68);
|
|
}
|
|
|
|
.tippy-box .bg-gray-300 {
|
|
background-color: rgb(209 213 219);
|
|
padding: 0.5rem 0.75rem;
|
|
}
|
|
.tippy-box:has(.bg-gray-300) .tippy-arrow {
|
|
color: rgb(209 213 219);
|
|
}
|
|
|
|
.tippy-box .bg-green-700 {
|
|
background-color: rgb(21 128 61);
|
|
padding: 0.5rem 0.75rem;
|
|
}
|
|
.tippy-box:has(.bg-green-700) .tippy-arrow {
|
|
color: rgb(21 128 61);
|
|
}
|
|
|
|
.tippy-box[data-placement^='top'] > .tippy-arrow::before {
|
|
border-top-color: currentColor;
|
|
}
|
|
|
|
.tippy-box[data-placement^='bottom'] > .tippy-arrow::before {
|
|
border-bottom-color: currentColor;
|
|
}
|
|
|
|
.tippy-box[data-placement^='left'] > .tippy-arrow::before {
|
|
border-left-color: currentColor;
|
|
}
|
|
|
|
.tippy-box[data-placement^='right'] > .tippy-arrow::before {
|
|
border-right-color: currentColor;
|
|
}
|
|
|
|
.tippy-box[data-placement^='top'] > .tippy-arrow {
|
|
bottom: 0;
|
|
}
|
|
|
|
.tippy-box[data-placement^='bottom'] > .tippy-arrow {
|
|
top: 0;
|
|
}
|
|
|
|
.tippy-box[data-placement^='left'] > .tippy-arrow {
|
|
right: 0;
|
|
}
|
|
|
|
.tippy-box[data-placement^='right'] > .tippy-arrow {
|
|
left: 0;
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
|
|
this.resources.tooltipStyles = CleanupManager.registerResource(
|
|
'tooltipStyles',
|
|
style,
|
|
(styleElement) => {
|
|
if (styleElement && styleElement.parentNode) {
|
|
styleElement.parentNode.removeChild(styleElement);
|
|
}
|
|
}
|
|
);
|
|
} catch (error) {
|
|
console.error('Error setting up styles:', error);
|
|
try {
|
|
document.head.insertAdjacentHTML('beforeend', `
|
|
<style id="tooltip-styles">
|
|
[data-tippy-root] {
|
|
position: fixed !important;
|
|
z-index: 9999 !important;
|
|
pointer-events: none !important;
|
|
}
|
|
|
|
.tippy-box {
|
|
font-size: 0.875rem;
|
|
line-height: 1.25rem;
|
|
font-weight: 500;
|
|
border-radius: 0.5rem;
|
|
color: white;
|
|
position: relative !important;
|
|
pointer-events: auto !important;
|
|
}
|
|
|
|
.tippy-content {
|
|
padding: 0.5rem 0.75rem !important;
|
|
}
|
|
|
|
.tippy-box .bg-gray-400 {
|
|
background-color: rgb(156 163 175);
|
|
padding: 0.5rem 0.75rem;
|
|
}
|
|
.tippy-box:has(.bg-gray-400) .tippy-arrow {
|
|
color: rgb(156 163 175);
|
|
}
|
|
|
|
.tippy-box .bg-red-500 {
|
|
background-color: rgb(239 68 68);
|
|
padding: 0.5rem 0.75rem;
|
|
}
|
|
.tippy-box:has(.bg-red-500) .tippy-arrow {
|
|
color: rgb(239 68 68);
|
|
}
|
|
|
|
.tippy-box .bg-gray-300 {
|
|
background-color: rgb(209 213 219);
|
|
padding: 0.5rem 0.75rem;
|
|
}
|
|
.tippy-box:has(.bg-gray-300) .tippy-arrow {
|
|
color: rgb(209 213 219);
|
|
}
|
|
|
|
.tippy-box .bg-green-700 {
|
|
background-color: rgb(21 128 61);
|
|
padding: 0.5rem 0.75rem;
|
|
}
|
|
.tippy-box:has(.bg-green-700) .tippy-arrow {
|
|
color: rgb(21 128 61);
|
|
}
|
|
|
|
.tippy-box[data-placement^='top'] > .tippy-arrow::before {
|
|
border-top-color: currentColor;
|
|
}
|
|
|
|
.tippy-box[data-placement^='bottom'] > .tippy-arrow::before {
|
|
border-bottom-color: currentColor;
|
|
}
|
|
|
|
.tippy-box[data-placement^='left'] > .tippy-arrow::before {
|
|
border-left-color: currentColor;
|
|
}
|
|
|
|
.tippy-box[data-placement^='right'] > .tippy-arrow::before {
|
|
border-right-color: currentColor;
|
|
}
|
|
|
|
.tippy-box[data-placement^='top'] > .tippy-arrow {
|
|
bottom: 0;
|
|
}
|
|
|
|
.tippy-box[data-placement^='bottom'] > .tippy-arrow {
|
|
top: 0;
|
|
}
|
|
|
|
.tippy-box[data-placement^='left'] > .tippy-arrow {
|
|
right: 0;
|
|
}
|
|
|
|
.tippy-box[data-placement^='right'] > .tippy-arrow {
|
|
left: 0;
|
|
}
|
|
</style>
|
|
`);
|
|
|
|
const styleElement = document.getElementById('tooltip-styles');
|
|
if (styleElement) {
|
|
this.resources.tooltipStyles = CleanupManager.registerResource(
|
|
'tooltipStyles',
|
|
styleElement,
|
|
(elem) => {
|
|
if (elem && elem.parentNode) {
|
|
elem.parentNode.removeChild(elem);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
} catch (e) {
|
|
console.error('Failed to add tooltip styles:', e);
|
|
}
|
|
}
|
|
}
|
|
|
|
initializeTooltips(selector = '[data-tooltip-target]') {
|
|
try {
|
|
document.querySelectorAll(selector).forEach(element => {
|
|
const targetId = element.getAttribute('data-tooltip-target');
|
|
if (!targetId) return;
|
|
|
|
const tooltipContent = document.getElementById(targetId);
|
|
|
|
if (tooltipContent) {
|
|
this.create(element, tooltipContent.innerHTML, {
|
|
placement: element.getAttribute('data-tooltip-placement') || 'top'
|
|
});
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Error initializing tooltips:', error);
|
|
}
|
|
}
|
|
|
|
dispose() {
|
|
this.log('Disposing TooltipManager');
|
|
|
|
try {
|
|
this.cleanup();
|
|
|
|
Object.values(this.resources).forEach(resourceId => {
|
|
if (resourceId) {
|
|
CleanupManager.unregisterResource(resourceId);
|
|
}
|
|
});
|
|
|
|
this.resources = {};
|
|
|
|
instance = null;
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Error disposing TooltipManager:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
setDebugMode(enabled) {
|
|
this.debug = Boolean(enabled);
|
|
return this.debug;
|
|
}
|
|
|
|
initialize(options = {}) {
|
|
try {
|
|
if (options.maxTooltips) {
|
|
this.maxTooltips = options.maxTooltips;
|
|
}
|
|
|
|
if (options.debug !== undefined) {
|
|
this.setDebugMode(options.debug);
|
|
}
|
|
|
|
this.setupStyles();
|
|
this.setupMutationObserver();
|
|
this.startPeriodicCleanup();
|
|
this.startDisconnectedElementsCheck();
|
|
|
|
this.log('TooltipManager initialized');
|
|
return this;
|
|
} catch (error) {
|
|
console.error('Error initializing TooltipManager:', error);
|
|
return this;
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
initialize: function(options = {}) {
|
|
if (!instance) {
|
|
const manager = new TooltipManagerImpl();
|
|
manager.initialize(options);
|
|
}
|
|
return instance;
|
|
},
|
|
|
|
getInstance: function() {
|
|
if (!instance) {
|
|
this.initialize();
|
|
}
|
|
return instance;
|
|
},
|
|
|
|
create: function(...args) {
|
|
const manager = this.getInstance();
|
|
return manager.create(...args);
|
|
},
|
|
|
|
destroy: function(...args) {
|
|
const manager = this.getInstance();
|
|
return manager.destroy(...args);
|
|
},
|
|
|
|
cleanup: function(...args) {
|
|
const manager = this.getInstance();
|
|
return manager.cleanup(...args);
|
|
},
|
|
|
|
thoroughCleanup: function() {
|
|
const manager = this.getInstance();
|
|
return manager.thoroughCleanup();
|
|
},
|
|
|
|
initializeTooltips: function(...args) {
|
|
const manager = this.getInstance();
|
|
return manager.initializeTooltips(...args);
|
|
},
|
|
|
|
setDebugMode: function(enabled) {
|
|
const manager = this.getInstance();
|
|
return manager.setDebugMode(enabled);
|
|
},
|
|
|
|
getActiveTooltipInstances: function() {
|
|
const manager = this.getInstance();
|
|
return manager.getActiveTooltipInstances();
|
|
},
|
|
|
|
dispose: function(...args) {
|
|
const manager = this.getInstance();
|
|
return manager.dispose(...args);
|
|
}
|
|
};
|
|
})();
|
|
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = TooltipManager;
|
|
}
|
|
|
|
if (typeof window !== 'undefined') {
|
|
window.TooltipManager = TooltipManager;
|
|
}
|
|
|
|
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
|
function initializeTooltipManager() {
|
|
if (!window.tooltipManagerInitialized) {
|
|
|
|
if (!window.CleanupManager) {
|
|
console.warn('CleanupManager not found. TooltipManager will run with limited functionality.');
|
|
|
|
window.CleanupManager = window.CleanupManager || {
|
|
registerResource: (type, resource, cleanup) => {
|
|
return Math.random().toString(36).substring(2, 9);
|
|
},
|
|
unregisterResource: () => {},
|
|
setTimeout: (callback, delay) => setTimeout(callback, delay),
|
|
setInterval: (callback, delay) => setInterval(callback, delay),
|
|
requestAnimationFrame: (callback) => requestAnimationFrame(callback),
|
|
addListener: (element, type, handler, options) => {
|
|
element.addEventListener(type, handler, options);
|
|
return handler;
|
|
}
|
|
};
|
|
}
|
|
|
|
window.TooltipManager.initialize({
|
|
maxTooltips: 200,
|
|
debug: false
|
|
});
|
|
|
|
window.TooltipManager.initializeTooltips();
|
|
window.tooltipManagerInitialized = true;
|
|
}
|
|
}
|
|
|
|
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
|
initializeTooltipManager();
|
|
} else {
|
|
document.addEventListener('DOMContentLoaded', initializeTooltipManager, { once: true });
|
|
}
|
|
}
|
|
|
|
if (typeof window !== 'undefined' && typeof console !== 'undefined') {
|
|
console.log('TooltipManager initialized');
|
|
}
|