import React, { useEffect, useRef, memo, ComponentType, CSSProperties, useMemo, MouseEvent, useCallback } from 'react'; import { DraggableCore } from 'react-draggable'; import cc from 'classcat'; import { useStoreActions } from '../../store/hooks'; import { Provider } from '../../contexts/NodeIdContext'; import { Node, NodeComponentProps, WrapNodeProps } from '../../types'; export default (NodeComponent: ComponentType) => { const NodeWrapper = ({ id, type, data, scale, xPos, yPos, selected, onClick, onMouseEnter, onMouseMove, onMouseLeave, onContextMenu, onNodeDragStart, onNodeDragStop, style, className, isDraggable, isSelectable, isConnectable, selectNodesOnDrag, sourcePosition, targetPosition, isHidden, isInitialized, snapToGrid, snapGrid, isDragging, resizeObserver, }: WrapNodeProps) => { const updateNodeDimensions = useStoreActions((actions) => actions.updateNodeDimensions); const addSelectedElements = useStoreActions((actions) => actions.addSelectedElements); const updateNodePosDiff = useStoreActions((actions) => actions.updateNodePosDiff); const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection); const nodeElement = useRef(null); const node = useMemo(() => ({ id, type, position: { x: xPos, y: yPos }, data }), [id, type, xPos, yPos, data]); const grid = useMemo(() => (snapToGrid ? snapGrid : [1, 1])! as [number, number], [snapToGrid, snapGrid]); const nodeStyle: CSSProperties = useMemo( () => ({ zIndex: selected ? 10 : 3, transform: `translate(${xPos}px,${yPos}px)`, pointerEvents: isSelectable || isDraggable || onClick ? 'all' : 'none', opacity: isInitialized ? 1 : 0, // prevents jumping of nodes on start ...style, }), [selected, xPos, yPos, isSelectable, isDraggable, onClick, isInitialized, style] ); const onMouseEnterHandler = useMemo(() => { if (!onMouseEnter || isDragging) { return; } return (event: MouseEvent) => onMouseEnter(event, node); }, [onMouseEnter, isDragging, node]); const onMouseMoveHandler = useMemo(() => { if (!onMouseMove || isDragging) { return; } return (event: MouseEvent) => onMouseMove(event, node); }, [onMouseMove, isDragging, node]); const onMouseLeaveHandler = useMemo(() => { if (!onMouseLeave || isDragging) { return; } return (event: MouseEvent) => onMouseLeave(event, node); }, [onMouseLeave, isDragging, node]); const onContextMenuHandler = useMemo(() => { if (!onContextMenu) { return; } return (event: MouseEvent) => onContextMenu(event, node); }, [onContextMenu, node]); const onSelectNodeHandler = useCallback( (event: MouseEvent) => { if (!isDraggable) { if (isSelectable) { unsetNodesSelection(); if (!selected) { addSelectedElements({ id: node.id, type: node.type } as Node); } } onClick?.(event, node); } }, [isSelectable, selected, isDraggable, onClick, node] ); const onDragStart = useCallback( (event) => { onNodeDragStart?.(event as MouseEvent, node); if (selectNodesOnDrag && isSelectable) { unsetNodesSelection(); if (!selected) { addSelectedElements({ id: node.id, type: node.type } as Node); } } }, [node, selected, selectNodesOnDrag, isSelectable, onNodeDragStart] ); const onDrag = useCallback( (_, data) => { updateNodePosDiff({ id, diff: { x: data.deltaX, y: data.deltaY, }, }); }, [id] ); const onDragStop = useCallback( (event) => { // onDragStop also gets called when user just clicks on a node. // Because of that we set dragging to true inside the onDrag handler and handle the click here if (!isDragging) { if (isSelectable && !selectNodesOnDrag && !selected) { addSelectedElements({ id: node.id, type: node.type } as Node); } onClick?.(event as MouseEvent, node); return; } updateNodePosDiff({ id: node.id, isDragging: false, }); onNodeDragStop?.(event as MouseEvent, node); }, [node, isSelectable, selectNodesOnDrag, onClick, onNodeDragStop, isDragging, selected] ); useEffect(() => { if (nodeElement.current && !isHidden) { updateNodeDimensions({ id, nodeElement: nodeElement.current }); } }, [id, isHidden]); useEffect(() => { if (nodeElement.current) { const currNode = nodeElement.current; resizeObserver.observe(currNode); return () => resizeObserver.unobserve(currNode); } return; }, []); if (isHidden) { return null; } const nodeClasses = cc([ 'react-flow__node', `react-flow__node-${type}`, className, { selected, selectable: isSelectable, }, ]); return (
); }; NodeWrapper.displayName = 'NodeWrapper'; return memo(NodeWrapper); };