/** * Easy Invoice Tooltip Manager * Reusable tooltip system for the Easy Invoice plugin */ (function($) { 'use strict'; // Tooltip Manager object window.EasyInvoiceTooltip = { // Initialize tooltip system init: function() { this.setupTooltipContainer(); this.bindEvents(); }, // Create tooltip container if it doesn't exist setupTooltipContainer: function() { if ($('#easy-invoice-tooltip').length === 0) { var tooltipHTML = `
`; $('body').append(tooltipHTML); } }, // Bind tooltip events bindEvents: function() { var self = this; // Mouse enter event $(document).off('mouseenter', '[data-tooltip]').on('mouseenter', '[data-tooltip]', function(e) { var tooltipText = $(this).data('tooltip'); var position = $(this).data('tooltip-position') || 'top'; // top, bottom, left, right var delay = $(this).data('tooltip-delay') || 200; self.showTooltip($(this), tooltipText, position, delay); }); // Mouse leave event $(document).off('mouseleave', '[data-tooltip]').on('mouseleave', '[data-tooltip]', function() { self.hideTooltip(); }); // Hide tooltip on scroll $(window).off('scroll.tooltip').on('scroll.tooltip', function() { self.hideTooltip(); }); }, // Show tooltip showTooltip: function($element, text, position, delay) { var self = this; var $tooltip = $('#easy-invoice-tooltip'); // Set tooltip content $tooltip.find('.tooltip-content').text(text); // Clear any existing timeout if (self.showTimeout) { clearTimeout(self.showTimeout); } // Show tooltip after delay self.showTimeout = setTimeout(function() { // Disable transitions temporarily to prevent sliding $tooltip.css('transition', 'none'); // First make tooltip visible but transparent to get dimensions $tooltip.show().css('opacity', '0').removeClass('opacity-0 scale-95'); // Position tooltip self.positionTooltip($element, $tooltip, position); // Re-enable transitions and make it visible with animation setTimeout(function() { $tooltip.css('transition', 'all 0.2s ease'); $tooltip.addClass('opacity-100 scale-100').css('opacity', '1'); }, 10); }, delay); }, // Hide tooltip hideTooltip: function() { var $tooltip = $('#easy-invoice-tooltip'); // Clear any existing timeout if (this.showTimeout) { clearTimeout(this.showTimeout); } $tooltip.removeClass('opacity-100 scale-100').addClass('opacity-0 scale-95'); setTimeout(function() { $tooltip.hide(); }, 200); }, // Position tooltip positionTooltip: function($element, $tooltip, position) { var elementRect = $element[0].getBoundingClientRect(); var $arrow = $tooltip.find('.tooltip-arrow'); // Get tooltip dimensions after it's visible var tooltipWidth = $tooltip.outerWidth(); var tooltipHeight = $tooltip.outerHeight(); var left, top; var arrowStyles = {}; var offset = 12; // Space between tooltip and element // Calculate position based on preference switch (position) { case 'top': left = elementRect.left + (elementRect.width / 2) - (tooltipWidth / 2); top = elementRect.top - tooltipHeight - offset; arrowStyles = { 'position': 'absolute', 'top': '100%', 'left': '50%', 'transform': 'translateX(-50%)', 'width': '0', 'height': '0', 'border-left': '6px solid transparent', 'border-right': '6px solid transparent', 'border-top': '6px solid #111827', 'border-bottom': 'none' }; break; case 'bottom': left = elementRect.left + (elementRect.width / 2) - (tooltipWidth / 2); top = elementRect.bottom + offset; arrowStyles = { 'position': 'absolute', 'bottom': '100%', 'left': '50%', 'transform': 'translateX(-50%)', 'width': '0', 'height': '0', 'border-left': '6px solid transparent', 'border-right': '6px solid transparent', 'border-bottom': '6px solid #111827', 'border-top': 'none' }; break; case 'left': left = elementRect.left - tooltipWidth - offset; top = elementRect.top + (elementRect.height / 2) - (tooltipHeight / 2); arrowStyles = { 'position': 'absolute', 'left': '100%', 'top': '50%', 'transform': 'translateY(-50%)', 'width': '0', 'height': '0', 'border-top': '6px solid transparent', 'border-bottom': '6px solid transparent', 'border-left': '6px solid #111827', 'border-right': 'none' }; break; case 'right': left = elementRect.right + offset; top = elementRect.top + (elementRect.height / 2) - (tooltipHeight / 2); arrowStyles = { 'position': 'absolute', 'right': '100%', 'top': '50%', 'transform': 'translateY(-50%)', 'width': '0', 'height': '0', 'border-top': '6px solid transparent', 'border-bottom': '6px solid transparent', 'border-right': '6px solid #111827', 'border-left': 'none' }; break; default: left = elementRect.left + (elementRect.width / 2) - (tooltipWidth / 2); top = elementRect.top - tooltipHeight - offset; arrowStyles = { 'position': 'absolute', 'top': '100%', 'left': '50%', 'transform': 'translateX(-50%)', 'width': '0', 'height': '0', 'border-left': '6px solid transparent', 'border-right': '6px solid transparent', 'border-top': '6px solid #111827', 'border-bottom': 'none' }; } // Ensure tooltip doesn't go off screen var adjustedPosition = this.adjustPosition(left, top, { width: tooltipWidth, height: tooltipHeight }, position); left = adjustedPosition.left; top = adjustedPosition.top; // Update arrow styles if position changed if (adjustedPosition.position !== position) { arrowStyles = this.getArrowStyles(adjustedPosition.position); } // Apply positioning $tooltip.css({ left: left + 'px', top: top + 'px' }); // Update arrow with complete styles $arrow.css(arrowStyles); }, // Adjust position to keep tooltip on screen adjustPosition: function(left, top, tooltipRect, position) { var windowWidth = window.innerWidth; var windowHeight = window.innerHeight; var margin = 10; // Check horizontal bounds if (left < margin) { left = margin; } else if (left + tooltipRect.width > windowWidth - margin) { left = windowWidth - tooltipRect.width - margin; } // Check vertical bounds and adjust position if needed if (top < margin) { if (position === 'top') { top = margin; position = 'bottom'; } } else if (top + tooltipRect.height > windowHeight - margin) { if (position === 'bottom') { top = windowHeight - tooltipRect.height - margin; position = 'top'; } } return { left: left, top: top, position: position }; }, // Get arrow styles for position getArrowStyles: function(position) { switch (position) { case 'top': return { 'position': 'absolute', 'top': '100%', 'left': '50%', 'transform': 'translateX(-50%)', 'width': '0', 'height': '0', 'border-left': '6px solid transparent', 'border-right': '6px solid transparent', 'border-top': '6px solid #111827', 'border-bottom': 'none' }; case 'bottom': return { 'position': 'absolute', 'bottom': '100%', 'left': '50%', 'transform': 'translateX(-50%)', 'width': '0', 'height': '0', 'border-left': '6px solid transparent', 'border-right': '6px solid transparent', 'border-bottom': '6px solid #111827', 'border-top': 'none' }; case 'left': return { 'position': 'absolute', 'left': '100%', 'top': '50%', 'transform': 'translateY(-50%)', 'width': '0', 'height': '0', 'border-top': '6px solid transparent', 'border-bottom': '6px solid transparent', 'border-left': '6px solid #111827', 'border-right': 'none' }; case 'right': return { 'position': 'absolute', 'right': '100%', 'top': '50%', 'transform': 'translateY(-50%)', 'width': '0', 'height': '0', 'border-top': '6px solid transparent', 'border-bottom': '6px solid transparent', 'border-right': '6px solid #111827', 'border-left': 'none' }; default: return { 'position': 'absolute', 'top': '100%', 'left': '50%', 'transform': 'translateX(-50%)', 'width': '0', 'height': '0', 'border-left': '6px solid transparent', 'border-right': '6px solid transparent', 'border-top': '6px solid #111827', 'border-bottom': 'none' }; } }, // Public method to show tooltip programmatically show: function($element, text, position, delay) { this.showTooltip($element, text, position || 'top', delay || 0); }, // Public method to hide tooltip programmatically hide: function() { this.hideTooltip(); } }; // Initialize tooltip system when document is ready $(document).ready(function() { EasyInvoiceTooltip.init(); }); })(jQuery);