refactor(nodes): only use node internals

This commit is contained in:
moklick
2021-11-18 13:01:11 +01:00
parent f0e79c634a
commit 0974aa5732
21 changed files with 171 additions and 151 deletions
+1
View File
@@ -147,6 +147,7 @@ const BasicFlow = () => {
}; };
const onNodesChange = useCallback((changes: NodeChange[]) => { const onNodesChange = useCallback((changes: NodeChange[]) => {
console.log('node change', changes);
setNodes((ns) => applyNodeChanges(changes, ns)); setNodes((ns) => applyNodeChanges(changes, ns));
}, []); }, []);
+4 -4
View File
@@ -3,13 +3,13 @@ import { EdgeProps, useStore, getBezierPath, ReactFlowState } from 'react-flow-r
import { getEdgeParams } from './utils'; import { getEdgeParams } from './utils';
const nodeSelector = (s: ReactFlowState) => s.nodes; const nodeSelector = (s: ReactFlowState) => s.nodeInternals;
const FloatingEdge: FC<EdgeProps> = ({ id, source, target, style }) => { const FloatingEdge: FC<EdgeProps> = ({ id, source, target, style }) => {
const nodes = useStore(nodeSelector); const nodeInternals = useStore(nodeSelector);
const sourceNode = useMemo(() => nodes.find((n) => n.id === source), [source, nodes]); const sourceNode = useMemo(() => nodeInternals.get(source), [source, nodeInternals]);
const targetNode = useMemo(() => nodes.find((n) => n.id === target), [target, nodes]); const targetNode = useMemo(() => nodeInternals.get(target), [target, nodeInternals]);
if (!sourceNode || !targetNode) { if (!sourceNode || !targetNode) {
return null; return null;
+62
View File
@@ -0,0 +1,62 @@
import { useState, useCallback } from 'react';
import { useEffect } from 'react';
import ReactFlow, { addEdge, MiniMap, Controls, Connection, Edge, Node } from 'react-flow-renderer';
const initialNodes: Node[] = [
{ id: '1', type: 'input', isHidden: true, data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
{ id: '2', isHidden: true, data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
{ id: '3', isHidden: true, data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
{ id: '4', isHidden: true, data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
];
const initialEdges: Edge[] = [
{ id: 'e1-2', source: '1', target: '2' },
{ id: 'e1-3', source: '1', target: '3' },
{ id: 'e3-4', source: '3', target: '4' },
];
const setHidden = (isHidden: boolean) => (els: any[]) =>
els.map((e: any) => {
e.isHidden = isHidden;
return e;
});
const HiddenFlow = () => {
const [nodes, setNodes] = useState<Node[]>(initialNodes);
const [edges, setEdges] = useState<Edge[]>(initialEdges);
const [isHidden, setIsHidden] = useState<boolean>(true);
const onConnect = useCallback((params: Edge | Connection) => {
setEdges((eds) => addEdge(params, eds));
}, []);
useEffect(() => {
setNodes(setHidden(isHidden));
setNodes(setHidden(isHidden));
}, [isHidden]);
return (
<ReactFlow nodes={nodes} edges={edges} onConnect={onConnect}>
<MiniMap />
<Controls />
<div style={{ position: 'absolute', left: 10, top: 10, zIndex: 4 }}>
<div>
<label htmlFor="ishidden">
isHidden
<input
id="ishidden"
type="checkbox"
checked={isHidden}
onChange={(event) => setIsHidden(event.target.checked)}
className="react-flow__ishidden"
/>
</label>
</div>
</div>
</ReactFlow>
);
};
export default HiddenFlow;
+2
View File
@@ -51,6 +51,8 @@ const StressFlow = () => {
}; };
const onNodesChange = useCallback((changes: NodeChange[]) => { const onNodesChange = useCallback((changes: NodeChange[]) => {
console.log('node change', changes);
setNodes((ns) => applyNodeChanges(changes, ns)); setNodes((ns) => applyNodeChanges(changes, ns));
}, []); }, []);
+5
View File
@@ -9,6 +9,7 @@ import CustomNode from './CustomNode';
import FloatingEdges from './FloatingEdges'; import FloatingEdges from './FloatingEdges';
import Layouting from './Layouting'; import Layouting from './Layouting';
import NestedNodes from './NestedNodes'; import NestedNodes from './NestedNodes';
import Hidden from './Hidden';
import './index.css'; import './index.css';
@@ -41,6 +42,10 @@ const routes = [
path: '/nested-nodes', path: '/nested-nodes',
component: NestedNodes, component: NestedNodes,
}, },
{
path: '/hidden',
component: Hidden,
},
]; ];
const Header = withRouter(({ history, location }) => { const Header = withRouter(({ history, location }) => {
-53
View File
@@ -1,53 +0,0 @@
import React, { useState } from 'react';
import { useEffect } from 'react';
import ReactFlow, { addEdge, MiniMap, Controls, Connection, Edge, Elements } from 'react-flow-renderer';
const initialElements: Elements = [
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
{ id: 'e1-2', source: '1', target: '2' },
{ id: 'e1-3', source: '1', target: '3' },
{ id: 'e3-4', source: '3', target: '4' },
];
const HiddenFlow = () => {
const [elements, setElements] = useState<Elements>(initialElements);
const [isHidden, setIsHidden] = useState<boolean>(false);
const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els));
useEffect(() => {
setElements((els) =>
els.map((e) => {
e.isHidden = isHidden;
return e;
})
);
}, [isHidden]);
return (
<ReactFlow elements={elements} onConnect={onConnect}>
<MiniMap />
<Controls />
<div style={{ position: 'absolute', left: 10, top: 10, zIndex: 4 }}>
<div>
<label htmlFor="ishidden">
isHidden
<input
id="ishidden"
type="checkbox"
checked={isHidden}
onChange={(event) => setIsHidden(event.target.checked)}
className="react-flow__ishidden"
/>
</label>
</div>
</div>
</ReactFlow>
);
};
export default HiddenFlow;
+7 -12
View File
@@ -28,7 +28,6 @@ const selector = (s: ReactFlowState) => ({
width: s.width, width: s.width,
height: s.height, height: s.height,
transform: s.transform, transform: s.transform,
nodes: s.nodes,
nodeInternals: s.nodeInternals, nodeInternals: s.nodeInternals,
}); });
@@ -42,13 +41,7 @@ const MiniMap = ({
nodeStrokeWidth = 2, nodeStrokeWidth = 2,
maskColor = 'rgb(240, 242, 243, 0.7)', maskColor = 'rgb(240, 242, 243, 0.7)',
}: MiniMapProps) => { }: MiniMapProps) => {
const { const { width: containerWidth, height: containerHeight, transform, nodeInternals } = useStore(selector, shallow);
width: containerWidth,
height: containerHeight,
transform,
nodes,
nodeInternals,
} = useStore(selector, shallow);
const [tX, tY, tScale] = transform; const [tX, tY, tScale] = transform;
const mapClasses = cc(['react-flow__minimap', className]); const mapClasses = cc(['react-flow__minimap', className]);
@@ -59,7 +52,9 @@ const MiniMap = ({
nodeStrokeColor instanceof Function ? nodeStrokeColor : () => nodeStrokeColor nodeStrokeColor instanceof Function ? nodeStrokeColor : () => nodeStrokeColor
) as StringFunc; ) as StringFunc;
const nodeClassNameFunc = (nodeClassName instanceof Function ? nodeClassName : () => nodeClassName) as StringFunc; const nodeClassNameFunc = (nodeClassName instanceof Function ? nodeClassName : () => nodeClassName) as StringFunc;
const hasNodes = nodes && nodes.length; const hasNodes = nodeInternals && nodeInternals.size > 0;
// @TODO: work with nodeInternals instead of converting it to an array
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
const bb = getRectOfNodes(nodes); const bb = getRectOfNodes(nodes);
const viewBB: Rect = { const viewBB: Rect = {
x: -tX / tScale, x: -tX / tScale,
@@ -88,9 +83,9 @@ const MiniMap = ({
style={style} style={style}
className={mapClasses} className={mapClasses}
> >
{nodes {Array.from(nodeInternals)
.filter((node) => !node.isHidden && node.width && node.height) .filter(([_, node]) => !node.isHidden && node.width && node.height)
.map((node) => { .map(([_, node]) => {
const positionAbsolute = nodeInternals.get(node.id)?.positionAbsolute; const positionAbsolute = nodeInternals.get(node.id)?.positionAbsolute;
return ( return (
+3 -3
View File
@@ -27,7 +27,7 @@ interface ConnectionLineProps {
CustomConnectionLineComponent?: ConnectionLineComponent; CustomConnectionLineComponent?: ConnectionLineComponent;
} }
const selector = (s: ReactFlowState) => ({ nodeInternals: s.nodeInternals, nodes: s.nodes, transform: s.transform }); const selector = (s: ReactFlowState) => ({ nodeInternals: s.nodeInternals, transform: s.transform });
export default ({ export default ({
connectionNodeId, connectionNodeId,
@@ -43,9 +43,9 @@ export default ({
const nodeId = connectionNodeId; const nodeId = connectionNodeId;
const handleId = connectionHandleId; const handleId = connectionHandleId;
const { nodeInternals, nodes, transform } = useStore(selector, shallow); const { nodeInternals, transform } = useStore(selector, shallow);
const sourceNodeInternals = useRef<NodeInternalsItem | undefined>(nodeInternals.get(nodeId)); const sourceNodeInternals = useRef<NodeInternalsItem | undefined>(nodeInternals.get(nodeId));
const sourceNode = useRef<Node | undefined>(nodes.find((n) => n.id === nodeId)); const sourceNode = useRef<Node | undefined>(nodeInternals.get(nodeId));
if ( if (
!sourceNode.current || !sourceNode.current ||
+1 -7
View File
@@ -56,7 +56,7 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
unselectNodesAndEdges, unselectNodesAndEdges,
unsetNodesSelection, unsetNodesSelection,
updateNodePosition, updateNodePosition,
updateNodeDimensions, // updateNodeDimensions,
} = useStore(selector, shallow); } = useStore(selector, shallow);
const nodeElement = useRef<HTMLDivElement>(null); const nodeElement = useRef<HTMLDivElement>(null);
@@ -201,12 +201,6 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
[node, onNodeDoubleClick] [node, onNodeDoubleClick]
); );
useEffect(() => {
if (nodeElement.current && (!isHidden || !isInitialized)) {
updateNodeDimensions([{ id, nodeElement: nodeElement.current, forceUpdate: true }]);
}
}, [id, isHidden, sourcePosition, targetPosition, type, isInitialized]);
useEffect(() => { useEffect(() => {
if (nodeElement.current) { if (nodeElement.current) {
const currNode = nodeElement.current; const currNode = nodeElement.current;
+4 -2
View File
@@ -15,12 +15,14 @@ export interface NodesSelectionProps {
onSelectionDragStop?: (event: MouseEvent, nodes: Node[]) => void; onSelectionDragStop?: (event: MouseEvent, nodes: Node[]) => void;
onSelectionContextMenu?: (event: MouseEvent, nodes: Node[]) => void; onSelectionContextMenu?: (event: MouseEvent, nodes: Node[]) => void;
} }
// @TODO: work with nodeInternals instead of converting it to an array
const selector = (s: ReactFlowState) => ({ const selector = (s: ReactFlowState) => ({
transform: s.transform, transform: s.transform,
selectedNodesBbox: s.selectedNodesBbox, selectedNodesBbox: s.selectedNodesBbox,
selectionActive: s.selectionActive, selectionActive: s.selectionActive,
selectedNodes: s.nodes.filter((n) => n.selected), selectedNodes: Array.from(s.nodeInternals)
.filter(([_, n]) => n.selected)
.map(([_, n]) => n),
snapToGrid: s.snapToGrid, snapToGrid: s.snapToGrid,
snapGrid: s.snapGrid, snapGrid: s.snapGrid,
updateNodePosition: s.updateNodePosition, updateNodePosition: s.updateNodePosition,
+4 -1
View File
@@ -8,8 +8,11 @@ interface SelectionListenerProps {
onSelectionChange: OnSelectionChangeFunc; onSelectionChange: OnSelectionChangeFunc;
} }
// @TODO: work with nodeInternals instead of converting it to an array
const selectedElementsSelector = (s: ReactFlowState) => ({ const selectedElementsSelector = (s: ReactFlowState) => ({
selectedNodes: s.nodes.filter((n) => n.selected), selectedNodes: Array.from(s.nodeInternals)
.filter(([_, n]) => n.selected)
.map(([_, node]) => node),
selectedEdges: s.edges.filter((e) => e.selected), selectedEdges: s.edges.filter((e) => e.selected),
}); });
+13 -8
View File
@@ -3,8 +3,6 @@ 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 useVisibleNodes from '../../hooks/useVisibleNodes';
import useNodeInternalsRef from '../../hooks/useNodeInternalsRef';
interface NodeRendererProps { interface NodeRendererProps {
nodeTypes: NodeTypesType; nodeTypes: NodeTypesType;
@@ -29,13 +27,20 @@ const selector = (s: ReactFlowState) => ({
updateNodeDimensions: s.updateNodeDimensions, updateNodeDimensions: s.updateNodeDimensions,
snapGrid: s.snapGrid, snapGrid: s.snapGrid,
snapToGrid: s.snapToGrid, snapToGrid: s.snapToGrid,
nodeInternals: s.nodeInternals,
}); });
const NodeRenderer = (props: NodeRendererProps) => { const NodeRenderer = (props: NodeRendererProps) => {
const { scale, nodesDraggable, nodesConnectable, elementsSelectable, updateNodeDimensions, snapGrid, snapToGrid } = const {
useStore(selector, shallow); scale,
const nodeInternals = useNodeInternalsRef(); nodesDraggable,
const nodes = useVisibleNodes(props.onlyRenderVisibleElements); nodesConnectable,
elementsSelectable,
updateNodeDimensions,
snapGrid,
snapToGrid,
nodeInternals,
} = useStore(selector, shallow);
const resizeObserver = useMemo(() => { const resizeObserver = useMemo(() => {
if (typeof ResizeObserver === 'undefined') { if (typeof ResizeObserver === 'undefined') {
@@ -54,9 +59,9 @@ const NodeRenderer = (props: NodeRendererProps) => {
return ( return (
<div className="react-flow__nodes react-flow__container"> <div className="react-flow__nodes react-flow__container">
{nodes.map((node) => { {Array.from(nodeInternals).map(([_, node]) => {
const nodeType = node.type || 'default'; const nodeType = node.type || 'default';
const internals = nodeInternals.current.get(node.id); const internals = nodeInternals.get(node.id);
if (!props.nodeTypes[nodeType]) { if (!props.nodeTypes[nodeType]) {
console.warn(`Node type "${nodeType}" not found. Using fallback type "default".`); console.warn(`Node type "${nodeType}" not found. Using fallback type "default".`);
+3 -1
View File
@@ -28,7 +28,9 @@ export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => {
const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode); const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode);
useEffect(() => { useEffect(() => {
const { nodes, edges } = store.getState(); const { nodeInternals, edges } = store.getState();
// @TODO: work with nodeInternals instead of converting it to an array
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
const selectedNodes = nodes.filter((n) => n.selected); const selectedNodes = nodes.filter((n) => n.selected);
const selectedEdges = edges.filter((e) => e.selected); const selectedEdges = edges.filter((e) => e.selected);
-14
View File
@@ -1,14 +0,0 @@
import { useRef, useEffect } from 'react';
import { useStoreApi } from '../store';
function useNodeInternalsRef() {
const store = useStoreApi();
const nodeInternals = useRef(store.getState().nodeInternals);
useEffect(() => store.subscribe((state) => (nodeInternals.current = state.nodeInternals)), []);
return nodeInternals;
}
export default useNodeInternalsRef;
+6 -3
View File
@@ -19,7 +19,9 @@ function useOnLoadHandler(onLoad: OnLoad<any> | undefined) {
}; };
const getNodes = (): Node[] => { const getNodes = (): Node[] => {
const { nodes = [] } = store.getState(); const { nodeInternals } = store.getState();
// @TODO: work with nodeInternals instead of converting it to an array
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
return nodes.map((n) => ({ ...n })); return nodes.map((n) => ({ ...n }));
}; };
@@ -29,8 +31,9 @@ function useOnLoadHandler(onLoad: OnLoad<any> | undefined) {
}; };
const toObject = (): FlowExportObject => { const toObject = (): FlowExportObject => {
const { nodes = [], edges = [], transform } = store.getState(); const { nodeInternals, edges = [], transform } = store.getState();
// @TODO: work with nodeInternals instead of converting it to an array
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
return { return {
nodes: nodes.map((n) => ({ ...n })), nodes: nodes.map((n) => ({ ...n })),
edges: edges.map((e) => ({ ...e })), edges: edges.map((e) => ({ ...e })),
+4 -2
View File
@@ -8,9 +8,11 @@ function useVisibleNodes(onlyRenderVisible: boolean) {
const nodes = useStore( const nodes = useStore(
useCallback( useCallback(
(s: ReactFlowState) => { (s: ReactFlowState) => {
// @TODO: work with nodeInternals instead of converting it to an array
const nodes = Array.from(s.nodeInternals).map(([_, node]) => node);
return onlyRenderVisible return onlyRenderVisible
? getNodesInside(s.nodes, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true) ? getNodesInside(nodes, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true)
: s.nodes; : nodes;
}, },
[onlyRenderVisible] [onlyRenderVisible]
) )
+3 -2
View File
@@ -41,8 +41,9 @@ const useZoomPanHelper = (): ZoomPanHelperFunctions => {
d3Zoom.transform(d3Selection, nextTransform); d3Zoom.transform(d3Selection, nextTransform);
}, },
fitView: (options: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => { fitView: (options: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => {
const { nodes, width, height, minZoom, maxZoom } = store.getState(); const { nodeInternals, width, height, minZoom, maxZoom } = store.getState();
// @TODO: work with nodeInternals instead of converting it to an array
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
if (!nodes.length) { if (!nodes.length) {
return; return;
} }
+39 -27
View File
@@ -42,7 +42,6 @@ const createStore = () =>
width: 0, width: 0,
height: 0, height: 0,
transform: [0, 0, 1], transform: [0, 0, 1],
nodes: [],
edges: [], edges: [],
onNodesChange: null, onNodesChange: null,
onEdgesChange: null, onEdgesChange: null,
@@ -87,28 +86,29 @@ const createStore = () =>
setNodes: (nodes: Node[]) => { setNodes: (nodes: Node[]) => {
const nodeInternals = createNodeInternals(nodes, get().nodeInternals); const nodeInternals = createNodeInternals(nodes, get().nodeInternals);
set({ nodes, nodeInternals }); set({ nodeInternals });
}, },
setEdges: (edges: Edge[]) => { setEdges: (edges: Edge[]) => {
set({ edges }); set({ edges });
}, },
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => { updateNodeDimensions: (updates: NodeDimensionUpdate[]) => {
const { onNodesChange, nodes, transform, nodeInternals } = get(); const { onNodesChange, transform, nodeInternals } = get();
const nodesToChange: NodeChange[] = updates.reduce<NodeChange[]>((res, update) => { const nodesToChange: NodeChange[] = updates.reduce<NodeChange[]>((res, update) => {
const node = nodes.find((n) => n.id === update.id); const node = nodeInternals.get(update.id);
if (node) { if (node) {
const dimensions = getDimensions(update.nodeElement); const dimensions = getDimensions(update.nodeElement);
const doUpdate = const doUpdate = !!(
dimensions.width && dimensions.width &&
dimensions.height && dimensions.height &&
(node.width !== dimensions.width || node.height !== dimensions.height || update.forceUpdate); (node.width !== dimensions.width || node.height !== dimensions.height || update.forceUpdate)
);
if (doUpdate) { if (doUpdate) {
const handleBounds = getHandleBounds(update.nodeElement, transform[2]); const handleBounds = getHandleBounds(update.nodeElement, transform[2]);
nodeInternals.set(node.id, { nodeInternals.set(node.id, {
...nodeInternals.get(node.id), ...node,
handleBounds, handleBounds,
...dimensions, ...dimensions,
}); });
@@ -127,17 +127,19 @@ const createStore = () =>
set({ nodeInternals: new Map(nodeInternals) }); set({ nodeInternals: new Map(nodeInternals) });
onNodesChange?.(nodesToChange); if (nodesToChange?.length > 0) {
onNodesChange?.(nodesToChange);
}
}, },
updateNodePosition: ({ id, diff, dragging }: NodeDiffUpdate) => { updateNodePosition: ({ id, diff, dragging }: NodeDiffUpdate) => {
const { onNodesChange, nodes, nodeExtent, nodeInternals } = get(); const { onNodesChange, nodeExtent, nodeInternals } = get();
if (onNodesChange) { if (onNodesChange) {
const matchingNodes = nodes.filter((n) => !!(n.selected || n.id === id)); const nodes = Array.from(nodeInternals);
const matchingNodes = nodes.filter(([_, n]) => !!(n.selected || n.id === id));
if (matchingNodes?.length) { if (matchingNodes?.length) {
onNodesChange( onNodesChange(
matchingNodes?.map((node) => { matchingNodes?.map(([_, node]) => {
const change: NodeDimensionChange = { const change: NodeDimensionChange = {
id: node.id, id: node.id,
type: 'dimensions', type: 'dimensions',
@@ -190,7 +192,7 @@ const createStore = () =>
}); });
}, },
updateUserSelection: (mousePos: XYPosition) => { updateUserSelection: (mousePos: XYPosition) => {
const { userSelectionRect, nodes, edges, transform, onNodesChange, onEdgesChange } = get(); const { userSelectionRect, nodeInternals, edges, transform, onNodesChange, onEdgesChange } = get();
const startX = userSelectionRect.startX ?? 0; const startX = userSelectionRect.startX ?? 0;
const startY = userSelectionRect.startY ?? 0; const startY = userSelectionRect.startY ?? 0;
@@ -202,6 +204,8 @@ const createStore = () =>
height: Math.abs(mousePos.y - startY), 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 selectedNodes = getNodesInside(nodes, nextUserSelectRect, transform, false, true);
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);
@@ -218,7 +222,9 @@ const createStore = () =>
}); });
}, },
unsetUserSelection: () => { unsetUserSelection: () => {
const { userSelectionRect, nodes } = get(); 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 selectedNodes = nodes.filter((node) => node.selected);
const stateUpdate = { const stateUpdate = {
@@ -240,8 +246,9 @@ const createStore = () =>
set(stateUpdate); set(stateUpdate);
}, },
addSelectedElements: (selectedElementsArr: Array<Node | Edge>) => { addSelectedElements: (selectedElementsArr: Array<Node | Edge>) => {
const { multiSelectionActive, onNodesChange, onEdgesChange, nodes, edges } = get(); const { multiSelectionActive, onNodesChange, onEdgesChange, nodeInternals, edges } = get();
// @TODO: work with nodeInternals instead of converting it to an array
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
let changedNodes; let changedNodes;
let changedEdges; let changedEdges;
@@ -266,7 +273,9 @@ const createStore = () =>
} }
}, },
unselectNodesAndEdges: () => { unselectNodesAndEdges: () => {
const { nodes, edges, onNodesChange, onEdgesChange } = get(); 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.map((n) => { const nodesToUnselect = nodes.map((n) => {
n.selected = false; n.selected = false;
@@ -308,8 +317,9 @@ const createStore = () =>
}, },
resetSelectedElements: () => { resetSelectedElements: () => {
const { nodes, edges, onNodesChange, onEdgesChange } = get(); 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 nodesToUnselect = nodes.filter((e) => e.selected).map(createNodeOrEdgeSelectionChange(false));
const edgesToUnselect = edges.filter((e) => e.selected).map(createNodeOrEdgeSelectionChange(false)); const edgesToUnselect = edges.filter((e) => e.selected).map(createNodeOrEdgeSelectionChange(false));
@@ -320,16 +330,18 @@ const createStore = () =>
onEdgesChange?.(edgesToUnselect as EdgeChange[]); onEdgesChange?.(edgesToUnselect as EdgeChange[]);
} }
}, },
setNodeExtent: (nodeExtent: CoordinateExtent) => setNodeExtent: (nodeExtent: CoordinateExtent) => {
const { nodeInternals } = get();
nodeInternals.forEach((node) => {
node.positionAbsolute = clampPosition(node.position, nodeExtent);
});
set({ set({
nodeExtent, nodeExtent,
nodes: get().nodes.map((node) => { nodeInternals: new Map(nodeInternals),
return { });
...node, },
position: clampPosition(node.position, nodeExtent),
};
}),
}),
unsetNodesSelection: () => set({ nodesSelectionActive: false }), unsetNodesSelection: () => set({ nodesSelectionActive: false }),
updateTransform: (transform: Transform) => set({ transform }), updateTransform: (transform: Transform) => set({ transform }),
updateSize: (size: Dimensions) => set({ width: size.width || 500, height: size.height || 500 }), updateSize: (size: Dimensions) => set({ width: size.width || 500, height: size.height || 500 }),
+9 -5
View File
@@ -39,11 +39,11 @@ export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals)
const z = node.zIndex ? node.zIndex : node.dragging || node.selected ? 1000 : 0; const z = node.zIndex ? node.zIndex : node.dragging || node.selected ? 1000 : 0;
const internals: NodeInternalsItem = { const internals: NodeInternalsItem = {
...nodeInternals.get(node.id), ...nodeInternals.get(node.id),
id: node.id, ...node,
width: node.width || null, positionAbsolute: {
height: node.height || null, x: node.position.x,
position: node.position, y: node.position.y,
positionAbsolute: node.position, },
z, z,
}; };
if (node.parentNode) { if (node.parentNode) {
@@ -56,6 +56,10 @@ export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals)
nodes.forEach((node) => { nodes.forEach((node) => {
const updatedInternals: NodeInternalsItem = nextNodeInternals.get(node.id)!; const updatedInternals: NodeInternalsItem = nextNodeInternals.get(node.id)!;
if (node.parentNode && !nextNodeInternals.has(node.parentNode)) {
throw new Error(`Parent node ${node.parentNode} not found`);
}
if (node.parentNode || parentNodes[node.id]) { if (node.parentNode || parentNodes[node.id]) {
let startingZ = updatedInternals.z || 0; let startingZ = updatedInternals.z || 0;
-1
View File
@@ -134,7 +134,6 @@ export interface ReactFlowState {
width: number; width: number;
height: number; height: number;
transform: Transform; transform: Transform;
nodes: Node[];
nodeInternals: NodeInternals; nodeInternals: NodeInternals;
edges: Edge[]; edges: Edge[];
selectedNodesBbox: Rect; selectedNodesBbox: Rect;
+1 -6
View File
@@ -101,12 +101,7 @@ export type NodeDimensionUpdate = {
forceUpdate?: boolean; forceUpdate?: boolean;
}; };
export type NodeInternalsItem = { export type NodeInternalsItem = Node & {
id?: string;
width?: number | null;
height?: number | null;
parentNode?: string;
position?: XYPosition;
positionAbsolute?: XYPosition; positionAbsolute?: XYPosition;
handleBounds?: NodeHandleBounds; handleBounds?: NodeHandleBounds;
z?: number; z?: number;