import { loggers } from './logger'; export interface CSSVariables { [key: string]: string; } export interface ThemeMetadata { id: string; name: string; description: string; author?: string; version?: string; } /** * Parses CSS variables from CSS text content using regex */ export const parseCSSVariables = (cssContent: string): CSSVariables => { const variables: CSSVariables = {}; // Parse :root rules and extract CSS variables const rootRegex = /:root\s*\{([^}]+)\}/gs; let rootMatch; while ((rootMatch = rootRegex.exec(cssContent)) !== null) { const rootContent = rootMatch[1]; const variableRegex = /--([^:]+):\s*([^;]+);/g; let variableMatch; while ((variableMatch = variableRegex.exec(rootContent)) !== null) { const [, name, value] = variableMatch; variables[name.trim()] = value.trim(); } } return variables; }; /** * Loads CSS file and extracts variables */ export const loadCSSVariables = async (cssFilePath: string): Promise => { try { const response = await fetch(cssFilePath); if (!response.ok) { throw new Error(`Failed to load CSS file: ${cssFilePath}`); } const cssContent = await response.text(); return parseCSSVariables(cssContent); } catch (error) { loggers.theme.warn(`Could not load CSS variables from ${cssFilePath}`, error instanceof Error ? error : new Error(String(error))); return {}; } }; /** * Gets computed CSS variable value from the document */ export const getCSSVariable = (variableName: string, element?: HTMLElement): string => { const target = element || document.documentElement; return getComputedStyle(target).getPropertyValue(`--${variableName}`).trim(); }; /** * Sets CSS variable on the document or element */ export const setCSSVariable = (variableName: string, value: string, element?: HTMLElement): void => { const target = element || document.documentElement; target.style.setProperty(`--${variableName}`, value); }; /** * Parses theme metadata from CSS comment block using regex */ export const parseThemeMetadata = (cssContent: string): ThemeMetadata | null => { const commentRegex = /\/\*\s*([\s\S]*?)\s*\*\//; const match = commentRegex.exec(cssContent); if (!match) return null; const commentContent = match[1]; const metadata: Partial = {}; // Parse each line in the comment const lines = commentContent.split('\n'); for (const line of lines) { const cleanLine = line.replace(/^\s*\*\s?/, '').trim(); if (cleanLine.includes(':')) { const [key, ...valueParts] = cleanLine.split(':'); const value = valueParts.join(':').trim(); switch (key.toLowerCase().trim()) { case 'theme': metadata.id = value.toLowerCase().replace(/\s+/g, '-'); break; case 'name': metadata.name = value; break; case 'description': metadata.description = value; break; case 'author': metadata.author = value; break; case 'version': metadata.version = value; break; } } } // Ensure required fields if (!metadata.id || !metadata.name || !metadata.description) { return null; } return metadata as ThemeMetadata; }; /** * Gets all CSS variables from computed styles */ export const getAllCSSVariables = (element?: HTMLElement): CSSVariables => { const target = element || document.documentElement; const computedStyles = getComputedStyle(target); const variables: CSSVariables = {}; // Get all CSS properties and filter for custom properties for (let i = 0; i < computedStyles.length; i++) { const property = computedStyles[i]; if (property.startsWith('--')) { const name = property.substring(2); // Remove '--' prefix const value = computedStyles.getPropertyValue(property).trim(); if (value) { variables[name] = value; } } } return variables; }; /** * Loads CSS file and extracts both metadata and variables */ export const loadThemeFromCSS = async (cssFilePath: string): Promise<{ metadata: ThemeMetadata | null; variables: CSSVariables; }> => { try { const response = await fetch(cssFilePath); if (!response.ok) { throw new Error(`Failed to load CSS file: ${cssFilePath}`); } const cssContent = await response.text(); return { metadata: parseThemeMetadata(cssContent), variables: parseCSSVariables(cssContent) }; } catch (error) { loggers.theme.warn(`Could not load theme from ${cssFilePath}`, error instanceof Error ? error : new Error(String(error))); return { metadata: null, variables: {} }; } };