* fix(fig): keep the sizes Figma derived for instance layers when a page loads Loading a page resynced every component its new instances used, which copied component sizes over the sizes Figma derived for instance layers on earlier pages, and over those of the page's own new instances. A text field filling a 312px instance came back at its component's 210px. The page now syncs only the instances it placed, through a new SceneGraph.syncInstance, and then puts back the sizes Figma derived for them unless that component layer's size was edited live. The gold input now matches the captured Figma Content width (374.86 instead of 633.69), and its placeholder matches the 97.51 x 39.38 Figma derived in the file. * fix(fig): keep live edits to mapped component layers over derived sizes restoreDerivedSizes checked only a layer's own component for live edits. A nested instance clone syncs from the layer its owning instance maps it to through sourceComponentId, so a live width edit to that layer was carried over by the sync and then replaced by the size Figma derived. The restore now reads the same source layers as reconcileLiveComponentEdits, through a shared syncSourceLayers helper.
69 lines
2.4 KiB
TypeScript
69 lines
2.4 KiB
TypeScript
import {
|
|
getInstanceOverride,
|
|
INSTANCE_SYNC_FIELDS,
|
|
type SceneGraph,
|
|
type SceneNode
|
|
} from '@open-pencil/scene-graph'
|
|
|
|
import type { MaterializedInstance } from './materialize-instance'
|
|
|
|
/** The instances that own `target`, nearest first, starting with `target` when it is one. */
|
|
function owningInstances(graph: SceneGraph, target: SceneNode): SceneNode[] {
|
|
const owners: SceneNode[] = []
|
|
let parent = target.parentId ? graph.getNode(target.parentId) : undefined
|
|
while (parent) {
|
|
if (parent.type === 'INSTANCE') owners.push(parent)
|
|
parent = parent.parentId ? graph.getNode(parent.parentId) : undefined
|
|
}
|
|
if (target.type === 'INSTANCE') owners.unshift(target)
|
|
return owners
|
|
}
|
|
|
|
/**
|
|
* The component layers `target` syncs from, one per owning instance: the layer the owner maps it
|
|
* to, or its own component.
|
|
*/
|
|
export function syncSourceLayers(graph: SceneGraph, target: SceneNode): SceneNode[] {
|
|
const sources: SceneNode[] = []
|
|
for (const owner of owningInstances(graph, target)) {
|
|
const mapped = getInstanceOverride(
|
|
owner.instanceOverrides,
|
|
owner.id,
|
|
target.id,
|
|
'sourceComponentId'
|
|
)
|
|
const sourceId = typeof mapped === 'string' ? mapped : target.componentId
|
|
const source = sourceId ? graph.getNode(sourceId) : undefined
|
|
if (source) sources.push(source)
|
|
}
|
|
return sources
|
|
}
|
|
|
|
/** Apply live component edits to new occurrences, never repaint or resync existing pages. */
|
|
export function reconcileLiveComponentEdits(
|
|
graph: SceneGraph,
|
|
materialized: MaterializedInstance
|
|
): void {
|
|
graph.preserveSourceMetadataDuring(() => {
|
|
for (const target of materialized.nodes.values()) {
|
|
const owners = owningInstances(graph, target)
|
|
const protectedFields = new Set<string>()
|
|
for (const owner of owners) {
|
|
const fields =
|
|
owner.id === target.id
|
|
? owner.instanceOverrides.self
|
|
: owner.instanceOverrides.descendants.get(target.id)
|
|
for (const field of fields?.keys() ?? []) protectedFields.add(field)
|
|
}
|
|
const updates: Partial<SceneNode> = {}
|
|
for (const source of syncSourceLayers(graph, target)) {
|
|
for (const field of INSTANCE_SYNC_FIELDS) {
|
|
if (!source.source.editedFields.includes(field) || protectedFields.has(field)) continue
|
|
Object.assign(updates, { [field]: structuredClone(source[field]) })
|
|
}
|
|
}
|
|
if (Object.keys(updates).length) graph.updateNode(target.id, updates)
|
|
}
|
|
})
|
|
}
|