UNPKG

@wix/css-property-parser

Version:

A comprehensive TypeScript library for parsing and serializing CSS property values with full MDN specification compliance

244 lines (243 loc) 7.75 kB
"use strict"; // CSS box-shadow property parser // Handles parsing of CSS box-shadow property according to MDN specification // https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow Object.defineProperty(exports, "__esModule", { value: true }); exports.parse = parse; exports.toCSSValue = toCSSValue; const length_1 = require('./length.cjs'); const color_1 = require('./color.cjs'); const css_variable_1 = require('./css-variable.cjs'); const shared_utils_1 = require('../utils/shared-utils.cjs'); /** * Parses a single box shadow value like "2px 2px 4px 1px red inset" or "inset 2px 2px 4px red" * Supports MDN syntax: offset-x offset-y [blur-radius] [spread-radius] [color] [inset] */ function parseSingleShadow(shadowStr) { if (!shadowStr || typeof shadowStr !== 'string') { return null; } const trimmed = shadowStr.trim(); if (trimmed === '') { return null; } // Use shared tokenizer for consistent parsing const tokens = (0, shared_utils_1.tokenize)(trimmed); if (tokens.length < 2) { return null; // Need at least offset-x and offset-y } // Track inset keyword let inset = false; let insetCount = 0; const lengthTokens = []; const colorTokens = []; const nonLengthTokens = []; // First pass: separate tokens by type for (const token of tokens) { if (token.toLowerCase() === 'inset') { inset = true; insetCount++; if (insetCount > 1) { return null; // Multiple inset keywords not allowed } } else { const lengthResult = (0, length_1.parse)(token); if (lengthResult) { lengthTokens.push(lengthResult); } else { const colorResult = (0, color_1.parse)(token); if (colorResult) { colorTokens.push(token); } else { nonLengthTokens.push(token); } } } } // Check for invalid tokens (non-length, non-color, non-inset) if (nonLengthTokens.length > 0) { return null; } // Need exactly 2, 3, or 4 length values (offset-x, offset-y, optional blur, optional spread) if (lengthTokens.length < 2 || lengthTokens.length > 4) { return null; } // Can have at most 1 color if (colorTokens.length > 1) { return null; } const result = { offsetX: lengthTokens[0], offsetY: lengthTokens[1] }; // Third length value is blur radius if (lengthTokens.length >= 3) { result.blurRadius = lengthTokens[2]; } // Fourth length value is spread radius if (lengthTokens.length === 4) { result.spreadRadius = lengthTokens[3]; } // Parse color if present if (colorTokens.length === 1) { const colorResult = (0, color_1.parse)(colorTokens[0]); if (colorResult) { result.color = colorResult; } } // Set inset flag if present if (inset) { result.inset = true; } return result; } /** * Parses a CSS box-shadow property string into structured components * Follows MDN specification: https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow */ function parse(value) { if (!value || typeof value !== 'string') { return null; } const trimmed = value.trim(); if (trimmed === '') { return null; } // CSS variables - parse and return directly if ((0, shared_utils_1.isCssVariable)(trimmed)) { return (0, css_variable_1.parse)(trimmed); } // Handle global keywords if ((0, shared_utils_1.isGlobalKeyword)(trimmed)) { return { type: 'keyword', keyword: trimmed.toLowerCase() }; } // Handle 'none' keyword if (trimmed.toLowerCase() === 'none') { return { type: 'keyword', keyword: 'none' }; } // Check for trailing comma or leading comma if (trimmed.endsWith(',') || trimmed.startsWith(',')) { return null; } // Check for double commas if (trimmed.includes(',,')) { return null; } // Parse multiple shadows separated by commas const shadowStrings = []; let current = ''; let parenDepth = 0; let inString = false; let stringChar = ''; for (let i = 0; i < trimmed.length; i++) { const char = trimmed[i]; if (!inString && (char === '"' || char === "'")) { inString = true; stringChar = char; current += char; } else if (inString && char === stringChar) { inString = false; stringChar = ''; current += char; } else if (!inString && char === '(') { parenDepth++; current += char; } else if (!inString && char === ')') { parenDepth--; current += char; } else if (!inString && parenDepth === 0 && char === ',') { if (current.trim()) { shadowStrings.push(current.trim()); current = ''; } else { return null; // Empty shadow between commas } } else { current += char; } } if (current.trim()) { shadowStrings.push(current.trim()); } if (shadowStrings.length === 0) { return null; } // Parse each shadow component const shadows = []; for (const shadowStr of shadowStrings) { const shadow = parseSingleShadow(shadowStr); if (!shadow) { return null; // Invalid shadow component } shadows.push(shadow); } return { shadows }; } /** * Converts a parsed box-shadow value back to CSS string representation */ function toCSSValue(parsed) { if (!parsed) { return null; } // Handle CSS variables if ('CSSvariable' in parsed) { return (0, css_variable_1.toCSSValue)(parsed); } if ('keyword' in parsed) { return parsed.keyword; } if ('shadows' in parsed && parsed.shadows.length > 0) { const shadowStrings = parsed.shadows.map((shadow) => { const parts = []; // Add inset keyword first if present if (shadow.inset) { parts.push('inset'); } // Add offsets (required) const offsetX = (0, length_1.toCSSValue)(shadow.offsetX); const offsetY = (0, length_1.toCSSValue)(shadow.offsetY); if (!offsetX || !offsetY) { return null; } parts.push(offsetX, offsetY); // Add blur radius (optional) if (shadow.blurRadius) { const blurValue = (0, length_1.toCSSValue)(shadow.blurRadius); if (blurValue) { parts.push(blurValue); } } // Add spread radius (optional) if (shadow.spreadRadius) { const spreadValue = (0, length_1.toCSSValue)(shadow.spreadRadius); if (spreadValue) { parts.push(spreadValue); } } // Add color (optional) if (shadow.color) { const colorValue = (0, color_1.toCSSValue)(shadow.color); if (colorValue) { parts.push(colorValue); } } return parts.join(' '); }); // Check if any shadow failed to serialize if (shadowStrings.some((s) => s === null)) { return null; } return shadowStrings.join(', '); } return null; }