refactor(state): separate nodes and edges

This commit is contained in:
moklick
2021-10-19 11:09:49 +02:00
parent 3dc79276ad
commit 410b12946d
22 changed files with 634 additions and 537 deletions
+88 -50
View File
@@ -4,7 +4,7 @@ import shallow from 'zustand/shallow';
import { useStore } from '../../store';
import ConnectionLine from '../../components/ConnectionLine/index';
import MarkerDefinitions from './MarkerDefinitions';
import { getEdgePositions, getHandle } from './utils';
import { getEdgePositions, getHandle, getSourceTargetNodes } from './utils';
import {
Position,
Edge,
@@ -15,15 +15,17 @@ import {
ConnectionMode,
OnEdgeUpdateFunc,
ReactFlowState,
NodeHandleBounds,
} from '../../types';
interface EdgeRendererProps {
nodes: Node[];
edges: Edge[];
edgeTypes: any;
connectionLineType: ConnectionLineType;
connectionLineStyle?: CSSProperties;
connectionLineComponent?: ConnectionLineComponent;
connectionMode?: ConnectionMode;
onElementClick?: (event: React.MouseEvent, element: Node | Edge) => void;
onEdgeClick?: (event: React.MouseEvent, node: Edge) => void;
onEdgeDoubleClick?: (event: React.MouseEvent, edge: Edge) => void;
arrowHeadColor: string;
markerEndId?: string;
@@ -42,7 +44,7 @@ interface EdgeWrapperProps {
edge: Edge;
edgeTypes: any;
markerEndId?: string;
onElementClick?: (event: React.MouseEvent, element: Node | Edge) => void;
onEdgeClick?: (event: React.MouseEvent, node: Edge) => void;
onEdgeContextMenu?: (event: React.MouseEvent, edge: Edge) => void;
onEdgeMouseEnter?: (event: React.MouseEvent, edge: Edge) => void;
onEdgeMouseMove?: (event: React.MouseEvent, edge: Edge) => void;
@@ -52,10 +54,18 @@ interface EdgeWrapperProps {
onEdgeUpdateStart?: (event: React.MouseEvent, edge: Edge) => void;
onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge) => void;
onEdgeUpdate?: OnEdgeUpdateFunc;
targetNode?: Node;
sourceNode?: Node;
elementsSelectable: boolean;
connectionMode?: ConnectionMode;
sourceNodeWidth?: number | null;
sourceNodeHeight?: number | null;
sourceNodeX?: number;
sourceNodeY?: number;
sourceNodeHandleBounds?: NodeHandleBounds;
targetNodeWidth?: number | null;
targetNodeHeight?: number | null;
targetNodeX?: number;
targetNodeY?: number;
targetNodeHandleBounds?: NodeHandleBounds;
}
const Edge = memo(
@@ -63,7 +73,7 @@ const Edge = memo(
edge,
edgeTypes,
markerEndId,
onElementClick,
onEdgeClick,
onEdgeContextMenu,
onEdgeMouseEnter,
onEdgeMouseMove,
@@ -73,10 +83,18 @@ const Edge = memo(
onEdgeUpdateStart,
onEdgeUpdateEnd,
onEdgeUpdate,
targetNode,
sourceNode,
elementsSelectable,
connectionMode,
elementsSelectable,
sourceNodeWidth,
sourceNodeHeight,
sourceNodeX,
sourceNodeY,
sourceNodeHandleBounds,
targetNodeWidth,
targetNodeHeight,
targetNodeX,
targetNodeY,
targetNodeHandleBounds,
}: EdgeWrapperProps) => {
const sourceHandleId = edge.sourceHandle || null;
const targetHandleId = edge.targetHandle || null;
@@ -88,31 +106,40 @@ const Edge = memo(
[edge, onEdgeUpdate]
);
if (!sourceNode) {
// source and target node need to be initialized
if (!sourceNodeHandleBounds || !targetNodeHandleBounds) {
return null;
}
if (
!sourceNodeWidth ||
!sourceNodeHeight ||
typeof sourceNodeX === 'undefined' ||
typeof sourceNodeY === 'undefined'
) {
console.warn(`couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`);
return null;
}
if (!targetNode) {
if (
!targetNodeWidth ||
!targetNodeHeight ||
typeof targetNodeX === 'undefined' ||
typeof targetNodeY === 'undefined'
) {
console.warn(`couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`);
return null;
}
// source and target node need to be initialized
if (!sourceNode.width || !targetNode.width) {
return null;
}
const edgeType = edge.type || 'default';
const EdgeComponent = edgeTypes[edgeType] || edgeTypes.default;
const targetNodeBounds = targetNode.handleBounds;
// when connection type is loose we can define all handles as sources
const targetNodeHandles =
connectionMode === ConnectionMode.Strict
? targetNodeBounds.target
: targetNodeBounds.target || targetNodeBounds.source;
const sourceHandle = getHandle(sourceNode.handleBounds.source, sourceHandleId);
const targetHandle = getHandle(targetNodeHandles, targetHandleId);
? targetNodeHandleBounds.target
: targetNodeHandleBounds.target || targetNodeHandleBounds.source;
const sourceHandle = getHandle(sourceNodeHandleBounds.source!, sourceHandleId);
const targetHandle = getHandle(targetNodeHandles!, targetHandleId);
const sourcePosition = sourceHandle ? sourceHandle.position : Position.Bottom;
const targetPosition = targetHandle ? targetHandle.position : Position.Top;
@@ -127,10 +154,10 @@ const Edge = memo(
}
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
sourceNode,
{ x: sourceNodeX, y: sourceNodeY, width: sourceNodeWidth, height: sourceNodeHeight },
sourceHandle,
sourcePosition,
targetNode,
{ x: targetNodeX, y: targetNodeY, width: targetNodeWidth, height: targetNodeHeight },
targetHandle,
targetPosition
);
@@ -156,8 +183,8 @@ const Edge = memo(
className={edge.className}
type={edge.type}
data={edge.data}
onClick={onElementClick}
selected={!!edge.selected}
onClick={onEdgeClick}
isSelected={!!edge.isSelected}
animated={edge.animated}
label={edge.label}
labelStyle={edge.labelStyle}
@@ -197,7 +224,6 @@ const Edge = memo(
const selector = (s: ReactFlowState) => ({
transform: s.transform,
edges: s.edges,
connectionNodeId: s.connectionNodeId,
connectionHandleId: s.connectionHandleId,
connectionHandleType: s.connectionHandleType,
@@ -206,12 +232,12 @@ const selector = (s: ReactFlowState) => ({
elementsSelectable: s.elementsSelectable,
width: s.width,
height: s.height,
connectionMode: s.connectionMode,
});
const EdgeRenderer = (props: EdgeRendererProps) => {
const {
transform,
edges,
connectionNodeId,
connectionHandleId,
connectionHandleType,
@@ -220,6 +246,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
elementsSelectable,
width,
height,
connectionMode,
} = useStore(selector, shallow);
if (!width) {
@@ -227,33 +254,44 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
}
const { connectionLineType, arrowHeadColor, connectionLineStyle, connectionLineComponent } = props;
const transformStyle = `translate(${transform[0]},${transform[1]}) scale(${transform[2]})`;
const renderConnectionLine = connectionNodeId && connectionHandleType;
return (
<svg width={width} height={height} className="react-flow__edges">
<MarkerDefinitions color={arrowHeadColor} />
<g transform={transformStyle}>
{edges.map((edge: Edge) => (
<Edge
key={edge.id}
edge={edge}
sourceNode={edge.sourceNode}
targetNode={edge.targetNode}
elementsSelectable={elementsSelectable}
markerEndId={props.markerEndId}
onEdgeContextMenu={props.onEdgeContextMenu}
onEdgeMouseEnter={props.onEdgeMouseEnter}
onEdgeMouseMove={props.onEdgeMouseMove}
onEdgeMouseLeave={props.onEdgeMouseLeave}
edgeUpdaterRadius={props.edgeUpdaterRadius}
onEdgeDoubleClick={props.onEdgeDoubleClick}
onEdgeUpdateStart={props.onEdgeUpdateStart}
onEdgeUpdateEnd={props.onEdgeUpdateEnd}
onEdgeUpdate={props.onEdgeUpdate}
edgeTypes={props.edgeTypes}
/>
))}
<g transform={`translate(${transform[0]},${transform[1]}) scale(${transform[2]})`}>
{props.edges.map((edge: Edge) => {
const { sourceNode, targetNode } = getSourceTargetNodes(edge, props.nodes);
return (
<Edge
key={edge.id}
edge={edge}
sourceNodeWidth={sourceNode?.width}
sourceNodeHeight={sourceNode?.height}
sourceNodeX={sourceNode?.position.x}
sourceNodeY={sourceNode?.position.y}
sourceNodeHandleBounds={sourceNode?.handleBounds}
targetNodeWidth={targetNode?.width}
targetNodeHeight={targetNode?.height}
targetNodeX={targetNode?.position.x}
targetNodeY={targetNode?.position.y}
targetNodeHandleBounds={targetNode?.handleBounds}
elementsSelectable={elementsSelectable}
markerEndId={props.markerEndId}
onEdgeContextMenu={props.onEdgeContextMenu}
onEdgeMouseEnter={props.onEdgeMouseEnter}
onEdgeMouseMove={props.onEdgeMouseMove}
onEdgeMouseLeave={props.onEdgeMouseLeave}
edgeUpdaterRadius={props.edgeUpdaterRadius}
onEdgeDoubleClick={props.onEdgeDoubleClick}
onEdgeUpdateStart={props.onEdgeUpdateStart}
onEdgeUpdateEnd={props.onEdgeUpdateEnd}
onEdgeUpdate={props.onEdgeUpdate}
edgeTypes={props.edgeTypes}
connectionMode={connectionMode}
/>
);
})}
{renderConnectionLine && (
<ConnectionLine
connectionNodeId={connectionNodeId!}
+10 -22
View File
@@ -14,6 +14,7 @@ import {
HandleElement,
Transform,
Edge,
Rect,
} from '../../types';
export function createEdgeTypes(edgeTypes: EdgeTypesType): EdgeTypesType {
@@ -39,11 +40,11 @@ export function createEdgeTypes(edgeTypes: EdgeTypesType): EdgeTypesType {
};
}
export function getHandlePosition(position: Position, node: Node, handle: any | null = null): XYPosition {
const x = (handle?.x || 0) + node.position.x;
const y = (handle?.y || 0) + node.position.y;
const width = handle?.width || node.__rf.width;
const height = handle?.height || node.__rf.height;
export function getHandlePosition(position: Position, nodeRect: Rect, handle: any | null = null): XYPosition {
const x = (handle?.x || 0) + nodeRect.x;
const y = (handle?.y || 0) + nodeRect.y;
const width = handle?.width || nodeRect.width;
const height = handle?.height || nodeRect.height;
switch (position) {
case Position.Top:
@@ -94,15 +95,15 @@ interface EdgePositions {
}
export const getEdgePositions = (
sourceNode: Node,
sourceNodeRect: Rect,
sourceHandle: HandleElement | unknown,
sourcePosition: Position,
targetNode: Node,
targetNodeRect: Rect,
targetHandle: HandleElement | unknown,
targetPosition: Position
): EdgePositions => {
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNode, sourceHandle);
const targetHandlePos = getHandlePosition(targetPosition, targetNode, targetHandle);
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNodeRect, sourceHandle);
const targetHandlePos = getHandlePosition(targetPosition, targetNodeRect, targetHandle);
return {
sourceX: sourceHandlePos.x,
@@ -169,16 +170,3 @@ export const getSourceTargetNodes = (edge: Edge, nodes: Node[]): SourceTargetNod
{ sourceNode: null, targetNode: null } as SourceTargetNode
);
};
export const extendEdgeWithSourceAndTarget = (edge: Edge, nodes: Node[]): Edge => {
const { sourceNode, targetNode } = getSourceTargetNodes(edge, nodes);
if (sourceNode) {
edge.sourceNode = sourceNode;
}
if (targetNode) {
edge.targetNode = targetNode;
}
return edge;
};
+2 -2
View File
@@ -22,6 +22,8 @@ interface FlowRendererProps
| 'arrowHeadColor'
| 'onlyRenderVisibleElements'
| 'selectNodesOnDrag'
| 'nodes'
| 'edges'
> {
children: ReactNode;
}
@@ -54,7 +56,6 @@ const FlowRenderer = ({
paneMoveable,
defaultPosition,
defaultZoom,
translateExtent,
preventScrolling,
onSelectionDragStart,
onSelectionDrag,
@@ -106,7 +107,6 @@ const FlowRenderer = ({
paneMoveable={paneMoveable}
defaultPosition={defaultPosition}
defaultZoom={defaultZoom}
translateExtent={translateExtent}
zoomActivationKeyCode={zoomActivationKeyCode}
preventScrolling={preventScrolling}
>
+12 -163
View File
@@ -1,7 +1,6 @@
import React, { useEffect, useRef, memo } from 'react';
import shallow from 'zustand/shallow';
import { useStore, useStoreApi } from '../../store';
import { useStoreApi } from '../../store';
import FlowRenderer from '../FlowRenderer';
import NodeRenderer from '../NodeRenderer';
import EdgeRenderer from '../EdgeRenderer';
@@ -10,17 +9,15 @@ import useZoomPanHelper from '../../hooks/useZoomPanHelper';
import { ReactFlowProps } from '../ReactFlow';
import { NodeTypesType, EdgeTypesType, ConnectionLineType, KeyCode, ReactFlowState } from '../../types';
import { NodeTypesType, EdgeTypesType, ConnectionLineType, KeyCode } from '../../types';
export interface GraphViewProps extends Omit<ReactFlowProps, 'onSelectionChange' | 'nodes' | 'edges'> {
export interface GraphViewProps extends Omit<ReactFlowProps, 'onSelectionChange'> {
nodeTypes: NodeTypesType;
edgeTypes: EdgeTypesType;
selectionKeyCode: KeyCode;
deleteKeyCode: KeyCode;
multiSelectionKeyCode: KeyCode;
connectionLineType: ConnectionLineType;
snapToGrid: boolean;
snapGrid: [number, number];
onlyRenderVisibleElements: boolean;
defaultZoom: number;
defaultPosition: [number, number];
@@ -28,33 +25,17 @@ export interface GraphViewProps extends Omit<ReactFlowProps, 'onSelectionChange'
selectNodesOnDrag: boolean;
}
const selector = (s: ReactFlowState) => ({
setOnConnect: s.setOnConnect,
setOnConnectStart: s.setOnConnectStart,
setOnConnectStop: s.setOnConnectStop,
setOnConnectEnd: s.setOnConnectEnd,
setSnapGrid: s.setSnapGrid,
setSnapToGrid: s.setSnapToGrid,
setNodesDraggable: s.setNodesDraggable,
setNodesConnectable: s.setNodesConnectable,
setElementsSelectable: s.setElementsSelectable,
setMinZoom: s.setMinZoom,
setMaxZoom: s.setMaxZoom,
setTranslateExtent: s.setTranslateExtent,
setNodeExtent: s.setNodeExtent,
setConnectionMode: s.setConnectionMode,
setOnNodesChange: s.setOnNodesChange,
setOnEdgesChange: s.setOnEdgesChange,
});
const GraphView = ({
nodes,
edges,
nodeTypes,
edgeTypes,
onMove,
onMoveStart,
onMoveEnd,
onLoad,
onElementClick,
onNodeClick,
onEdgeClick,
onNodeDoubleClick,
onEdgeDoubleClick,
onNodeMouseEnter,
@@ -68,7 +49,6 @@ const GraphView = ({
onSelectionDrag,
onSelectionDragStop,
onSelectionContextMenu,
connectionMode,
connectionLineType,
connectionLineStyle,
connectionLineComponent,
@@ -76,24 +56,12 @@ const GraphView = ({
multiSelectionKeyCode,
zoomActivationKeyCode,
deleteKeyCode,
onConnect,
onConnectStart,
onConnectStop,
onConnectEnd,
snapToGrid,
snapGrid,
onlyRenderVisibleElements,
nodesDraggable,
nodesConnectable,
elementsSelectable,
selectNodesOnDrag,
minZoom,
maxZoom,
defaultZoom,
defaultPosition,
translateExtent,
preventScrolling,
nodeExtent,
arrowHeadColor,
markerEndId,
zoomOnScroll,
@@ -114,31 +82,9 @@ const GraphView = ({
edgeUpdaterRadius,
onEdgeUpdateStart,
onEdgeUpdateEnd,
onNodesChange,
onEdgesChange,
}: GraphViewProps) => {
const isInitialized = useRef<boolean>(false);
const store = useStoreApi();
const {
setOnConnect,
setOnConnectStart,
setOnConnectStop,
setOnConnectEnd,
setSnapGrid,
setSnapToGrid,
setNodesDraggable,
setNodesConnectable,
setElementsSelectable,
setMinZoom,
setMaxZoom,
setTranslateExtent,
setNodeExtent,
setConnectionMode,
setOnNodesChange,
setOnEdgesChange,
} = useStore(selector, shallow);
const { zoomIn, zoomOut, zoomTo, transform, fitView, initialized } = useZoomPanHelper();
useEffect(() => {
@@ -160,102 +106,6 @@ const GraphView = ({
}
}, [onLoad, zoomIn, zoomOut, zoomTo, transform, fitView, initialized]);
useEffect(() => {
if (onConnect) {
setOnConnect(onConnect);
}
}, [onConnect]);
useEffect(() => {
if (onConnectStart) {
setOnConnectStart(onConnectStart);
}
}, [onConnectStart]);
useEffect(() => {
if (onConnectStop) {
setOnConnectStop(onConnectStop);
}
}, [onConnectStop]);
useEffect(() => {
if (onConnectEnd) {
setOnConnectEnd(onConnectEnd);
}
}, [onConnectEnd]);
useEffect(() => {
if (typeof snapToGrid !== 'undefined') {
setSnapToGrid(snapToGrid);
}
}, [snapToGrid]);
useEffect(() => {
if (typeof snapGrid !== 'undefined') {
setSnapGrid(snapGrid);
}
}, [snapGrid]);
useEffect(() => {
if (typeof nodesDraggable !== 'undefined') {
setNodesDraggable(nodesDraggable);
}
}, [nodesDraggable]);
useEffect(() => {
if (typeof nodesConnectable !== 'undefined') {
setNodesConnectable(nodesConnectable);
}
}, [nodesConnectable]);
useEffect(() => {
if (typeof elementsSelectable !== 'undefined') {
setElementsSelectable(elementsSelectable);
}
}, [elementsSelectable]);
useEffect(() => {
if (typeof minZoom !== 'undefined') {
setMinZoom(minZoom);
}
}, [minZoom]);
useEffect(() => {
if (typeof maxZoom !== 'undefined') {
setMaxZoom(maxZoom);
}
}, [maxZoom]);
useEffect(() => {
if (typeof translateExtent !== 'undefined') {
setTranslateExtent(translateExtent);
}
}, [translateExtent]);
useEffect(() => {
if (typeof nodeExtent !== 'undefined') {
setNodeExtent(nodeExtent);
}
}, [nodeExtent]);
useEffect(() => {
if (typeof connectionMode !== 'undefined') {
setConnectionMode(connectionMode);
}
}, [connectionMode]);
useEffect(() => {
if (typeof onNodesChange !== 'undefined') {
setOnNodesChange(onNodesChange);
}
}, [onNodesChange]);
useEffect(() => {
if (typeof onEdgesChange !== 'undefined') {
setOnEdgesChange(onEdgesChange);
}
}, [onEdgesChange]);
return (
<FlowRenderer
onPaneClick={onPaneClick}
@@ -278,7 +128,6 @@ const GraphView = ({
paneMoveable={paneMoveable}
defaultPosition={defaultPosition}
defaultZoom={defaultZoom}
translateExtent={translateExtent}
onSelectionDragStart={onSelectionDragStart}
onSelectionDrag={onSelectionDrag}
onSelectionDragStop={onSelectionDragStop}
@@ -287,7 +136,7 @@ const GraphView = ({
>
<NodeRenderer
nodeTypes={nodeTypes}
onElementClick={onElementClick}
onNodeClick={onNodeClick}
onNodeDoubleClick={onNodeDoubleClick}
onNodeMouseEnter={onNodeMouseEnter}
onNodeMouseMove={onNodeMouseMove}
@@ -297,18 +146,16 @@ const GraphView = ({
onNodeDrag={onNodeDrag}
onNodeDragStart={onNodeDragStart}
selectNodesOnDrag={selectNodesOnDrag}
snapToGrid={snapToGrid}
snapGrid={snapGrid}
onlyRenderVisibleElements={onlyRenderVisibleElements}
nodes={nodes}
/>
<EdgeRenderer
edgeTypes={edgeTypes}
onElementClick={onElementClick}
onEdgeClick={onEdgeClick}
onEdgeDoubleClick={onEdgeDoubleClick}
connectionLineType={connectionLineType}
connectionLineStyle={connectionLineStyle}
connectionLineComponent={connectionLineComponent}
connectionMode={connectionMode}
arrowHeadColor={arrowHeadColor}
markerEndId={markerEndId}
onEdgeUpdate={onEdgeUpdate}
@@ -320,6 +167,8 @@ const GraphView = ({
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
edgeUpdaterRadius={edgeUpdaterRadius}
nodes={nodes}
edges={edges}
/>
</FlowRenderer>
);
+25 -15
View File
@@ -2,11 +2,11 @@ import React, { memo, useMemo, ComponentType, MouseEvent } from 'react';
import shallow from 'zustand/shallow';
import { useStore } from '../../store';
import { Node, NodeTypesType, ReactFlowState, Edge, WrapNodeProps } from '../../types';
import { Node, NodeTypesType, ReactFlowState, WrapNodeProps } from '../../types';
interface NodeRendererProps {
nodeTypes: NodeTypesType;
selectNodesOnDrag: boolean;
onElementClick?: (event: MouseEvent, element: Node | Edge) => void;
onNodeClick?: (event: MouseEvent, element: Node) => void;
onNodeDoubleClick?: (event: MouseEvent, element: Node) => void;
onNodeMouseEnter?: (event: MouseEvent, node: Node) => void;
onNodeMouseMove?: (event: MouseEvent, node: Node) => void;
@@ -15,9 +15,8 @@ interface NodeRendererProps {
onNodeDragStart?: (event: MouseEvent, node: Node) => void;
onNodeDrag?: (event: MouseEvent, node: Node) => void;
onNodeDragStop?: (event: MouseEvent, node: Node) => void;
snapToGrid: boolean;
snapGrid: [number, number];
onlyRenderVisibleElements: boolean;
nodes: Node[];
}
const selector = (s: ReactFlowState) => ({
@@ -25,15 +24,21 @@ const selector = (s: ReactFlowState) => ({
nodesDraggable: s.nodesDraggable,
nodesConnectable: s.nodesConnectable,
elementsSelectable: s.elementsSelectable,
nodes: s.nodes,
updateNodeDimensions: s.updateNodeDimensions,
snapGrid: s.snapGrid,
snapToGrid: s.snapToGrid,
});
const NodeRenderer = (props: NodeRendererProps) => {
const { transform, nodesDraggable, nodesConnectable, elementsSelectable, nodes, updateNodeDimensions } = useStore(
selector,
shallow
);
const {
transform,
nodesDraggable,
nodesConnectable,
elementsSelectable,
updateNodeDimensions,
snapGrid,
snapToGrid,
} = useStore(selector, shallow);
// const visibleNodes = props.onlyRenderVisibleElements
// ? getNodesInside(nodes, { x: 0, y: 0, width, height }, transform, true)
@@ -63,7 +68,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
return (
<div className="react-flow__nodes" style={transformStyle}>
{nodes.map((node) => {
{props.nodes.map((node) => {
const nodeType = node.type || 'default';
const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType<WrapNodeProps>;
@@ -74,6 +79,11 @@ const NodeRenderer = (props: NodeRendererProps) => {
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 (
<NodeComponent
@@ -89,11 +99,11 @@ const NodeRenderer = (props: NodeRendererProps) => {
xPos={node.position.x}
yPos={node.position.y}
isDragging={node.isDragging}
isInitialized={node.width !== null && node.height !== null}
snapGrid={props.snapGrid}
snapToGrid={props.snapToGrid}
isInitialized={isInitialized}
snapGrid={snapGrid}
snapToGrid={snapToGrid}
selectNodesOnDrag={props.selectNodesOnDrag}
onClick={props.onElementClick}
onClick={props.onNodeClick}
onMouseEnter={props.onNodeMouseEnter}
onMouseMove={props.onNodeMouseMove}
onMouseLeave={props.onNodeMouseLeave}
@@ -103,7 +113,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
onNodeDrag={props.onNodeDrag}
onNodeDragStop={props.onNodeDragStop}
scale={transform[2]}
selected={!!node.selected}
isSelected={!!node.isSelected}
isDraggable={isDraggable}
isSelectable={isSelectable}
isConnectable={isConnectable}
+12 -15
View File
@@ -1,24 +1,21 @@
import React, { FC } from 'react';
import { Provider, createStore } from '../../store';
// import { ReactFlowState } from '../../types';
// const reactFlowVersionSelector = (s: ReactFlowState) => s.reactFlowVersion;
import { Provider, createStore, useStoreApi } from '../../store';
const Wrapper: FC = ({ children }) => {
// let isWrapped = useRef<boolean>(true);
let isWrapped = true;
// try {
// useStoreApi();
// } catch {
// isWrapped.current = false;
// }
try {
useStoreApi();
} catch (e) {
isWrapped = false;
}
// if (isWrapped) {
// // we need to wrap it with a fragment because it's not allowed for children to be a ReactNode
// // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051
// return <>{children}</>;
// }
if (isWrapped) {
// we need to wrap it with a fragment because it's not allowed for children to be a ReactNode
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051
return <>{children}</>;
}
return <Provider createStore={createStore}>{children}</Provider>;
};
+34 -28
View File
@@ -9,7 +9,7 @@ import React, {
import cc from 'classcat';
import GraphView from '../GraphView';
import ElementUpdater from '../../components/ElementUpdater';
import StoreUpdater from '../../components/StoreUpdater';
import DefaultNode from '../../components/Nodes/DefaultNode';
import InputNode from '../../components/Nodes/InputNode';
import OutputNode from '../../components/Nodes/OutputNode';
@@ -38,7 +38,8 @@ import {
PanOnScrollMode,
OnEdgeUpdateFunc,
NodeExtent,
ElementChange,
NodeChange,
EdgeChange,
} from '../../types';
import '../../style.css';
@@ -60,9 +61,10 @@ const defaultEdgeTypes = {
export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onLoad'> {
nodes: Node[];
edges: Edge[];
onNodesChange?: (nodeChanges: ElementChange[]) => void;
onEdgesChange?: (edgeChanges: ElementChange[]) => void;
onElementClick?: (event: ReactMouseEvent, element: Node | Edge) => void;
onNodesChange?: (nodeChanges: NodeChange[]) => void;
onEdgesChange?: (edgeChanges: EdgeChange[]) => void;
onNodeClick?: (event: React.MouseEvent, node: Node) => void;
onEdgeClick?: (event: React.MouseEvent, node: Edge) => void;
onNodeDoubleClick?: (event: ReactMouseEvent, node: Node) => void;
onNodeMouseEnter?: (event: ReactMouseEvent, node: Node) => void;
onNodeMouseMove?: (event: ReactMouseEvent, node: Node) => void;
@@ -129,8 +131,6 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
onEdgeUpdateStart?: (event: ReactMouseEvent, edge: Edge) => void;
onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge) => void;
edgeUpdaterRadius?: number;
nodeTypesId?: string;
edgeTypesId?: string;
}
export type ReactFlowRefType = HTMLDivElement;
@@ -146,7 +146,8 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
className,
nodeTypes = defaultNodeTypes,
edgeTypes = defaultEdgeTypes,
onElementClick,
onNodeClick,
onEdgeClick,
onLoad,
onMove,
onMoveStart,
@@ -212,27 +213,28 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
onEdgeUpdateStart,
onEdgeUpdateEnd,
edgeUpdaterRadius = 10,
nodeTypesId = '1',
edgeTypesId = '1',
onNodesChange,
onEdgesChange,
...rest
},
ref
) => {
const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), [nodeTypesId]);
const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), [edgeTypesId]);
const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), [nodeTypes]);
const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), [edgeTypes]);
const reactFlowClasses = cc(['react-flow', className]);
return (
<div {...rest} ref={ref} className={reactFlowClasses}>
<Wrapper>
<GraphView
nodes={nodes}
edges={edges}
onLoad={onLoad}
onMove={onMove}
onMoveStart={onMoveStart}
onMoveEnd={onMoveEnd}
onElementClick={onElementClick}
onNodeClick={onNodeClick}
onEdgeClick={onEdgeClick}
onNodeMouseEnter={onNodeMouseEnter}
onNodeMouseMove={onNodeMouseMove}
onNodeMouseLeave={onNodeMouseLeave}
@@ -243,7 +245,6 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
onNodeDragStop={onNodeDragStop}
nodeTypes={nodeTypesParsed}
edgeTypes={edgeTypesParsed}
connectionMode={connectionMode}
connectionLineType={connectionLineType}
connectionLineStyle={connectionLineStyle}
connectionLineComponent={connectionLineComponent}
@@ -251,24 +252,11 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
deleteKeyCode={deleteKeyCode}
multiSelectionKeyCode={multiSelectionKeyCode}
zoomActivationKeyCode={zoomActivationKeyCode}
onConnect={onConnect}
onConnectStart={onConnectStart}
onConnectStop={onConnectStop}
onConnectEnd={onConnectEnd}
snapToGrid={snapToGrid}
snapGrid={snapGrid}
onlyRenderVisibleElements={onlyRenderVisibleElements}
nodesDraggable={nodesDraggable}
nodesConnectable={nodesConnectable}
elementsSelectable={elementsSelectable}
selectNodesOnDrag={selectNodesOnDrag}
minZoom={minZoom}
maxZoom={maxZoom}
defaultZoom={defaultZoom}
defaultPosition={defaultPosition}
translateExtent={translateExtent}
preventScrolling={preventScrolling}
nodeExtent={nodeExtent}
arrowHeadColor={arrowHeadColor}
markerEndId={markerEndId}
zoomOnScroll={zoomOnScroll}
@@ -294,10 +282,28 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
edgeUpdaterRadius={edgeUpdaterRadius}
/>
<StoreUpdater
nodes={nodes}
edges={edges}
onConnect={onConnect}
onConnectStart={onConnectStart}
onConnectStop={onConnectStop}
onConnectEnd={onConnectEnd}
nodesDraggable={nodesDraggable}
nodesConnectable={nodesConnectable}
elementsSelectable={elementsSelectable}
minZoom={minZoom}
maxZoom={maxZoom}
nodeExtent={nodeExtent}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
snapToGrid={snapToGrid}
snapGrid={snapGrid}
connectionMode={connectionMode}
translateExtent={translateExtent}
/>
<ElementUpdater nodes={nodes} edges={edges} />
{onSelectionChange && <SelectionListener onSelectionChange={onSelectionChange} />}
{children}
</Wrapper>
+6 -9
View File
@@ -7,7 +7,7 @@ import { clamp } from '../../utils';
import useKeyPress from '../../hooks/useKeyPress';
import useResizeHandler from '../../hooks/useResizeHandler';
import { useStore, useStoreApi } from '../../store';
import { FlowTransform, TranslateExtent, PanOnScrollMode, KeyCode, ReactFlowState } from '../../types';
import { FlowTransform, PanOnScrollMode, KeyCode, ReactFlowState } from '../../types';
interface ZoomPaneProps {
selectionKeyPressed: boolean;
@@ -21,7 +21,6 @@ interface ZoomPaneProps {
paneMoveable?: boolean;
defaultPosition?: [number, number];
defaultZoom?: number;
translateExtent?: TranslateExtent;
onMove?: (flowTransform?: FlowTransform) => void;
onMoveStart?: (flowTransform?: FlowTransform) => void;
onMoveEnd?: (flowTransform?: FlowTransform) => void;
@@ -67,7 +66,6 @@ const ZoomPane = ({
paneMoveable = true,
defaultPosition = [0, 0],
defaultZoom = 1,
translateExtent,
zoomActivationKeyCode,
preventScrolling = true,
children,
@@ -82,14 +80,13 @@ const ZoomPane = ({
useEffect(() => {
if (zoomPane.current) {
const state = store.getState();
const currentTranslateExtent = typeof translateExtent !== 'undefined' ? translateExtent : state.translateExtent;
const d3ZoomInstance = zoom().scaleExtent([state.minZoom, state.maxZoom]).translateExtent(currentTranslateExtent);
const { minZoom, maxZoom, translateExtent } = store.getState();
const d3ZoomInstance = zoom().scaleExtent([minZoom, maxZoom]).translateExtent(translateExtent);
const selection = select(zoomPane.current as Element).call(d3ZoomInstance);
const clampedX = clamp(defaultPosition[0], currentTranslateExtent[0][0], currentTranslateExtent[1][0]);
const clampedY = clamp(defaultPosition[1], currentTranslateExtent[0][1], currentTranslateExtent[1][1]);
const clampedZoom = clamp(defaultZoom, state.minZoom, state.maxZoom);
const clampedX = clamp(defaultPosition[0], translateExtent[0][0], translateExtent[1][0]);
const clampedY = clamp(defaultPosition[1], translateExtent[0][1], translateExtent[1][1]);
const clampedZoom = clamp(defaultZoom, minZoom, maxZoom);
const updatedTransform = zoomIdentity.translate(clampedX, clampedY).scale(clampedZoom);
d3ZoomInstance.transform(selection, updatedTransform);