diff --git a/examples/react/src/examples/NodeResizer/index.tsx b/examples/react/src/examples/NodeResizer/index.tsx index 8d5b7602..397c1f22 100644 --- a/examples/react/src/examples/NodeResizer/index.tsx +++ b/examples/react/src/examples/NodeResizer/index.tsx @@ -37,6 +37,7 @@ const initialNodes: Node[] = [ type: 'defaultResizer', data: { label: 'default resizer' }, position: { x: 0, y: 0 }, + origin: [1, 1], style: { ...nodeStyle }, }, { @@ -127,7 +128,7 @@ const initialNodes: Node[] = [ data: { label: 'Parent', keepAspectRatio: true }, position: { x: 700, y: 0 }, width: 300, - height: 400, + height: 300, style: { ...nodeStyle }, }, { @@ -147,7 +148,9 @@ const initialNodes: Node[] = [ id: '5b', type: 'defaultResizer', data: { label: 'Child with expandParent' }, - position: { x: 150, y: 100 }, + position: { x: 100, y: 100 }, + width: 100, + height: 100, parentId: '5', expandParent: true, style: { ...nodeStyle }, @@ -155,8 +158,10 @@ const initialNodes: Node[] = [ { id: '5c', type: 'defaultResizer', - data: { label: 'Child with expandParent & keepAspectRatio', keepAspectRatio: true }, - position: { x: 25, y: 200 }, + data: { label: 'Child with expandParent & keepAspectRatio' }, + position: { x: 250, y: 200 }, + height: 100, + width: 100, parentId: '5', expandParent: true, style: { ...nodeStyle }, diff --git a/examples/react/src/examples/Subflow/DebugNode.tsx b/examples/react/src/examples/Subflow/DebugNode.tsx index 6a8a2d26..547da524 100644 --- a/examples/react/src/examples/Subflow/DebugNode.tsx +++ b/examples/react/src/examples/Subflow/DebugNode.tsx @@ -1,6 +1,6 @@ import React, { memo, FC, CSSProperties } from 'react'; -import { Handle, NodeProps, Position } from '@xyflow/react'; +import { Handle, NodeProps, Position, useInternalNode } from '@xyflow/react'; const infoStyle: CSSProperties = { fontSize: 11 }; const idStyle: CSSProperties = { @@ -12,6 +12,8 @@ const idStyle: CSSProperties = { }; const DebugNode: FC = ({ zIndex, positionAbsoluteX, positionAbsoluteY, id }) => { + const node = useInternalNode(id)!; + return ( <> @@ -19,6 +21,9 @@ const DebugNode: FC = ({ zIndex, positionAbsoluteX, positionAbsoluteY
x:{Math.round(positionAbsoluteX)} y:{Math.round(positionAbsoluteY)} z:{zIndex}
+
+ x:{Math.round(node.position.x)} y:{Math.round(node.position.y)} +
); diff --git a/examples/react/src/examples/Subflow/index.tsx b/examples/react/src/examples/Subflow/index.tsx index 7f1b3f4b..74654870 100644 --- a/examples/react/src/examples/Subflow/index.tsx +++ b/examples/react/src/examples/Subflow/index.tsx @@ -14,6 +14,8 @@ import { Background, Panel, NodeOrigin, + useUpdateNodeInternals, + ReactFlowProvider, } from '@xyflow/react'; import DebugNode from './DebugNode'; @@ -104,7 +106,7 @@ const initialNodes: Node[] = [ { id: '5b', data: { label: 'Node 5b' }, - position: { x: 225, y: 50 }, + position: { x: 200, y: 200 }, className: 'light', parentId: '5', expandParent: true, @@ -151,6 +153,7 @@ const nodeTypes = { const Subflow = () => { const [rfInstance, setRfInstance] = useState(null); + const updateNodeInternals = useUpdateNodeInternals(); const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); @@ -216,6 +219,7 @@ const Subflow = () => { onlyRenderVisibleElements={false} nodeTypes={nodeTypes} fitView + nodeOrigin={[0, 0]} > @@ -228,9 +232,14 @@ const Subflow = () => { + ); }; -export default Subflow; +export default () => ( + + + +); diff --git a/examples/svelte/src/routes/examples/custom-connection-line/ConnectionLine.svelte b/examples/svelte/src/routes/examples/custom-connection-line/ConnectionLine.svelte index 0a7f87b5..d315f572 100644 --- a/examples/svelte/src/routes/examples/custom-connection-line/ConnectionLine.svelte +++ b/examples/svelte/src/routes/examples/custom-connection-line/ConnectionLine.svelte @@ -1,9 +1,24 @@ -{#if $connection.path} - +{#if $connection.inProgress} + {/if} diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 8fa3d36d..64fd9491 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,12 @@ # @xyflow/react +## 12.0.0-next.25 + +- `useConnection` returns `ConnectionState` with `toNode` and `toHandle` +- add `toNode` and `toHandle` to custom connection line props +- node origin is part of position absolute +- refactor connection handling + ## 12.0.0-next.24 - fix `window` bug for SSR for real diff --git a/packages/react/package.json b/packages/react/package.json index 437485ec..957392b5 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/react", - "version": "12.0.0-next.24", + "version": "12.0.0-next.25", "description": "React Flow - A highly customizable React library for building node-based editors and interactive flow charts.", "keywords": [ "react", diff --git a/packages/react/src/additional-components/MiniMap/MiniMap.tsx b/packages/react/src/additional-components/MiniMap/MiniMap.tsx index 0a05890d..bd7d6634 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMap.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMap.tsx @@ -25,12 +25,8 @@ const selector = (s: ReactFlowState) => { return { viewBB, - boundingRect: - s.nodeLookup.size > 0 - ? getBoundsOfRects(getInternalNodesBounds(s.nodeLookup, { nodeOrigin: s.nodeOrigin }), viewBB) - : viewBB, + boundingRect: s.nodeLookup.size > 0 ? getBoundsOfRects(getInternalNodesBounds(s.nodeLookup), viewBB) : viewBB, rfId: s.rfId, - nodeOrigin: s.nodeOrigin, panZoom: s.panZoom, translateExtent: s.translateExtent, flowWidth: s.width, diff --git a/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx b/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx index da6d3745..5bb7e939 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ComponentType, memo } from 'react'; -import { NodeOrigin, getNodeDimensions, getNodePositionWithOrigin, nodeHasDimensions } from '@xyflow/system'; +import { getNodeDimensions, nodeHasDimensions } from '@xyflow/system'; import { shallow } from 'zustand/shallow'; import { useStore } from '../../hooks/useStore'; @@ -11,7 +11,6 @@ import type { MiniMapNodes as MiniMapNodesProps, GetMiniMapNodeAttribute, MiniMa declare const window: any; -const selector = (s: ReactFlowState) => s.nodeOrigin; const selectorNodeIds = (s: ReactFlowState) => s.nodes.map((node) => node.id); const getAttrFunction = (func: any): GetMiniMapNodeAttribute => func instanceof Function ? func : () => func; @@ -28,7 +27,6 @@ function MiniMapNodes({ onClick, }: MiniMapNodesProps) { const nodeIds = useStore(selectorNodeIds, shallow); - const nodeOrigin = useStore(selector); const nodeColorFunc = getAttrFunction(nodeColor); const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor); const nodeClassNameFunc = getAttrFunction(nodeClassName); @@ -46,7 +44,6 @@ function MiniMapNodes({ key={nodeId} id={nodeId} - nodeOrigin={nodeOrigin} nodeColorFunc={nodeColorFunc} nodeStrokeColorFunc={nodeStrokeColorFunc} nodeClassNameFunc={nodeClassNameFunc} @@ -63,7 +60,6 @@ function MiniMapNodes({ function NodeComponentWrapperInner({ id, - nodeOrigin, nodeColorFunc, nodeStrokeColorFunc, nodeClassNameFunc, @@ -74,7 +70,6 @@ function NodeComponentWrapperInner({ onClick, }: { id: string; - nodeOrigin: NodeOrigin; nodeColorFunc: GetMiniMapNodeAttribute; nodeStrokeColorFunc: GetMiniMapNodeAttribute; nodeClassNameFunc: GetMiniMapNodeAttribute; @@ -86,7 +81,7 @@ function NodeComponentWrapperInner({ }) { const { node, x, y } = useStore((s) => { const node = s.nodeLookup.get(id) as InternalNode; - const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute; + const { x, y } = node.internals.positionAbsolute; return { node, diff --git a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx index 79746f84..63128c09 100644 --- a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx +++ b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx @@ -67,26 +67,30 @@ function ResizeControl({ }, onChange: (change: XYResizerChange, childChanges: XYResizerChildChange[]) => { const { triggerNodeChanges, nodeLookup, parentLookup, nodeOrigin } = store.getState(); - const changes: NodeChange[] = []; const nextPosition = { x: change.x, y: change.y }; - const node = nodeLookup.get(id); + if (node && node.expandParent && node.parentId) { + const origin = node.origin ?? nodeOrigin; + const width = change.width ?? node.measured.width!; + const height = change.height ?? node.measured.height!; + const child: ParentExpandChild = { id: node.id, parentId: node.parentId, rect: { - width: change.width ?? node.measured.width!, - height: change.height ?? node.measured.height!, + width, + height, ...evaluateAbsolutePosition( { x: change.x ?? node.position.x, y: change.y ?? node.position.y, }, + { width, height }, node.parentId, nodeLookup, - node.origin ?? nodeOrigin + origin ), }, }; @@ -94,9 +98,10 @@ function ResizeControl({ const parentExpandChanges = handleExpandParent([child], nodeLookup, parentLookup, nodeOrigin); changes.push(...parentExpandChanges); - // when the parent was expanded by the child node, its position will be clamped at 0,0 - nextPosition.x = change.x ? Math.max(0, change.x) : undefined; - nextPosition.y = change.y ? Math.max(0, change.y) : undefined; + // when the parent was expanded by the child node, its position will be clamped at + // 0,0 when node origin is 0,0 and to width, height if it's 1,1 + nextPosition.x = change.x ? Math.max(origin[0] * width, change.x) : undefined; + nextPosition.y = change.y ? Math.max(origin[1] * height, change.y) : undefined; } if (nextPosition.x !== undefined && nextPosition.y !== undefined) { diff --git a/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx b/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx index f14f822e..b4431157 100644 --- a/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx +++ b/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx @@ -1,7 +1,7 @@ import { useCallback, CSSProperties } from 'react'; import cc from 'classcat'; import { shallow } from 'zustand/shallow'; -import { Rect, Position, getNodeToolbarTransform, getNodesBounds } from '@xyflow/system'; +import { Position, getNodeToolbarTransform, getInternalNodesBounds, NodeLookup } from '@xyflow/system'; import { InternalNode, ReactFlowState } from '../../types'; import { useStore } from '../../hooks/useStore'; @@ -17,21 +17,24 @@ const nodeEqualityFn = (a?: InternalNode, b?: InternalNode) => a?.selected !== b?.selected || a?.internals.z !== b?.internals.z; -const nodesEqualityFn = (a: InternalNode[], b: InternalNode[]) => { - if (a.length !== b.length) { +const nodesEqualityFn = (a: NodeLookup, b: NodeLookup) => { + if (a.size !== b.size) { return false; } - return !a.some((node, i) => nodeEqualityFn(node, b[i])); + for (const [key, node] of a) { + if (nodeEqualityFn(node, b.get(key))) { + return false; + } + } + + return true; }; const storeSelector = (state: ReactFlowState) => ({ - viewport: { - x: state.transform[0], - y: state.transform[1], - zoom: state.transform[2], - }, - nodeOrigin: state.nodeOrigin, + x: state.transform[0], + y: state.transform[1], + zoom: state.transform[2], selectedNodesCount: state.nodes.filter((node) => node.selected).length, }); @@ -49,36 +52,41 @@ export function NodeToolbar({ const contextNodeId = useNodeId(); const nodesSelector = useCallback( - (state: ReactFlowState): InternalNode[] => { + (state: ReactFlowState): NodeLookup => { const nodeIds = Array.isArray(nodeId) ? nodeId : [nodeId || contextNodeId || '']; - - return nodeIds.reduce((acc, id) => { + const internalNodes = nodeIds.reduce((res, id) => { const node = state.nodeLookup.get(id); if (node) { - acc.push(node); + res.set(node.id, node); } - return acc; - }, []); + + return res; + }, new Map()); + + return internalNodes; }, [nodeId, contextNodeId] ); const nodes = useStore(nodesSelector, nodesEqualityFn); - const { viewport, nodeOrigin, selectedNodesCount } = useStore(storeSelector, shallow); + const { x, y, zoom, selectedNodesCount } = useStore(storeSelector, shallow); // if isVisible is not set, we show the toolbar only if its node is selected and no other node is selected const isActive = - typeof isVisible === 'boolean' ? isVisible : nodes.length === 1 && nodes[0].selected && selectedNodesCount === 1; + typeof isVisible === 'boolean' + ? isVisible + : nodes.size === 1 && nodes.values().next().value.selected && selectedNodesCount === 1; - if (!isActive || !nodes.length) { + if (!isActive || !nodes.size) { return null; } - const nodeRect: Rect = getNodesBounds(nodes, { nodeOrigin }); - const zIndex: number = Math.max(...nodes.map((node) => node.internals.z + 1)); + const nodeRect = getInternalNodesBounds(nodes); + const nodesArray = Array.from(nodes.values()); + const zIndex = Math.max(...nodesArray.map((node) => node.internals.z + 1)); const wrapperStyle: CSSProperties = { position: 'absolute', - transform: getNodeToolbarTransform(nodeRect, viewport, position, offset, align), + transform: getNodeToolbarTransform(nodeRect, { x, y, zoom }, position, offset, align), zIndex, ...style, }; @@ -89,7 +97,8 @@ export function NodeToolbar({ style={wrapperStyle} className={cc(['react-flow__node-toolbar', className])} {...rest} - data-id={nodes.reduce((acc, node) => `${acc}${node.id} `, '').trim()} + // @todo: check if we could only do this for non-prod envs + data-id={nodesArray.reduce((acc, node) => `${acc}${node.id} `, '').trim()} > {children} diff --git a/packages/react/src/components/ConnectionLine/index.tsx b/packages/react/src/components/ConnectionLine/index.tsx index 33b1ca50..a5f7e1e2 100644 --- a/packages/react/src/components/ConnectionLine/index.tsx +++ b/packages/react/src/components/ConnectionLine/index.tsx @@ -1,132 +1,18 @@ -import { CSSProperties, useCallback } from 'react'; +import { CSSProperties } from 'react'; import { shallow } from 'zustand/shallow'; import cc from 'classcat'; import { - Position, ConnectionLineType, - ConnectionMode, getBezierPath, getSmoothStepPath, - type ConnectionStatus, - type HandleType, + getConnectionStatus, + getStraightPath, } from '@xyflow/system'; import { useStore } from '../../hooks/useStore'; import { getSimpleBezierPath } from '../Edges/SimpleBezierEdge'; -import type { ConnectionLineComponent, ReactFlowState, ReactFlowStore } from '../../types'; - -type ConnectionLineProps = { - nodeId: string; - handleType: HandleType; - type: ConnectionLineType; - style?: CSSProperties; - CustomComponent?: ConnectionLineComponent; - connectionStatus: ConnectionStatus | null; -}; - -const oppositePosition = { - [Position.Left]: Position.Right, - [Position.Right]: Position.Left, - [Position.Top]: Position.Bottom, - [Position.Bottom]: Position.Top, -}; - -const ConnectionLine = ({ - nodeId, - handleType, - style, - type = ConnectionLineType.Bezier, - CustomComponent, - connectionStatus, -}: ConnectionLineProps) => { - const { fromNode, handleId, toX, toY, connectionMode, endPosition, isValid } = useStore( - useCallback( - (s: ReactFlowStore) => ({ - fromNode: s.nodeLookup.get(nodeId), - handleId: s.connectionStartHandle?.handleId, - toX: (s.connectionPosition.x - s.transform[0]) / s.transform[2], - toY: (s.connectionPosition.y - s.transform[1]) / s.transform[2], - connectionMode: s.connectionMode, - endPosition: s.connectionEndHandle?.position, - isValid: s.connectionStatus === 'valid', - }), - [nodeId] - ), - shallow - ); - - const fromHandleBounds = fromNode?.internals.handleBounds; - let handleBounds = fromHandleBounds?.[handleType]; - - if (connectionMode === ConnectionMode.Loose) { - handleBounds = handleBounds ? handleBounds : fromHandleBounds?.[handleType === 'source' ? 'target' : 'source']; - } - - if (!fromNode || !handleBounds) { - return null; - } - - const fromHandle = handleId ? handleBounds.find((d) => d.id === handleId) : handleBounds[0]; - const fromHandleX = fromHandle ? fromHandle.x + fromHandle.width / 2 : (fromNode.measured.width ?? 0) / 2; - const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode.measured.height ?? 0; - const fromX = fromNode.internals.positionAbsolute.x + fromHandleX; - const fromY = fromNode.internals.positionAbsolute.y + fromHandleY; - const fromPosition = fromHandle?.position; - const toPosition = isValid && endPosition ? endPosition : fromPosition ? oppositePosition[fromPosition] : null; - - if (!fromPosition || !toPosition) { - return null; - } - - if (CustomComponent) { - return ( - - ); - } - - let dAttr = ''; - - const pathParams = { - sourceX: fromX, - sourceY: fromY, - sourcePosition: fromPosition, - targetX: toX, - targetY: toY, - targetPosition: toPosition, - }; - - if (type === ConnectionLineType.Bezier) { - // we assume the destination position is opposite to the source position - [dAttr] = getBezierPath(pathParams); - } else if (type === ConnectionLineType.Step) { - [dAttr] = getSmoothStepPath({ - ...pathParams, - borderRadius: 0, - }); - } else if (type === ConnectionLineType.SmoothStep) { - [dAttr] = getSmoothStepPath(pathParams); - } else if (type === ConnectionLineType.SimpleBezier) { - [dAttr] = getSimpleBezierPath(pathParams); - } else { - dAttr = `M${fromX},${fromY} ${toX},${toY}`; - } - - return ; -}; - -ConnectionLine.displayName = 'ConnectionLine'; +import type { ConnectionLineComponent, ReactFlowState } from '../../types'; +import { useConnection } from '../../hooks/useConnection'; type ConnectionLineWrapperProps = { type: ConnectionLineType; @@ -136,19 +22,18 @@ type ConnectionLineWrapperProps = { }; const selector = (s: ReactFlowState) => ({ - nodeId: s.connectionStartHandle?.nodeId, - handleType: s.connectionStartHandle?.type, nodesConnectable: s.nodesConnectable, - connectionStatus: s.connectionStatus, + isValid: s.connection.isValid, + inProgress: s.connection.inProgress, width: s.width, height: s.height, }); export function ConnectionLineWrapper({ containerStyle, style, type, component }: ConnectionLineWrapperProps) { - const { nodeId, handleType, nodesConnectable, width, height, connectionStatus } = useStore(selector, shallow); - const isValid = !!(nodeId && handleType && width && nodesConnectable); + const { nodesConnectable, width, height, isValid, inProgress } = useStore(selector, shallow); + const renderConnection = !!(width && nodesConnectable && inProgress); - if (!isValid) { + if (!renderConnection) { return null; } @@ -159,16 +44,79 @@ export function ConnectionLineWrapper({ containerStyle, style, type, component } height={height} className="react-flow__connectionline react-flow__container" > - - + + ); } + +type ConnectionLineProps = { + type: ConnectionLineType; + style?: CSSProperties; + CustomComponent?: ConnectionLineComponent; + isValid: boolean | null; +}; + +const ConnectionLine = ({ style, type = ConnectionLineType.Bezier, CustomComponent, isValid }: ConnectionLineProps) => { + const { inProgress, from, fromNode, fromHandle, fromPosition, to, toNode, toHandle, toPosition } = useConnection(); + + if (!inProgress) { + return; + } + + if (CustomComponent) { + return ( + + ); + } + + let path = ''; + + const pathParams = { + sourceX: from.x, + sourceY: from.y, + sourcePosition: fromPosition, + targetX: to.x, + targetY: to.y, + targetPosition: toPosition, + }; + + switch (type) { + case ConnectionLineType.Bezier: + [path] = getBezierPath(pathParams); + break; + case ConnectionLineType.SimpleBezier: + [path] = getSimpleBezierPath(pathParams); + break; + case ConnectionLineType.Step: + [path] = getSmoothStepPath({ + ...pathParams, + borderRadius: 0, + }); + break; + case ConnectionLineType.SmoothStep: + [path] = getSmoothStepPath(pathParams); + break; + default: + [path] = getStraightPath(pathParams); + } + + return ; +}; + +ConnectionLine.displayName = 'ConnectionLine'; diff --git a/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx b/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx index d1cfd138..236592ee 100644 --- a/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx +++ b/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx @@ -96,7 +96,7 @@ export function EdgeUpdateAnchors({ onReconnectEnd: _onReconnectEnd, updateConnection, getTransform: () => store.getState().transform, - getConnectionStartHandle: () => store.getState().connectionStartHandle, + getFromHandle: () => store.getState().connection.fromHandle, }); }; diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx index 760733a0..02ba9ced 100644 --- a/packages/react/src/components/Handle/index.tsx +++ b/packages/react/src/components/Handle/index.tsx @@ -39,28 +39,22 @@ const selector = (s: ReactFlowState) => ({ const connectingSelector = (nodeId: string | null, handleId: string | null, type: HandleType) => (state: ReactFlowState) => { - const { - connectionStartHandle: startHandle, - connectionEndHandle: endHandle, - connectionClickStartHandle: clickHandle, - connectionMode, - connectionStatus, - } = state; + const { connectionClickStartHandle: clickHandle, connectionMode, connection } = state; - const connectingTo = endHandle?.nodeId === nodeId && endHandle?.handleId === handleId && endHandle?.type === type; + const { fromHandle, toHandle, isValid } = connection; + + const connectingTo = toHandle?.nodeId === nodeId && toHandle?.id === handleId && toHandle?.type === type; return { - connectingFrom: - startHandle?.nodeId === nodeId && startHandle?.handleId === handleId && startHandle?.type === type, + connectingFrom: fromHandle?.nodeId === nodeId && fromHandle?.id === handleId && fromHandle?.type === type, connectingTo, - clickConnecting: - clickHandle?.nodeId === nodeId && clickHandle?.handleId === handleId && clickHandle?.type === type, + clickConnecting: clickHandle?.nodeId === nodeId && clickHandle?.id === handleId && clickHandle?.type === type, isPossibleEndHandle: connectionMode === ConnectionMode.Strict - ? startHandle?.type !== type - : nodeId !== startHandle?.nodeId || handleId !== startHandle?.handleId, - connectionInProcess: !!startHandle, - valid: connectingTo && connectionStatus === 'valid', + ? fromHandle?.type !== type + : nodeId !== fromHandle?.nodeId || handleId !== fromHandle?.id, + connectionInProcess: !!fromHandle, + valid: connectingTo && isValid, }; }; @@ -144,7 +138,7 @@ function HandleComponent( onConnect: onConnectExtended, isValidConnection: isValidConnection || currentStore.isValidConnection, getTransform: () => store.getState().transform, - getConnectionStartHandle: () => store.getState().connectionStartHandle, + getFromHandle: () => store.getState().connection.fromHandle, }); } @@ -172,7 +166,7 @@ function HandleComponent( if (!connectionClickStartHandle) { onClickConnectStart?.(event.nativeEvent, { nodeId, handleId, handleType: type }); - store.setState({ connectionClickStartHandle: { nodeId, type, handleId } }); + store.setState({ connectionClickStartHandle: { nodeId, type, id: handleId } }); return; } @@ -186,7 +180,7 @@ function HandleComponent( }, connectionMode, fromNodeId: connectionClickStartHandle.nodeId, - fromHandleId: connectionClickStartHandle.handleId || null, + fromHandleId: connectionClickStartHandle.id || null, fromType: connectionClickStartHandle.type, isValidConnection: isValidConnectionHandler, flowId, diff --git a/packages/react/src/components/NodeWrapper/index.tsx b/packages/react/src/components/NodeWrapper/index.tsx index 279cda82..655233c9 100644 --- a/packages/react/src/components/NodeWrapper/index.tsx +++ b/packages/react/src/components/NodeWrapper/index.tsx @@ -6,7 +6,6 @@ import { elementSelectionKeys, errorMessages, getNodeDimensions, - getPositionWithOrigin, isInputDOMNode, nodeHasDimensions, } from '@xyflow/system'; @@ -40,7 +39,6 @@ export function NodeWrapper({ rfId, nodeTypes, nodeExtent, - nodeOrigin, onError, }: NodeWrapperProps) { const { node, internals, isParent } = useStore((s) => { @@ -87,15 +85,11 @@ export function NodeWrapper({ const nodeDimensions = getNodeDimensions(node); const inlineDimensions = getNodeInlineStyleDimensions(node); + // TODO: clamping should happen earlier const clampedPosition = nodeExtent ? clampPosition(internals.positionAbsolute, nodeExtent) : internals.positionAbsolute; - const positionWithOrigin = getPositionWithOrigin({ - ...clampedPosition, - ...nodeDimensions, - origin: node.origin || nodeOrigin, - }); const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave; const onMouseEnterHandler = onMouseEnter @@ -181,7 +175,7 @@ export function NodeWrapper({ ref={nodeRef} style={{ zIndex: internals.z, - transform: `translate(${positionWithOrigin.x}px,${positionWithOrigin.y}px)`, + transform: `translate(${clampedPosition.x}px,${clampedPosition.y}px)`, pointerEvents: hasPointerEvents ? 'all' : 'none', visibility: hasDimensions ? 'visible' : 'hidden', ...node.style, diff --git a/packages/react/src/components/NodesSelection/index.tsx b/packages/react/src/components/NodesSelection/index.tsx index fda3a8c9..e2bcd238 100644 --- a/packages/react/src/components/NodesSelection/index.tsx +++ b/packages/react/src/components/NodesSelection/index.tsx @@ -21,7 +21,6 @@ export type NodesSelectionProps = { const selector = (s: ReactFlowState) => { const { width, height, x, y } = getInternalNodesBounds(s.nodeLookup, { - nodeOrigin: s.nodeOrigin, filter: (node) => !!node.selected, }); diff --git a/packages/react/src/components/ReactFlowProvider/index.tsx b/packages/react/src/components/ReactFlowProvider/index.tsx index f04ebebd..4843afeb 100644 --- a/packages/react/src/components/ReactFlowProvider/index.tsx +++ b/packages/react/src/components/ReactFlowProvider/index.tsx @@ -4,6 +4,7 @@ import { Provider } from '../../contexts/StoreContext'; import { createStore } from '../../store'; import { BatchProvider } from '../BatchProvider'; import type { Node, Edge } from '../../types'; +import { NodeOrigin } from '@xyflow/system'; export type ReactFlowProviderProps = { initialNodes?: Node[]; @@ -13,6 +14,7 @@ export type ReactFlowProviderProps = { initialWidth?: number; initialHeight?: number; fitView?: boolean; + nodeOrigin?: NodeOrigin; children: ReactNode; }; @@ -24,6 +26,7 @@ export function ReactFlowProvider({ initialWidth: width, initialHeight: height, fitView, + nodeOrigin, children, }: ReactFlowProviderProps) { const [store] = useState(() => @@ -35,6 +38,7 @@ export function ReactFlowProvider({ width, height, fitView, + nodeOrigin, }) ); diff --git a/packages/react/src/container/FlowRenderer/index.tsx b/packages/react/src/container/FlowRenderer/index.tsx index 30d17979..f3e0a23d 100644 --- a/packages/react/src/container/FlowRenderer/index.tsx +++ b/packages/react/src/container/FlowRenderer/index.tsx @@ -22,7 +22,6 @@ export type FlowRendererProps = Omit< | 'selectNodesOnDrag' | 'defaultMarkerColor' | 'rfId' - | 'nodeOrigin' > & { isControlledViewport: boolean; children: ReactNode; diff --git a/packages/react/src/container/GraphView/index.tsx b/packages/react/src/container/GraphView/index.tsx index 8050ca20..d74a0fdb 100644 --- a/packages/react/src/container/GraphView/index.tsx +++ b/packages/react/src/container/GraphView/index.tsx @@ -32,7 +32,6 @@ export type GraphViewProps > & { rfId: string; @@ -97,7 +96,6 @@ function GraphViewComponent diff --git a/packages/react/src/container/NodeRenderer/index.tsx b/packages/react/src/container/NodeRenderer/index.tsx index 39b20323..2b178ae5 100644 --- a/packages/react/src/container/NodeRenderer/index.tsx +++ b/packages/react/src/container/NodeRenderer/index.tsx @@ -22,7 +22,6 @@ export type NodeRendererProps = Pick< | 'noDragClassName' | 'rfId' | 'disableKeyboardA11y' - | 'nodeOrigin' | 'nodeExtent' | 'nodeTypes' >; @@ -72,7 +71,6 @@ function NodeRendererComponent(props: NodeRendererProps { - const { userSelectionRect, edgeLookup, transform, nodeOrigin, nodeLookup, triggerNodeChanges, triggerEdgeChanges } = + const { userSelectionRect, edgeLookup, transform, nodeLookup, triggerNodeChanges, triggerEdgeChanges } = store.getState(); if (!containerBounds.current || !userSelectionRect) { @@ -181,8 +181,7 @@ export function Pane({ nextUserSelectRect, transform, selectionMode === SelectionMode.Partial, - true, - nodeOrigin + true ); const selectedEdgeIds = new Set(); diff --git a/packages/react/src/container/ReactFlow/Wrapper.tsx b/packages/react/src/container/ReactFlow/Wrapper.tsx index af9ad4a4..ce1b5a74 100644 --- a/packages/react/src/container/ReactFlow/Wrapper.tsx +++ b/packages/react/src/container/ReactFlow/Wrapper.tsx @@ -3,6 +3,7 @@ import { useContext, type ReactNode } from 'react'; import StoreContext from '../../contexts/StoreContext'; import { ReactFlowProvider } from '../../components/ReactFlowProvider'; import type { Node, Edge } from '../../types'; +import { NodeOrigin } from '@xyflow/system'; export function Wrapper({ children, @@ -13,6 +14,7 @@ export function Wrapper({ width, height, fitView, + nodeOrigin, }: { children: ReactNode; nodes?: Node[]; @@ -22,6 +24,7 @@ export function Wrapper({ width?: number; height?: number; fitView?: boolean; + nodeOrigin?: NodeOrigin; }) { const isWrapped = useContext(StoreContext); @@ -40,6 +43,7 @@ export function Wrapper({ initialWidth={width} initialHeight={height} fitView={fitView} + nodeOrigin={nodeOrigin} > {children} diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx index 64ac7202..c301d9d4 100644 --- a/packages/react/src/container/ReactFlow/index.tsx +++ b/packages/react/src/container/ReactFlow/index.tsx @@ -157,7 +157,7 @@ function ReactFlow( data-testid="rf__wrapper" id={id} > - + onInit={onInit} onNodeClick={onNodeClick} @@ -217,7 +217,6 @@ function ReactFlow( noPanClassName={noPanClassName} rfId={rfId} disableKeyboardA11y={disableKeyboardA11y} - nodeOrigin={nodeOrigin} nodeExtent={nodeExtent} viewport={viewport} onViewportChange={onViewportChange} diff --git a/packages/react/src/hooks/useConnection.ts b/packages/react/src/hooks/useConnection.ts index 9b458e87..8a4b377b 100644 --- a/packages/react/src/hooks/useConnection.ts +++ b/packages/react/src/hooks/useConnection.ts @@ -2,33 +2,19 @@ import { shallow } from 'zustand/shallow'; import { useStore } from './useStore'; import type { ReactFlowStore } from '../types/store'; +import { ConnectionState, pointToRendererPoint } from '@xyflow/system'; -const selector = (s: ReactFlowStore) => ({ - startHandle: s.connectionStartHandle, - endHandle: s.connectionEndHandle, - status: s.connectionStatus, - position: s.connectionStartHandle ? s.connectionPosition : null, -}); - -type UseConnectionResult = { - /** The start handle where the user interaction started or null */ - startHandle: ReactFlowStore['connectionStartHandle']; - /** The target handle that's inside the connection radius or null */ - endHandle: ReactFlowStore['connectionEndHandle']; - /** The current connection status 'valid', 'invalid' or null*/ - status: ReactFlowStore['connectionStatus']; - /** The current connection position or null */ - position: ReactFlowStore['connectionPosition'] | null; +const selector = (s: ReactFlowStore): ConnectionState => { + return s.connection.inProgress + ? { ...s.connection, to: pointToRendererPoint(s.connection.to, s.transform) } + : { ...s.connection }; }; - /** - * Hook for accessing the ongoing connection. + * Hook for accessing the connection state. * * @public - * @returns ongoing connection + * @returns ConnectionState */ -export function useConnection(): UseConnectionResult { - const ongoingConnection = useStore(selector, shallow); - - return ongoingConnection; +export function useConnection(): ConnectionState { + return useStore(selector, shallow); } diff --git a/packages/react/src/hooks/useReactFlow.ts b/packages/react/src/hooks/useReactFlow.ts index 6766e775..faf9d31f 100644 --- a/packages/react/src/hooks/useReactFlow.ts +++ b/packages/react/src/hooks/useReactFlow.ts @@ -50,7 +50,7 @@ export function useReactFlow(node) ? node : nodeLookup.get(node.id)!; const position = nodeToUse.parentId - ? evaluateAbsolutePosition(nodeToUse.position, nodeToUse.parentId, nodeLookup, nodeOrigin) + ? evaluateAbsolutePosition(nodeToUse.position, nodeToUse.measured, nodeToUse.parentId, nodeLookup, nodeOrigin) : nodeToUse.position; const nodeWithPosition = { diff --git a/packages/react/src/hooks/useViewportHelper.ts b/packages/react/src/hooks/useViewportHelper.ts index ef4f6f38..245fafd7 100644 --- a/packages/react/src/hooks/useViewportHelper.ts +++ b/packages/react/src/hooks/useViewportHelper.ts @@ -45,22 +45,23 @@ const useViewportHelper = (): ViewportHelperFunctions => { return { x, y, zoom }; }, fitView: (options) => { - const { nodeLookup, width, height, nodeOrigin, minZoom, maxZoom, panZoom } = store.getState(); + const { nodeLookup, width, height, minZoom, maxZoom, panZoom } = store.getState(); - return panZoom - ? fitView( - { - nodeLookup, - width, - height, - nodeOrigin, - minZoom, - maxZoom, - panZoom, - }, - options - ) - : false; + if (!panZoom) { + return false; + } + + return fitView( + { + nodeLookup, + width, + height, + minZoom, + maxZoom, + panZoom, + }, + options + ); }, setCenter: (x, y, options) => { const { width, height, maxZoom, panZoom } = store.getState(); diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index e00cd0d7..e15d9ca2 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -49,7 +49,6 @@ export { type OnMoveStart, type OnMoveEnd, type Connection, - type ConnectionStatus, ConnectionMode, type OnConnectStartParams, type OnConnectStart, diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index 29f99999..57beb3f9 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -12,6 +12,8 @@ import { EdgeSelectionChange, NodeSelectionChange, ParentExpandChild, + initialConnection, + NodeOrigin, } from '@xyflow/system'; import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes'; @@ -26,6 +28,7 @@ const createStore = ({ width, height, fitView, + nodeOrigin, }: { nodes?: Node[]; edges?: Edge[]; @@ -34,10 +37,11 @@ const createStore = ({ width?: number; height?: number; fitView?: boolean; + nodeOrigin?: NodeOrigin; }) => createWithEqualityFn( (set, get) => ({ - ...getInitialState({ nodes, edges, width, height, fitView, defaultNodes, defaultEdges }), + ...getInitialState({ nodes, edges, width, height, fitView, nodeOrigin, defaultNodes, defaultEdges }), setNodes: (nodes: Node[]) => { const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect } = get(); // setNodes() is called exclusively in response to user actions: @@ -98,7 +102,7 @@ const createStore = ({ return; } - updateAbsolutePositions(nodeLookup, { nodeOrigin }); + updateAbsolutePositions(nodeLookup, parentLookup, { nodeOrigin }); // we call fitView once initially after all dimensions are set let nextFitViewDone = fitViewDone; @@ -155,8 +159,8 @@ const createStore = ({ } if (parentExpandChildren.length > 0) { - const { nodeLookup, parentLookup } = get(); - const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup); + const { nodeLookup, parentLookup, nodeOrigin } = get(); + const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup, nodeOrigin); changes.push(...parentExpandChanges); } @@ -288,7 +292,7 @@ const createStore = ({ return panBySystem({ delta, panZoom, transform, translateExtent, width, height }); }, fitView: (options?: FitViewOptions): boolean => { - const { panZoom, width, height, minZoom, maxZoom, nodeOrigin, nodeLookup } = get(); + const { panZoom, width, height, minZoom, maxZoom, nodeLookup } = get(); if (!panZoom) { return false; @@ -302,26 +306,17 @@ const createStore = ({ panZoom, minZoom, maxZoom, - nodeOrigin, }, options ); }, - cancelConnection: () => + cancelConnection: () => { set({ - connectionStatus: null, - connectionStartHandle: null, - connectionEndHandle: null, - }), - updateConnection: (params) => { - const { connectionPosition } = get(); - - const currentConnection = { - ...params, - connectionPosition: params.connectionPosition ?? connectionPosition, - }; - - set(currentConnection); + connection: { ...initialConnection }, + }); + }, + updateConnection: (connection) => { + set({ connection }); }, reset: () => set({ ...getInitialState() }), diff --git a/packages/react/src/store/initialState.ts b/packages/react/src/store/initialState.ts index d519665f..7d2789a2 100644 --- a/packages/react/src/store/initialState.ts +++ b/packages/react/src/store/initialState.ts @@ -7,6 +7,8 @@ import { updateConnectionLookup, devWarn, getInternalNodesBounds, + NodeOrigin, + initialConnection, } from '@xyflow/system'; import type { Edge, InternalNode, Node, ReactFlowStore } from '../types'; @@ -19,6 +21,7 @@ const getInitialState = ({ width, height, fitView, + nodeOrigin, }: { nodes?: Node[]; edges?: Edge[]; @@ -27,6 +30,7 @@ const getInitialState = ({ width?: number; height?: number; fitView?: boolean; + nodeOrigin?: NodeOrigin; } = {}): ReactFlowStore => { const nodeLookup = new Map(); const parentLookup = new Map(); @@ -34,21 +38,21 @@ const getInitialState = ({ const edgeLookup = new Map(); const storeEdges = defaultEdges ?? edges ?? []; const storeNodes = defaultNodes ?? nodes ?? []; + const storeNodeOrigin = nodeOrigin ?? [0, 0]; updateConnectionLookup(connectionLookup, edgeLookup, storeEdges); adoptUserNodes(storeNodes, nodeLookup, parentLookup, { - nodeOrigin: [0, 0], + nodeOrigin: storeNodeOrigin, elevateNodesOnSelect: false, }); let transform: Transform = [0, 0, 1]; if (fitView && width && height) { - // @todo users nodeOrigin should be used here const bounds = getInternalNodesBounds(nodeLookup, { - nodeOrigin: [0, 0], filter: (node) => !!((node.width || node.initialWidth) && (node.height || node.initialHeight)), }); + const { x, y, zoom } = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1); transform = [x, y, zoom]; } @@ -76,13 +80,11 @@ const getInitialState = ({ nodesSelectionActive: false, userSelectionActive: false, userSelectionRect: null, - connectionPosition: { x: 0, y: 0 }, - connectionStatus: null, connectionMode: ConnectionMode.Strict, domNode: null, paneDragging: false, noPanClassName: 'nopan', - nodeOrigin: [0, 0], + nodeOrigin: storeNodeOrigin, nodeDragThreshold: 1, snapGrid: [15, 15], @@ -103,8 +105,7 @@ const getInitialState = ({ multiSelectionActive: false, - connectionStartHandle: null, - connectionEndHandle: null, + connection: { ...initialConnection }, connectionClickStartHandle: null, connectOnClick: true, diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index 23bec358..640366d7 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -10,8 +10,7 @@ import type { HandleType, Connection, ConnectionLineType, - HandleElement, - ConnectionStatus, + Handle, EdgePosition, StepPathOptions, OnError, @@ -194,15 +193,17 @@ export type OnReconnect = (oldEdge: EdgeType, newC export type ConnectionLineComponentProps = { connectionLineStyle?: CSSProperties; connectionLineType: ConnectionLineType; - fromNode?: Node; - fromHandle?: HandleElement; + fromNode: Node; + fromHandle: Handle; fromX: number; fromY: number; toX: number; toY: number; fromPosition: Position; toPosition: Position; - connectionStatus: ConnectionStatus | null; + connectionStatus: 'valid' | 'invalid' | null; + toNode: Node | null; + toHandle: Handle | null; }; export type ConnectionLineComponent = ComponentType; diff --git a/packages/react/src/types/nodes.ts b/packages/react/src/types/nodes.ts index a01a894a..71e03036 100644 --- a/packages/react/src/types/nodes.ts +++ b/packages/react/src/types/nodes.ts @@ -1,12 +1,5 @@ import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react'; -import type { - CoordinateExtent, - NodeBase, - NodeOrigin, - OnError, - NodeProps as NodePropsBase, - InternalNodeBase, -} from '@xyflow/system'; +import type { CoordinateExtent, NodeBase, OnError, NodeProps as NodePropsBase, InternalNodeBase } from '@xyflow/system'; import { NodeTypes } from './general'; @@ -59,7 +52,6 @@ export type NodeWrapperProps = { disableKeyboardA11y: boolean; nodeTypes?: NodeTypes; nodeExtent?: CoordinateExtent; - nodeOrigin: NodeOrigin; onError?: OnError; }; diff --git a/packages/react/src/types/store.ts b/packages/react/src/types/store.ts index 7bf6e692..7ce5fabf 100644 --- a/packages/react/src/types/store.ts +++ b/packages/react/src/types/store.ts @@ -1,6 +1,6 @@ import { ConnectionMode, - type ConnectionStatus, + type ConnectionState, type CoordinateExtent, type InternalNodeUpdate, type UpdateNodePositions, @@ -10,9 +10,8 @@ import { type OnViewportChange, type SelectionRect, type SnapGrid, - type ConnectingHandle, + type Handle, type Transform, - type XYPosition, type PanZoomInstance, type PanBy, type OnConnectStart, @@ -25,8 +24,9 @@ import { type EdgeLookup, type ConnectionLookup, type NodeLookup, - NodeChange, - EdgeChange, + type NodeChange, + type EdgeChange, + type ParentLookup, } from '@xyflow/system'; import type { @@ -54,7 +54,7 @@ export type ReactFlowStore>; - parentLookup: Map[]>; + parentLookup: ParentLookup>; edges: Edge[]; edgeLookup: EdgeLookup; connectionLookup: ConnectionLookup; @@ -78,9 +78,9 @@ export type ReactFlowStore & Required>) | null; snapToGrid: boolean; snapGrid: SnapGrid; @@ -97,10 +97,6 @@ export type ReactFlowStore; onNodeDrag?: OnNodeDrag; onNodeDragStop?: OnNodeDrag; diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index a9c199c3..81de9ea1 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # @xyflow/svelte +## 0.1.8 + +- `useConnection` returns `ConnectionState` with `toNode` and `toHandle` +- node origin is part of position absolute +- refactor connection handling + ## 0.1.7 - revise selection usability (capture while dragging out of the flow) diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 86bd4daa..d2089204 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/svelte", - "version": "0.1.7", + "version": "0.1.8", "description": "Svelte Flow - A highly customizable Svelte library for building node-based editors, workflow systems, diagrams and more.", "keywords": [ "svelte", diff --git a/packages/svelte/src/lib/actions/drag/index.ts b/packages/svelte/src/lib/actions/drag/index.ts index 1405ccd9..f5649a29 100644 --- a/packages/svelte/src/lib/actions/drag/index.ts +++ b/packages/svelte/src/lib/actions/drag/index.ts @@ -34,7 +34,7 @@ export default function drag(domNode: Element, params: UseDragParams) { nodeExtent: get(store.nodeExtent), snapGrid: snapGrid ? snapGrid : [0, 0], snapToGrid: !!snapGrid, - nodeOrigin: [0, 0], + nodeOrigin: get(store.nodeOrigin), multiSelectionActive: get(store.multiselectionKeyPressed), domNode: get(store.domNode), transform: [vp.x, vp.y, vp.zoom], diff --git a/packages/svelte/src/lib/components/ConnectionLine/ConnectionLine.svelte b/packages/svelte/src/lib/components/ConnectionLine/ConnectionLine.svelte index 904a45d3..71e3f2c9 100644 --- a/packages/svelte/src/lib/components/ConnectionLine/ConnectionLine.svelte +++ b/packages/svelte/src/lib/components/ConnectionLine/ConnectionLine.svelte @@ -2,21 +2,59 @@ import cc from 'classcat'; import { useStore } from '$lib/store'; + import { + ConnectionLineType, + getBezierPath, + getConnectionStatus, + getSmoothStepPath, + getStraightPath + } from '@xyflow/system'; export let containerStyle: string = ''; export let style: string = ''; export let isCustomComponent: boolean = false; - const { width, height, connection } = useStore(); + const { width, height, connection, connectionLineType } = useStore(); + + let path: string | null = null; + + $: if ($connection.inProgress && !isCustomComponent) { + const { from, to, fromPosition, toPosition } = $connection; + const pathParams = { + sourceX: from.x, + sourceY: from.y, + sourcePosition: fromPosition, + targetX: to.x, + targetY: to.y, + targetPosition: toPosition + }; + + switch ($connectionLineType) { + case ConnectionLineType.Bezier: + [path] = getBezierPath(pathParams); + break; + case ConnectionLineType.Step: + [path] = getSmoothStepPath({ + ...pathParams, + borderRadius: 0 + }); + break; + case ConnectionLineType.SmoothStep: + [path] = getSmoothStepPath(pathParams); + break; + default: + [path] = getStraightPath(pathParams); + } + } -{#if $connection.path} +{#if $connection.inProgress} - + {#if !isCustomComponent} - + {/if} diff --git a/packages/svelte/src/lib/components/Handle/Handle.svelte b/packages/svelte/src/lib/components/Handle/Handle.svelte index 472ca2c2..90482aa1 100644 --- a/packages/svelte/src/lib/components/Handle/Handle.svelte +++ b/packages/svelte/src/lib/components/Handle/Handle.svelte @@ -103,7 +103,7 @@ $onConnectEndAction?.(event); }, getTransform: () => [$viewport.x, $viewport.y, $viewport.zoom], - getConnectionStartHandle: () => $connection.startHandle + getFromHandle: () => $connection.fromHandle }); } } @@ -128,21 +128,20 @@ prevConnections = connections ?? new Map(); } - $: connectionInProcess = !!$connection.startHandle; + $: connectionInProcess = !!$connection.fromHandle; $: connectingFrom = - $connection.startHandle?.nodeId === nodeId && - $connection.startHandle?.type === type && - $connection.startHandle?.handleId === handleId; + $connection.fromHandle?.nodeId === nodeId && + $connection.fromHandle?.type === type && + $connection.fromHandle?.id === handleId; $: connectingTo = - $connection.endHandle?.nodeId === nodeId && - $connection.endHandle?.type === type && - $connection.endHandle?.handleId === handleId; + $connection.toHandle?.nodeId === nodeId && + $connection.toHandle?.type === type && + $connection.toHandle?.id === handleId; $: isPossibleEndHandle = $connectionMode === ConnectionMode.Strict - ? $connection.startHandle?.type !== type - : nodeId !== $connection.startHandle?.nodeId || - handleId !== $connection.startHandle?.handleId; - $: valid = connectingTo && $connection.status === 'valid'; + ? $connection.fromHandle?.type !== type + : nodeId !== $connection.fromHandle?.nodeId || handleId !== $connection.fromHandle?.id; + $: valid = connectingTo && $connection.isValid;