feat: support css variables for SvgCss (#2459)

# Summary
Feature #2380 

We want to add support for CSS variables when passing them to parse the
SVG XML source function.

## Test Plan
Test app -> src -> Test2380

## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| iOS     |         		 |
| MacOS   |            |
| Android |         	|
| Web     |    		    |

---------

Co-authored-by: Jakub Grzywacz <jakub.grzywacz@swmansion.com>
This commit is contained in:
Bohdan Artiukhov
2024-10-02 09:23:34 +02:00
committed by GitHub
parent 9d9958264b
commit b4dc9756df
3 changed files with 137 additions and 2 deletions

View File

@@ -592,6 +592,64 @@ const parseProps = {
* @author strarsis <strarsis@gmail.com>
* @author modified by: msand <msand@abo.fi>
*/
function extractVariables(stylesheet: CssNode): Map<string, string> {
const variables = new Map<string, string>();
csstree.walk(stylesheet, {
visit: 'Declaration',
enter(node) {
const { property, value } = node as Declaration;
if (property.startsWith('--')) {
const variableName = property.trim();
const variableValue = csstree.generate(value).trim();
variables.set(variableName, variableValue);
}
},
});
return variables;
}
function resolveVariables(
value: string | CssNode | undefined,
variables: Map<string, string>
): string {
if (value === undefined) {
return '';
}
const valueStr = typeof value === 'string' ? value : csstree.generate(value);
return valueStr.replace(
/var\((--[^,)]+)(?:,\s*([^)]+))?\)/g,
(_, variableName, fallback) => {
const resolvedValue = variables.get(variableName);
if (resolvedValue !== undefined) {
return resolveVariables(resolvedValue, variables);
}
return fallback ? resolveVariables(fallback, variables) : '';
}
);
}
const propsToResolve = [
'color',
'fill',
'floodColor',
'lightingColor',
'stopColor',
'stroke',
];
const resolveElementVariables = (
element: XmlAST,
variables: Map<string, string>
) =>
propsToResolve.forEach((prop) => {
const value = element.props[prop] as string;
if (value && value.startsWith('var(')) {
element.props[prop] = resolveVariables(value, variables);
}
});
export const inlineStyles: Middleware = function inlineStyles(
document: XmlAST
) {
@@ -604,6 +662,7 @@ export const inlineStyles: Middleware = function inlineStyles(
}
const selectors: FlatSelectorList = [];
let variables = new Map<string, string>();
for (const element of styleElements) {
const { children } = element;
@@ -615,7 +674,10 @@ export const inlineStyles: Middleware = function inlineStyles(
// collect <style/>s and their css ast
try {
const styleString = children.join('');
flattenToSelectors(csstree.parse(styleString, parseProps), selectors);
const stylesheet = csstree.parse(styleString, parseProps);
variables = extractVariables(stylesheet);
flattenToSelectors(stylesheet, selectors);
} catch (parseError) {
console.warn(
'Warning: Parse error of styles of <style/> element, skipped. Error details: ' +
@@ -636,6 +698,15 @@ export const inlineStyles: Middleware = function inlineStyles(
// stable sort selectors
const sortedSelectors = sortSelectors(selectorsPseudo).reverse();
const elementsWithColor = cssSelect(
'*[color], *[fill], *[floodColor], *[lightingColor], *[stopColor], *[stroke]',
document,
cssSelectOpts
);
for (const element of elementsWithColor) {
resolveElementVariables(element, variables);
}
// match selectors
for (const { rule, item } of sortedSelectors) {
if (rule === null) {
@@ -667,7 +738,12 @@ export const inlineStyles: Middleware = function inlineStyles(
const current = priority.get(name);
if (current === undefined || current < important) {
priority.set(name, important as boolean);
style[camel] = val;
// Handle if value is undefined
if (val !== undefined) {
style[camel] = val;
} else {
console.warn(`Undefined value for style property: ${camel}`);
}
}
}
},