refactor(elements): render only visible elements
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { isNode, isEdge, getOutgoers, getIncomers, removeElements, addEdge } from '../../../dist/ReactFlow.js';
|
import { isNode, isEdge, getOutgoers, getIncomers, addEdge } from '../../../dist/ReactFlow.js';
|
||||||
|
|
||||||
const nodes = [
|
const nodes = [
|
||||||
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
|
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
|
||||||
@@ -69,66 +69,4 @@ describe('Graph Utils Testing', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('tests removeElements function', () => {
|
|
||||||
it('removes a node', () => {
|
|
||||||
const nextElements = removeElements([nodes[0]], elements);
|
|
||||||
|
|
||||||
const nextNodes = nextElements.filter((e) => isNode(e));
|
|
||||||
const nextEdges = nextElements.filter((e) => isEdge(e));
|
|
||||||
|
|
||||||
expect(nextNodes.length).to.be.equal(nodes.length - 1);
|
|
||||||
expect(nextEdges.length).to.be.equal(edges.length - 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('removes multiple nodes', () => {
|
|
||||||
const elementsToRemove = [nodes[0], nodes[1]];
|
|
||||||
const nextElements = removeElements(elementsToRemove, elements);
|
|
||||||
const nextNodes = nextElements.filter((e) => isNode(e));
|
|
||||||
const nextEdges = nextElements.filter((e) => isEdge(e));
|
|
||||||
|
|
||||||
expect(nextNodes.length).to.be.equal(nodes.length - 2);
|
|
||||||
expect(nextEdges.length).to.be.equal(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('removes no node', () => {
|
|
||||||
const nextElementsNoRemove = removeElements([], elements);
|
|
||||||
expect(nextElementsNoRemove.length).to.be.equal(elements.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('tries to removes node that does not exist', () => {
|
|
||||||
const nextElementsNoRemove = removeElements([{ id: 'id-that-does-not-exist' }], elements);
|
|
||||||
expect(nextElementsNoRemove.length).to.be.equal(elements.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('removes an edge', () => {
|
|
||||||
const nextElements = removeElements([edges[0]], elements);
|
|
||||||
|
|
||||||
const nextNodes = nextElements.filter((e) => isNode(e));
|
|
||||||
const nextEdges = nextElements.filter((e) => isEdge(e));
|
|
||||||
|
|
||||||
expect(nextNodes.length).to.be.equal(nodes.length);
|
|
||||||
expect(nextEdges.length).to.be.equal(edges.length - 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('removes multiple edges', () => {
|
|
||||||
const nextElements = removeElements([edges[0], edges[1]], elements);
|
|
||||||
|
|
||||||
const nextNodes = nextElements.filter((e) => isNode(e));
|
|
||||||
const nextEdges = nextElements.filter((e) => isEdge(e));
|
|
||||||
|
|
||||||
expect(nextNodes.length).to.be.equal(nodes.length);
|
|
||||||
expect(nextEdges.length).to.be.equal(edges.length - 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('removes node and edge', () => {
|
|
||||||
const nextElements = removeElements([nodes[0], edges[0]], elements);
|
|
||||||
|
|
||||||
const nextNodes = nextElements.filter((e) => isNode(e));
|
|
||||||
const nextEdges = nextElements.filter((e) => isEdge(e));
|
|
||||||
|
|
||||||
expect(nextNodes.length).to.be.equal(nodes.length - 1);
|
|
||||||
expect(nextEdges.length).to.be.equal(edges.length - 2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ const BasicFlow = () => {
|
|||||||
const [nodes, setNodes] = useState<Node[]>(initialNodes);
|
const [nodes, setNodes] = useState<Node[]>(initialNodes);
|
||||||
const [edges, setEdges] = useState<Edge[]>(initialEdges);
|
const [edges, setEdges] = useState<Edge[]>(initialEdges);
|
||||||
|
|
||||||
const onConnect = useCallback((params: Edge | Connection, nds: Node[]) => {
|
const onConnect = useCallback((params: Edge | Connection) => {
|
||||||
setEdges((eds) => addEdge(params, nds, eds));
|
setEdges((eds) => addEdge(params, eds));
|
||||||
}, []);
|
}, []);
|
||||||
const onLoad = useCallback((reactFlowInstance: OnLoadParams) => setRfInstance(reactFlowInstance), []);
|
const onLoad = useCallback((reactFlowInstance: OnLoadParams) => setRfInstance(reactFlowInstance), []);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import React, { memo, FC, CSSProperties } from 'react';
|
||||||
|
|
||||||
|
import { Handle, Position, NodeProps, Connection, Edge } from 'react-flow-renderer';
|
||||||
|
|
||||||
|
const targetHandleStyle: CSSProperties = { background: '#555' };
|
||||||
|
const sourceHandleStyleA: CSSProperties = { ...targetHandleStyle, top: 10 };
|
||||||
|
const sourceHandleStyleB: CSSProperties = { ...targetHandleStyle, bottom: 10, top: 'auto' };
|
||||||
|
|
||||||
|
const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params);
|
||||||
|
|
||||||
|
const ColorSelectorNode: FC<NodeProps> = ({ data, isConnectable }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Handle type="target" position={Position.Left} style={targetHandleStyle} onConnect={onConnect} />
|
||||||
|
<div>
|
||||||
|
Custom Color Picker Node: <strong>{data.color}</strong>
|
||||||
|
</div>
|
||||||
|
<input className="nodrag" type="color" onChange={data.onChange} defaultValue={data.color} />
|
||||||
|
<Handle type="source" position={Position.Right} id="a" style={sourceHandleStyleA} isConnectable={isConnectable} />
|
||||||
|
<Handle type="source" position={Position.Right} id="b" style={sourceHandleStyleB} isConnectable={isConnectable} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ColorSelectorNode);
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
import { useState, useEffect, MouseEvent, useCallback } from 'react';
|
||||||
|
import { ChangeEvent } from 'react';
|
||||||
|
|
||||||
|
import ReactFlow, {
|
||||||
|
addEdge,
|
||||||
|
MiniMap,
|
||||||
|
Controls,
|
||||||
|
Node,
|
||||||
|
OnLoadParams,
|
||||||
|
Position,
|
||||||
|
SnapGrid,
|
||||||
|
Connection,
|
||||||
|
Edge,
|
||||||
|
NodeChange,
|
||||||
|
applyNodeChanges,
|
||||||
|
applyEdgeChanges,
|
||||||
|
EdgeChange,
|
||||||
|
} from 'react-flow-renderer';
|
||||||
|
|
||||||
|
import ColorSelectorNode from './ColorSelectorNode';
|
||||||
|
|
||||||
|
const onLoad = (reactFlowInstance: OnLoadParams) => console.log('flow loaded:', reactFlowInstance);
|
||||||
|
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||||
|
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||||
|
|
||||||
|
const initBgColor = '#1A192B';
|
||||||
|
|
||||||
|
const connectionLineStyle = { stroke: '#fff' };
|
||||||
|
const snapGrid: SnapGrid = [16, 16];
|
||||||
|
const nodeTypes = {
|
||||||
|
selectorNode: ColorSelectorNode,
|
||||||
|
};
|
||||||
|
|
||||||
|
const CustomNodeFlow = () => {
|
||||||
|
const [nodes, setNodes] = useState<Node[]>([]);
|
||||||
|
const [edges, setEdges] = useState<Edge[]>([]);
|
||||||
|
const [bgColor, setBgColor] = useState<string>(initBgColor);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setNodes((nds) =>
|
||||||
|
nds.map((node) => {
|
||||||
|
if (node.id !== '2') {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
const color = event.target.value;
|
||||||
|
|
||||||
|
setBgColor(color);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
data: {
|
||||||
|
...node.data,
|
||||||
|
color,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
setNodes([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: 'input',
|
||||||
|
data: { label: 'An input node' },
|
||||||
|
position: { x: 0, y: 50 },
|
||||||
|
sourcePosition: Position.Right,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: 'selectorNode',
|
||||||
|
data: { onChange: onChange, color: initBgColor },
|
||||||
|
style: { border: '1px solid #777', padding: 10 },
|
||||||
|
position: { x: 250, y: 50 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: 'output',
|
||||||
|
data: { label: 'Output A' },
|
||||||
|
position: { x: 550, y: 25 },
|
||||||
|
targetPosition: Position.Left,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
type: 'output',
|
||||||
|
data: { label: 'Output B' },
|
||||||
|
position: { x: 550, y: 100 },
|
||||||
|
targetPosition: Position.Left,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
setEdges([
|
||||||
|
{ id: 'e1-2', source: '1', target: '2', animated: true, style: { stroke: '#fff' } },
|
||||||
|
{ id: 'e2a-3', source: '2', sourceHandle: 'a', target: '3', animated: true, style: { stroke: '#fff' } },
|
||||||
|
{ id: 'e2b-4', source: '2', sourceHandle: 'b', target: '4', animated: true, style: { stroke: '#fff' } },
|
||||||
|
]);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onConnect = (params: Connection | Edge) =>
|
||||||
|
setEdges((eds) => addEdge({ ...params, animated: true, style: { stroke: '#fff' } }, eds));
|
||||||
|
|
||||||
|
const onNodesChange = useCallback((changes: NodeChange[]) => {
|
||||||
|
setNodes((ns) => applyNodeChanges(changes, ns));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onEdgesChange = useCallback((changes: EdgeChange[]) => {
|
||||||
|
setEdges((es) => applyEdgeChanges(changes, es));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReactFlow
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
onNodesChange={onNodesChange}
|
||||||
|
onEdgesChange={onEdgesChange}
|
||||||
|
onNodeClick={onNodeClick}
|
||||||
|
onConnect={onConnect}
|
||||||
|
onNodeDragStop={onNodeDragStop}
|
||||||
|
style={{ background: bgColor }}
|
||||||
|
onLoad={onLoad}
|
||||||
|
nodeTypes={nodeTypes}
|
||||||
|
connectionLineStyle={connectionLineStyle}
|
||||||
|
snapToGrid={true}
|
||||||
|
snapGrid={snapGrid}
|
||||||
|
defaultZoom={1.5}
|
||||||
|
>
|
||||||
|
<MiniMap
|
||||||
|
nodeStrokeColor={(n: Node): string => {
|
||||||
|
if (n.type === 'input') return '#0041d0';
|
||||||
|
if (n.type === 'selectorNode') return bgColor;
|
||||||
|
if (n.type === 'output') return '#ff0072';
|
||||||
|
|
||||||
|
return '#eee';
|
||||||
|
}}
|
||||||
|
nodeColor={(n: Node): string => {
|
||||||
|
if (n.type === 'selectorNode') return bgColor;
|
||||||
|
|
||||||
|
return '#fff';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controls />
|
||||||
|
</ReactFlow>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomNodeFlow;
|
||||||
@@ -8,25 +8,27 @@ import ReactFlow, {
|
|||||||
Node,
|
Node,
|
||||||
NodeChange,
|
NodeChange,
|
||||||
applyNodeChanges,
|
applyNodeChanges,
|
||||||
|
Connection,
|
||||||
|
addEdge,
|
||||||
} from 'react-flow-renderer';
|
} from 'react-flow-renderer';
|
||||||
|
|
||||||
import { getElements } from './utils';
|
import { getNodesAndEdges } from './utils';
|
||||||
|
|
||||||
const buttonWrapperStyles: CSSProperties = { position: 'absolute', right: 10, top: 10, zIndex: 4 };
|
const buttonWrapperStyles: CSSProperties = { position: 'absolute', right: 10, top: 10, zIndex: 4 };
|
||||||
|
|
||||||
const onLoad = (reactFlowInstance: OnLoadParams) => {
|
const onLoad = (reactFlowInstance: OnLoadParams) => {
|
||||||
reactFlowInstance.fitView();
|
reactFlowInstance.fitView();
|
||||||
console.log(reactFlowInstance.getElements());
|
console.log(reactFlowInstance.getNodes());
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialElements = getElements(30, 30);
|
const { nodes: initialNodes, edges: initialEdges } = getNodesAndEdges(30, 30);
|
||||||
|
|
||||||
const StressFlow = () => {
|
const StressFlow = () => {
|
||||||
const [nodes, setNodes] = useState<Node[]>(initialElements.nodes);
|
const [nodes, setNodes] = useState<Node[]>(initialNodes);
|
||||||
const [edges, setEdges] = useState<Edge[]>(initialElements.edges);
|
const [edges, setEdges] = useState<Edge[]>(initialEdges);
|
||||||
// const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els));
|
const onConnect = useCallback((params: Edge | Connection) => {
|
||||||
// const onConnect = (params: Connection | Edge, nds: Node[]) => setElements((els) => addEdge(params, els));
|
setEdges((eds) => addEdge(params, eds));
|
||||||
|
}, []);
|
||||||
const updatePos = () => {
|
const updatePos = () => {
|
||||||
setNodes((nds) => {
|
setNodes((nds) => {
|
||||||
return nds.map((n) => {
|
return nds.map((n) => {
|
||||||
@@ -43,7 +45,7 @@ const StressFlow = () => {
|
|||||||
|
|
||||||
const updateElements = () => {
|
const updateElements = () => {
|
||||||
const grid = Math.ceil(Math.random() * 10);
|
const grid = Math.ceil(Math.random() * 10);
|
||||||
const initialElements = getElements(grid, grid);
|
const initialElements = getNodesAndEdges(grid, grid);
|
||||||
setNodes(initialElements.nodes);
|
setNodes(initialElements.nodes);
|
||||||
setEdges(initialElements.edges);
|
setEdges(initialElements.edges);
|
||||||
};
|
};
|
||||||
@@ -53,7 +55,14 @@ const StressFlow = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlow nodes={nodes} edges={edges} onLoad={onLoad} onNodesChange={onNodesChange}>
|
<ReactFlow
|
||||||
|
onlyRenderVisibleElements
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
onLoad={onLoad}
|
||||||
|
onConnect={onConnect}
|
||||||
|
onNodesChange={onNodesChange}
|
||||||
|
>
|
||||||
<MiniMap />
|
<MiniMap />
|
||||||
<Controls />
|
<Controls />
|
||||||
<Background />
|
<Background />
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ type ElementsCollection = {
|
|||||||
edges: Edge[];
|
edges: Edge[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getElements(xElements: number = 10, yElements: number = 10): ElementsCollection {
|
export function getNodesAndEdges(xElements: number = 10, yElements: number = 10): ElementsCollection {
|
||||||
const initialNodes = [];
|
const initialNodes = [];
|
||||||
const initialEdges: Edge[] = [];
|
const initialEdges: Edge[] = [];
|
||||||
let nodeId = 1;
|
let nodeId = 1;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { BrowserRouter as Router, Route, Switch, withRouter } from 'react-router
|
|||||||
import Basic from './Basic';
|
import Basic from './Basic';
|
||||||
import UpdateNode from './UpdateNode';
|
import UpdateNode from './UpdateNode';
|
||||||
import Stress from './Stress';
|
import Stress from './Stress';
|
||||||
|
import CustomNode from './CustomNode';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
@@ -21,6 +22,10 @@ const routes = [
|
|||||||
path: '/stress',
|
path: '/stress',
|
||||||
component: Stress,
|
component: Stress,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/custom-node',
|
||||||
|
component: CustomNode,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const Header = withRouter(({ history, location }) => {
|
const Header = withRouter(({ history, location }) => {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { MouseEvent as ReactMouseEvent } from 'react';
|
import { MouseEvent as ReactMouseEvent } from 'react';
|
||||||
import { GetState } from 'zustand';
|
|
||||||
|
|
||||||
import { getHostForElement } from '../../utils';
|
import { getHostForElement } from '../../utils';
|
||||||
import { ReactFlowState } from '../../types';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ElementId,
|
ElementId,
|
||||||
@@ -105,8 +103,7 @@ export function onMouseDown(
|
|||||||
onEdgeUpdateEnd?: (evt: MouseEvent) => void,
|
onEdgeUpdateEnd?: (evt: MouseEvent) => void,
|
||||||
onConnectStart?: OnConnectStartFunc,
|
onConnectStart?: OnConnectStartFunc,
|
||||||
onConnectStop?: OnConnectStopFunc,
|
onConnectStop?: OnConnectStopFunc,
|
||||||
onConnectEnd?: OnConnectEndFunc,
|
onConnectEnd?: OnConnectEndFunc
|
||||||
getState?: GetState<ReactFlowState>
|
|
||||||
): void {
|
): void {
|
||||||
const reactFlowNode = (event.target as Element).closest('.react-flow');
|
const reactFlowNode = (event.target as Element).closest('.react-flow');
|
||||||
// when react-flow is used inside a shadow root we can't use document
|
// when react-flow is used inside a shadow root we can't use document
|
||||||
@@ -180,8 +177,7 @@ export function onMouseDown(
|
|||||||
onConnectStop?.(event);
|
onConnectStop?.(event);
|
||||||
|
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
const nodes = getState?.().nodes;
|
onConnect?.(connection);
|
||||||
onConnect?.(connection, nodes || []);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onConnectEnd?.(event);
|
onConnectEnd?.(event);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import shallow from 'zustand/shallow';
|
|||||||
|
|
||||||
import { useStore } from '../../store';
|
import { useStore } from '../../store';
|
||||||
import NodeIdContext from '../../contexts/NodeIdContext';
|
import NodeIdContext from '../../contexts/NodeIdContext';
|
||||||
import { HandleProps, Connection, ElementId, Position, Node, ReactFlowState } from '../../types';
|
import { HandleProps, Connection, ElementId, Position, ReactFlowState } from '../../types';
|
||||||
|
|
||||||
import { onMouseDown, SetSourceIdFunc, SetPosition } from './handler';
|
import { onMouseDown, SetSourceIdFunc, SetPosition } from './handler';
|
||||||
|
|
||||||
@@ -52,9 +52,9 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|||||||
const isTarget = type === 'target';
|
const isTarget = type === 'target';
|
||||||
|
|
||||||
const onConnectExtended = useCallback(
|
const onConnectExtended = useCallback(
|
||||||
(params: Connection, nodes: Node[]) => {
|
(params: Connection) => {
|
||||||
onConnectAction?.(params, nodes);
|
onConnectAction?.(params);
|
||||||
onConnect?.(params, nodes);
|
onConnect?.(params);
|
||||||
},
|
},
|
||||||
[onConnectAction, onConnect]
|
[onConnectAction, onConnect]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const selector = (s: ReactFlowState) => ({
|
|||||||
unsetNodesSelection: s.unsetNodesSelection,
|
unsetNodesSelection: s.unsetNodesSelection,
|
||||||
updateNodePosition: s.updateNodePosition,
|
updateNodePosition: s.updateNodePosition,
|
||||||
updateNodeDimensions: s.updateNodeDimensions,
|
updateNodeDimensions: s.updateNodeDimensions,
|
||||||
|
unselectNodesAndEdges: s.unselectNodesAndEdges,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
||||||
@@ -48,10 +49,13 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
|||||||
resizeObserver,
|
resizeObserver,
|
||||||
dragHandle,
|
dragHandle,
|
||||||
}: WrapNodeProps) => {
|
}: WrapNodeProps) => {
|
||||||
const { addSelectedElements, unsetNodesSelection, updateNodePosition, updateNodeDimensions } = useStore(
|
const {
|
||||||
selector,
|
addSelectedElements,
|
||||||
shallow
|
unselectNodesAndEdges,
|
||||||
);
|
unsetNodesSelection,
|
||||||
|
updateNodePosition,
|
||||||
|
updateNodeDimensions,
|
||||||
|
} = useStore(selector, shallow);
|
||||||
const nodeElement = useRef<HTMLDivElement>(null);
|
const nodeElement = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const node = useMemo(() => ({ id, type, position: { x: xPos, y: yPos }, data }), [id, type, xPos, yPos, data]);
|
const node = useMemo(() => ({ id, type, position: { x: xPos, y: yPos }, data }), [id, type, xPos, yPos, data]);
|
||||||
@@ -142,8 +146,8 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
|||||||
addSelectedElements([node]);
|
addSelectedElements([node]);
|
||||||
}
|
}
|
||||||
} else if (!selectNodesOnDrag && !isSelected && isSelectable) {
|
} else if (!selectNodesOnDrag && !isSelected && isSelectable) {
|
||||||
|
unselectNodesAndEdges();
|
||||||
unsetNodesSelection();
|
unsetNodesSelection();
|
||||||
addSelectedElements([]);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[node, isSelected, selectNodesOnDrag, isSelectable, onNodeDragStart]
|
[node, isSelected, selectNodesOnDrag, isSelectable, onNodeDragStart]
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import shallow from 'zustand/shallow';
|
import shallow from 'zustand/shallow';
|
||||||
|
|
||||||
import { Elements, ReactFlowState } from '../../types';
|
import { ReactFlowState, OnSelectionChangeFunc } from '../../types';
|
||||||
import { useStore } from '../../store';
|
import { useStore } from '../../store';
|
||||||
|
|
||||||
interface SelectionListenerProps {
|
interface SelectionListenerProps {
|
||||||
onSelectionChange: (elements: Elements | null) => void;
|
onSelectionChange: OnSelectionChangeFunc;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedElementsSelector = (s: ReactFlowState) => [
|
const selectedElementsSelector = (s: ReactFlowState) => ({
|
||||||
...s.nodes.filter((n) => n.isSelected),
|
selectedNodes: s.nodes.filter((n) => n.isSelected),
|
||||||
...s.edges.filter((e) => e.isSelected),
|
selectedEdges: s.edges.filter((e) => e.isSelected),
|
||||||
];
|
});
|
||||||
|
|
||||||
// This is just a helper component for calling the onSelectionChange listener.
|
// This is just a helper component for calling the onSelectionChange listener.
|
||||||
|
|
||||||
export default ({ onSelectionChange }: SelectionListenerProps) => {
|
export default ({ onSelectionChange }: SelectionListenerProps) => {
|
||||||
const selectedElements = useStore(selectedElementsSelector, shallow);
|
const { selectedNodes, selectedEdges } = useStore(selectedElementsSelector, shallow);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onSelectionChange(selectedElements);
|
onSelectionChange({ nodes: selectedNodes, edges: selectedEdges });
|
||||||
}, [selectedElements]);
|
}, [selectedNodes, selectedEdges]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ import shallow from 'zustand/shallow';
|
|||||||
import { useStore } from '../../store';
|
import { useStore } from '../../store';
|
||||||
import ConnectionLine from '../../components/ConnectionLine/index';
|
import ConnectionLine from '../../components/ConnectionLine/index';
|
||||||
import MarkerDefinitions from './MarkerDefinitions';
|
import MarkerDefinitions from './MarkerDefinitions';
|
||||||
import { getEdgePositions, getHandle, getSourceTargetNodes } from './utils';
|
import { getEdgePositions, getHandle, getSourceTargetNodes, isEdgeVisible } from './utils';
|
||||||
import {
|
import {
|
||||||
Position,
|
Position,
|
||||||
Edge,
|
Edge,
|
||||||
Node,
|
|
||||||
Connection,
|
Connection,
|
||||||
ConnectionLineType,
|
ConnectionLineType,
|
||||||
ConnectionLineComponent,
|
ConnectionLineComponent,
|
||||||
@@ -19,8 +18,6 @@ import {
|
|||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
interface EdgeRendererProps {
|
interface EdgeRendererProps {
|
||||||
nodes: Node[];
|
|
||||||
edges: Edge[];
|
|
||||||
edgeTypes: any;
|
edgeTypes: any;
|
||||||
connectionLineType: ConnectionLineType;
|
connectionLineType: ConnectionLineType;
|
||||||
connectionLineStyle?: CSSProperties;
|
connectionLineStyle?: CSSProperties;
|
||||||
@@ -162,26 +159,12 @@ const Edge = memo(
|
|||||||
targetPosition
|
targetPosition
|
||||||
);
|
);
|
||||||
|
|
||||||
// const isVisible = onlyRenderVisibleElements
|
|
||||||
// ? isEdgeVisible({
|
|
||||||
// sourcePos: { x: sourceX, y: sourceY },
|
|
||||||
// targetPos: { x: targetX, y: targetY },
|
|
||||||
// width,
|
|
||||||
// height,
|
|
||||||
// transform,
|
|
||||||
// })
|
|
||||||
// : true;
|
|
||||||
|
|
||||||
// if (!isVisible) {
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EdgeComponent
|
<EdgeComponent
|
||||||
key={edge.id}
|
key={edge.id}
|
||||||
id={edge.id}
|
id={edge.id}
|
||||||
className={edge.className}
|
className={edge.className}
|
||||||
type={edge.type}
|
type={edgeType}
|
||||||
data={edge.data}
|
data={edge.data}
|
||||||
onClick={onEdgeClick}
|
onClick={onEdgeClick}
|
||||||
isSelected={!!edge.isSelected}
|
isSelected={!!edge.isSelected}
|
||||||
@@ -233,6 +216,7 @@ const selector = (s: ReactFlowState) => ({
|
|||||||
width: s.width,
|
width: s.width,
|
||||||
height: s.height,
|
height: s.height,
|
||||||
connectionMode: s.connectionMode,
|
connectionMode: s.connectionMode,
|
||||||
|
nodes: s.nodes,
|
||||||
});
|
});
|
||||||
|
|
||||||
const EdgeRenderer = (props: EdgeRendererProps) => {
|
const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||||
@@ -247,8 +231,42 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
connectionMode,
|
connectionMode,
|
||||||
|
nodes,
|
||||||
} = useStore(selector, shallow);
|
} = useStore(selector, shallow);
|
||||||
|
|
||||||
|
const edges = useStore(
|
||||||
|
useCallback(
|
||||||
|
(s: ReactFlowState) => {
|
||||||
|
if (!props.onlyRenderVisibleElements) {
|
||||||
|
return s.edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.edges.filter((e) => {
|
||||||
|
const { sourceNode, targetNode } = getSourceTargetNodes(e, s.nodes);
|
||||||
|
|
||||||
|
return (
|
||||||
|
sourceNode?.width &&
|
||||||
|
sourceNode?.height &&
|
||||||
|
targetNode?.width &&
|
||||||
|
targetNode?.height &&
|
||||||
|
isEdgeVisible({
|
||||||
|
sourcePos: sourceNode.position,
|
||||||
|
targetPos: targetNode.position,
|
||||||
|
sourceWidth: sourceNode.width,
|
||||||
|
sourceHeight: sourceNode.height,
|
||||||
|
targetWidth: targetNode.width,
|
||||||
|
targetHeight: targetNode.height,
|
||||||
|
width: s.width,
|
||||||
|
height: s.height,
|
||||||
|
transform: s.transform,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[props.onlyRenderVisibleElements]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
if (!width) {
|
if (!width) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -260,8 +278,8 @@ 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={`translate(${transform[0]},${transform[1]}) scale(${transform[2]})`}>
|
<g transform={`translate(${transform[0]},${transform[1]}) scale(${transform[2]})`}>
|
||||||
{props.edges.map((edge: Edge) => {
|
{edges.map((edge: Edge) => {
|
||||||
const { sourceNode, targetNode } = getSourceTargetNodes(edge, props.nodes);
|
const { sourceNode, targetNode } = getSourceTargetNodes(edge, nodes);
|
||||||
return (
|
return (
|
||||||
<Edge
|
<Edge
|
||||||
key={edge.id}
|
key={edge.id}
|
||||||
|
|||||||
@@ -116,17 +116,31 @@ export const getEdgePositions = (
|
|||||||
interface IsEdgeVisibleParams {
|
interface IsEdgeVisibleParams {
|
||||||
sourcePos: XYPosition;
|
sourcePos: XYPosition;
|
||||||
targetPos: XYPosition;
|
targetPos: XYPosition;
|
||||||
|
sourceWidth: number;
|
||||||
|
sourceHeight: number;
|
||||||
|
targetWidth: number;
|
||||||
|
targetHeight: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
transform: Transform;
|
transform: Transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isEdgeVisible({ sourcePos, targetPos, width, height, transform }: IsEdgeVisibleParams): boolean {
|
export function isEdgeVisible({
|
||||||
|
sourcePos,
|
||||||
|
targetPos,
|
||||||
|
sourceWidth,
|
||||||
|
sourceHeight,
|
||||||
|
targetWidth,
|
||||||
|
targetHeight,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
transform,
|
||||||
|
}: IsEdgeVisibleParams): boolean {
|
||||||
const edgeBox = {
|
const edgeBox = {
|
||||||
x: Math.min(sourcePos.x, targetPos.x),
|
x: Math.min(sourcePos.x, targetPos.x),
|
||||||
y: Math.min(sourcePos.y, targetPos.y),
|
y: Math.min(sourcePos.y, targetPos.y),
|
||||||
x2: Math.max(sourcePos.x, targetPos.x),
|
x2: Math.max(sourcePos.x + sourceWidth, targetPos.x + targetWidth),
|
||||||
y2: Math.max(sourcePos.y, targetPos.y),
|
y2: Math.max(sourcePos.y + sourceHeight, targetPos.y + targetHeight),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (edgeBox.x === edgeBox.x2) {
|
if (edgeBox.x === edgeBox.x2) {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { ReactFlowState } from '../../types';
|
|||||||
interface FlowRendererProps
|
interface FlowRendererProps
|
||||||
extends Omit<
|
extends Omit<
|
||||||
GraphViewProps,
|
GraphViewProps,
|
||||||
| 'elements'
|
|
||||||
| 'snapToGrid'
|
| 'snapToGrid'
|
||||||
| 'nodeTypes'
|
| 'nodeTypes'
|
||||||
| 'edgeTypes'
|
| 'edgeTypes'
|
||||||
@@ -22,8 +21,6 @@ interface FlowRendererProps
|
|||||||
| 'arrowHeadColor'
|
| 'arrowHeadColor'
|
||||||
| 'onlyRenderVisibleElements'
|
| 'onlyRenderVisibleElements'
|
||||||
| 'selectNodesOnDrag'
|
| 'selectNodesOnDrag'
|
||||||
| 'nodes'
|
|
||||||
| 'edges'
|
|
||||||
> {
|
> {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import { useStoreApi } from '../../store';
|
|||||||
import FlowRenderer from '../FlowRenderer';
|
import FlowRenderer from '../FlowRenderer';
|
||||||
import NodeRenderer from '../NodeRenderer';
|
import NodeRenderer from '../NodeRenderer';
|
||||||
import EdgeRenderer from '../EdgeRenderer';
|
import EdgeRenderer from '../EdgeRenderer';
|
||||||
import { onLoadProject, onLoadGetElements, onLoadToObject } from '../../utils/graph';
|
import { onLoadProject, onLoadGetNodes, onLoadGetEdges, onLoadToObject } from '../../utils/graph';
|
||||||
import useZoomPanHelper from '../../hooks/useZoomPanHelper';
|
import useZoomPanHelper from '../../hooks/useZoomPanHelper';
|
||||||
|
|
||||||
import { ReactFlowProps } from '../ReactFlow';
|
import { ReactFlowProps } from '../ReactFlow';
|
||||||
|
|
||||||
import { NodeTypesType, EdgeTypesType, ConnectionLineType, KeyCode } from '../../types';
|
import { NodeTypesType, EdgeTypesType, ConnectionLineType, KeyCode } from '../../types';
|
||||||
|
|
||||||
export interface GraphViewProps extends Omit<ReactFlowProps, 'onSelectionChange'> {
|
export interface GraphViewProps extends Omit<ReactFlowProps, 'onSelectionChange' | 'nodes' | 'edges'> {
|
||||||
nodeTypes: NodeTypesType;
|
nodeTypes: NodeTypesType;
|
||||||
edgeTypes: EdgeTypesType;
|
edgeTypes: EdgeTypesType;
|
||||||
selectionKeyCode: KeyCode;
|
selectionKeyCode: KeyCode;
|
||||||
@@ -26,8 +26,6 @@ export interface GraphViewProps extends Omit<ReactFlowProps, 'onSelectionChange'
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GraphView = ({
|
const GraphView = ({
|
||||||
nodes,
|
|
||||||
edges,
|
|
||||||
nodeTypes,
|
nodeTypes,
|
||||||
edgeTypes,
|
edgeTypes,
|
||||||
onMove,
|
onMove,
|
||||||
@@ -97,7 +95,8 @@ const GraphView = ({
|
|||||||
zoomTo,
|
zoomTo,
|
||||||
setTransform: transform,
|
setTransform: transform,
|
||||||
project: onLoadProject(store.getState),
|
project: onLoadProject(store.getState),
|
||||||
getElements: onLoadGetElements(store.getState),
|
getNodes: onLoadGetNodes(store.getState),
|
||||||
|
getEdges: onLoadGetEdges(store.getState),
|
||||||
toObject: onLoadToObject(store.getState),
|
toObject: onLoadToObject(store.getState),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -147,7 +146,6 @@ const GraphView = ({
|
|||||||
onNodeDragStart={onNodeDragStart}
|
onNodeDragStart={onNodeDragStart}
|
||||||
selectNodesOnDrag={selectNodesOnDrag}
|
selectNodesOnDrag={selectNodesOnDrag}
|
||||||
onlyRenderVisibleElements={onlyRenderVisibleElements}
|
onlyRenderVisibleElements={onlyRenderVisibleElements}
|
||||||
nodes={nodes}
|
|
||||||
/>
|
/>
|
||||||
<EdgeRenderer
|
<EdgeRenderer
|
||||||
edgeTypes={edgeTypes}
|
edgeTypes={edgeTypes}
|
||||||
@@ -167,8 +165,6 @@ const GraphView = ({
|
|||||||
onEdgeUpdateStart={onEdgeUpdateStart}
|
onEdgeUpdateStart={onEdgeUpdateStart}
|
||||||
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
||||||
edgeUpdaterRadius={edgeUpdaterRadius}
|
edgeUpdaterRadius={edgeUpdaterRadius}
|
||||||
nodes={nodes}
|
|
||||||
edges={edges}
|
|
||||||
/>
|
/>
|
||||||
</FlowRenderer>
|
</FlowRenderer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import React, { memo, useMemo, ComponentType, MouseEvent } from 'react';
|
import React, { memo, useMemo, ComponentType, MouseEvent, useCallback } from 'react';
|
||||||
import shallow from 'zustand/shallow';
|
import shallow from 'zustand/shallow';
|
||||||
|
|
||||||
import { useStore } from '../../store';
|
import { useStore } from '../../store';
|
||||||
import { Node, NodeTypesType, ReactFlowState, WrapNodeProps } from '../../types';
|
import { Node, NodeTypesType, ReactFlowState, WrapNodeProps } from '../../types';
|
||||||
|
import { getNodesInside } from '../../utils/graph';
|
||||||
interface NodeRendererProps {
|
interface NodeRendererProps {
|
||||||
nodeTypes: NodeTypesType;
|
nodeTypes: NodeTypesType;
|
||||||
selectNodesOnDrag: boolean;
|
selectNodesOnDrag: boolean;
|
||||||
@@ -16,7 +17,6 @@ interface NodeRendererProps {
|
|||||||
onNodeDrag?: (event: MouseEvent, node: Node) => void;
|
onNodeDrag?: (event: MouseEvent, node: Node) => void;
|
||||||
onNodeDragStop?: (event: MouseEvent, node: Node) => void;
|
onNodeDragStop?: (event: MouseEvent, node: Node) => void;
|
||||||
onlyRenderVisibleElements: boolean;
|
onlyRenderVisibleElements: boolean;
|
||||||
nodes: Node[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const selector = (s: ReactFlowState) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
@@ -40,9 +40,16 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
|||||||
snapToGrid,
|
snapToGrid,
|
||||||
} = useStore(selector, shallow);
|
} = useStore(selector, shallow);
|
||||||
|
|
||||||
// const visibleNodes = props.onlyRenderVisibleElements
|
const nodes = useStore(
|
||||||
// ? getNodesInside(nodes, { x: 0, y: 0, width, height }, transform, true)
|
useCallback(
|
||||||
// : nodes;
|
(s: ReactFlowState) => {
|
||||||
|
return props.onlyRenderVisibleElements
|
||||||
|
? getNodesInside(s.nodes, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true)
|
||||||
|
: s.nodes;
|
||||||
|
},
|
||||||
|
[props.onlyRenderVisibleElements]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const transformStyle = useMemo(
|
const transformStyle = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -68,7 +75,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="react-flow__nodes" style={transformStyle}>
|
<div className="react-flow__nodes" style={transformStyle}>
|
||||||
{props.nodes.map((node) => {
|
{nodes.map((node) => {
|
||||||
const nodeType = node.type || 'default';
|
const nodeType = node.type || 'default';
|
||||||
const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType<WrapNodeProps>;
|
const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType<WrapNodeProps>;
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../compon
|
|||||||
import { createEdgeTypes } from '../EdgeRenderer/utils';
|
import { createEdgeTypes } from '../EdgeRenderer/utils';
|
||||||
import Wrapper from './Wrapper';
|
import Wrapper from './Wrapper';
|
||||||
import {
|
import {
|
||||||
Elements,
|
OnSelectionChangeFunc,
|
||||||
NodeTypesType,
|
NodeTypesType,
|
||||||
EdgeTypesType,
|
EdgeTypesType,
|
||||||
OnLoadFunc,
|
OnLoadFunc,
|
||||||
@@ -81,7 +81,7 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
|
|||||||
onMove?: (flowTransform?: FlowTransform) => void;
|
onMove?: (flowTransform?: FlowTransform) => void;
|
||||||
onMoveStart?: (flowTransform?: FlowTransform) => void;
|
onMoveStart?: (flowTransform?: FlowTransform) => void;
|
||||||
onMoveEnd?: (flowTransform?: FlowTransform) => void;
|
onMoveEnd?: (flowTransform?: FlowTransform) => void;
|
||||||
onSelectionChange?: (elements: Elements | null) => void;
|
onSelectionChange?: OnSelectionChangeFunc;
|
||||||
onSelectionDragStart?: (event: ReactMouseEvent, nodes: Node[]) => void;
|
onSelectionDragStart?: (event: ReactMouseEvent, nodes: Node[]) => void;
|
||||||
onSelectionDrag?: (event: ReactMouseEvent, nodes: Node[]) => void;
|
onSelectionDrag?: (event: ReactMouseEvent, nodes: Node[]) => void;
|
||||||
onSelectionDragStop?: (event: ReactMouseEvent, nodes: Node[]) => void;
|
onSelectionDragStop?: (event: ReactMouseEvent, nodes: Node[]) => void;
|
||||||
@@ -227,8 +227,6 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
|||||||
<div {...rest} ref={ref} className={reactFlowClasses}>
|
<div {...rest} ref={ref} className={reactFlowClasses}>
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<GraphView
|
<GraphView
|
||||||
nodes={nodes}
|
|
||||||
edges={edges}
|
|
||||||
onLoad={onLoad}
|
onLoad={onLoad}
|
||||||
onMove={onMove}
|
onMove={onMove}
|
||||||
onMoveStart={onMoveStart}
|
onMoveStart={onMoveStart}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ export { getMarkerEnd, getCenter as getEdgeCenter } from './components/Edges/uti
|
|||||||
export {
|
export {
|
||||||
isNode,
|
isNode,
|
||||||
isEdge,
|
isEdge,
|
||||||
removeElements,
|
|
||||||
addEdge,
|
addEdge,
|
||||||
getOutgoers,
|
getOutgoers,
|
||||||
getIncomers,
|
getIncomers,
|
||||||
|
|||||||
+39
-33
@@ -10,7 +10,6 @@ import {
|
|||||||
NodeDimensionUpdate,
|
NodeDimensionUpdate,
|
||||||
NodeDiffUpdate,
|
NodeDiffUpdate,
|
||||||
XYPosition,
|
XYPosition,
|
||||||
Elements,
|
|
||||||
InitD3ZoomPayload,
|
InitD3ZoomPayload,
|
||||||
TranslateExtent,
|
TranslateExtent,
|
||||||
NodeExtent,
|
NodeExtent,
|
||||||
@@ -33,14 +32,11 @@ import { getHandleBounds } from '../components/Nodes/utils';
|
|||||||
|
|
||||||
const { Provider, useStore, useStoreApi } = createContext<ReactFlowState>();
|
const { Provider, useStore, useStoreApi } = createContext<ReactFlowState>();
|
||||||
|
|
||||||
const unselectElements = (elements: Elements): NodeChange[] | EdgeChange[] =>
|
const createNodeOrEdgeSelectionChange = (isSelected: boolean) => (item: Node | Edge) => ({
|
||||||
elements
|
id: item.id,
|
||||||
.filter((e) => e.isSelected)
|
type: 'select',
|
||||||
.map((e) => ({
|
isSelected,
|
||||||
id: e.id,
|
});
|
||||||
type: 'select',
|
|
||||||
isSelected: false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const createStore = () =>
|
const createStore = () =>
|
||||||
create<ReactFlowState>((set, get) => ({
|
create<ReactFlowState>((set, get) => ({
|
||||||
@@ -219,8 +215,12 @@ const createStore = () =>
|
|||||||
const selectedEdgeIds = getConnectedEdges(selectedNodes, edges).map((e) => e.id);
|
const selectedEdgeIds = getConnectedEdges(selectedNodes, edges).map((e) => e.id);
|
||||||
const selectedNodeIds = selectedNodes.map((n) => n.id);
|
const selectedNodeIds = selectedNodes.map((n) => n.id);
|
||||||
|
|
||||||
onNodesChange?.(nodes.map((n) => ({ id: n.id, type: 'select', isSelected: selectedNodeIds.includes(n.id) })));
|
onNodesChange?.(
|
||||||
onEdgesChange?.(edges.map((e) => ({ id: e.id, type: 'select', isSelected: selectedEdgeIds.includes(e.id) })));
|
nodes.map((n) => createNodeOrEdgeSelectionChange(selectedNodeIds.includes(n.id))(n)) as NodeChange[]
|
||||||
|
);
|
||||||
|
onEdgesChange?.(
|
||||||
|
edges.map((e) => createNodeOrEdgeSelectionChange(selectedEdgeIds.includes(e.id))(e)) as EdgeChange[]
|
||||||
|
);
|
||||||
|
|
||||||
set({
|
set({
|
||||||
userSelectionRect: nextUserSelectRect,
|
userSelectionRect: nextUserSelectRect,
|
||||||
@@ -248,31 +248,22 @@ const createStore = () =>
|
|||||||
|
|
||||||
set(stateUpdate);
|
set(stateUpdate);
|
||||||
},
|
},
|
||||||
addSelectedElements: (elements: Elements) => {
|
addSelectedElements: (selectedElementsArr: Array<Node | Edge>) => {
|
||||||
const { multiSelectionActive, onNodesChange, onEdgesChange, nodes, edges } = get();
|
const { multiSelectionActive, onNodesChange, onEdgesChange, nodes, edges } = get();
|
||||||
const selectedElementsArr = Array.isArray(elements) ? elements : [elements];
|
|
||||||
|
|
||||||
let changedNodes;
|
let changedNodes;
|
||||||
let changedEdges;
|
let changedEdges;
|
||||||
|
|
||||||
if (multiSelectionActive) {
|
if (multiSelectionActive) {
|
||||||
changedNodes = selectedElementsArr
|
changedNodes = selectedElementsArr.filter(isNode).map(createNodeOrEdgeSelectionChange(true));
|
||||||
.filter(isNode)
|
changedEdges = selectedElementsArr.filter(isEdge).map(createNodeOrEdgeSelectionChange(true));
|
||||||
.map((node) => ({ id: node.id, type: 'select', isSelected: true }));
|
|
||||||
changedEdges = selectedElementsArr
|
|
||||||
.filter(isEdge)
|
|
||||||
.map((edge) => ({ id: edge.id, type: 'select', isSelected: true }));
|
|
||||||
} else {
|
} else {
|
||||||
changedNodes = nodes.map((node) => ({
|
changedNodes = nodes.map((node) =>
|
||||||
id: node.id,
|
createNodeOrEdgeSelectionChange(selectedElementsArr.some((e) => e.id === node.id))(node)
|
||||||
type: 'select',
|
);
|
||||||
isSelected: selectedElementsArr.some((e) => e.id === node.id),
|
changedEdges = edges.map((edge) =>
|
||||||
}));
|
createNodeOrEdgeSelectionChange(selectedElementsArr.some((e) => e.id === edge.id))(edge)
|
||||||
changedEdges = edges.map((edge) => ({
|
);
|
||||||
id: edge.id,
|
|
||||||
type: 'select',
|
|
||||||
isSelected: selectedElementsArr.some((e) => e.id === edge.id),
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changedNodes.length) {
|
if (changedNodes.length) {
|
||||||
@@ -283,6 +274,21 @@ const createStore = () =>
|
|||||||
onEdgesChange?.(changedEdges as EdgeChange[]);
|
onEdgesChange?.(changedEdges as EdgeChange[]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
unselectNodesAndEdges: () => {
|
||||||
|
const { nodes, edges, onNodesChange, onEdgesChange } = get();
|
||||||
|
const nodesToUnselect = nodes.map((n) => {
|
||||||
|
n.isSelected = false;
|
||||||
|
return createNodeOrEdgeSelectionChange(false)(n);
|
||||||
|
}) as NodeChange[];
|
||||||
|
const edgesToUnselect = edges.map(createNodeOrEdgeSelectionChange(false)) as EdgeChange[];
|
||||||
|
|
||||||
|
if (nodesToUnselect.length) {
|
||||||
|
onNodesChange?.(nodesToUnselect);
|
||||||
|
}
|
||||||
|
if (edgesToUnselect.length) {
|
||||||
|
onEdgesChange?.(edgesToUnselect);
|
||||||
|
}
|
||||||
|
},
|
||||||
initD3Zoom: ({ d3Zoom, d3Selection, d3ZoomHandler, transform }: InitD3ZoomPayload) =>
|
initD3Zoom: ({ d3Zoom, d3Selection, d3ZoomHandler, transform }: InitD3ZoomPayload) =>
|
||||||
set({
|
set({
|
||||||
d3Zoom,
|
d3Zoom,
|
||||||
@@ -312,14 +318,14 @@ const createStore = () =>
|
|||||||
resetSelectedElements: () => {
|
resetSelectedElements: () => {
|
||||||
const { nodes, edges, onNodesChange, onEdgesChange } = get();
|
const { nodes, edges, onNodesChange, onEdgesChange } = get();
|
||||||
|
|
||||||
const nodesToUnselect = unselectElements(nodes) as NodeChange[];
|
const nodesToUnselect = nodes.filter((e) => e.isSelected).map(createNodeOrEdgeSelectionChange(false));
|
||||||
const edgesToUnselect = unselectElements(edges) as EdgeChange[];
|
const edgesToUnselect = edges.filter((e) => e.isSelected).map(createNodeOrEdgeSelectionChange(false));
|
||||||
|
|
||||||
if (nodesToUnselect.length) {
|
if (nodesToUnselect.length) {
|
||||||
onNodesChange?.(nodesToUnselect);
|
onNodesChange?.(nodesToUnselect as NodeChange[]);
|
||||||
}
|
}
|
||||||
if (edgesToUnselect.length) {
|
if (edgesToUnselect.length) {
|
||||||
onEdgesChange?.(edgesToUnselect);
|
onEdgesChange?.(edgesToUnselect as EdgeChange[]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setNodeExtent: (nodeExtent: NodeExtent) =>
|
setNodeExtent: (nodeExtent: NodeExtent) =>
|
||||||
|
|||||||
+6
-2
@@ -312,7 +312,8 @@ export type OnLoadParams<T = any> = {
|
|||||||
zoomTo: (zoomLevel: number) => void;
|
zoomTo: (zoomLevel: number) => void;
|
||||||
fitView: FitViewFunc;
|
fitView: FitViewFunc;
|
||||||
project: ProjectFunc;
|
project: ProjectFunc;
|
||||||
getElements: () => Elements<T>;
|
getNodes: () => Node<T>[];
|
||||||
|
getEdges: () => Edge<T>[];
|
||||||
setTransform: (transform: FlowTransform) => void;
|
setTransform: (transform: FlowTransform) => void;
|
||||||
toObject: ToObjectFunc<T>;
|
toObject: ToObjectFunc<T>;
|
||||||
};
|
};
|
||||||
@@ -353,7 +354,7 @@ export type ConnectionLineComponentProps = {
|
|||||||
|
|
||||||
export type ConnectionLineComponent = React.ComponentType<ConnectionLineComponentProps>;
|
export type ConnectionLineComponent = React.ComponentType<ConnectionLineComponentProps>;
|
||||||
|
|
||||||
export type OnConnectFunc = (connection: Connection, nodes: Node[]) => void;
|
export type OnConnectFunc = (connection: Connection) => void;
|
||||||
export type OnConnectStartParams = {
|
export type OnConnectStartParams = {
|
||||||
nodeId: ElementId | null;
|
nodeId: ElementId | null;
|
||||||
handleId: ElementId | null;
|
handleId: ElementId | null;
|
||||||
@@ -490,6 +491,7 @@ export interface ReactFlowState {
|
|||||||
unsetUserSelection: () => void;
|
unsetUserSelection: () => void;
|
||||||
unsetNodesSelection: () => void;
|
unsetNodesSelection: () => void;
|
||||||
resetSelectedElements: () => void;
|
resetSelectedElements: () => void;
|
||||||
|
unselectNodesAndEdges: () => void;
|
||||||
addSelectedElements: (elements: Elements) => void;
|
addSelectedElements: (elements: Elements) => void;
|
||||||
updateTransform: (transform: Transform) => void;
|
updateTransform: (transform: Transform) => void;
|
||||||
updateSize: (size: Dimensions) => void;
|
updateSize: (size: Dimensions) => void;
|
||||||
@@ -522,3 +524,5 @@ export interface ReactFlowState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type UpdateNodeInternals = (nodeId: ElementId) => void;
|
export type UpdateNodeInternals = (nodeId: ElementId) => void;
|
||||||
|
|
||||||
|
export type OnSelectionChangeFunc = (params: { nodes: Node[]; edges: Edge[] }) => void;
|
||||||
|
|||||||
+32
-36
@@ -24,52 +24,38 @@ export const isEdge = (element: Node | Connection | Edge): element is Edge =>
|
|||||||
export const isNode = (element: Node | Connection | Edge): element is Node =>
|
export const isNode = (element: Node | Connection | Edge): element is Node =>
|
||||||
'id' in element && !('source' in element) && !('target' in element);
|
'id' in element && !('source' in element) && !('target' in element);
|
||||||
|
|
||||||
export const getOutgoers = (node: Node, elements: Elements): Node[] => {
|
export const getOutgoers = (node: Node, nodes: Node[], edges: Edge[]): Node[] => {
|
||||||
if (!isNode(node)) {
|
if (!isNode(node)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const outgoerIds = elements.filter((e) => isEdge(e) && e.source === node.id).map((e) => (e as Edge).target);
|
const outgoerIds = edges.filter((e) => e.source === node.id).map((e) => e.target);
|
||||||
return elements.filter((e) => outgoerIds.includes(e.id)) as Node[];
|
return nodes.filter((n) => outgoerIds.includes(n.id));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getIncomers = (node: Node, elements: Elements): Node[] => {
|
export const getIncomers = (node: Node, nodes: Node[], edges: Edge[]): Node[] => {
|
||||||
if (!isNode(node)) {
|
if (!isNode(node)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const incomersIds = elements.filter((e) => isEdge(e) && e.target === node.id).map((e) => (e as Edge).source);
|
const incomersIds = edges.filter((e) => e.target === node.id).map((e) => e.source);
|
||||||
return elements.filter((e) => incomersIds.includes(e.id)) as Node[];
|
return nodes.filter((n) => incomersIds.includes(n.id));
|
||||||
};
|
|
||||||
|
|
||||||
export const removeElements = (elementsToRemove: Elements, elements: Elements): Elements => {
|
|
||||||
const nodeIdsToRemove = elementsToRemove.map((n) => n.id);
|
|
||||||
|
|
||||||
return elements.filter((element) => {
|
|
||||||
const edgeElement = element as Edge;
|
|
||||||
return !(
|
|
||||||
nodeIdsToRemove.includes(element.id) ||
|
|
||||||
nodeIdsToRemove.includes(edgeElement.target) ||
|
|
||||||
nodeIdsToRemove.includes(edgeElement.source)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEdgeId = ({ source, sourceHandle, target, targetHandle }: Connection): ElementId =>
|
const getEdgeId = ({ source, sourceHandle, target, targetHandle }: Connection): ElementId =>
|
||||||
`reactflow__edge-${source}${sourceHandle}-${target}${targetHandle}`;
|
`reactflow__edge-${source}${sourceHandle}-${target}${targetHandle}`;
|
||||||
|
|
||||||
const connectionExists = (edge: Edge, elements: Elements) => {
|
const connectionExists = (edge: Edge, edges: Edge[]) => {
|
||||||
return elements.some(
|
return edges.some(
|
||||||
(el) =>
|
(e) =>
|
||||||
isEdge(el) &&
|
edge.source === e.source &&
|
||||||
el.source === edge.source &&
|
edge.target === e.target &&
|
||||||
el.target === edge.target &&
|
(edge.sourceHandle === e.sourceHandle || (!edge.sourceHandle && !e.sourceHandle)) &&
|
||||||
(el.sourceHandle === edge.sourceHandle || (!el.sourceHandle && !edge.sourceHandle)) &&
|
(edge.targetHandle === e.targetHandle || (!edge.targetHandle && !e.targetHandle))
|
||||||
(el.targetHandle === edge.targetHandle || (!el.targetHandle && !edge.targetHandle))
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addEdge = (edgeParams: Edge | Connection, nodes: Node[], edges: Edge[]): Edge[] => {
|
export const addEdge = (edgeParams: Edge | Connection, edges: Edge[]): Edge[] => {
|
||||||
if (!edgeParams.source || !edgeParams.target) {
|
if (!edgeParams.source || !edgeParams.target) {
|
||||||
console.warn("Can't create edge. An edge needs a source and a target.");
|
console.warn("Can't create edge. An edge needs a source and a target.");
|
||||||
return edges;
|
return edges;
|
||||||
@@ -85,7 +71,7 @@ export const addEdge = (edgeParams: Edge | Connection, nodes: Node[], edges: Edg
|
|||||||
} as Edge;
|
} as Edge;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectionExists(edge, nodes)) {
|
if (connectionExists(edge, edges)) {
|
||||||
return edges;
|
return edges;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +197,13 @@ export const getNodesInside = (
|
|||||||
const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y));
|
const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y));
|
||||||
const overlappingArea = Math.ceil(xOverlap * yOverlap);
|
const overlappingArea = Math.ceil(xOverlap * yOverlap);
|
||||||
|
|
||||||
if (width === null || height === null || isDragging) {
|
if (
|
||||||
|
typeof width === 'undefined' ||
|
||||||
|
typeof height === 'undefined' ||
|
||||||
|
width === null ||
|
||||||
|
height === null ||
|
||||||
|
isDragging
|
||||||
|
) {
|
||||||
// nodes are initialized with width and height = null
|
// nodes are initialized with width and height = null
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -232,15 +224,19 @@ export const getConnectedEdges = (nodes: Node[], edges: Edge[]): Edge[] => {
|
|||||||
return edges.filter((edge) => nodeIds.includes(edge.source) || nodeIds.includes(edge.target));
|
return edges.filter((edge) => nodeIds.includes(edge.source) || nodeIds.includes(edge.target));
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseElements = (nodes: Node[], edges: Edge[]): Elements => {
|
export const onLoadGetNodes = (getState: GetState<ReactFlowState>) => {
|
||||||
return [...nodes.map((n) => ({ ...n })), ...edges.map((e) => ({ ...e }))];
|
return (): Node[] => {
|
||||||
|
const { nodes = [] } = getState();
|
||||||
|
|
||||||
|
return nodes.map((n) => ({ ...n }));
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const onLoadGetElements = (getState: GetState<ReactFlowState>) => {
|
export const onLoadGetEdges = (getState: GetState<ReactFlowState>) => {
|
||||||
return (): Elements => {
|
return (): Edge[] => {
|
||||||
const { nodes = [], edges = [] } = getState();
|
const { edges = [] } = getState();
|
||||||
|
|
||||||
return parseElements(nodes, edges);
|
return edges.map((e) => ({ ...e }));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user