Merge pull request #3043 from wbkd/fix/handle-connectable-start

refactor(handles): reduce re-renderings, handles on top of each other
This commit is contained in:
Moritz Klack
2023-05-09 17:09:43 +02:00
committed by GitHub
6 changed files with 43 additions and 38 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@reactflow/core': patch
---
handles: handles on top of each other, reduce re-renderings
@@ -2,8 +2,9 @@ import { Handle, NodeProps, Position, ReactFlowState, useStore } from 'reactflow
const connectionNodeIdSelector = (state: ReactFlowState) => state.connectionNodeId;
export default function CustomNode({ id, isConnectable }: NodeProps) {
export default function CustomNode({ id }: NodeProps) {
const connectionNodeId = useStore(connectionNodeIdSelector);
const isConnecting = !!connectionNodeId;
const isTarget = connectionNodeId && connectionNodeId !== id;
const targetHandleStyle = { zIndex: isTarget ? 3 : 1 };
@@ -18,19 +19,16 @@ export default function CustomNode({ id, isConnectable }: NodeProps) {
backgroundColor: isTarget ? '#ffcce3' : '#ccd9f6',
}}
>
{!isConnecting && (
<Handle className="customHandle" style={{ zIndex: 2 }} position={Position.Right} type="source" />
)}
<Handle
className="targetHandle"
style={{ zIndex: 2 }}
position={Position.Right}
type="source"
isConnectable={isConnectable}
/>
<Handle
className="targetHandle"
className="customHandle"
style={targetHandleStyle}
position={Position.Left}
type="target"
isConnectable={isConnectable}
isConnectableStart={false}
/>
{label}
</div>
@@ -28,19 +28,7 @@
border: 2px solid #222138;
}
div.sourceHandle {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
border-radius: 0;
transform: none;
border: none;
opacity: 0;
}
div.targetHandle {
div.customHandle {
width: 100%;
height: 100%;
background: blue;
+10 -10
View File
@@ -51,7 +51,7 @@ export function handlePointerDown({
cancelConnection,
} = getState();
let autoPanId = 0;
let prevClosestHandle: ConnectionHandle | null;
let closestHandle: ConnectionHandle | null;
const { x, y } = getEventPosition(event);
const clickedHandle = doc?.elementFromPoint(x, y);
@@ -106,9 +106,9 @@ export function handlePointerDown({
function onPointerMove(event: MouseEvent | TouchEvent) {
const { transform } = getState();
connectionPosition = getEventPosition(event, containerBounds);
prevClosestHandle = getClosestHandle(
connectionPosition = getEventPosition(event, containerBounds);
closestHandle = getClosestHandle(
pointToRendererPoint(connectionPosition, transform, false, [1, 1]),
connectionRadius,
handleLookup
@@ -121,7 +121,7 @@ export function handlePointerDown({
const result = isValidHandle(
event,
prevClosestHandle,
closestHandle,
connectionMode,
nodeId,
handleId,
@@ -136,20 +136,20 @@ export function handlePointerDown({
setState({
connectionPosition:
prevClosestHandle && isValid
closestHandle && isValid
? rendererPointToPoint(
{
x: prevClosestHandle.x,
y: prevClosestHandle.y,
x: closestHandle.x,
y: closestHandle.y,
},
transform
)
: connectionPosition,
connectionStatus: getConnectionStatus(!!prevClosestHandle, isValid),
connectionStatus: getConnectionStatus(!!closestHandle, isValid),
connectionEndHandle: result.endHandle,
});
if (!prevClosestHandle && !isValid && !handleDomNode) {
if (!closestHandle && !isValid && !handleDomNode) {
return resetRecentHandle(prevActiveHandle);
}
@@ -164,7 +164,7 @@ export function handlePointerDown({
}
function onPointerUp(event: MouseEvent | TouchEvent) {
if ((prevClosestHandle || handleDomNode) && connection && isValid) {
if ((closestHandle || handleDomNode) && connection && isValid) {
onConnect?.(connection);
}
@@ -62,7 +62,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
const store = useStoreApi();
const nodeId = useNodeId();
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) {
store.getState().onError?.('010', errorMessages['error010']());
+18 -4
View File
@@ -41,18 +41,30 @@ export function getClosestHandle(
connectionRadius: number,
handles: ConnectionHandle[]
): ConnectionHandle | null {
let closestHandle: ConnectionHandle | null = null;
let closestHandles: ConnectionHandle[] = [];
let minDistance = Infinity;
handles.forEach((handle) => {
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;
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 = {
@@ -81,6 +93,8 @@ export function isValidHandle(
);
const { x, y } = getEventPosition(event);
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 result: Result = {