UNPKG

@wix/css-property-parser

Version:

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

160 lines (159 loc) 6.7 kB
// Position data type parser // Handles parsing of CSS position values according to MDN specification // https://developer.mozilla.org/en-US/docs/Web/CSS/position_value // (background-position, object-position, etc.) and reconstruction back to CSS string import { isCssVariable } from '../utils/shared-utils.js'; import { parse as parseLengthPercentage } from './length-percentage.js'; import { parse as parseCSSVariable } from './css-variable.js'; // Position keywords const HORIZONTAL_KEYWORDS = ['left', 'center', 'right']; const VERTICAL_KEYWORDS = ['top', 'center', 'bottom']; /** * Parses a CSS position value into structured components * @param value - The CSS position value * @returns Parsed position object or null if invalid */ export function parse(value) { if (!value || value.trim() === '') { return null; } const trimmed = value.trim(); // Check for CSS variables - parse them if valid if (isCssVariable(trimmed)) { return parseCSSVariable(trimmed); } // Handle single value if (!trimmed.includes(' ')) { return parseSingleValue(trimmed); } // Handle multiple values const tokens = trimmed.split(/\s+/); if (tokens.length === 2) { return parseTwoValues(tokens[0], tokens[1]); } else if (tokens.length === 3) { return parseThreeValues(tokens[0], tokens[1], tokens[2]); } else if (tokens.length === 4) { return parseFourValues(tokens[0], tokens[1], tokens[2], tokens[3]); } return null; } /** * Converts a parsed position back to a CSS value string * @param parsed - The parsed position object * @returns CSS value string or null if invalid */ export function toCSSValue(parsed) { if (!parsed) { return null; } // Handle CSS variables if ('CSSvariable' in parsed) { if (parsed.defaultValue) { return `var(${parsed.CSSvariable}, ${parsed.defaultValue})`; } return `var(${parsed.CSSvariable})`; } // Handle position values - cast to CSSPositionValue after type guard const positionValue = parsed; // Validate the parsed position if (typeof positionValue.x !== 'string' || typeof positionValue.y !== 'string') { return null; } const parts = []; parts.push(positionValue.x); if (positionValue.xOffset) parts.push(positionValue.xOffset); parts.push(positionValue.y); if (positionValue.yOffset) parts.push(positionValue.yOffset); return parts.join(' '); } // Internal helper functions (not exported) function parseSingleValue(value) { if (HORIZONTAL_KEYWORDS.includes(value)) { return { type: 'position', x: value, y: 'center' }; } else if (VERTICAL_KEYWORDS.includes(value)) { return { type: 'position', x: 'center', y: value }; } else if (parseLengthPercentage(value) !== null) { return { type: 'position', x: value, y: 'center' }; } return null; } function parseTwoValues(first, second) { // Handle 'center center' as a special case first if (first === 'center' && second === 'center') { return { type: 'position', x: 'center', y: 'center' }; } // Both horizontal keywords (excluding center) if (HORIZONTAL_KEYWORDS.includes(first) && HORIZONTAL_KEYWORDS.includes(second) && first !== 'center' && second !== 'center') { return null; // Invalid } // Both vertical keywords (excluding center) if (VERTICAL_KEYWORDS.includes(first) && VERTICAL_KEYWORDS.includes(second) && first !== 'center' && second !== 'center') { return null; // Invalid } // Check if both are length/percentage values first const firstIsLP = parseLengthPercentage(first) !== null; const secondIsLP = parseLengthPercentage(second) !== null; // Both are length/percentage if (firstIsLP && secondIsLP) { return { type: 'position', x: first, y: second }; } // First is horizontal keyword, second is vertical keyword or length/percentage if (HORIZONTAL_KEYWORDS.includes(first) && (VERTICAL_KEYWORDS.includes(second) || secondIsLP)) { return { type: 'position', x: first, y: second }; } // First is vertical keyword, second is horizontal keyword or length/percentage if (VERTICAL_KEYWORDS.includes(first) && (HORIZONTAL_KEYWORDS.includes(second) || secondIsLP)) { return { type: 'position', x: second, y: first }; } // First is length/percentage, second is vertical keyword if (firstIsLP && VERTICAL_KEYWORDS.includes(second)) { return { type: 'position', x: first, y: second }; } return null; } function parseThreeValues(first, second, third) { // Reject invalid combinations like 'left right center' upfront if (HORIZONTAL_KEYWORDS.includes(first) && HORIZONTAL_KEYWORDS.includes(second) && first !== 'center' && second !== 'center') { return null; } if (VERTICAL_KEYWORDS.includes(first) && VERTICAL_KEYWORDS.includes(second) && first !== 'center' && second !== 'center') { return null; } // Pattern: keyword offset keyword if (HORIZONTAL_KEYWORDS.includes(first) && parseLengthPercentage(second) !== null && VERTICAL_KEYWORDS.includes(third)) { return { type: 'position', x: first, y: third, xOffset: second }; } if (VERTICAL_KEYWORDS.includes(first) && parseLengthPercentage(second) !== null && HORIZONTAL_KEYWORDS.includes(third)) { return { type: 'position', x: third, y: first, yOffset: second }; } // Pattern: keyword keyword offset if (HORIZONTAL_KEYWORDS.includes(first) && VERTICAL_KEYWORDS.includes(second) && parseLengthPercentage(third) !== null) { return { type: 'position', x: first, y: second, yOffset: third }; } if (VERTICAL_KEYWORDS.includes(first) && HORIZONTAL_KEYWORDS.includes(second) && parseLengthPercentage(third) !== null) { return { type: 'position', x: second, y: first, xOffset: third }; } return null; } function parseFourValues(first, second, third, fourth) { // Pattern: horizontal offset vertical offset if (HORIZONTAL_KEYWORDS.includes(first) && parseLengthPercentage(second) !== null && VERTICAL_KEYWORDS.includes(third) && parseLengthPercentage(fourth) !== null) { return { type: 'position', x: first, y: third, xOffset: second, yOffset: fourth }; } if (VERTICAL_KEYWORDS.includes(first) && parseLengthPercentage(second) !== null && HORIZONTAL_KEYWORDS.includes(third) && parseLengthPercentage(fourth) !== null) { return { type: 'position', x: third, y: first, yOffset: second, xOffset: fourth }; } return null; }