"use client" import { type PointerEvent } from "react" import { useReactFlow } from "@xyflow/react" interface Bounds { minWidth?: number maxWidth?: number minHeight?: number maxHeight?: number } /** 拖动期间持续更新 ReactFlow node 的 width/height,同时写到 style.width/height 让 wrapper 真实渲染。 * 自适应 viewport zoom。不走 xyflow NodeResizeControl(glass-node overflow:hidden + reserved-type CSS 干扰下不响应)。 */ function startResize( e: PointerEvent, setNodes: ReturnType["setNodes"], getZoom: ReturnType["getZoom"], axis: "x" | "y" | "xy", bounds: Required, ) { e.preventDefault() e.stopPropagation() const target = e.currentTarget const nodeEl = target.closest(".react-flow__node") as HTMLElement | null const nodeId = nodeEl?.getAttribute("data-id") if (!nodeId || !nodeEl) return target.setPointerCapture(e.pointerId) const startX = e.clientX const startY = e.clientY const zoom = getZoom() || 1 const startWidth = parseFloat(getComputedStyle(nodeEl).width) / zoom const startHeight = parseFloat(getComputedStyle(nodeEl).height) / zoom const onMove = (ev: globalThis.PointerEvent) => { const dx = (ev.clientX - startX) / zoom const dy = (ev.clientY - startY) / zoom const wantW = axis === "y" ? null : Math.max(bounds.minWidth, Math.min(bounds.maxWidth, startWidth + dx)) const wantH = axis === "x" ? null : Math.max(bounds.minHeight, Math.min(bounds.maxHeight, startHeight + dy)) setNodes((nodes) => nodes.map((n) => { if (n.id !== nodeId) return n const nextStyle = { ...n.style } const patch: Record = {} if (wantW !== null) { patch.width = wantW; nextStyle.width = wantW } if (wantH !== null) { patch.height = wantH; nextStyle.height = wantH } return { ...n, ...patch, style: nextStyle } }), ) } const onUp = () => { window.removeEventListener("pointermove", onMove) window.removeEventListener("pointerup", onUp) try { target.releasePointerCapture(e.pointerId) } catch {} } window.addEventListener("pointermove", onMove) window.addEventListener("pointerup", onUp) } /** 右边缘把手 — 只改宽度 */ export function ResizeRight({ minWidth = 240, maxWidth = 1400 }: Bounds) { const { setNodes, getZoom } = useReactFlow() return (
startResize(e, setNodes, getZoom, "x", { minWidth, maxWidth, minHeight: 0, maxHeight: 0 })} title="拖动调整宽度" className="nodrag absolute z-20 hover:bg-violet-400/60 active:bg-violet-400/80 transition rounded-r" style={{ right: 0, top: 12, bottom: 12, width: 6, cursor: "ew-resize", touchAction: "none" }} /> ) } /** 右下角把手 — 同时改宽和高 */ export function ResizeBR({ minWidth = 240, maxWidth = 1400, minHeight = 120, maxHeight = 1400, }: Bounds) { const { setNodes, getZoom } = useReactFlow() return (
startResize(e, setNodes, getZoom, "xy", { minWidth, maxWidth, minHeight, maxHeight })} title="拖动调整大小(宽 × 高)" className="nodrag absolute z-30 hover:bg-violet-400/80 active:bg-violet-400 transition" style={{ right: 0, bottom: 0, width: 14, height: 14, cursor: "nwse-resize", touchAction: "none", background: "linear-gradient(135deg, transparent 50%, rgba(167,139,250,0.55) 50%)", }} /> ) }