Merge pull request #4395 from xyflow/enhance-connection
Enhance connection
This commit is contained in:
@@ -7,7 +7,6 @@ import {
|
|||||||
ConnectionMode,
|
ConnectionMode,
|
||||||
getBezierPath,
|
getBezierPath,
|
||||||
getSmoothStepPath,
|
getSmoothStepPath,
|
||||||
type ConnectionStatus,
|
|
||||||
type HandleType,
|
type HandleType,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
@@ -15,13 +14,21 @@ import { useStore } from '../../hooks/useStore';
|
|||||||
import { getSimpleBezierPath } from '../Edges/SimpleBezierEdge';
|
import { getSimpleBezierPath } from '../Edges/SimpleBezierEdge';
|
||||||
import type { ConnectionLineComponent, ReactFlowState, ReactFlowStore } from '../../types';
|
import type { ConnectionLineComponent, ReactFlowState, ReactFlowStore } from '../../types';
|
||||||
|
|
||||||
|
function getConnectionStatus(isValid: boolean | null) {
|
||||||
|
if (isValid === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid ? 'valid' : 'invalid';
|
||||||
|
}
|
||||||
|
|
||||||
type ConnectionLineProps = {
|
type ConnectionLineProps = {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
handleType: HandleType;
|
handleType: HandleType;
|
||||||
type: ConnectionLineType;
|
type: ConnectionLineType;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
CustomComponent?: ConnectionLineComponent;
|
CustomComponent?: ConnectionLineComponent;
|
||||||
connectionStatus: ConnectionStatus | null;
|
isValid: boolean | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const oppositePosition = {
|
const oppositePosition = {
|
||||||
@@ -37,18 +44,17 @@ const ConnectionLine = ({
|
|||||||
style,
|
style,
|
||||||
type = ConnectionLineType.Bezier,
|
type = ConnectionLineType.Bezier,
|
||||||
CustomComponent,
|
CustomComponent,
|
||||||
connectionStatus,
|
isValid,
|
||||||
}: ConnectionLineProps) => {
|
}: ConnectionLineProps) => {
|
||||||
const { fromNode, handleId, toX, toY, connectionMode, endPosition, isValid } = useStore(
|
const { fromNode, startHandle, endHandle, toX, toY, connectionMode } = useStore(
|
||||||
useCallback(
|
useCallback(
|
||||||
(s: ReactFlowStore) => ({
|
(s: ReactFlowStore) => ({
|
||||||
fromNode: s.nodeLookup.get(nodeId),
|
fromNode: s.nodeLookup.get(nodeId),
|
||||||
handleId: s.connectionStartHandle?.handleId,
|
startHandle: s.connection.fromHandle,
|
||||||
toX: (s.connectionPosition.x - s.transform[0]) / s.transform[2],
|
endHandle: s.connection.toHandle,
|
||||||
toY: (s.connectionPosition.y - s.transform[1]) / s.transform[2],
|
toX: (s.connection.position.x - s.transform[0]) / s.transform[2],
|
||||||
|
toY: (s.connection.position.y - s.transform[1]) / s.transform[2],
|
||||||
connectionMode: s.connectionMode,
|
connectionMode: s.connectionMode,
|
||||||
endPosition: s.connectionEndHandle?.position,
|
|
||||||
isValid: s.connectionStatus === 'valid',
|
|
||||||
}),
|
}),
|
||||||
[nodeId]
|
[nodeId]
|
||||||
),
|
),
|
||||||
@@ -66,13 +72,15 @@ const ConnectionLine = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleId = startHandle?.handleId;
|
||||||
const fromHandle = handleId ? handleBounds.find((d) => d.id === handleId) : handleBounds[0];
|
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 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 fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode.measured.height ?? 0;
|
||||||
const fromX = fromNode.internals.positionAbsolute.x + fromHandleX;
|
const fromX = fromNode.internals.positionAbsolute.x + fromHandleX;
|
||||||
const fromY = fromNode.internals.positionAbsolute.y + fromHandleY;
|
const fromY = fromNode.internals.positionAbsolute.y + fromHandleY;
|
||||||
const fromPosition = fromHandle?.position;
|
const fromPosition = fromHandle?.position;
|
||||||
const toPosition = isValid && endPosition ? endPosition : fromPosition ? oppositePosition[fromPosition] : null;
|
const toPosition =
|
||||||
|
isValid && endHandle?.position ? endHandle.position : fromPosition ? oppositePosition[fromPosition] : null;
|
||||||
|
|
||||||
if (!fromPosition || !toPosition) {
|
if (!fromPosition || !toPosition) {
|
||||||
return null;
|
return null;
|
||||||
@@ -83,7 +91,7 @@ const ConnectionLine = ({
|
|||||||
<CustomComponent
|
<CustomComponent
|
||||||
connectionLineType={type}
|
connectionLineType={type}
|
||||||
connectionLineStyle={style}
|
connectionLineStyle={style}
|
||||||
fromNode={fromNode}
|
fromNode={fromNode.internals.userNode}
|
||||||
fromHandle={fromHandle}
|
fromHandle={fromHandle}
|
||||||
fromX={fromX}
|
fromX={fromX}
|
||||||
fromY={fromY}
|
fromY={fromY}
|
||||||
@@ -91,7 +99,7 @@ const ConnectionLine = ({
|
|||||||
toY={toY}
|
toY={toY}
|
||||||
fromPosition={fromPosition}
|
fromPosition={fromPosition}
|
||||||
toPosition={toPosition}
|
toPosition={toPosition}
|
||||||
connectionStatus={connectionStatus}
|
connectionStatus={getConnectionStatus(isValid)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -136,19 +144,19 @@ type ConnectionLineWrapperProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const selector = (s: ReactFlowState) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
nodeId: s.connectionStartHandle?.nodeId,
|
nodeId: s.connection.fromHandle?.nodeId,
|
||||||
handleType: s.connectionStartHandle?.type,
|
handleType: s.connection.fromHandle?.type,
|
||||||
nodesConnectable: s.nodesConnectable,
|
nodesConnectable: s.nodesConnectable,
|
||||||
connectionStatus: s.connectionStatus,
|
isValid: s.connection.isValid,
|
||||||
width: s.width,
|
width: s.width,
|
||||||
height: s.height,
|
height: s.height,
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ConnectionLineWrapper({ containerStyle, style, type, component }: ConnectionLineWrapperProps) {
|
export function ConnectionLineWrapper({ containerStyle, style, type, component }: ConnectionLineWrapperProps) {
|
||||||
const { nodeId, handleType, nodesConnectable, width, height, connectionStatus } = useStore(selector, shallow);
|
const { nodeId, handleType, nodesConnectable, width, height, isValid } = useStore(selector, shallow);
|
||||||
const isValid = !!(nodeId && handleType && width && nodesConnectable);
|
const renderConnectionLine = !!(nodeId && handleType && width && nodesConnectable);
|
||||||
|
|
||||||
if (!isValid) {
|
if (!renderConnectionLine) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,14 +167,14 @@ export function ConnectionLineWrapper({ containerStyle, style, type, component }
|
|||||||
height={height}
|
height={height}
|
||||||
className="react-flow__connectionline react-flow__container"
|
className="react-flow__connectionline react-flow__container"
|
||||||
>
|
>
|
||||||
<g className={cc(['react-flow__connection', connectionStatus])}>
|
<g className={cc(['react-flow__connection', getConnectionStatus(isValid)])}>
|
||||||
<ConnectionLine
|
<ConnectionLine
|
||||||
nodeId={nodeId}
|
nodeId={nodeId}
|
||||||
handleType={handleType}
|
handleType={handleType}
|
||||||
style={style}
|
style={style}
|
||||||
type={type}
|
type={type}
|
||||||
CustomComponent={component}
|
CustomComponent={component}
|
||||||
connectionStatus={connectionStatus}
|
isValid={isValid}
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export function EdgeUpdateAnchors<EdgeType extends Edge = Edge>({
|
|||||||
onReconnectEnd: _onReconnectEnd,
|
onReconnectEnd: _onReconnectEnd,
|
||||||
updateConnection,
|
updateConnection,
|
||||||
getTransform: () => store.getState().transform,
|
getTransform: () => store.getState().transform,
|
||||||
getConnectionStartHandle: () => store.getState().connectionStartHandle,
|
getFromHandle: () => store.getState().connection.fromHandle,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -39,28 +39,23 @@ const selector = (s: ReactFlowState) => ({
|
|||||||
|
|
||||||
const connectingSelector =
|
const connectingSelector =
|
||||||
(nodeId: string | null, handleId: string | null, type: HandleType) => (state: ReactFlowState) => {
|
(nodeId: string | null, handleId: string | null, type: HandleType) => (state: ReactFlowState) => {
|
||||||
const {
|
const { connectionClickStartHandle: clickHandle, connectionMode, connection } = state;
|
||||||
connectionStartHandle: startHandle,
|
|
||||||
connectionEndHandle: endHandle,
|
|
||||||
connectionClickStartHandle: clickHandle,
|
|
||||||
connectionMode,
|
|
||||||
connectionStatus,
|
|
||||||
} = state;
|
|
||||||
|
|
||||||
const connectingTo = endHandle?.nodeId === nodeId && endHandle?.handleId === handleId && endHandle?.type === type;
|
const { fromHandle, toHandle, isValid } = connection;
|
||||||
|
|
||||||
|
const connectingTo = toHandle?.nodeId === nodeId && toHandle?.handleId === handleId && toHandle?.type === type;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
connectingFrom:
|
connectingFrom: fromHandle?.nodeId === nodeId && fromHandle?.handleId === handleId && fromHandle?.type === type,
|
||||||
startHandle?.nodeId === nodeId && startHandle?.handleId === handleId && startHandle?.type === type,
|
|
||||||
connectingTo,
|
connectingTo,
|
||||||
clickConnecting:
|
clickConnecting:
|
||||||
clickHandle?.nodeId === nodeId && clickHandle?.handleId === handleId && clickHandle?.type === type,
|
clickHandle?.nodeId === nodeId && clickHandle?.handleId === handleId && clickHandle?.type === type,
|
||||||
isPossibleEndHandle:
|
isPossibleEndHandle:
|
||||||
connectionMode === ConnectionMode.Strict
|
connectionMode === ConnectionMode.Strict
|
||||||
? startHandle?.type !== type
|
? fromHandle?.type !== type
|
||||||
: nodeId !== startHandle?.nodeId || handleId !== startHandle?.handleId,
|
: nodeId !== fromHandle?.nodeId || handleId !== fromHandle?.handleId,
|
||||||
connectionInProcess: !!startHandle,
|
connectionInProcess: !!fromHandle,
|
||||||
valid: connectingTo && connectionStatus === 'valid',
|
valid: connectingTo && isValid,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -144,7 +139,7 @@ function HandleComponent(
|
|||||||
onConnect: onConnectExtended,
|
onConnect: onConnectExtended,
|
||||||
isValidConnection: isValidConnection || currentStore.isValidConnection,
|
isValidConnection: isValidConnection || currentStore.isValidConnection,
|
||||||
getTransform: () => store.getState().transform,
|
getTransform: () => store.getState().transform,
|
||||||
getConnectionStartHandle: () => store.getState().connectionStartHandle,
|
getFromHandle: () => store.getState().connection.fromHandle,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,33 +2,19 @@ import { shallow } from 'zustand/shallow';
|
|||||||
|
|
||||||
import { useStore } from './useStore';
|
import { useStore } from './useStore';
|
||||||
import type { ReactFlowStore } from '../types/store';
|
import type { ReactFlowStore } from '../types/store';
|
||||||
|
import { ConnectionState } from '@xyflow/system';
|
||||||
|
|
||||||
const selector = (s: ReactFlowStore) => ({
|
const selector = (s: ReactFlowStore) => ({
|
||||||
startHandle: s.connectionStartHandle,
|
...s.connection,
|
||||||
endHandle: s.connectionEndHandle,
|
inProgress: s.connection.fromHandle !== null,
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook for accessing the ongoing connection.
|
* Hook for accessing the ongoing connection.
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
* @returns ongoing connection
|
* @returns ongoing connection
|
||||||
*/
|
*/
|
||||||
export function useConnection(): UseConnectionResult {
|
export function useConnection(): ConnectionState & { inProgress: boolean } {
|
||||||
const ongoingConnection = useStore(selector, shallow);
|
return useStore(selector, shallow);
|
||||||
|
|
||||||
return ongoingConnection;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ export {
|
|||||||
type OnMoveStart,
|
type OnMoveStart,
|
||||||
type OnMoveEnd,
|
type OnMoveEnd,
|
||||||
type Connection,
|
type Connection,
|
||||||
type ConnectionStatus,
|
|
||||||
ConnectionMode,
|
ConnectionMode,
|
||||||
type OnConnectStartParams,
|
type OnConnectStartParams,
|
||||||
type OnConnectStart,
|
type OnConnectStart,
|
||||||
|
|||||||
@@ -307,18 +307,25 @@ const createStore = ({
|
|||||||
options
|
options
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
cancelConnection: () =>
|
cancelConnection: () => {
|
||||||
|
const { connection } = get();
|
||||||
set({
|
set({
|
||||||
connectionStatus: null,
|
connection: {
|
||||||
connectionStartHandle: null,
|
position: connection.position,
|
||||||
connectionEndHandle: null,
|
fromHandle: null,
|
||||||
}),
|
toHandle: null,
|
||||||
|
isValid: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
updateConnection: (params) => {
|
updateConnection: (params) => {
|
||||||
const { connectionPosition } = get();
|
const { connection } = get();
|
||||||
|
|
||||||
const currentConnection = {
|
const currentConnection = {
|
||||||
...params,
|
connection: {
|
||||||
connectionPosition: params.connectionPosition ?? connectionPosition,
|
...params,
|
||||||
|
position: params.position ?? connection.position,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
set(currentConnection);
|
set(currentConnection);
|
||||||
|
|||||||
@@ -76,8 +76,6 @@ const getInitialState = ({
|
|||||||
nodesSelectionActive: false,
|
nodesSelectionActive: false,
|
||||||
userSelectionActive: false,
|
userSelectionActive: false,
|
||||||
userSelectionRect: null,
|
userSelectionRect: null,
|
||||||
connectionPosition: { x: 0, y: 0 },
|
|
||||||
connectionStatus: null,
|
|
||||||
connectionMode: ConnectionMode.Strict,
|
connectionMode: ConnectionMode.Strict,
|
||||||
domNode: null,
|
domNode: null,
|
||||||
paneDragging: false,
|
paneDragging: false,
|
||||||
@@ -103,8 +101,12 @@ const getInitialState = ({
|
|||||||
|
|
||||||
multiSelectionActive: false,
|
multiSelectionActive: false,
|
||||||
|
|
||||||
connectionStartHandle: null,
|
connection: {
|
||||||
connectionEndHandle: null,
|
fromHandle: null,
|
||||||
|
toHandle: null,
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
isValid: null,
|
||||||
|
},
|
||||||
connectionClickStartHandle: null,
|
connectionClickStartHandle: null,
|
||||||
connectOnClick: true,
|
connectOnClick: true,
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import type {
|
|||||||
Connection,
|
Connection,
|
||||||
ConnectionLineType,
|
ConnectionLineType,
|
||||||
HandleElement,
|
HandleElement,
|
||||||
ConnectionStatus,
|
|
||||||
EdgePosition,
|
EdgePosition,
|
||||||
StepPathOptions,
|
StepPathOptions,
|
||||||
OnError,
|
OnError,
|
||||||
@@ -202,7 +201,7 @@ export type ConnectionLineComponentProps = {
|
|||||||
toY: number;
|
toY: number;
|
||||||
fromPosition: Position;
|
fromPosition: Position;
|
||||||
toPosition: Position;
|
toPosition: Position;
|
||||||
connectionStatus: ConnectionStatus | null;
|
connectionStatus: 'valid' | 'invalid' | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConnectionLineComponent = ComponentType<ConnectionLineComponentProps>;
|
export type ConnectionLineComponent = ComponentType<ConnectionLineComponentProps>;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
ConnectionMode,
|
ConnectionMode,
|
||||||
type ConnectionStatus,
|
type ConnectionState,
|
||||||
type CoordinateExtent,
|
type CoordinateExtent,
|
||||||
type InternalNodeUpdate,
|
type InternalNodeUpdate,
|
||||||
type UpdateNodePositions,
|
type UpdateNodePositions,
|
||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
type SnapGrid,
|
type SnapGrid,
|
||||||
type ConnectingHandle,
|
type ConnectingHandle,
|
||||||
type Transform,
|
type Transform,
|
||||||
type XYPosition,
|
|
||||||
type PanZoomInstance,
|
type PanZoomInstance,
|
||||||
type PanBy,
|
type PanBy,
|
||||||
type OnConnectStart,
|
type OnConnectStart,
|
||||||
@@ -25,8 +24,8 @@ import {
|
|||||||
type EdgeLookup,
|
type EdgeLookup,
|
||||||
type ConnectionLookup,
|
type ConnectionLookup,
|
||||||
type NodeLookup,
|
type NodeLookup,
|
||||||
NodeChange,
|
type NodeChange,
|
||||||
EdgeChange,
|
type EdgeChange,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -78,9 +77,9 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
|
|||||||
userSelectionActive: boolean;
|
userSelectionActive: boolean;
|
||||||
userSelectionRect: SelectionRect | null;
|
userSelectionRect: SelectionRect | null;
|
||||||
|
|
||||||
connectionPosition: XYPosition;
|
connection: ConnectionState;
|
||||||
connectionStatus: ConnectionStatus | null;
|
|
||||||
connectionMode: ConnectionMode;
|
connectionMode: ConnectionMode;
|
||||||
|
connectionClickStartHandle: ConnectingHandle | null;
|
||||||
|
|
||||||
snapToGrid: boolean;
|
snapToGrid: boolean;
|
||||||
snapGrid: SnapGrid;
|
snapGrid: SnapGrid;
|
||||||
@@ -97,10 +96,6 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
|
|||||||
|
|
||||||
multiSelectionActive: boolean;
|
multiSelectionActive: boolean;
|
||||||
|
|
||||||
connectionStartHandle: ConnectingHandle | null;
|
|
||||||
connectionEndHandle: ConnectingHandle | null;
|
|
||||||
connectionClickStartHandle: ConnectingHandle | null;
|
|
||||||
|
|
||||||
onNodeDragStart?: OnNodeDrag<NodeType>;
|
onNodeDragStart?: OnNodeDrag<NodeType>;
|
||||||
onNodeDrag?: OnNodeDrag<NodeType>;
|
onNodeDrag?: OnNodeDrag<NodeType>;
|
||||||
onNodeDragStop?: OnNodeDrag<NodeType>;
|
onNodeDragStop?: OnNodeDrag<NodeType>;
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
$onConnectEndAction?.(event);
|
$onConnectEndAction?.(event);
|
||||||
},
|
},
|
||||||
getTransform: () => [$viewport.x, $viewport.y, $viewport.zoom],
|
getTransform: () => [$viewport.x, $viewport.y, $viewport.zoom],
|
||||||
getConnectionStartHandle: () => $connection.startHandle
|
getFromHandle: () => $connection.startHandle
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ export {
|
|||||||
type OnMoveStart,
|
type OnMoveStart,
|
||||||
type OnMoveEnd,
|
type OnMoveEnd,
|
||||||
type Connection,
|
type Connection,
|
||||||
type ConnectionStatus,
|
|
||||||
ConnectionMode,
|
ConnectionMode,
|
||||||
type OnConnectStartParams,
|
type OnConnectStartParams,
|
||||||
type OnConnectStart,
|
type OnConnectStart,
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import {
|
|||||||
type XYPosition,
|
type XYPosition,
|
||||||
type CoordinateExtent,
|
type CoordinateExtent,
|
||||||
type UpdateConnection,
|
type UpdateConnection,
|
||||||
errorMessages
|
errorMessages,
|
||||||
|
type ConnectionState
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions, ConnectionData } from '$lib/types';
|
import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions, ConnectionData } from '$lib/types';
|
||||||
@@ -340,12 +341,17 @@ export function createStore({
|
|||||||
// by creating an internal, unexposed store and using a derived store
|
// by creating an internal, unexposed store and using a derived store
|
||||||
// we prevent using slow get() calls
|
// we prevent using slow get() calls
|
||||||
const currentConnection = writable<ConnectionData>(initConnectionUpdateData);
|
const currentConnection = writable<ConnectionData>(initConnectionUpdateData);
|
||||||
const updateConnection: UpdateConnection = (newConnection: ConnectionData) => {
|
const updateConnection: UpdateConnection = (newConnection: ConnectionState) => {
|
||||||
currentConnection.set(newConnection);
|
currentConnection.set({
|
||||||
|
connectionStartHandle: newConnection.fromHandle,
|
||||||
|
connectionEndHandle: newConnection.toHandle,
|
||||||
|
connectionPosition: newConnection.position,
|
||||||
|
connectionStatus: newConnection.isValid ? 'valid' : 'invalid'
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function cancelConnection() {
|
function cancelConnection() {
|
||||||
updateConnection(initConnectionUpdateData);
|
currentConnection.set(initConnectionUpdateData);
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
|
|||||||
@@ -37,8 +37,6 @@ export type HandleConnection = Connection & {
|
|||||||
edgeId: string;
|
edgeId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConnectionStatus = 'valid' | 'invalid';
|
|
||||||
|
|
||||||
export enum ConnectionMode {
|
export enum ConnectionMode {
|
||||||
Strict = 'strict',
|
Strict = 'strict',
|
||||||
Loose = 'loose',
|
Loose = 'loose',
|
||||||
@@ -135,12 +133,23 @@ export type OnError = (id: string, message: string) => void;
|
|||||||
export type UpdateNodePositions = (dragItems: Map<string, NodeDragItem | InternalNodeBase>, dragging?: boolean) => void;
|
export type UpdateNodePositions = (dragItems: Map<string, NodeDragItem | InternalNodeBase>, dragging?: boolean) => void;
|
||||||
export type PanBy = (delta: XYPosition) => boolean;
|
export type PanBy = (delta: XYPosition) => boolean;
|
||||||
|
|
||||||
export type UpdateConnection = (params: {
|
export type NoConnectionInProgress = {
|
||||||
connectionPosition: XYPosition | null;
|
position: XYPosition;
|
||||||
connectionStatus: ConnectionStatus | null;
|
isValid: null;
|
||||||
connectionStartHandle: ConnectingHandle | null;
|
fromHandle: null;
|
||||||
connectionEndHandle: ConnectingHandle | null;
|
toHandle: null;
|
||||||
}) => void;
|
};
|
||||||
|
|
||||||
|
export type ConnectionInProgress = {
|
||||||
|
position: XYPosition;
|
||||||
|
isValid: boolean | null;
|
||||||
|
fromHandle: ConnectingHandle;
|
||||||
|
toHandle: ConnectingHandle | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ConnectionState = ConnectionInProgress | NoConnectionInProgress;
|
||||||
|
|
||||||
|
export type UpdateConnection = (params: ConnectionState) => void;
|
||||||
|
|
||||||
export type ColorModeClass = 'light' | 'dark';
|
export type ColorModeClass = 'light' | 'dark';
|
||||||
export type ColorMode = ColorModeClass | 'system';
|
export type ColorMode = ColorModeClass | 'system';
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
Position,
|
Position,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
import { getClosestHandle, getConnectionStatus, getHandleLookup, getHandleType } from './utils';
|
import { getClosestHandle, isConnectionValid, getHandleLookup, getHandleType } from './utils';
|
||||||
|
|
||||||
export type OnPointerDownParams = {
|
export type OnPointerDownParams = {
|
||||||
autoPanOnConnect: boolean;
|
autoPanOnConnect: boolean;
|
||||||
@@ -39,7 +39,7 @@ export type OnPointerDownParams = {
|
|||||||
isValidConnection?: IsValidConnection;
|
isValidConnection?: IsValidConnection;
|
||||||
onReconnectEnd?: (evt: MouseEvent | TouchEvent) => void;
|
onReconnectEnd?: (evt: MouseEvent | TouchEvent) => void;
|
||||||
getTransform: () => Transform;
|
getTransform: () => Transform;
|
||||||
getConnectionStartHandle: () => ConnectingHandle | null;
|
getFromHandle: () => ConnectingHandle | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IsValidParams = {
|
export type IsValidParams = {
|
||||||
@@ -63,12 +63,12 @@ type Result = {
|
|||||||
handleDomNode: Element | null;
|
handleDomNode: Element | null;
|
||||||
isValid: boolean;
|
isValid: boolean;
|
||||||
connection: Connection | null;
|
connection: Connection | null;
|
||||||
endHandle: ConnectingHandle | null;
|
toHandle: ConnectingHandle | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const alwaysValid = () => true;
|
const alwaysValid = () => true;
|
||||||
|
|
||||||
let connectionStartHandle: ConnectingHandle | null = null;
|
let fromHandle: ConnectingHandle | null = null;
|
||||||
|
|
||||||
function onPointerDown(
|
function onPointerDown(
|
||||||
event: MouseEvent | TouchEvent,
|
event: MouseEvent | TouchEvent,
|
||||||
@@ -93,7 +93,7 @@ function onPointerDown(
|
|||||||
onReconnectEnd,
|
onReconnectEnd,
|
||||||
updateConnection,
|
updateConnection,
|
||||||
getTransform,
|
getTransform,
|
||||||
getConnectionStartHandle,
|
getFromHandle,
|
||||||
}: OnPointerDownParams
|
}: OnPointerDownParams
|
||||||
) {
|
) {
|
||||||
// when xyflow is used inside a shadow root we can't use document
|
// when xyflow is used inside a shadow root we can't use document
|
||||||
@@ -110,7 +110,7 @@ function onPointerDown(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let connectionPosition = getEventPosition(event, containerBounds);
|
let position = getEventPosition(event, containerBounds);
|
||||||
let autoPanStarted = false;
|
let autoPanStarted = false;
|
||||||
let connection: Connection | null = null;
|
let connection: Connection | null = null;
|
||||||
let isValid = false;
|
let isValid = false;
|
||||||
@@ -128,38 +128,39 @@ function onPointerDown(
|
|||||||
if (!autoPanOnConnect || !containerBounds) {
|
if (!autoPanOnConnect || !containerBounds) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const [x, y] = calcAutoPan(connectionPosition, containerBounds);
|
const [x, y] = calcAutoPan(position, containerBounds);
|
||||||
|
|
||||||
panBy({ x, y });
|
panBy({ x, y });
|
||||||
autoPanId = requestAnimationFrame(autoPan);
|
autoPanId = requestAnimationFrame(autoPan);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stays the same for all consecutive pointermove events
|
// Stays the same for all consecutive pointermove events
|
||||||
connectionStartHandle = {
|
fromHandle = {
|
||||||
nodeId,
|
nodeId,
|
||||||
handleId,
|
handleId,
|
||||||
type: handleType,
|
type: handleType,
|
||||||
position: (clickedHandle?.getAttribute('data-handlepos') as Position) || Position.Top,
|
position: (clickedHandle?.getAttribute('data-handlepos') as Position) ?? Position.Top,
|
||||||
};
|
};
|
||||||
|
|
||||||
updateConnection({
|
updateConnection({
|
||||||
connectionPosition,
|
position,
|
||||||
connectionStatus: null,
|
isValid: null,
|
||||||
connectionStartHandle,
|
fromHandle,
|
||||||
connectionEndHandle: null,
|
toHandle: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
onConnectStart?.(event, { nodeId, handleId, handleType });
|
onConnectStart?.(event, { nodeId, handleId, handleType });
|
||||||
|
|
||||||
function onPointerMove(event: MouseEvent | TouchEvent) {
|
function onPointerMove(event: MouseEvent | TouchEvent) {
|
||||||
if (!getConnectionStartHandle()) {
|
if (!getFromHandle() || !fromHandle) {
|
||||||
onPointerUp(event);
|
onPointerUp(event);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const transform = getTransform();
|
const transform = getTransform();
|
||||||
connectionPosition = getEventPosition(event, containerBounds);
|
position = getEventPosition(event, containerBounds);
|
||||||
closestHandle = getClosestHandle(
|
closestHandle = getClosestHandle(
|
||||||
pointToRendererPoint(connectionPosition, transform, false, [1, 1]),
|
pointToRendererPoint(position, transform, false, [1, 1]),
|
||||||
connectionRadius,
|
connectionRadius,
|
||||||
handleLookup
|
handleLookup
|
||||||
);
|
);
|
||||||
@@ -186,8 +187,8 @@ function onPointerDown(
|
|||||||
isValid = result.isValid;
|
isValid = result.isValid;
|
||||||
|
|
||||||
updateConnection({
|
updateConnection({
|
||||||
connectionStartHandle,
|
fromHandle,
|
||||||
connectionPosition:
|
position:
|
||||||
closestHandle && isValid
|
closestHandle && isValid
|
||||||
? rendererPointToPoint(
|
? rendererPointToPoint(
|
||||||
{
|
{
|
||||||
@@ -196,9 +197,9 @@ function onPointerDown(
|
|||||||
},
|
},
|
||||||
transform
|
transform
|
||||||
)
|
)
|
||||||
: connectionPosition,
|
: position,
|
||||||
connectionStatus: getConnectionStatus(!!closestHandle, isValid),
|
isValid: isConnectionValid(!!closestHandle, isValid),
|
||||||
connectionEndHandle: result.endHandle,
|
toHandle: result.toHandle,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,7 +222,7 @@ function onPointerDown(
|
|||||||
isValid = false;
|
isValid = false;
|
||||||
connection = null;
|
connection = null;
|
||||||
handleDomNode = null;
|
handleDomNode = null;
|
||||||
connectionStartHandle = null;
|
fromHandle = null;
|
||||||
|
|
||||||
doc.removeEventListener('mousemove', onPointerMove as EventListener);
|
doc.removeEventListener('mousemove', onPointerMove as EventListener);
|
||||||
doc.removeEventListener('mouseup', onPointerUp as EventListener);
|
doc.removeEventListener('mouseup', onPointerUp as EventListener);
|
||||||
@@ -267,7 +268,7 @@ function isValidHandle(
|
|||||||
handleDomNode: handleToCheck,
|
handleDomNode: handleToCheck,
|
||||||
isValid: false,
|
isValid: false,
|
||||||
connection: null,
|
connection: null,
|
||||||
endHandle: null,
|
toHandle: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (handleToCheck) {
|
if (handleToCheck) {
|
||||||
@@ -300,7 +301,7 @@ function isValidHandle(
|
|||||||
|
|
||||||
result.isValid = isValid && isValidConnection(connection);
|
result.isValid = isValid && isValidConnection(connection);
|
||||||
|
|
||||||
result.endHandle = {
|
result.toHandle = {
|
||||||
nodeId: handleNodeId as string,
|
nodeId: handleNodeId as string,
|
||||||
handleId,
|
handleId,
|
||||||
type: handleType as HandleType,
|
type: handleType as HandleType,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { getHandlePosition } from '../utils';
|
import { getHandlePosition } from '../utils';
|
||||||
import {
|
import {
|
||||||
ConnectionStatus,
|
|
||||||
type HandleType,
|
type HandleType,
|
||||||
type NodeHandleBounds,
|
type NodeHandleBounds,
|
||||||
type XYPosition,
|
type XYPosition,
|
||||||
@@ -105,14 +104,14 @@ export function getHandleType(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getConnectionStatus(isInsideConnectionRadius: boolean, isHandleValid: boolean) {
|
export function isConnectionValid(isInsideConnectionRadius: boolean, isHandleValid: boolean) {
|
||||||
let connectionStatus = null;
|
let isValid: boolean | null = null;
|
||||||
|
|
||||||
if (isHandleValid) {
|
if (isHandleValid) {
|
||||||
connectionStatus = 'valid';
|
isValid = true;
|
||||||
} else if (isInsideConnectionRadius && !isHandleValid) {
|
} else if (isInsideConnectionRadius && !isHandleValid) {
|
||||||
connectionStatus = 'invalid';
|
isValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return connectionStatus as ConnectionStatus;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user