123 lines
No EOL
4.6 KiB
JavaScript
123 lines
No EOL
4.6 KiB
JavaScript
/**
|
|
* tooltip.js
|
|
*
|
|
* This script uses FloatingUI to create modern, styled tooltips for elements with the
|
|
* custom attribute "data-tooltip-html". The tooltips are styled using Tailwind CSS classes
|
|
* to support both light and dark themes and include a dynamically positioned arrow.
|
|
*
|
|
* Make sure the FloatingUIDOM global is available.
|
|
* For example, include in your base template:
|
|
* <script src="https://unpkg.com/@floating-ui/dom"></script>
|
|
*/
|
|
|
|
const { computePosition, offset, flip, shift, arrow } = FloatingUIDOM;
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
document.querySelectorAll('[data-tooltip-html]').forEach((el) => {
|
|
let tooltipContainer = null;
|
|
let arrowElement = null;
|
|
let fadeOutTimeout;
|
|
|
|
const showTooltip = () => {
|
|
if (tooltipContainer) return; // Tooltip already visible
|
|
|
|
// Retrieve the custom HTML content from the data attribute
|
|
const tooltipContent = el.getAttribute('data-tooltip-html');
|
|
|
|
// Create a container for the tooltip (with modern styling)
|
|
tooltipContainer = document.createElement('div');
|
|
tooltipContainer.classList.add(
|
|
'bg-black', 'text-white',
|
|
'shadow-lg', 'rounded-lg', 'p-2',
|
|
// Transition classes for simple fade in/out
|
|
'transition-opacity', 'duration-200', 'opacity-0'
|
|
);
|
|
tooltipContainer.style.position = 'absolute';
|
|
tooltipContainer.style.zIndex = '9999';
|
|
|
|
// Set the HTML content for the tooltip
|
|
tooltipContainer.innerHTML = '<div class="p-2">' + tooltipContent + '</div>';
|
|
|
|
// Create the arrow element. The arrow is styled as a small rotated square.
|
|
arrowElement = document.createElement('div');
|
|
arrowElement.classList.add(
|
|
'w-3', 'h-3',
|
|
'bg-black',
|
|
'transform', 'rotate-45'
|
|
);
|
|
arrowElement.style.position = 'absolute';
|
|
|
|
// Append the arrow into the tooltip container
|
|
tooltipContainer.appendChild(arrowElement);
|
|
|
|
// Append the tooltip container to the document body
|
|
document.body.appendChild(tooltipContainer);
|
|
|
|
// Use Floating UI to position the tooltip, including the arrow middleware
|
|
computePosition(el, tooltipContainer, {
|
|
middleware: [
|
|
offset(8),
|
|
flip(),
|
|
shift({ padding: 5 }),
|
|
arrow({ element: arrowElement })
|
|
]
|
|
}).then(({ x, y, placement, middlewareData }) => {
|
|
Object.assign(tooltipContainer.style, {
|
|
left: `${x}px`,
|
|
top: `${y}px`
|
|
});
|
|
|
|
// Position the arrow using the arrow middleware data
|
|
const { x: arrowX, y: arrowY } = middlewareData.arrow || {};
|
|
|
|
// Reset any previous inline values
|
|
arrowElement.style.left = '';
|
|
arrowElement.style.top = '';
|
|
arrowElement.style.right = '';
|
|
arrowElement.style.bottom = '';
|
|
|
|
// Adjust the arrow's position according to the placement
|
|
if (placement.startsWith('top')) {
|
|
arrowElement.style.bottom = '-4px';
|
|
arrowElement.style.left = arrowX !== undefined ? `${arrowX}px` : '50%';
|
|
} else if (placement.startsWith('bottom')) {
|
|
arrowElement.style.top = '-4px';
|
|
arrowElement.style.left = arrowX !== undefined ? `${arrowX}px` : '50%';
|
|
} else if (placement.startsWith('left')) {
|
|
arrowElement.style.right = '-4px';
|
|
arrowElement.style.top = arrowY !== undefined ? `${arrowY}px` : '50%';
|
|
} else if (placement.startsWith('right')) {
|
|
arrowElement.style.left = '-4px';
|
|
arrowElement.style.top = arrowY !== undefined ? `${arrowY}px` : '50%';
|
|
}
|
|
});
|
|
|
|
// Trigger a fade-in by moving from opacity-0 to opacity-100
|
|
requestAnimationFrame(() => {
|
|
tooltipContainer.classList.remove('opacity-0');
|
|
tooltipContainer.classList.add('opacity-100');
|
|
});
|
|
};
|
|
|
|
const hideTooltip = () => {
|
|
if (tooltipContainer) {
|
|
tooltipContainer.classList.remove('opacity-100');
|
|
tooltipContainer.classList.add('opacity-0');
|
|
// Remove the tooltip from the DOM after the transition duration
|
|
fadeOutTimeout = setTimeout(() => {
|
|
if (tooltipContainer && tooltipContainer.parentNode) {
|
|
tooltipContainer.parentNode.removeChild(tooltipContainer);
|
|
}
|
|
tooltipContainer = null;
|
|
arrowElement = null;
|
|
}, 200); // Matches the duration-200 class (200ms)
|
|
}
|
|
};
|
|
|
|
// Attach event listeners to show/hide the tooltip
|
|
el.addEventListener('mouseenter', showTooltip);
|
|
el.addEventListener('mouseleave', hideTooltip);
|
|
el.addEventListener('focus', showTooltip);
|
|
el.addEventListener('blur', hideTooltip);
|
|
});
|
|
});
|