fix(fig): preserve imported instance layout sync

This commit is contained in:
Danila Poyarkov 2026-05-18 18:21:27 +03:00
parent 6d8920a496
commit 9e2cd356e5
4 changed files with 63 additions and 8 deletions

View file

@ -38,11 +38,7 @@ export function syncChildrenDeep(
const tgtNode = graph.getNode(tgt.childIds[i])
if (!srcNode || !tgtNode || srcNode.type !== tgtNode.type) continue
if (
srcNode.type === 'INSTANCE' &&
swappedInstances.has(src.childIds[i]) &&
srcNode.componentId !== tgtNode.componentId
) {
if (srcNode.type === 'INSTANCE' && srcNode.componentId !== tgtNode.componentId) {
recloneChildren(graph, src.childIds[i], tgtNode, swappedInstances, protections)
continue
}

View file

@ -32,12 +32,12 @@ function applyFrameSize(graph: SceneGraph, frame: SceneNode, yogaNode: YogaNode)
}
function updateChildFromYoga(graph: SceneGraph, child: SceneNode, yogaChild: YogaNode): void {
if (!child.visible || child.layoutPositioning === 'ABSOLUTE' || child.type === 'INSTANCE') return
if (!child.visible || child.layoutPositioning === 'ABSOLUTE') return
const derived = child.figmaDerivedLayout
graph.updateNode(child.id, {
x: derived?.x ?? yogaChild.getComputedLeft(),
y: derived?.y ?? yogaChild.getComputedTop(),
x: child.type === 'INSTANCE' ? yogaChild.getComputedLeft() : (derived?.x ?? yogaChild.getComputedLeft()),
y: child.type === 'INSTANCE' ? yogaChild.getComputedTop() : (derived?.y ?? yogaChild.getComputedTop()),
width: derived?.width ?? yogaChild.getComputedWidth(),
height: derived?.height ?? yogaChild.getComputedHeight()
})

View file

@ -0,0 +1,34 @@
import { describe, expect, test } from 'bun:test'
import { SceneGraph } from '@open-pencil/core'
import { syncChildrenDeep } from '#core/kiwi/instance-overrides/sync/clones'
describe('instance override clone sync', () => {
test('reclones nested instance children when the source component changes', () => {
const graph = new SceneGraph()
const page = graph.getPages()[0]
const sourceParent = graph.createNode('FRAME', page.id)
const targetParent = graph.createNode('FRAME', page.id)
const sourceComponent = graph.createNode('COMPONENT', page.id, { name: 'source component' })
const targetComponent = graph.createNode('COMPONENT', page.id, { name: 'target component' })
graph.createNode('TEXT', sourceComponent.id, { name: 'label', text: 'Source' })
graph.createNode('TEXT', targetComponent.id, { name: 'label', text: 'Target' })
const sourceChild = graph.createNode('INSTANCE', sourceParent.id, {
name: 'nested',
componentId: sourceComponent.id
})
graph.populateInstanceChildren(sourceChild.id, sourceComponent.id)
const targetChild = graph.createNode('INSTANCE', targetParent.id, {
name: 'nested',
componentId: targetComponent.id
})
graph.populateInstanceChildren(targetChild.id, targetComponent.id)
syncChildrenDeep(graph, sourceParent.id, targetParent.id, new Set())
const syncedChild = graph.getNode(targetChild.id)
expect(syncedChild?.componentId).toBe(sourceComponent.id)
expect(graph.getChildren(targetChild.id).map((child) => child.text)).toEqual(['Source'])
})
})

View file

@ -103,4 +103,29 @@ describe('imported auto-layout bounds', () => {
expect(graph.getNode(field.id)).toMatchObject({ x: 0, y: 0, width: 276, height: 40 })
})
test('uses Yoga positions for imported instances while preserving imported size', () => {
const graph = new SceneGraph()
const page = graph.getPages()[0]
const row = graph.createNode('FRAME', page.id, {
width: 200,
height: 40,
layoutMode: 'HORIZONTAL',
primaryAxisSizing: 'FIXED',
counterAxisSizing: 'FIXED',
primaryAxisAlign: 'MAX',
counterAxisAlign: 'CENTER'
})
const instance = graph.createNode('INSTANCE', row.id, {
x: 0,
y: 0,
width: 40,
height: 20,
figmaDerivedLayout: { x: 0, y: 0, width: 40, height: 20 }
})
computeAllLayouts(graph)
expect(graph.getNode(instance.id)).toMatchObject({ x: 160, y: 10, width: 40, height: 20 })
})
})