Merge pull request #3974 from xyflow/fix/ref
fix(react): ReactFlow comp ref type
This commit is contained in:
@@ -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<MyNode[]>([]);
|
||||
const onNodesChange: OnNodesChange<MyNode> = useCallback(
|
||||
(changes) =>
|
||||
@@ -165,6 +166,7 @@ const CustomNodeFlow = () => {
|
||||
minZoom={0.3}
|
||||
maxZoom={2}
|
||||
onBeforeDelete={onBeforeDelete}
|
||||
ref={ref}
|
||||
>
|
||||
<MiniMap<MyNode>
|
||||
nodeStrokeColor={(n: MyNode): string => {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @xyflow/react
|
||||
|
||||
## 12.0.0-next.11
|
||||
|
||||
## Patch changes
|
||||
|
||||
- fix `ref` prop for `ReactFlow` and `Handle` component
|
||||
|
||||
## 12.0.0-next.10
|
||||
|
||||
## ⚠️ Breaking changes
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
/*
|
||||
* The Handle component is used to connect nodes. When the user mousedowns a handle, we start the connection process.
|
||||
* 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,
|
||||
forwardRef,
|
||||
type HTMLAttributes,
|
||||
type MouseEvent as ReactMouseEvent,
|
||||
type TouchEvent as ReactTouchEvent,
|
||||
type ForwardedRef,
|
||||
} from 'react';
|
||||
import cc from 'classcat';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import {
|
||||
@@ -58,190 +60,186 @@ const connectingSelector =
|
||||
};
|
||||
};
|
||||
|
||||
const HandleComponent = forwardRef<HTMLDivElement, HandleComponentProps>(
|
||||
(
|
||||
{
|
||||
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<HTMLDivElement>
|
||||
) {
|
||||
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<HTMLDivElement> | ReactTouchEvent<HTMLDivElement>) => {
|
||||
if (!nodeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
onConnectAction?.(edgeParams);
|
||||
onConnect?.(edgeParams);
|
||||
};
|
||||
const isMouseTriggered = isMouseEvent(event.nativeEvent);
|
||||
|
||||
const onPointerDown = (event: ReactMouseEvent<HTMLDivElement> | ReactTouchEvent<HTMLDivElement>) => {
|
||||
if (!nodeId) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
isConnectableStart &&
|
||||
((isMouseTriggered && (event as ReactMouseEvent<HTMLDivElement>).button === 0) || !isMouseTriggered)
|
||||
) {
|
||||
const currentStore = store.getState();
|
||||
|
||||
const isMouseTriggered = isMouseEvent(event.nativeEvent);
|
||||
|
||||
if (
|
||||
isConnectableStart &&
|
||||
((isMouseTriggered && (event as ReactMouseEvent<HTMLDivElement>).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<HTMLDivElement>);
|
||||
} else {
|
||||
onTouchStart?.(event as ReactTouchEvent<HTMLDivElement>);
|
||||
}
|
||||
};
|
||||
|
||||
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<HTMLDivElement>);
|
||||
} else {
|
||||
onTouchStart?.(event as ReactTouchEvent<HTMLDivElement>);
|
||||
}
|
||||
};
|
||||
|
||||
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 (
|
||||
<div
|
||||
data-handleid={handleId}
|
||||
data-nodeid={nodeId}
|
||||
data-handlepos={position}
|
||||
data-id={`${rfId}-${nodeId}-${handleId}-${type}`}
|
||||
className={cc([
|
||||
'react-flow__handle',
|
||||
`react-flow__handle-${position}`,
|
||||
'nodrag',
|
||||
noPanClassName,
|
||||
className,
|
||||
{
|
||||
source: !isTarget,
|
||||
target: isTarget,
|
||||
connectable: isConnectable,
|
||||
connectablestart: isConnectableStart,
|
||||
connectableend: isConnectableEnd,
|
||||
clickconnecting: clickConnecting,
|
||||
connectingfrom: connectingFrom,
|
||||
connectingto: connectingTo,
|
||||
valid,
|
||||
// shows where you can start a connection from
|
||||
// and where you can end it while connecting
|
||||
connectionindicator:
|
||||
isConnectable &&
|
||||
(!connectionInProcess || isPossibleEndHandle) &&
|
||||
(connectionInProcess ? isConnectableEnd : isConnectableStart),
|
||||
},
|
||||
])}
|
||||
onMouseDown={onPointerDown}
|
||||
onTouchStart={onPointerDown}
|
||||
onClick={connectOnClick ? onClick : undefined}
|
||||
ref={ref}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
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 (
|
||||
<div
|
||||
data-handleid={handleId}
|
||||
data-nodeid={nodeId}
|
||||
data-handlepos={position}
|
||||
data-id={`${rfId}-${nodeId}-${handleId}-${type}`}
|
||||
className={cc([
|
||||
'react-flow__handle',
|
||||
`react-flow__handle-${position}`,
|
||||
'nodrag',
|
||||
noPanClassName,
|
||||
className,
|
||||
{
|
||||
source: !isTarget,
|
||||
target: isTarget,
|
||||
connectable: isConnectable,
|
||||
connectablestart: isConnectableStart,
|
||||
connectableend: isConnectableEnd,
|
||||
clickconnecting: clickConnecting,
|
||||
connectingfrom: connectingFrom,
|
||||
connectingto: connectingTo,
|
||||
valid,
|
||||
// shows where you can start a connection from
|
||||
// and where you can end it while connecting
|
||||
connectionindicator:
|
||||
isConnectable &&
|
||||
(!connectionInProcess || isPossibleEndHandle) &&
|
||||
(connectionInProcess ? isConnectableEnd : isConnectableStart),
|
||||
},
|
||||
])}
|
||||
onMouseDown={onPointerDown}
|
||||
onTouchStart={onPointerDown}
|
||||
onClick={connectOnClick ? onClick : undefined}
|
||||
ref={ref}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Handle component is the part of a node that can be used to connect nodes.
|
||||
* The Handle component is a UI element that is used to connect nodes.
|
||||
*/
|
||||
export const Handle = memo(HandleComponent);
|
||||
export const Handle = memo(forwardRef(HandleComponent));
|
||||
|
||||
@@ -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<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
||||
debug,
|
||||
...rest
|
||||
}: ReactFlowProps<NodeType, EdgeType>,
|
||||
ref: ForwardedRef<ReactFlowRefType>
|
||||
ref: ForwardedRef<HTMLDivElement>
|
||||
) {
|
||||
const rfId = id || '1';
|
||||
const colorModeClassName = useColorModeClass(colorMode);
|
||||
@@ -286,4 +286,4 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
||||
);
|
||||
}
|
||||
|
||||
export default forwardRef(ReactFlow) as typeof ReactFlow;
|
||||
export default forwardRef(ReactFlow);
|
||||
|
||||
@@ -509,5 +509,3 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
*/
|
||||
debug?: boolean;
|
||||
}
|
||||
|
||||
export type ReactFlowRefType = HTMLDivElement;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ComponentType } from 'react';
|
||||
import {
|
||||
FitViewParamsBase,
|
||||
FitViewOptionsBase,
|
||||
@@ -15,7 +16,13 @@ import {
|
||||
} from '@xyflow/system';
|
||||
|
||||
import type { NodeChange, EdgeChange, Node, Edge, ReactFlowInstance, EdgeProps } from '.';
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
// this is needed, to use generics + forwardRef
|
||||
declare module 'react' {
|
||||
function forwardRef<T, P>(
|
||||
render: (props: P, ref: React.Ref<T>) => React.ReactNode | null
|
||||
): (props: P & React.RefAttributes<T>) => React.ReactNode | null;
|
||||
}
|
||||
|
||||
export type OnNodesChange<NodeType extends Node = Node> = (changes: NodeChange<NodeType>[]) => void;
|
||||
export type OnEdgesChange<EdgeType extends Edge = Edge> = (changes: EdgeChange<EdgeType>[]) => void;
|
||||
|
||||
Reference in New Issue
Block a user