"use client"; import { useContext, useEffect, useRef, useState } from "react"; import { FoldVertical, UnfoldVertical, ExternalLink } from "lucide-react"; import { Handle, NodeToolbar, Position, useViewport, type NodeProps } from "@xyflow/react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import type { CanvasNode } from "@/lib/canvas"; import { NodeEditing, TransformableNode, ResizeToggle } from "./transformable-node"; import { FrameFeedback } from "./frame-feedback"; import { FrameLabelMenu } from "./frame-label-menu"; import { ContentHandles } from "./content-handles"; import styles from "./canvas.module.css"; export function FrameNode(props: NodeProps & { directAnchors?: boolean }) { const { directAnchors = false, ...nodeProps } = props; const { id, data, selected } = nodeProps; const editing = useContext(NodeEditing); const feedback = useContext(FrameFeedback); const incoming = feedback.drops.filter((drop) => drop.to === id && drop.from !== id).length; const outgoing = feedback.drops.filter((drop) => drop.from === id && drop.to !== id).length; const { zoom } = useViewport(); const reduce = useReducedMotion(); const [title, setTitle] = useState(data.title); useEffect(() => setTitle(data.title), [data.title]); const cancelBlur = useRef(false); const mode = data.labelMode || "fixed"; function saveTitle() { const name = title.trim() || "未命名分区"; if (name !== data.title) editing.frame(id, { title: name }); setTitle(name); } return <> {selected && event.stopPropagation()} onClick={(event) => event.stopPropagation()} onDoubleClick={(event) => event.stopPropagation()} onKeyDown={(event) => event.stopPropagation()}> setTitle(event.target.value)} onBlur={() => { if (cancelBlur.current) cancelBlur.current = false; else saveTitle(); }} onKeyDown={(event) => { if (event.key === "Enter" && !event.nativeEvent.isComposing) { event.currentTarget.blur(); } if (event.key === "Escape") { cancelBlur.current = true; setTitle(data.title); event.currentTarget.blur(); } }} /> editing.frame(id, { labelMode })} /> {data.subgraph && <> } } {data.subgraph && data.presentation?.collapsed && event.stopPropagation()}>{data.title}打开子画布} {mode !== "hidden" &&
{data.title}
} {(incoming > 0 || outgoing > 0) &&
{incoming > 0 ? `松开加入 · ${incoming} 项` : `松开移出 · ${outgoing} 项`}
} {(["top", "right", "bottom", "left"] as const).map((side) =>
)} {directAnchors && } {data.ports?.map((port) => { const peers = data.ports!.filter((item) => item.direction === port.direction); return ; })} ; }