Feature: Draggable edge
This commit is contained in:
@@ -105,6 +105,7 @@ const BasicFlow = () => <ReactFlow elements={elements} />;
|
|||||||
- `onPaneClick(event: MouseEvent)`: called when user clicks directly on the canvas
|
- `onPaneClick(event: MouseEvent)`: called when user clicks directly on the canvas
|
||||||
- `onPaneContextMenu(event: MouseEvent)`: called when user does a right-click on the canvas
|
- `onPaneContextMenu(event: MouseEvent)`: called when user does a right-click on the canvas
|
||||||
- `onPaneScroll(event: WheelEvent)`: called when user scrolls pane (only works when `zoomOnScroll` is set to `false)
|
- `onPaneScroll(event: WheelEvent)`: called when user scrolls pane (only works when `zoomOnScroll` is set to `false)
|
||||||
|
- `onEdgeUpdate(oldEdge: Edge, newConnection: Connection)`: called when user press at either end of the edge and point to the target node
|
||||||
|
|
||||||
#### Interaction
|
#### Interaction
|
||||||
- `nodesDraggable`: default: `true`. This applies to all nodes. You can also change the behavior of a specific node with the `draggable` node option
|
- `nodesDraggable`: default: `true`. This applies to all nodes. You can also change the behavior of a specific node with the `draggable` node option
|
||||||
@@ -586,6 +587,12 @@ Returns an array with elements with the added edge.
|
|||||||
|
|
||||||
`addEdge = (edgeParams: Edge, elements: Elements): Elements`
|
`addEdge = (edgeParams: Edge, elements: Elements): Elements`
|
||||||
|
|
||||||
|
### updateEdge
|
||||||
|
|
||||||
|
Returns an array with elements with the updated edge.
|
||||||
|
|
||||||
|
`updateEdge = (oldEdge: Edge, newConnection: Connection, elements: Elements): Elements`
|
||||||
|
|
||||||
### getOutgoers
|
### getOutgoers
|
||||||
|
|
||||||
Returns all direct child nodes of the passed node.
|
Returns all direct child nodes of the passed node.
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import ReactFlow, { MiniMap, Controls, Background, updateEdge } from 'react-flow-renderer';
|
||||||
|
|
||||||
|
const onLoad = (reactFlowInstance) => {
|
||||||
|
console.log('flow loaded:', reactFlowInstance);
|
||||||
|
reactFlowInstance.fitView();
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialElements = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: 'input',
|
||||||
|
data: {
|
||||||
|
label: (
|
||||||
|
<>
|
||||||
|
Node <strong>A</strong>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
position: { x: 250, y: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
data: {
|
||||||
|
label: (
|
||||||
|
<>
|
||||||
|
Node <strong>B</strong>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
position: { x: 100, y: 100 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
data: {
|
||||||
|
label: (
|
||||||
|
<>
|
||||||
|
Node <strong>C</strong>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
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 snapGrid = [16, 16];
|
||||||
|
|
||||||
|
const DragEdge = () => {
|
||||||
|
const [elements, setElements] = useState(initialElements);
|
||||||
|
const onEdgeUpdate = (oldEdge, newConnection) => {
|
||||||
|
setElements((els) => updateEdge(oldEdge, newConnection, els));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReactFlow
|
||||||
|
elements={elements}
|
||||||
|
onLoad={onLoad}
|
||||||
|
snapToGrid={true}
|
||||||
|
snapGrid={snapGrid}
|
||||||
|
onEdgeUpdate={onEdgeUpdate}
|
||||||
|
>
|
||||||
|
<MiniMap
|
||||||
|
nodeStrokeColor={(n) => {
|
||||||
|
if (n.style?.background) return n.style.background;
|
||||||
|
if (n.type === 'input') return '#0041d0';
|
||||||
|
if (n.type === 'output') return '#ff0072';
|
||||||
|
if (n.type === 'default') return '#1a192b';
|
||||||
|
|
||||||
|
return '#eee';
|
||||||
|
}}
|
||||||
|
nodeColor={(n) => {
|
||||||
|
if (n.style?.background) return n.style.background;
|
||||||
|
|
||||||
|
return '#fff';
|
||||||
|
}}
|
||||||
|
borderRadius={2}
|
||||||
|
/>
|
||||||
|
<Controls />
|
||||||
|
<Background color="#aaa" gap={16} />
|
||||||
|
</ReactFlow>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DragEdge;
|
||||||
@@ -16,6 +16,7 @@ import Hidden from './Hidden';
|
|||||||
import EdgeTypes from './EdgeTypes';
|
import EdgeTypes from './EdgeTypes';
|
||||||
import CustomConnectionLine from './CustomConnectionLine';
|
import CustomConnectionLine from './CustomConnectionLine';
|
||||||
import NodeTypeChange from './NodeTypeChange';
|
import NodeTypeChange from './NodeTypeChange';
|
||||||
|
import DraggableEdge from './DraggableEdge';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
@@ -84,6 +85,11 @@ const routes = [
|
|||||||
path: '/nodetype-change',
|
path: '/nodetype-change',
|
||||||
component: NodeTypeChange,
|
component: NodeTypeChange,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/draggable-edge',
|
||||||
|
component: DraggableEdge,
|
||||||
|
label: 'Draggable Edge',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const navLinks = routes.filter((route) => route.label);
|
const navLinks = routes.filter((route) => route.label);
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
|||||||
elementsSelectable,
|
elementsSelectable,
|
||||||
markerEndId,
|
markerEndId,
|
||||||
isHidden,
|
isHidden,
|
||||||
|
onEitherEndOfEdgePress,
|
||||||
}: WrapEdgeProps) => {
|
}: WrapEdgeProps) => {
|
||||||
const setSelectedElements = useStoreActions((actions) => actions.setSelectedElements);
|
const setSelectedElements = useStoreActions((actions) => actions.setSelectedElements);
|
||||||
|
|
||||||
@@ -63,12 +64,53 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
|||||||
[elementsSelectable, id, source, target, type, data, onClick]
|
[elementsSelectable, id, source, target, type, data, onClick]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleEitherEndOfEdgePress = useCallback(
|
||||||
|
(event: React.MouseEvent<SVGGElement, MouseEvent>, isEdgeHeader?: boolean): void => {
|
||||||
|
if (elementsSelectable) {
|
||||||
|
setSelectedElements({ id, source, target });
|
||||||
|
}
|
||||||
|
|
||||||
|
const edgeElement: Edge = { id, source, target, type };
|
||||||
|
|
||||||
|
if (typeof data !== 'undefined') {
|
||||||
|
edgeElement.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
onEitherEndOfEdgePress(event, edgeElement, isEdgeHeader);
|
||||||
|
},
|
||||||
|
[elementsSelectable, id, source, target, type, data, onEitherEndOfEdgePress]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleEdgeHeaderPress = useCallback(
|
||||||
|
(event: React.MouseEvent<SVGGElement, MouseEvent>): void => {
|
||||||
|
handleEitherEndOfEdgePress(event, true);
|
||||||
|
},
|
||||||
|
[handleEitherEndOfEdgePress],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleEdgeFooterPress = useCallback(
|
||||||
|
(event: React.MouseEvent<SVGGElement, MouseEvent>): void => {
|
||||||
|
handleEitherEndOfEdgePress(event);
|
||||||
|
},
|
||||||
|
[handleEitherEndOfEdgePress],
|
||||||
|
);
|
||||||
|
|
||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<g className={edgeClasses} onClick={onEdgeClick} style={edgeGroupStyle}>
|
<g className={edgeClasses} onClick={onEdgeClick} style={edgeGroupStyle}>
|
||||||
|
<g onMouseDown={handleEdgeFooterPress}>
|
||||||
|
<circle
|
||||||
|
className="move-handler"
|
||||||
|
cx={sourceX}
|
||||||
|
cy={sourceY}
|
||||||
|
r="12"
|
||||||
|
stroke="transparent"
|
||||||
|
fill="transparent"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
<EdgeComponent
|
<EdgeComponent
|
||||||
id={id}
|
id={id}
|
||||||
source={source}
|
source={source}
|
||||||
@@ -92,6 +134,16 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
|||||||
targetPosition={targetPosition}
|
targetPosition={targetPosition}
|
||||||
markerEndId={markerEndId}
|
markerEndId={markerEndId}
|
||||||
/>
|
/>
|
||||||
|
<g onMouseDown={handleEdgeHeaderPress}>
|
||||||
|
<circle
|
||||||
|
className="move-handler"
|
||||||
|
cx={targetX}
|
||||||
|
cy={targetY}
|
||||||
|
r="12"
|
||||||
|
stroke="transparent"
|
||||||
|
fill="transparent"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
type ValidConnectionFunc = (connection: Connection) => boolean;
|
type ValidConnectionFunc = (connection: Connection) => boolean;
|
||||||
type SetSourceIdFunc = (params: SetConnectionId) => void;
|
export type SetSourceIdFunc = (params: SetConnectionId) => void;
|
||||||
|
|
||||||
interface BaseHandleProps {
|
interface BaseHandleProps {
|
||||||
type: HandleType;
|
type: HandleType;
|
||||||
@@ -40,7 +40,7 @@ type Result = {
|
|||||||
isHoveringHandle: boolean;
|
isHoveringHandle: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function onMouseDown(
|
export function onMouseDown(
|
||||||
event: ReactMouseEvent,
|
event: ReactMouseEvent,
|
||||||
nodeId: ElementId,
|
nodeId: ElementId,
|
||||||
setConnectionNodeId: SetSourceIdFunc,
|
setConnectionNodeId: SetSourceIdFunc,
|
||||||
@@ -67,7 +67,7 @@ function onMouseDown(
|
|||||||
y: event.clientY - containerBounds.top,
|
y: event.clientY - containerBounds.top,
|
||||||
});
|
});
|
||||||
setConnectionNodeId({ connectionNodeId: nodeId, connectionHandleType: handleType });
|
setConnectionNodeId({ connectionNodeId: nodeId, connectionHandleType: handleType });
|
||||||
|
|
||||||
if (onConnectStart) {
|
if (onConnectStart) {
|
||||||
onConnectStart(event, { nodeId, handleType });
|
onConnectStart(event, { nodeId, handleType });
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ function onMouseDown(
|
|||||||
|
|
||||||
function onMouseUp(event: MouseEvent) {
|
function onMouseUp(event: MouseEvent) {
|
||||||
const { connection, isValid } = checkElementBelowIsValid(event);
|
const { connection, isValid } = checkElementBelowIsValid(event);
|
||||||
|
|
||||||
if (onConnectStop) {
|
if (onConnectStop) {
|
||||||
onConnectStop(event);
|
onConnectStop(event);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { memo, CSSProperties } from 'react';
|
import React, { memo, CSSProperties } from 'react';
|
||||||
|
|
||||||
import { useStoreState } from '../../store/hooks';
|
import { useStoreState, useStoreActions } from '../../store/hooks';
|
||||||
import ConnectionLine from '../../components/ConnectionLine/index';
|
import ConnectionLine from '../../components/ConnectionLine/index';
|
||||||
import { isEdge } from '../../utils/graph';
|
import { isEdge } from '../../utils/graph';
|
||||||
import MarkerDefinitions from './MarkerDefinitions';
|
import MarkerDefinitions from './MarkerDefinitions';
|
||||||
@@ -14,7 +14,10 @@ import {
|
|||||||
Elements,
|
Elements,
|
||||||
ConnectionLineType,
|
ConnectionLineType,
|
||||||
ConnectionLineComponent,
|
ConnectionLineComponent,
|
||||||
|
Connection,
|
||||||
|
OnEdgeUpdateFunc
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
import { onMouseDown, SetSourceIdFunc } from '../../components/Handle/BaseHandle';
|
||||||
|
|
||||||
interface EdgeRendererProps {
|
interface EdgeRendererProps {
|
||||||
edgeTypes: any;
|
edgeTypes: any;
|
||||||
@@ -24,6 +27,7 @@ interface EdgeRendererProps {
|
|||||||
arrowHeadColor: string;
|
arrowHeadColor: string;
|
||||||
markerEndId?: string;
|
markerEndId?: string;
|
||||||
connectionLineComponent?: ConnectionLineComponent;
|
connectionLineComponent?: ConnectionLineComponent;
|
||||||
|
onEdgeUpdate?: OnEdgeUpdateFunc;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EdgePositions {
|
interface EdgePositions {
|
||||||
@@ -130,7 +134,9 @@ function renderEdge(
|
|||||||
props: EdgeRendererProps,
|
props: EdgeRendererProps,
|
||||||
nodes: Node[],
|
nodes: Node[],
|
||||||
selectedElements: Elements | null,
|
selectedElements: Elements | null,
|
||||||
elementsSelectable: boolean
|
elementsSelectable: boolean,
|
||||||
|
setConnectionNodeId: SetSourceIdFunc,
|
||||||
|
setPosition: (pos: XYPosition) => void,
|
||||||
) {
|
) {
|
||||||
const [sourceId, sourceHandleId] = edge.source.split('__');
|
const [sourceId, sourceHandleId] = edge.source.split('__');
|
||||||
const [targetId, targetHandleId] = edge.target.split('__');
|
const [targetId, targetHandleId] = edge.target.split('__');
|
||||||
@@ -168,6 +174,30 @@ function renderEdge(
|
|||||||
|
|
||||||
const isSelected = selectedElements ? selectedElements.some((elm) => isEdge(elm) && elm.id === edge.id) : false;
|
const isSelected = selectedElements ? selectedElements.some((elm) => isEdge(elm) && elm.id === edge.id) : false;
|
||||||
|
|
||||||
|
const onConnect = (connection: Connection) => {
|
||||||
|
const { onEdgeUpdate } = props;
|
||||||
|
if (onEdgeUpdate) {
|
||||||
|
onEdgeUpdate(edge, connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEitherEndOfEdgePress = (event: React.MouseEvent, edge: Edge, isEdgeHeader = false) => {
|
||||||
|
const { source, target } = edge;
|
||||||
|
const nodeId = isEdgeHeader ? source : target;
|
||||||
|
const isValidConnection = () => true;
|
||||||
|
const isTarget = !isEdgeHeader;
|
||||||
|
|
||||||
|
onMouseDown(
|
||||||
|
event,
|
||||||
|
nodeId,
|
||||||
|
setConnectionNodeId,
|
||||||
|
setPosition,
|
||||||
|
onConnect,
|
||||||
|
isTarget,
|
||||||
|
isValidConnection,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EdgeComponent
|
<EdgeComponent
|
||||||
key={edge.id}
|
key={edge.id}
|
||||||
@@ -199,6 +229,7 @@ function renderEdge(
|
|||||||
elementsSelectable={elementsSelectable}
|
elementsSelectable={elementsSelectable}
|
||||||
markerEndId={props.markerEndId}
|
markerEndId={props.markerEndId}
|
||||||
isHidden={edge.isHidden}
|
isHidden={edge.isHidden}
|
||||||
|
onEitherEndOfEdgePress={handleEitherEndOfEdgePress}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -215,6 +246,8 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
|||||||
const elementsSelectable = useStoreState((state) => state.elementsSelectable);
|
const elementsSelectable = useStoreState((state) => state.elementsSelectable);
|
||||||
const width = useStoreState((state) => state.width);
|
const width = useStoreState((state) => state.width);
|
||||||
const height = useStoreState((state) => state.height);
|
const height = useStoreState((state) => state.height);
|
||||||
|
const setConnectionNodeId = useStoreActions((actions) => actions.setConnectionNodeId);
|
||||||
|
const setPosition = useStoreActions((actions) => actions.setConnectionPosition);
|
||||||
|
|
||||||
const { connectionLineType, arrowHeadColor, connectionLineStyle, connectionLineComponent } = props;
|
const { connectionLineType, arrowHeadColor, connectionLineStyle, connectionLineComponent } = props;
|
||||||
|
|
||||||
@@ -229,7 +262,16 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
|||||||
<svg width={width} height={height} className="react-flow__edges">
|
<svg width={width} height={height} className="react-flow__edges">
|
||||||
<MarkerDefinitions color={arrowHeadColor} />
|
<MarkerDefinitions color={arrowHeadColor} />
|
||||||
<g transform={transformStyle}>
|
<g transform={transformStyle}>
|
||||||
{edges.map((edge: Edge) => renderEdge(edge, props, nodes, selectedElements, elementsSelectable))}
|
{edges.map((edge: Edge) =>
|
||||||
|
renderEdge(
|
||||||
|
edge,
|
||||||
|
props,
|
||||||
|
nodes,
|
||||||
|
selectedElements,
|
||||||
|
elementsSelectable,
|
||||||
|
setConnectionNodeId,
|
||||||
|
setPosition,
|
||||||
|
))}
|
||||||
{renderConnectionLine && (
|
{renderConnectionLine && (
|
||||||
<ConnectionLine
|
<ConnectionLine
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ import {
|
|||||||
OnConnectStartFunc,
|
OnConnectStartFunc,
|
||||||
OnConnectStopFunc,
|
OnConnectStopFunc,
|
||||||
OnConnectEndFunc,
|
OnConnectEndFunc,
|
||||||
TranslateExtent,
|
TranslateExtent,
|
||||||
|
OnEdgeUpdateFunc,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
export interface GraphViewProps {
|
export interface GraphViewProps {
|
||||||
@@ -77,6 +78,7 @@ export interface GraphViewProps {
|
|||||||
zoomOnScroll?: boolean;
|
zoomOnScroll?: boolean;
|
||||||
zoomOnDoubleClick?: boolean;
|
zoomOnDoubleClick?: boolean;
|
||||||
paneMoveable?: boolean;
|
paneMoveable?: boolean;
|
||||||
|
onEdgeUpdate?: OnEdgeUpdateFunc;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GraphView = ({
|
const GraphView = ({
|
||||||
@@ -128,6 +130,7 @@ const GraphView = ({
|
|||||||
onPaneClick,
|
onPaneClick,
|
||||||
onPaneScroll,
|
onPaneScroll,
|
||||||
onPaneContextMenu,
|
onPaneContextMenu,
|
||||||
|
onEdgeUpdate,
|
||||||
}: GraphViewProps) => {
|
}: GraphViewProps) => {
|
||||||
const isInitialised = useRef<boolean>(false);
|
const isInitialised = useRef<boolean>(false);
|
||||||
const zoomPane = useRef<HTMLDivElement>(null);
|
const zoomPane = useRef<HTMLDivElement>(null);
|
||||||
@@ -310,6 +313,7 @@ const GraphView = ({
|
|||||||
arrowHeadColor={arrowHeadColor}
|
arrowHeadColor={arrowHeadColor}
|
||||||
markerEndId={markerEndId}
|
markerEndId={markerEndId}
|
||||||
connectionLineComponent={connectionLineComponent}
|
connectionLineComponent={connectionLineComponent}
|
||||||
|
onEdgeUpdate={onEdgeUpdate}
|
||||||
/>
|
/>
|
||||||
<UserSelection selectionKeyPressed={selectionKeyPressed} />
|
<UserSelection selectionKeyPressed={selectionKeyPressed} />
|
||||||
{nodesSelectionActive && (
|
{nodesSelectionActive && (
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
OnConnectStopFunc,
|
OnConnectStopFunc,
|
||||||
OnConnectEndFunc,
|
OnConnectEndFunc,
|
||||||
TranslateExtent,
|
TranslateExtent,
|
||||||
|
OnEdgeUpdateFunc,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import '../../style.css';
|
import '../../style.css';
|
||||||
@@ -99,6 +100,7 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
|
|||||||
markerEndId?: string;
|
markerEndId?: string;
|
||||||
zoomOnScroll?: boolean;
|
zoomOnScroll?: boolean;
|
||||||
zoomOnDoubleClick?: boolean;
|
zoomOnDoubleClick?: boolean;
|
||||||
|
onEdgeUpdate?: OnEdgeUpdateFunc;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReactFlow = ({
|
const ReactFlow = ({
|
||||||
@@ -153,6 +155,7 @@ const ReactFlow = ({
|
|||||||
onPaneScroll,
|
onPaneScroll,
|
||||||
onPaneContextMenu,
|
onPaneContextMenu,
|
||||||
children,
|
children,
|
||||||
|
onEdgeUpdate,
|
||||||
...rest
|
...rest
|
||||||
}: ReactFlowProps) => {
|
}: ReactFlowProps) => {
|
||||||
const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), []);
|
const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), []);
|
||||||
@@ -211,6 +214,7 @@ const ReactFlow = ({
|
|||||||
onSelectionDrag={onSelectionDrag}
|
onSelectionDrag={onSelectionDrag}
|
||||||
onSelectionDragStop={onSelectionDragStop}
|
onSelectionDragStop={onSelectionDragStop}
|
||||||
onSelectionContextMenu={onSelectionContextMenu}
|
onSelectionContextMenu={onSelectionContextMenu}
|
||||||
|
onEdgeUpdate={onEdgeUpdate}
|
||||||
/>
|
/>
|
||||||
{onSelectionChange && <SelectionListener onSelectionChange={onSelectionChange} />}
|
{onSelectionChange && <SelectionListener onSelectionChange={onSelectionChange} />}
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ export { getBezierPath } from './components/Edges/BezierEdge';
|
|||||||
export { getSmoothStepPath } from './components/Edges/SmoothStepEdge';
|
export { getSmoothStepPath } from './components/Edges/SmoothStepEdge';
|
||||||
export { getMarkerEnd, getCenter as getEdgeCenter } from './components/Edges/utils';
|
export { getMarkerEnd, getCenter as getEdgeCenter } from './components/Edges/utils';
|
||||||
|
|
||||||
export { isNode, isEdge, removeElements, addEdge, getOutgoers, getIncomers, getConnectedEdges } from './utils/graph';
|
export { isNode, isEdge, removeElements, addEdge, getOutgoers, getIncomers, getConnectedEdges, updateEdge } from './utils/graph';
|
||||||
|
|
||||||
export * from './additional-components';
|
export * from './additional-components';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|||||||
@@ -228,3 +228,7 @@
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translate(0, -50%);
|
transform: translate(0, -50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.move-handler {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
@@ -119,6 +119,7 @@ export interface WrapEdgeProps {
|
|||||||
elementsSelectable?: boolean;
|
elementsSelectable?: boolean;
|
||||||
markerEndId?: string;
|
markerEndId?: string;
|
||||||
isHidden?: boolean;
|
isHidden?: boolean;
|
||||||
|
onEitherEndOfEdgePress: (event: React.MouseEvent, edge: Edge, isEdgeHeader?: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EdgeProps {
|
export interface EdgeProps {
|
||||||
@@ -311,3 +312,5 @@ export type FlowTransform = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TranslateExtent = [[number, number], [number, number]];
|
export type TranslateExtent = [[number, number], [number, number]];
|
||||||
|
|
||||||
|
export type OnEdgeUpdateFunc = (oldEdge: Edge, newConnection: Connection) => void;
|
||||||
@@ -67,6 +67,37 @@ export const addEdge = (edgeParams: Edge | Connection, elements: Elements): Elem
|
|||||||
return elements.concat(edge);
|
return elements.concat(edge);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateEdge = (oldEdge: Edge, newConnection: Connection, elements: Elements): Elements => {
|
||||||
|
if (!newConnection.source || !newConnection.target) {
|
||||||
|
throw new Error("Can't create new edge. An edge needs a source and a target.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure that there is node with the target and one with the source id
|
||||||
|
[newConnection.source, newConnection.target].forEach((id) => {
|
||||||
|
const nodeId = id.includes('__') ? id.split('__')[0] : id;
|
||||||
|
|
||||||
|
if (!elements.find((e) => isNode(e) && e.id === nodeId)) {
|
||||||
|
throw new Error(`Can't create edge. Node with id=${nodeId} does not exist.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const foundEdge = elements.find(e => isEdge(e) && e.id === oldEdge.id) as Edge;
|
||||||
|
if (!foundEdge) {
|
||||||
|
throw new Error(`The old edge with id=${oldEdge.id} does not exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove old edge and create the new edge with parameters of old edge.
|
||||||
|
const edge = {
|
||||||
|
...oldEdge,
|
||||||
|
id: getEdgeId(newConnection),
|
||||||
|
source: newConnection.source,
|
||||||
|
target: newConnection.target,
|
||||||
|
} as Edge;
|
||||||
|
|
||||||
|
return elements.filter(e => e.id !== oldEdge.id).concat(edge);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export const pointToRendererPoint = (
|
export const pointToRendererPoint = (
|
||||||
{ x, y }: XYPosition,
|
{ x, y }: XYPosition,
|
||||||
[tx, ty, tScale]: Transform,
|
[tx, ty, tScale]: Transform,
|
||||||
|
|||||||
Reference in New Issue
Block a user