Merge pull request #4432 from xyflow/fix/connectionprops

refactor(useConnection): return internal node, add node generic
This commit is contained in:
Moritz Klack
2024-07-10 14:14:44 +02:00
committed by GitHub
11 changed files with 55 additions and 46 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@xyflow/react': patch
'@xyflow/system': patch
---
refactor(useConnection): return internal node, add node generic
@@ -1,9 +1,8 @@
import { FC } from 'react'; import { getBezierPath, ConnectionLineComponentProps, InternalNode } from '@xyflow/react';
import { getBezierPath, ConnectionLineComponentProps, Node } from '@xyflow/react';
import { getEdgeParams } from './utils'; import { getEdgeParams } from './utils';
const FloatingConnectionLine: FC<ConnectionLineComponentProps> = ({ toX, toY, fromPosition, toPosition, fromNode }) => { function FloatingConnectionLine({ toX, toY, fromPosition, toPosition, fromNode }: ConnectionLineComponentProps) {
if (!fromNode) { if (!fromNode) {
return null; return null;
} }
@@ -13,7 +12,7 @@ const FloatingConnectionLine: FC<ConnectionLineComponentProps> = ({ toX, toY, fr
width: 1, width: 1,
height: 1, height: 1,
position: { x: toX, y: toY }, position: { x: toX, y: toY },
} as Node; } as InternalNode;
const { sx, sy } = getEdgeParams(fromNode, targetNode); const { sx, sy } = getEdgeParams(fromNode, targetNode);
@@ -32,6 +31,6 @@ const FloatingConnectionLine: FC<ConnectionLineComponentProps> = ({ toX, toY, fr
<circle cx={toX} cy={toY} fill="#fff" r={3} stroke="#222" strokeWidth={1.5} /> <circle cx={toX} cy={toY} fill="#fff" r={3} stroke="#222" strokeWidth={1.5} />
</g> </g>
); );
}; }
export default FloatingConnectionLine; export default FloatingConnectionLine;
@@ -1,9 +1,9 @@
import { FC, CSSProperties } from 'react'; import { CSSProperties } from 'react';
import { EdgeProps, useStore, getBezierPath } from '@xyflow/react'; import { EdgeProps, useStore, getBezierPath } from '@xyflow/react';
import { getEdgeParams } from './utils'; import { getEdgeParams } from './utils';
const FloatingEdge: FC<EdgeProps> = ({ id, source, target, style }) => { function FloatingEdge({ id, source, target, style }: EdgeProps) {
const { sourceNode, targetNode } = useStore((s) => { const { sourceNode, targetNode } = useStore((s) => {
const sourceNode = s.nodeLookup.get(source); const sourceNode = s.nodeLookup.get(source);
const targetNode = s.nodeLookup.get(target); const targetNode = s.nodeLookup.get(target);
@@ -31,6 +31,6 @@ const FloatingEdge: FC<EdgeProps> = ({ id, source, target, style }) => {
<path id={id} className="react-flow__edge-path" d={path} style={style as CSSProperties} /> <path id={id} className="react-flow__edge-path" d={path} style={style as CSSProperties} />
</g> </g>
); );
}; }
export default FloatingEdge; export default FloatingEdge;
@@ -25,7 +25,7 @@ const edgeTypes: EdgeTypes = {
floating: FloatingEdge, floating: FloatingEdge,
}; };
const FloatingEdges = () => { function FloatingEdges() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
@@ -49,6 +49,6 @@ const FloatingEdges = () => {
</ReactFlow> </ReactFlow>
</div> </div>
); );
}; }
export default FloatingEdges; export default FloatingEdges;
@@ -2,21 +2,19 @@ import { Position, XYPosition, Node, Edge, InternalNode } from '@xyflow/react';
// this helper function returns the intersection point // this helper function returns the intersection point
// of the line between the center of the intersectionNode and the target node // of the line between the center of the intersectionNode and the target node
function getNodeIntersection(intersectionNode: Node, targetNode: Node): XYPosition { function getNodeIntersection(intersectionNode: InternalNode, targetNode: InternalNode): XYPosition {
// https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a const { internals: intersectionInternals } = intersectionNode;
const { position: intersectionNodePosition } = intersectionNode;
const { width: intersectionNodeWidth, height: intersectionNodeHeight } = intersectionNode.measured ?? { const { width: intersectionNodeWidth, height: intersectionNodeHeight } = intersectionNode.measured ?? {
width: 0, width: 0,
height: 0, height: 0,
}; };
const targetPosition = targetNode.position; const targetPosition = targetNode.internals.positionAbsolute;
const w = (intersectionNodeWidth ?? 0) / 2; const w = (intersectionNodeWidth ?? 0) / 2;
const h = (intersectionNodeHeight ?? 0) / 2; const h = (intersectionNodeHeight ?? 0) / 2;
const x2 = intersectionNodePosition.x + w; const x2 = intersectionInternals.positionAbsolute.x + w;
const y2 = intersectionNodePosition.y + h; const y2 = intersectionInternals.positionAbsolute.y + h;
const x1 = targetPosition.x + w; const x1 = targetPosition.x + w;
const y1 = targetPosition.y + h; const y1 = targetPosition.y + h;
@@ -92,7 +90,13 @@ export function createElements(): NodesAndEdges {
const x = 250 * Math.cos(radians) + center.x; const x = 250 * Math.cos(radians) + center.x;
const y = 250 * Math.sin(radians) + center.y; const y = 250 * Math.sin(radians) + center.y;
nodes.push({ id: `${i}`, data: { label: 'Source' }, position: { x, y } }); const isChild = i === 1;
nodes.push({
id: `${i}`,
data: { label: 'Source' },
position: isChild ? { x: 0, y: 0 } : { x, y },
parentId: isChild ? '0' : undefined,
});
edges.push({ edges.push({
id: `edge-${i}`, id: `edge-${i}`,
@@ -40,7 +40,7 @@ const initialEdges: Edge[] = [
{ id: 'e1-3', source: '1', target: '3' }, { id: 'e1-3', source: '1', target: '3' },
]; ];
const UseZoomPanHelperFlow = () => { const UseConnectionFlow = () => {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
@@ -68,7 +68,7 @@ const UseZoomPanHelperFlow = () => {
const WrappedFlow = () => ( const WrappedFlow = () => (
<ReactFlowProvider> <ReactFlowProvider>
<UseZoomPanHelperFlow /> <UseConnectionFlow />
</ReactFlowProvider> </ReactFlowProvider>
); );
+6 -6
View File
@@ -1,10 +1,10 @@
import { shallow } from 'zustand/shallow'; import { shallow } from 'zustand/shallow';
import { useStore } from './useStore';
import type { ReactFlowStore } from '../types/store';
import { ConnectionState, pointToRendererPoint } from '@xyflow/system'; import { ConnectionState, pointToRendererPoint } from '@xyflow/system';
const selector = (s: ReactFlowStore): ConnectionState => { import { useStore } from './useStore';
import type { InternalNode, Node, ReactFlowStore } from '../types';
const selector = (s: ReactFlowStore) => {
return s.connection.inProgress return s.connection.inProgress
? { ...s.connection, to: pointToRendererPoint(s.connection.to, s.transform) } ? { ...s.connection, to: pointToRendererPoint(s.connection.to, s.transform) }
: { ...s.connection }; : { ...s.connection };
@@ -15,6 +15,6 @@ const selector = (s: ReactFlowStore): ConnectionState => {
* @public * @public
* @returns ConnectionState * @returns ConnectionState
*/ */
export function useConnection(): ConnectionState { export function useConnection<NodeType extends Node = Node>(): ConnectionState<InternalNode<NodeType>> {
return useStore(selector, shallow); return useStore(selector, shallow) as ConnectionState<InternalNode<NodeType>>;
} }
+7 -5
View File
@@ -16,7 +16,7 @@ import type {
OnError, OnError,
} from '@xyflow/system'; } from '@xyflow/system';
import { EdgeTypes, Node } from '.'; import { EdgeTypes, InternalNode, Node } from '.';
export type EdgeLabelOptions = { export type EdgeLabelOptions = {
label?: string | ReactNode; label?: string | ReactNode;
@@ -190,10 +190,10 @@ export type SimpleBezierEdgeProps = EdgeComponentProps;
export type OnReconnect<EdgeType extends Edge = Edge> = (oldEdge: EdgeType, newConnection: Connection) => void; export type OnReconnect<EdgeType extends Edge = Edge> = (oldEdge: EdgeType, newConnection: Connection) => void;
export type ConnectionLineComponentProps = { export type ConnectionLineComponentProps<NodeType extends Node = Node> = {
connectionLineStyle?: CSSProperties; connectionLineStyle?: CSSProperties;
connectionLineType: ConnectionLineType; connectionLineType: ConnectionLineType;
fromNode: Node; fromNode: InternalNode<NodeType>;
fromHandle: Handle; fromHandle: Handle;
fromX: number; fromX: number;
fromY: number; fromY: number;
@@ -202,8 +202,10 @@ export type ConnectionLineComponentProps = {
fromPosition: Position; fromPosition: Position;
toPosition: Position; toPosition: Position;
connectionStatus: 'valid' | 'invalid' | null; connectionStatus: 'valid' | 'invalid' | null;
toNode: Node | null; toNode: InternalNode<NodeType> | null;
toHandle: Handle | null; toHandle: Handle | null;
}; };
export type ConnectionLineComponent = ComponentType<ConnectionLineComponentProps>; export type ConnectionLineComponent<NodeType extends Node = Node> = ComponentType<
ConnectionLineComponentProps<NodeType>
>;
+2 -2
View File
@@ -78,7 +78,7 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
userSelectionActive: boolean; userSelectionActive: boolean;
userSelectionRect: SelectionRect | null; userSelectionRect: SelectionRect | null;
connection: ConnectionState; connection: ConnectionState<InternalNode<NodeType>>;
connectionMode: ConnectionMode; connectionMode: ConnectionMode;
connectionClickStartHandle: (Pick<Handle, 'nodeId' | 'id'> & Required<Pick<Handle, 'type'>>) | null; connectionClickStartHandle: (Pick<Handle, 'nodeId' | 'id'> & Required<Pick<Handle, 'type'>>) | null;
@@ -163,7 +163,7 @@ export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
setTranslateExtent: (translateExtent: CoordinateExtent) => void; setTranslateExtent: (translateExtent: CoordinateExtent) => void;
setNodeExtent: (nodeExtent: CoordinateExtent) => void; setNodeExtent: (nodeExtent: CoordinateExtent) => void;
cancelConnection: () => void; cancelConnection: () => void;
updateConnection: UpdateConnection; updateConnection: UpdateConnection<InternalNode<NodeType>>;
reset: () => void; reset: () => void;
triggerNodeChanges: (changes: NodeChange<NodeType>[]) => void; triggerNodeChanges: (changes: NodeChange<NodeType>[]) => void;
triggerEdgeChanges: (changes: EdgeChange<EdgeType>[]) => void; triggerEdgeChanges: (changes: EdgeChange<EdgeType>[]) => void;
+9 -11
View File
@@ -148,36 +148,34 @@ export const initialConnection: NoConnection = {
export type NoConnection = { export type NoConnection = {
inProgress: false; inProgress: false;
isValid: null; isValid: null;
from: null; from: null;
fromHandle: null; fromHandle: null;
fromPosition: null; fromPosition: null;
fromNode: null; fromNode: null;
to: null; to: null;
toHandle: null; toHandle: null;
toPosition: null; toPosition: null;
toNode: null; toNode: null;
}; };
export type ConnectionInProgress<NodeType extends InternalNodeBase = InternalNodeBase> = {
export type ConnectionInProgress = {
inProgress: true; inProgress: true;
isValid: boolean | null; isValid: boolean | null;
from: XYPosition; from: XYPosition;
fromHandle: Handle; fromHandle: Handle;
fromPosition: Position; fromPosition: Position;
fromNode: NodeBase; fromNode: NodeType;
to: XYPosition; to: XYPosition;
toHandle: Handle | null; toHandle: Handle | null;
toPosition: Position; toPosition: Position;
toNode: NodeBase | null; toNode: NodeType | null;
}; };
export type ConnectionState<NodeType extends InternalNodeBase = InternalNodeBase> =
| ConnectionInProgress<NodeType>
| NoConnection;
export type ConnectionState = ConnectionInProgress | NoConnection; export type UpdateConnection<NodeType extends InternalNodeBase = InternalNodeBase> = (
params: ConnectionState<NodeType>
export type UpdateConnection = (params: ConnectionState) => void; ) => void;
export type ColorModeClass = 'light' | 'dark'; export type ColorModeClass = 'light' | 'dark';
export type ColorMode = ColorModeClass | 'system'; export type ColorMode = ColorModeClass | 'system';
+2 -2
View File
@@ -104,7 +104,7 @@ function onPointerDown(
from, from,
fromHandle, fromHandle,
fromPosition: fromHandle.position, fromPosition: fromHandle.position,
fromNode: fromNodeInternal.internals.userNode, fromNode: fromNodeInternal,
to: position, to: position,
toHandle: null, toHandle: null,
@@ -163,7 +163,7 @@ function onPointerDown(
: position, : position,
toHandle: result.toHandle, toHandle: result.toHandle,
toPosition: isValid && result.toHandle ? result.toHandle.position : oppositePosition[fromHandle.position], toPosition: isValid && result.toHandle ? result.toHandle.position : oppositePosition[fromHandle.position],
toNode: result.toHandle ? nodeLookup.get(result.toHandle.nodeId)!.internals.userNode : null, toNode: result.toHandle ? nodeLookup.get(result.toHandle.nodeId)! : null,
}; };
// we don't want to trigger an update when the connection // we don't want to trigger an update when the connection