From e972656c3d02c62ba2f0bfbbd82f3cbef302609b Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 4 Mar 2024 12:01:26 +0100 Subject: [PATCH] fix(react): ReactFlow comp ref type --- .../react/src/examples/CustomNode/index.tsx | 4 +- .../react/src/components/Handle/index.tsx | 345 +++++++++--------- .../react/src/container/ReactFlow/index.tsx | 6 +- packages/react/src/types/component-props.ts | 2 - packages/react/src/types/general.ts | 9 +- 5 files changed, 188 insertions(+), 178 deletions(-) diff --git a/examples/react/src/examples/CustomNode/index.tsx b/examples/react/src/examples/CustomNode/index.tsx index 16a99803..21306745 100644 --- a/examples/react/src/examples/CustomNode/index.tsx +++ b/examples/react/src/examples/CustomNode/index.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, MouseEvent, ChangeEvent, useCallback } from 'react'; +import { useState, useEffect, MouseEvent, ChangeEvent, useCallback, useRef } from 'react'; import { ReactFlow, MiniMap, @@ -45,6 +45,7 @@ const nodeTypes = { }; const CustomNodeFlow = () => { + const ref = useRef(null); const [nodes, setNodes] = useState([]); const onNodesChange: OnNodesChange = useCallback( (changes) => @@ -165,6 +166,7 @@ const CustomNodeFlow = () => { minZoom={0.3} maxZoom={2} onBeforeDelete={onBeforeDelete} + ref={ref} > nodeStrokeColor={(n: MyNode): string => { diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx index 75c36b8c..6a72b8e4 100644 --- a/packages/react/src/components/Handle/index.tsx +++ b/packages/react/src/components/Handle/index.tsx @@ -3,7 +3,14 @@ * The user can then drag the connection to another handle or node. When the user releases the mouse, we check if the * connection is valid and if so, we call the onConnect callback. */ -import { memo, HTMLAttributes, forwardRef, MouseEvent as ReactMouseEvent, TouchEvent as ReactTouchEvent } from 'react'; +import { + memo, + HTMLAttributes, + forwardRef, + MouseEvent as ReactMouseEvent, + TouchEvent as ReactTouchEvent, + ForwardedRef, +} from 'react'; import cc from 'classcat'; import { shallow } from 'zustand/shallow'; import { @@ -58,190 +65,186 @@ const connectingSelector = }; }; -const HandleComponent = forwardRef( - ( - { - type = 'source', - position = Position.Top, - isValidConnection, - isConnectable = true, - isConnectableStart = true, - isConnectableEnd = true, - id, - onConnect, - children, - className, - onMouseDown, - onTouchStart, - ...rest - }, - ref - ) => { - const handleId = id || null; - const isTarget = type === 'target'; - const store = useStoreApi(); - const nodeId = useNodeId(); - const { connectOnClick, noPanClassName, rfId } = useStore(selector, shallow); - const { connectingFrom, connectingTo, clickConnecting, isPossibleEndHandle, connectionInProcess, valid } = useStore( - connectingSelector(nodeId, handleId, type), - shallow - ); +function HandleComponent( + { + type = 'source', + position = Position.Top, + isValidConnection, + isConnectable = true, + isConnectableStart = true, + isConnectableEnd = true, + id, + onConnect, + children, + className, + onMouseDown, + onTouchStart, + ...rest + }: HandleComponentProps, + ref: ForwardedRef +) { + const handleId = id || null; + const isTarget = type === 'target'; + const store = useStoreApi(); + const nodeId = useNodeId(); + const { connectOnClick, noPanClassName, rfId } = useStore(selector, shallow); + const { connectingFrom, connectingTo, clickConnecting, isPossibleEndHandle, connectionInProcess, valid } = useStore( + connectingSelector(nodeId, handleId, type), + shallow + ); - if (!nodeId) { - store.getState().onError?.('010', errorMessages['error010']()); + if (!nodeId) { + store.getState().onError?.('010', errorMessages['error010']()); + } + + const onConnectExtended = (params: Connection) => { + const { defaultEdgeOptions, onConnect: onConnectAction, hasDefaultEdges } = store.getState(); + + const edgeParams = { + ...defaultEdgeOptions, + ...params, + }; + if (hasDefaultEdges) { + const { edges, setEdges } = store.getState(); + setEdges(addEdge(edgeParams, edges)); } - const onConnectExtended = (params: Connection) => { - const { defaultEdgeOptions, onConnect: onConnectAction, hasDefaultEdges } = store.getState(); + onConnectAction?.(edgeParams); + onConnect?.(edgeParams); + }; - const edgeParams = { - ...defaultEdgeOptions, - ...params, - }; - if (hasDefaultEdges) { - const { edges, setEdges } = store.getState(); - setEdges(addEdge(edgeParams, edges)); - } + const onPointerDown = (event: ReactMouseEvent | ReactTouchEvent) => { + if (!nodeId) { + return; + } - onConnectAction?.(edgeParams); - onConnect?.(edgeParams); - }; + const isMouseTriggered = isMouseEvent(event.nativeEvent); - const onPointerDown = (event: ReactMouseEvent | ReactTouchEvent) => { - if (!nodeId) { - return; - } + if ( + isConnectableStart && + ((isMouseTriggered && (event as ReactMouseEvent).button === 0) || !isMouseTriggered) + ) { + const currentStore = store.getState(); - const isMouseTriggered = isMouseEvent(event.nativeEvent); - - if ( - isConnectableStart && - ((isMouseTriggered && (event as ReactMouseEvent).button === 0) || !isMouseTriggered) - ) { - const currentStore = store.getState(); - - XYHandle.onPointerDown(event.nativeEvent, { - autoPanOnConnect: currentStore.autoPanOnConnect, - connectionMode: currentStore.connectionMode, - connectionRadius: currentStore.connectionRadius, - domNode: currentStore.domNode, - nodes: currentStore.nodes, - lib: currentStore.lib, - isTarget, - handleId, - nodeId, - flowId: currentStore.rfId, - panBy: currentStore.panBy, - cancelConnection: currentStore.cancelConnection, - onConnectStart: currentStore.onConnectStart, - onConnectEnd: currentStore.onConnectEnd, - updateConnection: currentStore.updateConnection, - onConnect: onConnectExtended, - isValidConnection: isValidConnection || currentStore.isValidConnection, - getTransform: () => store.getState().transform, - }); - } - - if (isMouseTriggered) { - onMouseDown?.(event as ReactMouseEvent); - } else { - onTouchStart?.(event as ReactTouchEvent); - } - }; - - const onClick = (event: ReactMouseEvent) => { - const { - onClickConnectStart, - onClickConnectEnd, - connectionClickStartHandle, - connectionMode, - isValidConnection: isValidConnectionStore, - lib, - rfId: flowId, - } = store.getState(); - - if (!nodeId || (!connectionClickStartHandle && !isConnectableStart)) { - return; - } - - if (!connectionClickStartHandle) { - onClickConnectStart?.(event.nativeEvent, { nodeId, handleId, handleType: type }); - store.setState({ connectionClickStartHandle: { nodeId, type, handleId } }); - return; - } - - const doc = getHostForElement(event.target as HTMLElement); - const isValidConnectionHandler = isValidConnection || isValidConnectionStore; - const { connection, isValid } = XYHandle.isValid(event.nativeEvent, { - handle: { - nodeId, - id: handleId, - type, - }, - connectionMode, - fromNodeId: connectionClickStartHandle.nodeId, - fromHandleId: connectionClickStartHandle.handleId || null, - fromType: connectionClickStartHandle.type, - isValidConnection: isValidConnectionHandler, - flowId, - doc, - lib, + XYHandle.onPointerDown(event.nativeEvent, { + autoPanOnConnect: currentStore.autoPanOnConnect, + connectionMode: currentStore.connectionMode, + connectionRadius: currentStore.connectionRadius, + domNode: currentStore.domNode, + nodes: currentStore.nodes, + lib: currentStore.lib, + isTarget, + handleId, + nodeId, + flowId: currentStore.rfId, + panBy: currentStore.panBy, + cancelConnection: currentStore.cancelConnection, + onConnectStart: currentStore.onConnectStart, + onConnectEnd: currentStore.onConnectEnd, + updateConnection: currentStore.updateConnection, + onConnect: onConnectExtended, + isValidConnection: isValidConnection || currentStore.isValidConnection, + getTransform: () => store.getState().transform, }); + } - if (isValid && connection) { - onConnectExtended(connection); - } + if (isMouseTriggered) { + onMouseDown?.(event as ReactMouseEvent); + } else { + onTouchStart?.(event as ReactTouchEvent); + } + }; - onClickConnectEnd?.(event as unknown as MouseEvent); + const onClick = (event: ReactMouseEvent) => { + const { + onClickConnectStart, + onClickConnectEnd, + connectionClickStartHandle, + connectionMode, + isValidConnection: isValidConnectionStore, + lib, + rfId: flowId, + } = store.getState(); - store.setState({ connectionClickStartHandle: null }); - }; + if (!nodeId || (!connectionClickStartHandle && !isConnectableStart)) { + return; + } - return ( -
- {children} -
- ); - } -); + if (!connectionClickStartHandle) { + onClickConnectStart?.(event.nativeEvent, { nodeId, handleId, handleType: type }); + store.setState({ connectionClickStartHandle: { nodeId, type, handleId } }); + return; + } -HandleComponent.displayName = 'Handle'; + const doc = getHostForElement(event.target as HTMLElement); + const isValidConnectionHandler = isValidConnection || isValidConnectionStore; + const { connection, isValid } = XYHandle.isValid(event.nativeEvent, { + handle: { + nodeId, + id: handleId, + type, + }, + connectionMode, + fromNodeId: connectionClickStartHandle.nodeId, + fromHandleId: connectionClickStartHandle.handleId || null, + fromType: connectionClickStartHandle.type, + isValidConnection: isValidConnectionHandler, + flowId, + doc, + lib, + }); + + if (isValid && connection) { + onConnectExtended(connection); + } + + onClickConnectEnd?.(event as unknown as MouseEvent); + + store.setState({ connectionClickStartHandle: null }); + }; + + return ( +
+ {children} +
+ ); +} /** * The Handle component is the part of a node that can be used to connect nodes. */ -export const Handle = memo(HandleComponent); +export const Handle = memo(forwardRef(HandleComponent)); diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx index 88d8843e..eb741684 100644 --- a/packages/react/src/container/ReactFlow/index.tsx +++ b/packages/react/src/container/ReactFlow/index.tsx @@ -9,7 +9,7 @@ import { StoreUpdater } from '../../components/StoreUpdater'; import { useColorModeClass } from '../../hooks/useColorModeClass'; import { GraphView } from '../GraphView'; import { Wrapper } from './Wrapper'; -import type { Edge, Node, ReactFlowProps, ReactFlowRefType } from '../../types'; +import type { Edge, Node, ReactFlowProps } from '../../types'; import { defaultViewport as initViewport, defaultNodeOrigin } from './init-values'; const wrapperStyle: CSSProperties = { @@ -142,7 +142,7 @@ function ReactFlow( debug, ...rest }: ReactFlowProps, - ref: ForwardedRef + ref: ForwardedRef ) { const rfId = id || '1'; const colorModeClassName = useColorModeClass(colorMode); @@ -286,4 +286,4 @@ function ReactFlow( ); } -export default forwardRef(ReactFlow) as typeof ReactFlow; +export default forwardRef(ReactFlow); diff --git a/packages/react/src/types/component-props.ts b/packages/react/src/types/component-props.ts index e8d91ba4..6deca270 100644 --- a/packages/react/src/types/component-props.ts +++ b/packages/react/src/types/component-props.ts @@ -509,5 +509,3 @@ export interface ReactFlowProps( + render: (props: P, ref: React.Ref) => React.ReactNode | null + ): (props: P & React.RefAttributes) => React.ReactNode | null; +} export type OnNodesChange = (changes: NodeChange[]) => void; export type OnEdgesChange = (changes: EdgeChange[]) => void;