Merge branch 'main' into v11
This commit is contained in:
@@ -1,77 +1,70 @@
|
||||
import React, { useRef, CSSProperties } from 'react';
|
||||
import React, { CSSProperties, useCallback } from 'react';
|
||||
import shallow from 'zustand/shallow';
|
||||
|
||||
import { useStore } from '../../store';
|
||||
import { getBezierPath } from '../Edges/BezierEdge';
|
||||
import { getSmoothStepPath } from '../Edges/SmoothStepEdge';
|
||||
import { ConnectionLineType, ConnectionLineComponent, HandleType, Node, ReactFlowState, Position } from '../../types';
|
||||
import { ConnectionLineType, ConnectionLineComponent, HandleType, Position } from '../../types';
|
||||
import { getSimpleBezierPath } from '../Edges/SimpleBezierEdge';
|
||||
import { internalsSymbol } from '../../utils';
|
||||
|
||||
interface ConnectionLineProps {
|
||||
type ConnectionLineProps = {
|
||||
connectionNodeId: string;
|
||||
connectionHandleId: string | null;
|
||||
connectionHandleType: HandleType;
|
||||
connectionPositionX: number;
|
||||
connectionPositionY: number;
|
||||
connectionLineType: ConnectionLineType;
|
||||
isConnectable: boolean;
|
||||
connectionLineStyle?: CSSProperties;
|
||||
CustomConnectionLineComponent?: ConnectionLineComponent;
|
||||
}
|
||||
};
|
||||
|
||||
const selector = (s: ReactFlowState) => ({ nodeInternals: s.nodeInternals, transform: s.transform });
|
||||
const oppositePosition = {
|
||||
[Position.Left]: Position.Right,
|
||||
[Position.Right]: Position.Left,
|
||||
[Position.Top]: Position.Bottom,
|
||||
[Position.Bottom]: Position.Top,
|
||||
};
|
||||
|
||||
export default ({
|
||||
connectionNodeId,
|
||||
connectionHandleId,
|
||||
connectionHandleType,
|
||||
connectionLineStyle,
|
||||
connectionPositionX,
|
||||
connectionPositionY,
|
||||
connectionLineType = ConnectionLineType.Bezier,
|
||||
isConnectable,
|
||||
CustomConnectionLineComponent,
|
||||
}: ConnectionLineProps) => {
|
||||
const nodeId = connectionNodeId;
|
||||
const handleId = connectionHandleId;
|
||||
const { fromNode, handleId, toX, toY } = useStore(
|
||||
useCallback(
|
||||
(s) => ({
|
||||
fromNode: s.nodeInternals.get(connectionNodeId),
|
||||
handleId: s.connectionHandleId,
|
||||
toX: (s.connectionPosition.x - s.transform[0]) / s.transform[2],
|
||||
toY: (s.connectionPosition.y - s.transform[1]) / s.transform[2],
|
||||
}),
|
||||
[connectionNodeId]
|
||||
),
|
||||
shallow
|
||||
);
|
||||
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
|
||||
|
||||
const { nodeInternals, transform } = useStore(selector, shallow);
|
||||
const fromNode = useRef<Node | undefined>(nodeInternals.get(nodeId));
|
||||
const fromHandleBounds = fromNode.current?.[internalsSymbol]?.handleBounds;
|
||||
|
||||
if (!fromNode.current || !isConnectable || !fromHandleBounds?.[connectionHandleType]) {
|
||||
if (!fromNode || !isConnectable || !fromHandleBounds?.[connectionHandleType]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleBound = fromHandleBounds[connectionHandleType];
|
||||
const fromHandle = handleId ? handleBound?.find((d) => d.id === handleId) : handleBound?.[0];
|
||||
const fromHandleX = fromHandle ? fromHandle.x + fromHandle.width / 2 : (fromNode.current?.width ?? 0) / 2;
|
||||
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode.current?.height ?? 0;
|
||||
const fromX = (fromNode.current.positionAbsolute?.x || 0) + fromHandleX;
|
||||
const fromY = (fromNode.current.positionAbsolute?.y || 0) + fromHandleY;
|
||||
|
||||
const toX = (connectionPositionX - transform[0]) / transform[2];
|
||||
const toY = (connectionPositionY - transform[1]) / transform[2];
|
||||
const handleBound = fromHandleBounds[connectionHandleType]!;
|
||||
const fromHandle = handleId ? handleBound.find((d) => d.id === handleId) : handleBound[0];
|
||||
const fromHandleX = fromHandle ? fromHandle.x + fromHandle.width / 2 : (fromNode?.width ?? 0) / 2;
|
||||
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode?.height ?? 0;
|
||||
const fromX = (fromNode?.positionAbsolute?.x || 0) + fromHandleX;
|
||||
const fromY = (fromNode?.positionAbsolute?.y || 0) + fromHandleY;
|
||||
|
||||
const fromPosition = fromHandle?.position;
|
||||
|
||||
let toPosition: Position | undefined;
|
||||
switch (fromPosition) {
|
||||
case Position.Left:
|
||||
toPosition = Position.Right;
|
||||
break;
|
||||
case Position.Right:
|
||||
toPosition = Position.Left;
|
||||
break;
|
||||
case Position.Top:
|
||||
toPosition = Position.Bottom;
|
||||
break;
|
||||
case Position.Bottom:
|
||||
toPosition = Position.Top;
|
||||
break;
|
||||
if (!fromPosition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let toPosition: Position = oppositePosition[fromPosition];
|
||||
|
||||
let sourceX: number,
|
||||
sourceY: number,
|
||||
sourcePosition: Position | undefined,
|
||||
@@ -106,19 +99,25 @@ export default ({
|
||||
return (
|
||||
<g className="react-flow__connection">
|
||||
<CustomConnectionLineComponent
|
||||
sourceX={sourceX}
|
||||
sourceY={sourceY}
|
||||
sourcePosition={sourcePosition}
|
||||
targetX={targetX}
|
||||
targetY={targetY}
|
||||
targetPosition={targetPosition}
|
||||
connectionLineType={connectionLineType}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
fromNode={fromNode.current}
|
||||
fromNode={fromNode}
|
||||
fromHandle={fromHandle}
|
||||
// backward compatibility, mark as deprecated?
|
||||
sourceNode={fromNode.current}
|
||||
fromX={fromX}
|
||||
fromY={fromY}
|
||||
toX={toX}
|
||||
toY={toY}
|
||||
fromPosition={fromPosition}
|
||||
toPosition={toPosition}
|
||||
// remove in v11
|
||||
sourcePosition={sourcePosition}
|
||||
targetPosition={targetPosition}
|
||||
sourceNode={fromNode}
|
||||
sourceHandle={fromHandle}
|
||||
targetX={targetX}
|
||||
targetY={targetY}
|
||||
sourceX={sourceX}
|
||||
sourceY={sourceY}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
import React, { memo, ComponentType, useState, useMemo } from 'react';
|
||||
import cc from 'classcat';
|
||||
import shallow from 'zustand/shallow';
|
||||
|
||||
import { useStore, useStoreApi } from '../../store';
|
||||
import { EdgeProps, WrapEdgeProps, ReactFlowState, Connection } from '../../types';
|
||||
import { useStoreApi } from '../../store';
|
||||
import { EdgeProps, WrapEdgeProps, Connection } from '../../types';
|
||||
import { handleMouseDown } from '../../components/Handle/handler';
|
||||
import { EdgeAnchor } from './EdgeAnchor';
|
||||
import { getMarkerId } from '../../utils/graph';
|
||||
import { getMouseHandler } from './utils';
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
addSelectedEdges: s.addSelectedEdges,
|
||||
connectionMode: s.connectionMode,
|
||||
});
|
||||
|
||||
export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||
const EdgeWrapper = ({
|
||||
id,
|
||||
@@ -56,11 +50,11 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||
rfId,
|
||||
}: WrapEdgeProps): JSX.Element | null => {
|
||||
const [updating, setUpdating] = useState<boolean>(false);
|
||||
const { addSelectedEdges, connectionMode } = useStore(selector, shallow);
|
||||
const store = useStoreApi();
|
||||
|
||||
const onEdgeClick = (event: React.MouseEvent<SVGGElement, MouseEvent>): void => {
|
||||
const edge = store.getState().edges.find((e) => e.id === id)!;
|
||||
const { edges, addSelectedEdges } = store.getState();
|
||||
const edge = edges.find((e) => e.id === id)!;
|
||||
|
||||
if (elementsSelectable) {
|
||||
store.setState({ nodesSelectionActive: false });
|
||||
@@ -86,32 +80,24 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||
|
||||
onEdgeUpdateStart?.(event, edge, handleType);
|
||||
|
||||
const _onEdgeUpdate = onEdgeUpdateEnd
|
||||
const _onEdgeUpdateEnd = onEdgeUpdateEnd
|
||||
? (evt: MouseEvent): void => onEdgeUpdateEnd(evt, edge, handleType)
|
||||
: undefined;
|
||||
|
||||
const onConnectEdge = (connection: Connection) => {
|
||||
const { edges } = store.getState();
|
||||
const edge = edges.find((e) => e.id === id);
|
||||
const onConnectEdge = (connection: Connection) => onEdgeUpdate?.(edge, connection);
|
||||
|
||||
if (edge && onEdgeUpdate) {
|
||||
onEdgeUpdate(edge, connection);
|
||||
}
|
||||
};
|
||||
|
||||
handleMouseDown(
|
||||
handleMouseDown({
|
||||
event,
|
||||
handleId,
|
||||
nodeId,
|
||||
store.setState,
|
||||
onConnectEdge,
|
||||
onConnect: onConnectEdge,
|
||||
isTarget,
|
||||
getState: store.getState,
|
||||
setState: store.setState,
|
||||
isValidConnection,
|
||||
connectionMode,
|
||||
handleType,
|
||||
_onEdgeUpdate,
|
||||
store.getState
|
||||
);
|
||||
elementEdgeUpdaterType: handleType,
|
||||
onEdgeUpdateEnd: _onEdgeUpdateEnd,
|
||||
});
|
||||
};
|
||||
|
||||
const onEdgeUpdaterSourceMouseDown = (event: React.MouseEvent<SVGGElement, MouseEvent>): void =>
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
import { MouseEvent as ReactMouseEvent } from 'react';
|
||||
import { SetState } from 'zustand';
|
||||
import { GetState, SetState } from 'zustand';
|
||||
|
||||
import { getHostForElement } from '../../utils';
|
||||
import {
|
||||
OnConnect,
|
||||
OnConnectStart,
|
||||
OnConnectStop,
|
||||
OnConnectEnd,
|
||||
ConnectionMode,
|
||||
Connection,
|
||||
HandleType,
|
||||
ReactFlowState,
|
||||
} from '../../types';
|
||||
import { OnConnect, ConnectionMode, Connection, HandleType, ReactFlowState } from '../../types';
|
||||
|
||||
type ValidConnectionFunc = (connection: Connection) => boolean;
|
||||
|
||||
@@ -46,6 +37,24 @@ export function checkElementBelowIsValid(
|
||||
if (elementBelow && (elementBelowIsTarget || elementBelowIsSource)) {
|
||||
result.isHoveringHandle = true;
|
||||
|
||||
const elementBelowNodeId = elementBelow.getAttribute('data-nodeid');
|
||||
const elementBelowHandleId = elementBelow.getAttribute('data-handleid');
|
||||
const connection: Connection = isTarget
|
||||
? {
|
||||
source: elementBelowNodeId,
|
||||
sourceHandle: elementBelowHandleId,
|
||||
target: nodeId,
|
||||
targetHandle: handleId,
|
||||
}
|
||||
: {
|
||||
source: nodeId,
|
||||
sourceHandle: handleId,
|
||||
target: elementBelowNodeId,
|
||||
targetHandle: elementBelowHandleId,
|
||||
};
|
||||
|
||||
result.connection = connection;
|
||||
|
||||
// in strict mode we don't allow target to target or source to source connections
|
||||
const isValid =
|
||||
connectionMode === ConnectionMode.Strict
|
||||
@@ -53,23 +62,6 @@ export function checkElementBelowIsValid(
|
||||
: true;
|
||||
|
||||
if (isValid) {
|
||||
const elementBelowNodeId = elementBelow.getAttribute('data-nodeid');
|
||||
const elementBelowHandleId = elementBelow.getAttribute('data-handleid');
|
||||
const connection: Connection = isTarget
|
||||
? {
|
||||
source: elementBelowNodeId,
|
||||
sourceHandle: elementBelowHandleId,
|
||||
target: nodeId,
|
||||
targetHandle: handleId,
|
||||
}
|
||||
: {
|
||||
source: nodeId,
|
||||
sourceHandle: handleId,
|
||||
target: elementBelowNodeId,
|
||||
targetHandle: elementBelowHandleId,
|
||||
};
|
||||
|
||||
result.connection = connection;
|
||||
result.isValid = isValidConnection(connection);
|
||||
}
|
||||
}
|
||||
@@ -82,21 +74,29 @@ function resetRecentHandle(hoveredHandle: Element): void {
|
||||
hoveredHandle?.classList.remove('react-flow__handle-connecting');
|
||||
}
|
||||
|
||||
export function handleMouseDown(
|
||||
event: ReactMouseEvent,
|
||||
handleId: string | null,
|
||||
nodeId: string,
|
||||
setState: SetState<ReactFlowState>,
|
||||
onConnect: OnConnect,
|
||||
isTarget: boolean,
|
||||
isValidConnection: ValidConnectionFunc,
|
||||
connectionMode: ConnectionMode,
|
||||
elementEdgeUpdaterType?: HandleType,
|
||||
onEdgeUpdateEnd?: (evt: MouseEvent) => void,
|
||||
onConnectStart?: OnConnectStart,
|
||||
onConnectStop?: OnConnectStop,
|
||||
onConnectEnd?: OnConnectEnd
|
||||
): void {
|
||||
export function handleMouseDown({
|
||||
event,
|
||||
handleId,
|
||||
nodeId,
|
||||
onConnect,
|
||||
isTarget,
|
||||
getState,
|
||||
setState,
|
||||
isValidConnection,
|
||||
elementEdgeUpdaterType,
|
||||
onEdgeUpdateEnd,
|
||||
}: {
|
||||
event: ReactMouseEvent;
|
||||
handleId: string | null;
|
||||
nodeId: string;
|
||||
onConnect: OnConnect;
|
||||
isTarget: boolean;
|
||||
getState: GetState<ReactFlowState>;
|
||||
setState: SetState<ReactFlowState>;
|
||||
isValidConnection: ValidConnectionFunc;
|
||||
elementEdgeUpdaterType?: HandleType;
|
||||
onEdgeUpdateEnd?: (evt: MouseEvent) => void;
|
||||
}): void {
|
||||
const reactFlowNode = (event.target as Element).closest('.react-flow');
|
||||
// when react-flow is used inside a shadow root we can't use document
|
||||
const doc = getHostForElement(event.target as HTMLElement);
|
||||
@@ -113,6 +113,7 @@ export function handleMouseDown(
|
||||
return;
|
||||
}
|
||||
|
||||
const { onConnectStart, connectionMode } = getState();
|
||||
const handleType = elementEdgeUpdaterType ? elementEdgeUpdaterType : elementBelowIsTarget ? 'target' : 'source';
|
||||
const containerBounds = reactFlowNode.getBoundingClientRect();
|
||||
let recentHoveredHandle: Element;
|
||||
@@ -151,9 +152,7 @@ export function handleMouseDown(
|
||||
return resetRecentHandle(recentHoveredHandle);
|
||||
}
|
||||
|
||||
const isOwnHandle = connection.source === connection.target;
|
||||
|
||||
if (!isOwnHandle && elementBelow) {
|
||||
if (connection.source !== connection.target && elementBelow) {
|
||||
recentHoveredHandle = elementBelow;
|
||||
elementBelow.classList.add('react-flow__handle-connecting');
|
||||
elementBelow.classList.toggle('react-flow__handle-valid', isValid);
|
||||
@@ -161,6 +160,7 @@ export function handleMouseDown(
|
||||
}
|
||||
|
||||
function onMouseUp(event: MouseEvent) {
|
||||
const { onConnectStop, onConnectEnd } = getState();
|
||||
const { connection, isValid } = checkElementBelowIsValid(
|
||||
event,
|
||||
connectionMode,
|
||||
|
||||
@@ -14,17 +14,8 @@ const alwaysValid = () => true;
|
||||
export type HandleComponentProps = HandleProps & Omit<HTMLAttributes<HTMLDivElement>, 'id'>;
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
onConnectAction: s.onConnect,
|
||||
onConnectStart: s.onConnectStart,
|
||||
onConnectStop: s.onConnectStop,
|
||||
onConnectEnd: s.onConnectEnd,
|
||||
onClickConnectStart: s.onClickConnectStart,
|
||||
onClickConnectStop: s.onClickConnectStop,
|
||||
onClickConnectEnd: s.onClickConnectEnd,
|
||||
connectionMode: s.connectionMode,
|
||||
connectionStartHandle: s.connectionStartHandle,
|
||||
connectOnClick: s.connectOnClick,
|
||||
hasDefaultEdges: s.hasDefaultEdges,
|
||||
});
|
||||
|
||||
const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
||||
@@ -45,25 +36,13 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
||||
) => {
|
||||
const store = useStoreApi();
|
||||
const nodeId = useContext(NodeIdContext) as string;
|
||||
const {
|
||||
onConnectAction,
|
||||
onConnectStart,
|
||||
onConnectStop,
|
||||
onConnectEnd,
|
||||
onClickConnectStart,
|
||||
onClickConnectStop,
|
||||
onClickConnectEnd,
|
||||
connectionMode,
|
||||
connectionStartHandle,
|
||||
connectOnClick,
|
||||
hasDefaultEdges,
|
||||
} = useStore(selector, shallow);
|
||||
const { connectionStartHandle, connectOnClick } = useStore(selector, shallow);
|
||||
|
||||
const handleId = id || null;
|
||||
const isTarget = type === 'target';
|
||||
|
||||
const onConnectExtended = (params: Connection) => {
|
||||
const { defaultEdgeOptions } = store.getState();
|
||||
const { defaultEdgeOptions, onConnect: onConnectAction, hasDefaultEdges } = store.getState();
|
||||
|
||||
const edgeParams = {
|
||||
...defaultEdgeOptions,
|
||||
@@ -80,26 +59,22 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
||||
|
||||
const onMouseDownHandler = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (event.button === 0) {
|
||||
handleMouseDown(
|
||||
handleMouseDown({
|
||||
event,
|
||||
handleId,
|
||||
nodeId,
|
||||
store.setState,
|
||||
onConnectExtended,
|
||||
onConnect: onConnectExtended,
|
||||
isTarget,
|
||||
getState: store.getState,
|
||||
setState: store.setState,
|
||||
isValidConnection,
|
||||
connectionMode,
|
||||
undefined,
|
||||
undefined,
|
||||
onConnectStart,
|
||||
onConnectStop,
|
||||
onConnectEnd
|
||||
);
|
||||
});
|
||||
}
|
||||
onMouseDown?.(event);
|
||||
};
|
||||
|
||||
const onClick = (event: React.MouseEvent) => {
|
||||
const { onClickConnectStart, onClickConnectStop, onClickConnectEnd, connectionMode } = store.getState();
|
||||
if (!connectionStartHandle) {
|
||||
onClickConnectStart?.(event, { nodeId, handleId, handleType: type });
|
||||
store.setState({ connectionStartHandle: { nodeId, type, handleId } });
|
||||
@@ -128,28 +103,26 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
||||
store.setState({ connectionStartHandle: null });
|
||||
};
|
||||
|
||||
const handleClasses = cc([
|
||||
'react-flow__handle',
|
||||
`react-flow__handle-${position}`,
|
||||
'nodrag',
|
||||
className,
|
||||
{
|
||||
source: !isTarget,
|
||||
target: isTarget,
|
||||
connectable: isConnectable,
|
||||
connecting:
|
||||
connectionStartHandle?.nodeId === nodeId &&
|
||||
connectionStartHandle?.handleId === handleId &&
|
||||
connectionStartHandle?.type === type,
|
||||
},
|
||||
]);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-handleid={handleId}
|
||||
data-nodeid={nodeId}
|
||||
data-handlepos={position}
|
||||
className={handleClasses}
|
||||
className={cc([
|
||||
'react-flow__handle',
|
||||
`react-flow__handle-${position}`,
|
||||
'nodrag',
|
||||
className,
|
||||
{
|
||||
source: !isTarget,
|
||||
target: isTarget,
|
||||
connectable: isConnectable,
|
||||
connecting:
|
||||
connectionStartHandle?.nodeId === nodeId &&
|
||||
connectionStartHandle?.handleId === handleId &&
|
||||
connectionStartHandle?.type === type,
|
||||
},
|
||||
])}
|
||||
onMouseDown={onMouseDownHandler}
|
||||
onClick={connectOnClick ? onClick : undefined}
|
||||
ref={ref}
|
||||
|
||||
@@ -4,20 +4,10 @@ import { GetState, SetState } from 'zustand';
|
||||
import { HandleElement, Node, Position, ReactFlowState } from '../../types';
|
||||
import { getDimensions } from '../../utils';
|
||||
|
||||
export const getHandleBounds = (nodeElement: HTMLDivElement, scale: number) => {
|
||||
const bounds = nodeElement.getBoundingClientRect();
|
||||
|
||||
return {
|
||||
source: getHandleBoundsByHandleType('.source', nodeElement, bounds, scale),
|
||||
target: getHandleBoundsByHandleType('.target', nodeElement, bounds, scale),
|
||||
};
|
||||
};
|
||||
|
||||
export const getHandleBoundsByHandleType = (
|
||||
export const getHandleBounds = (
|
||||
selector: string,
|
||||
nodeElement: HTMLDivElement,
|
||||
parentBounds: DOMRect,
|
||||
k: number
|
||||
zoom: number
|
||||
): HandleElement[] | null => {
|
||||
const handles = nodeElement.querySelectorAll(selector);
|
||||
|
||||
@@ -26,19 +16,17 @@ export const getHandleBoundsByHandleType = (
|
||||
}
|
||||
|
||||
const handlesArray = Array.from(handles) as HTMLDivElement[];
|
||||
const nodeBounds = nodeElement.getBoundingClientRect();
|
||||
|
||||
return handlesArray.map((handle): HandleElement => {
|
||||
const bounds = handle.getBoundingClientRect();
|
||||
const dimensions = getDimensions(handle);
|
||||
const handleId = handle.getAttribute('data-handleid');
|
||||
const handlePosition = handle.getAttribute('data-handlepos') as unknown as Position;
|
||||
const handleBounds = handle.getBoundingClientRect();
|
||||
|
||||
return {
|
||||
id: handleId,
|
||||
position: handlePosition,
|
||||
x: (bounds.left - parentBounds.left) / k,
|
||||
y: (bounds.top - parentBounds.top) / k,
|
||||
...dimensions,
|
||||
id: handle.getAttribute('data-handleid'),
|
||||
position: handle.getAttribute('data-handlepos') as unknown as Position,
|
||||
x: (handleBounds.left - nodeBounds.left) / zoom,
|
||||
y: (handleBounds.top - nodeBounds.top) / zoom,
|
||||
...getDimensions(handle),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import React, { useEffect, useRef, memo, ComponentType, MouseEvent } from 'react';
|
||||
import cc from 'classcat';
|
||||
|
||||
import { useStore, useStoreApi } from '../../store';
|
||||
import { useStoreApi } from '../../store';
|
||||
import { Provider } from '../../contexts/NodeIdContext';
|
||||
import { NodeProps, WrapNodeProps, ReactFlowState } from '../../types';
|
||||
import { NodeProps, WrapNodeProps } from '../../types';
|
||||
import useDrag from '../../hooks/useDrag';
|
||||
import { getMouseHandler, handleNodeClick } from './utils';
|
||||
|
||||
const selector = (s: ReactFlowState) => s.updateNodeDimensions;
|
||||
|
||||
export default (NodeComponent: ComponentType<NodeProps>) => {
|
||||
const NodeWrapper = ({
|
||||
id,
|
||||
@@ -23,9 +21,6 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
||||
onMouseLeave,
|
||||
onContextMenu,
|
||||
onDoubleClick,
|
||||
onDragStart,
|
||||
onDrag,
|
||||
onDragStop,
|
||||
style,
|
||||
className,
|
||||
isDraggable,
|
||||
@@ -44,7 +39,6 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
||||
initialized,
|
||||
}: WrapNodeProps) => {
|
||||
const store = useStoreApi();
|
||||
const updateNodeDimensions = useStore(selector);
|
||||
const nodeRef = useRef<HTMLDivElement>(null);
|
||||
const prevSourcePosition = useRef(sourcePosition);
|
||||
const prevTargetPosition = useRef(targetPosition);
|
||||
@@ -96,14 +90,11 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
||||
if (targetPosChanged) {
|
||||
prevTargetPosition.current = targetPosition;
|
||||
}
|
||||
updateNodeDimensions([{ id, nodeElement: nodeRef.current, forceUpdate: true }]);
|
||||
store.getState().updateNodeDimensions([{ id, nodeElement: nodeRef.current, forceUpdate: true }]);
|
||||
}
|
||||
}, [id, type, sourcePosition, targetPosition]);
|
||||
|
||||
const dragging = useDrag({
|
||||
onStart: onDragStart,
|
||||
onDrag: onDrag,
|
||||
onStop: onDragStop,
|
||||
nodeRef,
|
||||
disabled: hidden || !isDraggable,
|
||||
noDragClassName,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* made a selection with on or several nodes
|
||||
*/
|
||||
|
||||
import React, { memo, useCallback, useRef, MouseEvent } from 'react';
|
||||
import React, { memo, useRef, MouseEvent } from 'react';
|
||||
import cc from 'classcat';
|
||||
import shallow from 'zustand/shallow';
|
||||
|
||||
@@ -13,16 +13,14 @@ import { getRectOfNodes } from '../../utils/graph';
|
||||
import useDrag from '../../hooks/useDrag';
|
||||
|
||||
export interface NodesSelectionProps {
|
||||
onSelectionDragStart?: (event: MouseEvent, nodes: Node[]) => void;
|
||||
onSelectionDrag?: (event: MouseEvent, nodes: Node[]) => void;
|
||||
onSelectionDragStop?: (event: MouseEvent, nodes: Node[]) => void;
|
||||
onSelectionContextMenu?: (event: MouseEvent, nodes: Node[]) => void;
|
||||
noPanClassName?: string;
|
||||
}
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
transform: s.transform,
|
||||
transformString: `translate(${s.transform[0]}px,${s.transform[1]}px) scale(${s.transform[2]})`,
|
||||
userSelectionActive: s.userSelectionActive,
|
||||
...getRectOfNodes(Array.from(s.nodeInternals.values()).filter((n) => n.selected)),
|
||||
});
|
||||
|
||||
const bboxSelector = (s: ReactFlowState) => {
|
||||
@@ -30,31 +28,14 @@ const bboxSelector = (s: ReactFlowState) => {
|
||||
return getRectOfNodes(selectedNodes);
|
||||
};
|
||||
|
||||
function useGetMemoizedHandler(handler?: (event: MouseEvent, nodes: Node[]) => void) {
|
||||
return useCallback((event: MouseEvent, _: Node, nodes: Node[]) => handler?.(event, nodes), [handler]);
|
||||
}
|
||||
|
||||
function NodesSelection({
|
||||
onSelectionDragStart,
|
||||
onSelectionDrag,
|
||||
onSelectionDragStop,
|
||||
onSelectionContextMenu,
|
||||
noPanClassName,
|
||||
}: NodesSelectionProps) {
|
||||
function NodesSelection({ onSelectionContextMenu, noPanClassName }: NodesSelectionProps) {
|
||||
const store = useStoreApi();
|
||||
const { transform, userSelectionActive } = useStore(selector, shallow);
|
||||
const { transformString, userSelectionActive } = useStore(selector, shallow);
|
||||
const { width, height, x: left, y: top } = useStore(bboxSelector, shallow);
|
||||
|
||||
const nodeRef = useRef(null);
|
||||
|
||||
// it's important that these handlers are memoized to avoid multiple creation of d3 drag handler
|
||||
const onStart = useGetMemoizedHandler(onSelectionDragStart);
|
||||
const onDrag = useGetMemoizedHandler(onSelectionDrag);
|
||||
const onStop = useGetMemoizedHandler(onSelectionDragStop);
|
||||
|
||||
useDrag({
|
||||
onStart,
|
||||
onDrag,
|
||||
onStop,
|
||||
nodeRef,
|
||||
});
|
||||
|
||||
@@ -73,7 +54,7 @@ function NodesSelection({
|
||||
<div
|
||||
className={cc(['react-flow__nodesselection', 'react-flow__container', noPanClassName])}
|
||||
style={{
|
||||
transform: `translate(${transform[0]}px,${transform[1]}px) scale(${transform[2]})`,
|
||||
transform: transformString,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -3,56 +3,46 @@ import { SetState } from 'zustand';
|
||||
import shallow from 'zustand/shallow';
|
||||
|
||||
import { useStore, useStoreApi } from '../../store';
|
||||
import {
|
||||
Node,
|
||||
Edge,
|
||||
ReactFlowState,
|
||||
OnConnect,
|
||||
OnConnectStart,
|
||||
OnConnectStop,
|
||||
OnConnectEnd,
|
||||
CoordinateExtent,
|
||||
OnNodesChange,
|
||||
OnEdgesChange,
|
||||
ConnectionMode,
|
||||
SnapGrid,
|
||||
DefaultEdgeOptions,
|
||||
FitViewOptions,
|
||||
OnNodesDelete,
|
||||
OnEdgesDelete,
|
||||
} from '../../types';
|
||||
import { Node, Edge, ReactFlowState, CoordinateExtent, ReactFlowProps } from '../../types';
|
||||
|
||||
interface StoreUpdaterProps {
|
||||
nodes?: Node[];
|
||||
edges?: Edge[];
|
||||
defaultNodes?: Node[];
|
||||
defaultEdges?: Edge[];
|
||||
onConnect?: OnConnect;
|
||||
onConnectStart?: OnConnectStart;
|
||||
onConnectStop?: OnConnectStop;
|
||||
onConnectEnd?: OnConnectEnd;
|
||||
onClickConnectStart?: OnConnectStart;
|
||||
onClickConnectStop?: OnConnectStop;
|
||||
onClickConnectEnd?: OnConnectEnd;
|
||||
nodesDraggable?: boolean;
|
||||
nodesConnectable?: boolean;
|
||||
minZoom?: number;
|
||||
maxZoom?: number;
|
||||
nodeExtent?: CoordinateExtent;
|
||||
onNodesChange?: OnNodesChange;
|
||||
onEdgesChange?: OnEdgesChange;
|
||||
elementsSelectable?: boolean;
|
||||
connectionMode?: ConnectionMode;
|
||||
snapToGrid?: boolean;
|
||||
snapGrid?: SnapGrid;
|
||||
translateExtent?: CoordinateExtent;
|
||||
connectOnClick: boolean;
|
||||
defaultEdgeOptions?: DefaultEdgeOptions;
|
||||
fitView?: boolean;
|
||||
fitViewOptions?: FitViewOptions;
|
||||
onNodesDelete?: OnNodesDelete;
|
||||
onEdgesDelete?: OnEdgesDelete;
|
||||
}
|
||||
type StoreUpdaterProps = Pick<
|
||||
ReactFlowProps,
|
||||
| 'nodes'
|
||||
| 'edges'
|
||||
| 'defaultNodes'
|
||||
| 'defaultEdges'
|
||||
| 'onConnect'
|
||||
| 'onConnectStart'
|
||||
| 'onConnectStop'
|
||||
| 'onConnectEnd'
|
||||
| 'onClickConnectStart'
|
||||
| 'onClickConnectStop'
|
||||
| 'onClickConnectEnd'
|
||||
| 'nodesDraggable'
|
||||
| 'nodesConnectable'
|
||||
| 'minZoom'
|
||||
| 'maxZoom'
|
||||
| 'nodeExtent'
|
||||
| 'onNodesChange'
|
||||
| 'onEdgesChange'
|
||||
| 'elementsSelectable'
|
||||
| 'connectionMode'
|
||||
| 'snapToGrid'
|
||||
| 'snapGrid'
|
||||
| 'translateExtent'
|
||||
| 'connectOnClick'
|
||||
| 'defaultEdgeOptions'
|
||||
| 'fitView'
|
||||
| 'fitViewOptions'
|
||||
| 'onNodesDelete'
|
||||
| 'onEdgesDelete'
|
||||
| 'onNodeDragStart'
|
||||
| 'onNodeDrag'
|
||||
| 'onNodeDragStop'
|
||||
| 'onSelectionDragStart'
|
||||
| 'onSelectionDrag'
|
||||
| 'onSelectionDragStop'
|
||||
>;
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
setNodes: s.setNodes,
|
||||
@@ -112,6 +102,12 @@ const StoreUpdater = ({
|
||||
fitViewOptions,
|
||||
onNodesDelete,
|
||||
onEdgesDelete,
|
||||
onNodeDrag,
|
||||
onNodeDragStart,
|
||||
onNodeDragStop,
|
||||
onSelectionDrag,
|
||||
onSelectionDragStart,
|
||||
onSelectionDragStop,
|
||||
}: StoreUpdaterProps) => {
|
||||
const {
|
||||
setNodes,
|
||||
@@ -154,6 +150,12 @@ const StoreUpdater = ({
|
||||
useDirectStoreUpdater('fitViewOnInitOptions', fitViewOptions, store.setState);
|
||||
useDirectStoreUpdater('onNodesDelete', onNodesDelete, store.setState);
|
||||
useDirectStoreUpdater('onEdgesDelete', onEdgesDelete, store.setState);
|
||||
useDirectStoreUpdater('onNodeDrag', onNodeDrag, store.setState);
|
||||
useDirectStoreUpdater('onNodeDragStart', onNodeDragStart, store.setState);
|
||||
useDirectStoreUpdater('onNodeDragStop', onNodeDragStop, store.setState);
|
||||
useDirectStoreUpdater('onSelectionDrag', onSelectionDrag, store.setState);
|
||||
useDirectStoreUpdater('onSelectionDragStart', onSelectionDragStart, store.setState);
|
||||
useDirectStoreUpdater('onSelectionDragStop', onSelectionDragStop, store.setState);
|
||||
|
||||
useStoreUpdater<Node[]>(nodes, setNodes);
|
||||
useStoreUpdater<Edge[]>(edges, setEdges);
|
||||
|
||||
Reference in New Issue
Block a user