From 83e0a333b04df8f7272f0f0269363b7bf24fe2ce Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 27 Feb 2024 16:54:21 +0100 Subject: [PATCH 1/7] fixed handle connections --- .../react/src/components/Handle/index.tsx | 19 +++++++++++++++---- packages/react/src/store/index.ts | 6 ++---- packages/system/src/styles/init.css | 5 +++++ packages/system/src/xyhandle/utils.ts | 4 ++-- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx index 36778039..25794ee6 100644 --- a/packages/react/src/components/Handle/index.tsx +++ b/packages/react/src/components/Handle/index.tsx @@ -16,6 +16,7 @@ import { type HandleProps, type Connection, type HandleType, + ConnectionMode, } from '@xyflow/system'; import { useStore, useStoreApi } from '../../hooks/useStore'; @@ -36,14 +37,19 @@ const connectingSelector = connectionStartHandle: startHandle, connectionEndHandle: endHandle, connectionClickStartHandle: clickHandle, + connectionMode, } = state; - return { connecting: (startHandle?.nodeId === nodeId && startHandle?.handleId === handleId && startHandle?.type === type) || (endHandle?.nodeId === nodeId && endHandle?.handleId === handleId && endHandle?.type === type), clickConnecting: clickHandle?.nodeId === nodeId && clickHandle?.handleId === handleId && clickHandle?.type === type, + isPossibleEndHandle: + connectionMode === ConnectionMode.Strict + ? startHandle?.type !== type + : nodeId !== startHandle?.nodeId || handleId !== startHandle?.handleId, + connectionInProcess: !!startHandle, }; }; @@ -71,7 +77,10 @@ const HandleComponent = forwardRef( const store = useStoreApi(); const nodeId = useNodeId(); const { connectOnClick, noPanClassName, rfId } = useStore(selector, shallow); - const { connecting, clickConnecting } = useStore(connectingSelector(nodeId, handleId, type), shallow); + const { connecting, clickConnecting, isPossibleEndHandle, connectionInProcess } = useStore( + connectingSelector(nodeId, handleId, type), + shallow + ); if (!nodeId) { store.getState().onError?.('010', errorMessages['error010']()); @@ -201,10 +210,12 @@ const HandleComponent = forwardRef( connectable: isConnectable, connectablestart: isConnectableStart, connectableend: isConnectableEnd, - connecting: clickConnecting, + connecting: connecting || clickConnecting, // this class is used to style the handle when the user is connecting connectionindicator: - isConnectable && ((isConnectableStart && !connecting) || (isConnectableEnd && connecting)), + isConnectable && + (!connectionInProcess || isPossibleEndHandle) && + (connectionInProcess ? isConnectableEnd : isConnectableStart), }, ])} onMouseDown={onPointerDown} diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index 71a51a1e..cae14173 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -291,13 +291,11 @@ const createRFStore = ({ connectionEndHandle: null, }), updateConnection: (params) => { - const { connectionStatus, connectionStartHandle, connectionEndHandle, connectionPosition } = get(); + const { connectionPosition } = get(); const currentConnection = { + ...params, connectionPosition: params.connectionPosition ?? connectionPosition, - connectionStatus: params.connectionStatus ?? connectionStatus, - connectionStartHandle: params.connectionStartHandle ?? connectionStartHandle, - connectionEndHandle: params.connectionEndHandle ?? connectionEndHandle, }; set(currentConnection); diff --git a/packages/system/src/styles/init.css b/packages/system/src/styles/init.css index 46628842..d74519a6 100644 --- a/packages/system/src/styles/init.css +++ b/packages/system/src/styles/init.css @@ -223,9 +223,14 @@ svg.xy-flow__connectionline { min-width: 5px; min-height: 5px; + &.connecting { + pointer-events: all; + } + &.connectionindicator { pointer-events: all; cursor: crosshair; + background: green; } &-bottom { diff --git a/packages/system/src/xyhandle/utils.ts b/packages/system/src/xyhandle/utils.ts index b829f691..c10356b1 100644 --- a/packages/system/src/xyhandle/utils.ts +++ b/packages/system/src/xyhandle/utils.ts @@ -38,7 +38,7 @@ export function getClosestHandle( let closestHandles: ConnectionHandle[] = []; let minDistance = Infinity; - handles.forEach((handle) => { + for (const handle of handles) { const distance = Math.sqrt(Math.pow(handle.x - pos.x, 2) + Math.pow(handle.y - pos.y, 2)); if (distance <= connectionRadius) { if (distance < minDistance) { @@ -49,7 +49,7 @@ export function getClosestHandle( } minDistance = distance; } - }); + } if (!closestHandles.length) { return null; From d9e7f1f7281e04fbf7721b05fcda9b98220cf608 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 27 Feb 2024 17:13:25 +0100 Subject: [PATCH 2/7] remove debug color --- packages/system/src/styles/init.css | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/system/src/styles/init.css b/packages/system/src/styles/init.css index d74519a6..04ee6c98 100644 --- a/packages/system/src/styles/init.css +++ b/packages/system/src/styles/init.css @@ -230,7 +230,6 @@ svg.xy-flow__connectionline { &.connectionindicator { pointer-events: all; cursor: crosshair; - background: green; } &-bottom { From 019909da5d4a7166cf14b7a9b1059a745345f508 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Wed, 28 Feb 2024 12:23:00 +0100 Subject: [PATCH 3/7] added connectionfrom connectionto and clickconnecting css --- .../react/src/examples/TouchDevice/index.tsx | 3 +-- .../src/examples/TouchDevice/touch-device.css | 27 +++++++++++++++++++ .../TouchDevice/touch-device.module.css | 19 ------------- .../react/src/components/Handle/index.tsx | 17 +++++++----- packages/system/src/styles/init.css | 12 ++++++++- 5 files changed, 50 insertions(+), 28 deletions(-) create mode 100644 examples/react/src/examples/TouchDevice/touch-device.css delete mode 100644 examples/react/src/examples/TouchDevice/touch-device.module.css diff --git a/examples/react/src/examples/TouchDevice/index.tsx b/examples/react/src/examples/TouchDevice/index.tsx index 636a36a4..2073d13a 100644 --- a/examples/react/src/examples/TouchDevice/index.tsx +++ b/examples/react/src/examples/TouchDevice/index.tsx @@ -1,7 +1,7 @@ import { useCallback } from 'react'; import { ReactFlow, Node, Edge, useNodesState, useEdgesState, Position, Connection, addEdge } from '@xyflow/react'; -import styles from './touch-device.module.css'; +import './touch-device.css'; const initialNodes: Node[] = [ { @@ -42,7 +42,6 @@ const TouchDeviceFlow = () => { onConnectEnd={onConnectEnd} onClickConnectStart={onClickConnectStart} onClickConnectEnd={onClickConnectEnd} - className={styles.flow} /> ); }; diff --git a/examples/react/src/examples/TouchDevice/touch-device.css b/examples/react/src/examples/TouchDevice/touch-device.css new file mode 100644 index 00000000..3ec36be2 --- /dev/null +++ b/examples/react/src/examples/TouchDevice/touch-device.css @@ -0,0 +1,27 @@ +.react-flow .react-flow__handle { + width: 20px; + height: 20px; + border-radius: 3px; + background-color: #9f7aea; +} + +.react-flow__handle-right { + --translate: translate(50%, -50%); +} + +.react-flow__handle-left { + --translate: translate(-50%, -50%); +} + +@keyframes bounce { + 0% { + transform: var(--translate) scale(1); + } + 50% { + transform: var(--translate) scale(1.1); + } +} + +.react-flow .react-flow__handle.clickconnecting { + animation: bounce 1600ms infinite ease-in; +} diff --git a/examples/react/src/examples/TouchDevice/touch-device.module.css b/examples/react/src/examples/TouchDevice/touch-device.module.css deleted file mode 100644 index b5f65209..00000000 --- a/examples/react/src/examples/TouchDevice/touch-device.module.css +++ /dev/null @@ -1,19 +0,0 @@ -.flow :global .react-flow__handle { - width: 20px; - height: 20px; - border-radius: 3px; - background-color: #9f7aea; -} - -.flow :global .react-flow__handle.connecting { - animation: bounce 1600ms infinite ease-out; -} - -@keyframes bounce { - 0% { - transform: translate(0, -50%) scale(1); - } - 50% { - transform: translate(0, -50%) scale(1.1); - } -} diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx index 25794ee6..6ab33c3b 100644 --- a/packages/react/src/components/Handle/index.tsx +++ b/packages/react/src/components/Handle/index.tsx @@ -39,10 +39,12 @@ const connectingSelector = connectionClickStartHandle: clickHandle, connectionMode, } = state; + const connectingFrom = + startHandle?.nodeId === nodeId && startHandle?.handleId === handleId && startHandle?.type === type; + const connectingTo = endHandle?.nodeId === nodeId && endHandle?.handleId === handleId && endHandle?.type === type; return { - connecting: - (startHandle?.nodeId === nodeId && startHandle?.handleId === handleId && startHandle?.type === type) || - (endHandle?.nodeId === nodeId && endHandle?.handleId === handleId && endHandle?.type === type), + connectingFrom, + connectingTo, clickConnecting: clickHandle?.nodeId === nodeId && clickHandle?.handleId === handleId && clickHandle?.type === type, isPossibleEndHandle: @@ -77,7 +79,7 @@ const HandleComponent = forwardRef( const store = useStoreApi(); const nodeId = useNodeId(); const { connectOnClick, noPanClassName, rfId } = useStore(selector, shallow); - const { connecting, clickConnecting, isPossibleEndHandle, connectionInProcess } = useStore( + const { connectingFrom, connectingTo, clickConnecting, isPossibleEndHandle, connectionInProcess } = useStore( connectingSelector(nodeId, handleId, type), shallow ); @@ -210,8 +212,11 @@ const HandleComponent = forwardRef( connectable: isConnectable, connectablestart: isConnectableStart, connectableend: isConnectableEnd, - connecting: connecting || clickConnecting, - // this class is used to style the handle when the user is connecting + clickconnecting: clickConnecting, + connectingfrom: connectingFrom, + connectingto: connectingTo, + // shows where you can start a connection from + // and where you can end it while connecting connectionindicator: isConnectable && (!connectionInProcess || isPossibleEndHandle) && diff --git a/packages/system/src/styles/init.css b/packages/system/src/styles/init.css index 04ee6c98..e4d48221 100644 --- a/packages/system/src/styles/init.css +++ b/packages/system/src/styles/init.css @@ -223,13 +223,23 @@ svg.xy-flow__connectionline { min-width: 5px; min-height: 5px; - &.connecting { + &.connectingfrom { pointer-events: all; + background: violet !important; + } + + &.clickconnecting { + background: greenyellow !important; + } + + &.connectingto { + background: red !important; } &.connectionindicator { pointer-events: all; cursor: crosshair; + background: green; } &-bottom { From 5bab60724fbbf45dd6a72cca463abad0925239e7 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Wed, 28 Feb 2024 12:38:43 +0100 Subject: [PATCH 4/7] simplified --- packages/react/src/components/Handle/index.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx index 6ab33c3b..378edb30 100644 --- a/packages/react/src/components/Handle/index.tsx +++ b/packages/react/src/components/Handle/index.tsx @@ -39,12 +39,10 @@ const connectingSelector = connectionClickStartHandle: clickHandle, connectionMode, } = state; - const connectingFrom = - startHandle?.nodeId === nodeId && startHandle?.handleId === handleId && startHandle?.type === type; - const connectingTo = endHandle?.nodeId === nodeId && endHandle?.handleId === handleId && endHandle?.type === type; return { - connectingFrom, - connectingTo, + connectingFrom: + startHandle?.nodeId === nodeId && startHandle?.handleId === handleId && startHandle?.type === type, + connectingTo: endHandle?.nodeId === nodeId && endHandle?.handleId === handleId && endHandle?.type === type, clickConnecting: clickHandle?.nodeId === nodeId && clickHandle?.handleId === handleId && clickHandle?.type === type, isPossibleEndHandle: From 417a3f528df5b60ccd1e2cdd100d3f6f7ed7344c Mon Sep 17 00:00:00 2001 From: peterkogo Date: Wed, 28 Feb 2024 12:51:43 +0100 Subject: [PATCH 5/7] removed debug colors --- packages/system/src/styles/init.css | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/system/src/styles/init.css b/packages/system/src/styles/init.css index e4d48221..72f0b2e8 100644 --- a/packages/system/src/styles/init.css +++ b/packages/system/src/styles/init.css @@ -225,21 +225,11 @@ svg.xy-flow__connectionline { &.connectingfrom { pointer-events: all; - background: violet !important; - } - - &.clickconnecting { - background: greenyellow !important; - } - - &.connectingto { - background: red !important; } &.connectionindicator { pointer-events: all; cursor: crosshair; - background: green; } &-bottom { From 68ce560cf177f6284fdeee7fff41c5663f367f60 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 29 Feb 2024 12:53:04 +0100 Subject: [PATCH 6/7] chore(handles): cleanup --- .../react/src/examples/TouchDevice/index.tsx | 1 + .../src/examples/TouchDevice/touch-device.css | 8 ++++---- .../examples/Validation/validation.module.css | 6 +++++- .../src/routes/examples/validation/style.css | 6 +++++- packages/react/src/components/Handle/index.tsx | 10 ++++++++-- .../src/lib/components/Handle/Handle.svelte | 18 +++++++++++++++++- packages/system/src/xyhandle/XYHandle.ts | 16 +--------------- packages/system/src/xyhandle/utils.ts | 4 ---- 8 files changed, 41 insertions(+), 28 deletions(-) diff --git a/examples/react/src/examples/TouchDevice/index.tsx b/examples/react/src/examples/TouchDevice/index.tsx index 2073d13a..e412bee5 100644 --- a/examples/react/src/examples/TouchDevice/index.tsx +++ b/examples/react/src/examples/TouchDevice/index.tsx @@ -42,6 +42,7 @@ const TouchDeviceFlow = () => { onConnectEnd={onConnectEnd} onClickConnectStart={onClickConnectStart} onClickConnectEnd={onClickConnectEnd} + className="touch-flow" /> ); }; diff --git a/examples/react/src/examples/TouchDevice/touch-device.css b/examples/react/src/examples/TouchDevice/touch-device.css index 3ec36be2..92f85c27 100644 --- a/examples/react/src/examples/TouchDevice/touch-device.css +++ b/examples/react/src/examples/TouchDevice/touch-device.css @@ -1,15 +1,15 @@ -.react-flow .react-flow__handle { +.react-flow.touch-flow .react-flow__handle { width: 20px; height: 20px; border-radius: 3px; background-color: #9f7aea; } -.react-flow__handle-right { +.touch-flow .react-flow__handle-right { --translate: translate(50%, -50%); } -.react-flow__handle-left { +.touch-flow .react-flow__handle-left { --translate: translate(-50%, -50%); } @@ -22,6 +22,6 @@ } } -.react-flow .react-flow__handle.clickconnecting { +.react-flow.touch-flow .react-flow__handle.clickconnecting { animation: bounce 1600ms infinite ease-in; } diff --git a/examples/react/src/examples/Validation/validation.module.css b/examples/react/src/examples/Validation/validation.module.css index c19a8723..3fbe2f94 100644 --- a/examples/react/src/examples/Validation/validation.module.css +++ b/examples/react/src/examples/Validation/validation.module.css @@ -24,10 +24,14 @@ background: #fff; } -.validationflow :global .connecting { +.validationflow :global .connectingto { background: #ff6060; } +.validationflow :global .react-flow__node-custominput .connectingfrom { + background: #55dd99; +} + .validationflow :global .valid { background: #55dd99; } diff --git a/examples/svelte/src/routes/examples/validation/style.css b/examples/svelte/src/routes/examples/validation/style.css index 6839e129..a6bbf75a 100644 --- a/examples/svelte/src/routes/examples/validation/style.css +++ b/examples/svelte/src/routes/examples/validation/style.css @@ -1,7 +1,11 @@ -.svelte-flow__handle.connecting { +.svelte-flow__handle.connectingto { background: #ff6060; } +.svelte-flow__handle.connectingfrom { + background: #55dd99; +} + .svelte-flow__handle.valid { background: #55dd99; } diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx index 378edb30..75c36b8c 100644 --- a/packages/react/src/components/Handle/index.tsx +++ b/packages/react/src/components/Handle/index.tsx @@ -38,11 +38,15 @@ const connectingSelector = connectionEndHandle: endHandle, connectionClickStartHandle: clickHandle, connectionMode, + connectionStatus, } = state; + + const connectingTo = endHandle?.nodeId === nodeId && endHandle?.handleId === handleId && endHandle?.type === type; + return { connectingFrom: startHandle?.nodeId === nodeId && startHandle?.handleId === handleId && startHandle?.type === type, - connectingTo: endHandle?.nodeId === nodeId && endHandle?.handleId === handleId && endHandle?.type === type, + connectingTo, clickConnecting: clickHandle?.nodeId === nodeId && clickHandle?.handleId === handleId && clickHandle?.type === type, isPossibleEndHandle: @@ -50,6 +54,7 @@ const connectingSelector = ? startHandle?.type !== type : nodeId !== startHandle?.nodeId || handleId !== startHandle?.handleId, connectionInProcess: !!startHandle, + valid: connectingTo && connectionStatus === 'valid', }; }; @@ -77,7 +82,7 @@ const HandleComponent = forwardRef( const store = useStoreApi(); const nodeId = useNodeId(); const { connectOnClick, noPanClassName, rfId } = useStore(selector, shallow); - const { connectingFrom, connectingTo, clickConnecting, isPossibleEndHandle, connectionInProcess } = useStore( + const { connectingFrom, connectingTo, clickConnecting, isPossibleEndHandle, connectionInProcess, valid } = useStore( connectingSelector(nodeId, handleId, type), shallow ); @@ -213,6 +218,7 @@ const HandleComponent = forwardRef( clickconnecting: clickConnecting, connectingfrom: connectingFrom, connectingto: connectingTo, + valid, // shows where you can start a connection from // and where you can end it while connecting connectionindicator: diff --git a/packages/svelte/src/lib/components/Handle/Handle.svelte b/packages/svelte/src/lib/components/Handle/Handle.svelte index f8a43dd4..e35804fc 100644 --- a/packages/svelte/src/lib/components/Handle/Handle.svelte +++ b/packages/svelte/src/lib/components/Handle/Handle.svelte @@ -56,7 +56,8 @@ onconnect: onConnectAction, onconnectstart: onConnectStartAction, onconnectend: onConnectEndAction, - flowId + flowId, + connection } = store; function onPointerDown(event: MouseEvent | TouchEvent) { @@ -123,6 +124,16 @@ prevConnections = connections ?? new Map(); } + $: connectingFrom = + $connection.startHandle?.nodeId === nodeId && + $connection.startHandle?.type === type && + $connection.startHandle?.handleId === handleId; + $: connectingTo = + $connection.endHandle?.nodeId === nodeId && + $connection.endHandle?.type === type && + $connection.endHandle?.handleId === handleId; + $: valid = connectingTo && $connection.status === 'valid'; + // @todo implement connectablestart, connectableend @@ -141,6 +152,11 @@ The Handle component is the part of a node that can be used to connect nodes. 'nodrag', 'nopan', position, + { + valid, + connectingto: connectingTo, + connectingfrom: connectingFrom + }, className ])} class:source={!isTarget} diff --git a/packages/system/src/xyhandle/XYHandle.ts b/packages/system/src/xyhandle/XYHandle.ts index 4ddb5313..4f99a97c 100644 --- a/packages/system/src/xyhandle/XYHandle.ts +++ b/packages/system/src/xyhandle/XYHandle.ts @@ -15,7 +15,7 @@ import { type ConnectionHandle, } from '../types'; -import { getClosestHandle, getConnectionStatus, getHandleLookup, getHandleType, resetRecentHandle } from './utils'; +import { getClosestHandle, getConnectionStatus, getHandleLookup, getHandleType } from './utils'; export type OnPointerDownParams = { autoPanOnConnect: boolean; @@ -107,7 +107,6 @@ function onPointerDown( return; } - let prevActiveHandle: Element; let connectionPosition = getEventPosition(event, containerBounds); let autoPanStarted = false; let connection: Connection | null = null; @@ -194,18 +193,6 @@ function onPointerDown( connectionStatus: getConnectionStatus(!!closestHandle, isValid), connectionEndHandle: result.endHandle, }); - - if (!closestHandle && !isValid && !handleDomNode) { - return resetRecentHandle(prevActiveHandle, lib); - } - - if (connection?.source !== connection?.target && handleDomNode) { - resetRecentHandle(prevActiveHandle, lib); - prevActiveHandle = handleDomNode; - handleDomNode.classList.add('connecting', `${lib}-flow__handle-connecting`); - handleDomNode.classList.toggle('valid', isValid); - handleDomNode.classList.toggle(`${lib}-flow__handle-valid`, isValid); - } } function onPointerUp(event: MouseEvent | TouchEvent) { @@ -221,7 +208,6 @@ function onPointerDown( onEdgeUpdateEnd?.(event); } - resetRecentHandle(prevActiveHandle, lib); cancelConnection(); cancelAnimationFrame(autoPanId); autoPanStarted = false; diff --git a/packages/system/src/xyhandle/utils.ts b/packages/system/src/xyhandle/utils.ts index c10356b1..8c82f6e3 100644 --- a/packages/system/src/xyhandle/utils.ts +++ b/packages/system/src/xyhandle/utils.ts @@ -101,10 +101,6 @@ export function getHandleType( return null; } -export function resetRecentHandle(handleDomNode: Element, lib: string): void { - handleDomNode?.classList.remove('valid', 'connecting', `${lib}-flow__handle-valid`, `${lib}-flow__handle-connecting`); -} - export function getConnectionStatus(isInsideConnectionRadius: boolean, isHandleValid: boolean) { let connectionStatus = null; From 59e656ea24aca044851971930da823644f159faa Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 29 Feb 2024 12:54:24 +0100 Subject: [PATCH 7/7] chore(changelogs) --- packages/react/CHANGELOG.md | 1 + packages/svelte/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 69b51bf7..0f5f3b92 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -5,6 +5,7 @@ ## ⚠️ Breaking changes - `useNodesData` not only returns data objects but also the type and the id of the node +- status class names for Handle components are slightly different. It's now "connectingfrom" and "connectingto" instead of "connecting" ## Patch changes diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 948e69eb..ea7f4181 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -5,6 +5,7 @@ ## ⚠️ Breaking changes - `useNodesData` not only returns data objects but also the type and the id of the node +- status class names for Handle components are slightly different. It's now "connectingfrom" and "connectingto" instead of "connecting" ## Patch changes