const { STYLE_MAPPINGS } = require("./mappings"); class CSSGenerator { /** * Creates a new CSS generator instance. * @param {Object} [options] - Options object * @param {boolean} [options.minified=true] - Minify generated class names * @param {boolean} [options.debug=false] - Enable debug logging */ constructor(options = {}) { this.options = options; this.cssRules = new Map(); this.classCounter = 0; if (this.options.debug) { console.log( "[CSSGenerator] Initialized with options:", JSON.stringify(options, null, 2) ); } } /** * Generates a class name for the given element type, based on the counter * and the minified option. If minified is true, the class name will be a * single lowercase letter (a-z), or a single uppercase letter (A-Z) if * the counter is between 26 and 51. Otherwise, it will be a complex * class name (e.g. "zabcdefg") with a counter starting from 52. * * @param {string} elementType - The type of the element for which to * generate a class name. * @return {string} The generated class name. */ generateClassName(elementType) { if (this.options.debug) { console.log( `\n[CSSGenerator] Generating class name for element type: "${elementType}"` ); console.log(`[CSSGenerator] Current class counter: ${this.classCounter}`); } let className; if (!this.options.minified) { className = `blueprint-${elementType}-${this.classCounter++}`; if (this.options.debug) { console.log( `[CSSGenerator] Generated readable class name: "${className}"` ); } return className; } if (this.classCounter < 26) { className = String.fromCharCode(97 + this.classCounter++); if (this.options.debug) { console.log( `[CSSGenerator] Generated lowercase class name: "${className}" (counter: ${ this.classCounter - 1 })` ); } return className; } if (this.classCounter < 52) { className = String.fromCharCode(65 + (this.classCounter++ - 26)); if (this.options.debug) { console.log( `[CSSGenerator] Generated uppercase class name: "${className}" (counter: ${ this.classCounter - 1 })` ); } return className; } const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; const base = chars.length; let num = this.classCounter++ - 52; let result = ""; do { result = chars[num % base] + result; num = Math.floor(num / base); } while (num > 0); result = "z" + result; if (this.options.debug) { console.log( `[CSSGenerator] Generated complex class name: "${result}" (counter: ${ this.classCounter - 1 })` ); } return result; } /** * Converts a node to CSS properties, using the style mappings to process * the node's properties. The generated CSS properties are returned as a * Map, where each key is a CSS property name and each value is the value * for that property. * * @param {Object} node - The node to convert * @return {Object} - The generated CSS properties and nested rules */ nodeToCSSProperties(node) { if (this.options.debug) { console.log(`\n[CSSGenerator] Converting node to CSS properties`); console.log(`[CSSGenerator] Node tag: "${node.tag}"`); console.log("[CSSGenerator] Node properties:", node.props); } const cssProps = new Map(); const nestedRules = new Map(); node.props.forEach((prop) => { if (typeof prop === "object") { if (this.options.debug) { console.log(`[CSSGenerator] Skipping object property:`, prop); } return; } const [name, value] = prop.split(/[-:]/); if (this.options.debug) { console.log( `\n[CSSGenerator] Processing property - name: "${name}", value: "${value}"` ); } // This is for customization of css properties if (name === "width" && !isNaN(value)) { cssProps.set("width", `${value}% !important`); cssProps.set("max-width", "none !important"); if (this.options.debug) { console.log( `[CSSGenerator] Set width: ${value}% !important and max-width: none !important` ); } return; } if (name === "height" && !isNaN(value)) { cssProps.set("height", `${value}% !important`); cssProps.set("max-height", "none !important"); if (this.options.debug) { console.log( `[CSSGenerator] Set height: ${value}% !important and max-height: none !important` ); } return; } if (name === "padding" && !isNaN(value)) { cssProps.set("padding", `${value}px !important`); if (this.options.debug) { console.log(`[CSSGenerator] Set padding: ${value}px !important`); } return; } if (name === "margin" && !isNaN(value)) { cssProps.set("margin", `${value}px !important`); if (this.options.debug) { console.log(`[CSSGenerator] Set margin: ${value}px !important`); } return; } if (name === "marginTop" && !isNaN(value)) { cssProps.set("margin-top", `${value}px !important`); if (this.options.debug) { console.log(`[CSSGenerator] Set margin-top: ${value}px !important`); } return; } if (name === "marginBottom" && !isNaN(value)) { cssProps.set("margin-bottom", `${value}px !important`); if (this.options.debug) { console.log(`[CSSGenerator] Set margin-bottom: ${value}px !important`); } return; } if (name === "marginLeft" && !isNaN(value)) { cssProps.set("margin-left", `${value}px !important`); if (this.options.debug) { console.log(`[CSSGenerator] Set margin-left: ${value}px !important`); } return; } if (name === "marginRight" && !isNaN(value)) { cssProps.set("margin-right", `${value}px !important`); if (this.options.debug) { console.log(`[CSSGenerator] Set margin-right: ${value}px !important`); } return; } if (name === "color") { cssProps.set("color", `${value} !important`); if (this.options.debug) { console.log(`[CSSGenerator] Set color: ${value} !important`); } return; } if (name === "backgroundColor") { cssProps.set("background-color", `${value} !important`); if (this.options.debug) { console.log(`[CSSGenerator] Set background-color: ${value} !important`); } return; } const style = STYLE_MAPPINGS[name]; if (style) { if (this.options.debug) { console.log(`[CSSGenerator] Processing style mapping for: "${name}"`); } Object.entries(style).forEach(([key, baseValue]) => { if (typeof baseValue === "object") { if (key.startsWith(":") || key.startsWith(">")) { nestedRules.set(key, baseValue); if (this.options.debug) { console.log( `[CSSGenerator] Added nested rule: "${key}" =>`, baseValue ); } } else { let finalValue = baseValue; if (value && key === "gridTemplateColumns" && !isNaN(value)) { finalValue = `repeat(${value}, 1fr)`; if (this.options.debug) { console.log( `[CSSGenerator] Set grid template columns: ${finalValue}` ); } } cssProps.set(key, finalValue); if (this.options.debug) { console.log( `[CSSGenerator] Set CSS property: "${key}" = "${finalValue}"` ); } } } else { let finalValue = baseValue; if (value && key === "gridTemplateColumns" && !isNaN(value)) { finalValue = `repeat(${value}, 1fr)`; if (this.options.debug) { console.log( `[CSSGenerator] Set grid template columns: ${finalValue}` ); } } cssProps.set(key, finalValue); if (this.options.debug) { console.log( `[CSSGenerator] Set CSS property: "${key}" = "${finalValue}"` ); } } }); } }); if (this.options.debug) { console.log("\n[CSSGenerator] CSS properties generation complete"); console.log(`[CSSGenerator] Generated ${cssProps.size} CSS properties`); console.log(`[CSSGenerator] Generated ${nestedRules.size} nested rules`); } return { cssProps, nestedRules }; } /** * Generates the CSS code for the given style mappings. If minified is true, * the generated CSS will be minified. Otherwise, it will be formatted with * indentation and newlines. * * @return {string} The generated CSS code */ generateCSS() { if (this.options.debug) { console.log("\n[CSSGenerator] Starting CSS generation"); console.log(`[CSSGenerator] Processing ${this.cssRules.size} rule sets`); } /** * Converts a camelCase string to kebab-case (lowercase with hyphens * separating words) * * @param {string} str The string to convert * @return {string} The converted string */ const toKebabCase = (str) => str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(); let css = ""; this.cssRules.forEach((props, selector) => { if (props.cssProps.size > 0) { if (this.options.debug) { console.log( `\n[CSSGenerator] Generating CSS for selector: "${selector}"` ); console.log( `[CSSGenerator] Properties count: ${props.cssProps.size}` ); } css += `${selector} {${this.options.minified ? "" : "\n"}`; props.cssProps.forEach((value, prop) => { const cssProperty = toKebabCase(prop); css += `${ this.options.minified ? "" : " " }${cssProperty}: ${value};${this.options.minified ? "" : "\n"}`; if (this.options.debug) { console.log( `[CSSGenerator] Added property: ${cssProperty}: ${value}` ); } }); css += `}${this.options.minified ? "" : "\n"}`; } if (props.nestedRules.size > 0) { if (this.options.debug) { console.log( `\n[CSSGenerator] Processing ${props.nestedRules.size} nested rules for "${selector}"` ); } props.nestedRules.forEach((rules, nestedSelector) => { const fullSelector = nestedSelector.startsWith(">") ? `${selector} ${nestedSelector}` : `${selector}${nestedSelector}`; if (this.options.debug) { console.log( `[CSSGenerator] Generating nested selector: "${fullSelector}"` ); } css += `${fullSelector} {${this.options.minified ? "" : "\n"}`; Object.entries(rules).forEach(([prop, value]) => { if (typeof value === "object") { const pseudoSelector = `${fullSelector}${prop}`; if (this.options.debug) { console.log( `[CSSGenerator] Generating pseudo-selector: "${pseudoSelector}"` ); } css += `}${this.options.minified ? "" : "\n"}${pseudoSelector} {${ this.options.minified ? "" : "\n" }`; Object.entries(value).forEach(([nestedProp, nestedValue]) => { const cssProperty = toKebabCase(nestedProp); css += `${ this.options.minified ? "" : " " }${cssProperty}: ${nestedValue};${ this.options.minified ? "" : "\n" }`; if (this.options.debug) { console.log( `[CSSGenerator] Added nested property: ${cssProperty}: ${nestedValue}` ); } }); } else { const cssProperty = toKebabCase(prop); css += `${ this.options.minified ? "" : " " }${cssProperty}: ${value};${this.options.minified ? "" : "\n"}`; if (this.options.debug) { console.log( `[CSSGenerator] Added property: ${cssProperty}: ${value}` ); } } }); css += `}${this.options.minified ? "" : "\n"}`; }); } }); if (this.options.debug) { console.log("\n[CSSGenerator] CSS generation complete"); console.log( `[CSSGenerator] Generated ${css.split("\n").length} lines of CSS` ); } return css; } } module.exports = CSSGenerator;