@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
JavaScript
// 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;
}