UNPKG

@wix/css-property-parser

Version:

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

221 lines (220 loc) 8.58 kB
"use strict"; // CSS text-decoration property parser // Handles parsing of CSS text-decoration property according to MDN specification // https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration Object.defineProperty(exports, "__esModule", { value: true }); exports.parse = parse; exports.toCSSValue = toCSSValue; const shared_utils_1 = require('../utils/shared-utils.cjs'); const length_1 = require('./length.cjs'); const percentage_1 = require('./percentage.cjs'); const color_1 = require('./color.cjs'); const css_variable_1 = require('./css-variable.cjs'); // Re-export for tests const types_1 = require('../types.cjs'); // Type guards for text-decoration values function isTextDecorationLine(value) { return types_1.TEXT_DECORATION_LINE_KEYWORDS.includes(value.toLowerCase()); } function isTextDecorationStyle(value) { return types_1.TEXT_DECORATION_STYLE_KEYWORDS.includes(value.toLowerCase()); } function isTextDecorationThicknessKeyword(value) { return types_1.TEXT_DECORATION_THICKNESS_KEYWORDS.includes(value.toLowerCase()); } function hasKeywordProperty(value) { return value !== null && typeof value === 'object' && 'keyword' in value; } function hasLinesProperty(value) { return value !== null && typeof value === 'object' && 'lines' in value; } /** * Parses a CSS text-decoration property string into structured components * Follows MDN specification: https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration */ 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 global keywords if ((0, shared_utils_1.isGlobalKeyword)(trimmed)) { const globalKeyword = trimmed.toLowerCase(); // Create properly typed global keyword objects for each property const lineGlobalKeyword = { type: 'keyword', keyword: globalKeyword }; const styleGlobalKeyword = { type: 'keyword', keyword: globalKeyword }; const colorGlobalKeyword = { type: 'keyword', keyword: globalKeyword }; const thicknessGlobalKeyword = { type: 'keyword', keyword: globalKeyword }; return { textDecorationLine: lineGlobalKeyword, textDecorationStyle: styleGlobalKeyword, textDecorationColor: colorGlobalKeyword, textDecorationThickness: thicknessGlobalKeyword }; } // Parse shorthand components using shared tokenizer const tokens = (0, shared_utils_1.tokenize)(trimmed); // Initialize constituent properties with defaults let textDecorationLine = { type: 'keyword', lines: ['none'] }; let textDecorationStyle = { type: 'keyword', keyword: 'solid' }; let textDecorationColor = { type: 'color', format: 'named', values: { name: 'currentcolor' } }; let textDecorationThickness = { type: 'keyword', keyword: 'auto' }; // Track which tokens have been consumed const remainingTokens = [...tokens]; const lineValues = []; let foundLine = false; // Parse each token and assign to appropriate constituent property for (let i = 0; i < remainingTokens.length; i++) { const token = remainingTokens[i]; // Try text-decoration-line if (isTextDecorationLine(token)) { const lineValue = token.toLowerCase(); if (lineValue === 'none') { // 'none' cannot be combined with other lines if (lineValues.length > 0) { return null; } lineValues.push(lineValue); } else { // Check if 'none' was already set if (lineValues.includes('none')) { return null; } // Don't add duplicates if (!lineValues.includes(lineValue)) { lineValues.push(lineValue); } } foundLine = true; remainingTokens.splice(i, 1); i--; // Adjust index after splice continue; } // Try text-decoration-style if (isTextDecorationStyle(token)) { textDecorationStyle = { type: 'keyword', keyword: token.toLowerCase() }; remainingTokens.splice(i, 1); i--; // Adjust index after splice continue; } // Try text-decoration-thickness keywords if (isTextDecorationThicknessKeyword(token)) { textDecorationThickness = { type: 'keyword', keyword: token.toLowerCase() }; remainingTokens.splice(i, 1); i--; // Adjust index after splice continue; } // Try length/percentage for thickness const lengthResult = (0, length_1.parse)(token); if (lengthResult) { textDecorationThickness = lengthResult; remainingTokens.splice(i, 1); i--; // Adjust index after splice continue; } const percentageResult = (0, percentage_1.parse)(token); if (percentageResult) { textDecorationThickness = percentageResult; remainingTokens.splice(i, 1); i--; // Adjust index after splice continue; } // Try color const colorResult = (0, color_1.parse)(token); if (colorResult) { // Handle CSS variables from color parser if ('CSSvariable' in colorResult) { // CSS variables shouldn't be parsed at the token level in shorthand // They should be handled at the top level continue; } textDecorationColor = colorResult; remainingTokens.splice(i, 1); i--; // Adjust index after splice continue; } } // If there are remaining unrecognized tokens, the input is invalid if (remainingTokens.length > 0) { return null; } // Must have at least one line decoration specified if (!foundLine) { return null; } // Set the line decoration with all collected lines textDecorationLine = { type: 'keyword', lines: lineValues.length > 0 ? lineValues : ['none'] }; return { textDecorationLine, textDecorationStyle, textDecorationColor, textDecorationThickness }; } /** * Converts a parsed text-decoration back to a CSS value string */ function toCSSValue(parsed) { if (!parsed) { return null; } // Handle CSS variables if ('CSSvariable' in parsed) { return (0, css_variable_1.toCSSValue)(parsed); } const parts = []; // Add text-decoration-line if (hasKeywordProperty(parsed.textDecorationLine)) { if ((0, shared_utils_1.isGlobalKeyword)(parsed.textDecorationLine.keyword)) { return parsed.textDecorationLine.keyword; } } else if (hasLinesProperty(parsed.textDecorationLine)) { const lines = parsed.textDecorationLine.lines; if (lines.length > 0 && !lines.includes('none')) { parts.push(...lines); } } // Add text-decoration-style if (hasKeywordProperty(parsed.textDecorationStyle)) { if (parsed.textDecorationStyle.keyword !== 'solid') { parts.push(parsed.textDecorationStyle.keyword); } } // Add text-decoration-color let colorValue = null; if ('keyword' in parsed.textDecorationColor) { // Handle TextDecorationColorKeyword colorValue = parsed.textDecorationColor.keyword; } else { // Handle CSSColorValue colorValue = (0, color_1.toCSSValue)(parsed.textDecorationColor); } if (colorValue && colorValue !== 'currentcolor') { parts.push(colorValue); } // Add text-decoration-thickness if (hasKeywordProperty(parsed.textDecorationThickness)) { if (parsed.textDecorationThickness.keyword !== 'auto') { parts.push(parsed.textDecorationThickness.keyword); } } else { const thicknessValue = (0, length_1.toCSSValue)(parsed.textDecorationThickness) || (0, percentage_1.toCSSValue)(parsed.textDecorationThickness); if (thicknessValue) { parts.push(thicknessValue); } } return parts.length > 0 ? parts.join(' ') : 'none'; } // Export centralized type for external use