UNPKG

@wix/css-property-parser

Version:

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

111 lines (110 loc) 3.66 kB
// Length data type parser // Handles parsing of CSS length values according to MDN specification // https://developer.mozilla.org/en-US/docs/Web/CSS/length // with units (px, em, rem, %, etc.) and reconstruction back to CSS string import { parse as parseNumber } from './number.js'; import { parse as parseCSSVariable, toCSSValue as cssVariableToCSSValue } from './css-variable.js'; import { isCssVariable } from '../utils/shared-utils.js'; import { parseCSSFunction, cssFunctionToCSSValue } from '../utils/css-function-parser.js'; // CSS length units categorized by type const LengthUnits = { // Absolute length units absolute: ['px', 'spx', 'cm', 'mm', 'q', 'in', 'pc', 'pt'], // Relative length units relative: ['em', 'rem', 'ex', 'ch', 'lh', 'rlh', 'cap', 'rcap', 'ic', 'ric'], // Viewport percentage units viewport: [ 'vh', 'vw', 'vmin', 'vmax', 'vi', 'vb', 'dvh', 'dvw', 'dvi', 'dvb', 'dvmin', 'dvmax', 'lvh', 'lvw', 'lvi', 'lvb', 'lvmin', 'lvmax', 'svh', 'svw', 'svi', 'svb', 'svmin', 'svmax' ], // Container query length units container: ['cqw', 'cqh', 'cqi', 'cqb', 'cqmin', 'cqmax'] }; // All valid length units flattened const ALL_LENGTH_UNITS = [ ...LengthUnits.absolute, ...LengthUnits.relative, ...LengthUnits.viewport, ...LengthUnits.container, '%' // percentage ]; /** * Parses a CSS length value into structured components * @param value - The CSS length value string * @returns Parsed length object 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 - ALWAYS CHECK FIRST if (isCssVariable(trimmed)) { return parseCSSVariable(trimmed); } // Handle special case: zero can be unitless if (trimmed === '0') { return { type: 'length', value: 0, unit: '' }; } // Handle CSS math functions: calc(), clamp(), min(), max() const functionResult = parseCSSFunction(trimmed); if (functionResult) { return functionResult; } // Regular length with unit const match = trimmed.match(/^([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)([a-zA-Z%]+)$/); if (!match) return null; const [, numberPart, unit] = match; const parsedNumber = parseNumber(numberPart); if (!parsedNumber) return null; // Check if it's a simple number value (not calc expression or keyword) if (!('value' in parsedNumber)) { return null; } const lowerUnit = unit.toLowerCase(); // Validate the unit if (!ALL_LENGTH_UNITS.includes(lowerUnit)) { return null; } return { type: 'length', value: parsedNumber.value, unit: lowerUnit }; } /** * Converts a parsed length back to a CSS value string * @param parsed - The parsed length object * @returns CSS value string or null if invalid */ export function toCSSValue(parsed) { if (!parsed) { return null; } // Handle CSS variables if ('CSSvariable' in parsed) { return cssVariableToCSSValue(parsed); } // Handle CSS math function expressions if ('expression' in parsed) { return cssFunctionToCSSValue(parsed); } // Handle regular length values if ('value' in parsed && 'unit' in parsed) { // Special case: zero can be unitless if (parsed.value === 0 && parsed.unit === '') { return '0'; } return `${parsed.value}${parsed.unit}`; } return null; }