openpencil/packages/fig/src/instance-overrides/sync/propagate.ts
Danila Poyarkov 2bd2304bcb refactor(fig): own instance interpretation
- Move component properties, symbol overrides, derived symbol data, and clone synchronization into @open-pencil/fig\n- Add the public instance-overrides subpath with package tests and packed smoke coverage\n- Remove cross-package forwarding modules and enforce direct owner imports in architecture checks
2026-07-18 02:06:34 +03:00

89 lines
2.7 KiB
TypeScript

import type { SceneGraph } from '@open-pencil/scene-graph'
import type { ProtectionMap } from '../patches'
import { buildClonesMap, syncChildrenDeep } from './clones'
import { syncNodeProps } from './fields'
function expandSeedsToParents(graph: SceneGraph, seeds: Set<string>): Set<string> {
const expanded = new Set(seeds)
for (const seedId of seeds) {
let cur = graph.getNode(seedId)
while (cur?.parentId) {
const parent = graph.getNode(cur.parentId)
if (!parent) break
if (parent.type === 'INSTANCE' || parent.type === 'COMPONENT') expanded.add(parent.id)
cur = parent
}
}
return expanded
}
function buildNeedsSyncSet(
expandedSeeds: Set<string>,
clonesOf: Map<string, string[]>
): Set<string> {
const needsSync = new Set<string>()
const queue = [...expandedSeeds]
for (let id = queue.pop(); id !== undefined; id = queue.pop()) {
const clones = clonesOf.get(id)
if (!clones) continue
for (const cloneId of clones) {
if (needsSync.has(cloneId)) continue
needsSync.add(cloneId)
queue.push(cloneId)
}
}
return needsSync
}
export function propagateOverridesTransitively(
graph: SceneGraph,
seeds: Set<string>,
swappedInstances: Set<string>,
componentIdRoot: Map<string, string>,
protect?: Set<string>,
activeNodeIds?: Set<string>,
protections?: ProtectionMap
): void {
if (seeds.size === 0) return
componentIdRoot.clear()
const clonesOf = buildClonesMap(graph, activeNodeIds)
const expandedSeeds = expandSeedsToParents(graph, seeds)
const needsSync = buildNeedsSyncSet(expandedSeeds, clonesOf)
const skip = protect && protect.size > 0 ? new Set([...seeds, ...protect]) : seeds
const visited = new Set<string>()
const syncQueue = [...expandedSeeds]
let index = 0
while (index < syncQueue.length) {
const sourceId = syncQueue[index]
index++
const clones = clonesOf.get(sourceId)
if (!clones) continue
const source = graph.getNode(sourceId)
if (!source) continue
for (const cloneId of clones) {
if (!needsSync.has(cloneId) || visited.has(cloneId)) continue
visited.add(cloneId)
const node = graph.getNode(cloneId)
if (!node) continue
if (skip.has(cloneId)) {
syncQueue.push(cloneId)
continue
}
syncNodeProps(graph, source, node, protections)
if (source.childIds.length !== node.childIds.length) {
for (const childId of Array.from(node.childIds)) graph.deleteNode(childId)
if (source.childIds.length > 0) graph.populateInstanceChildren(node.id, sourceId)
} else if (source.childIds.length > 0 && node.childIds.length > 0) {
syncChildrenDeep(graph, sourceId, node.id, swappedInstances, skip, protections)
}
syncQueue.push(cloneId)
}
}
}