Skip to content

Commit

Permalink
feat: support css variables for SvgCss (#2459)
Browse files Browse the repository at this point in the history
# 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 <[email protected]>
  • Loading branch information
bohdanprog and jakex7 authored Oct 2, 2024
1 parent 9d99582 commit b4dc975
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 2 deletions.
1 change: 1 addition & 0 deletions apps/test-examples/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import Test2327 from './src/Test2327';
import Test2233 from './src/Test2233';
import Test2363 from './src/Test2363';
import Test2366 from './src/Test2366';
import Test2380 from './src/Test2380';
import Test2397 from './src/Test2397';
import Test2403 from './src/Test2403';
import Test2407 from './src/Test2407';
Expand Down
58 changes: 58 additions & 0 deletions apps/test-examples/src/Test2380.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';
import {View} from 'react-native';
import {SvgCss} from 'react-native-svg/css';

const xml = `
<svg width="200" height="200" id="mySVG" viewBox="0 0 200 200">
<style>
#mySVG {
--my-color: #ff0000;
--color: #3e3efe;
--green-color: green;
--my-gold-stop-color: gold;
--my-red-stop-color: red;
--my-feFlood-color: green;
}
#newId {
fill: purple;
}
</style>
<defs>
<linearGradient id="myGradient">
<stop offset="5%" stop-color="var(--my-gold-stop-color)" />
<stop offset="95%" stop-color="var(--my-red-stop-color)" />
</linearGradient>
<filter id="spotlight">
<feFlood
result="floodFill"
x="0"
y="0"
width="100%"
height="100%"
flood-color="var(--my-feFlood-color)"
flood-opacity="1" />
<feBlend in="SourceGraphic" in2="floodFill" mode="multiply" />
</filter>
</defs>
<rect fill="var(--color)" x="40" y="40" width="32" height="32" />
<rect id="newId" x="80" y="40" width="32" height="32" />
<text fill="var(--color)" x="20" y="100" stroke="var(--green-color)" stroke-width="1" font-size="16">Hello</text>
<circle cx="140" cy="55" r="15" fill="url(#myGradient)" />
<image
href="https://static-00.iconduck.com/assets.00/mdn-icon-2048x1806-enhibj42.png"
x="0"
y="40"
width="32"
height="32"
style="filter:url(#spotlight);" />
</svg>
`;

export default function SvgComponent() {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<SvgCss xml={xml} height="200" width="200" />
</View>
);
}
80 changes: 78 additions & 2 deletions src/css/css.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,64 @@ const parseProps = {
* @author strarsis <[email protected]>
* @author modified by: msand <[email protected]>
*/

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
) {
Expand All @@ -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;
Expand All @@ -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: ' +
Expand All @@ -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) {
Expand Down Expand Up @@ -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}`);
}
}
}
},
Expand Down

0 comments on commit b4dc975

Please sign in to comment.