From 095e1fbe81347fa5c1fa8320d81a33a57b24ba1d Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 2 Nov 2022 14:38:37 +0100 Subject: [PATCH] feat(edge-labels): add div based renderer --- examples/vite-app/src/App/index.tsx | 6 + .../src/examples/EdgeRenderer/CustomEdge.tsx | 61 ++++++ .../src/examples/EdgeRenderer/CustomEdge2.tsx | 51 +++++ .../src/examples/EdgeRenderer/index.tsx | 206 ++++++++++++++++++ packages/core/package.json | 1 + .../components/EdgeLabelRenderer/index.tsx | 15 ++ .../core/src/container/GraphView/index.tsx | 2 + packages/core/src/index.ts | 1 + packages/core/src/styles/init.css | 7 + pnpm-lock.yaml | 2 + 10 files changed, 352 insertions(+) create mode 100644 examples/vite-app/src/examples/EdgeRenderer/CustomEdge.tsx create mode 100644 examples/vite-app/src/examples/EdgeRenderer/CustomEdge2.tsx create mode 100644 examples/vite-app/src/examples/EdgeRenderer/index.tsx create mode 100644 packages/core/src/components/EdgeLabelRenderer/index.tsx diff --git a/examples/vite-app/src/App/index.tsx b/examples/vite-app/src/App/index.tsx index 90339935..0e2823f6 100644 --- a/examples/vite-app/src/App/index.tsx +++ b/examples/vite-app/src/App/index.tsx @@ -10,6 +10,7 @@ import DefaultNodes from '../examples/DefaultNodes'; import DragHandle from '../examples/DragHandle'; import DragNDrop from '../examples/DragNDrop'; import Edges from '../examples/Edges'; +import EdgeRenderer from '../examples/EdgeRenderer'; import EdgeTypes from '../examples/EdgeTypes'; import Empty from '../examples/Empty'; import FloatingEdges from '../examples/FloatingEdges'; @@ -95,6 +96,11 @@ const routes: IRoute[] = [ path: '/edges', component: Edges, }, + { + name: 'Edge Renderer', + path: '/edge-renderer', + component: EdgeRenderer, + }, { name: 'Edge Types', path: '/edge-types', diff --git a/examples/vite-app/src/examples/EdgeRenderer/CustomEdge.tsx b/examples/vite-app/src/examples/EdgeRenderer/CustomEdge.tsx new file mode 100644 index 00000000..f7a42270 --- /dev/null +++ b/examples/vite-app/src/examples/EdgeRenderer/CustomEdge.tsx @@ -0,0 +1,61 @@ +import { FC, MouseEvent } from 'react'; +import { EdgeProps, getBezierPath, EdgeLabelRenderer, useStore, ReactFlowStore } from 'reactflow'; + +const CustomEdge: FC = ({ + id, + source, + target, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + data, +}) => { + const isConnectedNodeDragging = useStore((s) => + Array.from(s.nodeInternals.values()).find((n) => n.dragging && (target === n.id || source === n.id)) + ); + + const [edgePath, labelX, labelY] = getBezierPath({ + sourceX, + sourceY, + sourcePosition, + targetX, + targetY, + targetPosition, + }); + + const onClick = (event: MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + + console.log('click', data.text); + }; + + return ( + <> + + + +
+ {data.text} + + +
+
+ + ); +}; + +export default CustomEdge; diff --git a/examples/vite-app/src/examples/EdgeRenderer/CustomEdge2.tsx b/examples/vite-app/src/examples/EdgeRenderer/CustomEdge2.tsx new file mode 100644 index 00000000..e0e8e794 --- /dev/null +++ b/examples/vite-app/src/examples/EdgeRenderer/CustomEdge2.tsx @@ -0,0 +1,51 @@ +import { FC } from 'react'; +import { EdgeProps, getBezierPath, EdgeLabelRenderer, useStore } from 'reactflow'; + +const CustomEdge: FC = ({ + id, + source, + target, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + data, +}) => { + const isConnectedNodeDragging = useStore((s) => + Array.from(s.nodeInternals.values()).find((n) => n.dragging && (target === n.id || source === n.id)) + ); + + const [edgePath, labelX, labelY] = getBezierPath({ + sourceX, + sourceY, + sourcePosition, + targetX, + targetY, + targetPosition, + }); + + return ( + <> + + + +
+ {data.text} +
+
+ + ); +}; + +export default CustomEdge; diff --git a/examples/vite-app/src/examples/EdgeRenderer/index.tsx b/examples/vite-app/src/examples/EdgeRenderer/index.tsx new file mode 100644 index 00000000..f4315360 --- /dev/null +++ b/examples/vite-app/src/examples/EdgeRenderer/index.tsx @@ -0,0 +1,206 @@ +import React, { MouseEvent, useCallback } from 'react'; +import ReactFlow, { + Controls, + Background, + MiniMap, + addEdge, + Connection, + Edge, + EdgeTypes, + MarkerType, + Node, + useEdgesState, + useNodesState, +} from 'reactflow'; + +import CustomEdge from './CustomEdge'; +import CustomEdge2 from './CustomEdge2'; + +const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); +const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); +const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge); +const onEdgeDoubleClick = (_: MouseEvent, edge: Edge) => console.log('dblclick', edge); +const onEdgeMouseEnter = (_: MouseEvent, edge: Edge) => console.log('enter', edge); +const onEdgeMouseMove = (_: MouseEvent, edge: Edge) => console.log('move', edge); +const onEdgeMouseLeave = (_: MouseEvent, edge: Edge) => console.log('leave', edge); + +const initialNodes: Node[] = [ + { + id: '1', + type: 'input', + data: { label: 'Input 1' }, + position: { x: 250, y: 0 }, + }, + { id: '2', data: { label: 'Node 2' }, position: { x: 150, y: 100 } }, + { id: '2a', data: { label: 'Node 2a' }, position: { x: 0, y: 180 } }, + { id: '2b', data: { label: 'Node 2b' }, position: { x: -40, y: 300 } }, + { id: '3', data: { label: 'Node 3' }, position: { x: 250, y: 200 } }, + { id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 300 } }, + { id: '3a', data: { label: 'Node 3a' }, position: { x: 150, y: 300 } }, + { id: '5', data: { label: 'Node 5' }, position: { x: 250, y: 400 } }, + { + id: '6', + type: 'output', + data: { label: 'Output 6' }, + position: { x: 50, y: 550 }, + }, + { + id: '7', + type: 'output', + data: { label: 'Output 7' }, + position: { x: 250, y: 550 }, + }, + { + id: '8', + type: 'output', + data: { label: 'Output 8' }, + position: { x: 525, y: 600 }, + }, + { + id: '9', + type: 'output', + data: { label: 'Output 9' }, + position: { x: 675, y: 500 }, + }, +]; + +const initialEdges: Edge[] = [ + { + id: 'e1-2', + source: '1', + target: '2', + label: 'bezier edge (default)', + className: 'normal-edge', + }, + { + id: 'e2-2a', + source: '2', + target: '2a', + type: 'smoothstep', + label: 'smoothstep edge', + }, + { + id: 'e2a-2b', + source: '2a', + target: '2b', + type: 'simplebezier', + label: 'simple bezier edge', + }, + { id: 'e2-3', source: '2', target: '3', type: 'step', label: 'step edge' }, + { + id: 'e3-4', + source: '3', + target: '4', + type: 'straight', + label: 'straight edge', + }, + { + id: 'e3-3a', + source: '3', + target: '3a', + type: 'straight', + label: 'label only edge', + style: { stroke: 'none' }, + }, + { + id: 'e3-5', + source: '4', + target: '5', + animated: true, + label: 'animated styled edge', + style: { stroke: 'red' }, + }, + { + id: 'e5-7', + source: '5', + target: '7', + label: 'label with styled bg', + labelBgPadding: [8, 4], + labelBgBorderRadius: 4, + labelBgStyle: { fill: '#FFCC00', color: '#fff', fillOpacity: 0.7 }, + markerEnd: { + type: MarkerType.ArrowClosed, + }, + }, + { + id: 'e5-8', + source: '5', + target: '8', + type: 'custom', + data: { text: 'custom edge' }, + }, + { + id: 'e5-9', + source: '5', + target: '9', + type: 'custom2', + data: { text: 'custom edge 2' }, + }, + { + id: 'e5-6', + source: '5', + target: '6', + label: ( + <> + i am using + + {''} + + + ), + labelStyle: { fill: 'red', fontWeight: 700 }, + style: { stroke: '#ffcc00' }, + markerEnd: { + type: MarkerType.Arrow, + color: '#FFCC00', + markerUnits: 'userSpaceOnUse', + width: 20, + height: 20, + strokeWidth: 2, + }, + markerStart: { + type: MarkerType.ArrowClosed, + color: '#FFCC00', + orient: 'auto-start-reverse', + markerUnits: 'userSpaceOnUse', + width: 20, + height: 20, + }, + }, +]; + +const edgeTypes: EdgeTypes = { + custom: CustomEdge, + custom2: CustomEdge2, +}; + +const EdgesFlow = () => { + const [nodes, , onNodesChange] = useNodesState(initialNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]); + + return ( + + + + + + ); +}; + +export default EdgesFlow; diff --git a/packages/core/package.json b/packages/core/package.json index ea025613..231578f0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -58,6 +58,7 @@ "@reactflow/tsconfig": "workspace:*", "@types/node": "^18.7.16", "@types/react": "^18.0.17", + "@types/react-dom": "^18.0.6", "react": "^18.2.0", "typescript": "^4.8.3" }, diff --git a/packages/core/src/components/EdgeLabelRenderer/index.tsx b/packages/core/src/components/EdgeLabelRenderer/index.tsx new file mode 100644 index 00000000..0647364a --- /dev/null +++ b/packages/core/src/components/EdgeLabelRenderer/index.tsx @@ -0,0 +1,15 @@ +import { useRef } from 'react'; +import type { ReactNode } from 'react'; +import { createPortal } from 'react-dom'; + +function EdgeLabelRenderer({ children }: { children: ReactNode }) { + const wrapperRef = useRef(document.getElementById('edgelabel-portal')); + + if (!wrapperRef.current) { + return null; + } + + return createPortal(children, wrapperRef.current); +} + +export default EdgeLabelRenderer; diff --git a/packages/core/src/container/GraphView/index.tsx b/packages/core/src/container/GraphView/index.tsx index 690b66f6..4190bed0 100644 --- a/packages/core/src/container/GraphView/index.tsx +++ b/packages/core/src/container/GraphView/index.tsx @@ -158,6 +158,8 @@ const GraphView = ({ disableKeyboardA11y={disableKeyboardA11y} rfId={rfId} /> +
+