UNPKG

@wix/css-property-parser

Version:

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

149 lines (148 loc) 4.99 kB
// Border-color property parser // Handles parsing of CSS border-color shorthand property according to MDN specification // https://developer.mozilla.org/en-US/docs/Web/CSS/border-color import { parse as parseColor, toCSSValue as colorToCSSValue } from './color.js'; import { parse as parseCSSVariable, toCSSValue as cssVariableToCSSValue } from './css-variable.js'; import { isCssVariable, isGlobalKeyword, tokenize, expandShorthandValues } from '../utils/shared-utils.js'; /** * Check if value is a border color keyword */ function isBorderColorKeyword(value) { return value.toLowerCase() === 'currentcolor'; } /** * Parse a single color value for border-color */ function parseSingleColor(value) { const trimmed = value.trim(); // CSS variables should be handled at the property level, not individual color level if (isCssVariable(trimmed)) { return null; // CSS variables apply to entire property } // Global keywords - return as BorderColorKeyword for individual color processing if (isGlobalKeyword(trimmed)) { return { type: 'keyword', keyword: trimmed.toLowerCase() }; } // Border color keyword (currentcolor) if (isBorderColorKeyword(trimmed)) { return { type: 'keyword', keyword: 'currentcolor' }; } // Color values - delegate to color evaluator const colorResult = parseColor(trimmed); if (colorResult && 'format' in colorResult) { // Only return actual color values, not CSS variables return colorResult; } return null; } /** * Parse a CSS border-color property string */ export 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 (isCssVariable(trimmed)) { return parseCSSVariable(trimmed); } // Tokenize the value (handles functions and quotes properly) const tokens = tokenize(trimmed); // Must have 1-4 values if (tokens.length === 0 || tokens.length > 4) { return null; } // Parse each token as a color const colors = []; for (const token of tokens) { const colorResult = parseSingleColor(token); if (!colorResult) { return null; // Invalid color } colors.push(colorResult); } // Expand shorthand values (1-4 values → 4 values) // Convert to strings for expandShorthandValues, then map back const colorStrings = colors.map(color => { if ('keyword' in color) { return color.keyword; } else { return colorToCSSValue(color) || ''; } }); const expandedStrings = expandShorthandValues(colorStrings); // Convert back to color objects const expandedColors = expandedStrings.map(colorStr => { const parsed = parseSingleColor(colorStr); if (!parsed) { throw new Error(`Failed to re-parse color: ${colorStr}`); } return parsed; }); return { borderTopColor: expandedColors[0], borderRightColor: expandedColors[1], borderBottomColor: expandedColors[2], borderLeftColor: expandedColors[3] }; } /** * Convert a single color value to CSS string */ function convertColorToCSSValue(color) { // Handle CSS variables if ('CSSvariable' in color) { return cssVariableToCSSValue(color); } if ('keyword' in color) { return color.keyword; } return colorToCSSValue(color); } /** * Convert BorderColorValue back to CSS string */ export function toCSSValue(parsed) { if (!parsed) { return null; } // Handle CSS variables if ('CSSvariable' in parsed) { return cssVariableToCSSValue(parsed); } // Handle shorthand expansion if ('borderTopColor' in parsed) { const { borderTopColor, borderRightColor, borderBottomColor, borderLeftColor } = parsed; // Convert each color to CSS string const top = convertColorToCSSValue(borderTopColor); const right = convertColorToCSSValue(borderRightColor); const bottom = convertColorToCSSValue(borderBottomColor); const left = convertColorToCSSValue(borderLeftColor); if (!top || !right || !bottom || !left) { return null; } // Optimize shorthand output if (top === right && right === bottom && bottom === left) { // All four values are the same return top; } else if (top === bottom && right === left) { // Top/bottom same, left/right same return `${top} ${right}`; } else if (right === left) { // Left/right same return `${top} ${right} ${bottom}`; } else { // All four values different return `${top} ${right} ${bottom} ${left}`; } } return null; }