fix(svg): preserve explicit none paints

- Normalize SVG paint sentinels before creating icon vector nodes
- Keep inline outline artwork transparent instead of falling back to black
- Cover fill and stroke none values through the Design JSX renderer

Co-authored-by: Rob Coenen <753704+rcoenen@users.noreply.github.com>
This commit is contained in:
Danila Poyarkov 2026-08-01 15:41:46 +03:00
parent 80cebe0767
commit f047b0bcb4
3 changed files with 27 additions and 4 deletions

View file

@ -4,6 +4,7 @@
### Fixed
- Keep `fill="none"` and `stroke="none"` SVG paths transparent when rendering inline artwork. (#446)
- Match regional browser languages to supported locales without selecting a secondary language. (#417)
- Save auto-layout frames that stretch their children to `.fig` without failing. (#427)
- Preserve multiple colors in imported vector artwork such as multi-color logos. (#386)

View file

@ -154,6 +154,10 @@ function combinedTransform(parent: string | null, element: Element): string | nu
return current ?? parent
}
function normalizeSVGPaint(value: string | null): string | null {
return value?.trim().toLowerCase() === 'none' ? null : value
}
function appendShapePath(
tagName: string,
element: Element,
@ -167,8 +171,8 @@ function appendShapePath(
const strokeWidth = Number.parseFloat(presentation.strokeWidth)
result.push({
d: pathData,
fill: presentation.fill === 'none' ? null : presentation.fill,
stroke: presentation.stroke === 'none' ? null : presentation.stroke,
fill: normalizeSVGPaint(presentation.fill),
stroke: normalizeSVGPaint(presentation.stroke),
strokeWidth: Number.isFinite(strokeWidth) ? strokeWidth : 1,
strokeCap: presentation.strokeCap,
strokeJoin: presentation.strokeJoin,
@ -285,8 +289,8 @@ export function scalePathInfos(
: svgpath(path.d).scale(scaleX, scaleY).round(2).toString()
return {
vectorNetwork: parseSVGPath(scaledD, path.fillRule),
fill: path.fill,
stroke: path.stroke,
fill: normalizeSVGPaint(path.fill),
stroke: normalizeSVGPaint(path.stroke),
strokeWidth: path.strokeWidth * Math.min(scaleX, scaleY),
strokeCap: path.strokeCap,
strokeJoin: path.strokeJoin

View file

@ -41,6 +41,24 @@ describe('jsx gaps', () => {
}
})
it('respects none paints on inline SVG paths', async () => {
const g = makeSceneGraph()
await renderJSX(
g,
`<svg viewBox="0 0 24 24" w={24} h={24}>
<path d="M2 2L22 22" fill="none" stroke="#021A3B" stroke-width="2" />
<path d="M2 22L12 2L22 22Z" fill="#FF0000" stroke="none" />
</svg>`
)
const vectors = [...g.nodes.values()].filter((node) => node.type === 'VECTOR')
expect(vectors).toHaveLength(2)
expect(vectors[0].fills).toEqual([])
expect(vectors[0].strokes).toHaveLength(1)
expect(vectors[1].fills).toHaveLength(1)
expect(vectors[1].strokes).toEqual([])
})
it('instance overrides apply child text by name', async () => {
const g = makeSceneGraph()
await renderJSX(