Merge pull request #78 from open-pencil/detach-orphaned-instances

Detach orphaned instances to FRAME on clipboard paste
This commit is contained in:
Danila Poyarkov 2026-03-09 00:11:05 +03:00 committed by GitHub
commit 3cd0e47fc8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 49 additions and 0 deletions

View file

@ -227,6 +227,17 @@ export function importClipboardNodes(
if (ourId) graph.deleteNode(ourId)
}
// Detach orphaned instances whose components weren't in the paste data.
// Without the component, children can't be populated — convert to FRAME
// so the node at least renders its own fills/strokes/layout.
for (const [, ourId] of created) {
const node = graph.getNode(ourId)
if (!node || node.type !== 'INSTANCE') continue
if (node.childIds.length === 0 && !graph.getNode(node.componentId)) {
graph.updateNode(ourId, { type: 'FRAME', componentId: '' })
}
}
return createdIds
}

View file

@ -372,6 +372,44 @@ describe('importClipboardNodes', () => {
}
})
it('detaches orphaned instances to FRAME when component is missing', () => {
const { graph, pageId } = createGraphWithPage()
const nodeChanges = [
{ guid: { sessionID: 0, localID: 0 }, type: 'DOCUMENT', name: 'Doc' },
{ guid: { sessionID: 0, localID: 1 }, parentIndex: { guid: { sessionID: 0, localID: 0 }, position: '!' }, type: 'CANVAS', name: 'Page' },
// Frame containing an instance whose component is NOT in the clipboard
{ guid: { sessionID: 0, localID: 10 }, parentIndex: { guid: { sessionID: 0, localID: 1 }, position: '!' }, type: 'FRAME', name: 'Card', size: { x: 400, y: 200 }, transform: { m00: 1, m01: 0, m02: 0, m10: 0, m11: 1, m12: 0 } },
{
guid: { sessionID: 0, localID: 11 },
parentIndex: { guid: { sessionID: 0, localID: 10 }, position: '!' },
type: 'INSTANCE', name: 'Button',
size: { x: 120, y: 40 },
transform: { m00: 1, m01: 0, m02: 20, m10: 0, m11: 1, m12: 20 },
fillPaints: [{ type: 'SOLID', color: { r: 0.2, g: 0.4, b: 1, a: 1 }, opacity: 1, visible: true }],
cornerRadius: 8,
symbolData: { symbolID: { sessionID: 99, localID: 999 } },
},
] as any[]
const created = importClipboardNodes(nodeChanges, graph, pageId)
expect(created).toHaveLength(1)
const card = graph.getNode(created[0])!
const children = graph.getChildren(card.id)
expect(children).toHaveLength(1)
const button = children[0]
expect(button.type).toBe('FRAME')
expect(button.name).toBe('Button')
expect(button.componentId).toBe('')
expect(button.fills).toHaveLength(1)
expect(button.fills[0].color.b).toBe(1)
expect(button.cornerRadius).toBe(8)
expect(button.width).toBe(120)
expect(button.height).toBe(40)
})
it('applies symbolOverrides text to instance children via overrideKey', () => {
const { graph, pageId } = createGraphWithPage()