refactor(handles): reduce re-renderings, handles on top of each other #3010
This commit is contained in:
@@ -2,8 +2,9 @@ import { Handle, NodeProps, Position, ReactFlowState, useStore } from 'reactflow
|
|||||||
|
|
||||||
const connectionNodeIdSelector = (state: ReactFlowState) => state.connectionNodeId;
|
const connectionNodeIdSelector = (state: ReactFlowState) => state.connectionNodeId;
|
||||||
|
|
||||||
export default function CustomNode({ id, isConnectable }: NodeProps) {
|
export default function CustomNode({ id }: NodeProps) {
|
||||||
const connectionNodeId = useStore(connectionNodeIdSelector);
|
const connectionNodeId = useStore(connectionNodeIdSelector);
|
||||||
|
const isConnecting = !!connectionNodeId;
|
||||||
const isTarget = connectionNodeId && connectionNodeId !== id;
|
const isTarget = connectionNodeId && connectionNodeId !== id;
|
||||||
|
|
||||||
const targetHandleStyle = { zIndex: isTarget ? 3 : 1 };
|
const targetHandleStyle = { zIndex: isTarget ? 3 : 1 };
|
||||||
@@ -18,19 +19,16 @@ export default function CustomNode({ id, isConnectable }: NodeProps) {
|
|||||||
backgroundColor: isTarget ? '#ffcce3' : '#ccd9f6',
|
backgroundColor: isTarget ? '#ffcce3' : '#ccd9f6',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{!isConnecting && (
|
||||||
|
<Handle className="customHandle" style={{ zIndex: 2 }} position={Position.Right} type="source" />
|
||||||
|
)}
|
||||||
|
|
||||||
<Handle
|
<Handle
|
||||||
className="targetHandle"
|
className="customHandle"
|
||||||
style={{ zIndex: 2 }}
|
|
||||||
position={Position.Right}
|
|
||||||
type="source"
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
/>
|
|
||||||
<Handle
|
|
||||||
className="targetHandle"
|
|
||||||
style={targetHandleStyle}
|
style={targetHandleStyle}
|
||||||
position={Position.Left}
|
position={Position.Left}
|
||||||
type="target"
|
type="target"
|
||||||
isConnectable={isConnectable}
|
isConnectableStart={false}
|
||||||
/>
|
/>
|
||||||
{label}
|
{label}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,19 +28,7 @@
|
|||||||
border: 2px solid #222138;
|
border: 2px solid #222138;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.sourceHandle {
|
div.customHandle {
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
transform: none;
|
|
||||||
border: none;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.targetHandle {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: blue;
|
background: blue;
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export function handlePointerDown({
|
|||||||
cancelConnection,
|
cancelConnection,
|
||||||
} = getState();
|
} = getState();
|
||||||
let autoPanId = 0;
|
let autoPanId = 0;
|
||||||
let prevClosestHandle: ConnectionHandle | null;
|
let closestHandle: ConnectionHandle | null;
|
||||||
|
|
||||||
const { x, y } = getEventPosition(event);
|
const { x, y } = getEventPosition(event);
|
||||||
const clickedHandle = doc?.elementFromPoint(x, y);
|
const clickedHandle = doc?.elementFromPoint(x, y);
|
||||||
@@ -106,9 +106,9 @@ export function handlePointerDown({
|
|||||||
|
|
||||||
function onPointerMove(event: MouseEvent | TouchEvent) {
|
function onPointerMove(event: MouseEvent | TouchEvent) {
|
||||||
const { transform } = getState();
|
const { transform } = getState();
|
||||||
connectionPosition = getEventPosition(event, containerBounds);
|
|
||||||
|
|
||||||
prevClosestHandle = getClosestHandle(
|
connectionPosition = getEventPosition(event, containerBounds);
|
||||||
|
closestHandle = getClosestHandle(
|
||||||
pointToRendererPoint(connectionPosition, transform, false, [1, 1]),
|
pointToRendererPoint(connectionPosition, transform, false, [1, 1]),
|
||||||
connectionRadius,
|
connectionRadius,
|
||||||
handleLookup
|
handleLookup
|
||||||
@@ -121,7 +121,7 @@ export function handlePointerDown({
|
|||||||
|
|
||||||
const result = isValidHandle(
|
const result = isValidHandle(
|
||||||
event,
|
event,
|
||||||
prevClosestHandle,
|
closestHandle,
|
||||||
connectionMode,
|
connectionMode,
|
||||||
nodeId,
|
nodeId,
|
||||||
handleId,
|
handleId,
|
||||||
@@ -136,20 +136,20 @@ export function handlePointerDown({
|
|||||||
|
|
||||||
setState({
|
setState({
|
||||||
connectionPosition:
|
connectionPosition:
|
||||||
prevClosestHandle && isValid
|
closestHandle && isValid
|
||||||
? rendererPointToPoint(
|
? rendererPointToPoint(
|
||||||
{
|
{
|
||||||
x: prevClosestHandle.x,
|
x: closestHandle.x,
|
||||||
y: prevClosestHandle.y,
|
y: closestHandle.y,
|
||||||
},
|
},
|
||||||
transform
|
transform
|
||||||
)
|
)
|
||||||
: connectionPosition,
|
: connectionPosition,
|
||||||
connectionStatus: getConnectionStatus(!!prevClosestHandle, isValid),
|
connectionStatus: getConnectionStatus(!!closestHandle, isValid),
|
||||||
connectionEndHandle: result.endHandle,
|
connectionEndHandle: result.endHandle,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!prevClosestHandle && !isValid && !handleDomNode) {
|
if (!closestHandle && !isValid && !handleDomNode) {
|
||||||
return resetRecentHandle(prevActiveHandle);
|
return resetRecentHandle(prevActiveHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ export function handlePointerDown({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onPointerUp(event: MouseEvent | TouchEvent) {
|
function onPointerUp(event: MouseEvent | TouchEvent) {
|
||||||
if ((prevClosestHandle || handleDomNode) && connection && isValid) {
|
if ((closestHandle || handleDomNode) && connection && isValid) {
|
||||||
onConnect?.(connection);
|
onConnect?.(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const nodeId = useNodeId();
|
const nodeId = useNodeId();
|
||||||
const { connectOnClick, noPanClassName } = useStore(selector, shallow);
|
const { connectOnClick, noPanClassName } = useStore(selector, shallow);
|
||||||
const { connecting, clickConnecting } = useStore(connectingSelector(nodeId, handleId, type));
|
const { connecting, clickConnecting } = useStore(connectingSelector(nodeId, handleId, type), shallow);
|
||||||
|
|
||||||
if (!nodeId) {
|
if (!nodeId) {
|
||||||
store.getState().onError?.('010', errorMessages['error010']());
|
store.getState().onError?.('010', errorMessages['error010']());
|
||||||
|
|||||||
@@ -41,18 +41,30 @@ export function getClosestHandle(
|
|||||||
connectionRadius: number,
|
connectionRadius: number,
|
||||||
handles: ConnectionHandle[]
|
handles: ConnectionHandle[]
|
||||||
): ConnectionHandle | null {
|
): ConnectionHandle | null {
|
||||||
let closestHandle: ConnectionHandle | null = null;
|
let closestHandles: ConnectionHandle[] = [];
|
||||||
let minDistance = Infinity;
|
let minDistance = Infinity;
|
||||||
|
|
||||||
handles.forEach((handle) => {
|
handles.forEach((handle) => {
|
||||||
const distance = Math.sqrt(Math.pow(handle.x - pos.x, 2) + Math.pow(handle.y - pos.y, 2));
|
const distance = Math.sqrt(Math.pow(handle.x - pos.x, 2) + Math.pow(handle.y - pos.y, 2));
|
||||||
if (distance <= connectionRadius && distance < minDistance) {
|
if (distance <= connectionRadius) {
|
||||||
|
if (distance < minDistance) {
|
||||||
|
closestHandles = [handle];
|
||||||
|
} else if (distance === minDistance) {
|
||||||
|
// when multiple handles are on the same distance we collect all of them
|
||||||
|
closestHandles.push(handle);
|
||||||
|
}
|
||||||
minDistance = distance;
|
minDistance = distance;
|
||||||
closestHandle = handle;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return closestHandle;
|
if (!closestHandles.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return closestHandles.length === 1
|
||||||
|
? closestHandles[0]
|
||||||
|
: // if multiple handles are layouted on top of each other we take the one with type = target because it's more likely that the user wants to connect to this one
|
||||||
|
closestHandles.find((handle) => handle.type === 'target') || closestHandles[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result = {
|
type Result = {
|
||||||
@@ -81,6 +93,8 @@ export function isValidHandle(
|
|||||||
);
|
);
|
||||||
const { x, y } = getEventPosition(event);
|
const { x, y } = getEventPosition(event);
|
||||||
const handleBelow = doc.elementFromPoint(x, y);
|
const handleBelow = doc.elementFromPoint(x, y);
|
||||||
|
// we always want to prioritize the handle below the mouse cursor over the closest distance handle,
|
||||||
|
// because it could be that the center of another handle is closer to the mouse pointer than the handle below the cursor
|
||||||
const handleToCheck = handleBelow?.classList.contains('react-flow__handle') ? handleBelow : handleDomNode;
|
const handleToCheck = handleBelow?.classList.contains('react-flow__handle') ? handleBelow : handleDomNode;
|
||||||
|
|
||||||
const result: Result = {
|
const result: Result = {
|
||||||
|
|||||||
Reference in New Issue
Block a user