UNPKG

@hitachivantara/uikit-react-lab

Version:

Contributed React components for the NEXT UI Kit.

206 lines (205 loc) 6.41 kB
import { jsxs, Fragment, jsx } from "react/jsx-runtime"; import { useState, useRef, useCallback } from "react"; import { useDroppable, useDndMonitor } from "@dnd-kit/core"; import { Global } from "@emotion/react"; import { addEdge, applyNodeChanges, applyEdgeChanges, MarkerType, ReactFlow } from "reactflow"; import { uid } from "uid"; import { useUniqueId } from "@hitachivantara/uikit-react-core"; import { flowStyles } from "./base.js"; import { useClasses } from "./Flow.styles.js"; import { staticClasses } from "./Flow.styles.js"; import { useNodeMetaRegistry } from "./FlowContext/NodeMetaContext.js"; import { useFlowInstance } from "./hooks/useFlowInstance.js"; const getNode = (nodes, nodeId) => { return nodes.find((n) => n.id === nodeId); }; const validateEdge = (nodes, edges, connection, nodeMetaRegistry) => { const { source: sourceId, sourceHandle, target: targetId, targetHandle } = connection; if (!sourceHandle || !targetHandle || !sourceId || !targetId) return false; const sourceNode = getNode(nodes, sourceId); const targetNode = getNode(nodes, targetId); if (!sourceNode || !targetNode) return false; const sourceType = sourceNode.type; const targetType = targetNode.type; if (!sourceType || !targetType) return false; const inputs = nodeMetaRegistry[targetId]?.inputs || []; const outputs = nodeMetaRegistry[sourceId]?.outputs || []; const source = outputs.flatMap((out) => out.outputs || out).find((out) => out.id === sourceHandle); const target = inputs.flatMap((inp) => inp.inputs || inp).find((inp) => inp.id === targetHandle); const sourceProvides = source?.provides || ""; const targetAccepts = target?.accepts || []; const sourceMaxConnections = source?.maxConnections; const targetMaxConnections = target?.maxConnections; let isValid = targetAccepts.length === 0 || targetAccepts.includes(sourceProvides); if (isValid && targetMaxConnections != null) { const targetConnections = edges.filter( (edg) => edg.target === targetId && edg.targetHandle === targetHandle ).length; isValid = targetConnections < targetMaxConnections; } if (isValid && sourceMaxConnections != null) { const sourceConnections = edges.filter( (edg) => edg.source === sourceId && edg.sourceHandle === sourceHandle ).length; isValid = sourceConnections < sourceMaxConnections; } return isValid; }; const HvDroppableFlow = ({ id, className, children, onFlowChange, onDndDrop, classes: classesProp, nodes: initialNodes = [], edges: initialEdges = [], onConnect: onConnectProp, onNodesChange: onNodesChangeProp, onEdgesChange: onEdgesChangeProp, defaultEdgeOptions: defaultEdgeOptionsProp, nodeTypes, ...others }) => { const { classes, cx } = useClasses(classesProp); const elementId = useUniqueId(id); const reactFlowInstance = useFlowInstance(); const [nodes, setNodes] = useState(initialNodes); const [edges, setEdges] = useState(initialEdges); const nodesRef = useRef(initialNodes); const edgesRef = useRef(initialEdges); const updateNodes = (nds) => { setNodes(nds); nodesRef.current = nds; }; const updateEdges = (eds) => { setEdges(eds); edgesRef.current = eds; }; const { setNodeRef } = useDroppable({ id: elementId }); const handleDragEnd = useCallback( (event) => { if (event.over?.id !== elementId) return; const hvFlow = event.active.data.current?.hvFlow; const type = hvFlow?.type; if (!type || !nodeTypes?.[type]) { return; } const position = reactFlowInstance.screenToFlowPosition({ x: hvFlow?.x || 0, y: hvFlow?.y || 0 }); const data = { nodeLabel: hvFlow?.label, ...hvFlow?.data }; const newNode = { id: uid(), position, data, type }; if (onDndDrop) { onDndDrop(event, newNode); return; } updateNodes(nodes.concat(newNode)); }, [elementId, nodeTypes, nodes, onDndDrop, reactFlowInstance] ); useDndMonitor({ onDragEnd: handleDragEnd }); const handleFlowChange = useCallback( (nds, eds) => { const isDragging = nds.find((node) => node.dragging); if (!isDragging) { onFlowChange?.(nds, eds); } }, [onFlowChange] ); const handleConnect = useCallback( (connection) => { const eds = addEdge(connection, edgesRef.current); updateEdges(eds); handleFlowChange(nodesRef.current, eds); onConnectProp?.(connection); }, [handleFlowChange, onConnectProp] ); const handleNodesChange = useCallback( (changes) => { const nds = applyNodeChanges(changes, nodesRef.current); updateNodes(nds); handleFlowChange(nds, edgesRef.current); onNodesChangeProp?.(changes); }, [handleFlowChange, onNodesChangeProp] ); const handleEdgesChange = useCallback( (changes) => { const eds = applyEdgeChanges(changes, edgesRef.current); updateEdges(eds); handleFlowChange(nodesRef.current, eds); onEdgesChangeProp?.(changes); }, [handleFlowChange, onEdgesChangeProp] ); const { registry } = useNodeMetaRegistry(); const isValidConnection = (connection) => validateEdge(nodes, edges, connection, registry); const defaultEdgeOptions = { markerEnd: { type: MarkerType.ArrowClosed, height: 20, width: 20 }, type: "smoothstep", pathOptions: { borderRadius: 40 }, ...defaultEdgeOptionsProp }; return /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx(Global, { styles: flowStyles }), /* @__PURE__ */ jsx( "div", { id: elementId, ref: setNodeRef, className: cx(classes.root, className), children: /* @__PURE__ */ jsx( ReactFlow, { nodes, edges, nodeTypes, onNodesChange: handleNodesChange, onEdgesChange: handleEdgesChange, onConnect: handleConnect, isValidConnection, defaultEdgeOptions, snapGrid: [1, 1], snapToGrid: true, onError: (code, message) => { }, ...others, children } ) } ) ] }); }; export { HvDroppableFlow, staticClasses as flowClasses, getNode };