refactor(selection): only fire selection changes that changed
This commit is contained in:
@@ -65,6 +65,7 @@ const UpdatableEdge = () => {
|
||||
const onConnect = (connection: Connection) => setEdges((els) => addEdge(connection, els));
|
||||
|
||||
const onNodesChange = useCallback((changes: NodeChange[]) => {
|
||||
console.log(changes);
|
||||
setNodes((ns) => applyNodeChanges(changes, ns));
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { getMarkerId } from '../../utils/graph';
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
addSelectedEdges: s.addSelectedEdges,
|
||||
setConnectionNodeId: s.setConnectionNodeId,
|
||||
unsetNodesSelection: s.unsetNodesSelection,
|
||||
setNodesSelectionActive: s.setNodesSelectionActive,
|
||||
setPosition: s.setConnectionPosition,
|
||||
connectionMode: s.connectionMode,
|
||||
});
|
||||
@@ -57,7 +57,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||
markerStart,
|
||||
}: WrapEdgeProps): JSX.Element | null => {
|
||||
const store = useStoreApi();
|
||||
const { addSelectedEdges, setConnectionNodeId, unsetNodesSelection, setPosition, connectionMode } = useStore(
|
||||
const { addSelectedEdges, setConnectionNodeId, setNodesSelectionActive, setPosition, connectionMode } = useStore(
|
||||
selector,
|
||||
shallow
|
||||
);
|
||||
@@ -99,7 +99,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||
const onEdgeClick = useCallback(
|
||||
(event: React.MouseEvent<SVGGElement, MouseEvent>): void => {
|
||||
if (elementsSelectable) {
|
||||
unsetNodesSelection();
|
||||
setNodesSelectionActive(false);
|
||||
addSelectedEdges([edgeElement.id]);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { NodeProps, WrapNodeProps, ReactFlowState } from '../../types';
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
addSelectedNodes: s.addSelectedNodes,
|
||||
unsetNodesSelection: s.unsetNodesSelection,
|
||||
setNodesSelectionActive: s.setNodesSelectionActive,
|
||||
updateNodePosition: s.updateNodePosition,
|
||||
updateNodeDimensions: s.updateNodeDimensions,
|
||||
unselectNodesAndEdges: s.unselectNodesAndEdges,
|
||||
@@ -56,7 +56,7 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
||||
const {
|
||||
addSelectedNodes,
|
||||
unselectNodesAndEdges,
|
||||
unsetNodesSelection,
|
||||
setNodesSelectionActive,
|
||||
updateNodePosition,
|
||||
// updateNodeDimensions,
|
||||
} = useStore(selector, shallow);
|
||||
@@ -127,7 +127,7 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
||||
(event: MouseEvent) => {
|
||||
if (!isDraggable) {
|
||||
if (isSelectable) {
|
||||
unsetNodesSelection();
|
||||
setNodesSelectionActive(false);
|
||||
|
||||
if (!selected) {
|
||||
addSelectedNodes([node.id]);
|
||||
@@ -145,14 +145,14 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
||||
onNodeDragStart?.(event as MouseEvent, node);
|
||||
|
||||
if (selectNodesOnDrag && isSelectable) {
|
||||
unsetNodesSelection();
|
||||
setNodesSelectionActive(false);
|
||||
|
||||
if (!selected) {
|
||||
addSelectedNodes([node.id]);
|
||||
}
|
||||
} else if (!selectNodesOnDrag && !selected && isSelectable) {
|
||||
unselectNodesAndEdges();
|
||||
unsetNodesSelection();
|
||||
setNodesSelectionActive(false);
|
||||
}
|
||||
},
|
||||
[node, selected, selectNodesOnDrag, isSelectable, onNodeDragStart]
|
||||
@@ -163,9 +163,7 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
||||
node.position.x += draggableData.deltaX;
|
||||
node.position.y += draggableData.deltaY;
|
||||
|
||||
if (onNodeDrag) {
|
||||
onNodeDrag(event as MouseEvent, node);
|
||||
}
|
||||
onNodeDrag?.(event as MouseEvent, node);
|
||||
|
||||
updateNodePosition({ id, dragging: true, diff: { x: draggableData.deltaX, y: draggableData.deltaY } });
|
||||
},
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
* made a selectio with on or several nodes
|
||||
*/
|
||||
|
||||
import React, { useMemo, useCallback, useRef, MouseEvent } from 'react';
|
||||
import ReactDraggable, { DraggableData } from 'react-draggable';
|
||||
import React, { memo, useMemo, useCallback, useRef, MouseEvent } from 'react';
|
||||
import { DraggableCore, DraggableData } from 'react-draggable';
|
||||
import cc from 'classcat';
|
||||
|
||||
import { useStore } from '../../store';
|
||||
import { Node, ReactFlowState } from '../../types';
|
||||
import { getRectOfNodes } from '../..';
|
||||
|
||||
export interface NodesSelectionProps {
|
||||
onSelectionDragStart?: (event: MouseEvent, nodes: Node[]) => void;
|
||||
@@ -21,7 +22,7 @@ export interface NodesSelectionProps {
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
transform: s.transform,
|
||||
selectedNodesBbox: s.selectedNodesBbox,
|
||||
selectionActive: s.selectionActive,
|
||||
userSelectionActive: s.userSelectionActive,
|
||||
selectedNodes: Array.from(s.nodeInternals)
|
||||
.filter(([_, n]) => n.selected)
|
||||
.map(([_, n]) => n),
|
||||
@@ -30,14 +31,14 @@ const selector = (s: ReactFlowState) => ({
|
||||
updateNodePosition: s.updateNodePosition,
|
||||
});
|
||||
|
||||
export default ({
|
||||
function NodesSelection({
|
||||
onSelectionDragStart,
|
||||
onSelectionDrag,
|
||||
onSelectionDragStop,
|
||||
onSelectionContextMenu,
|
||||
noPanClassName,
|
||||
}: NodesSelectionProps) => {
|
||||
const { transform, selectedNodesBbox, selectionActive, selectedNodes, snapToGrid, snapGrid, updateNodePosition } =
|
||||
}: NodesSelectionProps) {
|
||||
const { transform, userSelectionActive, selectedNodes, snapToGrid, snapGrid, updateNodePosition } =
|
||||
useStore(selector);
|
||||
const [tX, tY, tScale] = transform;
|
||||
const nodeRef = useRef(null);
|
||||
@@ -51,6 +52,8 @@ export default ({
|
||||
[tX, tY, tScale]
|
||||
);
|
||||
|
||||
const selectedNodesBbox = useMemo(() => getRectOfNodes(selectedNodes), [selectedNodes]);
|
||||
|
||||
const innerStyle = useMemo(
|
||||
() => ({
|
||||
width: selectedNodesBbox.width,
|
||||
@@ -70,10 +73,6 @@ export default ({
|
||||
|
||||
const onDrag = useCallback(
|
||||
(event: MouseEvent, data: DraggableData) => {
|
||||
if (onSelectionDrag) {
|
||||
onSelectionDrag(event, selectedNodes);
|
||||
}
|
||||
|
||||
updateNodePosition({
|
||||
diff: {
|
||||
x: data.deltaX,
|
||||
@@ -81,6 +80,8 @@ export default ({
|
||||
},
|
||||
dragging: true,
|
||||
});
|
||||
|
||||
onSelectionDrag?.(event, selectedNodes);
|
||||
},
|
||||
[onSelectionDrag, selectedNodes, updateNodePosition]
|
||||
);
|
||||
@@ -103,13 +104,13 @@ export default ({
|
||||
[onSelectionContextMenu, selectedNodes]
|
||||
);
|
||||
|
||||
if (!selectedNodes || selectionActive) {
|
||||
if (!selectedNodes?.length || userSelectionActive) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cc(['react-flow__nodesselection', 'react-flow__container', noPanClassName])} style={style}>
|
||||
<ReactDraggable
|
||||
<DraggableCore
|
||||
scale={tScale}
|
||||
grid={grid}
|
||||
onStart={(event) => onStart(event as MouseEvent)}
|
||||
@@ -124,7 +125,9 @@ export default ({
|
||||
onContextMenu={onContextMenu}
|
||||
style={innerStyle}
|
||||
/>
|
||||
</ReactDraggable>
|
||||
</DraggableCore>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default memo(NodesSelection);
|
||||
|
||||
@@ -2,104 +2,139 @@
|
||||
* The user selection rectangle gets displayed when a user drags the mouse while pressing shift
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import React, { memo, useState, useRef, useCallback } from 'react';
|
||||
import shallow from 'zustand/shallow';
|
||||
|
||||
import { useStore } from '../../store';
|
||||
import { XYPosition, ReactFlowState } from '../../types';
|
||||
import { useStore, useStoreApi } from '../../store';
|
||||
import { getSelectionChanges } from '../../utils/changes';
|
||||
import { XYPosition, ReactFlowState, SelectionRect, NodeChange, EdgeChange } from '../../types';
|
||||
import { getConnectedEdges, getNodesInside } from '../../utils/graph';
|
||||
|
||||
type UserSelectionProps = {
|
||||
selectionKeyPressed: boolean;
|
||||
};
|
||||
|
||||
function getMousePosition(event: React.MouseEvent): XYPosition | void {
|
||||
const reactFlowNode = (event.target as Element).closest('.react-flow');
|
||||
if (!reactFlowNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const containerBounds = reactFlowNode.getBoundingClientRect();
|
||||
|
||||
function getMousePosition(event: React.MouseEvent, containerBounds: DOMRect): XYPosition {
|
||||
return {
|
||||
x: event.clientX - containerBounds.left,
|
||||
y: event.clientY - containerBounds.top,
|
||||
};
|
||||
}
|
||||
|
||||
const userSelectionRectSelector = (state: ReactFlowState) => state.userSelectionRect;
|
||||
|
||||
const SelectionRect = () => {
|
||||
const userSelectionRect = useStore(userSelectionRectSelector);
|
||||
|
||||
if (!userSelectionRect.draw) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="react-flow__selection react-flow__container"
|
||||
style={{
|
||||
width: userSelectionRect.width,
|
||||
height: userSelectionRect.height,
|
||||
transform: `translate(${userSelectionRect.x}px, ${userSelectionRect.y}px)`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
selectionActive: s.selectionActive,
|
||||
userSelectionActive: s.userSelectionActive,
|
||||
elementsSelectable: s.elementsSelectable,
|
||||
setUserSelection: s.setUserSelection,
|
||||
updateUserSelection: s.updateUserSelection,
|
||||
unsetUserSelection: s.unsetUserSelection,
|
||||
unsetNodesSelection: s.unsetNodesSelection,
|
||||
setUserSelectionActive: s.setUserSelectionActive,
|
||||
setNodesSelectionActive: s.setNodesSelectionActive,
|
||||
});
|
||||
|
||||
export default memo(({ selectionKeyPressed }: UserSelectionProps) => {
|
||||
const {
|
||||
selectionActive,
|
||||
elementsSelectable,
|
||||
setUserSelection,
|
||||
updateUserSelection,
|
||||
unsetUserSelection,
|
||||
unsetNodesSelection,
|
||||
} = useStore(selector);
|
||||
const initialRect: SelectionRect = {
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
draw: false,
|
||||
};
|
||||
|
||||
const renderUserSelectionPane = selectionActive || selectionKeyPressed;
|
||||
export default memo(({ selectionKeyPressed }: UserSelectionProps) => {
|
||||
const store = useStoreApi();
|
||||
const prevSelectedNodesCount = useRef<number>(0);
|
||||
const prevSelectedEdgesCount = useRef<number>(0);
|
||||
const containerBounds = useRef<DOMRect>();
|
||||
const [userSelectionRect, setUserSelectionRect] = useState<SelectionRect>(initialRect);
|
||||
const { userSelectionActive, elementsSelectable, setUserSelectionActive, setNodesSelectionActive } = useStore(
|
||||
selector,
|
||||
shallow
|
||||
);
|
||||
|
||||
const renderUserSelectionPane = userSelectionActive || selectionKeyPressed;
|
||||
|
||||
const resetUserSelection = useCallback(() => {
|
||||
setUserSelectionRect(initialRect);
|
||||
setUserSelectionActive(false);
|
||||
|
||||
prevSelectedNodesCount.current = 0;
|
||||
prevSelectedEdgesCount.current = 0;
|
||||
}, []);
|
||||
|
||||
const onMouseDown = useCallback((event: React.MouseEvent): void => {
|
||||
const reactFlowNode = (event.target as Element).closest('.react-flow')!;
|
||||
containerBounds.current = reactFlowNode.getBoundingClientRect();
|
||||
|
||||
const mousePos = getMousePosition(event, containerBounds.current!);
|
||||
|
||||
setUserSelectionRect({
|
||||
width: 0,
|
||||
height: 0,
|
||||
startX: mousePos.x,
|
||||
startY: mousePos.y,
|
||||
x: mousePos.x,
|
||||
y: mousePos.y,
|
||||
draw: true,
|
||||
});
|
||||
|
||||
setUserSelectionActive(true);
|
||||
setNodesSelectionActive(false);
|
||||
}, []);
|
||||
|
||||
const onMouseMove = (event: React.MouseEvent): void => {
|
||||
if (!selectionKeyPressed || !userSelectionRect.draw || !containerBounds.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mousePos = getMousePosition(event, containerBounds.current!);
|
||||
const startX = userSelectionRect.startX ?? 0;
|
||||
const startY = userSelectionRect.startY ?? 0;
|
||||
|
||||
const nextUserSelectRect = {
|
||||
...userSelectionRect,
|
||||
x: mousePos.x < startX ? mousePos.x : userSelectionRect.x,
|
||||
y: mousePos.y < startY ? mousePos.y : userSelectionRect.y,
|
||||
width: Math.abs(mousePos.x - startX),
|
||||
height: Math.abs(mousePos.y - startY),
|
||||
};
|
||||
|
||||
const { nodeInternals, edges, transform, onNodesChange, onEdgesChange } = store.getState();
|
||||
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
|
||||
const selectedNodes = getNodesInside(nodes, nextUserSelectRect, transform, false, true);
|
||||
const selectedEdgeIds = getConnectedEdges(selectedNodes, edges).map((e) => e.id);
|
||||
const selectedNodeIds = selectedNodes.map((n) => n.id);
|
||||
|
||||
if (prevSelectedNodesCount.current !== selectedNodeIds.length) {
|
||||
prevSelectedNodesCount.current = selectedNodeIds.length;
|
||||
const changes = getSelectionChanges(nodes, selectedNodeIds) as NodeChange[];
|
||||
if (changes.length) {
|
||||
onNodesChange?.(changes);
|
||||
}
|
||||
}
|
||||
|
||||
if (prevSelectedEdgesCount.current !== selectedEdgeIds.length) {
|
||||
prevSelectedEdgesCount.current = selectedEdgeIds.length;
|
||||
const changes = getSelectionChanges(edges, selectedEdgeIds) as EdgeChange[];
|
||||
if (changes.length) {
|
||||
onEdgesChange?.(changes);
|
||||
}
|
||||
}
|
||||
|
||||
setUserSelectionRect(nextUserSelectRect);
|
||||
};
|
||||
|
||||
const onMouseUp = useCallback(() => {
|
||||
setNodesSelectionActive(prevSelectedNodesCount.current > 0);
|
||||
resetUserSelection();
|
||||
}, []);
|
||||
|
||||
const onMouseLeave = useCallback(() => {
|
||||
setNodesSelectionActive(false);
|
||||
resetUserSelection();
|
||||
}, []);
|
||||
|
||||
if (!elementsSelectable || !renderUserSelectionPane) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onMouseDown = (event: React.MouseEvent): void => {
|
||||
const mousePos = getMousePosition(event);
|
||||
if (!mousePos) {
|
||||
return;
|
||||
}
|
||||
|
||||
setUserSelection(mousePos);
|
||||
};
|
||||
|
||||
const onMouseMove = (event: React.MouseEvent): void => {
|
||||
if (!selectionKeyPressed || !selectionActive) {
|
||||
return;
|
||||
}
|
||||
const mousePos = getMousePosition(event);
|
||||
|
||||
if (!mousePos) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateUserSelection(mousePos);
|
||||
};
|
||||
|
||||
const onMouseUp = () => unsetUserSelection();
|
||||
|
||||
const onMouseLeave = () => {
|
||||
unsetUserSelection();
|
||||
unsetNodesSelection();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="react-flow__selectionpane react-flow__container"
|
||||
@@ -108,7 +143,16 @@ export default memo(({ selectionKeyPressed }: UserSelectionProps) => {
|
||||
onMouseUp={onMouseUp}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
<SelectionRect />
|
||||
{userSelectionRect.draw && (
|
||||
<div
|
||||
className="react-flow__selection react-flow__container"
|
||||
style={{
|
||||
width: userSelectionRect.width,
|
||||
height: userSelectionRect.height,
|
||||
transform: `translate(${userSelectionRect.x}px, ${userSelectionRect.y}px)`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@ interface FlowRendererProps
|
||||
}
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
unsetNodesSelection: s.unsetNodesSelection,
|
||||
setNodesSelectionActive: s.setNodesSelectionActive,
|
||||
resetSelectedElements: s.resetSelectedElements,
|
||||
nodesSelectionActive: s.nodesSelectionActive,
|
||||
});
|
||||
@@ -62,7 +62,7 @@ const FlowRenderer = ({
|
||||
noWheelClassName,
|
||||
noPanClassName,
|
||||
}: FlowRendererProps) => {
|
||||
const { unsetNodesSelection, resetSelectedElements, nodesSelectionActive } = useStore(selector, shallow);
|
||||
const { setNodesSelectionActive, resetSelectedElements, nodesSelectionActive } = useStore(selector, shallow);
|
||||
|
||||
const selectionKeyPressed = useKeyPress(selectionKeyCode);
|
||||
|
||||
@@ -71,7 +71,7 @@ const FlowRenderer = ({
|
||||
const onClick = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
onPaneClick?.(event);
|
||||
unsetNodesSelection();
|
||||
setNodesSelectionActive(false);
|
||||
resetSelectedElements();
|
||||
},
|
||||
[onPaneClick]
|
||||
|
||||
@@ -12,7 +12,7 @@ interface HookParams {
|
||||
}
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
unsetNodesSelection: s.unsetNodesSelection,
|
||||
setNodesSelectionActive: s.setNodesSelectionActive,
|
||||
setMultiSelectionActive: s.setMultiSelectionActive,
|
||||
resetSelectedElements: s.resetSelectedElements,
|
||||
onNodesChange: s.onNodesChange,
|
||||
@@ -21,7 +21,7 @@ const selector = (s: ReactFlowState) => ({
|
||||
|
||||
export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => {
|
||||
const store = useStoreApi();
|
||||
const { unsetNodesSelection, setMultiSelectionActive, resetSelectedElements, onNodesChange, onEdgesChange } =
|
||||
const { setNodesSelectionActive, setMultiSelectionActive, resetSelectedElements, onNodesChange, onEdgesChange } =
|
||||
useStore(selector, shallow);
|
||||
|
||||
const deleteKeyPressed = useKeyPress(deleteKeyCode);
|
||||
@@ -46,7 +46,7 @@ export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => {
|
||||
onNodesChange?.(nodeChanges);
|
||||
onEdgesChange?.(edgeChanges);
|
||||
|
||||
unsetNodesSelection();
|
||||
setNodesSelectionActive(false);
|
||||
resetSelectedElements();
|
||||
}
|
||||
}, [deleteKeyPressed, onNodesChange, onEdgesChange]);
|
||||
|
||||
+13
-84
@@ -27,9 +27,9 @@ import {
|
||||
XYPosition,
|
||||
ReactFlowStore,
|
||||
} from '../types';
|
||||
import { getRectOfNodes, getNodesInside, getConnectedEdges } from '../utils/graph';
|
||||
import { getHandleBounds } from '../components/Nodes/utils';
|
||||
import { createNodeInternals, createNodeOrEdgeSelectionChange } from './utils';
|
||||
import { createSelectionChange, getSelectionChanges } from '../utils/changes';
|
||||
import { createNodeInternals } from './utils';
|
||||
|
||||
const { Provider, useStore, useStoreApi } = createContext<ReactFlowState>();
|
||||
|
||||
@@ -55,7 +55,7 @@ const initialState: ReactFlowStore = {
|
||||
translateExtent: infiniteExtent,
|
||||
nodeExtent: infiniteExtent,
|
||||
nodesSelectionActive: false,
|
||||
selectionActive: false,
|
||||
userSelectionActive: false,
|
||||
userSelectionRect: {
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
@@ -181,74 +181,6 @@ const createStore = () =>
|
||||
}
|
||||
}
|
||||
},
|
||||
setUserSelection: (mousePos: XYPosition) => {
|
||||
set({
|
||||
selectionActive: true,
|
||||
userSelectionRect: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
startX: mousePos.x,
|
||||
startY: mousePos.y,
|
||||
x: mousePos.x,
|
||||
y: mousePos.y,
|
||||
draw: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
updateUserSelection: (mousePos: XYPosition) => {
|
||||
const { userSelectionRect, nodeInternals, edges, transform, onNodesChange, onEdgesChange } = get();
|
||||
const startX = userSelectionRect.startX ?? 0;
|
||||
const startY = userSelectionRect.startY ?? 0;
|
||||
|
||||
const nextUserSelectRect = {
|
||||
...userSelectionRect,
|
||||
x: mousePos.x < startX ? mousePos.x : userSelectionRect.x,
|
||||
y: mousePos.y < startY ? mousePos.y : userSelectionRect.y,
|
||||
width: Math.abs(mousePos.x - startX),
|
||||
height: Math.abs(mousePos.y - startY),
|
||||
};
|
||||
|
||||
// @TODO: work with nodeInternals instead of converting it to an array
|
||||
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
|
||||
const selectedNodes = getNodesInside(nodes, nextUserSelectRect, transform, false, true);
|
||||
const selectedEdgeIds = getConnectedEdges(selectedNodes, edges).map((e) => e.id);
|
||||
const selectedNodeIds = selectedNodes.map((n) => n.id);
|
||||
|
||||
onNodesChange?.(
|
||||
nodes.map((n) => createNodeOrEdgeSelectionChange(selectedNodeIds.includes(n.id))(n)) as NodeChange[]
|
||||
);
|
||||
onEdgesChange?.(
|
||||
edges.map((e) => createNodeOrEdgeSelectionChange(selectedEdgeIds.includes(e.id))(e)) as EdgeChange[]
|
||||
);
|
||||
|
||||
set({
|
||||
userSelectionRect: nextUserSelectRect,
|
||||
});
|
||||
},
|
||||
unsetUserSelection: () => {
|
||||
const { userSelectionRect, nodeInternals } = get();
|
||||
// @TODO: work with nodeInternals instead of converting it to an array
|
||||
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
|
||||
const selectedNodes = nodes.filter((node) => node.selected);
|
||||
|
||||
const stateUpdate = {
|
||||
selectionActive: false,
|
||||
userSelectionRect: {
|
||||
...userSelectionRect,
|
||||
draw: false,
|
||||
},
|
||||
selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
|
||||
nodesSelectionActive: false,
|
||||
};
|
||||
|
||||
if (selectedNodes && selectedNodes.length > 0) {
|
||||
const selectedNodesBbox = getRectOfNodes(selectedNodes);
|
||||
stateUpdate.selectedNodesBbox = selectedNodesBbox;
|
||||
stateUpdate.nodesSelectionActive = true;
|
||||
}
|
||||
|
||||
set(stateUpdate);
|
||||
},
|
||||
addSelectedNodes: (selectedNodeIds: string[]) => {
|
||||
const { multiSelectionActive, onNodesChange, nodeInternals } = get();
|
||||
// @TODO: work with nodeInternals instead of converting it to an array
|
||||
@@ -256,11 +188,9 @@ const createStore = () =>
|
||||
let changedNodes;
|
||||
|
||||
if (multiSelectionActive) {
|
||||
changedNodes = selectedNodeIds.map(createNodeOrEdgeSelectionChange(true));
|
||||
changedNodes = selectedNodeIds.map((nodeId) => createSelectionChange(nodeId, true));
|
||||
} else {
|
||||
changedNodes = nodes.map((node) =>
|
||||
createNodeOrEdgeSelectionChange(selectedNodeIds.some((nodeId) => nodeId === node.id))(node.id)
|
||||
);
|
||||
changedNodes = getSelectionChanges(nodes, selectedNodeIds);
|
||||
}
|
||||
|
||||
if (changedNodes.length) {
|
||||
@@ -273,11 +203,9 @@ const createStore = () =>
|
||||
let changedEdges;
|
||||
|
||||
if (multiSelectionActive) {
|
||||
changedEdges = selectedEdgeIds.map(createNodeOrEdgeSelectionChange(true));
|
||||
changedEdges = selectedEdgeIds.map((edgeId) => createSelectionChange(edgeId, true));
|
||||
} else {
|
||||
changedEdges = edges.map((edge) =>
|
||||
createNodeOrEdgeSelectionChange(selectedEdgeIds.some((edgeId) => edgeId === edge.id))(edge.id)
|
||||
);
|
||||
changedEdges = getSelectionChanges(edges, selectedEdgeIds);
|
||||
}
|
||||
|
||||
if (changedEdges.length) {
|
||||
@@ -291,9 +219,9 @@ const createStore = () =>
|
||||
|
||||
const nodesToUnselect = nodes.map((n) => {
|
||||
n.selected = false;
|
||||
return createNodeOrEdgeSelectionChange(false)(n);
|
||||
return createSelectionChange(n.id, false);
|
||||
}) as NodeChange[];
|
||||
const edgesToUnselect = edges.map(createNodeOrEdgeSelectionChange(false)) as EdgeChange[];
|
||||
const edgesToUnselect = edges.map((edge) => createSelectionChange(edge.id, false)) as EdgeChange[];
|
||||
|
||||
if (nodesToUnselect.length) {
|
||||
onNodesChange?.(nodesToUnselect);
|
||||
@@ -332,8 +260,8 @@ const createStore = () =>
|
||||
const { nodeInternals, edges, onNodesChange, onEdgesChange } = get();
|
||||
// @TODO: work with nodeInternals instead of converting it to an array
|
||||
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
|
||||
const nodesToUnselect = nodes.filter((e) => e.selected).map(createNodeOrEdgeSelectionChange(false));
|
||||
const edgesToUnselect = edges.filter((e) => e.selected).map(createNodeOrEdgeSelectionChange(false));
|
||||
const nodesToUnselect = nodes.filter((e) => e.selected).map((n) => createSelectionChange(n.id, false));
|
||||
const edgesToUnselect = edges.filter((e) => e.selected).map((e) => createSelectionChange(e.id, false));
|
||||
|
||||
if (nodesToUnselect.length) {
|
||||
onNodesChange?.(nodesToUnselect as NodeChange[]);
|
||||
@@ -354,7 +282,8 @@ const createStore = () =>
|
||||
nodeInternals: new Map(nodeInternals),
|
||||
});
|
||||
},
|
||||
unsetNodesSelection: () => set({ nodesSelectionActive: false }),
|
||||
setUserSelectionActive: (userSelectionActive: boolean) => set({ userSelectionActive }),
|
||||
setNodesSelectionActive: (nodesSelectionActive: boolean) => set({ nodesSelectionActive }),
|
||||
updateTransform: (transform: Transform) => set({ transform }),
|
||||
updateSize: (size: Dimensions) => set({ width: size.width || 500, height: size.height || 500 }),
|
||||
setOnConnect: (onConnect: OnConnect) => set({ onConnect }),
|
||||
|
||||
+1
-7
@@ -1,4 +1,4 @@
|
||||
import { Node, Edge, NodeInternals, NodeInternalsItem, XYZPosition } from '../types';
|
||||
import { Node, NodeInternals, NodeInternalsItem, XYZPosition } from '../types';
|
||||
import { isNumeric } from '../utils';
|
||||
|
||||
type ParentNodes = Record<string, boolean>;
|
||||
@@ -104,9 +104,3 @@ export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals)
|
||||
|
||||
return nextNodeInternals;
|
||||
}
|
||||
|
||||
export const createNodeOrEdgeSelectionChange = (selected: boolean) => (item: Node | Edge | string) => ({
|
||||
id: typeof item === 'string' ? item : item.id,
|
||||
type: 'select',
|
||||
selected,
|
||||
});
|
||||
|
||||
@@ -149,7 +149,7 @@ export type ReactFlowStore = {
|
||||
nodeExtent: CoordinateExtent;
|
||||
|
||||
nodesSelectionActive: boolean;
|
||||
selectionActive: boolean;
|
||||
userSelectionActive: boolean;
|
||||
|
||||
userSelectionRect: SelectionRect;
|
||||
|
||||
@@ -176,10 +176,8 @@ export type ReactFlowActions = {
|
||||
setEdges: (edges: Edge[]) => void;
|
||||
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
|
||||
updateNodePosition: (update: NodeDiffUpdate) => void;
|
||||
setUserSelection: (mousePos: XYPosition) => void;
|
||||
updateUserSelection: (mousePos: XYPosition) => void;
|
||||
unsetUserSelection: () => void;
|
||||
unsetNodesSelection: () => void;
|
||||
setNodesSelectionActive: (isActive: boolean) => void;
|
||||
setUserSelectionActive: (isActive: boolean) => void;
|
||||
resetSelectedElements: () => void;
|
||||
unselectNodesAndEdges: () => void;
|
||||
addSelectedNodes: (nodeIds: string[]) => void;
|
||||
|
||||
@@ -49,3 +49,23 @@ export function applyNodeChanges(changes: NodeChange[], nodes: Node[]): Node[] {
|
||||
export function applyEdgeChanges(changes: EdgeChange[], edges: Edge[]): Edge[] {
|
||||
return applyChanges(changes, edges) as Edge[];
|
||||
}
|
||||
|
||||
export const createSelectionChange = (id: string, selected: boolean) => ({
|
||||
id,
|
||||
type: 'select',
|
||||
selected,
|
||||
});
|
||||
|
||||
export function getSelectionChanges(items: any[], selectedIds: string[]) {
|
||||
return items.reduce((res, item) => {
|
||||
const willBeSelected = selectedIds.includes(item.id);
|
||||
|
||||
if (!item.selected && willBeSelected) {
|
||||
res.push(createSelectionChange(item.id, true));
|
||||
} else if (item.selected && !willBeSelected) {
|
||||
res.push(createSelectionChange(item.id, false));
|
||||
}
|
||||
|
||||
return res;
|
||||
}, []);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user