UNPKG

@wix/css-property-parser

Version:

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

166 lines (165 loc) 5.32 kB
// CSS gap property parser - Phase 3 refactored // Handles parsing of CSS gap property according to MDN specification // https://developer.mozilla.org/en-US/docs/Web/CSS/gap import { parse as parseLength, toCSSValue as lengthToCSSValue } from './length.js'; import { parse as parsePercentage, toCSSValue as percentageToCSSValue } from './percentage.js'; import { parse as parseCSSVariable, toCSSValue as cssVariableToCSSValue } from './css-variable.js'; import { isCssVariable, isGlobalKeyword, tokenize, isNonNegative } from '../utils/shared-utils.js'; // ======================================== // Utilities // ======================================== /** * Checks if a string is a valid gap value component */ function isGapValue(value) { const trimmed = value.trim(); // CSS variables are valid gap values if (isCssVariable(trimmed)) { return true; } // Global keywords are valid if (isGlobalKeyword(trimmed)) { return true; } // Normal keyword is valid for gap if (trimmed.toLowerCase() === 'normal') { return true; } // Try to parse as percentage (must be non-negative) const percentageResult = parsePercentage(trimmed); if (percentageResult) { return isNonNegative(percentageResult); } // Try to parse as length (must be non-negative) const lengthResult = parseLength(trimmed); if (lengthResult) { return isNonNegative(lengthResult); } return false; } /** * Parses a single gap value into atomic type */ function parseGapValue(value) { const trimmed = value.trim(); // Handle CSS variables in individual components if (isCssVariable(trimmed)) { return parseCSSVariable(trimmed); } // First validate the value if (!isGapValue(trimmed)) { return null; } // Handle keywords if (isGlobalKeyword(trimmed)) { return { type: 'keyword', keyword: trimmed.toLowerCase() }; } if (trimmed.toLowerCase() === 'normal') { return { type: 'keyword', keyword: 'normal' }; } // Try percentage first const percentageResult = parsePercentage(trimmed); if (percentageResult) { return percentageResult; } // Try length const lengthResult = parseLength(trimmed); if (lengthResult) { return lengthResult; } return null; } // ======================================== // Main Functions // ======================================== /** * Parses a CSS gap property value into structured components * @param value - The CSS gap property string * @returns Parsed gap object with rowGap and columnGap or null if invalid */ 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 if (isCssVariable(trimmed)) { return parseCSSVariable(trimmed); } if (isGlobalKeyword(trimmed)) { const keyword = { type: 'keyword', keyword: trimmed.toLowerCase() }; return { rowGap: keyword, columnGap: keyword }; } // Use shared tokenize utility to handle calc expressions properly const values = tokenize(trimmed); if (values.length === 0 || values.length > 2) { return null; } const parsedValues = []; for (const val of values) { const parsedValue = parseGapValue(val); if (!parsedValue) { return null; } parsedValues.push(parsedValue); } if (parsedValues.length === 1) { return { rowGap: parsedValues[0], columnGap: parsedValues[0] }; } else { return { rowGap: parsedValues[0], columnGap: parsedValues[1] }; } } /** * Converts a parsed gap back to a CSS value string * @param parsed - The parsed gap object * @returns CSS value string or null if invalid */ export function toCSSValue(parsed) { if (!parsed) { return null; } // Handle CSS variables for entire shorthand if ('CSSvariable' in parsed) { return cssVariableToCSSValue(parsed); } // Handle expanded gap (GapExpanded) const gapExpanded = parsed; const rowGapValue = convertGapValueToCSSValue(gapExpanded.rowGap); const columnGapValue = convertGapValueToCSSValue(gapExpanded.columnGap); if (!rowGapValue || !columnGapValue) { return null; } // If both values are the same, return single value if (rowGapValue === columnGapValue) { return rowGapValue; } // Return two values return `${rowGapValue} ${columnGapValue}`; } /** * Converts a single gap value back to CSS string */ function convertGapValueToCSSValue(gapValue) { // Handle CSS variables in components if ('CSSvariable' in gapValue) { return cssVariableToCSSValue(gapValue); } // Handle keywords if ('keyword' in gapValue) { return gapValue.keyword; } // Handle length/percentage by delegating to the appropriate toCSSValue function const lengthValue = lengthToCSSValue(gapValue); if (lengthValue) { return lengthValue; } const percentageValue = percentageToCSSValue(gapValue); if (percentageValue) { return percentageValue; } return null; }