import type { NodeTree, Node, NodeId } from '@craftjs/core'; import { getRandomId } from '@craftjs/utils'; /** * Returns a deep-cloned NodeTree where every node id in the tree * (rootNodeId, the `nodes` map keys, each `node.id`, each internal * `node.data.parent`, every `node.data.nodes` entry, and every * `node.data.linkedNodes` value) has been remapped to a fresh, * collision-free id generated by Craft.js's own id generator. * * The subtree root's `data.parent` is deliberately left untouched -- * `actions.addNodeTree(tree, parentId)` sets that itself when the tree is * attached to its new parent. * * The input tree is not mutated. */ export function regenerateTreeIds(tree: NodeTree): NodeTree { const oldIds = Object.keys(tree.nodes); // Build old -> new id map first so all cross-references can be rewritten // consistently regardless of iteration order. const idMap = new Map(); for (const oldId of oldIds) { idMap.set(oldId, getRandomId()); } const remapId = (id: NodeId): NodeId => idMap.get(id) ?? id; const newNodes: Record = {}; for (const oldId of oldIds) { const oldNode = tree.nodes[oldId]; const newId = idMap.get(oldId)!; const newParent = oldId === tree.rootNodeId ? oldNode.data.parent // subtree root's parent is set by the caller : oldNode.data.parent !== null && oldNode.data.parent !== undefined ? remapId(oldNode.data.parent) : oldNode.data.parent; const newLinkedNodes: Record = {}; for (const [slot, linkedId] of Object.entries(oldNode.data.linkedNodes || {})) { newLinkedNodes[slot] = remapId(linkedId); } // Deep-clone data so mutable sub-objects (props, custom, etc.) are never // shared by reference between the original node and its regenerated // copy. Craft.js's setProp mutates data.props in place, so a shallow // copy here would let edits to the duplicate silently corrupt the // original. const clonedData = structuredClone(oldNode.data); const newNode: Node = { ...oldNode, id: newId, data: { ...clonedData, parent: newParent, nodes: (oldNode.data.nodes || []).map(remapId), linkedNodes: newLinkedNodes, }, }; newNodes[newId] = newNode; } return { rootNodeId: remapId(tree.rootNodeId), nodes: newNodes, }; }