@wix/css-property-parser
Version:
A comprehensive TypeScript library for parsing and serializing CSS property values with full MDN specification compliance
167 lines (166 loc) • 5.89 kB
JavaScript
// Border-width property parser
// Handles parsing of CSS border-width shorthand property according to MDN specification
// https://developer.mozilla.org/en-US/docs/Web/CSS/border-width
import { parse as parseLength, toCSSValue as lengthToCSSValue } from './length.js';
import { parse as parseCSSVariable, toCSSValue as cssVariableToCSSValue } from './css-variable.js';
import { isCssVariable, isGlobalKeyword, tokenize, expandShorthandValues } from '../utils/shared-utils.js';
// Import centralized types
import { BORDER_WIDTH_KEYWORDS } from '../types.js';
/**
* Check if value is a border width keyword
*/
function isBorderWidthKeyword(value) {
return BORDER_WIDTH_KEYWORDS.includes(value.toLowerCase());
}
/**
* Parse a single border width value (length or keyword)
*/
function parseSingleBorderWidth(value) {
const trimmed = value.trim();
// Handle CSS variables in individual components
if (isCssVariable(trimmed)) {
return parseCSSVariable(trimmed);
}
// Global keywords
if (isGlobalKeyword(trimmed)) {
return { type: 'keyword', keyword: trimmed.toLowerCase() };
}
// Border width keywords
if (isBorderWidthKeyword(trimmed)) {
return { type: 'keyword', keyword: trimmed.toLowerCase() };
}
// Length values (non-negative only)
const lengthResult = parseLength(trimmed);
if (lengthResult &&
(('value' in lengthResult && lengthResult.value >= 0) ||
('expression' in lengthResult))) {
return lengthResult;
}
return null;
}
/**
* Parse a CSS border-width shorthand property string
* Supports 1-4 values: top [right [bottom [left]]]
*/
export function parse(value) {
if (!value || typeof value !== 'string') {
return null;
}
const trimmed = value.trim();
if (trimmed === '') {
return null;
}
// CSS variables can be parsed directly for entire shorthand
const cssVariableResult = parseCSSVariable(trimmed);
if (cssVariableResult) {
return cssVariableResult;
}
// Handle single global keyword
if (isGlobalKeyword(trimmed)) {
const keyword = { type: 'keyword', keyword: trimmed.toLowerCase() };
return {
borderTopWidth: keyword,
borderRightWidth: keyword,
borderBottomWidth: keyword,
borderLeftWidth: keyword
};
}
// Handle single border width keyword
if (isBorderWidthKeyword(trimmed)) {
const keyword = { type: 'keyword', keyword: trimmed.toLowerCase() };
return {
borderTopWidth: keyword,
borderRightWidth: keyword,
borderBottomWidth: keyword,
borderLeftWidth: keyword
};
}
// Tokenize the value to handle multiple values
const tokens = tokenize(trimmed);
// Validate we have 1-4 tokens
if (tokens.length === 0 || tokens.length > 4) {
return null;
}
// Validate each token can be parsed
for (const token of tokens) {
const parsed = parseSingleBorderWidth(token);
if (parsed === null) {
return null; // Invalid token
}
}
// Expand shorthand values to 4 values (top, right, bottom, left) using string tokens
const expandedStrings = expandShorthandValues(tokens);
const [topStr, rightStr, bottomStr, leftStr] = expandedStrings;
// Parse each expanded value
const borderTopWidth = parseSingleBorderWidth(topStr);
const borderRightWidth = parseSingleBorderWidth(rightStr);
const borderBottomWidth = parseSingleBorderWidth(bottomStr);
const borderLeftWidth = parseSingleBorderWidth(leftStr);
if (!borderTopWidth || !borderRightWidth || !borderBottomWidth || !borderLeftWidth) {
return null;
}
// Return the expanded structure
return {
borderTopWidth,
borderRightWidth,
borderBottomWidth,
borderLeftWidth
};
}
/**
* Convert BorderWidthValue back to CSS string
*/
export function toCSSValue(parsed) {
if (!parsed) {
return null;
}
// Handle CSS variables for entire shorthand
if ('CSSvariable' in parsed) {
return cssVariableToCSSValue(parsed);
}
// Handle expanded shorthand structure
const borderWidthExpanded = parsed;
const topValue = serializeSingleBorderWidth(borderWidthExpanded.borderTopWidth);
const rightValue = serializeSingleBorderWidth(borderWidthExpanded.borderRightWidth);
const bottomValue = serializeSingleBorderWidth(borderWidthExpanded.borderBottomWidth);
const leftValue = serializeSingleBorderWidth(borderWidthExpanded.borderLeftWidth);
if (!topValue || !rightValue || !bottomValue || !leftValue) {
return null;
}
// Compress to shortest form
if (topValue === rightValue && rightValue === bottomValue && bottomValue === leftValue) {
// All same: "1px"
return topValue;
}
else if (topValue === bottomValue && rightValue === leftValue) {
// Top/bottom same, left/right same: "1px 2px"
return `${topValue} ${rightValue}`;
}
else if (rightValue === leftValue) {
// Left/right same: "1px 2px 3px"
return `${topValue} ${rightValue} ${bottomValue}`;
}
else {
// All different: "1px 2px 3px 4px"
return `${topValue} ${rightValue} ${bottomValue} ${leftValue}`;
}
}
/**
* Serialize a single border width value to CSS string
*/
function serializeSingleBorderWidth(value) {
// Handle CSS variables in components
if ('CSSvariable' in value) {
return cssVariableToCSSValue(value);
}
// Handle keywords
if ('keyword' in value) {
return value.keyword;
}
// Handle lengths by delegating to the length toCSSValue function
const lengthValue = lengthToCSSValue(value);
if (lengthValue) {
return lengthValue;
}
return null;
}