UNPKG

@wix/css-property-parser

Version:

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

646 lines (645 loc) 26 kB
"use strict"; // Background and all constituent properties implementation // Comprehensive implementation following MDN specification // https://developer.mozilla.org/en-US/docs/Web/CSS/background Object.defineProperty(exports, "__esModule", { value: true }); exports.BackgroundImage = exports.BackgroundPosition = exports.BackgroundSize = exports.BackgroundRepeat = exports.BackgroundOrigin = exports.BackgroundColor = exports.BackgroundClip = exports.BackgroundAttachment = void 0; exports.parse = parse; exports.toCSSValue = toCSSValue; /* eslint-disable @typescript-eslint/no-namespace */ const shared_utils_1 = require('../utils/shared-utils.cjs'); const color_1 = require('./color.cjs'); const length_1 = require('./length.cjs'); const percentage_1 = require('./percentage.cjs'); const position_1 = require('./position.cjs'); const css_variable_1 = require('./css-variable.cjs'); // ============================================================================= // BACKGROUND-ATTACHMENT PROPERTY // ============================================================================= const types_1 = require('../types.cjs'); var BackgroundAttachment; (function (BackgroundAttachment) { const ATTACHMENT_KEYWORDS = types_1.BACKGROUND_ATTACHMENT_KEYWORDS; function parse(value) { if (!value || typeof value !== 'string') return null; const trimmed = value.trim(); if (trimmed === '') return null; // CSS variables - return proper CSSVariable object if ((0, shared_utils_1.isCssVariable)(trimmed)) { return (0, css_variable_1.parse)(trimmed); } // Global keywords const globalKeyword = (0, shared_utils_1.getValidKeyword)(trimmed.toLowerCase(), ['inherit', 'initial', 'unset', 'revert', 'revert-layer']); if (globalKeyword) { return { type: 'keyword', keyword: globalKeyword }; } // Attachment-specific keywords const lowerValue = trimmed.toLowerCase(); const attachmentKeyword = (0, shared_utils_1.getValidKeyword)(lowerValue, ATTACHMENT_KEYWORDS); if (attachmentKeyword) { return { type: 'keyword', keyword: attachmentKeyword }; } return null; } BackgroundAttachment.parse = parse; function toCSSValue(parsed) { if (!parsed) return null; // Handle CSS variables if ('CSSvariable' in parsed) { return (0, css_variable_1.toCSSValue)(parsed); } // Handle keyword values return parsed.keyword; } BackgroundAttachment.toCSSValue = toCSSValue; })(BackgroundAttachment || (exports.BackgroundAttachment = BackgroundAttachment = {})); // ============================================================================= // BACKGROUND-CLIP PROPERTY // ============================================================================= var BackgroundClip; (function (BackgroundClip) { function parse(value) { if (!value || typeof value !== 'string') return null; const trimmed = value.trim(); if (trimmed === '') return null; // CSS variables - return proper CSSVariable object if ((0, shared_utils_1.isCssVariable)(trimmed)) { return (0, css_variable_1.parse)(trimmed); } // Global keywords const globalKeyword = (0, shared_utils_1.getValidKeyword)(trimmed.toLowerCase(), ['inherit', 'initial', 'unset', 'revert', 'revert-layer']); if (globalKeyword) { return { type: 'keyword', keyword: globalKeyword }; } // Clip-specific keywords const lowerValue = trimmed.toLowerCase(); const clipKeyword = (0, shared_utils_1.getValidKeyword)(lowerValue, types_1.BOX_KEYWORDS); if (clipKeyword) { return { type: 'keyword', keyword: clipKeyword }; } return null; } BackgroundClip.parse = parse; function toCSSValue(parsed) { if (!parsed) return null; // Handle CSS variables if ('CSSvariable' in parsed) { return (0, css_variable_1.toCSSValue)(parsed); } // Handle keyword values return parsed.keyword; } BackgroundClip.toCSSValue = toCSSValue; })(BackgroundClip || (exports.BackgroundClip = BackgroundClip = {})); // ============================================================================= // BACKGROUND-COLOR PROPERTY // ============================================================================= var BackgroundColor; (function (BackgroundColor) { const COLOR_KEYWORDS = types_1.BACKGROUND_COLOR_KEYWORDS; function parse(value) { if (!value || typeof value !== 'string') return null; const trimmed = value.trim(); if (trimmed === '') return null; // CSS variables - return proper CSSVariable object if ((0, shared_utils_1.isCssVariable)(trimmed)) { return (0, css_variable_1.parse)(trimmed); } // Global keywords const globalKeyword = (0, shared_utils_1.getValidKeyword)(trimmed.toLowerCase(), ['inherit', 'initial', 'unset', 'revert', 'revert-layer']); if (globalKeyword) { return { type: 'keyword', keyword: globalKeyword }; } // Transparent keyword const lowerValue = trimmed.toLowerCase(); const colorKeyword = (0, shared_utils_1.getValidKeyword)(lowerValue, COLOR_KEYWORDS); if (colorKeyword) { return { type: 'keyword', keyword: colorKeyword }; } // Try to parse as color const colorResult = (0, color_1.parse)(trimmed); if (colorResult) { // Handle CSS variables from color parser - // CSS variables should be handled at the main background level, not here if ('CSSvariable' in colorResult) { return null; } return colorResult; } return null; } BackgroundColor.parse = parse; function toCSSValue(parsed) { if (!parsed) return null; // Handle CSS variables if ('CSSvariable' in parsed) { return (0, css_variable_1.toCSSValue)(parsed); } if ('keyword' in parsed) { return parsed.keyword; } else { return (0, color_1.toCSSValue)(parsed); } } BackgroundColor.toCSSValue = toCSSValue; })(BackgroundColor || (exports.BackgroundColor = BackgroundColor = {})); // ============================================================================= // BACKGROUND-ORIGIN PROPERTY // ============================================================================= var BackgroundOrigin; (function (BackgroundOrigin) { function parse(value) { if (!value || typeof value !== 'string') return null; const trimmed = value.trim(); if (trimmed === '') return null; // CSS variables - return proper CSSVariable object if ((0, shared_utils_1.isCssVariable)(trimmed)) { return (0, css_variable_1.parse)(trimmed); } // Global keywords const globalKeyword = (0, shared_utils_1.getValidKeyword)(trimmed.toLowerCase(), ['inherit', 'initial', 'unset', 'revert', 'revert-layer']); if (globalKeyword) { return { type: 'keyword', keyword: globalKeyword }; } // Origin-specific keywords const lowerValue = trimmed.toLowerCase(); const originKeyword = (0, shared_utils_1.getValidKeyword)(lowerValue, types_1.BOX_KEYWORDS); if (originKeyword) { return { type: 'keyword', keyword: originKeyword }; } return null; } BackgroundOrigin.parse = parse; function toCSSValue(parsed) { if (!parsed) return null; // Handle CSS variables if ('CSSvariable' in parsed) { return (0, css_variable_1.toCSSValue)(parsed); } // Handle keyword values return parsed.keyword; } BackgroundOrigin.toCSSValue = toCSSValue; })(BackgroundOrigin || (exports.BackgroundOrigin = BackgroundOrigin = {})); // ============================================================================= // BACKGROUND-REPEAT PROPERTY // ============================================================================= var BackgroundRepeat; (function (BackgroundRepeat) { const REPEAT_KEYWORDS = types_1.BACKGROUND_REPEAT_KEYWORDS; function parse(value) { if (!value || typeof value !== 'string') return null; const trimmed = value.trim(); if (trimmed === '') return null; // CSS variables - return proper CSSVariable object if ((0, shared_utils_1.isCssVariable)(trimmed)) { return (0, css_variable_1.parse)(trimmed); } // Global keywords const globalKeyword = (0, shared_utils_1.getValidKeyword)(trimmed.toLowerCase(), ['inherit', 'initial', 'unset', 'revert', 'revert-layer']); if (globalKeyword) { return { type: 'keyword', keyword: globalKeyword }; } // Check for single or two-value repeat syntax const lowerValue = trimmed.toLowerCase(); const tokens = lowerValue.split(/\s+/); if (tokens.length === 1) { // Single value const repeatKeyword = (0, shared_utils_1.getValidKeyword)(tokens[0], REPEAT_KEYWORDS); if (repeatKeyword) { return { type: 'keyword', keyword: repeatKeyword }; } } else if (tokens.length === 2) { // Two values const firstKeyword = (0, shared_utils_1.getValidKeyword)(tokens[0], REPEAT_KEYWORDS); const secondKeyword = (0, shared_utils_1.getValidKeyword)(tokens[1], REPEAT_KEYWORDS); if (firstKeyword && secondKeyword) { // Combine keywords for compound repeat values const combinedKeyword = `${firstKeyword} ${secondKeyword}`; return { type: 'keyword', keyword: combinedKeyword }; } } return null; } BackgroundRepeat.parse = parse; function toCSSValue(parsed) { if (!parsed) return null; // Handle CSS variables if ('CSSvariable' in parsed) { return (0, css_variable_1.toCSSValue)(parsed); } // Handle keyword values return parsed.keyword; } BackgroundRepeat.toCSSValue = toCSSValue; })(BackgroundRepeat || (exports.BackgroundRepeat = BackgroundRepeat = {})); // ============================================================================= // BACKGROUND-SIZE PROPERTY // ============================================================================= var BackgroundSize; (function (BackgroundSize) { const SIZE_KEYWORDS = types_1.BACKGROUND_SIZE_KEYWORDS; function parse(value) { if (!value || typeof value !== 'string') return null; const trimmed = value.trim(); if (trimmed === '') return null; // CSS variables - return proper CSSVariable object if ((0, shared_utils_1.isCssVariable)(trimmed)) { return (0, css_variable_1.parse)(trimmed); } // Global keywords const globalKeyword = (0, shared_utils_1.getValidKeyword)(trimmed.toLowerCase(), ['inherit', 'initial', 'unset', 'revert', 'revert-layer']); if (globalKeyword) { return { type: 'keyword', keyword: globalKeyword }; } // Size keywords (cover, contain, auto) const lowerValue = trimmed.toLowerCase(); const sizeKeyword = (0, shared_utils_1.getValidKeyword)(lowerValue, SIZE_KEYWORDS); if (sizeKeyword) { return { type: 'keyword', keyword: sizeKeyword }; } // Parse as length/percentage values const tokens = lowerValue.split(/\s+/); if (tokens.length === 1) { // Single value const sizeValue = parseSizeValue(tokens[0]); if (sizeValue) { return { width: sizeValue }; } } else if (tokens.length === 2) { // Two values const width = parseSizeValue(tokens[0]); const height = parseSizeValue(tokens[1]); if (width && height) { return { width, height }; } } return null; } BackgroundSize.parse = parse; function parseSizeValue(value) { // Try auto keyword first if (value === 'auto') { return { type: 'keyword', keyword: 'auto' }; } // Try length const lengthResult = (0, length_1.parse)(value); if (lengthResult) { return lengthResult; } // Try percentage const percentageResult = (0, percentage_1.parse)(value); if (percentageResult) { return percentageResult; } return null; } function toCSSValue(parsed) { if (!parsed) return null; // Handle CSS variables if ('CSSvariable' in parsed) { return (0, css_variable_1.toCSSValue)(parsed); } if ('keyword' in parsed) { return parsed.keyword; } if ('width' in parsed) { const widthStr = sizeValueToString(parsed.width); if ('height' in parsed) { const heightStr = sizeValueToString(parsed.height); return `${widthStr} ${heightStr}`; } return widthStr; } return null; } BackgroundSize.toCSSValue = toCSSValue; function sizeValueToString(sizeValue) { if (typeof sizeValue === 'object' && sizeValue !== null) { if ('keyword' in sizeValue) { return sizeValue.keyword; } if ('value' in sizeValue && 'unit' in sizeValue) { const typedValue = sizeValue; return `${typedValue.value}${typedValue.unit}`; } } return ''; } })(BackgroundSize || (exports.BackgroundSize = BackgroundSize = {})); // ============================================================================= // BACKGROUND-POSITION PROPERTY // ============================================================================= var BackgroundPosition; (function (BackgroundPosition) { function parse(value) { if (!value || typeof value !== 'string') return null; const trimmed = value.trim(); if (trimmed === '') return null; // CSS variables - return proper CSSVariable object if ((0, shared_utils_1.isCssVariable)(trimmed)) { return (0, css_variable_1.parse)(trimmed); } // Global keywords const globalKeyword = (0, shared_utils_1.getValidKeyword)(trimmed.toLowerCase(), ['inherit', 'initial', 'unset', 'revert', 'revert-layer']); if (globalKeyword) { return { type: 'keyword', keyword: globalKeyword }; } // Use position evaluator for position parsing const positionResult = (0, position_1.parse)(trimmed); if (positionResult) { // Handle CSS variables from position parser - // CSS variables should be handled at the main background level, not here if ('CSSvariable' in positionResult) { return null; } return positionResult; } return null; } BackgroundPosition.parse = parse; function toCSSValue(parsed) { if (!parsed) return null; // Handle CSS variables if ('CSSvariable' in parsed) { return (0, css_variable_1.toCSSValue)(parsed); } if ('keyword' in parsed) { return parsed.keyword; } return (0, position_1.toCSSValue)(parsed); } BackgroundPosition.toCSSValue = toCSSValue; })(BackgroundPosition || (exports.BackgroundPosition = BackgroundPosition = {})); // ============================================================================= // BACKGROUND-IMAGE PROPERTY // ============================================================================= var BackgroundImage; (function (BackgroundImage) { const IMAGE_KEYWORDS = types_1.BACKGROUND_IMAGE_KEYWORDS; const GRADIENT_FUNCTIONS = ['linear-gradient', 'radial-gradient', 'conic-gradient', 'repeating-linear-gradient', 'repeating-radial-gradient', 'repeating-conic-gradient']; function parse(value) { if (!value || typeof value !== 'string') return null; const trimmed = value.trim(); if (trimmed === '') return null; // CSS variables - return proper CSSVariable object if ((0, shared_utils_1.isCssVariable)(trimmed)) { return (0, css_variable_1.parse)(trimmed); } // Global keywords const globalKeyword = (0, shared_utils_1.getValidKeyword)(trimmed.toLowerCase(), ['inherit', 'initial', 'unset', 'revert', 'revert-layer']); if (globalKeyword) { return { type: 'keyword', keyword: globalKeyword }; } // Image keywords (none) const lowerValue = trimmed.toLowerCase(); const imageKeyword = (0, shared_utils_1.getValidKeyword)(lowerValue, IMAGE_KEYWORDS); if (imageKeyword) { return { type: 'keyword', keyword: imageKeyword }; } // Check for URL if (lowerValue.startsWith('url(')) { return parseUrl(trimmed); } // Check for gradients for (const gradientFunction of GRADIENT_FUNCTIONS) { if (lowerValue.startsWith(gradientFunction + '(')) { return parseGradient(trimmed, gradientFunction); } } return null; } BackgroundImage.parse = parse; function parseUrl(value) { const match = value.match(/^url\(\s*(['"]?)(.*?)\1\s*\)$/); if (!match) { return null; } const [, quote, url] = match; return { type: 'url', url: url, quoted: !!quote }; } function parseGradient(value, functionName) { // Simple gradient parsing - just store the full value if (value.endsWith(')')) { return { type: 'gradient', function: functionName, value: value }; } return null; } function toCSSValue(parsed) { if (!parsed) return null; // Handle CSS variables if ('CSSvariable' in parsed) { return (0, css_variable_1.toCSSValue)(parsed); } if ('keyword' in parsed) { return parsed.keyword; } if ('type' in parsed) { if (parsed.type === 'url') { if (parsed.quoted) { return `url("${parsed.url}")`; } else { return `url(${parsed.url})`; } } if (parsed.type === 'gradient') { return parsed.value; } } return null; } BackgroundImage.toCSSValue = toCSSValue; })(BackgroundImage || (exports.BackgroundImage = BackgroundImage = {})); // ============================================================================= // MAIN BACKGROUND SHORTHAND PROPERTY // ============================================================================= 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); } // Global keywords const globalKeyword = (0, shared_utils_1.getValidKeyword)(trimmed.toLowerCase(), ['inherit', 'initial', 'unset', 'revert', 'revert-layer']); if (globalKeyword) { return { type: 'keyword', keyword: globalKeyword }; } // Try to parse as expanded background const expanded = parseExpandedBackground(trimmed); if (expanded) { return expanded; } // If nothing matches, treat as keyword (fallback behavior for backward compatibility) return { type: 'keyword', keyword: trimmed.toLowerCase() }; } function parseExpandedBackground(value) { // Default values for all background properties const defaults = { backgroundAttachment: { type: 'keyword', keyword: 'scroll' }, backgroundClip: { type: 'keyword', keyword: 'border-box' }, backgroundColor: { type: 'keyword', keyword: 'transparent' }, backgroundImage: { type: 'keyword', keyword: 'none' }, backgroundOrigin: { type: 'keyword', keyword: 'padding-box' }, backgroundPosition: { type: 'position', x: '0%', y: '0%' }, backgroundRepeat: { type: 'keyword', keyword: 'repeat' }, backgroundSize: { type: 'keyword', keyword: 'auto' } }; // Parse individual components const tokens = (0, shared_utils_1.tokenize)(value); const result = { ...defaults }; let foundMatch = false; for (const token of tokens) { // Try each constituent parser const attachmentResult = BackgroundAttachment.parse(token); if (attachmentResult) { result.backgroundAttachment = attachmentResult; foundMatch = true; continue; } const clipResult = BackgroundClip.parse(token); if (clipResult) { result.backgroundClip = clipResult; foundMatch = true; continue; } const colorResult = BackgroundColor.parse(token); if (colorResult) { result.backgroundColor = colorResult; foundMatch = true; continue; } const imageResult = BackgroundImage.parse(token); if (imageResult) { result.backgroundImage = imageResult; foundMatch = true; continue; } const originResult = BackgroundOrigin.parse(token); if (originResult) { result.backgroundOrigin = originResult; foundMatch = true; continue; } const positionResult = BackgroundPosition.parse(token); if (positionResult) { result.backgroundPosition = positionResult; foundMatch = true; continue; } const repeatResult = BackgroundRepeat.parse(token); if (repeatResult) { result.backgroundRepeat = repeatResult; foundMatch = true; continue; } const sizeResult = BackgroundSize.parse(token); if (sizeResult) { result.backgroundSize = sizeResult; foundMatch = true; continue; } } return foundMatch ? result : null; } function toCSSValue(parsed) { if (!parsed) return null; // Handle CSS variables if ('CSSvariable' in parsed) { return (0, css_variable_1.toCSSValue)(parsed); } if ('keyword' in parsed) { return parsed.keyword; } if ('layers' in parsed) { // Multi-layer background return parsed.layers.map(layer => expandedToCSSValue(layer)).join(', '); } // Single expanded background return expandedToCSSValue(parsed); } function expandedToCSSValue(expanded) { const parts = []; // Add non-default values if (expanded.backgroundImage && !('keyword' in expanded.backgroundImage && expanded.backgroundImage.keyword === 'none')) { const imageValue = BackgroundImage.toCSSValue(expanded.backgroundImage); if (imageValue) parts.push(imageValue); } if (expanded.backgroundPosition && 'x' in expanded.backgroundPosition && !(expanded.backgroundPosition.x === '0%' && expanded.backgroundPosition.y === '0%')) { const positionValue = BackgroundPosition.toCSSValue(expanded.backgroundPosition); if (positionValue) parts.push(positionValue); } if (expanded.backgroundSize && !('keyword' in expanded.backgroundSize && expanded.backgroundSize.keyword === 'auto')) { const sizeValue = BackgroundSize.toCSSValue(expanded.backgroundSize); if (sizeValue) parts.push('/', sizeValue); } if (expanded.backgroundRepeat && !('keyword' in expanded.backgroundRepeat && expanded.backgroundRepeat.keyword === 'repeat')) { const repeatValue = BackgroundRepeat.toCSSValue(expanded.backgroundRepeat); if (repeatValue) parts.push(repeatValue); } if (expanded.backgroundAttachment && !('keyword' in expanded.backgroundAttachment && expanded.backgroundAttachment.keyword === 'scroll')) { const attachmentValue = BackgroundAttachment.toCSSValue(expanded.backgroundAttachment); if (attachmentValue) parts.push(attachmentValue); } if (expanded.backgroundOrigin && !('keyword' in expanded.backgroundOrigin && expanded.backgroundOrigin.keyword === 'padding-box')) { const originValue = BackgroundOrigin.toCSSValue(expanded.backgroundOrigin); if (originValue) parts.push(originValue); } if (expanded.backgroundClip && !('keyword' in expanded.backgroundClip && expanded.backgroundClip.keyword === 'border-box')) { const clipValue = BackgroundClip.toCSSValue(expanded.backgroundClip); if (clipValue) parts.push(clipValue); } if (expanded.backgroundColor && !('keyword' in expanded.backgroundColor && expanded.backgroundColor.keyword === 'transparent')) { const colorValue = BackgroundColor.toCSSValue(expanded.backgroundColor); if (colorValue) parts.push(colorValue); } return parts.join(' ').trim() || 'transparent'; }