feat(wrapper): add onSelectionChange handler
This commit is contained in:
@@ -72,6 +72,7 @@ const BasicFlow = () => (
|
||||
- `onConnect`: connect handler
|
||||
- `onLoad`: editor load handler
|
||||
- `onMove`: move handler
|
||||
- `onSelectionChange`: fired when element selection changes
|
||||
- `nodeTypes`: object with [node types](#node-types--custom-nodes)
|
||||
- `edgeTypes`: object with [edge types](#edge-types--custom-edges)
|
||||
- `style`: css style passed to the wrapper
|
||||
|
||||
@@ -5,6 +5,7 @@ import ReactFlow, { removeElements, addEdge, MiniMap, Controls } from 'react-flo
|
||||
const onNodeDragStart = node => console.log('drag start', node);
|
||||
const onNodeDragStop = node => console.log('drag stop', node);
|
||||
const onElementClick = element => console.log('click', element);
|
||||
const onSelectionChange = elements => console.log('selection change', elements);
|
||||
const onLoad = (graph) => {
|
||||
console.log('graph loaded:', graph);
|
||||
graph.fitView();
|
||||
@@ -53,6 +54,7 @@ const OverviewFlow = () => {
|
||||
onConnect={onConnect}
|
||||
onNodeDragStart={onNodeDragStart}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
onSelectionChange={onSelectionChange}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
onLoad={onLoad}
|
||||
connectionLineStyle={{ stroke: '#ddd', strokeWidth: 2 }}
|
||||
|
||||
@@ -50,9 +50,11 @@ export default memo(() => {
|
||||
};
|
||||
const offsetX: number = scaledClient.x - position.x - tx;
|
||||
const offsetY: number = scaledClient.y - position.y - ty;
|
||||
const selectedNodes = (state.selectedElements.filter(isNode) as Node[]).map(
|
||||
(selectedNode) => state.nodes.find((node) => node.id === selectedNode.id)! as Node
|
||||
);
|
||||
const selectedNodes = state.selectedElements
|
||||
? (state.selectedElements.filter(isNode) as Node[]).map(
|
||||
(selectedNode) => state.nodes.find((node) => node.id === selectedNode.id)! as Node
|
||||
)
|
||||
: [];
|
||||
|
||||
const nextStartPositions = getStartPositions(selectedNodes);
|
||||
|
||||
@@ -68,14 +70,16 @@ export default memo(() => {
|
||||
y: evt.clientY / tScale,
|
||||
};
|
||||
|
||||
(state.selectedElements.filter(isNode) as Node[]).forEach((node) => {
|
||||
const pos: XYPosition = {
|
||||
x: startPositions[node.id].x + scaledClient.x - position.x - offset.x - tx,
|
||||
y: startPositions[node.id].y + scaledClient.y - position.y - offset.y - ty,
|
||||
};
|
||||
if (state.selectedElements) {
|
||||
(state.selectedElements.filter(isNode) as Node[]).forEach((node) => {
|
||||
const pos: XYPosition = {
|
||||
x: startPositions[node.id].x + scaledClient.x - position.x - offset.x - tx,
|
||||
y: startPositions[node.id].y + scaledClient.y - position.y - offset.y - ty,
|
||||
};
|
||||
|
||||
updateNodePos({ id: node.id, pos });
|
||||
});
|
||||
updateNodePos({ id: node.id, pos });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { Elements } from '../../types';
|
||||
import { useStoreState } from '../../store/hooks';
|
||||
|
||||
interface SelectionListenerProps {
|
||||
onSelectionChange: (elements: Elements | null) => void;
|
||||
}
|
||||
|
||||
// This is just a helper component for calling the onSelectionChange listener.
|
||||
// As soon as easy-peasy has implemented the effectOn hook, we can remove this compeonent
|
||||
// and use the hook instead. https://github.com/ctrlplusb/easy-peasy/pull/459
|
||||
|
||||
export default ({ onSelectionChange }: SelectionListenerProps) => {
|
||||
const selectedElements = useStoreState((s) => s.selectedElements);
|
||||
|
||||
useEffect(() => {
|
||||
onSelectionChange(selectedElements);
|
||||
}, [selectedElements]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -117,7 +117,7 @@ function renderEdge(
|
||||
edge: Edge,
|
||||
props: EdgeRendererProps,
|
||||
nodes: Node[],
|
||||
selectedElements: Elements,
|
||||
selectedElements: Elements | null,
|
||||
isInteractive: boolean
|
||||
) {
|
||||
const [sourceId, sourceHandleId] = edge.source.split('__');
|
||||
@@ -154,9 +154,9 @@ function renderEdge(
|
||||
targetPosition
|
||||
);
|
||||
|
||||
const isSelected = (selectedElements as Edge[]).some(
|
||||
(elm) => isEdge(elm) && elm.source === sourceId && elm.target === targetId
|
||||
);
|
||||
const isSelected = selectedElements
|
||||
? (selectedElements as Edge[]).some((elm) => isEdge(elm) && elm.source === sourceId && elm.target === targetId)
|
||||
: false;
|
||||
|
||||
return (
|
||||
<EdgeComponent
|
||||
|
||||
@@ -16,7 +16,7 @@ function renderNode(
|
||||
node: Node,
|
||||
props: NodeRendererProps,
|
||||
transform: Transform,
|
||||
selectedElements: Elements,
|
||||
selectedElements: Elements | null,
|
||||
isInteractive: boolean
|
||||
) {
|
||||
const nodeType = node.type || 'default';
|
||||
@@ -25,7 +25,7 @@ function renderNode(
|
||||
console.warn(`No node type found for type "${nodeType}". Using fallback type "default".`);
|
||||
}
|
||||
|
||||
const isSelected = selectedElements.some(({ id }) => id === node.id);
|
||||
const isSelected = selectedElements ? selectedElements.some(({ id }) => id === node.id) : false;
|
||||
|
||||
return (
|
||||
<NodeComponent
|
||||
|
||||
@@ -14,6 +14,7 @@ import DefaultNode from '../../components/Nodes/DefaultNode';
|
||||
import InputNode from '../../components/Nodes/InputNode';
|
||||
import OutputNode from '../../components/Nodes/OutputNode';
|
||||
import { createNodeTypes } from '../NodeRenderer/utils';
|
||||
import SelectionListener from '../../components/SelectionListener';
|
||||
import BezierEdge from '../../components/Edges/BezierEdge';
|
||||
import StraightEdge from '../../components/Edges/StraightEdge';
|
||||
import StepEdge from '../../components/Edges/StepEdge';
|
||||
@@ -32,6 +33,7 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
|
||||
onConnect: (connection: Edge | Connection) => void;
|
||||
onLoad: OnLoadFunc;
|
||||
onMove: () => void;
|
||||
onSelectionChange: (elements: Elements | null) => void;
|
||||
nodeTypes: NodeTypesType;
|
||||
edgeTypes: EdgeTypesType;
|
||||
connectionLineType: string;
|
||||
@@ -62,6 +64,7 @@ const ReactFlow = ({
|
||||
onConnect,
|
||||
onNodeDragStart,
|
||||
onNodeDragStop,
|
||||
onSelectionChange,
|
||||
connectionLineType,
|
||||
connectionLineStyle,
|
||||
deleteKeyCode,
|
||||
@@ -105,6 +108,7 @@ const ReactFlow = ({
|
||||
onlyRenderVisibleNodes={onlyRenderVisibleNodes}
|
||||
isInteractive={isInteractive}
|
||||
/>
|
||||
{onSelectionChange && <SelectionListener onSelectionChange={onSelectionChange} />}
|
||||
{children}
|
||||
</StoreProvider>
|
||||
</div>
|
||||
@@ -143,7 +147,6 @@ ReactFlow.defaultProps = {
|
||||
snapGrid: [16, 16],
|
||||
onlyRenderVisibleNodes: true,
|
||||
isInteractive: true,
|
||||
className: '',
|
||||
};
|
||||
|
||||
export default ReactFlow;
|
||||
|
||||
@@ -11,22 +11,19 @@ interface HookParams {
|
||||
}
|
||||
|
||||
export default ({ deleteKeyCode, onElementsRemove }: HookParams): void => {
|
||||
const state = useStoreState(s => ({
|
||||
const state = useStoreState((s) => ({
|
||||
selectedElements: s.selectedElements,
|
||||
edges: s.edges,
|
||||
}));
|
||||
const setNodesSelection = useStoreActions(a => a.setNodesSelection);
|
||||
const setNodesSelection = useStoreActions((a) => a.setNodesSelection);
|
||||
const deleteKeyPressed = useKeyPress(deleteKeyCode);
|
||||
|
||||
useEffect(() => {
|
||||
if (deleteKeyPressed && state.selectedElements.length) {
|
||||
if (deleteKeyPressed && state.selectedElements) {
|
||||
let elementsToRemove = state.selectedElements;
|
||||
|
||||
// we also want to remove the edges if only one node is selected
|
||||
if (
|
||||
state.selectedElements.length === 1 &&
|
||||
!isEdge(state.selectedElements[0])
|
||||
) {
|
||||
if (state.selectedElements.length === 1 && !isEdge(state.selectedElements[0])) {
|
||||
const node = (state.selectedElements[0] as unknown) as Node;
|
||||
const connectedEdges = getConnectedEdges([node], state.edges);
|
||||
elementsToRemove = [...state.selectedElements, ...connectedEdges];
|
||||
|
||||
+9
-19
@@ -56,7 +56,7 @@ export interface StoreModel {
|
||||
transform: Transform;
|
||||
nodes: Node[];
|
||||
edges: Edge[];
|
||||
selectedElements: Elements;
|
||||
selectedElements: Elements | null;
|
||||
selectedNodesBbox: Rect;
|
||||
|
||||
d3Zoom: ZoomBehavior<Element, unknown> | null;
|
||||
@@ -95,8 +95,6 @@ export interface StoreModel {
|
||||
|
||||
setSelectedElements: Action<StoreModel, Elements | Node | Edge>;
|
||||
|
||||
updateSelection: Action<StoreModel, SelectionRect>;
|
||||
|
||||
updateTransform: Action<StoreModel, TransformXYK>;
|
||||
|
||||
updateSize: Action<StoreModel, Dimensions>;
|
||||
@@ -122,7 +120,7 @@ const storeModel: StoreModel = {
|
||||
transform: [0, 0, 1],
|
||||
nodes: [],
|
||||
edges: [],
|
||||
selectedElements: [],
|
||||
selectedElements: null,
|
||||
selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
|
||||
|
||||
d3Zoom: null,
|
||||
@@ -250,8 +248,11 @@ const storeModel: StoreModel = {
|
||||
const selectedElementsUpdated = !isEqual(nextSelectedElements, state.selectedElements);
|
||||
|
||||
state.selection = nextRect;
|
||||
state.selectedElements = selectedElementsUpdated ? nextSelectedElements : state.selectedElements;
|
||||
state.userSelectionRect = nextRect;
|
||||
|
||||
if (selectedElementsUpdated) {
|
||||
state.selectedElements = nextSelectedElements.length > 0 ? nextSelectedElements : null;
|
||||
}
|
||||
}),
|
||||
|
||||
unsetUserSelection: action((state) => {
|
||||
@@ -261,7 +262,7 @@ const storeModel: StoreModel = {
|
||||
state.selectionActive = false;
|
||||
state.userSelectionRect = { ...state.userSelectionRect, draw: false };
|
||||
state.nodesSelectionActive = false;
|
||||
state.selectedElements = [];
|
||||
state.selectedElements = null;
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -283,7 +284,7 @@ const storeModel: StoreModel = {
|
||||
setNodesSelection: action((state, { isActive, selection }) => {
|
||||
if (!isActive || typeof selection === 'undefined') {
|
||||
state.nodesSelectionActive = false;
|
||||
state.selectedElements = [];
|
||||
state.selectedElements = null;
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -291,7 +292,7 @@ const storeModel: StoreModel = {
|
||||
|
||||
if (!selectedNodes.length) {
|
||||
state.nodesSelectionActive = false;
|
||||
state.selectedElements = [];
|
||||
state.selectedElements = null;
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -311,17 +312,6 @@ const storeModel: StoreModel = {
|
||||
state.selectedElements = selectedElements;
|
||||
}),
|
||||
|
||||
updateSelection: action((state, selection) => {
|
||||
const selectedNodes = getNodesInside(state.nodes, selection, state.transform);
|
||||
const selectedEdges = getConnectedEdges(selectedNodes, state.edges);
|
||||
|
||||
const nextSelectedElements = [...selectedNodes, ...selectedEdges];
|
||||
const selectedElementsUpdated = !isEqual(nextSelectedElements, state.selectedElements);
|
||||
|
||||
state.selection = selection;
|
||||
state.selectedElements = selectedElementsUpdated ? nextSelectedElements : state.selectedElements;
|
||||
}),
|
||||
|
||||
updateTransform: action((state, transform) => {
|
||||
state.transform = [transform.x, transform.y, transform.k];
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user