@wix/css-property-parser
Version:
A comprehensive TypeScript library for parsing and serializing CSS property values with full MDN specification compliance
124 lines (123 loc) • 4.5 kB
JavaScript
;
// Font Family property parser - Modern API Implementation
// Handles parsing of CSS font-family property according to MDN specification
// https://developer.mozilla.org/en-US/docs/Web/CSS/font-family
Object.defineProperty(exports, "__esModule", { value: true });
exports.parse = parse;
exports.toCSSValue = toCSSValue;
const shared_utils_1 = require('../utils/shared-utils.cjs');
const css_variable_1 = require('./css-variable.cjs');
// Generic font families as per CSS spec
const GENERIC_FAMILIES = [
'serif', 'sans-serif', 'monospace', 'cursive', 'fantasy',
'system-ui', 'ui-serif', 'ui-sans-serif', 'ui-monospace',
'ui-rounded', 'math', 'emoji', 'fangsong'
];
/**
* Parse font family value - reusing logic from font.ts but modernized
*/
function parseFontFamily(value) {
if (!value || value.trim() === '') {
return null;
}
const families = [];
const parts = value.split(',');
for (const part of parts) {
const trimmed = part.trim();
if (trimmed === '')
continue;
// Check for invalid tokens that suggest this isn't a font family
// Reject if it contains CSS units (px, em, rem, %, etc.)
if (/\d+(px|em|rem|pt|pc|in|cm|mm|ex|ch|vh|vw|vmin|vmax|%)(\s|$)/i.test(trimmed)) {
return null;
}
// Handle quotes - preserve them but validate the content
const family = trimmed;
let unquotedFamily = family;
if ((family.startsWith('"') && family.endsWith('"')) ||
(family.startsWith("'") && family.endsWith("'"))) {
unquotedFamily = family.slice(1, -1);
// Reject empty quoted strings
if (unquotedFamily === '') {
return null;
}
// Process Unicode escape sequences in quoted strings
try {
unquotedFamily = unquotedFamily.replace(/\\u([0-9a-fA-F]{4})/g, (match, hex) => {
return String.fromCharCode(parseInt(hex, 16));
});
}
catch {
// Invalid Unicode escape sequence
return null;
}
// Reject quoted strings with invalid characters (newlines, tabs, etc.)
if (/[\n\r\t]/.test(unquotedFamily)) {
return null;
}
}
// Validate font family names for invalid characters
// Generic families are always valid
const lowerFamily = unquotedFamily.toLowerCase();
if (!GENERIC_FAMILIES.includes(lowerFamily)) {
// For non-generic families, specifically reject characters that are clearly invalid in font names
// Reject if contains numbers followed by units or other clearly non-font-name patterns
if (/^\d/.test(unquotedFamily) || /[<>{}[\]()@#$%^&*+=|\\:;"?/]/.test(unquotedFamily)) {
return null;
}
}
families.push(family);
}
if (families.length === 0) {
return null;
}
return { type: 'font-list', families };
}
/**
* Parses a CSS font-family property value
* @param value - The CSS font-family property string
* @returns Parsed font family object or null if invalid
*/
function parse(value) {
if (!value || typeof value !== 'string') {
return null;
}
const trimmed = value.trim();
if (trimmed === '') {
return null;
}
// CSS variables - parse and return directly (modern pattern)
if ((0, shared_utils_1.isCssVariable)(trimmed)) {
return (0, css_variable_1.parse)(trimmed);
}
// Global keywords - not supported in current type system for font-family
// TODO: Add FontFamilyKeywordValue type to support global keywords
if ((0, shared_utils_1.isGlobalKeyword)(trimmed)) {
return null;
}
// Parse font family list
const result = parseFontFamily(trimmed);
if (result) {
return result;
}
return null;
}
/**
* Converts a parsed font family back to a CSS value string
* @param parsed - The parsed font family object
* @returns CSS value string or null if invalid
*/
function toCSSValue(parsed) {
if (!parsed) {
return null;
}
// Handle CSS variables
if ('CSSvariable' in parsed) {
return (0, css_variable_1.toCSSValue)(parsed);
}
// Handle font family list
if ('families' in parsed && parsed.type === 'font-list') {
return parsed.families.join(', ');
}
return null;
}