refactor(nodes): only use node internals
This commit is contained in:
@@ -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));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -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 }) => {
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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 ||
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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".`);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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 })),
|
||||||
|
|||||||
@@ -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]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user