/** * Theme Adapter * Ensures tooltips match the current dashboard theme * Integrates with Driver.js to apply theme-specific styling */ (function(window) { 'use strict'; /** * Theme configuration mapping for Driver.js * Maps dashboard themes to Driver.js styling */ const THEME_CONFIGS = { dark: { backgroundColor: 'var(--card-base)', textColor: 'var(--fg)', primaryColor: 'var(--accent)', overlayColor: 'rgba(0, 0, 0, 0.7)', borderColor: 'var(--border)', highlightColor: 'var(--accent)', fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" }, light: { backgroundColor: 'var(--card-base)', textColor: 'var(--fg)', primaryColor: 'var(--accent-strong)', overlayColor: 'rgba(0, 0, 0, 0.5)', borderColor: 'var(--border)', highlightColor: 'var(--accent-strong)', fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" }, blue: { backgroundColor: 'var(--card-base)', textColor: 'var(--fg)', primaryColor: 'var(--accent)', overlayColor: 'rgba(25, 8, 172, 0.7)', borderColor: 'var(--border)', highlightColor: 'var(--accent)', fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" }, nord: { backgroundColor: 'var(--card-base)', textColor: 'var(--fg)', primaryColor: 'var(--accent)', overlayColor: 'rgba(46, 52, 64, 0.7)', borderColor: 'var(--border)', highlightColor: 'var(--accent)', fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" }, dracula: { backgroundColor: 'var(--card-base)', textColor: 'var(--fg)', primaryColor: 'var(--accent)', overlayColor: 'rgba(40, 42, 54, 0.7)', borderColor: 'var(--border)', highlightColor: 'var(--accent)', fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" }, 'solarized-dark': { backgroundColor: 'var(--card-base)', textColor: 'var(--fg)', primaryColor: 'var(--accent)', overlayColor: 'rgba(0, 43, 54, 0.7)', borderColor: 'var(--border)', highlightColor: 'var(--accent)', fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" }, 'solarized-light': { backgroundColor: 'var(--card-base)', textColor: 'var(--fg)', primaryColor: 'var(--accent)', overlayColor: 'rgba(253, 246, 227, 0.7)', borderColor: 'var(--border)', highlightColor: 'var(--accent)', fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" } }; /** * ThemeAdapter class * Manages theme integration for the tooltip system */ class ThemeAdapter { constructor() { this.currentTheme = this.getCurrentTheme(); this.themeChangeCallbacks = []; this._setupThemeChangeListener(); } /** * Get the current theme name from document root class * @returns {string} Current theme name (e.g., 'dark', 'light', 'blue') */ getCurrentTheme() { const root = document.documentElement; const classList = Array.from(root.classList); // Check for theme classes const themeClasses = ['light', 'blue', 'nord', 'dracula', 'solarized-dark', 'solarized-light']; const foundTheme = themeClasses.find(theme => classList.includes(theme)); // Default to 'dark' if no theme class found return foundTheme || 'dark'; } /** * Get Driver.js theme configuration for current theme * @returns {Object} Theme configuration object */ getDriverTheme() { const themeName = this.getCurrentTheme(); const config = THEME_CONFIGS[themeName] || THEME_CONFIGS.dark; // Resolve CSS variables to actual values const resolvedConfig = {}; for (const [key, value] of Object.entries(config)) { if (typeof value === 'string' && value.startsWith('var(')) { // Extract CSS variable name const varName = value.match(/var\((--[^)]+)\)/)?.[1]; if (varName) { const computedValue = getComputedStyle(document.documentElement) .getPropertyValue(varName) .trim(); resolvedConfig[key] = computedValue || value; } else { resolvedConfig[key] = value; } } else { resolvedConfig[key] = value; } } return resolvedConfig; } /** * Register a callback for theme changes * @param {Function} callback - Function to call when theme changes */ onThemeChange(callback) { if (typeof callback === 'function') { this.themeChangeCallbacks.push(callback); } } /** * Setup theme change listener using MutationObserver * @private */ _setupThemeChangeListener() { const root = document.documentElement; // Create observer to watch for class changes on root element const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'attributes' && mutation.attributeName === 'class') { const newTheme = this.getCurrentTheme(); if (newTheme !== this.currentTheme) { const oldTheme = this.currentTheme; this.currentTheme = newTheme; this._notifyThemeChange(newTheme, oldTheme); } } }); }); // Start observing observer.observe(root, { attributes: true, attributeFilter: ['class'] }); console.log('[ThemeAdapter] Theme change listener initialized'); } /** * Notify all registered callbacks of theme change * @private * @param {string} newTheme - New theme name * @param {string} oldTheme - Old theme name */ _notifyThemeChange(newTheme, oldTheme) { console.log(`[ThemeAdapter] Theme changed: ${oldTheme} → ${newTheme}`); this.themeChangeCallbacks.forEach(callback => { try { callback(newTheme, oldTheme); } catch (error) { console.error('[ThemeAdapter] Error in theme change callback:', error); } }); } /** * Apply theme to Driver.js instance * @param {Object} driver - Driver.js instance */ applyTheme(driver) { if (!driver) { console.warn('[ThemeAdapter] No driver instance provided'); return; } const themeConfig = this.getDriverTheme(); // Apply theme configuration to driver // Note: Driver.js v1.0+ uses CSS variables, so we inject a style element this._injectDriverStyles(themeConfig); console.log('[ThemeAdapter] Theme applied to driver:', this.currentTheme); } /** * Inject custom styles for Driver.js based on theme * @private * @param {Object} themeConfig - Theme configuration */ _injectDriverStyles(themeConfig) { // Remove existing theme styles const existingStyle = document.getElementById('driver-theme-styles'); if (existingStyle) { existingStyle.remove(); } // Create new style element const style = document.createElement('style'); style.id = 'driver-theme-styles'; style.textContent = ` .driver-popover { background: ${themeConfig.backgroundColor} !important; color: ${themeConfig.textColor} !important; border: 1px solid ${themeConfig.borderColor} !important; border-radius: 12px !important; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4) !important; font-family: ${themeConfig.fontFamily} !important; } .driver-popover-title { color: ${themeConfig.textColor} !important; font-weight: 600 !important; font-family: ${themeConfig.fontFamily} !important; } .driver-popover-description { color: ${themeConfig.textColor} !important; font-family: ${themeConfig.fontFamily} !important; } .driver-popover-footer button { background: ${themeConfig.primaryColor} !important; color: ${themeConfig.backgroundColor} !important; border: none !important; font-family: ${themeConfig.fontFamily} !important; font-weight: 500 !important; } .driver-popover-footer button:hover { opacity: 0.9 !important; } .driver-popover-close-btn { color: ${themeConfig.textColor} !important; } .driver-overlay { background: ${themeConfig.overlayColor} !important; } .driver-highlighted-element { outline: 2px solid ${themeConfig.highlightColor} !important; outline-offset: 4px !important; } .driver-popover-progress-text { color: ${themeConfig.textColor} !important; opacity: 0.7 !important; font-family: ${themeConfig.fontFamily} !important; } `; document.head.appendChild(style); } /** * Get all available theme names * @returns {string[]} Array of theme names */ getAvailableThemes() { return Object.keys(THEME_CONFIGS); } /** * Check if a theme is available * @param {string} themeName - Theme name to check * @returns {boolean} True if theme is available */ isThemeAvailable(themeName) { return THEME_CONFIGS.hasOwnProperty(themeName); } } // Export to global scope window.ThemeAdapter = ThemeAdapter; console.log('[ThemeAdapter] Module loaded'); })(window);