UNPKG

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