mirror of
https://github.com/zoriya/react-native-svg.git
synced 2026-06-05 07:59:28 +00:00
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:
@@ -27,6 +27,7 @@ import Test2327 from './src/Test2327';
|
|||||||
import Test2233 from './src/Test2233';
|
import Test2233 from './src/Test2233';
|
||||||
import Test2363 from './src/Test2363';
|
import Test2363 from './src/Test2363';
|
||||||
import Test2366 from './src/Test2366';
|
import Test2366 from './src/Test2366';
|
||||||
|
import Test2380 from './src/Test2380';
|
||||||
import Test2397 from './src/Test2397';
|
import Test2397 from './src/Test2397';
|
||||||
import Test2403 from './src/Test2403';
|
import Test2403 from './src/Test2403';
|
||||||
import Test2407 from './src/Test2407';
|
import Test2407 from './src/Test2407';
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
+78
-2
@@ -592,6 +592,64 @@ const parseProps = {
|
|||||||
* @author strarsis <strarsis@gmail.com>
|
* @author strarsis <strarsis@gmail.com>
|
||||||
* @author modified by: msand <msand@abo.fi>
|
* @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(
|
export const inlineStyles: Middleware = function inlineStyles(
|
||||||
document: XmlAST
|
document: XmlAST
|
||||||
) {
|
) {
|
||||||
@@ -604,6 +662,7 @@ export const inlineStyles: Middleware = function inlineStyles(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectors: FlatSelectorList = [];
|
const selectors: FlatSelectorList = [];
|
||||||
|
let variables = new Map<string, string>();
|
||||||
|
|
||||||
for (const element of styleElements) {
|
for (const element of styleElements) {
|
||||||
const { children } = element;
|
const { children } = element;
|
||||||
@@ -615,7 +674,10 @@ export const inlineStyles: Middleware = function inlineStyles(
|
|||||||
// collect <style/>s and their css ast
|
// collect <style/>s and their css ast
|
||||||
try {
|
try {
|
||||||
const styleString = children.join('');
|
const styleString = children.join('');
|
||||||
flattenToSelectors(csstree.parse(styleString, parseProps), selectors);
|
const stylesheet = csstree.parse(styleString, parseProps);
|
||||||
|
|
||||||
|
variables = extractVariables(stylesheet);
|
||||||
|
flattenToSelectors(stylesheet, selectors);
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
console.warn(
|
console.warn(
|
||||||
'Warning: Parse error of styles of <style/> element, skipped. Error details: ' +
|
'Warning: Parse error of styles of <style/> element, skipped. Error details: ' +
|
||||||
@@ -636,6 +698,15 @@ export const inlineStyles: Middleware = function inlineStyles(
|
|||||||
// stable sort selectors
|
// stable sort selectors
|
||||||
const sortedSelectors = sortSelectors(selectorsPseudo).reverse();
|
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
|
// match selectors
|
||||||
for (const { rule, item } of sortedSelectors) {
|
for (const { rule, item } of sortedSelectors) {
|
||||||
if (rule === null) {
|
if (rule === null) {
|
||||||
@@ -667,7 +738,12 @@ export const inlineStyles: Middleware = function inlineStyles(
|
|||||||
const current = priority.get(name);
|
const current = priority.get(name);
|
||||||
if (current === undefined || current < important) {
|
if (current === undefined || current < important) {
|
||||||
priority.set(name, important as boolean);
|
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}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user