@hitachivantara/uikit-react-lab
Version:
Contributed React components for the NEXT UI Kit.
206 lines (205 loc) • 6.41 kB
JavaScript
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
};