UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

365 lines (362 loc) â€ĸ 13.3 kB
import { canUseDOM } from './ssr.js'; class GlassStyleProbes { constructor() { this.probeResults = []; this.observer = null; this.performanceObserver = null; this.isMonitoring = false; } static getInstance() { if (!GlassStyleProbes.instance) { GlassStyleProbes.instance = new GlassStyleProbes(); } return GlassStyleProbes.instance; } // Start monitoring glass elements startMonitoring() { // Skip monitoring during SSR if (!canUseDOM || this.isMonitoring) return; console.log("🔍 Starting AuraGlass runtime probes..."); this.isMonitoring = true; this.setupMutationObserver(); this.setupPerformanceObserver(); this.schedulePeriodicProbes(); // Initial scan of existing glass elements this.scanExistingElements(); } // Stop monitoring stopMonitoring() { if (!this.isMonitoring) return; console.log("âšī¸ Stopping AuraGlass runtime probes..."); this.isMonitoring = false; if (this.observer) { this.observer.disconnect(); this.observer = null; } if (this.performanceObserver) { this.performanceObserver.disconnect(); this.performanceObserver = null; } } setupMutationObserver() { if (!canUseDOM) return; this.observer = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.type === "childList") { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { this.probeElement(node); } }); } else if (mutation.type === "attributes") { if (mutation.attributeName === "class" || mutation.attributeName === "style") { this.probeElement(mutation.target); } } }); }); this.observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class", "style"] }); } setupPerformanceObserver() { if (!canUseDOM) return; if ("PerformanceObserver" in window) { try { this.performanceObserver = new PerformanceObserver(list => { const entries = list.getEntries(); entries.forEach(entry => { if (entry.name.includes("glass") || entry.name.includes("backdrop")) { console.log("đŸƒâ€â™‚ī¸ Glass performance entry:", entry); } }); }); this.performanceObserver.observe({ entryTypes: ["measure", "paint", "layout-shift"] }); } catch (error) { console.warn("âš ī¸ Performance observer setup failed:", error); } } } schedulePeriodicProbes() { // Run comprehensive probes every 10 seconds setInterval(() => { if (this.isMonitoring) { this.runComprehensiveProbe(); } }, 10000); } scanExistingElements() { if (!canUseDOM) return; const glassElements = document.querySelectorAll('[class*="glass-"]'); glassElements.forEach(element => { this.probeElement(element); }); } probeElement(element) { if (!this.isGlassElement(element)) return; const result = this.analyzeElement(element); this.probeResults.push(result); // Keep only last 100 results to prevent memory issues if (this.probeResults.length > 100) { this.probeResults = this.probeResults.slice(-100); } // Report critical issues immediately if (result.compliance.accessibilityScore < 0.7) { console.warn("âš ī¸ Glass accessibility issue detected:", result); } if (!result.performance.backdropSupported) { console.warn("âš ī¸ Backdrop filter not supported, glass effects disabled"); } } isGlassElement(element) { const classes = element.className || ""; return classes.includes("glass-") || element.getAttribute("data-glass") !== null || this.hasGlassStyles(element); } hasGlassStyles(element) { const computedStyle = window.getComputedStyle(element); return computedStyle.backdropFilter !== "none" || computedStyle.backdropFilter !== "none"; } analyzeElement(element) { const startTime = performance.now(); const glassConfig = this.extractGlassConfiguration(element); const performanceData = this.analyzePerformance(element); const complianceData = this.analyzeCompliance(element); const usageData = this.analyzeUsage(element); const endTime = performance.now(); performanceData.renderTime = endTime - startTime; return { timestamp: Date.now(), elementId: element.id, glassConfiguration: glassConfig, performance: performanceData, compliance: complianceData, usage: usageData }; } extractGlassConfiguration(element) { const classes = element.className || ""; // Extract intent from class names like "glass-primary-level2" const intentMatch = classes.match(/glass-(neutral|primary|secondary|success|warning|danger|info)-/); const elevationMatch = classes.match(/glass-\w+-(level[1-4])/); const tierMatch = classes.match(/glass-tier-(high|medium|low)/) || classes.match(/tier-(high|medium|low)/); return { intent: intentMatch ? intentMatch[1] : "unknown", elevation: elevationMatch ? elevationMatch[1] : "unknown", tier: tierMatch ? tierMatch[1] : "high" // default to high }; } analyzePerformance(element) { const computedStyle = window.getComputedStyle(element); const backdropSupported = this.testBackdropSupport(); const gpuAccelerated = this.testGPUAcceleration(computedStyle); // Estimate memory usage (rough approximation) const memoryUsage = this.estimateMemoryUsage(element); return { backdropSupported, gpuAccelerated, memoryUsage }; } analyzeCompliance(element) { const computedStyle = window.getComputedStyle(element); const contrastRatio = this.calculateContrastRatio(element); const minVisibility = this.checkMinimumVisibility(computedStyle); const accessibilityScore = this.calculateAccessibilityScore(element); return { wcagContrast: contrastRatio, minVisibility, accessibilityScore }; } analyzeUsage(element) { const classes = element.className || ""; const inlineStyles = element.getAttribute("style") || ""; const warnings = []; // Determine which API was used let apiUsed = "unknown"; if (classes.match(/glass-\w+-level[1-4]/)) { apiUsed = "css-classes"; // Using generated CSS classes } else if (inlineStyles.includes("backdrop") || classes.includes("createGlassStyle")) { apiUsed = "createGlassStyle"; // Using unified API } else if (classes.includes("glassSurface") || classes.includes("glassBorder")) { apiUsed = "legacy"; warnings.push("Using deprecated glass API - migrate to createGlassStyle()"); } // Check for deprecated patterns if (inlineStyles.includes("backdrop-filter")) { warnings.push("Inline backdrop-filter detected - should use unified token system"); } if (inlineStyles.match(/rgba\(\s*255,\s*255,\s*255,\s*0\.[0-9]/)) { warnings.push("Hardcoded glass background - should use token system"); } return { apiUsed, deprecationWarnings: warnings }; } testBackdropSupport() { const testEl = document.createElement("div"); testEl.style.backdropFilter = "blur(1px)"; return testEl.style.backdropFilter !== ""; } testGPUAcceleration(computedStyle) { return computedStyle.transform !== "none" || computedStyle.willChange.includes("transform"); } estimateMemoryUsage(element) { // Rough estimation based on element complexity const rect = element.getBoundingClientRect(); const area = rect.width * rect.height; const complexity = (element.children.length + 1) * (element.className.split(" ").length + 1); return Math.round(area * complexity / 1000); // KB estimate } calculateContrastRatio(element) { try { const computedStyle = window.getComputedStyle(element); const backgroundColor = computedStyle.backgroundColor; const color = computedStyle.color; // Simplified contrast calculation const bgLuminance = this.getColorLuminance(backgroundColor); const textLuminance = this.getColorLuminance(color); const lighter = Math.max(bgLuminance, textLuminance); const darker = Math.min(bgLuminance, textLuminance); return (lighter + 0.05) / (darker + 0.05); } catch { return 1; // Fallback if calculation fails } } getColorLuminance(color) { // Simplified luminance calculation const rgb = this.parseColor(color); if (!rgb) return 1; const [r, g, b] = rgb.map(c => { c = c / 255; return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4); }); return 0.2126 * r + 0.7152 * g + 0.0722 * b; } parseColor(color) { const match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); return match ? [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])] : null; } checkMinimumVisibility(computedStyle) { const opacity = parseFloat(computedStyle.opacity); const backgroundAlpha = this.extractAlphaFromBackground(computedStyle.backgroundColor); return opacity >= 0.05 && backgroundAlpha >= 0.05; } extractAlphaFromBackground(backgroundColor) { const match = backgroundColor.match(/rgba?\([^,]+,[^,]+,[^,]+,\s*([\d.]+)\)/); return match ? parseFloat(match[1]) : 1; } calculateAccessibilityScore(element) { let score = 1.0; const contrastRatio = this.calculateContrastRatio(element); if (contrastRatio < 4.5) score -= 0.3; // WCAG AA failure if (contrastRatio < 3.0) score -= 0.3; // Severe contrast issue const computedStyle = window.getComputedStyle(element); if (!this.checkMinimumVisibility(computedStyle)) score -= 0.2; if (!element.getAttribute("aria-label") && !element.textContent) { score -= 0.1; // Missing accessibility info } return Math.max(0, score); } runComprehensiveProbe() { const glassElements = document.querySelectorAll('[class*="glass-"]'); const summary = { timestamp: Date.now(), totalElements: glassElements.length, compliance: { passed: 0, warning: 0, failed: 0 }, performance: { backdropSupported: this.testBackdropSupport(), averageRenderTime: 0, memoryUsage: 0 }, usage: { unified: 0, legacy: 0, unknown: 0 }, deprecationWarnings: [] }; let totalRenderTime = 0; let totalMemory = 0; this.probeResults.slice(-glassElements.length).forEach(result => { // Compliance scoring if (result.compliance.accessibilityScore >= 0.8) summary.compliance.passed++;else if (result.compliance.accessibilityScore >= 0.6) summary.compliance.warning++;else summary.compliance.failed++; // Performance aggregation if (result.performance.renderTime) totalRenderTime += result.performance.renderTime; if (result.performance.memoryUsage) totalMemory += result.performance.memoryUsage; // Usage patterns if (result.usage.apiUsed === "createGlassStyle" || result.usage.apiUsed === "css-classes") { summary.usage.unified++; } else if (result.usage.apiUsed === "legacy") { summary.usage.legacy++; } else { summary.usage.unknown++; } // Collect warnings summary.deprecationWarnings.push(...result.usage.deprecationWarnings); }); summary.performance.averageRenderTime = totalRenderTime / glassElements.length; summary.performance.memoryUsage = totalMemory; // Remove duplicate warnings summary.deprecationWarnings = [...new Set(summary.deprecationWarnings)]; console.log("📊 AuraGlass Comprehensive Probe Summary:", summary); // Store for potential reporting window.__auraglassProbeData = { latestSummary: summary, allResults: this.probeResults }; } // Public API for accessing probe data getProbeResults() { return [...this.probeResults]; } getLatestSummary() { return window.__auraglassProbeData?.latestSummary; } // Force a probe of specific element probeElementById(elementId) { const element = document.getElementById(elementId); if (!element) return null; return this.analyzeElement(element); } } /** * Utility to manually start probes (call from useEffect or initialization code) * @param options - Configuration options */ function startGlassProbes(options) { if (!canUseDOM) return; const probes = GlassStyleProbes.getInstance(); // Start monitoring after DOM is ready if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => { probes.startMonitoring(); }); } else { probes.startMonitoring(); } // Expose to global scope for debugging if requested if (options?.exposeGlobally && process.env.NODE_ENV === "development") { window.__auraglassProbes = probes; } return probes; } /** * Utility to stop probes */ function stopGlassProbes() { if (!canUseDOM) return; GlassStyleProbes.getInstance().stopMonitoring(); } export { GlassStyleProbes, GlassStyleProbes as default, startGlassProbes, stopGlassProbes }; //# sourceMappingURL=glassStyleProbes.js.map