From 814352ec90b504acfc6b334e7c654cb796def9a1 Mon Sep 17 00:00:00 2001 From: xemc <0xemc@protonmail.com> Date: Wed, 26 Aug 2026 10:17:13 +1000 Subject: [PATCH 1/2] fix(figma-api): preserve variable bindings when converting a node to a component createComponentFromNode copied a hand-picked list of fields onto the new COMPONENT node but omitted boundVariables/variableModes, silently dropping any variable bindings on the frame itself (padding, radius, stroke, etc). Child nodes were unaffected since they're copied via cloneTree, which already carries bindings through cloneNodeProps. --- packages/core/src/figma-api/index.ts | 4 +++- .../api/create/component-from-node.test.ts | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/core/src/figma-api/index.ts b/packages/core/src/figma-api/index.ts index ef1ef4dab..38017a6a7 100644 --- a/packages/core/src/figma-api/index.ts +++ b/packages/core/src/figma-api/index.ts @@ -256,7 +256,9 @@ export class FigmaAPI implements NodeProxyHost { paddingBottom: raw.paddingBottom, paddingLeft: raw.paddingLeft, pluginData: structuredClone(raw.pluginData), - pluginRelaunchData: structuredClone(raw.pluginRelaunchData) + pluginRelaunchData: structuredClone(raw.pluginRelaunchData), + boundVariables: { ...raw.boundVariables }, + variableModes: { ...raw.variableModes } }) for (const childId of raw.childIds) { this.graph.cloneTree(childId, comp.id) diff --git a/tests/engine/figma/api/create/component-from-node.test.ts b/tests/engine/figma/api/create/component-from-node.test.ts index 6180763df..cf3954ed9 100644 --- a/tests/engine/figma/api/create/component-from-node.test.ts +++ b/tests/engine/figma/api/create/component-from-node.test.ts @@ -22,4 +22,21 @@ describe('createComponentFromNode', () => { expect(comp.children[0].name).toBe('Background') expect(api.getNodeById(frameId)).toBeNull() }) + + test('preserves the frame own variable bindings', () => { + const api = createAPI() + const collection = api.createVariableCollection('Radii') + const variable = api.createVariable('radius/md', 'FLOAT', collection.id, 8) + + const frame = api.createFrame() + frame.name = 'Card' + frame.resize(200, 50) + frame.cornerRadius = 8 + api.bindVariable(frame.id, 'cornerRadius', variable.id) + + const comp = api.createComponentFromNode(frame) + + const raw = api.graph.getNode(comp.id) + expect(raw?.boundVariables.cornerRadius).toBe(variable.id) + }) }) From b9ab77d7e9c2b7db5d768122a1767769bb3d46b7 Mon Sep 17 00:00:00 2001 From: xemc <0xemc@protonmail.com> Date: Wed, 26 Aug 2026 10:18:28 +1000 Subject: [PATCH 2/2] docs: add changelog entry for variable-binding fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index adff3b329..ffc922fad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ ### Fixed +- Preserve a frame's own variable bindings (padding, radius, stroke, etc.) when converting it into a component with `create_component`; previously only child-level bindings survived. - Keep text-editing carets, hit testing, and selection highlights aligned with vertically centered or bottom-aligned text. (#539) - Match AI chat code-block syntax colors and backgrounds to the active light or dark theme. (#537) - Let desktop users select and copy AI chat text without replacing it with the selected canvas layers. (#538)