@@ -51,16 +51,16 @@ describe('Basic Flow Rendering', () => {
|
||||
.type('{shift}', { release: false })
|
||||
.wait(50)
|
||||
.get('.react-flow__pane')
|
||||
.trigger('mousedown', 1000, 50, { button: 0, force: true })
|
||||
.trigger('mousemove', 1, 400, { button: 0 })
|
||||
.trigger('mousedown', 1, 10, { button: 0, force: true })
|
||||
.trigger('mousemove', 1000, 450, { button: 0 })
|
||||
.wait(50)
|
||||
.trigger('mouseup', 1, 200, { force: true });
|
||||
.trigger('mouseup', 1000, 450, { button: 0, force: true });
|
||||
|
||||
cy.wait(100);
|
||||
cy.wait(200);
|
||||
|
||||
cy.get('.react-flow__node').eq(1).should('have.class', 'selected');
|
||||
|
||||
cy.get('.react-flow__node').eq(0).should('have.not.class', 'selected');
|
||||
cy.get('.react-flow__node').eq(3).should('have.not.class', 'selected');
|
||||
|
||||
cy.get('.react-flow__nodesselection-rect');
|
||||
|
||||
@@ -74,11 +74,13 @@ describe('Basic Flow Rendering', () => {
|
||||
.trigger('mousedown', 'topRight', { button: 0, force: true })
|
||||
.trigger('mousemove', 'bottomLeft', { button: 0 })
|
||||
.wait(50)
|
||||
.trigger('mouseup', 'bottomLeft', { force: true })
|
||||
.wait(50)
|
||||
.trigger('mouseup', 'bottomLeft', { button: 0, force: true })
|
||||
.wait(400)
|
||||
.get('.react-flow__node')
|
||||
.should('have.class', 'selected')
|
||||
.get('.react-flow__nodesselection-rect');
|
||||
.should('have.class', 'selected');
|
||||
|
||||
cy.wait(200);
|
||||
cy.get('.react-flow__nodesselection-rect');
|
||||
|
||||
cy.get('body').type('{shift}', { release: true });
|
||||
});
|
||||
@@ -113,14 +115,14 @@ describe('Basic Flow Rendering', () => {
|
||||
cy.get('.react-flow__node')
|
||||
.contains('Node 3')
|
||||
.find('.react-flow__handle.source')
|
||||
.trigger('mousedown', { button: 0 });
|
||||
.trigger('mousedown', { force: true, button: 0 });
|
||||
|
||||
cy.get('.react-flow__node')
|
||||
.contains('Node 4')
|
||||
.find('.react-flow__handle.target')
|
||||
.trigger('mousemove', { force: true })
|
||||
.wait(50)
|
||||
.trigger('mouseup', { force: true });
|
||||
.trigger('mousemove', { force: true, button: 0 })
|
||||
.wait(200)
|
||||
.trigger('mouseup', { force: true, button: 0 });
|
||||
|
||||
cy.get('.react-flow__edge').should('have.length', 3);
|
||||
});
|
||||
|
||||
@@ -48,8 +48,6 @@ const initialEdges: Edge[] = [
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
];
|
||||
|
||||
const nodeOrigin: NodeOrigin = [0.5, 0.5];
|
||||
|
||||
const defaultEdgeOptions = {};
|
||||
|
||||
const BasicFlow = () => {
|
||||
@@ -96,7 +94,6 @@ const BasicFlow = () => {
|
||||
selectNodesOnDrag={false}
|
||||
elevateEdgesOnSelect
|
||||
elevateNodesOnSelect={false}
|
||||
// nodeOrigin={nodeOrigin}
|
||||
>
|
||||
<Background variant={BackgroundVariant.Dots} />
|
||||
<MiniMap />
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React, { FC } from 'react';
|
||||
import { ConnectionLineComponentProps } from 'reactflow';
|
||||
|
||||
const ConnectionLine: FC<ConnectionLineComponentProps> = ({ fromX, fromY, toX, toY }) => {
|
||||
function ConnectionLine({ fromX, fromY, toX, toY }: ConnectionLineComponentProps) {
|
||||
return (
|
||||
<g>
|
||||
<>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#222"
|
||||
@@ -12,8 +11,8 @@ const ConnectionLine: FC<ConnectionLineComponentProps> = ({ fromX, fromY, toX, t
|
||||
d={`M${fromX},${fromY} C ${fromX} ${toY} ${fromX} ${toY} ${toX},${toY}`}
|
||||
/>
|
||||
<circle cx={toX} cy={toY} fill="#fff" r={3} stroke="#222" strokeWidth={1.5} />
|
||||
</g>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default ConnectionLine;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MouseEvent, useCallback } from 'react';
|
||||
import ReactFlow, { addEdge, Node, Connection, Edge, useNodesState, useEdgesState, NodeOrigin } from 'reactflow';
|
||||
import ReactFlow, { addEdge, Node, Connection, Edge, useNodesState, useEdgesState } from 'reactflow';
|
||||
|
||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
@@ -100,7 +100,6 @@ const BasicFlow = () => {
|
||||
onNodeClick={onNodeClick}
|
||||
onConnect={onConnect}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
disableKeyboardA11y
|
||||
onNodeDrag={onNodeDrag}
|
||||
>
|
||||
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { MouseEvent, useCallback } from 'react';
|
||||
import { MouseEvent, useCallback } from 'react';
|
||||
import ReactFlow, {
|
||||
useReactFlow,
|
||||
NodeTypes,
|
||||
@@ -184,6 +184,7 @@ const UpdateNodeInternalsFlow = () => {
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
|
||||
const onConnect = (params: Edge | Connection) => setEdges((els) => addEdge(params, els));
|
||||
|
||||
const { project } = useReactFlow();
|
||||
const onEdgeUpdate = (oldEdge: Edge, newConnection: Connection) =>
|
||||
setEdges((els) => updateEdge(oldEdge, newConnection, els));
|
||||
|
||||
@@ -6,16 +6,15 @@ import { getBezierPath } from '../Edges/BezierEdge';
|
||||
import { getSmoothStepPath } from '../Edges/SmoothStepEdge';
|
||||
import { getSimpleBezierPath } from '../Edges/SimpleBezierEdge';
|
||||
import { internalsSymbol } from '../../utils';
|
||||
import type { ConnectionLineComponent, HandleType, ReactFlowStore } from '../../types';
|
||||
import type { ConnectionLineComponent, HandleType, ReactFlowState, ReactFlowStore } from '../../types';
|
||||
import { Position, ConnectionLineType, ConnectionMode } from '../../types';
|
||||
|
||||
type ConnectionLineProps = {
|
||||
connectionNodeId: string;
|
||||
connectionHandleType: HandleType;
|
||||
connectionLineType: ConnectionLineType;
|
||||
isConnectable: boolean;
|
||||
connectionLineStyle?: CSSProperties;
|
||||
CustomConnectionLineComponent?: ConnectionLineComponent;
|
||||
nodeId: string;
|
||||
handleType: HandleType;
|
||||
type: ConnectionLineType;
|
||||
style?: CSSProperties;
|
||||
CustomComponent?: ConnectionLineComponent;
|
||||
};
|
||||
|
||||
const oppositePosition = {
|
||||
@@ -26,69 +25,62 @@ const oppositePosition = {
|
||||
};
|
||||
|
||||
const ConnectionLine = ({
|
||||
connectionNodeId,
|
||||
connectionHandleType,
|
||||
connectionLineStyle,
|
||||
connectionLineType = ConnectionLineType.Bezier,
|
||||
isConnectable,
|
||||
CustomConnectionLineComponent,
|
||||
nodeId,
|
||||
handleType,
|
||||
style,
|
||||
type = ConnectionLineType.Bezier,
|
||||
CustomComponent,
|
||||
}: ConnectionLineProps) => {
|
||||
const { fromNode, handleId, toX, toY, connectionMode } = useStore(
|
||||
useCallback(
|
||||
(s: ReactFlowStore) => ({
|
||||
fromNode: s.nodeInternals.get(connectionNodeId),
|
||||
fromNode: s.nodeInternals.get(nodeId),
|
||||
handleId: s.connectionHandleId,
|
||||
toX: (s.connectionPosition.x - s.transform[0]) / s.transform[2],
|
||||
toY: (s.connectionPosition.y - s.transform[1]) / s.transform[2],
|
||||
connectionMode: s.connectionMode,
|
||||
}),
|
||||
[connectionNodeId]
|
||||
[nodeId]
|
||||
),
|
||||
shallow
|
||||
);
|
||||
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
|
||||
let handleBounds = fromHandleBounds?.[connectionHandleType];
|
||||
let handleBounds = fromHandleBounds?.[handleType];
|
||||
|
||||
if (connectionMode === ConnectionMode.Loose) {
|
||||
handleBounds = handleBounds
|
||||
? handleBounds
|
||||
: fromHandleBounds?.[connectionHandleType === 'source' ? 'target' : 'source'];
|
||||
handleBounds = handleBounds ? handleBounds : fromHandleBounds?.[handleType === 'source' ? 'target' : 'source'];
|
||||
}
|
||||
|
||||
if (!fromNode || !isConnectable || !handleBounds) {
|
||||
if (!fromNode || !handleBounds) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fromHandle = handleId ? handleBounds.find((d) => d.id === handleId) : handleBounds[0];
|
||||
const fromHandleX = fromHandle ? fromHandle.x + fromHandle.width / 2 : (fromNode?.width ?? 0) / 2;
|
||||
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode?.height ?? 0;
|
||||
const fromX = (fromNode?.positionAbsolute?.x || 0) + fromHandleX;
|
||||
const fromY = (fromNode?.positionAbsolute?.y || 0) + fromHandleY;
|
||||
|
||||
const fromHandleX = fromHandle ? fromHandle.x + fromHandle.width / 2 : (fromNode.width ?? 0) / 2;
|
||||
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode.height ?? 0;
|
||||
const fromX = (fromNode.positionAbsolute?.x ?? 0) + fromHandleX;
|
||||
const fromY = (fromNode.positionAbsolute?.y ?? 0) + fromHandleY;
|
||||
const fromPosition = fromHandle?.position;
|
||||
const toPosition = fromPosition ? oppositePosition[fromPosition] : null;
|
||||
|
||||
if (!fromPosition) {
|
||||
if (!fromPosition || !toPosition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const toPosition: Position = oppositePosition[fromPosition];
|
||||
|
||||
if (CustomConnectionLineComponent) {
|
||||
if (CustomComponent) {
|
||||
return (
|
||||
<g className="react-flow__connection">
|
||||
<CustomConnectionLineComponent
|
||||
connectionLineType={connectionLineType}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
fromNode={fromNode}
|
||||
fromHandle={fromHandle}
|
||||
fromX={fromX}
|
||||
fromY={fromY}
|
||||
toX={toX}
|
||||
toY={toY}
|
||||
fromPosition={fromPosition}
|
||||
toPosition={toPosition}
|
||||
/>
|
||||
</g>
|
||||
<CustomComponent
|
||||
connectionLineType={type}
|
||||
connectionLineStyle={style}
|
||||
fromNode={fromNode}
|
||||
fromHandle={fromHandle}
|
||||
fromX={fromX}
|
||||
fromY={fromY}
|
||||
toX={toX}
|
||||
toY={toY}
|
||||
fromPosition={fromPosition}
|
||||
toPosition={toPosition}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -103,29 +95,62 @@ const ConnectionLine = ({
|
||||
targetPosition: toPosition,
|
||||
};
|
||||
|
||||
if (connectionLineType === ConnectionLineType.Bezier) {
|
||||
if (type === ConnectionLineType.Bezier) {
|
||||
// we assume the destination position is opposite to the source position
|
||||
[dAttr] = getBezierPath(pathParams);
|
||||
} else if (connectionLineType === ConnectionLineType.Step) {
|
||||
} else if (type === ConnectionLineType.Step) {
|
||||
[dAttr] = getSmoothStepPath({
|
||||
...pathParams,
|
||||
borderRadius: 0,
|
||||
});
|
||||
} else if (connectionLineType === ConnectionLineType.SmoothStep) {
|
||||
} else if (type === ConnectionLineType.SmoothStep) {
|
||||
[dAttr] = getSmoothStepPath(pathParams);
|
||||
} else if (connectionLineType === ConnectionLineType.SimpleBezier) {
|
||||
} else if (type === ConnectionLineType.SimpleBezier) {
|
||||
[dAttr] = getSimpleBezierPath(pathParams);
|
||||
} else {
|
||||
dAttr = `M${fromX},${fromY} ${toX},${toY}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<g className="react-flow__connection">
|
||||
<path d={dAttr} fill="none" className="react-flow__connection-path" style={connectionLineStyle} />
|
||||
</g>
|
||||
);
|
||||
return <path d={dAttr} fill="none" className="react-flow__connection-path" style={style} />;
|
||||
};
|
||||
|
||||
ConnectionLine.displayName = 'ConnectionLine';
|
||||
|
||||
export default ConnectionLine;
|
||||
type ConnectionLineWrapperProps = {
|
||||
type: ConnectionLineType;
|
||||
component?: ConnectionLineComponent;
|
||||
containerStyle?: CSSProperties;
|
||||
style?: CSSProperties;
|
||||
};
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
nodeId: s.connectionNodeId,
|
||||
handleType: s.connectionHandleType,
|
||||
nodesConnectable: s.nodesConnectable,
|
||||
width: s.width,
|
||||
height: s.height,
|
||||
});
|
||||
|
||||
function ConnectionLineWrapper({ containerStyle, style, type, component }: ConnectionLineWrapperProps) {
|
||||
const { nodeId, handleType, nodesConnectable, width, height } = useStore(selector, shallow);
|
||||
const isValid = !!(nodeId && handleType && width && nodesConnectable);
|
||||
|
||||
if (!isValid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<svg
|
||||
style={containerStyle}
|
||||
width={width}
|
||||
height={height}
|
||||
className="react-flow__edges react-flow__connectionline react-flow__container"
|
||||
>
|
||||
<g className="react-flow__connection">
|
||||
<ConnectionLine nodeId={nodeId} handleType={handleType} style={style} type={type} CustomComponent={component} />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConnectionLineWrapper;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { EdgeAnchor } from './EdgeAnchor';
|
||||
import { getMarkerId } from '../../utils/graph';
|
||||
import { getMouseHandler } from './utils';
|
||||
import { elementSelectionKeys } from '../../utils';
|
||||
import type { EdgeProps, WrapEdgeProps, Connection } from '../../types';
|
||||
import type { EdgeProps, WrapEdgeProps, Connection, HandleType } from '../../types';
|
||||
|
||||
export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||
const EdgeWrapper = ({
|
||||
@@ -115,7 +115,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||
getState: store.getState,
|
||||
setState: store.setState,
|
||||
isValidConnection,
|
||||
elementEdgeUpdaterType: handleType,
|
||||
edgeUpdaterType: handleType,
|
||||
onEdgeUpdateEnd: _onEdgeUpdateEnd,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5,11 +5,12 @@ import { shallow } from 'zustand/shallow';
|
||||
import { useStore, useStoreApi } from '../../hooks/useStore';
|
||||
import { useNodeId } from '../../contexts/NodeIdContext';
|
||||
import { handlePointerDown } from './handler';
|
||||
import { devWarn, getHostForElement, isMouseEvent } from '../../utils';
|
||||
import { getHostForElement, isMouseEvent } from '../../utils';
|
||||
import { addEdge } from '../../utils/graph';
|
||||
import { Position } from '../../types';
|
||||
import type { HandleProps, Connection, ReactFlowState } from '../../types';
|
||||
import { isValidHandle } from './utils';
|
||||
import { errorMessages } from '../../contants';
|
||||
|
||||
const alwaysValid = () => true;
|
||||
|
||||
@@ -42,7 +43,8 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
||||
const nodeId = useNodeId();
|
||||
|
||||
if (!nodeId) {
|
||||
devWarn('Handle: No node id found. Make sure to only use a Handle inside a custom Node.');
|
||||
store.getState().onError?.('010', errorMessages['010']());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ export function isValidHandle(
|
||||
const isValid =
|
||||
connectionMode === ConnectionMode.Strict
|
||||
? (isTarget && handleIsSource) || (!isTarget && handleIsTarget)
|
||||
: handleNodeId !== handle.nodeId || handleId !== handle.id;
|
||||
: handleNodeId !== fromNodeId || handleId !== fromHandleId;
|
||||
|
||||
if (isValid) {
|
||||
result.isValid = isValidConnection(connection);
|
||||
|
||||
@@ -47,6 +47,7 @@ type StoreUpdaterProps = Pick<
|
||||
| 'elevateNodesOnSelect'
|
||||
| 'autoPanOnConnect'
|
||||
| 'autoPanOnNodeDrag'
|
||||
| 'onError'
|
||||
> & { rfId: string };
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
@@ -123,6 +124,7 @@ const StoreUpdater = ({
|
||||
rfId,
|
||||
autoPanOnConnect,
|
||||
autoPanOnNodeDrag,
|
||||
onError,
|
||||
}: StoreUpdaterProps) => {
|
||||
const {
|
||||
setNodes,
|
||||
@@ -178,6 +180,7 @@ const StoreUpdater = ({
|
||||
useDirectStoreUpdater('rfId', rfId, store.setState);
|
||||
useDirectStoreUpdater('autoPanOnConnect', autoPanOnConnect, store.setState);
|
||||
useDirectStoreUpdater('autoPanOnNodeDrag', autoPanOnNodeDrag, store.setState);
|
||||
useDirectStoreUpdater('onError', onError, store.setState);
|
||||
|
||||
useStoreUpdater<Node[]>(nodes, setNodes);
|
||||
useStoreUpdater<Edge[]>(edges, setEdges);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { devWarn } from '../../utils';
|
||||
import { MarkerType } from '../../types';
|
||||
import type { EdgeMarker } from '../../types';
|
||||
import { useStoreApi } from '../../hooks/useStore';
|
||||
import { errorMessages } from '../../contants';
|
||||
|
||||
type SymbolProps = Omit<EdgeMarker, 'type'>;
|
||||
|
||||
@@ -38,11 +39,12 @@ export const MarkerSymbols = {
|
||||
};
|
||||
|
||||
export function useMarkerSymbol(type: MarkerType) {
|
||||
const store = useStoreApi();
|
||||
const symbol = useMemo(() => {
|
||||
const symbolExists = Object.prototype.hasOwnProperty.call(MarkerSymbols, type);
|
||||
|
||||
if (!symbolExists) {
|
||||
devWarn(`Marker type "${type}" doesn't exist. Help: https://reactflow.dev/error#900`);
|
||||
store.getState().onError?.('009', errorMessages['009'](type));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,27 +1,20 @@
|
||||
import { memo } from 'react';
|
||||
import { memo, ReactNode } from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import cc from 'classcat';
|
||||
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
import useVisibleEdges from '../../hooks/useVisibleEdges';
|
||||
import ConnectionLine from '../../components/ConnectionLine/index';
|
||||
import MarkerDefinitions from './MarkerDefinitions';
|
||||
import { getEdgePositions, getHandle, getNodeData } from './utils';
|
||||
|
||||
import { GraphViewProps } from '../GraphView';
|
||||
import { devWarn } from '../../utils';
|
||||
import { ConnectionMode, Position } from '../../types';
|
||||
import type { Edge, ReactFlowState } from '../../types';
|
||||
import { errorMessages } from '../../contants';
|
||||
|
||||
type EdgeRendererProps = Pick<
|
||||
GraphViewProps,
|
||||
| 'edgeTypes'
|
||||
| 'connectionLineType'
|
||||
| 'connectionLineType'
|
||||
| 'connectionLineStyle'
|
||||
| 'connectionLineComponent'
|
||||
| 'connectionLineContainerStyle'
|
||||
| 'connectionLineContainerStyle'
|
||||
| 'onEdgeClick'
|
||||
| 'onEdgeDoubleClick'
|
||||
| 'defaultMarkerColor'
|
||||
@@ -40,11 +33,10 @@ type EdgeRendererProps = Pick<
|
||||
| 'disableKeyboardA11y'
|
||||
> & {
|
||||
elevateEdgesOnSelect: boolean;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
connectionNodeId: s.connectionNodeId,
|
||||
connectionHandleType: s.connectionHandleType,
|
||||
nodesConnectable: s.nodesConnectable,
|
||||
edgesFocusable: s.edgesFocusable,
|
||||
elementsSelectable: s.elementsSelectable,
|
||||
@@ -52,35 +44,38 @@ const selector = (s: ReactFlowState) => ({
|
||||
height: s.height,
|
||||
connectionMode: s.connectionMode,
|
||||
nodeInternals: s.nodeInternals,
|
||||
onError: s.onError,
|
||||
});
|
||||
|
||||
const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||
const {
|
||||
connectionNodeId,
|
||||
connectionHandleType,
|
||||
nodesConnectable,
|
||||
edgesFocusable,
|
||||
elementsSelectable,
|
||||
width,
|
||||
height,
|
||||
connectionMode,
|
||||
nodeInternals,
|
||||
} = useStore(selector, shallow);
|
||||
const edgeTree = useVisibleEdges(props.onlyRenderVisibleElements, nodeInternals, props.elevateEdgesOnSelect);
|
||||
const EdgeRenderer = ({
|
||||
defaultMarkerColor,
|
||||
onlyRenderVisibleElements,
|
||||
elevateEdgesOnSelect,
|
||||
rfId,
|
||||
edgeTypes,
|
||||
noPanClassName,
|
||||
onEdgeUpdate,
|
||||
onEdgeContextMenu,
|
||||
onEdgeMouseEnter,
|
||||
onEdgeMouseMove,
|
||||
onEdgeMouseLeave,
|
||||
onEdgeClick,
|
||||
edgeUpdaterRadius,
|
||||
onEdgeDoubleClick,
|
||||
onEdgeUpdateStart,
|
||||
onEdgeUpdateEnd,
|
||||
children,
|
||||
}: EdgeRendererProps) => {
|
||||
const { edgesFocusable, elementsSelectable, width, height, connectionMode, nodeInternals, onError } = useStore(
|
||||
selector,
|
||||
shallow
|
||||
);
|
||||
const edgeTree = useVisibleEdges(onlyRenderVisibleElements, nodeInternals, elevateEdgesOnSelect);
|
||||
|
||||
if (!width) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
connectionLineType,
|
||||
defaultMarkerColor,
|
||||
connectionLineStyle,
|
||||
connectionLineComponent,
|
||||
connectionLineContainerStyle,
|
||||
} = props;
|
||||
const renderConnectionLine = connectionNodeId && connectionHandleType;
|
||||
|
||||
return (
|
||||
<>
|
||||
{edgeTree.map(({ level, edges, isMaxLevel }) => (
|
||||
@@ -91,11 +86,11 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||
height={height}
|
||||
className="react-flow__edges react-flow__container"
|
||||
>
|
||||
{isMaxLevel && <MarkerDefinitions defaultColor={defaultMarkerColor} rfId={props.rfId} />}
|
||||
{isMaxLevel && <MarkerDefinitions defaultColor={defaultMarkerColor} rfId={rfId} />}
|
||||
<g>
|
||||
{edges.map((edge: Edge) => {
|
||||
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(nodeInternals.get(edge.source)!);
|
||||
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(nodeInternals.get(edge.target)!);
|
||||
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(nodeInternals.get(edge.source));
|
||||
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(nodeInternals.get(edge.target));
|
||||
|
||||
if (!sourceIsValid || !targetIsValid) {
|
||||
return null;
|
||||
@@ -103,32 +98,25 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||
|
||||
let edgeType = edge.type || 'default';
|
||||
|
||||
if (!props.edgeTypes[edgeType]) {
|
||||
devWarn(
|
||||
`Edge type "${edgeType}" not found. Using fallback type "default". Help: https://reactflow.dev/error#300`
|
||||
);
|
||||
|
||||
if (!edgeTypes[edgeType]) {
|
||||
onError?.('011', errorMessages['011'](edgeType));
|
||||
edgeType = 'default';
|
||||
}
|
||||
|
||||
const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default;
|
||||
// when connection type is loose we can define all handles as sources
|
||||
const EdgeComponent = edgeTypes[edgeType] || edgeTypes.default;
|
||||
// when connection type is loose we can define all handles as sources and connect source -> source
|
||||
const targetNodeHandles =
|
||||
connectionMode === ConnectionMode.Strict
|
||||
? targetHandleBounds!.target
|
||||
: (targetHandleBounds!.target ?? []).concat(targetHandleBounds!.source ?? []);
|
||||
const sourceHandle = getHandle(sourceHandleBounds!.source!, edge.sourceHandle || null);
|
||||
const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle || null);
|
||||
const sourceHandle = getHandle(sourceHandleBounds!.source!, edge.sourceHandle);
|
||||
const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle);
|
||||
const sourcePosition = sourceHandle?.position || Position.Bottom;
|
||||
const targetPosition = targetHandle?.position || Position.Top;
|
||||
const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined'));
|
||||
|
||||
if (!sourceHandle || !targetHandle) {
|
||||
devWarn(
|
||||
`Couldn't create edge for ${!sourceHandle ? 'source' : 'target'} handle id: ${
|
||||
!sourceHandle ? edge.sourceHandle : edge.targetHandle
|
||||
}; edge id: ${edge.id}. Help: https://reactflow.dev/error#800`
|
||||
);
|
||||
onError?.('008', errorMessages['008'](sourceHandle, edge));
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -146,7 +134,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||
<EdgeComponent
|
||||
key={edge.id}
|
||||
id={edge.id}
|
||||
className={cc([edge.className, props.noPanClassName])}
|
||||
className={cc([edge.className, noPanClassName])}
|
||||
type={edgeType}
|
||||
data={edge.data}
|
||||
selected={!!edge.selected}
|
||||
@@ -172,17 +160,17 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||
sourcePosition={sourcePosition}
|
||||
targetPosition={targetPosition}
|
||||
elementsSelectable={elementsSelectable}
|
||||
onEdgeUpdate={props.onEdgeUpdate}
|
||||
onContextMenu={props.onEdgeContextMenu}
|
||||
onMouseEnter={props.onEdgeMouseEnter}
|
||||
onMouseMove={props.onEdgeMouseMove}
|
||||
onMouseLeave={props.onEdgeMouseLeave}
|
||||
onClick={props.onEdgeClick}
|
||||
edgeUpdaterRadius={props.edgeUpdaterRadius}
|
||||
onEdgeDoubleClick={props.onEdgeDoubleClick}
|
||||
onEdgeUpdateStart={props.onEdgeUpdateStart}
|
||||
onEdgeUpdateEnd={props.onEdgeUpdateEnd}
|
||||
rfId={props.rfId}
|
||||
onEdgeUpdate={onEdgeUpdate}
|
||||
onContextMenu={onEdgeContextMenu}
|
||||
onMouseEnter={onEdgeMouseEnter}
|
||||
onMouseMove={onEdgeMouseMove}
|
||||
onMouseLeave={onEdgeMouseLeave}
|
||||
onClick={onEdgeClick}
|
||||
edgeUpdaterRadius={edgeUpdaterRadius}
|
||||
onEdgeDoubleClick={onEdgeDoubleClick}
|
||||
onEdgeUpdateStart={onEdgeUpdateStart}
|
||||
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
||||
rfId={rfId}
|
||||
ariaLabel={edge.ariaLabel}
|
||||
isFocusable={isFocusable}
|
||||
pathOptions={'pathOptions' in edge ? edge.pathOptions : undefined}
|
||||
@@ -193,23 +181,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||
</g>
|
||||
</svg>
|
||||
))}
|
||||
{renderConnectionLine && (
|
||||
<svg
|
||||
style={connectionLineContainerStyle}
|
||||
width={width}
|
||||
height={height}
|
||||
className="react-flow__edges react-flow__connectionline react-flow__container"
|
||||
>
|
||||
<ConnectionLine
|
||||
connectionNodeId={connectionNodeId!}
|
||||
connectionHandleType={connectionHandleType!}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
connectionLineType={connectionLineType}
|
||||
isConnectable={nodesConnectable}
|
||||
CustomConnectionLineComponent={connectionLineComponent}
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -72,21 +72,18 @@ export function getHandlePosition(position: Position, nodeRect: Rect, handle: Ha
|
||||
}
|
||||
}
|
||||
|
||||
export function getHandle(bounds: HandleElement[], handleId: string | null): HandleElement | null {
|
||||
export function getHandle(bounds: HandleElement[], handleId?: string | null): HandleElement | null {
|
||||
if (!bounds) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// there is no handleId when there are no multiple handles/ handles with ids
|
||||
// so we just pick the first one
|
||||
let handle: HandleElement | null = null;
|
||||
if (bounds.length === 1 || !handleId) {
|
||||
handle = bounds[0];
|
||||
} else if (handleId) {
|
||||
handle = bounds.find((d) => d.id === handleId)!;
|
||||
if (handleId) {
|
||||
return bounds.find((d) => d.id === handleId)!;
|
||||
} else if (bounds.length === 1) {
|
||||
return bounds[0];
|
||||
}
|
||||
|
||||
return typeof handle === 'undefined' ? null : handle;
|
||||
return null;
|
||||
}
|
||||
|
||||
interface EdgePositions {
|
||||
@@ -167,16 +164,15 @@ export function isEdgeVisible({
|
||||
return overlappingArea > 0;
|
||||
}
|
||||
|
||||
export function getNodeData(node: Node): [Rect, NodeHandleBounds | null, boolean] {
|
||||
export function getNodeData(node?: Node): [Rect, NodeHandleBounds | null, boolean] {
|
||||
const handleBounds = node?.[internalsSymbol]?.handleBounds || null;
|
||||
|
||||
const isInvalid =
|
||||
!node ||
|
||||
!handleBounds ||
|
||||
!node.width ||
|
||||
!node.height ||
|
||||
typeof node.positionAbsolute?.x === 'undefined' ||
|
||||
typeof node.positionAbsolute?.y === 'undefined';
|
||||
const isValid =
|
||||
handleBounds &&
|
||||
node?.width &&
|
||||
node?.height &&
|
||||
typeof node?.positionAbsolute?.x !== 'undefined' &&
|
||||
typeof node?.positionAbsolute?.y !== 'undefined';
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -186,6 +182,6 @@ export function getNodeData(node: Node): [Rect, NodeHandleBounds | null, boolean
|
||||
height: node?.height || 0,
|
||||
},
|
||||
handleBounds,
|
||||
!isInvalid,
|
||||
!!isValid,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import EdgeRenderer from '../EdgeRenderer';
|
||||
import ViewportWrapper from '../Viewport';
|
||||
import useOnInitHandler from '../../hooks/useOnInitHandler';
|
||||
import type { EdgeTypesWrapped, NodeTypesWrapped, ReactFlowProps } from '../../types';
|
||||
import ConnectionLine from '../../components/ConnectionLine';
|
||||
|
||||
export type GraphViewProps = Omit<ReactFlowProps, 'onSelectionChange' | 'nodes' | 'edges' | 'nodeTypes' | 'edgeTypes'> &
|
||||
Required<
|
||||
@@ -149,10 +150,6 @@ const GraphView = ({
|
||||
edgeTypes={edgeTypes}
|
||||
onEdgeClick={onEdgeClick}
|
||||
onEdgeDoubleClick={onEdgeDoubleClick}
|
||||
connectionLineType={connectionLineType}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
connectionLineComponent={connectionLineComponent}
|
||||
connectionLineContainerStyle={connectionLineContainerStyle}
|
||||
onEdgeUpdate={onEdgeUpdate}
|
||||
onlyRenderVisibleElements={onlyRenderVisibleElements}
|
||||
onEdgeContextMenu={onEdgeContextMenu}
|
||||
@@ -167,7 +164,14 @@ const GraphView = ({
|
||||
elevateEdgesOnSelect={!!elevateEdgesOnSelect}
|
||||
disableKeyboardA11y={disableKeyboardA11y}
|
||||
rfId={rfId}
|
||||
/>
|
||||
>
|
||||
<ConnectionLine
|
||||
style={connectionLineStyle}
|
||||
type={connectionLineType}
|
||||
component={connectionLineComponent}
|
||||
containerStyle={connectionLineContainerStyle}
|
||||
/>
|
||||
</EdgeRenderer>
|
||||
<div className="react-flow__edgelabel-renderer" />
|
||||
|
||||
<NodeRenderer
|
||||
|
||||
@@ -4,12 +4,13 @@ import { shallow } from 'zustand/shallow';
|
||||
|
||||
import useVisibleNodes from '../../hooks/useVisibleNodes';
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
import { clampPosition, devWarn, internalsSymbol } from '../../utils';
|
||||
import { clampPosition, internalsSymbol } from '../../utils';
|
||||
import { containerStyle } from '../../styles';
|
||||
import { GraphViewProps } from '../GraphView';
|
||||
import { getPositionWithOrigin } from './utils';
|
||||
import { Position } from '../../types';
|
||||
import type { ReactFlowState, WrapNodeProps } from '../../types';
|
||||
import { errorMessages } from '../../contants';
|
||||
|
||||
type NodeRendererProps = Pick<
|
||||
GraphViewProps,
|
||||
@@ -36,13 +37,12 @@ const selector = (s: ReactFlowState) => ({
|
||||
nodesFocusable: s.nodesFocusable,
|
||||
elementsSelectable: s.elementsSelectable,
|
||||
updateNodeDimensions: s.updateNodeDimensions,
|
||||
onError: s.onError,
|
||||
});
|
||||
|
||||
const NodeRenderer = (props: NodeRendererProps) => {
|
||||
const { nodesDraggable, nodesConnectable, nodesFocusable, elementsSelectable, updateNodeDimensions } = useStore(
|
||||
selector,
|
||||
shallow
|
||||
);
|
||||
const { nodesDraggable, nodesConnectable, nodesFocusable, elementsSelectable, updateNodeDimensions, onError } =
|
||||
useStore(selector, shallow);
|
||||
const nodes = useVisibleNodes(props.onlyRenderVisibleElements);
|
||||
const resizeObserverRef = useRef<ResizeObserver>();
|
||||
|
||||
@@ -78,9 +78,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
||||
let nodeType = node.type || 'default';
|
||||
|
||||
if (!props.nodeTypes[nodeType]) {
|
||||
devWarn(
|
||||
`Node type "${nodeType}" not found. Using fallback type "default". Help: https://reactflow.dev/error#300`
|
||||
);
|
||||
onError?.('003', errorMessages['003'](nodeType));
|
||||
|
||||
nodeType = 'default';
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import InputNode from '../../components/Nodes/InputNode';
|
||||
import OutputNode from '../../components/Nodes/OutputNode';
|
||||
import GroupNode from '../../components/Nodes/GroupNode';
|
||||
import wrapNode from '../../components/Nodes/wrapNode';
|
||||
import { devWarn } from '../../utils';
|
||||
import type { NodeTypes, NodeProps, NodeTypesWrapped, NodeOrigin, XYPosition } from '../../types';
|
||||
|
||||
export type CreateNodeTypes = (nodeTypes: NodeTypes) => NodeTypesWrapped;
|
||||
@@ -51,7 +50,6 @@ export const getPositionWithOrigin = ({
|
||||
}
|
||||
|
||||
if (origin[0] < 0 || origin[1] < 0 || origin[0] > 1 || origin[1] > 1) {
|
||||
devWarn('nodeOrigin must be between 0 and 1');
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
|
||||
@@ -163,6 +163,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
||||
autoPanOnConnect = true,
|
||||
autoPanOnNodeDrag = true,
|
||||
connectionRadius = 20,
|
||||
onError,
|
||||
style,
|
||||
id,
|
||||
...rest
|
||||
@@ -292,6 +293,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
||||
rfId={rfId}
|
||||
autoPanOnConnect={autoPanOnConnect}
|
||||
autoPanOnNodeDrag={autoPanOnNodeDrag}
|
||||
onError={onError}
|
||||
/>
|
||||
<SelectionListener onSelectionChange={onSelectionChange} />
|
||||
{children}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { devWarn } from '../../utils';
|
||||
import { CreateEdgeTypes } from '../EdgeRenderer/utils';
|
||||
import { CreateNodeTypes } from '../NodeRenderer/utils';
|
||||
import type { EdgeTypes, EdgeTypesWrapped, NodeTypes, NodeTypesWrapped } from '../../types';
|
||||
import { errorMessages } from '../../contants';
|
||||
|
||||
export function useNodeOrEdgeTypes(nodeOrEdgeTypes: NodeTypes, createTypes: CreateNodeTypes): NodeTypesWrapped;
|
||||
export function useNodeOrEdgeTypes(nodeOrEdgeTypes: EdgeTypes, createTypes: CreateEdgeTypes): EdgeTypesWrapped;
|
||||
@@ -16,9 +17,7 @@ export function useNodeOrEdgeTypes(nodeOrEdgeTypes: any, createTypes: any): any
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const typeKeys = Object.keys(nodeOrEdgeTypes);
|
||||
if (shallow(typesKeysRef.current, typeKeys)) {
|
||||
devWarn(
|
||||
"It looks like you have created a new nodeTypes or edgeTypes object. If this wasn't on purpose please define the nodeTypes/edgeTypes outside of the component or memoize them. Help: https://reactflow.dev/error#200"
|
||||
);
|
||||
devWarn('002', errorMessages['002']());
|
||||
}
|
||||
|
||||
typesKeysRef.current = typeKeys;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Edge, HandleElement } from './types';
|
||||
|
||||
export const errorMessages = {
|
||||
'001': () =>
|
||||
'[React Flow]: Seems like you have not used zustand provider as an ancestor. Help: https://reactflow.dev/error#001',
|
||||
'002': () =>
|
||||
"It looks like you've created a new nodeTypes or edgeTypes object. If this wasn't on purpose please define the nodeTypes/edgeTypes outside of the component or memoize them.",
|
||||
'003': (nodeType: string) => `Node type "${nodeType}" not found. Using fallback type "default".`,
|
||||
'004': () => 'The React Flow parent container needs a width and a height to render the graph.',
|
||||
'005': () => 'Only child nodes can use a parent extent.',
|
||||
'006': () => "Can't create edge. An edge needs a source and a target.",
|
||||
'007': (id: string) => `The old edge with id=${id} does not exist.`,
|
||||
'009': (type: string) => `Marker type "${type}" doesn't exist.`,
|
||||
'008': (sourceHandle: HandleElement | null, edge: Edge) =>
|
||||
`Couldn't create edge for ${!sourceHandle ? 'source' : 'target'} handle id: "${
|
||||
!sourceHandle ? edge.sourceHandle : edge.targetHandle
|
||||
}", edge id: ${edge.id}.`,
|
||||
'010': () => 'Handle: No node id found. Make sure to only use a Handle inside a custom Node.',
|
||||
'011': (edgeType: string) => `Edge type "${edgeType}" not found. Using fallback type "default".`,
|
||||
};
|
||||
@@ -61,6 +61,7 @@ function useDrag({
|
||||
snapGrid,
|
||||
snapToGrid,
|
||||
nodeOrigin,
|
||||
onError,
|
||||
} = store.getState();
|
||||
|
||||
lastPos.current = { x, y };
|
||||
@@ -75,7 +76,7 @@ function useDrag({
|
||||
nextPosition.y = snapGrid[1] * Math.round(nextPosition.y / snapGrid[1]);
|
||||
}
|
||||
|
||||
const updatedPos = calcNextPosition(n, nextPosition, nodeInternals, nodeExtent, nodeOrigin);
|
||||
const updatedPos = calcNextPosition(n, nextPosition, nodeInternals, nodeExtent, nodeOrigin, onError);
|
||||
|
||||
// we want to make sure that we only fire a change event when there is a changes
|
||||
hasChange = hasChange || n.position.x !== updatedPos.position.x || n.position.y !== updatedPos.position.y;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { RefObject } from 'react';
|
||||
|
||||
import { clampPosition, devWarn, isNumeric } from '../../utils';
|
||||
import type { CoordinateExtent, Node, NodeDragItem, NodeInternals, NodeOrigin, XYPosition } from '../../types';
|
||||
import { clampPosition, isNumeric } from '../../utils';
|
||||
import type { CoordinateExtent, Node, NodeDragItem, NodeInternals, NodeOrigin, OnError, XYPosition } from '../../types';
|
||||
import { getNodePositionWithOrigin } from '../../utils/graph';
|
||||
import { errorMessages } from '../../contants';
|
||||
|
||||
export function isParentSelected(node: Node, nodeInternals: NodeInternals): boolean {
|
||||
if (!node.parentNode) {
|
||||
@@ -62,7 +63,8 @@ export function calcNextPosition(
|
||||
nextPosition: XYPosition,
|
||||
nodeInternals: NodeInternals,
|
||||
nodeExtent?: CoordinateExtent,
|
||||
nodeOrigin: NodeOrigin = [0, 0]
|
||||
nodeOrigin: NodeOrigin = [0, 0],
|
||||
onError?: OnError
|
||||
): { position: XYPosition; positionAbsolute: XYPosition } {
|
||||
let currentExtent = node.extent || nodeExtent;
|
||||
|
||||
@@ -81,7 +83,7 @@ export function calcNextPosition(
|
||||
]
|
||||
: currentExtent;
|
||||
} else {
|
||||
devWarn('Only child nodes can use a parent extent. Help: https://reactflow.dev/error#500');
|
||||
onError?.('005', errorMessages['005']());
|
||||
|
||||
currentExtent = nodeExtent;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ import { useEffect } from 'react';
|
||||
import type { MutableRefObject } from 'react';
|
||||
|
||||
import { useStoreApi } from '../hooks/useStore';
|
||||
import { devWarn, getDimensions } from '../utils';
|
||||
import { getDimensions } from '../utils';
|
||||
import { errorMessages } from '../contants';
|
||||
|
||||
function useResizeHandler(rendererNode: MutableRefObject<HTMLDivElement | null>): void {
|
||||
const store = useStoreApi();
|
||||
@@ -18,9 +19,7 @@ function useResizeHandler(rendererNode: MutableRefObject<HTMLDivElement | null>)
|
||||
const size = getDimensions(rendererNode.current);
|
||||
|
||||
if (size.height === 0 || size.width === 0) {
|
||||
devWarn(
|
||||
'The React Flow parent container needs a width and a height to render the graph. Help: https://reactflow.dev/error#400'
|
||||
);
|
||||
store.getState().onError?.('004', errorMessages['004']());
|
||||
}
|
||||
|
||||
store.setState({ width: size.width || 500, height: size.height || 500 });
|
||||
|
||||
@@ -3,10 +3,10 @@ import { useStore as useZustandStore } from 'zustand';
|
||||
import type { StoreApi } from 'zustand';
|
||||
|
||||
import StoreContext from '../contexts/RFStoreContext';
|
||||
import { errorMessages } from '../contants';
|
||||
import type { ReactFlowState } from '../types';
|
||||
|
||||
const errorMessage =
|
||||
'[React Flow]: Seems like you have not used zustand provider as an ancestor. Help: https://reactflow.dev/error#100';
|
||||
const zustandErrorMessage = errorMessages['001']();
|
||||
|
||||
type ExtractState = StoreApi<ReactFlowState> extends { getState: () => infer T } ? T : never;
|
||||
|
||||
@@ -17,7 +17,7 @@ function useStore<StateSlice = ExtractState>(
|
||||
const store = useContext(StoreContext);
|
||||
|
||||
if (store === null) {
|
||||
throw new Error(errorMessage);
|
||||
throw new Error(zustandErrorMessage);
|
||||
}
|
||||
|
||||
return useZustandStore(store, selector, equalityFn);
|
||||
@@ -27,7 +27,7 @@ const useStoreApi = () => {
|
||||
const store = useContext(StoreContext);
|
||||
|
||||
if (store === null) {
|
||||
throw new Error(errorMessage);
|
||||
throw new Error(zustandErrorMessage);
|
||||
}
|
||||
|
||||
return useMemo(
|
||||
|
||||
@@ -7,7 +7,8 @@ function useUpdateNodePositions() {
|
||||
const store = useStoreApi();
|
||||
|
||||
const updatePositions = useCallback((params: { x: number; y: number; isShiftPressed: boolean }) => {
|
||||
const { nodeInternals, nodeExtent, updateNodePositions, getNodes, snapToGrid, snapGrid } = store.getState();
|
||||
const { nodeInternals, nodeExtent, updateNodePositions, getNodes, snapToGrid, snapGrid, onError } =
|
||||
store.getState();
|
||||
const selectedNodes = getNodes().filter((n) => n.selected);
|
||||
// by default a node moves 5px on each key press, or 20px if shift is pressed
|
||||
// if snap grid is enabled, we use that for the velocity.
|
||||
@@ -27,10 +28,17 @@ function useUpdateNodePositions() {
|
||||
nextPosition.y = snapGrid[1] * Math.round(nextPosition.y / snapGrid[1]);
|
||||
}
|
||||
|
||||
const updatedPos = calcNextPosition(n, nextPosition, nodeInternals, nodeExtent);
|
||||
const { positionAbsolute, position } = calcNextPosition(
|
||||
n,
|
||||
nextPosition,
|
||||
nodeInternals,
|
||||
nodeExtent,
|
||||
undefined,
|
||||
onError
|
||||
);
|
||||
|
||||
n.position = updatedPos.position;
|
||||
n.positionAbsolute = updatedPos.positionAbsolute;
|
||||
n.position = position;
|
||||
n.positionAbsolute = positionAbsolute;
|
||||
}
|
||||
|
||||
return n;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { devWarn } from '../utils';
|
||||
import { ConnectionMode } from '../types';
|
||||
import type { CoordinateExtent, ReactFlowStore } from '../types';
|
||||
|
||||
@@ -59,6 +60,7 @@ const initialState: ReactFlowStore = {
|
||||
autoPanOnConnect: true,
|
||||
autoPanOnNodeDrag: true,
|
||||
connectionRadius: 20,
|
||||
onError: devWarn,
|
||||
};
|
||||
|
||||
export default initialState;
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
.react-flow__connection {
|
||||
pointer-events: none;
|
||||
|
||||
&.animated {
|
||||
.animated {
|
||||
stroke-dasharray: 5;
|
||||
animation: dashdraw 0.5s linear infinite;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import type {
|
||||
EdgeMouseHandler,
|
||||
HandleType,
|
||||
SelectionMode,
|
||||
OnError,
|
||||
} from '.';
|
||||
|
||||
export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
|
||||
@@ -142,6 +143,7 @@ export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
|
||||
autoPanOnNodeDrag?: boolean;
|
||||
autoPanOnConnect?: boolean;
|
||||
connectionRadius?: number;
|
||||
onError?: OnError;
|
||||
};
|
||||
|
||||
export type ReactFlowRefType = HTMLDivElement;
|
||||
|
||||
@@ -206,6 +206,7 @@ export type ReactFlowStore = {
|
||||
|
||||
onNodesDelete?: OnNodesDelete;
|
||||
onEdgesDelete?: OnEdgesDelete;
|
||||
onError?: OnError;
|
||||
|
||||
// event handlers
|
||||
onViewportChangeStart?: OnViewportChange;
|
||||
@@ -270,3 +271,5 @@ export type SelectionRect = Rect & {
|
||||
startX: number;
|
||||
startY: number;
|
||||
};
|
||||
|
||||
export type OnError = (id: string, message: string) => void;
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
NodeInternals,
|
||||
NodeOrigin,
|
||||
} from '../types';
|
||||
import { errorMessages } from '../contants';
|
||||
|
||||
export const isEdge = (element: Node | Connection | Edge): element is Edge =>
|
||||
'id' in element && 'source' in element && 'target' in element;
|
||||
@@ -70,7 +71,7 @@ const connectionExists = (edge: Edge, edges: Edge[]) => {
|
||||
|
||||
export const addEdge = (edgeParams: Edge | Connection, edges: Edge[]): Edge[] => {
|
||||
if (!edgeParams.source || !edgeParams.target) {
|
||||
devWarn("Can't create edge. An edge needs a source and a target. Help: https://reactflow.dev/error#600");
|
||||
devWarn('006', errorMessages['006']());
|
||||
|
||||
return edges;
|
||||
}
|
||||
@@ -94,7 +95,7 @@ export const addEdge = (edgeParams: Edge | Connection, edges: Edge[]): Edge[] =>
|
||||
|
||||
export const updateEdge = (oldEdge: Edge, newConnection: Connection, edges: Edge[]): Edge[] => {
|
||||
if (!newConnection.source || !newConnection.target) {
|
||||
devWarn("Can't create a new edge. An edge needs a source and a target. Help: https://reactflow.dev/error#600");
|
||||
devWarn('006', errorMessages['006']());
|
||||
|
||||
return edges;
|
||||
}
|
||||
@@ -102,7 +103,7 @@ export const updateEdge = (oldEdge: Edge, newConnection: Connection, edges: Edge
|
||||
const foundEdge = edges.find((e) => e.id === oldEdge.id) as Edge;
|
||||
|
||||
if (!foundEdge) {
|
||||
devWarn(`The old edge with id=${oldEdge.id} does not exist. Help: https://reactflow.dev/error#700`);
|
||||
devWarn('007', errorMessages['007'](oldEdge.id));
|
||||
|
||||
return edges;
|
||||
}
|
||||
|
||||
@@ -89,9 +89,9 @@ export const internalsSymbol = Symbol.for('internals');
|
||||
// used for a11y key board controls for nodes and edges
|
||||
export const elementSelectionKeys = ['Enter', ' ', 'Escape'];
|
||||
|
||||
export const devWarn = (message: string) => {
|
||||
export const devWarn = (id: string, message: string) => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn(`[React Flow]: ${message}`);
|
||||
console.warn(`[React Flow]: ${message} Help: https://reactflow.dev/error#${id}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -118,7 +118,7 @@ export function isInputDOMNode(event: KeyboardEvent | ReactKeyboardEvent): boole
|
||||
|
||||
export const isMouseEvent = (
|
||||
event: MouseEvent | ReactMouseEvent | TouchEvent | ReactTouchEvent
|
||||
): event is MouseEvent | ReactMouseEvent => 'button' in event;
|
||||
): event is MouseEvent | ReactMouseEvent => 'clientX' in event;
|
||||
|
||||
export const getEventPosition = (
|
||||
event: MouseEvent | ReactMouseEvent | TouchEvent | ReactTouchEvent,
|
||||
|
||||
Reference in New Issue
Block a user