@matthewgapp/solidjs-flow
Version:
React Flow - A highly customizable React library for building node-based editors and interactive flow charts.
244 lines (201 loc) • 7.51 kB
text/typescript
import {
evaluateAbsolutePosition,
getElementsToRemove,
getOverlappingArea,
isRectObject,
nodeToRect,
type Rect,
} from '@xyflow/system';
import useViewportHelper from './useViewportHelper';
import { useStoreApi } from './useStore';
import { useBatchContext } from '../components/BatchProvider';
import { isNode } from '../utils';
import type { ReactFlowInstance, Instance, Node, Edge, InternalNode } from '../types';
import { batch } from 'solid-js';
/**
* Hook for accessing the ReactFlow instance.
*
* @public
* @returns ReactFlowInstance
*/
export function useSolidFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(): ReactFlowInstance<
NodeType,
EdgeType
> {
const viewportHelper = useViewportHelper();
const store = useStoreApi();
const batchContext = useBatchContext();
const getNodes = () => store.nodes.get().map((n) => ({ ...n })) as NodeType[];
const getInternalNode: Instance.GetInternalNode<NodeType> = (id) =>
store.nodeLookup.get(id) as InternalNode<NodeType>;
const getNode: Instance.GetNode<NodeType> = (id) => getInternalNode(id)?.internals.userNode as NodeType;
const getEdges: Instance.GetEdges<EdgeType> = () => {
const { edges } = store;
return edges.get().map((e) => ({ ...e })) as EdgeType[];
};
const getEdge: Instance.GetEdge<EdgeType> = (id) => store.edgeLookup.get(id) as EdgeType;
const setNodes: Instance.SetNodes<NodeType> = (payload) => {
batchContext.nodeQueue.push(payload as NodeType[]);
};
const setEdges: Instance.SetEdges<EdgeType> = (payload) => {
batchContext.edgeQueue.push(payload as EdgeType[]);
};
const addNodes: Instance.AddNodes<NodeType> = (payload) => {
const newNodes = Array.isArray(payload) ? payload : [payload];
batchContext.nodeQueue.push((nodes) => [...nodes, ...newNodes]);
};
const addEdges: Instance.AddEdges<EdgeType> = (payload) => {
const newEdges = Array.isArray(payload) ? payload : [payload];
batchContext.edgeQueue.push((edges) => [...edges, ...newEdges]);
};
const toObject: Instance.ToObject<NodeType, EdgeType> = () => {
const { nodes, edges, transform } = store;
const [x, y, zoom] = transform.get();
return {
nodes: nodes.get().map((n) => ({ ...n })) as NodeType[],
edges: edges.get().map((e) => ({ ...e })) as EdgeType[],
viewport: {
x,
y,
zoom,
},
};
};
const deleteElements: Instance.DeleteElements = async ({ nodes: nodesToRemove = [], edges: edgesToRemove = [] }) => {
const {
nodes,
edges,
hasDefaultNodes,
hasDefaultEdges,
onNodesDelete,
onEdgesDelete,
onNodesChange,
onEdgesChange,
onDelete,
onBeforeDelete,
} = store;
const { nodes: matchingNodes, edges: matchingEdges } = await getElementsToRemove({
nodesToRemove,
edgesToRemove,
nodes: nodes.get(),
edges: edges.get(),
onBeforeDelete: onBeforeDelete.get(),
});
console.log('matchingNodes', matchingNodes);
console.log('matchingEdges', matchingEdges);
return batch(() => {
const hasMatchingEdges = matchingEdges.length > 0;
const hasMatchingNodes = matchingNodes.length > 0;
if (hasMatchingEdges) {
onNodesDelete.get()?.(matchingNodes);
}
if (hasMatchingEdges) {
if (hasDefaultEdges.get()) {
const nextEdges = edges.get().filter((e) => !matchingEdges.some((mE) => mE.id === e.id));
store.setEdges(nextEdges);
}
onEdgesDelete.get()?.(matchingEdges);
onEdgesChange.get()?.(
matchingEdges.map((edge) => ({
id: edge.id,
type: 'remove',
}))
);
}
if (hasMatchingNodes) {
if (hasDefaultNodes.get()) {
const nextNodes = nodes.get().filter((n) => !matchingNodes.some((mN) => mN.id === n.id));
store.setNodes(nextNodes);
}
onNodesDelete.get()?.(matchingNodes);
onNodesChange.get()?.(matchingNodes.map((node) => ({ id: node.id, type: 'remove' })));
}
if (hasMatchingNodes || hasMatchingEdges) {
onDelete.get()?.({ nodes: matchingNodes, edges: matchingEdges });
}
return { deletedNodes: matchingNodes, deletedEdges: matchingEdges };
});
};
const getNodeRect = (node: NodeType | { id: string }): Rect | null => {
const { nodeLookup, nodeOrigin } = store;
const nodeToUse = isNode<NodeType>(node) ? node : nodeLookup.get(node.id)!;
const position = nodeToUse.parentId
? evaluateAbsolutePosition(nodeToUse.position, nodeToUse.parentId, nodeLookup, nodeOrigin.get())
: nodeToUse.position;
const nodeWithPosition = {
id: nodeToUse.id,
position,
width: nodeToUse.measured?.width ?? nodeToUse.width,
height: nodeToUse.measured?.height ?? nodeToUse.height,
data: nodeToUse.data,
};
return nodeToRect(nodeWithPosition);
};
const getIntersectingNodes: Instance.GetIntersectingNodes<NodeType> = (nodeOrRect, partially = true, nodes) => {
const isRect = isRectObject(nodeOrRect);
const nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect);
const hasNodesOption = nodes !== undefined;
if (!nodeRect) {
return [];
}
return (nodes || store.nodes.get()).filter((n) => {
const internalNode = store.nodeLookup.get(n.id);
if (internalNode && !isRect && (n.id === nodeOrRect!.id || !internalNode.internals.positionAbsolute)) {
return false;
}
const currNodeRect = nodeToRect(hasNodesOption ? n : internalNode!);
const overlappingArea = getOverlappingArea(currNodeRect, nodeRect);
const partiallyVisible = partially && overlappingArea > 0;
return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
}) as NodeType[];
};
const isNodeIntersecting: Instance.IsNodeIntersecting<NodeType> = (nodeOrRect, area, partially = true) => {
const isRect = isRectObject(nodeOrRect);
const nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect);
if (!nodeRect) {
return false;
}
const overlappingArea = getOverlappingArea(nodeRect, area);
const partiallyVisible = partially && overlappingArea > 0;
return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
};
const updateNode: Instance.UpdateNode<NodeType> = (id, nodeUpdate, options = { replace: false }) => {
setNodes((prevNodes) =>
prevNodes.map((node) => {
if (node.id === id) {
const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node as NodeType) : nodeUpdate;
return options.replace && isNode(nextNode) ? (nextNode as NodeType) : { ...node, ...nextNode };
}
return node;
})
);
};
const updateNodeData: Instance.UpdateNodeData<NodeType> = (id, dataUpdate, options = { replace: false }) => {
updateNode(
id,
(node) => {
const nextData = typeof dataUpdate === 'function' ? dataUpdate(node) : dataUpdate;
return options.replace ? { ...node, data: nextData } : { ...node, data: { ...node.data, ...nextData } };
},
options
);
};
return {
...viewportHelper,
getNodes,
getNode,
getInternalNode,
getEdges,
getEdge,
setNodes,
setEdges,
addNodes,
addEdges,
toObject,
deleteElements,
getIntersectingNodes,
isNodeIntersecting,
updateNode,
updateNodeData,
};
}