Merge branch 'next-release' into jackfishwick-main

This commit is contained in:
moklick
2022-12-08 21:10:50 +01:00
23 changed files with 109 additions and 72 deletions
+7
View File
@@ -0,0 +1,7 @@
---
'@reactflow/core': patch
'@reactflow/minimap': patch
'@reactflow/node-toolbar': patch
---
use getNodes function
+6
View File
@@ -0,0 +1,6 @@
---
'@reactflow/core': patch
'@reactflow/minimap': patch
---
Use translateExtent correctly
+5
View File
@@ -0,0 +1,5 @@
---
'@reactflow/core': patch
---
Only trigger drag event when change happened
@@ -1,5 +1,5 @@
import { FC, MouseEvent } from 'react'; import { FC, MouseEvent } from 'react';
import { EdgeProps, getBezierPath, EdgeLabelRenderer, useStore, ReactFlowStore } from 'reactflow'; import { EdgeProps, getBezierPath, EdgeLabelRenderer, useStore } from 'reactflow';
const CustomEdge: FC<EdgeProps> = ({ const CustomEdge: FC<EdgeProps> = ({
id, id,
@@ -14,7 +14,7 @@ const CustomEdge: FC<EdgeProps> = ({
data, data,
}) => { }) => {
const isConnectedNodeDragging = useStore((s) => const isConnectedNodeDragging = useStore((s) =>
Array.from(s.nodeInternals.values()).find((n) => n.dragging && (target === n.id || source === n.id)) s.getNodes().find((n) => n.dragging && (target === n.id || source === n.id))
); );
const [edgePath, labelX, labelY] = getBezierPath({ const [edgePath, labelX, labelY] = getBezierPath({
@@ -14,7 +14,7 @@ const CustomEdge: FC<EdgeProps> = ({
data, data,
}) => { }) => {
const isConnectedNodeDragging = useStore((s) => const isConnectedNodeDragging = useStore((s) =>
Array.from(s.nodeInternals.values()).find((n) => n.dragging && (target === n.id || source === n.id)) s.getNodes().find((n) => n.dragging && (target === n.id || source === n.id))
); );
const [edgePath, labelX, labelY] = getBezierPath({ const [edgePath, labelX, labelY] = getBezierPath({
@@ -1,7 +1,8 @@
import { NodeToolbar, ReactFlowState, useStore } from 'reactflow'; import { NodeToolbar, ReactFlowState, useStore } from 'reactflow';
const selectedNodesSelector = (state: ReactFlowState) => const selectedNodesSelector = (state: ReactFlowState) =>
Array.from(state.nodeInternals.values()) state
.getNodes()
.filter((node) => node.selected) .filter((node) => node.selected)
.map((node) => node.id); .map((node) => node.id);
@@ -1,15 +1,16 @@
import { useStore, useStoreApi } from 'reactflow'; import { useReactFlow, useStore } from 'reactflow';
import styles from './provider.module.css'; import styles from './provider.module.css';
const Sidebar = () => { const Sidebar = () => {
const store = useStoreApi(); const { setNodes } = useReactFlow();
const nodeInternals = useStore((store) => store.nodeInternals); const nodeInfos = useStore((store) =>
store.getNodes().map((n) => `Node ${n.id} - x: ${n.position.x.toFixed(2)}, y: ${n.position.y.toFixed(2)}`)
);
const transform = useStore((store) => store.transform); const transform = useStore((store) => store.transform);
const selectAll = () => { const selectAll = () => {
nodeInternals.forEach((node) => (node.selected = true)); setNodes((nodes) => nodes.map((n) => ({ ...n, selected: true })));
store.setState({ nodeInternals: new Map(nodeInternals) });
}; };
return ( return (
@@ -22,10 +23,8 @@ const Sidebar = () => {
[{transform[0].toFixed(2)}, {transform[1].toFixed(2)}, {transform[2].toFixed(2)}] [{transform[0].toFixed(2)}, {transform[1].toFixed(2)}, {transform[2].toFixed(2)}]
</div> </div>
<div className={styles.title}>Nodes</div> <div className={styles.title}>Nodes</div>
{Array.from(nodeInternals).map(([, node]) => ( {nodeInfos.map((info, index) => (
<div key={node.id}> <div key={index}>{info}</div>
Node {node.id} - x: {node.position.x.toFixed(2)}, y: {node.position.y.toFixed(2)}
</div>
))} ))}
<div className={styles.selectall}> <div className={styles.selectall}>
@@ -27,7 +27,7 @@ const selector = (s: ReactFlowState) => ({
}); });
const bboxSelector = (s: ReactFlowState) => { const bboxSelector = (s: ReactFlowState) => {
const selectedNodes = Array.from(s.nodeInternals.values()).filter((n) => n.selected); const selectedNodes = s.getNodes().filter((n) => n.selected);
return getRectOfNodes(selectedNodes, s.nodeOrigin); return getRectOfNodes(selectedNodes, s.nodeOrigin);
}; };
@@ -55,7 +55,10 @@ function NodesSelection({ onSelectionContextMenu, noPanClassName, disableKeyboar
const onContextMenu = onSelectionContextMenu const onContextMenu = onSelectionContextMenu
? (event: MouseEvent) => { ? (event: MouseEvent) => {
const selectedNodes = Array.from(store.getState().nodeInternals.values()).filter((n) => n.selected); const selectedNodes = store
.getState()
.getNodes()
.filter((n) => n.selected);
onSelectionContextMenu(event, selectedNodes); onSelectionContextMenu(event, selectedNodes);
} }
: undefined; : undefined;
@@ -9,7 +9,7 @@ type SelectionListenerProps = {
}; };
const selector = (s: ReactFlowState) => ({ const selector = (s: ReactFlowState) => ({
selectedNodes: Array.from(s.nodeInternals.values()).filter((n) => n.selected), selectedNodes: s.getNodes().filter((n) => n.selected),
selectedEdges: s.edges.filter((e) => e.selected), selectedEdges: s.edges.filter((e) => e.selected),
}); });
@@ -158,8 +158,8 @@ const UserSelection = memo(
height: Math.abs(mousePos.y - startY), height: Math.abs(mousePos.y - startY),
}; };
const { nodeInternals, edges, transform, onNodesChange, onEdgesChange, nodeOrigin } = store.getState(); const { nodeInternals, edges, transform, onNodesChange, onEdgesChange, nodeOrigin, getNodes } = store.getState();
const nodes = Array.from(nodeInternals.values()); const nodes = getNodes();
const selectedNodes = getNodesInside( const selectedNodes = getNodesInside(
nodeInternals, nodeInternals,
nextUserSelectRect, nextUserSelectRect,
+10 -10
View File
@@ -5,13 +5,12 @@ import type { D3ZoomEvent } from 'd3-zoom';
import { select, pointer } from 'd3-selection'; import { select, pointer } from 'd3-selection';
import shallow from 'zustand/shallow'; import shallow from 'zustand/shallow';
import { clamp } from '../../utils';
import useKeyPress from '../../hooks/useKeyPress'; import useKeyPress from '../../hooks/useKeyPress';
import useResizeHandler from '../../hooks/useResizeHandler'; import useResizeHandler from '../../hooks/useResizeHandler';
import { useStore, useStoreApi } from '../../hooks/useStore'; import { useStore, useStoreApi } from '../../hooks/useStore';
import { containerStyle } from '../../styles'; import { containerStyle } from '../../styles';
import type { FlowRendererProps } from '../FlowRenderer'; import type { FlowRendererProps } from '../FlowRenderer';
import { PanOnScrollMode } from '../../types'; import { CoordinateExtent, PanOnScrollMode } from '../../types';
import type { Viewport, ReactFlowState } from '../../types'; import type { Viewport, ReactFlowState } from '../../types';
type ZoomPaneProps = Omit< type ZoomPaneProps = Omit<
@@ -78,22 +77,24 @@ const ZoomPane = ({
useEffect(() => { useEffect(() => {
if (zoomPane.current) { if (zoomPane.current) {
const bbox = zoomPane.current.getBoundingClientRect();
const d3ZoomInstance = zoom().scaleExtent([minZoom, maxZoom]).translateExtent(translateExtent); const d3ZoomInstance = zoom().scaleExtent([minZoom, maxZoom]).translateExtent(translateExtent);
const selection = select(zoomPane.current as Element).call(d3ZoomInstance); const selection = select(zoomPane.current as Element).call(d3ZoomInstance);
const updatedTransform = zoomIdentity.translate(defaultViewport.x, defaultViewport.y).scale(defaultViewport.zoom);
const extent: CoordinateExtent = [
[0, 0],
[bbox.width, bbox.height],
];
const clampedX = clamp(defaultViewport.x, translateExtent[0][0], translateExtent[1][0]); const constrainedTransform = d3ZoomInstance.constrain()(updatedTransform, extent, translateExtent);
const clampedY = clamp(defaultViewport.y, translateExtent[0][1], translateExtent[1][1]); d3ZoomInstance.transform(selection, constrainedTransform);
const clampedZoom = clamp(defaultViewport.zoom, minZoom, maxZoom);
const updatedTransform = zoomIdentity.translate(clampedX, clampedY).scale(clampedZoom);
d3ZoomInstance.transform(selection, updatedTransform);
store.setState({ store.setState({
d3Zoom: d3ZoomInstance, d3Zoom: d3ZoomInstance,
d3Selection: selection, d3Selection: selection,
d3ZoomHandler: selection.on('wheel.zoom'), d3ZoomHandler: selection.on('wheel.zoom'),
// we need to pass transform because zoom handler is not registered when we set the initial transform // we need to pass transform because zoom handler is not registered when we set the initial transform
transform: [clampedX, clampedY, clampedZoom], transform: [constrainedTransform.x, constrainedTransform.y, constrainedTransform.k],
domNode: zoomPane.current.closest('.react-flow') as HTMLDivElement, domNode: zoomPane.current.closest('.react-flow') as HTMLDivElement,
}); });
} }
@@ -164,7 +165,6 @@ const ZoomPane = ({
} else if (!userSelectionActive) { } else if (!userSelectionActive) {
d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => { d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
const { onViewportChange } = store.getState(); const { onViewportChange } = store.getState();
store.setState({ transform: [event.transform.x, event.transform.y, event.transform.k] }); store.setState({ transform: [event.transform.x, event.transform.y, event.transform.k] });
if (onMove || onViewportChange) { if (onMove || onViewportChange) {
+11
View File
@@ -108,6 +108,9 @@ function useDrag({
x: pointerPos.xSnapped, x: pointerPos.xSnapped,
y: pointerPos.ySnapped, y: pointerPos.ySnapped,
}; };
let hasChange = false;
dragItems.current = dragItems.current.map((n) => { dragItems.current = dragItems.current.map((n) => {
const nextPosition = { x: pointerPos.x - n.distance.x, y: pointerPos.y - n.distance.y }; const nextPosition = { x: pointerPos.x - n.distance.x, y: pointerPos.y - n.distance.y };
@@ -118,12 +121,20 @@ function useDrag({
const updatedPos = calcNextPosition(n, nextPosition, nodeInternals, nodeExtent, nodeOrigin); const updatedPos = calcNextPosition(n, nextPosition, nodeInternals, nodeExtent, nodeOrigin);
// we want to make sure that we only fire a change event when there is a changes
hasChange =
hasChange || n.position.x !== updatedPos.position.x || n.position.y !== updatedPos.position.y;
n.position = updatedPos.position; n.position = updatedPos.position;
n.positionAbsolute = updatedPos.positionAbsolute; n.positionAbsolute = updatedPos.positionAbsolute;
return n; return n;
}); });
if (!hasChange) {
return;
}
const onDrag = nodeId ? onNodeDrag : wrapSelectionDragFunc(onSelectionDrag); const onDrag = nodeId ? onNodeDrag : wrapSelectionDragFunc(onSelectionDrag);
updateNodePositions(dragItems.current, true, true); updateNodePositions(dragItems.current, true, true);
@@ -18,11 +18,10 @@ export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => {
useEffect(() => { useEffect(() => {
if (deleteKeyPressed) { if (deleteKeyPressed) {
const { nodeInternals, edges } = store.getState(); const { edges, getNodes } = store.getState();
const nodes = Array.from(nodeInternals.values()); const selectedNodes = getNodes().filter((node) => node.selected);
const selectedNodes = nodes.filter((node) => node.selected);
const selectedEdges = edges.filter((edge) => edge.selected); const selectedEdges = edges.filter((edge) => edge.selected);
deleteElements({nodes: selectedNodes, edges: selectedEdges}); deleteElements({ nodes: selectedNodes, edges: selectedEdges });
store.setState({ nodesSelectionActive: false }); store.setState({ nodesSelectionActive: false });
} }
}, [deleteKeyPressed]); }, [deleteKeyPressed]);
+1 -1
View File
@@ -1,7 +1,7 @@
import { useStore } from '../hooks/useStore'; import { useStore } from '../hooks/useStore';
import type { Node, ReactFlowState } from '../types'; import type { Node, ReactFlowState } from '../types';
const nodesSelector = (state: ReactFlowState) => Array.from(state.nodeInternals.values()); const nodesSelector = (state: ReactFlowState) => state.getNodes();
function useNodes<NodeData>(): Node<NodeData>[] { function useNodes<NodeData>(): Node<NodeData>[] {
const nodes = useStore(nodesSelector); const nodes = useStore(nodesSelector);
@@ -7,7 +7,7 @@ const selector = (s: ReactFlowState) => {
return false; return false;
} }
return Array.from(s.nodeInternals.values()).every((n) => n[internalsSymbol]?.handleBounds !== undefined); return s.getNodes().every((n) => n[internalsSymbol]?.handleBounds !== undefined);
}; };
function useNodesInitialized(): boolean { function useNodesInitialized(): boolean {
+14 -15
View File
@@ -24,14 +24,14 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
const store = useStoreApi(); const store = useStoreApi();
const getNodes = useCallback<Instance.GetNodes<NodeData>>(() => { const getNodes = useCallback<Instance.GetNodes<NodeData>>(() => {
const { nodeInternals } = store.getState(); return store
const nodes = Array.from(nodeInternals.values()); .getState()
return nodes.map((n) => ({ ...n })); .getNodes()
.map((n) => ({ ...n }));
}, []); }, []);
const getNode = useCallback<Instance.GetNode<NodeData>>((id) => { const getNode = useCallback<Instance.GetNode<NodeData>>((id) => {
const { nodeInternals } = store.getState(); return store.getState().nodeInternals.get(id);
return nodeInternals.get(id);
}, []); }, []);
const getEdges = useCallback<Instance.GetEdges<EdgeData>>(() => { const getEdges = useCallback<Instance.GetEdges<EdgeData>>(() => {
@@ -45,8 +45,8 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
}, []); }, []);
const setNodes = useCallback<Instance.SetNodes<NodeData>>((payload) => { const setNodes = useCallback<Instance.SetNodes<NodeData>>((payload) => {
const { nodeInternals, setNodes, hasDefaultNodes, onNodesChange } = store.getState(); const { getNodes, setNodes, hasDefaultNodes, onNodesChange } = store.getState();
const nodes = Array.from(nodeInternals.values()); const nodes = getNodes();
const nextNodes = typeof payload === 'function' ? payload(nodes) : payload; const nextNodes = typeof payload === 'function' ? payload(nodes) : payload;
if (hasDefaultNodes) { if (hasDefaultNodes) {
@@ -77,10 +77,10 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
const addNodes = useCallback<Instance.AddNodes<NodeData>>((payload) => { const addNodes = useCallback<Instance.AddNodes<NodeData>>((payload) => {
const nodes = Array.isArray(payload) ? payload : [payload]; const nodes = Array.isArray(payload) ? payload : [payload];
const { nodeInternals, setNodes, hasDefaultNodes, onNodesChange } = store.getState(); const { getNodes, setNodes, hasDefaultNodes, onNodesChange } = store.getState();
if (hasDefaultNodes) { if (hasDefaultNodes) {
const currentNodes = Array.from(nodeInternals.values()); const currentNodes = getNodes();
const nextNodes = [...currentNodes, ...nodes]; const nextNodes = [...currentNodes, ...nodes];
setNodes(nextNodes); setNodes(nextNodes);
} else if (onNodesChange) { } else if (onNodesChange) {
@@ -102,11 +102,10 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
}, []); }, []);
const toObject = useCallback<Instance.ToObject<NodeData, EdgeData>>(() => { const toObject = useCallback<Instance.ToObject<NodeData, EdgeData>>(() => {
const { nodeInternals, edges = [], transform } = store.getState(); const { getNodes, edges = [], transform } = store.getState();
const nodes = Array.from(nodeInternals.values());
const [x, y, zoom] = transform; const [x, y, zoom] = transform;
return { return {
nodes: nodes.map((n) => ({ ...n })), nodes: getNodes().map((n) => ({ ...n })),
edges: edges.map((e) => ({ ...e })), edges: edges.map((e) => ({ ...e })),
viewport: { viewport: {
x, x,
@@ -119,6 +118,7 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
const deleteElements = useCallback<Instance.DeleteElements>(({ nodes: nodesDeleted, edges: edgesDeleted }) => { const deleteElements = useCallback<Instance.DeleteElements>(({ nodes: nodesDeleted, edges: edgesDeleted }) => {
const { const {
nodeInternals, nodeInternals,
getNodes,
edges, edges,
hasDefaultNodes, hasDefaultNodes,
hasDefaultEdges, hasDefaultEdges,
@@ -127,10 +127,9 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
onNodesChange, onNodesChange,
onEdgesChange, onEdgesChange,
} = store.getState(); } = store.getState();
const nodes = Array.from(nodeInternals.values());
const nodeIds = (nodesDeleted || []).map((node) => node.id); const nodeIds = (nodesDeleted || []).map((node) => node.id);
const edgeIds = (edgesDeleted || []).map((edge) => edge.id); const edgeIds = (edgesDeleted || []).map((edge) => edge.id);
const nodesToRemove = nodes.reduce<Node[]>((res, node) => { const nodesToRemove = getNodes().reduce<Node[]>((res, node) => {
const parentHit = !nodeIds.includes(node.id) && node.parentNode && res.find((n) => n.id === node.parentNode); const parentHit = !nodeIds.includes(node.id) && node.parentNode && res.find((n) => n.id === node.parentNode);
const deletable = typeof node.deletable === 'boolean' ? node.deletable : true; const deletable = typeof node.deletable === 'boolean' ? node.deletable : true;
if (deletable && (nodeIds.includes(node.id) || parentHit)) { if (deletable && (nodeIds.includes(node.id) || parentHit)) {
@@ -219,7 +218,7 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
return []; return [];
} }
return (nodes || Array.from(store.getState().nodeInternals.values())).filter((n) => { return (nodes || store.getState().getNodes()).filter((n) => {
if (!isRect && (n.id === node!.id || !n.positionAbsolute)) { if (!isRect && (n.id === node!.id || !n.positionAbsolute)) {
return false; return false;
} }
@@ -8,8 +8,8 @@ function useUpdateNodePositions() {
const store = useStoreApi(); const store = useStoreApi();
const updatePositions = useCallback((positionDiff: XYPosition) => { const updatePositions = useCallback((positionDiff: XYPosition) => {
const { nodeInternals, nodeExtent, updateNodePositions, snapToGrid, snapGrid } = store.getState(); const { nodeInternals, nodeExtent, updateNodePositions, getNodes, snapToGrid, snapGrid } = store.getState();
const selectedNodes = Array.from(nodeInternals.values()).filter((n) => n.selected); const selectedNodes = getNodes().filter((n) => n.selected);
const nodeUpdates = selectedNodes.map((n) => { const nodeUpdates = selectedNodes.map((n) => {
if (n.positionAbsolute) { if (n.positionAbsolute) {
+1 -1
View File
@@ -10,7 +10,7 @@ function useVisibleNodes(onlyRenderVisible: boolean) {
(s: ReactFlowState) => (s: ReactFlowState) =>
onlyRenderVisible onlyRenderVisible
? getNodesInside(s.nodeInternals, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true) ? getNodesInside(s.nodeInternals, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true)
: Array.from(s.nodeInternals.values()), : s.getNodes(),
[onlyRenderVisible] [onlyRenderVisible]
) )
); );
+14 -12
View File
@@ -27,6 +27,9 @@ const createRFStore = () =>
const { nodeInternals, nodeOrigin } = get(); const { nodeInternals, nodeOrigin } = get();
set({ nodeInternals: createNodeInternals(nodes, nodeInternals, nodeOrigin) }); set({ nodeInternals: createNodeInternals(nodes, nodeInternals, nodeOrigin) });
}, },
getNodes: () => {
return Array.from(get().nodeInternals.values());
},
setEdges: (edges: Edge[]) => { setEdges: (edges: Edge[]) => {
const { defaultEdgeOptions = {} } = get(); const { defaultEdgeOptions = {} } = get();
set({ edges: edges.map((e) => ({ ...defaultEdgeOptions, ...e })) }); set({ edges: edges.map((e) => ({ ...defaultEdgeOptions, ...e })) });
@@ -125,11 +128,11 @@ const createRFStore = () =>
}, },
triggerNodeChanges: (changes: NodeChange[]) => { triggerNodeChanges: (changes: NodeChange[]) => {
const { onNodesChange, nodeInternals, hasDefaultNodes, nodeOrigin } = get(); const { onNodesChange, nodeInternals, hasDefaultNodes, nodeOrigin, getNodes } = get();
if (changes?.length) { if (changes?.length) {
if (hasDefaultNodes) { if (hasDefaultNodes) {
const nodes = applyNodeChanges(changes, Array.from(nodeInternals.values())); const nodes = applyNodeChanges(changes, getNodes());
const nextNodeInternals = createNodeInternals(nodes, nodeInternals, nodeOrigin); const nextNodeInternals = createNodeInternals(nodes, nodeInternals, nodeOrigin);
set({ nodeInternals: nextNodeInternals }); set({ nodeInternals: nextNodeInternals });
} }
@@ -139,14 +142,14 @@ const createRFStore = () =>
}, },
addSelectedNodes: (selectedNodeIds: string[]) => { addSelectedNodes: (selectedNodeIds: string[]) => {
const { multiSelectionActive, nodeInternals, edges } = get(); const { multiSelectionActive, edges, getNodes } = get();
let changedNodes: NodeSelectionChange[]; let changedNodes: NodeSelectionChange[];
let changedEdges: EdgeSelectionChange[] | null = null; let changedEdges: EdgeSelectionChange[] | null = null;
if (multiSelectionActive) { if (multiSelectionActive) {
changedNodes = selectedNodeIds.map((nodeId) => createSelectionChange(nodeId, true)) as NodeSelectionChange[]; changedNodes = selectedNodeIds.map((nodeId) => createSelectionChange(nodeId, true)) as NodeSelectionChange[];
} else { } else {
changedNodes = getSelectionChanges(Array.from(nodeInternals.values()), selectedNodeIds); changedNodes = getSelectionChanges(getNodes(), selectedNodeIds);
changedEdges = getSelectionChanges(edges, []); changedEdges = getSelectionChanges(edges, []);
} }
@@ -158,7 +161,7 @@ const createRFStore = () =>
}); });
}, },
addSelectedEdges: (selectedEdgeIds: string[]) => { addSelectedEdges: (selectedEdgeIds: string[]) => {
const { multiSelectionActive, edges, nodeInternals } = get(); const { multiSelectionActive, edges, getNodes } = get();
let changedEdges: EdgeSelectionChange[]; let changedEdges: EdgeSelectionChange[];
let changedNodes: NodeSelectionChange[] | null = null; let changedNodes: NodeSelectionChange[] | null = null;
@@ -166,7 +169,7 @@ const createRFStore = () =>
changedEdges = selectedEdgeIds.map((edgeId) => createSelectionChange(edgeId, true)) as EdgeSelectionChange[]; changedEdges = selectedEdgeIds.map((edgeId) => createSelectionChange(edgeId, true)) as EdgeSelectionChange[];
} else { } else {
changedEdges = getSelectionChanges(edges, selectedEdgeIds); changedEdges = getSelectionChanges(edges, selectedEdgeIds);
changedNodes = getSelectionChanges(Array.from(nodeInternals.values()), []); changedNodes = getSelectionChanges(getNodes(), []);
} }
updateNodesAndEdgesSelections({ updateNodesAndEdgesSelections({
@@ -177,8 +180,8 @@ const createRFStore = () =>
}); });
}, },
unselectNodesAndEdges: ({ nodes, edges }: UnselectNodesAndEdgesParams = {}) => { unselectNodesAndEdges: ({ nodes, edges }: UnselectNodesAndEdgesParams = {}) => {
const { nodeInternals, edges: storeEdges } = get(); const { edges: storeEdges, getNodes } = get();
const nodesToUnselect = nodes ? nodes : Array.from(nodeInternals.values()); const nodesToUnselect = nodes ? nodes : getNodes();
const edgesToUnselect = edges ? edges : storeEdges; const edgesToUnselect = edges ? edges : storeEdges;
const changedNodes = nodesToUnselect.map((n) => { const changedNodes = nodesToUnselect.map((n) => {
@@ -209,14 +212,13 @@ const createRFStore = () =>
set({ maxZoom }); set({ maxZoom });
}, },
setTranslateExtent: (translateExtent: CoordinateExtent) => { setTranslateExtent: (translateExtent: CoordinateExtent) => {
const { d3Zoom } = get(); get().d3Zoom?.translateExtent(translateExtent);
d3Zoom?.translateExtent(translateExtent);
set({ translateExtent }); set({ translateExtent });
}, },
resetSelectedElements: () => { resetSelectedElements: () => {
const { nodeInternals, edges } = get(); const { edges, getNodes } = get();
const nodes = Array.from(nodeInternals.values()); const nodes = getNodes();
const nodesToUnselect = nodes const nodesToUnselect = nodes
.filter((e) => e.selected) .filter((e) => e.selected)
+2 -4
View File
@@ -120,7 +120,7 @@ type InternalFitViewOptions = {
export function fitView(get: StoreApi<ReactFlowState>['getState'], options: InternalFitViewOptions = {}) { export function fitView(get: StoreApi<ReactFlowState>['getState'], options: InternalFitViewOptions = {}) {
const { const {
nodeInternals, getNodes,
width, width,
height, height,
minZoom, minZoom,
@@ -134,9 +134,7 @@ export function fitView(get: StoreApi<ReactFlowState>['getState'], options: Inte
if ((options.initial && !fitViewOnInitDone && fitViewOnInit) || !options.initial) { if ((options.initial && !fitViewOnInitDone && fitViewOnInit) || !options.initial) {
if (d3Zoom && d3Selection) { if (d3Zoom && d3Selection) {
const nodes = Array.from(nodeInternals.values()).filter((n) => const nodes = getNodes().filter((n) => (options.includeHiddenNodes ? n.width && n.height : !n.hidden));
options.includeHiddenNodes ? n.width && n.height : !n.hidden
);
const nodesInitialized = nodes.every((n) => n.width && n.height); const nodesInitialized = nodes.every((n) => n.width && n.height);
+1
View File
@@ -212,6 +212,7 @@ export type ReactFlowStore = {
export type ReactFlowActions = { export type ReactFlowActions = {
setNodes: (nodes: Node[]) => void; setNodes: (nodes: Node[]) => void;
getNodes: () => Node[];
setEdges: (edges: Edge[]) => void; setEdges: (edges: Edge[]) => void;
setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => void; setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => void;
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void; updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
+9 -3
View File
@@ -14,6 +14,7 @@ import {
getBoundsOfRects, getBoundsOfRects,
useStoreApi, useStoreApi,
getNodePositionWithOrigin, getNodePositionWithOrigin,
CoordinateExtent,
} from '@reactflow/core'; } from '@reactflow/core';
import type { ReactFlowState, Rect } from '@reactflow/core'; import type { ReactFlowState, Rect } from '@reactflow/core';
@@ -26,7 +27,7 @@ const defaultWidth = 200;
const defaultHeight = 150; const defaultHeight = 150;
const selector = (s: ReactFlowState) => { const selector = (s: ReactFlowState) => {
const nodes = Array.from(s.nodeInternals.values()); const nodes = s.getNodes();
const viewBB: Rect = { const viewBB: Rect = {
x: -s.transform[0] / s.transform[2], x: -s.transform[0] / s.transform[2],
y: -s.transform[1] / s.transform[2], y: -s.transform[1] / s.transform[2],
@@ -109,7 +110,7 @@ function MiniMap({
}; };
const panHandler = (event: D3ZoomEvent<HTMLDivElement, any>) => { const panHandler = (event: D3ZoomEvent<HTMLDivElement, any>) => {
const { transform, d3Selection, d3Zoom } = store.getState(); const { transform, d3Selection, d3Zoom, translateExtent, width, height } = store.getState();
if (event.sourceEvent.type !== 'mousemove' || !d3Selection || !d3Zoom) { if (event.sourceEvent.type !== 'mousemove' || !d3Selection || !d3Zoom) {
return; return;
@@ -120,10 +121,15 @@ function MiniMap({
x: transform[0] - event.sourceEvent.movementX * viewScaleRef.current * Math.max(1, transform[2]), x: transform[0] - event.sourceEvent.movementX * viewScaleRef.current * Math.max(1, transform[2]),
y: transform[1] - event.sourceEvent.movementY * viewScaleRef.current * Math.max(1, transform[2]), y: transform[1] - event.sourceEvent.movementY * viewScaleRef.current * Math.max(1, transform[2]),
}; };
const extent: CoordinateExtent = [
[0, 0],
[width, height],
];
const nextTransform = zoomIdentity.translate(position.x, position.y).scale(transform[2]); const nextTransform = zoomIdentity.translate(position.x, position.y).scale(transform[2]);
const constrainedTransform = d3Zoom.constrain()(nextTransform, extent, translateExtent);
d3Zoom.transform(d3Selection, nextTransform); d3Zoom.transform(d3Selection, constrainedTransform);
}; };
const zoomAndPanHandler = zoom() const zoomAndPanHandler = zoom()
+1 -1
View File
@@ -31,7 +31,7 @@ const nodesEqualityFn = (a: Node[], b: Node[]) => {
const storeSelector = (state: ReactFlowState) => ({ const storeSelector = (state: ReactFlowState) => ({
transform: state.transform, transform: state.transform,
nodeOrigin: state.nodeOrigin, nodeOrigin: state.nodeOrigin,
selectedNodesCount: Array.from(state.nodeInternals.values()).filter((node) => node.selected).length, selectedNodesCount: state.getNodes().filter((node) => node.selected).length,
}); });
function getTransform(nodeRect: Rect, transform: Transform, position: Position, offset: number): string { function getTransform(nodeRect: Rect, transform: Transform, position: Position, offset: number): string {