UNPKG

@wix/css-property-parser

Version:

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

173 lines (172 loc) 6.75 kB
"use strict"; // CSS border-radius property parser - Phase 3 refactored // Handles parsing of CSS border-radius shorthand property according to MDN specification // https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius Object.defineProperty(exports, "__esModule", { value: true }); exports.parse = parse; exports.toCSSValue = toCSSValue; const length_1 = require('./length.cjs'); const percentage_1 = require('./percentage.cjs'); const css_variable_1 = require('./css-variable.cjs'); const shared_utils_1 = require('../utils/shared-utils.cjs'); // ======================================== // Property-specific keywords // ======================================== const BORDER_RADIUS_KEYWORDS = new Set(['inherit', 'initial', 'unset', 'revert', 'revert-layer']); function isBorderRadiusKeyword(value) { return BORDER_RADIUS_KEYWORDS.has(value.toLowerCase()); } // ======================================== // Core parsing functions // ======================================== function parseRadiusValue(value) { const trimmed = value.trim(); if ((0, shared_utils_1.isGlobalKeyword)(trimmed)) { return { type: 'keyword', keyword: trimmed.toLowerCase() }; } if (isBorderRadiusKeyword(trimmed)) { return { type: 'keyword', keyword: trimmed.toLowerCase() }; } // Try percentage first (to handle cases like '50%') const percentageResult = (0, percentage_1.parse)(trimmed); if (percentageResult && (0, shared_utils_1.isNonNegative)(percentageResult)) { return percentageResult; } // Try length const lengthResult = (0, length_1.parse)(trimmed); if (lengthResult && (0, shared_utils_1.isNonNegative)(lengthResult)) { return lengthResult; } return null; } function parseEllipticalSyntax(value) { // Handle elliptical syntax with slash separator const ellipticalParts = value.split('/'); if (ellipticalParts.length > 2) { return null; // Invalid: more than one slash } const horizontalPart = ellipticalParts[0]; const verticalPart = ellipticalParts.length === 2 ? ellipticalParts[1] : ''; // Check for empty parts if (!horizontalPart || !horizontalPart.trim()) { return null; } // If there's a slash, there must be valid vertical values if (ellipticalParts.length === 2 && (!verticalPart || !verticalPart.trim())) { return null; } const horizontalTokens = (0, shared_utils_1.tokenize)(horizontalPart); const verticalTokens = ellipticalParts.length === 2 ? (0, shared_utils_1.tokenize)(verticalPart) : []; if (horizontalTokens.length === 0 || horizontalTokens.length > 4) { return null; } if (ellipticalParts.length === 2 && (verticalTokens.length === 0 || verticalTokens.length > 4)) { return null; } return { horizontal: horizontalTokens, vertical: verticalTokens }; } // ======================================== // Main parse function // ======================================== function parse(value) { if (!value || typeof value !== 'string') { return null; } const trimmed = value.trim(); if (!trimmed) { return null; } // CSS variables - parse and return directly if ((0, shared_utils_1.isCssVariable)(trimmed)) { return (0, css_variable_1.parse)(trimmed); } // Handle elliptical syntax const ellipticalResult = parseEllipticalSyntax(trimmed); if (!ellipticalResult) { return null; } const { horizontal, vertical } = ellipticalResult; // For elliptical syntax, we need to implement proper type support first // TODO: Implement full elliptical syntax support with proper types if (vertical.length > 0) { // Elliptical syntax not yet fully implemented - requires type system updates return null; } // Parse horizontal values const parsedValues = []; for (const token of horizontal) { const parsed = parseRadiusValue(token); if (!parsed) { return null; } parsedValues.push(parsed); } if (parsedValues.length === 0 || parsedValues.length > 4) { return null; } // Apply CSS shorthand expansion rules (1→4, 2→4, 3→4, 4→4) const expandedValues = (0, shared_utils_1.expandShorthandValues)(horizontal); const [topLeft, topRight, bottomRight, bottomLeft] = expandedValues; // Parse each corner value const borderTopLeftRadius = parseRadiusValue(topLeft); const borderTopRightRadius = parseRadiusValue(topRight); const borderBottomRightRadius = parseRadiusValue(bottomRight); const borderBottomLeftRadius = parseRadiusValue(bottomLeft); if (!borderTopLeftRadius || !borderTopRightRadius || !borderBottomRightRadius || !borderBottomLeftRadius) { return null; } return { borderTopLeftRadius, borderTopRightRadius, borderBottomRightRadius, borderBottomLeftRadius }; } // ======================================== // CSS value serialization // ======================================== function toCSSValue(parsed) { if (!parsed) { return null; } // Handle CSS variables if ('CSSvariable' in parsed) { return (0, css_variable_1.toCSSValue)(parsed); } const topLeft = serializeRadiusValue(parsed.borderTopLeftRadius); const topRight = serializeRadiusValue(parsed.borderTopRightRadius); const bottomRight = serializeRadiusValue(parsed.borderBottomRightRadius); const bottomLeft = serializeRadiusValue(parsed.borderBottomLeftRadius); if (!topLeft || !topRight || !bottomRight || !bottomLeft) { return null; } // Optimize output by removing redundant values const values = [topLeft, topRight, bottomRight, bottomLeft]; // Remove trailing values that match earlier ones (CSS shorthand compression) if (values[3] === values[1]) values.pop(); // Remove bottom-left if same as top-right if (values.length === 3 && values[2] === values[0]) values.pop(); // Remove bottom-right if same as top-left if (values.length === 2 && values[1] === values[0]) values.pop(); // Remove top-right if same as top-left return values.join(' '); } function serializeRadiusValue(radiusValue) { // Handle keywords if ('keyword' in radiusValue) { return radiusValue.keyword; } // Handle length/percentage by delegating to the appropriate toCSSValue function const lengthValue = (0, length_1.toCSSValue)(radiusValue); if (lengthValue) { return lengthValue; } const percentageValue = (0, percentage_1.toCSSValue)(radiusValue); if (percentageValue) { return percentageValue; } return null; }