import { memo, useMemo, useEffect, useRef, type ComponentType } from 'react'; import { shallow } from 'zustand/shallow'; import { internalsSymbol, errorMessages, Position, clampPosition, getPositionWithOrigin } from '@xyflow/system'; import useVisibleNodes from '../../hooks/useVisibleNodes'; import { useStore } from '../../hooks/useStore'; import { containerStyle } from '../../styles/utils'; import { GraphViewProps } from '../GraphView'; import type { NodeTypesWrapped, ReactFlowState, WrapNodeProps } from '../../types'; type NodeRendererProps = Pick< GraphViewProps, | 'onNodeClick' | 'onNodeDoubleClick' | 'onNodeMouseEnter' | 'onNodeMouseMove' | 'onNodeMouseLeave' | 'onNodeContextMenu' | 'onlyRenderVisibleElements' | 'noPanClassName' | 'noDragClassName' | 'rfId' | 'disableKeyboardA11y' | 'nodeOrigin' | 'nodeExtent' > & { nodeTypes: NodeTypesWrapped; }; const selector = (s: ReactFlowState) => ({ nodesDraggable: s.nodesDraggable, nodesConnectable: s.nodesConnectable, nodesFocusable: s.nodesFocusable, elementsSelectable: s.elementsSelectable, updateNodeDimensions: s.updateNodeDimensions, onError: s.onError, }); const NodeRenderer = (props: NodeRendererProps) => { const { nodesDraggable, nodesConnectable, nodesFocusable, elementsSelectable, updateNodeDimensions, onError } = useStore(selector, shallow); const nodes = useVisibleNodes(props.onlyRenderVisibleElements); const resizeObserverRef = useRef(); const resizeObserver = useMemo(() => { if (typeof ResizeObserver === 'undefined') { return null; } const observer = new ResizeObserver((entries: ResizeObserverEntry[]) => { const updates = entries.map((entry: ResizeObserverEntry) => ({ id: entry.target.getAttribute('data-id') as string, nodeElement: entry.target as HTMLDivElement, forceUpdate: true, })); updateNodeDimensions(updates); }); resizeObserverRef.current = observer; return observer; }, []); useEffect(() => { return () => { resizeObserverRef?.current?.disconnect(); }; }, []); return (
{nodes.map((node) => { let nodeType = node.type || 'default'; if (!props.nodeTypes[nodeType]) { onError?.('003', errorMessages['error003'](nodeType)); nodeType = 'default'; } const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType; const isDraggable = !!(node.draggable || (nodesDraggable && typeof node.draggable === 'undefined')); const isSelectable = !!(node.selectable || (elementsSelectable && typeof node.selectable === 'undefined')); const isConnectable = !!(node.connectable || (nodesConnectable && typeof node.connectable === 'undefined')); const isFocusable = !!(node.focusable || (nodesFocusable && typeof node.focusable === 'undefined')); const clampedPosition = props.nodeExtent ? clampPosition(node.positionAbsolute, props.nodeExtent) : node.positionAbsolute; const posX = clampedPosition?.x ?? 0; const posY = clampedPosition?.y ?? 0; const posOrigin = getPositionWithOrigin({ x: posX, y: posY, width: node.width ?? 0, height: node.height ?? 0, origin: node.origin || props.nodeOrigin, }); const initialized = (!!node.width && !!node.height) || (!!node.dimensions?.width && !!node.dimensions?.height); return (
); }; NodeRenderer.displayName = 'NodeRenderer'; export default memo(NodeRenderer);