Skip to content

Commit 354679e

Browse files
authored
Include BOOLEAN_OPERATION in SVG container collapse
fix: include BOOLEAN_OPERATION in SVG container collapse
2 parents b91f144 + 19c50b3 commit 354679e

2 files changed

Lines changed: 62 additions & 4 deletions

File tree

src/extractors/built-in.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,13 @@ export const layoutOnly = [layoutExtractor];
217217

218218
/**
219219
* Node types that can be exported as SVG images.
220-
* When a FRAME, GROUP, or INSTANCE contains only these types, we can collapse it to IMAGE-SVG.
221-
* Note: FRAME/GROUP/INSTANCE are NOT included here—they're only eligible if collapsed to IMAGE-SVG.
220+
* When a FRAME, GROUP, INSTANCE, or BOOLEAN_OPERATION contains only these types, we can collapse
221+
* it to IMAGE-SVG. BOOLEAN_OPERATION is included because it's both a collapsible container AND
222+
* SVG-eligible as a child (boolean ops always produce vector output).
222223
*/
223224
export const SVG_ELIGIBLE_TYPES = new Set([
224225
"IMAGE-SVG", // VECTOR nodes are converted to IMAGE-SVG, or containers that were collapsed
226+
"BOOLEAN_OPERATION",
225227
"STAR",
226228
"LINE",
227229
"ELLIPSE",
@@ -232,7 +234,7 @@ export const SVG_ELIGIBLE_TYPES = new Set([
232234
/**
233235
* afterChildren callback that collapses SVG-heavy containers to IMAGE-SVG.
234236
*
235-
* If a FRAME, GROUP, or INSTANCE contains only SVG-eligible children, the parent
237+
* If a FRAME, GROUP, INSTANCE, or BOOLEAN_OPERATION contains only SVG-eligible children, the parent
236238
* is marked as IMAGE-SVG and children are omitted, reducing payload size.
237239
*
238240
* @param node - Original Figma node
@@ -248,7 +250,10 @@ export function collapseSvgContainers(
248250
const allChildrenAreSvgEligible = children.every((child) => SVG_ELIGIBLE_TYPES.has(child.type));
249251

250252
if (
251-
(node.type === "FRAME" || node.type === "GROUP" || node.type === "INSTANCE") &&
253+
(node.type === "FRAME" ||
254+
node.type === "GROUP" ||
255+
node.type === "INSTANCE" ||
256+
node.type === "BOOLEAN_OPERATION") &&
252257
allChildrenAreSvgEligible &&
253258
!hasImageFillInChildren(node)
254259
) {

src/tests/tree-walker.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,59 @@ describe("extractFromDesign", () => {
122122
});
123123
});
124124

125+
describe("collapseSvgContainers", () => {
126+
it("collapses BOOLEAN_OPERATION nodes to IMAGE-SVG", async () => {
127+
const booleanOpNode = makeNode({
128+
id: "5:1",
129+
name: "Combined Shape",
130+
type: "BOOLEAN_OPERATION",
131+
booleanOperation: "UNION",
132+
children: [
133+
makeNode({ id: "5:2", name: "Circle", type: "ELLIPSE" }),
134+
makeNode({ id: "5:3", name: "Square", type: "RECTANGLE" }),
135+
],
136+
});
137+
138+
const { nodes } = await extractFromDesign([booleanOpNode], allExtractors, {
139+
afterChildren: collapseSvgContainers,
140+
});
141+
142+
expect(nodes).toHaveLength(1);
143+
expect(nodes[0].type).toBe("IMAGE-SVG");
144+
expect(nodes[0].children).toBeUndefined();
145+
});
146+
147+
it("collapses a frame containing a BOOLEAN_OPERATION to IMAGE-SVG", async () => {
148+
const frameWithBoolOp = makeNode({
149+
id: "6:1",
150+
name: "Icon Frame",
151+
type: "FRAME",
152+
children: [
153+
makeNode({
154+
id: "6:2",
155+
name: "Union",
156+
type: "BOOLEAN_OPERATION",
157+
booleanOperation: "UNION",
158+
children: [
159+
makeNode({ id: "6:3", name: "A", type: "RECTANGLE" }),
160+
makeNode({ id: "6:4", name: "B", type: "ELLIPSE" }),
161+
],
162+
}),
163+
],
164+
});
165+
166+
const { nodes } = await extractFromDesign([frameWithBoolOp], allExtractors, {
167+
afterChildren: collapseSvgContainers,
168+
});
169+
170+
// The BOOLEAN_OPERATION collapses to IMAGE-SVG first (bottom-up),
171+
// then the FRAME sees all children are SVG-eligible and collapses too.
172+
expect(nodes).toHaveLength(1);
173+
expect(nodes[0].type).toBe("IMAGE-SVG");
174+
expect(nodes[0].children).toBeUndefined();
175+
});
176+
});
177+
125178
describe("simplifyRawFigmaObject", () => {
126179
it("produces a complete SimplifiedDesign from a mock API response", async () => {
127180
const mockResponse = {

0 commit comments

Comments
 (0)