UNPKG

@wix/css-property-parser

Version:

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

212 lines (211 loc) 7.22 kB
import { isCssVariable, isGlobalKeyword, tokenize, getValidKeyword } from '../utils/shared-utils.js'; import { parse as parseCSSVariable, toCSSValue as cssVariableToCSSValue } from './css-variable.js'; import { GRID_ROW_KEYWORDS } from '../types.js'; /** * Parses a single grid line value (<grid-line>) * Syntax: auto | <custom-ident> | [ [ <integer> ] && <custom-ident>? ] | [ span && [ <integer> || <custom-ident> ] ] */ function parseGridLine(value) { if (!value || typeof value !== 'string') return null; const trimmed = value.trim(); if (trimmed === '') return null; // CSS variables if (isCssVariable(trimmed)) { return parseCSSVariable(trimmed); } // Handle global keywords first if (isGlobalKeyword(trimmed)) { return { type: 'keyword', keyword: trimmed.toLowerCase() }; } // Handle auto keyword if (trimmed.toLowerCase() === 'auto') { return { type: 'keyword', keyword: 'auto' }; } // Handle span syntax if (trimmed.toLowerCase().includes('span')) { const tokens = tokenize(trimmed.toLowerCase()); // Find span keyword const spanIndex = tokens.findIndex(token => token === 'span'); if (spanIndex === -1) return null; let count; let name; // Process tokens around span const otherTokens = tokens.filter((_, index) => index !== spanIndex); for (const token of otherTokens) { const intValue = parseInt(token, 10); if (!isNaN(intValue) && intValue > 0) { if (count !== undefined) return null; // Multiple numbers not allowed count = intValue; } else if (/^[a-zA-Z_][\w-]*$/.test(token) && token !== 'auto') { if (name !== undefined) return null; // Multiple names not allowed name = token; } else { return null; // Invalid token } } // Span requires at least count OR name if (count === undefined && name === undefined) { count = 1; // Default span count } return { type: 'span', count: count || 1, name }; } // Handle multi-token values (number + identifier combinations) const tokens = tokenize(trimmed); if (tokens.length === 2) { const [first, second] = tokens; // <integer> <custom-ident> const intValue = parseInt(first, 10); if (!isNaN(intValue) && intValue !== 0 && first === intValue.toString()) { if (/^[a-zA-Z_][\w-]*$/.test(second) && second !== 'span' && second !== 'auto') { return { type: 'named-line', name: second, count: intValue }; } } // <custom-ident> <integer> const intValue2 = parseInt(second, 10); if (!isNaN(intValue2) && intValue2 !== 0 && second === intValue2.toString()) { if (/^[a-zA-Z_][\w-]*$/.test(first) && first !== 'span' && first !== 'auto') { return { type: 'named-line', name: first, count: intValue2 }; } } return null; } // Handle single token values if (tokens.length === 1) { const token = tokens[0]; // Integer line number const intValue = parseInt(token, 10); if (!isNaN(intValue) && intValue !== 0 && token === intValue.toString()) { return { type: 'integer', value: intValue }; } // Custom identifier (grid area name or line name) if (/^[a-zA-Z_][\w-]*$/.test(token) && token !== 'span' && token !== 'auto') { return { type: 'named-line', name: token }; } return null; } return null; } /** * Converts a grid line value back to CSS string */ function gridLineToCSSValue(gridLine) { if (!gridLine) return null; switch (gridLine.type) { case 'keyword': return gridLine.keyword; case 'integer': return gridLine.value.toString(); case 'named-line': { if (!gridLine.name) return null; const parts = []; if (gridLine.count !== undefined && gridLine.count !== 1) { parts.push(gridLine.count.toString()); } parts.push(gridLine.name); return parts.join(' '); } case 'span': { const parts = ['span']; if (gridLine.count !== undefined && gridLine.count !== 1) { parts.push(gridLine.count.toString()); } if (gridLine.name) { parts.push(gridLine.name); } return parts.join(' '); } case 'variable': return cssVariableToCSSValue(gridLine) || 'auto'; default: return null; } } /** * Parses a CSS grid-row property string into structured components * Syntax: <grid-line> [ / <grid-line> ]? */ export 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 (isCssVariable(trimmed)) { return parseCSSVariable(trimmed); } // Handle global keywords first if (isGlobalKeyword(trimmed)) { return { type: 'keyword', keyword: trimmed.toLowerCase() }; } // Handle grid-row keywords const gridRowKeyword = getValidKeyword(trimmed, GRID_ROW_KEYWORDS); if (gridRowKeyword) { return { type: 'keyword', keyword: gridRowKeyword }; } // Handle shorthand syntax with slash if (trimmed.includes(' / ')) { const parts = trimmed.split(' / '); if (parts.length === 2) { const start = parseGridLine(parts[0].trim()); const end = parseGridLine(parts[1].trim()); if (start && end) { return { type: 'grid-placement', start, end }; } } return null; } // Handle single grid line value const gridLine = parseGridLine(trimmed); if (gridLine) { return { type: 'grid-placement', start: gridLine }; } return null; } /** * Converts a parsed grid-row back to a CSS value string */ export function toCSSValue(parsed) { if (!parsed) return null; // Handle CSS variables if ('CSSvariable' in parsed) { return cssVariableToCSSValue(parsed); } // Handle keywords if (parsed.type === 'keyword') { return parsed.keyword; } // Handle grid placement if (parsed.type === 'grid-placement') { const start = gridLineToCSSValue(parsed.start || null); const end = gridLineToCSSValue(parsed.end || null); if (start && end) { // Don't output "auto" for end value if it's auto if (end === 'auto') { return start; } return `${start} / ${end}`; } else if (start) { return start; } } return null; }