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:
@@ -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;
|
||||
|
||||
@@ -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']());
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user