diff --git a/example/src/Basic/index.tsx b/example/src/Basic/index.tsx index bf5e2143..40ed203c 100644 --- a/example/src/Basic/index.tsx +++ b/example/src/Basic/index.tsx @@ -28,9 +28,7 @@ const initialNodes: Node[] = [ data: { label: 'Node 4' }, position: { x: 100, y: 200 }, className: 'light', - style: { backgroundColor: 'rgba(255, 0, 0, 0.8)' }, - width: 600, - height: 300, + style: { backgroundColor: 'rgba(255, 0, 0, 0.8)', width: 600, height: 300 }, }, { id: '4a', @@ -44,9 +42,7 @@ const initialNodes: Node[] = [ data: { label: 'Node 4b' }, position: { x: 150, y: 50 }, className: 'light', - style: { backgroundColor: 'rgba(255, 0, 255, 0.8)' }, - height: 300, - width: 300, + style: { backgroundColor: 'rgba(255, 0, 255, 0.8)', height: 300, width: 300 }, parentNode: '4', }, { diff --git a/src/additional-components/Background/index.tsx b/src/additional-components/Background/index.tsx index 10ecf3e1..8f73da12 100644 --- a/src/additional-components/Background/index.tsx +++ b/src/additional-components/Background/index.tsx @@ -31,7 +31,7 @@ const Background: FC = ({ // when there are multiple flows on a page we need to make sure that every background gets its own pattern. const patternId = useMemo(() => `pattern-${Math.floor(Math.random() * 100000)}`, []); - const bgClasses = cc(['react-flow__background', className]); + const bgClasses = cc(['react-flow__background', 'react-flow__container', className]); const scaledGap = gap * scale; const xOffset = x % scaledGap; const yOffset = y % scaledGap; diff --git a/src/components/ConnectionLine/index.tsx b/src/components/ConnectionLine/index.tsx index 24a0f57b..f89f62d9 100644 --- a/src/components/ConnectionLine/index.tsx +++ b/src/components/ConnectionLine/index.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState, CSSProperties } from 'react'; +import shallow from 'zustand/shallow'; import { useStore } from '../../store'; import { getBezierPath } from '../Edges/BezierEdge'; @@ -6,7 +7,6 @@ import { getSmoothStepPath } from '../Edges/SmoothStepEdge'; import { ElementId, NodeLookupItem, - Transform, HandleElement, Position, ConnectionLineType, @@ -23,13 +23,12 @@ interface ConnectionLineProps { connectionPositionX: number; connectionPositionY: number; connectionLineType: ConnectionLineType; - transform: Transform; isConnectable: boolean; connectionLineStyle?: CSSProperties; CustomConnectionLineComponent?: ConnectionLineComponent; } -const selector = (s: ReactFlowState) => s.nodeLookup; +const selector = (s: ReactFlowState) => ({ nodeLookup: s.nodeLookup, transform: s.transform }); export default ({ connectionNodeId, @@ -39,11 +38,10 @@ export default ({ connectionPositionX, connectionPositionY, connectionLineType = ConnectionLineType.Bezier, - transform, isConnectable, CustomConnectionLineComponent, }: ConnectionLineProps) => { - const nodeLookup = useStore(selector); + const { nodeLookup, transform } = useStore(selector, shallow); const [sourceNode, setSourceNode] = useState(null); const nodeId = connectionNodeId; const handleId = connectionHandleId; diff --git a/src/components/Nodes/wrapNode.tsx b/src/components/Nodes/wrapNode.tsx index 22fecc7a..dfc9cc71 100644 --- a/src/components/Nodes/wrapNode.tsx +++ b/src/components/Nodes/wrapNode.tsx @@ -23,8 +23,6 @@ export default (NodeComponent: ComponentType) => { scale, xPos, yPos, - width, - height, isSelected, onClick, onMouseEnter, @@ -74,8 +72,6 @@ export default (NodeComponent: ComponentType) => { isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave ? 'all' : 'none', // prevents jumping of nodes on start // opacity: isInitialized ? 1 : 0, - width: isParentNode && width !== null ? width : 'auto', - height: isParentNode && height !== null ? height : 'auto', ...style, }), [ @@ -91,8 +87,6 @@ export default (NodeComponent: ComponentType) => { onMouseMove, onMouseLeave, isParentNode, - width, - height, ] ); diff --git a/src/components/NodesSelection/index.tsx b/src/components/NodesSelection/index.tsx index 5cc8e7ac..e42bbc1f 100644 --- a/src/components/NodesSelection/index.tsx +++ b/src/components/NodesSelection/index.tsx @@ -103,7 +103,7 @@ export default ({ } return ( -
+
{ return (
{ return (
({ - transform: s.transform, connectionNodeId: s.connectionNodeId, connectionHandleId: s.connectionHandleId, connectionHandleType: s.connectionHandleType, @@ -222,7 +221,6 @@ const selector = (s: ReactFlowState) => ({ const EdgeRenderer = (props: EdgeRendererProps) => { const { - transform, connectionNodeId, connectionHandleId, connectionHandleType, @@ -246,7 +244,13 @@ const EdgeRenderer = (props: EdgeRendererProps) => { return ( <> {edgeTree.map(({ level, edges, isMaxLevel }) => ( - + {isMaxLevel && } {edges.map((edge: Edge) => { @@ -289,7 +293,6 @@ const EdgeRenderer = (props: EdgeRendererProps) => { connectionHandleType={connectionHandleType!} connectionPositionX={connectionPosition.x} connectionPositionY={connectionPosition.y} - transform={transform} connectionLineStyle={connectionLineStyle} connectionLineType={connectionLineType} isConnectable={nodesConnectable} diff --git a/src/container/FlowRenderer/index.tsx b/src/container/FlowRenderer/index.tsx index 91f6681d..9cfc3216 100644 --- a/src/container/FlowRenderer/index.tsx +++ b/src/container/FlowRenderer/index.tsx @@ -118,7 +118,12 @@ const FlowRenderer = ({ onSelectionContextMenu={onSelectionContextMenu} /> )} -
+
); }; diff --git a/src/container/GraphView/index.tsx b/src/container/GraphView/index.tsx index cbaa3820..6af31a8e 100644 --- a/src/container/GraphView/index.tsx +++ b/src/container/GraphView/index.tsx @@ -1,9 +1,10 @@ import React, { useEffect, useRef, memo } from 'react'; -import { useStoreApi, useStore } from '../../store'; +import { useStoreApi } from '../../store'; import FlowRenderer from '../FlowRenderer'; import NodeRenderer from '../NodeRenderer'; import EdgeRenderer from '../EdgeRenderer'; +import Viewport from '../Viewport'; import { onLoadProject, onLoadGetNodes, onLoadGetEdges, onLoadToObject } from '../../utils/graph'; import useZoomPanHelper from '../../hooks/useZoomPanHelper'; @@ -83,7 +84,6 @@ const GraphView = ({ const isInitialized = useRef(false); const store = useStoreApi(); const { zoomIn, zoomOut, zoomTo, transform: setTransform, fitView, initialized } = useZoomPanHelper(); - const transform = useStore((s) => s.transform); useEffect(() => { if (!isInitialized.current && initialized) { @@ -93,7 +93,7 @@ const GraphView = ({ zoomIn, zoomOut, zoomTo, - setTransform: setTransform, + setTransform, project: onLoadProject(store.getState), getNodes: onLoadGetNodes(store.getState), getEdges: onLoadGetEdges(store.getState), @@ -103,12 +103,7 @@ const GraphView = ({ isInitialized.current = true; } - }, [onLoad, zoomIn, zoomOut, zoomTo, transform, fitView, initialized]); - - const transformStyle = { - transform: `translate(${transform[0]}px,${transform[1]}px) scale(${transform[2]})`, - transformOrigin: '0 0', - }; + }, [onLoad, zoomIn, zoomOut, zoomTo, setTransform, fitView, initialized]); return ( -
+ -
+
); }; diff --git a/src/container/NodeRenderer/index.tsx b/src/container/NodeRenderer/index.tsx index 6661fb6e..4a774271 100644 --- a/src/container/NodeRenderer/index.tsx +++ b/src/container/NodeRenderer/index.tsx @@ -2,7 +2,7 @@ import React, { memo, useMemo, ComponentType, MouseEvent } from 'react'; import shallow from 'zustand/shallow'; import { useStore } from '../../store'; -import { Node, NodeTypesType, ReactFlowState, WrapNodeProps, SnapGrid, NodeRendererNode } from '../../types'; +import { Node, NodeTypesType, ReactFlowState, WrapNodeProps } from '../../types'; import useVisibleNodes from '../../hooks/useVisibleNodes'; import useNodeLookupRef from '../../hooks/useNodeLookupRef'; @@ -22,7 +22,7 @@ interface NodeRendererProps { } const selector = (s: ReactFlowState) => ({ - transform: s.transform, + scale: s.transform[2], nodesDraggable: s.nodesDraggable, nodesConnectable: s.nodesConnectable, elementsSelectable: s.elementsSelectable, @@ -31,107 +31,9 @@ const selector = (s: ReactFlowState) => ({ snapToGrid: s.snapToGrid, }); -interface NodeProps extends NodeRendererProps { - node: Node; - nodeType: string; - childNodes?: NodeRendererNode[]; - xPos?: number; - yPos?: number; - isDraggable?: boolean; - resizeObserver: ResizeObserver | null; - scale: number; - snapToGrid: boolean; - snapGrid: SnapGrid; - nodesDraggable: boolean; - nodesConnectable: boolean; - elementsSelectable: boolean; - treeLevel?: number; - isParentNode?: boolean; -} - -function Node({ - node, - childNodes, - nodeType, - isDraggable, - resizeObserver, - scale, - snapToGrid, - snapGrid, - nodesDraggable, - nodesConnectable, - elementsSelectable, - treeLevel = 0, - xPos, - yPos, - isParentNode = false, - ...props -}: NodeProps) { - // const onNodesChange = useStore((s) => s.onNodesChange); - const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType; - const isNodeDraggable = - typeof isDraggable !== 'undefined' - ? isDraggable - : !!(node.draggable || (nodesDraggable && typeof node.draggable === 'undefined')); - const isSelectable = !!(node.selectable || (elementsSelectable && typeof node.selectable === 'undefined')); - const isConnectable = !!(node.connectable || (nodesConnectable && typeof node.connectable === 'undefined')); - const isInitialized = - node.width !== null && - node.height !== null && - typeof node.width !== 'undefined' && - typeof node.height !== 'undefined'; - - return ( - - ); -} - const NodeRenderer = (props: NodeRendererProps) => { - const { - transform, - nodesDraggable, - nodesConnectable, - elementsSelectable, - updateNodeDimensions, - snapGrid, - snapToGrid, - } = useStore(selector, shallow); + const { scale, nodesDraggable, nodesConnectable, elementsSelectable, updateNodeDimensions, snapGrid, snapToGrid } = + useStore(selector, shallow); const nodeLookup = useNodeLookupRef(); const nodes = useVisibleNodes(props.onlyRenderVisibleElements); @@ -151,7 +53,7 @@ const NodeRenderer = (props: NodeRendererProps) => { }, []); return ( -
+
{nodes.map((node) => { const nodeType = node.type || 'default'; const lookupNode = nodeLookup.current.get(node.id); @@ -160,23 +62,51 @@ const NodeRenderer = (props: NodeRendererProps) => { console.warn(`Node type "${nodeType}" not found. Using fallback type "default".`); } + const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType; + const isDraggable = !!(node.draggable || (nodesDraggable && typeof node.draggable === 'undefined')); + const isSelectable = !!(node.selectable || (elementsSelectable && typeof node.selectable === 'undefined')); + const isConnectable = !!(node.connectable || (nodesConnectable && typeof node.connectable === 'undefined')); + const isInitialized = + node.width !== null && + node.height !== null && + typeof node.width !== 'undefined' && + typeof node.height !== 'undefined'; + return ( - ); })} diff --git a/src/container/Viewport/index.tsx b/src/container/Viewport/index.tsx new file mode 100644 index 00000000..590b7516 --- /dev/null +++ b/src/container/Viewport/index.tsx @@ -0,0 +1,25 @@ +import React, { ReactNode } from 'react'; + +import { useStore } from '../../store'; +import { ReactFlowState } from '../../types'; + +const selector = (s: ReactFlowState) => s.transform; + +type ViewportProps = { + children: ReactNode; +}; + +function Viewport({ children }: ViewportProps) { + const transform = useStore(selector); + + return ( +
+ {children} +
+ ); +} + +export default Viewport; diff --git a/src/style.css b/src/style.css index 6064fde8..fe74d2c0 100644 --- a/src/style.css +++ b/src/style.css @@ -5,12 +5,10 @@ overflow: hidden; } -.react-flow__renderer, -.react-flow__pane, -.react-flow__selectionpane { +.react-flow__container { + position: absolute; width: 100%; height: 100%; - position: absolute; top: 0; left: 0; } @@ -19,6 +17,11 @@ z-index: 1; } +.react-flow__viewport { + transform-origin: 0 0; + z-index: 2; +} + .react-flow__renderer { z-index: 4; } @@ -27,17 +30,9 @@ z-index: 5; } -.react-flow__selection { - position: absolute; - top: 0; - left: 0; -} - .react-flow__edges { - position: absolute; - top: 0; - left: 0; pointer-events: none; + overflow: visible; } .react-flow__edge { @@ -81,9 +76,6 @@ } .react-flow__nodes { - position: absolute; - width: 100%; - height: 100%; pointer-events: none; transform-origin: 0 0; } @@ -98,11 +90,6 @@ .react-flow__nodesselection { z-index: 3; - position: absolute; - width: 100%; - height: 100%; - top: 0; - left: 0; transform-origin: left top; pointer-events: none; @@ -153,14 +140,6 @@ /* additional components */ -.react-flow__background { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; -} - .react-flow__controls { position: absolute; z-index: 5; @@ -184,10 +163,3 @@ bottom: 10px; right: 10px; } - -.react-flow__container { - position: absolute; - width: 100%; - height: 100%; - pointer-events: none; -}