/** Splits a CSS style string into declarations, ignoring semicolons inside quotes/parentheses. */ function splitStyleDeclarations(styles: string): string[] { const result: string[] = [] let current = '' let inSingleQuote = false let inDoubleQuote = false let parenDepth = 0 const length = styles.length for (let i = 0; i < length; i += 1) { const char = styles[i] if (char === "'" && !inDoubleQuote) { inSingleQuote = !inSingleQuote current += char continue } if (char === '"' && !inSingleQuote) { inDoubleQuote = !inDoubleQuote current += char continue } if (!inSingleQuote && !inDoubleQuote) { if (char === '(') { parenDepth += 1 current += char continue } if (char === ')' && parenDepth > 0) { parenDepth -= 1 current += char continue } if (char === ';' && parenDepth === 0) { result.push(current) current = '' continue } } current += char } if (current) { result.push(current) } return result } /** Yields property/value pairs from a style string. */ function parseStyleEntries(styles: string | undefined): [property: string, value: string][] { const pairs: [string, string][] = [] const declarations = splitStyleDeclarations(styles || '') const numDeclarations = declarations.length for (let i = 0; i < numDeclarations; i += 1) { const declaration = declarations[i] const firstColonIndex = declaration.indexOf(':') if (firstColonIndex === -1) { continue } const property = declaration.slice(0, firstColonIndex).trim() const value = declaration.slice(firstColonIndex + 1).trim() if (property && value) { pairs.push([property, value]) } } return pairs } export function mergeAttributes(...objects: Record[]): Record { return objects .filter(item => !!item) .reduce((items, item) => { const mergedAttributes = { ...items } Object.entries(item).forEach(([key, value]) => { const exists = mergedAttributes[key] if (!exists) { mergedAttributes[key] = value return } if (key === 'class') { const valueClasses: string[] = value ? String(value).split(' ') : [] const existingClasses: string[] = mergedAttributes[key] ? mergedAttributes[key].split(' ') : [] const insertClasses = valueClasses.filter(valueClass => !existingClasses.includes(valueClass)) mergedAttributes[key] = [...existingClasses, ...insertClasses].join(' ') } else if (key === 'style') { const styleMap = new Map([...parseStyleEntries(mergedAttributes[key]), ...parseStyleEntries(value)]) mergedAttributes[key] = Array.from(styleMap.entries()) .map(([property, val]) => `${property}: ${val}`) .join('; ') } else { mergedAttributes[key] = value } }) return mergedAttributes }, {}) }