diff --git a/example/.gitignore b/example/.gitignore index 703c7975..4d29575d 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -21,5 +21,3 @@ npm-debug.log* yarn-debug.log* yarn-error.log* - -src_oldapi \ No newline at end of file diff --git a/example/src_oldapi/Basic/index.tsx b/example/src_oldapi/Basic/index.tsx new file mode 100644 index 00000000..19523f9b --- /dev/null +++ b/example/src_oldapi/Basic/index.tsx @@ -0,0 +1,98 @@ +import React, { useState, MouseEvent } from 'react'; + +import ReactFlow, { + removeElements, + addEdge, + isNode, + Background, + Elements, + BackgroundVariant, + FlowElement, + Node, + Edge, + Connection, + OnLoadParams, +} from 'react-flow-renderer'; + +const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); +const onElementClick = (_: MouseEvent, element: FlowElement) => console.log('click', element); + +const initialNodes: Node[] = [ + { id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' }, + { id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, className: 'light' }, + { id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' }, + { id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' }, +]; + +const initialEdges: Edge[] = [ + { id: 'e1-2', source: '1', target: '2', animated: true }, + { id: 'e1-3', source: '1', target: '3' }, +]; + +const BasicFlow = () => { + const [rfInstance, setRfInstance] = useState(null); + const [nodes, setNodes] = useState(initialNodes); + const [edges, setEdges] = useState(initialEdges); + // const onElementsRemove = (elementsToRemove: Elements) => setNodes((els) => removeElements(elementsToRemove, els)); + // const onConnect = (params: Edge | Connection) => setNodes((els) => addEdge(params, els)); + const onLoad = (reactFlowInstance: OnLoadParams) => setRfInstance(reactFlowInstance); + + const updatePos = () => { + setNodes((nds) => { + return nds.map((n) => { + n.position = { + x: Math.random() * 400, + y: Math.random() * 400, + }; + + return n; + }); + }); + }; + + const logToObject = () => console.log(rfInstance?.toObject()); + const resetTransform = () => rfInstance?.setTransform({ x: 0, y: 0, zoom: 1 }); + + const toggleClassnames = () => { + setNodes((nds) => { + return nds.map((n) => { + n.className = n.className === 'light' ? 'dark' : 'light'; + + return n; + }); + }); + }; + + return ( + + + +
+ + + + +
+
+ ); +}; + +export default BasicFlow; diff --git a/example/src_oldapi/CustomConnectionLine/ConnectionLine.tsx b/example/src_oldapi/CustomConnectionLine/ConnectionLine.tsx new file mode 100644 index 00000000..f24fcc28 --- /dev/null +++ b/example/src_oldapi/CustomConnectionLine/ConnectionLine.tsx @@ -0,0 +1,19 @@ +import React, { FC } from 'react'; +import { ConnectionLineComponentProps } from 'react-flow-renderer'; + +const ConnectionLine: FC = ({ sourceX, sourceY, targetX, targetY }) => { + return ( + + + + + ); +}; + +export default ConnectionLine; diff --git a/example/src_oldapi/CustomConnectionLine/index.tsx b/example/src_oldapi/CustomConnectionLine/index.tsx new file mode 100644 index 00000000..48cbf561 --- /dev/null +++ b/example/src_oldapi/CustomConnectionLine/index.tsx @@ -0,0 +1,33 @@ +import React, { useState } from 'react'; +import ReactFlow, { + removeElements, + addEdge, + Background, + BackgroundVariant, + Elements, + Connection, + Edge, +} from 'react-flow-renderer'; + +import ConnectionLine from './ConnectionLine'; + +const initialElements: Elements = [{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } }]; + +const ConnectionLineFlow = () => { + const [elements, setElements] = useState(initialElements); + const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + + return ( + + + + ); +}; + +export default ConnectionLineFlow; diff --git a/example/src_oldapi/CustomNode/ColorSelectorNode.tsx b/example/src_oldapi/CustomNode/ColorSelectorNode.tsx new file mode 100644 index 00000000..7ad20340 --- /dev/null +++ b/example/src_oldapi/CustomNode/ColorSelectorNode.tsx @@ -0,0 +1,25 @@ +import React, { memo, FC, CSSProperties } from 'react'; + +import { Handle, Position, NodeProps, Connection, Edge } from 'react-flow-renderer'; + +const targetHandleStyle: CSSProperties = { background: '#555' }; +const sourceHandleStyleA: CSSProperties = { ...targetHandleStyle, top: 10 }; +const sourceHandleStyleB: CSSProperties = { ...targetHandleStyle, bottom: 10, top: 'auto' }; + +const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params); + +const ColorSelectorNode: FC = ({ data, isConnectable }) => { + return ( + <> + +
+ Custom Color Picker Node: {data.color} +
+ + + + + ); +}; + +export default memo(ColorSelectorNode); diff --git a/example/src_oldapi/CustomNode/index.tsx b/example/src_oldapi/CustomNode/index.tsx new file mode 100644 index 00000000..804aabea --- /dev/null +++ b/example/src_oldapi/CustomNode/index.tsx @@ -0,0 +1,135 @@ +import React, { useState, useEffect, MouseEvent } from 'react'; +import { ChangeEvent } from 'react'; + +import ReactFlow, { + isEdge, + removeElements, + addEdge, + MiniMap, + Controls, + Node, + FlowElement, + OnLoadParams, + Elements, + Position, + SnapGrid, + Connection, + Edge, +} from 'react-flow-renderer'; + +import ColorSelectorNode from './ColorSelectorNode'; + +const onLoad = (reactFlowInstance: OnLoadParams) => console.log('flow loaded:', reactFlowInstance); +const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); +const onElementClick = (_: MouseEvent, element: FlowElement) => console.log('click', element); + +const initBgColor = '#1A192B'; + +const connectionLineStyle = { stroke: '#fff' }; +const snapGrid: SnapGrid = [16, 16]; +const nodeTypes = { + selectorNode: ColorSelectorNode, +}; + +const CustomNodeFlow = () => { + const [elements, setElements] = useState([]); + const [bgColor, setBgColor] = useState(initBgColor); + + useEffect(() => { + const onChange = (event: ChangeEvent) => { + setElements((els) => + els.map((e) => { + if (isEdge(e) || e.id !== '2') { + return e; + } + + const color = event.target.value; + + setBgColor(color); + + return { + ...e, + data: { + ...e.data, + color, + }, + }; + }) + ); + }; + + setElements([ + { + id: '1', + type: 'input', + data: { label: 'An input node' }, + position: { x: 0, y: 50 }, + sourcePosition: Position.Right, + }, + { + id: '2', + type: 'selectorNode', + data: { onChange: onChange, color: initBgColor }, + style: { border: '1px solid #777', padding: 10 }, + position: { x: 250, y: 50 }, + }, + { + id: '3', + type: 'output', + data: { label: 'Output A' }, + position: { x: 550, y: 25 }, + targetPosition: Position.Left, + }, + { + id: '4', + type: 'output', + data: { label: 'Output B' }, + position: { x: 550, y: 100 }, + targetPosition: Position.Left, + }, + + { id: 'e1-2', source: '1', target: '2', animated: true, style: { stroke: '#fff' } }, + { id: 'e2a-3', source: '2', sourceHandle: 'a', target: '3', animated: true, style: { stroke: '#fff' } }, + { id: 'e2b-4', source: '2', sourceHandle: 'b', target: '4', animated: true, style: { stroke: '#fff' } }, + ]); + }, []); + + const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); + const onConnect = (params: Connection | Edge) => + setElements((els) => addEdge({ ...params, animated: true, style: { stroke: '#fff' } }, els)); + + return ( + + { + if (n.type === 'input') return '#0041d0'; + if (n.type === 'selectorNode') return bgColor; + if (n.type === 'output') return '#ff0072'; + + return '#eee'; + }} + nodeColor={(n: Node): string => { + if (n.type === 'selectorNode') return bgColor; + + return '#fff'; + }} + /> + + + ); +}; + +export default CustomNodeFlow; diff --git a/example/src_oldapi/DragHandle/DragHandleNode.tsx b/example/src_oldapi/DragHandle/DragHandleNode.tsx new file mode 100644 index 00000000..377e45de --- /dev/null +++ b/example/src_oldapi/DragHandle/DragHandleNode.tsx @@ -0,0 +1,33 @@ +import { memo, FC } from 'react'; + +import { Handle, Position, NodeProps, Connection, Edge } from 'react-flow-renderer'; + +const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params); + +const labelStyle = { + display: 'flex', + alignItems: 'center', +}; + +const dragHandleStyle = { + display: 'inline-block', + width: 25, + height: 25, + backgroundColor: 'teal', + marginLeft: 5, + borderRadius: '50%', +}; + +const ColorSelectorNode: FC = () => { + return ( + <> + +
+ Only draggable here → +
+ + + ); +}; + +export default memo(ColorSelectorNode); diff --git a/example/src_oldapi/DragHandle/index.tsx b/example/src_oldapi/DragHandle/index.tsx new file mode 100644 index 00000000..b5620243 --- /dev/null +++ b/example/src_oldapi/DragHandle/index.tsx @@ -0,0 +1,21 @@ +import ReactFlow from 'react-flow-renderer'; + +import DragHandleNode from './DragHandleNode'; + +const nodeTypes = { + dragHandleNode: DragHandleNode, +}; + +const elements = [ + { + id: '2', + type: 'dragHandleNode', + dragHandle: '.custom-drag-handle', + style: { border: '1px solid #ddd', padding: '20px 40px' }, + position: { x: 200, y: 200 }, + }, +]; + +const DragHandleFlow = () => ; + +export default DragHandleFlow; diff --git a/example/src_oldapi/DragNDrop/Sidebar.tsx b/example/src_oldapi/DragNDrop/Sidebar.tsx new file mode 100644 index 00000000..7c1fba58 --- /dev/null +++ b/example/src_oldapi/DragNDrop/Sidebar.tsx @@ -0,0 +1,25 @@ +import React, { DragEvent } from 'react'; + +const onDragStart = (event: DragEvent, nodeType: string) => { + event.dataTransfer.setData('application/reactflow', nodeType); + event.dataTransfer.effectAllowed = 'move'; +}; + +const Sidebar = () => { + return ( + + ); +}; + +export default Sidebar; diff --git a/example/src_oldapi/DragNDrop/dnd.css b/example/src_oldapi/DragNDrop/dnd.css new file mode 100644 index 00000000..26d490c8 --- /dev/null +++ b/example/src_oldapi/DragNDrop/dnd.css @@ -0,0 +1,37 @@ +.dndflow { + flex-direction: column; + display: flex; + height: 100%; +} + +.dndflow aside { + border-right: 1px solid #eee; + padding: 15px 10px; + font-size: 12px; + background: #fcfcfc; +} + +.dndflow aside > * { + margin-bottom: 10px; + cursor: grab; +} + +.dndflow aside .description { + margin-bottom: 10px; +} + +.dndflow .reactflow-wrapper { + flex-grow: 1; + height: 100%; +} + +@media screen and (min-width: 768px) { + .dndflow { + flex-direction: row; + } + + .dndflow aside { + width: 20%; + max-width: 180px; + } +} diff --git a/example/src_oldapi/DragNDrop/index.tsx b/example/src_oldapi/DragNDrop/index.tsx new file mode 100644 index 00000000..0c238fb6 --- /dev/null +++ b/example/src_oldapi/DragNDrop/index.tsx @@ -0,0 +1,75 @@ +import React, { useState, DragEvent } from 'react'; +import ReactFlow, { + ReactFlowProvider, + addEdge, + removeElements, + Controls, + OnLoadParams, + Elements, + Connection, + Edge, + ElementId, + Node, +} from 'react-flow-renderer'; + +import Sidebar from './Sidebar'; + +import './dnd.css'; + +const initialElements = [{ id: '1', type: 'input', data: { label: 'input node' }, position: { x: 250, y: 5 } }]; + +const onDragOver = (event: DragEvent) => { + event.preventDefault(); + event.dataTransfer.dropEffect = 'move'; +}; + +let id = 0; +const getId = (): ElementId => `dndnode_${id++}`; + +const DnDFlow = () => { + const [reactFlowInstance, setReactFlowInstance] = useState(); + const [elements, setElements] = useState(initialElements); + + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); + const onLoad = (_reactFlowInstance: OnLoadParams) => setReactFlowInstance(_reactFlowInstance); + + const onDrop = (event: DragEvent) => { + event.preventDefault(); + + if (reactFlowInstance) { + const type = event.dataTransfer.getData('application/reactflow'); + const position = reactFlowInstance.project({ x: event.clientX, y: event.clientY - 40 }); + const newNode: Node = { + id: getId(), + type, + position, + data: { label: `${type} node` }, + }; + + setElements((es) => es.concat(newNode)); + } + }; + + return ( +
+ +
+ + + +
+ +
+
+ ); +}; + +export default DnDFlow; diff --git a/example/src_oldapi/EdgeTypes/index.tsx b/example/src_oldapi/EdgeTypes/index.tsx new file mode 100644 index 00000000..7c87fe7e --- /dev/null +++ b/example/src_oldapi/EdgeTypes/index.tsx @@ -0,0 +1,54 @@ +/** + * Example for checking the different edge types and source and target positions + */ +import React, { useState } from 'react'; + +import ReactFlow, { + removeElements, + addEdge, + MiniMap, + Controls, + Background, + OnLoadParams, + Connection, + Edge, + Elements, +} from 'react-flow-renderer'; +import { getElements } from './utils'; + +const onLoad = (reactFlowInstance: OnLoadParams) => { + reactFlowInstance.fitView(); + console.log(reactFlowInstance.getElements()); +}; + +const initialElements = getElements(); + +const multiSelectionKeyCode = ['ShiftLeft', 'ShiftRight']; +const deleteKeyCode = ['AltLeft+KeyD', 'Backspace']; + +const EdgeTypesFlow = () => { + const [elements, setElements] = useState(initialElements); + const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + + return ( + + + + + + ); +}; + +export default EdgeTypesFlow; diff --git a/example/src_oldapi/EdgeTypes/utils.ts b/example/src_oldapi/EdgeTypes/utils.ts new file mode 100644 index 00000000..727501a7 --- /dev/null +++ b/example/src_oldapi/EdgeTypes/utils.ts @@ -0,0 +1,106 @@ +import { ElementId, Elements, Position } from 'react-flow-renderer'; + +const nodeWidth = 80; +const nodeGapWidth = nodeWidth * 2; +const nodeStyle = { width: nodeWidth, fontSize: 11, color: 'white' }; + +const sourceTargetPositions = [ + { source: Position.Bottom, target: Position.Top }, + { source: Position.Right, target: Position.Left }, +]; +const nodeColors = [ + ['#1e9e99', '#4cb3ac', '#6ec9c0', '#8ddfd4'], + ['#0f4c75', '#1b5d8b', '#276fa1', '#3282b8'], +]; +const edgeTypes = ['default', 'step', 'smoothstep', 'straight']; +const offsets = [ + { + x: 0, + y: -nodeGapWidth, + }, + { + x: nodeGapWidth, + y: -nodeGapWidth, + }, + { + x: nodeGapWidth, + y: 0, + }, + { + x: nodeGapWidth, + y: nodeGapWidth, + }, + { + x: 0, + y: nodeGapWidth, + }, + { + x: -nodeGapWidth, + y: nodeGapWidth, + }, + { + x: -nodeGapWidth, + y: 0, + }, + { + x: -nodeGapWidth, + y: -nodeGapWidth, + }, +]; + +let id = 0; +const getNodeId = (): ElementId => (id++).toString(); + +export function getElements(): Elements { + const initialElements = []; + + for (let sourceTargetIndex = 0; sourceTargetIndex < sourceTargetPositions.length; sourceTargetIndex++) { + const currSourceTargetPos = sourceTargetPositions[sourceTargetIndex]; + + for (let edgeTypeIndex = 0; edgeTypeIndex < edgeTypes.length; edgeTypeIndex++) { + const currEdgeType = edgeTypes[edgeTypeIndex]; + + for (let offsetIndex = 0; offsetIndex < offsets.length; offsetIndex++) { + const currOffset = offsets[offsetIndex]; + + const style = { ...nodeStyle, background: nodeColors[sourceTargetIndex][edgeTypeIndex] }; + const sourcePosition = { + x: offsetIndex * nodeWidth * 4, + y: edgeTypeIndex * 300 + sourceTargetIndex * edgeTypes.length * 300, + }; + const sourceId = getNodeId(); + const sourceData = { label: `Source ${sourceId}` }; + const sourceNode = { + id: sourceId, + style, + data: sourceData, + position: sourcePosition, + sourcePosition: currSourceTargetPos.source, + targetPosition: currSourceTargetPos.target, + }; + + const targetId = getNodeId(); + const targetData = { label: `Target ${targetId}` }; + const targetPosition = { + x: sourcePosition.x + currOffset.x, + y: sourcePosition.y + currOffset.y, + }; + const targetNode = { + id: targetId, + style, + data: targetData, + position: targetPosition, + sourcePosition: currSourceTargetPos.source, + targetPosition: currSourceTargetPos.target, + }; + + initialElements.push(sourceNode); + initialElements.push(targetNode); + + initialElements.push({ id: `${sourceId}-${targetId}`, source: sourceId, target: targetId, type: currEdgeType }); + } + } + } + + return initialElements; +} diff --git a/example/src_oldapi/Edges/CustomEdge.tsx b/example/src_oldapi/Edges/CustomEdge.tsx new file mode 100644 index 00000000..f72ff831 --- /dev/null +++ b/example/src_oldapi/Edges/CustomEdge.tsx @@ -0,0 +1,31 @@ +import { FC } from 'react'; +import { EdgeProps, getBezierPath, getMarkerEnd } from 'react-flow-renderer'; + +const CustomEdge: FC = ({ + id, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + data, + arrowHeadType, + markerEndId, +}) => { + const edgePath = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition }); + const markerEnd = getMarkerEnd(arrowHeadType, markerEndId); + + return ( + <> + + + + {data.text} + + + + ); +}; + +export default CustomEdge; diff --git a/example/src_oldapi/Edges/CustomEdge2.tsx b/example/src_oldapi/Edges/CustomEdge2.tsx new file mode 100644 index 00000000..361b7f92 --- /dev/null +++ b/example/src_oldapi/Edges/CustomEdge2.tsx @@ -0,0 +1,44 @@ +import { FC } from 'react'; +import { EdgeProps, getBezierPath, getMarkerEnd, EdgeText, getEdgeCenter } from 'react-flow-renderer'; + +const CustomEdge: FC = ({ + id, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + data, + arrowHeadType, + markerEndId, +}) => { + const edgePath = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition }); + const markerEnd = getMarkerEnd(arrowHeadType, markerEndId); + const [centerX, centerY] = getEdgeCenter({ + sourceX, + sourceY, + targetX, + targetY, + }); + + return ( + <> + + console.log(data)} + /> + ; + + ); +}; + +export default CustomEdge; diff --git a/example/src_oldapi/Edges/index.tsx b/example/src_oldapi/Edges/index.tsx new file mode 100644 index 00000000..39320c6d --- /dev/null +++ b/example/src_oldapi/Edges/index.tsx @@ -0,0 +1,115 @@ +import React, { useState, MouseEvent } from 'react'; + +import ReactFlow, { + removeElements, + addEdge, + MiniMap, + Controls, + Background, + OnLoadParams, + FlowElement, + EdgeTypesType, + Elements, + Connection, + Edge, + ArrowHeadType, + Node, +} from 'react-flow-renderer'; + +import CustomEdge from './CustomEdge'; +import CustomEdge2 from './CustomEdge2'; + +const onLoad = (reactFlowInstance: OnLoadParams) => reactFlowInstance.fitView(); +const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); +const onElementClick = (_: MouseEvent, element: FlowElement) => console.log('click', element); + +const initialElements: Elements = [ + { 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: '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 } }, + { 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: '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-6', + source: '5', + target: '6', + label: ( + <> + i am using + + {''} + + + ), + labelStyle: { fill: 'red', fontWeight: 700 }, + arrowHeadType: ArrowHeadType.Arrow, + }, + { + 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 }, + arrowHeadType: ArrowHeadType.ArrowClosed, + }, + { + id: 'e5-8', + source: '5', + target: '8', + type: 'custom', + data: { text: 'custom edge' }, + arrowHeadType: ArrowHeadType.ArrowClosed, + }, + { + id: 'e5-9', + source: '5', + target: '9', + type: 'custom2', + data: { text: 'custom edge 2' }, + }, +]; + +const edgeTypes: EdgeTypesType = { + custom: CustomEdge, + custom2: CustomEdge2, +}; + +const EdgesFlow = () => { + const [elements, setElements] = useState(initialElements); + + const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + + return ( + + + + + + ); +}; + +export default EdgesFlow; diff --git a/example/src_oldapi/Empty/index.tsx b/example/src_oldapi/Empty/index.tsx new file mode 100644 index 00000000..9bfc7c25 --- /dev/null +++ b/example/src_oldapi/Empty/index.tsx @@ -0,0 +1,60 @@ +import React, { useState, MouseEvent, CSSProperties } from 'react'; + +import ReactFlow, { + removeElements, + addEdge, + MiniMap, + Controls, + Background, + OnLoadParams, + Elements, + ElementId, + Node, + FlowElement, + BackgroundVariant, + Connection, + Edge, +} from 'react-flow-renderer'; + +const onLoad = (reactFlowInstance: OnLoadParams) => console.log('flow loaded:', reactFlowInstance); +const onElementClick = (_: MouseEvent, element: FlowElement) => console.log('click', element); +const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); + +const buttonStyle: CSSProperties = { position: 'absolute', left: 10, top: 10, zIndex: 4 }; + +const EmptyFlow = () => { + const [elements, setElements] = useState([]); + const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + const addRandomNode = () => { + const nodeId: ElementId = (elements.length + 1).toString(); + const newNode: Node = { + id: nodeId, + data: { label: `Node: ${nodeId}` }, + position: { x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight }, + }; + setElements((els) => els.concat(newNode)); + }; + + return ( + onConnect(p)} + onNodeDragStop={onNodeDragStop} + onlyRenderVisibleElements={false} + > + + + + + + + ); +}; + +export default EmptyFlow; diff --git a/example/src_oldapi/Hidden/index.tsx b/example/src_oldapi/Hidden/index.tsx new file mode 100644 index 00000000..9869b2bc --- /dev/null +++ b/example/src_oldapi/Hidden/index.tsx @@ -0,0 +1,53 @@ +import React, { useState } from 'react'; + +import { useEffect } from 'react'; +import ReactFlow, { addEdge, MiniMap, Controls, Connection, Edge, Elements } from 'react-flow-renderer'; + +const initialElements: Elements = [ + { id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } }, + { id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } }, + { id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } }, + { id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } }, + { id: 'e1-2', source: '1', target: '2' }, + { id: 'e1-3', source: '1', target: '3' }, + { id: 'e3-4', source: '3', target: '4' }, +]; + +const HiddenFlow = () => { + const [elements, setElements] = useState(initialElements); + const [isHidden, setIsHidden] = useState(false); + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + + useEffect(() => { + setElements((els) => + els.map((e) => { + e.isHidden = isHidden; + return e; + }) + ); + }, [isHidden]); + + return ( + + + + +
+
+ +
+
+
+ ); +}; + +export default HiddenFlow; diff --git a/example/src_oldapi/Interaction/index.tsx b/example/src_oldapi/Interaction/index.tsx new file mode 100644 index 00000000..2ca7900c --- /dev/null +++ b/example/src_oldapi/Interaction/index.tsx @@ -0,0 +1,226 @@ +import React, { useState, MouseEvent, WheelEvent } from 'react'; +import ReactFlow, { + addEdge, + MiniMap, + Controls, + Elements, + Node, + FlowElement, + Connection, + Edge, + PanOnScrollMode, + FlowTransform, +} from 'react-flow-renderer'; + +const initialElements: Elements = [ + { id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } }, + { id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } }, + { id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } }, + { id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } }, + { id: 'e1-2', source: '1', target: '2', animated: true }, + { id: 'e1-3', source: '1', target: '3' }, +]; + +const onNodeDragStart = (_: MouseEvent, node: Node) => console.log('drag start', node); +const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); +const onElementClick = (_: MouseEvent, element: FlowElement) => console.log('click', element); +const onPaneClick = (event: MouseEvent) => console.log('onPaneClick', event); +const onPaneScroll = (event?: WheelEvent) => console.log('onPaneScroll', event); +const onPaneContextMenu = (event: MouseEvent) => console.log('onPaneContextMenu', event); +const onMoveEnd = (flowTranasform?: FlowTransform) => console.log('onMoveEnd', flowTranasform); + +const InteractionFlow = () => { + const [elements, setElements] = useState(initialElements); + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + + const [isSelectable, setIsSelectable] = useState(false); + const [isDraggable, setIsDraggable] = useState(false); + const [isConnectable, setIsConnectable] = useState(false); + const [zoomOnScroll, setZoomOnScroll] = useState(false); + const [zoomOnPinch, setZoomOnPinch] = useState(false); + const [panOnScroll, setPanOnScroll] = useState(false); + const [panOnScrollMode, setPanOnScrollMode] = useState(PanOnScrollMode.Free); + const [zoomOnDoubleClick, setZoomOnDoubleClick] = useState(false); + const [paneMoveable, setPaneMoveable] = useState(true); + const [captureZoomClick, setCaptureZoomClick] = useState(false); + const [captureZoomScroll, setCaptureZoomScroll] = useState(false); + const [captureElementClick, setCaptureElementClick] = useState(false); + + return ( + + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ); +}; + +export default InteractionFlow; diff --git a/example/src_oldapi/Layouting/index.tsx b/example/src_oldapi/Layouting/index.tsx new file mode 100644 index 00000000..34ee23e6 --- /dev/null +++ b/example/src_oldapi/Layouting/index.tsx @@ -0,0 +1,86 @@ +import React, { useState } from 'react'; +import ReactFlow, { + ReactFlowProvider, + addEdge, + removeElements, + Controls, + isNode, + Elements, + Connection, + Edge, + NodeExtent, + Position, +} from 'react-flow-renderer'; +import dagre from 'dagre'; + +import initialElements from './initial-elements'; + +import './layouting.css'; + +const dagreGraph = new dagre.graphlib.Graph(); +dagreGraph.setDefaultEdgeLabel(() => ({})); + +const nodeExtent: NodeExtent = [ + [0, 0], + [1000, 1000], +]; + +const LayoutFlow = () => { + const [elements, setElements] = useState(initialElements); + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); + + const onLayout = (direction: string) => { + const isHorizontal = direction === 'LR'; + dagreGraph.setGraph({ rankdir: direction }); + + elements.forEach((el) => { + if (isNode(el)) { + dagreGraph.setNode(el.id, { width: 150, height: 50 }); + } else { + dagreGraph.setEdge(el.source, el.target); + } + }); + + dagre.layout(dagreGraph); + + const layoutedElements = elements.map((el) => { + if (isNode(el)) { + const nodeWithPosition = dagreGraph.node(el.id); + el.targetPosition = isHorizontal ? Position.Left : Position.Top; + el.sourcePosition = isHorizontal ? Position.Right : Position.Bottom; + // we need to pass a slightly different position in order to notify react flow about the change + // @TODO how can we change the position handling so that we dont need this hack? + el.position = { x: nodeWithPosition.x + Math.random() / 1000, y: nodeWithPosition.y }; + } + + return el; + }); + + setElements(layoutedElements); + }; + + return ( +
+ + onLayout('TB')} + > + + +
+ + +
+
+
+ ); +}; + +export default LayoutFlow; diff --git a/example/src_oldapi/Layouting/initial-elements.ts b/example/src_oldapi/Layouting/initial-elements.ts new file mode 100644 index 00000000..404b1b8b --- /dev/null +++ b/example/src_oldapi/Layouting/initial-elements.ts @@ -0,0 +1,71 @@ +import { Elements, XYPosition } from 'react-flow-renderer'; + +const position: XYPosition = { x: 0, y: 0 }; + +const elements: Elements = [ + { + id: '1', + type: 'input', + data: { label: 'input' }, + position, + }, + { + id: '2', + data: { label: 'node 2' }, + position, + }, + { + id: '2a', + data: { label: 'node 2a' }, + position, + }, + { + id: '2b', + data: { label: 'node 2b' }, + position, + }, + { + id: '2c', + data: { label: 'node 2c' }, + position, + }, + { + id: '2d', + data: { label: 'node 2d' }, + position, + }, + { + id: '3', + data: { label: 'node 3' }, + position, + }, + { + id: '4', + data: { label: 'node 4' }, + position, + }, + { + id: '5', + data: { label: 'node 5' }, + position, + }, + { + id: '6', + type: 'output', + data: { label: 'output' }, + position, + }, + { id: '7', type: 'output', data: { label: 'output' }, position: { x: 400, y: 450 } }, + { id: 'e12', source: '1', target: '2', type: 'smoothstep' }, + { id: 'e13', source: '1', target: '3', type: 'smoothstep' }, + { id: 'e22a', source: '2', target: '2a', type: 'smoothstep' }, + { id: 'e22b', source: '2', target: '2b', type: 'smoothstep' }, + { id: 'e22c', source: '2', target: '2c', type: 'smoothstep' }, + { id: 'e2c2d', source: '2c', target: '2d', type: 'smoothstep' }, + + { id: 'e45', source: '4', target: '5', type: 'smoothstep' }, + { id: 'e56', source: '5', target: '6', type: 'smoothstep' }, + { id: 'e57', source: '5', target: '7', type: 'smoothstep' }, +]; + +export default elements; diff --git a/example/src_oldapi/Layouting/layouting.css b/example/src_oldapi/Layouting/layouting.css new file mode 100644 index 00000000..6d248b22 --- /dev/null +++ b/example/src_oldapi/Layouting/layouting.css @@ -0,0 +1,11 @@ +.layoutflow { + flex-grow: 1; + position: relative; +} + +.layoutflow .controls { + position: absolute; + right: 10px; + top: 10px; + z-index: 10; +} diff --git a/example/src_oldapi/MultiFlows/index.tsx b/example/src_oldapi/MultiFlows/index.tsx new file mode 100644 index 00000000..8e185992 --- /dev/null +++ b/example/src_oldapi/MultiFlows/index.tsx @@ -0,0 +1,45 @@ +import React, { useState, FC } from 'react'; + +import ReactFlow, { + removeElements, + addEdge, + Background, + Elements, + Edge, + Connection, + ReactFlowProvider, +} from 'react-flow-renderer'; + +import './multiflows.css'; + +const initialElements: Elements = [ + { id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' }, + { id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, className: 'light' }, + { id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' }, + { id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' }, + { id: 'e1-2', source: '1', target: '2', animated: true }, + { id: 'e1-3', source: '1', target: '3' }, +]; + +const Flow: FC = () => { + const [elements, setElements] = useState(initialElements); + const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); + const onConnect = (params: Edge | Connection) => setElements((els) => addEdge(params, els)); + + return ( + + + + + + ); +}; + +const MultiFlows: FC = () => ( +
+ + +
+); + +export default MultiFlows; diff --git a/example/src_oldapi/MultiFlows/multiflows.css b/example/src_oldapi/MultiFlows/multiflows.css new file mode 100644 index 00000000..b7017390 --- /dev/null +++ b/example/src_oldapi/MultiFlows/multiflows.css @@ -0,0 +1,13 @@ +.react-flow__example-multiflows { + display: flex; + height: 100%; +} + +.react-flow__example-multiflows .react-flow { + width: 100%; + height: 100%; +} + +.react-flow__example-multiflows .react-flow:first-child { + border-right: 2px solid #333; +} diff --git a/example/src_oldapi/NodeTypeChange/index.tsx b/example/src_oldapi/NodeTypeChange/index.tsx new file mode 100644 index 00000000..c5e4ff90 --- /dev/null +++ b/example/src_oldapi/NodeTypeChange/index.tsx @@ -0,0 +1,55 @@ +import React, { useState, CSSProperties } from 'react'; + +import ReactFlow, { addEdge, isEdge, OnLoadParams, Elements, Position, Connection, Edge } from 'react-flow-renderer'; + +const onLoad = (reactFlowInstance: OnLoadParams) => reactFlowInstance.fitView(); + +const initialElements: Elements = [ + { + id: '1', + sourcePosition: Position.Right, + type: 'input', + data: { label: 'Input' }, + position: { x: 0, y: 80 }, + }, + { + id: '2', + type: 'output', + sourcePosition: Position.Right, + targetPosition: Position.Left, + data: { label: 'A Node' }, + position: { x: 250, y: 0 }, + }, + { id: 'e1-2', source: '1', type: 'smoothstep', target: '2', animated: true }, +]; + +const buttonStyle: CSSProperties = { position: 'absolute', right: 10, top: 30, zIndex: 4 }; + +const NodeTypeChangeFlow = () => { + const [elements, setElements] = useState(initialElements); + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + const changeType = () => { + setElements((elms) => + elms.map((el) => { + if (isEdge(el) || el.type === 'input') { + return el; + } + + return { + ...el, + type: el.type === 'default' ? 'output' : 'default', + }; + }) + ); + }; + + return ( + + + + ); +}; + +export default NodeTypeChangeFlow; diff --git a/example/src_oldapi/NodeTypesObjectChange/index.tsx b/example/src_oldapi/NodeTypesObjectChange/index.tsx new file mode 100644 index 00000000..54b34fcc --- /dev/null +++ b/example/src_oldapi/NodeTypesObjectChange/index.tsx @@ -0,0 +1,76 @@ +import React, { useState, CSSProperties, FC } from 'react'; + +import ReactFlow, { + addEdge, + Elements, + Position, + Connection, + Edge, + NodeProps, + NodeTypesType, +} from 'react-flow-renderer'; + +const initialElements: Elements = [ + { + id: '1', + sourcePosition: Position.Right, + type: 'input', + data: { label: 'Input' }, + position: { x: 0, y: 80 }, + }, + { + id: '2', + type: 'a', + sourcePosition: Position.Right, + targetPosition: Position.Left, + data: { label: 'A Node' }, + position: { x: 250, y: 0 }, + }, +]; + +const buttonStyle: CSSProperties = { position: 'absolute', right: 10, top: 30, zIndex: 4 }; + +const nodeStyles: CSSProperties = { padding: '10px 15px', border: '1px solid #ddd' }; + +const NodeA: FC = () => { + return
A
; +}; + +const NodeB: FC = () => { + return
B
; +}; + +type NodeTypesObject = { + [key: string]: NodeTypesType; +}; + +const nodeTypesObjects: NodeTypesObject = { + a: { + a: NodeA, + }, + b: { + b: NodeB, + }, +}; + +const NodeTypeChangeFlow = () => { + const [nodeTypesId, setNodeTypesId] = useState('a'); + const [elements, setElements] = useState(initialElements); + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + const changeType = () => setNodeTypesId((nt) => (nt === 'a' ? 'b' : 'a')); + + return ( + + + + ); +}; + +export default NodeTypeChangeFlow; diff --git a/example/src_oldapi/Overview/index.tsx b/example/src_oldapi/Overview/index.tsx new file mode 100644 index 00000000..ee13f916 --- /dev/null +++ b/example/src_oldapi/Overview/index.tsx @@ -0,0 +1,202 @@ +import React, { useState, MouseEvent, CSSProperties } from 'react'; + +import ReactFlow, { + removeElements, + addEdge, + MiniMap, + Controls, + Background, + isNode, + Node, + Elements, + FlowElement, + OnLoadParams, + FlowTransform, + SnapGrid, + ArrowHeadType, + Connection, + Edge, +} from 'react-flow-renderer'; + +const onNodeDragStart = (_: MouseEvent, node: Node) => console.log('drag start', node); +const onNodeDrag = (_: MouseEvent, node: Node) => console.log('drag', node); +const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); +const onNodeDoubleClick = (_: MouseEvent, node: Node) => console.log('node double click', node); +const onPaneClick = (event: MouseEvent) => console.log('pane click', event); +const onPaneScroll = (event?: MouseEvent) => console.log('pane scroll', event); +const onPaneContextMenu = (event: MouseEvent) => console.log('pane context menu', event); +const onSelectionDrag = (_: MouseEvent, nodes: Node[]) => console.log('selection drag', nodes); +const onSelectionDragStart = (_: MouseEvent, nodes: Node[]) => console.log('selection drag start', nodes); +const onSelectionDragStop = (_: MouseEvent, nodes: Node[]) => console.log('selection drag stop', nodes); +const onSelectionContextMenu = (event: MouseEvent, nodes: Node[]) => { + event.preventDefault(); + console.log('selection context menu', nodes); +}; +const onElementClick = (_: MouseEvent, element: FlowElement) => + console.log(`${isNode(element) ? 'node' : 'edge'} click:`, element); +const onSelectionChange = (elements: Elements | null) => console.log('selection change', elements); +const onLoad = (reactFlowInstance: OnLoadParams) => { + console.log('flow loaded:', reactFlowInstance); + reactFlowInstance.fitView(); +}; + +const onMoveStart = (transform?: FlowTransform) => console.log('zoom/move start', transform); +const onMoveEnd = (transform?: FlowTransform) => console.log('zoom/move end', transform); +const onEdgeContextMenu = (_: MouseEvent, edge: Edge) => console.log('edge context menu', edge); +const onEdgeMouseEnter = (_: MouseEvent, edge: Edge) => console.log('edge mouse enter', edge); +const onEdgeMouseMove = (_: MouseEvent, edge: Edge) => console.log('edge mouse move', edge); +const onEdgeMouseLeave = (_: MouseEvent, edge: Edge) => console.log('edge mouse leave', edge); +const onEdgeDoubleClick = (_: MouseEvent, edge: Edge) => console.log('edge double click', edge); + +const initialElements: Elements = [ + { + id: '1', + type: 'input', + data: { + label: ( + <> + Welcome to React Flow! + + ), + }, + position: { x: 250, y: 0 }, + }, + { + id: '2', + data: { + label: ( + <> + This is a default node + + ), + }, + position: { x: 100, y: 100 }, + }, + { + id: '3', + data: { + label: ( + <> + This one has a custom style + + ), + }, + position: { x: 400, y: 100 }, + style: { background: '#D6D5E6', color: '#333', border: '1px solid #222138', width: 180 }, + }, + { + id: '4', + position: { x: 250, y: 200 }, + data: { + label: ( + <> + You can find the docs on{' '} + + Github + + + ), + }, + }, + { + id: '5', + data: { + label: ( + <> + Or check out the other examples + + ), + }, + position: { x: 250, y: 325 }, + }, + { + id: '6', + type: 'output', + data: { + label: ( + <> + An output node + + ), + }, + position: { x: 100, y: 480 }, + }, + { id: '7', type: 'output', data: { label: 'Another output node' }, position: { x: 400, y: 450 } }, + { id: 'e1-2', source: '1', target: '2', label: 'this is an edge label' }, + { id: 'e1-3', source: '1', target: '3' }, + { id: 'e3-4', source: '3', target: '4', animated: true, label: 'animated edge' }, + { id: 'e4-5', source: '4', target: '5', arrowHeadType: ArrowHeadType.Arrow, label: 'edge with arrow head' }, + { id: 'e5-6', source: '5', target: '6', type: 'smoothstep', label: 'smooth step edge' }, + { + id: 'e5-7', + source: '5', + target: '7', + type: 'step', + style: { stroke: '#f6ab6c' }, + label: 'a step edge', + animated: true, + labelStyle: { fill: '#f6ab6c', fontWeight: 700 }, + }, +]; + +const connectionLineStyle: CSSProperties = { stroke: '#ddd' }; +const snapGrid: SnapGrid = [16, 16]; + +const nodeStrokeColor = (n: Node): string => { + if (n.style?.background) return n.style.background as string; + if (n.type === 'input') return '#0041d0'; + if (n.type === 'output') return '#ff0072'; + if (n.type === 'default') return '#1a192b'; + + return '#eee'; +}; + +const nodeColor = (n: Node): string => { + if (n.style?.background) return n.style.background as string; + + return '#fff'; +}; + +const OverviewFlow = () => { + const [elements, setElements] = useState(initialElements); + const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + + return ( + + + + + + ); +}; + +export default OverviewFlow; diff --git a/example/src_oldapi/Provider/Sidebar.tsx b/example/src_oldapi/Provider/Sidebar.tsx new file mode 100644 index 00000000..0b2c5f24 --- /dev/null +++ b/example/src_oldapi/Provider/Sidebar.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { useStoreState, useStoreActions } from 'react-flow-renderer'; + +const Sidebar = () => { + const nodes = useStoreState((store) => store.nodes); + const transform = useStoreState((store) => store.transform); + const setSelectedElements = useStoreActions((actions) => actions.setSelectedElements); + + const selectAll = () => { + setSelectedElements(nodes.map((node) => ({ id: node.id, type: node.type }))); + }; + + return ( + + ); +}; + +export default Sidebar; diff --git a/example/src_oldapi/Provider/index.tsx b/example/src_oldapi/Provider/index.tsx new file mode 100644 index 00000000..a7a78b7c --- /dev/null +++ b/example/src_oldapi/Provider/index.tsx @@ -0,0 +1,57 @@ +import React, { useState, MouseEvent } from 'react'; +import ReactFlow, { + ReactFlowProvider, + addEdge, + removeElements, + Controls, + OnLoadParams, + FlowElement, + Connection, + Edge, + Elements, + ConnectionMode, +} from 'react-flow-renderer'; + +import Sidebar from './Sidebar'; + +import './provider.css'; + +const onElementClick = (_: MouseEvent, element: FlowElement) => console.log('click', element); +const onLoad = (reactFlowInstance: OnLoadParams) => console.log('flow loaded:', reactFlowInstance); + +const initialElements: Elements = [ + { id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } }, + { id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } }, + { id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } }, + { id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } }, + { id: 'e1-2', source: '1', target: '2', animated: true }, + { id: 'e1-3', source: '1', target: '3' }, +]; + +const ProviderFlow = () => { + const [elements, setElements] = useState(initialElements); + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); + + return ( +
+ + +
+ + + +
+
+
+ ); +}; + +export default ProviderFlow; diff --git a/example/src_oldapi/Provider/provider.css b/example/src_oldapi/Provider/provider.css new file mode 100644 index 00000000..aa719acc --- /dev/null +++ b/example/src_oldapi/Provider/provider.css @@ -0,0 +1,45 @@ +.providerflow { + flex-direction: column; + display: flex; + height: 100%; +} + +.providerflow aside { + border-right: 1px solid #eee; + padding: 15px 10px; + font-size: 12px; + background: #fcfcfc; +} + +.providerflow aside .description { + margin-bottom: 10px; +} + +.providerflow aside .title { + font-weight: 700; + margin-bottom: 5px; +} + +.providerflow aside .transform { + margin-bottom: 20px; +} + +.providerflow .reactflow-wrapper { + flex-grow: 1; + height: 100%; +} + +.providerflow .selectall { + margin-top: 10px; +} + +@media screen and (min-width: 768px) { + .providerflow { + flex-direction: row; + } + + .providerflow aside { + width: 20%; + max-width: 250px; + } +} \ No newline at end of file diff --git a/example/src_oldapi/SaveRestore/Controls.tsx b/example/src_oldapi/SaveRestore/Controls.tsx new file mode 100644 index 00000000..f79f5e47 --- /dev/null +++ b/example/src_oldapi/SaveRestore/Controls.tsx @@ -0,0 +1,61 @@ +import React, { memo, useCallback, Dispatch, FC } from 'react'; +import { useZoomPanHelper, OnLoadParams, Elements, FlowExportObject } from 'react-flow-renderer'; +import localforage from 'localforage'; + +localforage.config({ + name: 'react-flow', + storeName: 'flows', +}); + +const flowKey = 'example-flow'; + +const getNodeId = () => `randomnode_${+new Date()}`; + +type ControlsProps = { + rfInstance?: OnLoadParams; + setElements: Dispatch>>; +}; + +const Controls: FC = ({ rfInstance, setElements }) => { + const { transform } = useZoomPanHelper(); + + const onSave = useCallback(() => { + if (rfInstance) { + const flow = rfInstance.toObject(); + localforage.setItem(flowKey, flow); + } + }, [rfInstance]); + + const onRestore = useCallback(() => { + const restoreFlow = async () => { + const flow: FlowExportObject | null = await localforage.getItem(flowKey); + + if (flow) { + const [x = 0, y = 0] = flow.position; + setElements(flow.elements || []); + transform({ x, y, zoom: flow.zoom || 0 }); + } + }; + + restoreFlow(); + }, [setElements, transform]); + + const onAdd = useCallback(() => { + const newNode = { + id: `random_node-${getNodeId()}`, + data: { label: 'Added node' }, + position: { x: Math.random() * window.innerWidth - 100, y: Math.random() * window.innerHeight }, + }; + setElements((els) => els.concat(newNode)); + }, [setElements]); + + return ( +
+ + + +
+ ); +}; + +export default memo(Controls); diff --git a/example/src_oldapi/SaveRestore/index.tsx b/example/src_oldapi/SaveRestore/index.tsx new file mode 100644 index 00000000..9457248d --- /dev/null +++ b/example/src_oldapi/SaveRestore/index.tsx @@ -0,0 +1,37 @@ +import React, { useState } from 'react'; +import ReactFlow, { + ReactFlowProvider, + removeElements, + addEdge, + Elements, + Connection, + Edge, + OnLoadParams, +} from 'react-flow-renderer'; + +import Controls from './Controls'; + +import './save.css'; + +const initialElements: Elements = [ + { id: '1', data: { label: 'Node 1' }, position: { x: 100, y: 100 } }, + { id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 200 } }, + { id: 'e1-2', source: '1', target: '2' }, +]; + +const SaveRestore = () => { + const [rfInstance, setRfInstance] = useState(); + const [elements, setElements] = useState(initialElements); + const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + + return ( + + + + + + ); +}; + +export default SaveRestore; diff --git a/example/src_oldapi/SaveRestore/save.css b/example/src_oldapi/SaveRestore/save.css new file mode 100644 index 00000000..ffd028dd --- /dev/null +++ b/example/src_oldapi/SaveRestore/save.css @@ -0,0 +1,11 @@ +.save__controls { + position: absolute; + right: 10px; + top: 10px; + z-index: 4; + font-size: 12px; +} + +.save__controls button { + margin-left: 5px; +} diff --git a/example/src_oldapi/Stress/index.tsx b/example/src_oldapi/Stress/index.tsx new file mode 100644 index 00000000..b734c6b6 --- /dev/null +++ b/example/src_oldapi/Stress/index.tsx @@ -0,0 +1,70 @@ +import React, { useState, CSSProperties } from 'react'; +import ReactFlow, { + removeElements, + addEdge, + MiniMap, + isNode, + Controls, + Background, + OnLoadParams, + Elements, + Connection, + Edge, +} from 'react-flow-renderer'; + +import { getElements } from './utils'; + +const buttonWrapperStyles: CSSProperties = { position: 'absolute', right: 10, top: 10, zIndex: 4 }; + +const onLoad = (reactFlowInstance: OnLoadParams) => { + reactFlowInstance.fitView(); + console.log(reactFlowInstance.getElements()); +}; + +const initialElements: Elements = getElements(30, 30); + +const StressFlow = () => { + const [elements, setElements] = useState(initialElements); + const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + + const updatePos = () => { + setElements((elms) => { + return elms.map((el) => { + if (isNode(el)) { + return { + ...el, + position: { + x: Math.random() * window.innerWidth, + y: Math.random() * window.innerHeight, + }, + }; + } + + return el; + }); + }); + }; + + const updateElements = () => { + const grid = Math.ceil(Math.random() * 10); + setElements(getElements(grid, grid)); + }; + + return ( + + + + + +
+ + +
+
+ ); +}; + +export default StressFlow; diff --git a/example/src_oldapi/Stress/utils.ts b/example/src_oldapi/Stress/utils.ts new file mode 100644 index 00000000..a37eb8de --- /dev/null +++ b/example/src_oldapi/Stress/utils.ts @@ -0,0 +1,30 @@ +import { Elements } from 'react-flow-renderer'; + +export function getElements(xElements: number = 10, yElements: number = 10): Elements { + const initialElements = []; + let nodeId = 1; + let recentNodeId = null; + + for (let y = 0; y < yElements; y++) { + for (let x = 0; x < xElements; x++) { + const position = { x: x * 100, y: y * 50 }; + const data = { label: `Node ${nodeId}` }; + const node = { + id: nodeId.toString(), + style: { width: 50, fontSize: 11 }, + data, + position, + }; + initialElements.push(node); + + if (recentNodeId && nodeId <= xElements * yElements) { + initialElements.push({ id: `${x}-${y}`, source: recentNodeId.toString(), target: nodeId.toString() }); + } + + recentNodeId = nodeId; + nodeId++; + } + } + + return initialElements; +} diff --git a/example/src_oldapi/Switch/index.tsx b/example/src_oldapi/Switch/index.tsx new file mode 100644 index 00000000..03dacf99 --- /dev/null +++ b/example/src_oldapi/Switch/index.tsx @@ -0,0 +1,53 @@ +import React, { useState, MouseEvent } from 'react'; + +import ReactFlow, { removeElements, addEdge, Node, FlowElement, Elements, Connection, Edge } from 'react-flow-renderer'; + +const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); +const onElementClick = (_: MouseEvent, element: FlowElement) => console.log('click', element); + +const elementsA: Elements = [ + { id: '1a', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' }, + { id: '2a', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, className: 'light' }, + { id: '3a', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' }, + { id: '4a', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' }, + { id: 'e1-2', source: '1a', target: '2a' }, + { id: 'e1-3', source: '1a', target: '3a' }, +]; + +const elementsB: Elements = [ + { id: 'inputb', type: 'input', data: { label: 'Input' }, position: { x: 300, y: 5 }, className: 'light' }, + { id: '1b', data: { label: 'Node 1' }, position: { x: 0, y: 100 }, className: 'light' }, + { id: '2b', data: { label: 'Node 2' }, position: { x: 200, y: 100 }, className: 'light' }, + { id: '3b', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' }, + { id: '4b', data: { label: 'Node 4' }, position: { x: 600, y: 100 }, className: 'light' }, + + { id: 'e1b', source: 'inputb', target: '1b' }, + { id: 'e2b', source: 'inputb', target: '2b' }, + { id: 'e3b', source: 'inputb', target: '3b' }, + { id: 'e4b', source: 'inputb', target: '4b' }, +]; + +const BasicFlow = () => { + const [elements, setElements] = useState(elementsA); + const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + + return ( + +
+ + +
+
+ ); +}; + +export default BasicFlow; diff --git a/example/src_oldapi/Undirectional/CustomNode.tsx b/example/src_oldapi/Undirectional/CustomNode.tsx new file mode 100644 index 00000000..ab5853e8 --- /dev/null +++ b/example/src_oldapi/Undirectional/CustomNode.tsx @@ -0,0 +1,19 @@ +import React, { memo, FC, CSSProperties } from 'react'; + +import { Handle, Position, NodeProps } from 'react-flow-renderer'; + +const nodeStyles: CSSProperties = { padding: '10px 15px', border: '1px solid #ddd' }; + +const CustomNode: FC = ({ id }) => { + return ( +
+
node {id}
+ + + + +
+ ); +}; + +export default memo(CustomNode); diff --git a/example/src_oldapi/Undirectional/index.tsx b/example/src_oldapi/Undirectional/index.tsx new file mode 100644 index 00000000..f05ad21e --- /dev/null +++ b/example/src_oldapi/Undirectional/index.tsx @@ -0,0 +1,221 @@ +import React, { useState, useCallback } from 'react'; + +import ReactFlow, { + NodeTypesType, + addEdge, + useZoomPanHelper, + ReactFlowProvider, + Elements, + Connection, + Edge, + ElementId, + ConnectionLineType, + ConnectionMode, + updateEdge, + ArrowHeadType, +} from 'react-flow-renderer'; +import CustomNode from './CustomNode'; + +const initialElements: Elements = [ + { + id: '00', + type: 'custom', + position: { x: 300, y: 250 }, + }, + { + id: '01', + type: 'custom', + position: { x: 100, y: 50 }, + }, + { + id: '02', + type: 'custom', + position: { x: 500, y: 50 }, + }, + { + id: '03', + type: 'custom', + position: { x: 500, y: 500 }, + }, + { + id: '04', + type: 'custom', + position: { x: 100, y: 500 }, + }, + { + id: '10', + type: 'custom', + position: { x: 300, y: 5 }, + }, + { + id: '20', + type: 'custom', + position: { x: 600, y: 250 }, + }, + { + id: '30', + type: 'custom', + position: { x: 300, y: 600 }, + }, + { + id: '40', + type: 'custom', + position: { x: 5, y: 250 }, + }, + { + id: 'e0-1a', + source: '00', + target: '01', + sourceHandle: 'left', + targetHandle: 'bottom', + type: 'smoothstep', + arrowHeadType: ArrowHeadType.Arrow, + }, + { + id: 'e0-1b', + source: '00', + target: '01', + sourceHandle: 'top', + targetHandle: 'right', + type: 'smoothstep', + arrowHeadType: ArrowHeadType.Arrow, + }, + { + id: 'e0-2a', + source: '00', + target: '02', + sourceHandle: 'top', + targetHandle: 'left', + type: 'smoothstep', + arrowHeadType: ArrowHeadType.Arrow, + }, + { + id: 'e0-2b', + source: '00', + target: '02', + sourceHandle: 'right', + targetHandle: 'bottom', + type: 'smoothstep', + arrowHeadType: ArrowHeadType.Arrow, + }, + { + id: 'e0-3a', + source: '00', + target: '03', + sourceHandle: 'right', + targetHandle: 'top', + type: 'smoothstep', + arrowHeadType: ArrowHeadType.Arrow, + }, + { + id: 'e0-3b', + source: '00', + target: '03', + sourceHandle: 'bottom', + targetHandle: 'left', + type: 'smoothstep', + arrowHeadType: ArrowHeadType.Arrow, + }, + { + id: 'e0-4a', + source: '00', + target: '04', + sourceHandle: 'bottom', + targetHandle: 'right', + type: 'smoothstep', + arrowHeadType: ArrowHeadType.Arrow, + }, + { + id: 'e0-4b', + source: '00', + target: '04', + sourceHandle: 'left', + targetHandle: 'top', + type: 'smoothstep', + arrowHeadType: ArrowHeadType.Arrow, + }, + { + id: 'e0-10', + source: '00', + target: '10', + sourceHandle: 'top', + targetHandle: 'bottom', + type: 'smoothstep', + arrowHeadType: ArrowHeadType.Arrow, + }, + { + id: 'e0-20', + source: '00', + target: '20', + sourceHandle: 'right', + targetHandle: 'left', + type: 'smoothstep', + arrowHeadType: ArrowHeadType.Arrow, + }, + { + id: 'e0-30', + source: '00', + target: '30', + sourceHandle: 'bottom', + targetHandle: 'top', + type: 'smoothstep', + arrowHeadType: ArrowHeadType.Arrow, + }, + { + id: 'e0-40', + source: '00', + target: '40', + sourceHandle: 'left', + targetHandle: 'right', + type: 'smoothstep', + arrowHeadType: ArrowHeadType.Arrow, + }, +]; + +const nodeTypes: NodeTypesType = { + custom: CustomNode, +}; + +let id = 4; +const getId = (): ElementId => `${id++}`; + +const UpdateNodeInternalsFlow = () => { + const [elements, setElements] = useState(initialElements); + const onConnect = (params: Connection | Edge) => + setElements((els) => addEdge({ ...params, type: 'smoothstep' }, els)); + const { project } = useZoomPanHelper(); + const onEdgeUpdate = (oldEdge: Edge, newConnection: Connection) => + setElements((els) => updateEdge(oldEdge, newConnection, els)); + + const onPaneClick = useCallback( + (evt) => + setElements((els) => + els.concat({ + id: getId(), + position: project({ x: evt.clientX, y: evt.clientY - 40 }), + type: 'custom', + }) + ), + [project] + ); + + return ( + + ); +}; + +const WrappedFlow = () => ( + + + +); + +export default WrappedFlow; diff --git a/example/src_oldapi/UpdatableEdge/index.tsx b/example/src_oldapi/UpdatableEdge/index.tsx new file mode 100644 index 00000000..4ffdab04 --- /dev/null +++ b/example/src_oldapi/UpdatableEdge/index.tsx @@ -0,0 +1,79 @@ +import React, { useState } from 'react'; +import ReactFlow, { + Controls, + updateEdge, + addEdge, + Elements, + OnLoadParams, + Connection, + Edge, + removeElements, +} from 'react-flow-renderer'; + +const initialElements: Elements = [ + { + id: '1', + type: 'input', + data: { + label: ( + <> + Node A + + ), + }, + position: { x: 250, y: 0 }, + }, + { + id: '2', + data: { + label: ( + <> + Node B + + ), + }, + position: { x: 100, y: 100 }, + }, + { + id: '3', + data: { + label: ( + <> + Node C + + ), + }, + position: { x: 400, y: 100 }, + style: { background: '#D6D5E6', color: '#333', border: '1px solid #222138', width: 180 }, + }, + { id: 'e1-2', source: '1', target: '2', label: 'This is a draggable edge' }, +]; + +const onLoad = (reactFlowInstance: OnLoadParams) => reactFlowInstance.fitView(); +const onEdgeUpdateStart = (_: React.MouseEvent, edge: Edge) => console.log('start update', edge); +const onEdgeUpdateEnd = (_: MouseEvent, edge: Edge) => console.log('end update', edge); + +const UpdatableEdge = () => { + const [elements, setElements] = useState(initialElements); + const onEdgeUpdate = (oldEdge: Edge, newConnection: Connection) => + setElements((els) => updateEdge(oldEdge, newConnection, els)); + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); + + return ( + + + + ); +}; + +export default UpdatableEdge; diff --git a/example/src_oldapi/UpdateNode/index.tsx b/example/src_oldapi/UpdateNode/index.tsx new file mode 100644 index 00000000..a7cce8f2 --- /dev/null +++ b/example/src_oldapi/UpdateNode/index.tsx @@ -0,0 +1,78 @@ +import React, { useEffect, useState } from 'react'; +import ReactFlow, { Elements } from 'react-flow-renderer'; + +import './updatenode.css'; + +const initialElements: Elements = [ + { id: '1', data: { label: '-' }, position: { x: 100, y: 100 } }, + { id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 200 } }, + { id: 'e1-2', source: '1', target: '2' }, +]; + +const UpdateNode = () => { + const [elements, setElements] = useState(initialElements); + const [nodeName, setNodeName] = useState('Node 1'); + const [nodeBg, setNodeBg] = useState('#eee'); + const [nodeHidden, setNodeHidden] = useState(false); + + useEffect(() => { + setElements((els) => + els.map((el) => { + if (el.id === '1') { + // it's important that you create a new object here in order to notify react flow about the change + el.data = { + ...el.data, + label: nodeName, + }; + } + + return el; + }) + ); + }, [nodeName, setElements]); + + useEffect(() => { + setElements((els) => + els.map((el) => { + if (el.id === '1') { + // it's important that you create a new object here in order to notify react flow about the change + el.style = { ...el.style, backgroundColor: nodeBg }; + } + + return el; + }) + ); + }, [nodeBg, setElements]); + + useEffect(() => { + setElements((els) => + els.map((el) => { + if (el.id === '1' || el.id === 'e1-2') { + // when you update a simple type you can just update the value + el.isHidden = nodeHidden; + } + + return el; + }) + ); + }, [nodeHidden, setElements]); + + return ( + +
+ + setNodeName(evt.target.value)} /> + + + setNodeBg(evt.target.value)} /> + +
+ + setNodeHidden(evt.target.checked)} /> +
+
+
+ ); +}; + +export default UpdateNode; diff --git a/example/src_oldapi/UpdateNode/updatenode.css b/example/src_oldapi/UpdateNode/updatenode.css new file mode 100644 index 00000000..95438054 --- /dev/null +++ b/example/src_oldapi/UpdateNode/updatenode.css @@ -0,0 +1,21 @@ +.updatenode__controls { + position: absolute; + right: 10px; + top: 10px; + z-index: 4; + font-size: 12px; +} + +.updatenode__controls label { + display: block; +} + +.updatenode__bglabel { + margin-top: 10px; +} + +.updatenode__checkboxwrapper { + margin-top: 10px; + display: flex; + align-items: center; +} diff --git a/example/src_oldapi/UseUpdateNodeInternals/CustomNode.tsx b/example/src_oldapi/UseUpdateNodeInternals/CustomNode.tsx new file mode 100644 index 00000000..969a4c3c --- /dev/null +++ b/example/src_oldapi/UseUpdateNodeInternals/CustomNode.tsx @@ -0,0 +1,34 @@ +import React, { memo, FC, useMemo, CSSProperties } from 'react'; + +import { Handle, Position, NodeProps } from 'react-flow-renderer'; + +const nodeStyles: CSSProperties = { padding: 10, border: '1px solid #ddd' }; + +const CustomNode: FC = ({ data }) => { + const handles = useMemo( + () => + Array.from({ length: data.handleCount }, (x, i) => { + const handleId = `handle-${i}`; + return ( + + ); + }), + [data.handleCount, data.handlePosition] + ); + + return ( +
+ +
output handle count: {data.handleCount}
+ {handles} +
+ ); +}; + +export default memo(CustomNode); diff --git a/example/src_oldapi/UseUpdateNodeInternals/index.tsx b/example/src_oldapi/UseUpdateNodeInternals/index.tsx new file mode 100644 index 00000000..2b254c5a --- /dev/null +++ b/example/src_oldapi/UseUpdateNodeInternals/index.tsx @@ -0,0 +1,101 @@ +import React, { useState, useCallback, CSSProperties } from 'react'; + +import ReactFlow, { + NodeTypesType, + addEdge, + useZoomPanHelper, + ReactFlowProvider, + Elements, + Connection, + Edge, + ElementId, + useUpdateNodeInternals, + Position, + isEdge, +} from 'react-flow-renderer'; +import CustomNode from './CustomNode'; + +const initialHandleCount = 1; + +const initialElements: Elements = [ + { + id: '1', + type: 'custom', + data: { label: 'Node 1', handleCount: initialHandleCount, handlePosition: 0 }, + position: { x: 250, y: 5 }, + }, +]; + +const buttonWrapperStyles: CSSProperties = { position: 'absolute', right: 10, top: 10, zIndex: 10 }; + +const nodeTypes: NodeTypesType = { + custom: CustomNode, +}; + +let id = 5; +const getId = (): ElementId => `${id++}`; + +const UpdateNodeInternalsFlow = () => { + const [elements, setElements] = useState(initialElements); + const updateNodeInternals = useUpdateNodeInternals(); + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + const { project } = useZoomPanHelper(); + + const onPaneClick = useCallback( + (evt) => + setElements((els) => + els.concat({ + id: getId(), + position: project({ x: evt.clientX, y: evt.clientY - 40 }), + data: { label: 'new node' }, + targetPosition: Position.Left, + sourcePosition: Position.Right, + }) + ), + [project] + ); + + const toggleHandleCount = useCallback(() => { + setElements((els) => + els.map((el) => { + if (isEdge(el)) { + return el; + } + + return { ...el, data: { ...el.data, handleCount: el.data?.handleCount === 1 ? 2 : 1 } }; + }) + ); + }, []); + + const toggleHandlePosition = useCallback(() => { + setElements((els) => + els.map((el) => { + if (isEdge(el)) { + return el; + } + + return { ...el, data: { ...el.data, handlePosition: el.data?.handlePosition === 0 ? 1 : 0 } }; + }) + ); + }, []); + + const updateNode = useCallback(() => updateNodeInternals('1'), [updateNodeInternals]); + + return ( + +
+ + + +
+
+ ); +}; + +const WrappedFlow = () => ( + + + +); + +export default WrappedFlow; diff --git a/example/src_oldapi/UseZoomPanHelper/index.tsx b/example/src_oldapi/UseZoomPanHelper/index.tsx new file mode 100644 index 00000000..94ff30f8 --- /dev/null +++ b/example/src_oldapi/UseZoomPanHelper/index.tsx @@ -0,0 +1,65 @@ +import React, { useState, useCallback } from 'react'; + +import ReactFlow, { + removeElements, + addEdge, + Background, + MiniMap, + useZoomPanHelper, + ReactFlowProvider, + Elements, + ElementId, + Connection, + Edge, +} from 'react-flow-renderer'; + +const initialElements: Elements = [ + { id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' }, + { id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, className: 'light' }, + { id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' }, + { id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' }, + { id: 'e1-2', source: '1', target: '2', animated: true }, + { id: 'e1-3', source: '1', target: '3' }, +]; + +let id = 5; +const getId = (): ElementId => `${id++}`; + +const UseZoomPanHelperFlow = () => { + const [elements, setElements] = useState(initialElements); + const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + const { project } = useZoomPanHelper(); + + const onPaneClick = useCallback( + (evt) => { + const projectedPosition = project({ x: evt.clientX, y: evt.clientY - 40 }); + + setElements((els) => + els.concat({ + id: getId(), + position: projectedPosition, + data: { + label: `${projectedPosition.x}-${projectedPosition.y}`, + }, + }) + ); + }, + [project] + ); + + return ( + + + + + ); +}; + +const WrappedFlow = () => ( + + + +); + +export default WrappedFlow; diff --git a/example/src_oldapi/Validation/index.tsx b/example/src_oldapi/Validation/index.tsx new file mode 100644 index 00000000..d5209ed4 --- /dev/null +++ b/example/src_oldapi/Validation/index.tsx @@ -0,0 +1,73 @@ +import React, { useState, MouseEvent as ReactMouseEvent, FC } from 'react'; +import ReactFlow, { + addEdge, + Handle, + OnLoadParams, + Connection, + Position, + Elements, + Edge, + OnConnectStartParams, + NodeProps, + NodeTypesType, +} from 'react-flow-renderer'; + +import './validation.css'; + +const initialElements: Elements = [ + { id: '0', type: 'custominput', position: { x: 0, y: 150 } }, + { id: 'A', type: 'customnode', position: { x: 250, y: 0 } }, + { id: 'B', type: 'customnode', position: { x: 250, y: 150 } }, + { id: 'C', type: 'customnode', position: { x: 250, y: 300 } }, +]; + +const onLoad = (reactFlowInstance: OnLoadParams) => reactFlowInstance.fitView(); +const isValidConnection = (connection: Connection) => connection.target === 'B'; +const onConnectStart = (_: ReactMouseEvent, { nodeId, handleType }: OnConnectStartParams) => + console.log('on connect start', { nodeId, handleType }); +const onConnectStop = (event: MouseEvent) => console.log('on connect stop', event); +const onConnectEnd = (event: MouseEvent) => console.log('on connect end', event); + +const CustomInput: FC = () => ( + <> +
Only connectable with B
+ + +); + +const CustomNode: FC = ({ id }) => ( + <> + +
{id}
+ + +); + +const nodeTypes: NodeTypesType = { + custominput: CustomInput, + customnode: CustomNode, +}; + +const HorizontalFlow = () => { + const [elements, setElements] = useState(initialElements); + const onConnect = (params: Connection | Edge) => { + console.log('on connect', params); + setElements((els) => addEdge(params, els)); + }; + + return ( + + ); +}; + +export default HorizontalFlow; diff --git a/example/src_oldapi/Validation/validation.css b/example/src_oldapi/Validation/validation.css new file mode 100644 index 00000000..a777e6c5 --- /dev/null +++ b/example/src_oldapi/Validation/validation.css @@ -0,0 +1,31 @@ +.validationflow .react-flow__node { + width: 150px; + border-radius: 5px; + padding: 10px; + color: #555; + border: 1px solid #ddd; + text-align: center; + font-size: 12px; +} + +.validationflow .react-flow__node-customnode { + background: #e6e6e9; + border: 1px solid #ddd; +} + +.react-flow__node-custominput .react-flow__handle { + background: #e6e6e9; +} + +.validationflow .react-flow__node-custominput { + background: #fff; + +} + +.validationflow .react-flow__handle-connecting { + background: #ff6060; +} + +.validationflow .react-flow__handle-valid { + background: #55dd99; +} \ No newline at end of file diff --git a/example/src_oldapi/index.css b/example/src_oldapi/index.css new file mode 100644 index 00000000..3f6f9960 --- /dev/null +++ b/example/src_oldapi/index.css @@ -0,0 +1,82 @@ +body { + font-family: sans-serif; + color: #111; +} + +html, +body, +#root { + margin: 0; + height: 100%; +} + +#root { + display: flex; + flex-direction: column; +} + +header { + padding: 10px; + border-bottom: 1px solid #eee; + display: flex; + font-weight: 700; + align-items: center; +} + +.logo { + text-decoration: none; + display: block; + line-height: 1; +} + +header a, +header a:focus, +header a:active, +header a:visited { + color: #111; +} + +header a:hover { + color: #333; +} + +header select { + margin-left: 1em; +} + +.overview-example__add { + display: none; +} + +.react-flow__node a { + font-weight: 700; + color: #111; +} + +.react-flow__node.dark-node { + background: #0041d0; + color: #f8f8f8; +} + +.react-flow__node.dark { + background: #557; + color: #f8f8f8; +} + +.react-flow__node-selectorNode { + font-size: 12px; + background: #f0f2f3; + border: 1px solid 555; + border-radius: 5px; + text-align: center; +} + +.react-flow__node-selectorNode .react-flow__handle { + border-color: #f0f2f3; +} + +@media screen and (min-width: 768px) { + .overview-example__add { + display: block; + } +} diff --git a/example/src_oldapi/index.tsx b/example/src_oldapi/index.tsx new file mode 100644 index 00000000..f81278ba --- /dev/null +++ b/example/src_oldapi/index.tsx @@ -0,0 +1,165 @@ +import React, { ChangeEvent } from 'react'; +import ReactDOM from 'react-dom'; +import { BrowserRouter as Router, Route, Switch, withRouter } from 'react-router-dom'; + +import Overview from './Overview'; +import Basic from './Basic'; +import CustomNode from './CustomNode'; +import Stress from './Stress'; +import Interaction from './Interaction'; +import Empty from './Empty'; +import Edges from './Edges'; +import Validation from './Validation'; +import Provider from './Provider'; +import Hidden from './Hidden'; +import EdgeTypes from './EdgeTypes'; +import CustomConnectionLine from './CustomConnectionLine'; +import NodeTypeChange from './NodeTypeChange'; +import NodeTypesObjectChange from './NodeTypesObjectChange'; +import UpdatableEdge from './UpdatableEdge'; +import UpdateNode from './UpdateNode'; +import SaveRestore from './SaveRestore'; +import DragNDrop from './DragNDrop'; +import Layout from './Layouting'; +import SwitchFlows from './Switch'; +import UseZoomPanHelper from './UseZoomPanHelper'; +import UseUpdateNodeInternals from './UseUpdateNodeInternals'; +import Undirectional from './Undirectional'; +import MultiFlows from './MultiFlows'; +import DragHandle from './DragHandle'; + +import './index.css'; + +const routes = [ + { + path: '/', + component: Overview, + }, + { + path: '/edges', + component: Edges, + }, + { + path: '/custom-node', + component: CustomNode, + }, + { + path: '/validation', + component: Validation, + }, + { + path: '/provider', + component: Provider, + }, + { + path: '/stress', + component: Stress, + }, + { + path: '/interaction', + component: Interaction, + }, + { + path: '/basic', + component: Basic, + }, + { + path: '/empty', + component: Empty, + }, + { + path: '/hidden', + component: Hidden, + }, + { + path: '/edge-types', + component: EdgeTypes, + }, + { + path: '/custom-connectionline', + component: CustomConnectionLine, + }, + { + path: '/nodetype-change', + component: NodeTypeChange, + }, + { + path: '/nodetypesobject-change', + component: NodeTypesObjectChange, + }, + { + path: '/updatable-edge', + component: UpdatableEdge, + }, + { + path: '/update-node', + component: UpdateNode, + }, + { + path: '/save-restore', + component: SaveRestore, + }, + { + path: '/drag-and-drop', + component: DragNDrop, + }, + { + path: '/layouting', + component: Layout, + }, + { + path: '/switch', + component: SwitchFlows, + }, + { + path: '/usezoompanhelper', + component: UseZoomPanHelper, + }, + { + path: '/useupdatenodeinternals', + component: UseUpdateNodeInternals, + }, + { + path: '/undirectional', + component: Undirectional, + }, + { + path: '/multiflows', + component: MultiFlows, + }, + { + path: '/draghandle', + component: DragHandle, + }, +]; + +const Header = withRouter(({ history, location }) => { + const onChange = (event: ChangeEvent) => history.push(event.target.value); + + return ( +
+ + React Flow Dev + + +
+ ); +}); + +ReactDOM.render( + +
+ + {routes.map((route) => ( + } key={route.path} /> + ))} + + , + document.getElementById('root') +); diff --git a/example/src_oldapi/react-app-env.d.ts b/example/src_oldapi/react-app-env.d.ts new file mode 100644 index 00000000..6431bc5f --- /dev/null +++ b/example/src_oldapi/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/example/tsconfig.json b/example/tsconfig.json index a273b0cf..edc77511 100644 --- a/example/tsconfig.json +++ b/example/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -20,7 +16,6 @@ "noEmit": true, "jsx": "react-jsx" }, - "include": [ - "src" - ] + "include": ["src"], + "exclude": ["src_oldapi"] }