-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <[email protected]>
- Loading branch information
1 parent
9d99582
commit b4dc975
Showing
3 changed files
with
137 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
) { | ||
|
@@ -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}`); | ||
} | ||
} | ||
} | ||
}, | ||
|