import type { NodeChange } from "@xyflow/react"; import { applyNodeChanges } from "@xyflow/react"; import type { CanvasNode } from "./canvas-elements.ts"; import { nodeCenter, nodeSize } from "./canvas-geometry.ts"; /** Membership changes only on content drops, never on frame movement or resize. */ export function containingFrame(node: CanvasNode, nodes: CanvasNode[]) { if (node.data.kind === "frame") return undefined; const center = nodeCenter(node); return nodes.filter((frame) => { if (frame.data.kind !== "frame") return false; const { width, height } = nodeSize(frame); return center.x >= frame.position.x && center.x <= frame.position.x + width && center.y >= frame.position.y && center.y <= frame.position.y + height; }).sort((a, b) => nodeSize(a).width * nodeSize(a).height - nodeSize(b).width * nodeSize(b).height)[0]; } export function frameDropFeedback(nodes: CanvasNode[], droppedIds: string[]) { const ids = new Set(droppedIds); return nodes.filter((node) => ids.has(node.id) && node.data.kind !== "frame").map((node) => ({ id: node.id, from: node.data.frameId, to: containingFrame(node, nodes)?.id, })); } export function assignFrameMembership(nodes: CanvasNode[], droppedIds: string[]): CanvasNode[] { const dropped = new Set(droppedIds); return nodes.map((node) => { if (!dropped.has(node.id) || node.data.kind === "frame") return node; const frameId = containingFrame(node, nodes)?.id; if (frameId === node.data.frameId) return node; const { frameId: _old, ...data } = node.data; return { ...node, data: { ...data, ...(frameId ? { frameId } : {}) } }; }); } export function applyFrameNodeChanges(changes: NodeChange[], previous: CanvasNode[]) { const nodes = applyNodeChanges(changes, previous); const before = new Map(previous.map((node) => [node.id, node])); const positioned = new Set(changes.flatMap((change) => change.type === "position" && change.position ? [change.id] : [])); const frames = new Map(nodes.filter((node) => node.data.kind === "frame").map((node) => [node.id, node])); return nodes.map((node) => { const frameId = node.data.frameId; if (!frameId) return node; const frame = frames.get(frameId); if (!frame) { const { frameId: _old, ...data } = node.data; return { ...node, data }; } const oldFrame = before.get(frameId); if (!oldFrame || !positioned.has(frameId) || positioned.has(node.id)) return node; return { ...node, position: { x: node.position.x + frame.position.x - oldFrame.position.x, y: node.position.y + frame.position.y - oldFrame.position.y } }; }); }