@@ -9,6 +9,7 @@ import ReactFlow, {
|
|||||||
useReactFlow,
|
useReactFlow,
|
||||||
} from 'react-flow-renderer';
|
} from 'react-flow-renderer';
|
||||||
|
|
||||||
|
const onNodeDrag = (_: MouseEvent, node: Node) => console.log('drag', node);
|
||||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ const BasicFlow = () => {
|
|||||||
defaultEdges={initialEdges}
|
defaultEdges={initialEdges}
|
||||||
onNodeClick={onNodeClick}
|
onNodeClick={onNodeClick}
|
||||||
onNodeDragStop={onNodeDragStop}
|
onNodeDragStop={onNodeDragStop}
|
||||||
|
onNodeDrag={onNodeDrag}
|
||||||
className="react-flow-basic-example"
|
className="react-flow-basic-example"
|
||||||
minZoom={0.2}
|
minZoom={0.2}
|
||||||
maxZoom={4}
|
maxZoom={4}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { memo, useCallback, Dispatch, FC } from 'react';
|
import React, { memo, useCallback, Dispatch, FC } from 'react';
|
||||||
import { useReactFlow, ReactFlowInstance, Edge, Node, ReactFlowJsonObject } from 'react-flow-renderer';
|
import { useReactFlow, Edge, Node, ReactFlowJsonObject } from 'react-flow-renderer';
|
||||||
import localforage from 'localforage';
|
import localforage from 'localforage';
|
||||||
|
|
||||||
localforage.config({
|
localforage.config({
|
||||||
@@ -12,20 +12,17 @@ const flowKey = 'example-flow';
|
|||||||
const getNodeId = () => `randomnode_${+new Date()}`;
|
const getNodeId = () => `randomnode_${+new Date()}`;
|
||||||
|
|
||||||
type ControlsProps = {
|
type ControlsProps = {
|
||||||
rfInstance?: ReactFlowInstance;
|
|
||||||
setNodes: Dispatch<React.SetStateAction<Node<any>[]>>;
|
setNodes: Dispatch<React.SetStateAction<Node<any>[]>>;
|
||||||
setEdges: Dispatch<React.SetStateAction<Edge<any>[]>>;
|
setEdges: Dispatch<React.SetStateAction<Edge<any>[]>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Controls: FC<ControlsProps> = ({ rfInstance, setNodes, setEdges }) => {
|
const Controls: FC<ControlsProps> = ({ setNodes, setEdges }) => {
|
||||||
const { setViewport } = useReactFlow();
|
const { setViewport, toObject } = useReactFlow();
|
||||||
|
|
||||||
const onSave = useCallback(() => {
|
const onSave = useCallback(() => {
|
||||||
if (rfInstance) {
|
const flow = toObject();
|
||||||
const flow = rfInstance.toObject();
|
localforage.setItem(flowKey, flow);
|
||||||
localforage.setItem(flowKey, flow);
|
}, [toObject]);
|
||||||
}
|
|
||||||
}, [rfInstance]);
|
|
||||||
|
|
||||||
const onRestore = useCallback(() => {
|
const onRestore = useCallback(() => {
|
||||||
const restoreFlow = async () => {
|
const restoreFlow = async () => {
|
||||||
@@ -33,6 +30,7 @@ const Controls: FC<ControlsProps> = ({ rfInstance, setNodes, setEdges }) => {
|
|||||||
|
|
||||||
if (flow) {
|
if (flow) {
|
||||||
const { x, y, zoom } = flow.viewport;
|
const { x, y, zoom } = flow.viewport;
|
||||||
|
|
||||||
setNodes(flow.nodes || []);
|
setNodes(flow.nodes || []);
|
||||||
setEdges(flow.edges || []);
|
setEdges(flow.edges || []);
|
||||||
setViewport({ x, y, zoom: zoom || 0 });
|
setViewport({ x, y, zoom: zoom || 0 });
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { useState } from 'react';
|
import { useCallback } from 'react';
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
ReactFlowProvider,
|
ReactFlowProvider,
|
||||||
Node,
|
Node,
|
||||||
addEdge,
|
addEdge,
|
||||||
Connection,
|
Connection,
|
||||||
Edge,
|
Edge,
|
||||||
ReactFlowInstance,
|
|
||||||
useNodesState,
|
useNodesState,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
} from 'react-flow-renderer';
|
} from 'react-flow-renderer';
|
||||||
@@ -22,10 +21,9 @@ const initialNodes: Node[] = [
|
|||||||
const initialEdges: Edge[] = [{ id: 'e1-2', source: '1', target: '2' }];
|
const initialEdges: Edge[] = [{ id: 'e1-2', source: '1', target: '2' }];
|
||||||
|
|
||||||
const SaveRestore = () => {
|
const SaveRestore = () => {
|
||||||
const [rfInstance, setRfInstance] = useState<ReactFlowInstance>();
|
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||||
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
|
const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
@@ -35,9 +33,8 @@ const SaveRestore = () => {
|
|||||||
onNodesChange={onNodesChange}
|
onNodesChange={onNodesChange}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
onInit={setRfInstance}
|
|
||||||
>
|
>
|
||||||
<Controls rfInstance={rfInstance} setNodes={setNodes} setEdges={setEdges} />
|
<Controls setNodes={setNodes} setEdges={setEdges} />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
</ReactFlowProvider>
|
</ReactFlowProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const idStyle: CSSProperties = {
|
|||||||
left: 2,
|
left: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ColorSelectorNode: FC<NodeProps> = ({ zIndex, xPos, yPos, id }) => {
|
const DebugNode: FC<NodeProps> = ({ zIndex, xPos, yPos, id }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Handle type="target" position={Position.Top} />
|
<Handle type="target" position={Position.Top} />
|
||||||
@@ -24,4 +24,4 @@ const ColorSelectorNode: FC<NodeProps> = ({ zIndex, xPos, yPos, id }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(ColorSelectorNode);
|
export default memo(DebugNode);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import ReactFlow, {
|
|||||||
} from 'react-flow-renderer';
|
} from 'react-flow-renderer';
|
||||||
import DebugNode from './DebugNode';
|
import DebugNode from './DebugNode';
|
||||||
|
|
||||||
|
const onNodeDrag = (_: MouseEvent, node: Node, nodes: Node[]) => console.log('drag', node, nodes);
|
||||||
const onNodeDragStop = (_: MouseEvent, node: Node, nodes: Node[]) => console.log('drag stop', node, nodes);
|
const onNodeDragStop = (_: MouseEvent, node: Node, nodes: Node[]) => console.log('drag stop', node, nodes);
|
||||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||||
const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge);
|
const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge);
|
||||||
@@ -82,6 +83,7 @@ const initialNodes: Node[] = [
|
|||||||
position: { x: 25, y: 50 },
|
position: { x: 25, y: 50 },
|
||||||
className: 'light',
|
className: 'light',
|
||||||
parentNode: '5',
|
parentNode: '5',
|
||||||
|
extent: 'parent',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '5b',
|
id: '5b',
|
||||||
@@ -167,6 +169,7 @@ const Subflow = () => {
|
|||||||
onNodeClick={onNodeClick}
|
onNodeClick={onNodeClick}
|
||||||
onEdgeClick={onEdgeClick}
|
onEdgeClick={onEdgeClick}
|
||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
|
onNodeDrag={onNodeDrag}
|
||||||
onNodeDragStop={onNodeDragStop}
|
onNodeDragStop={onNodeDragStop}
|
||||||
className="react-flow-basic-example"
|
className="react-flow-basic-example"
|
||||||
defaultZoom={1.5}
|
defaultZoom={1.5}
|
||||||
|
|||||||
@@ -46,6 +46,24 @@ export function checkElementBelowIsValid(
|
|||||||
if (elementBelow && (elementBelowIsTarget || elementBelowIsSource)) {
|
if (elementBelow && (elementBelowIsTarget || elementBelowIsSource)) {
|
||||||
result.isHoveringHandle = true;
|
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
|
// in strict mode we don't allow target to target or source to source connections
|
||||||
const isValid =
|
const isValid =
|
||||||
connectionMode === ConnectionMode.Strict
|
connectionMode === ConnectionMode.Strict
|
||||||
@@ -53,23 +71,6 @@ export function checkElementBelowIsValid(
|
|||||||
: true;
|
: true;
|
||||||
|
|
||||||
if (isValid) {
|
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);
|
result.isValid = isValidConnection(connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,9 +152,7 @@ export function handleMouseDown(
|
|||||||
return resetRecentHandle(recentHoveredHandle);
|
return resetRecentHandle(recentHoveredHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOwnHandle = connection.source === connection.target;
|
if (connection.source !== connection.target && elementBelow) {
|
||||||
|
|
||||||
if (!isOwnHandle && elementBelow) {
|
|
||||||
recentHoveredHandle = elementBelow;
|
recentHoveredHandle = elementBelow;
|
||||||
elementBelow.classList.add('react-flow__handle-connecting');
|
elementBelow.classList.add('react-flow__handle-connecting');
|
||||||
elementBelow.classList.toggle('react-flow__handle-valid', isValid);
|
elementBelow.classList.toggle('react-flow__handle-valid', isValid);
|
||||||
|
|||||||
@@ -4,21 +4,17 @@ import { GetState, SetState } from 'zustand';
|
|||||||
import { HandleElement, Node, Position, ReactFlowState } from '../../types';
|
import { HandleElement, Node, Position, ReactFlowState } from '../../types';
|
||||||
import { getDimensions } from '../../utils';
|
import { getDimensions } from '../../utils';
|
||||||
|
|
||||||
export const getHandleBounds = (nodeElement: HTMLDivElement, scale: number) => {
|
function getTranslateValues(domNode: HTMLDivElement): [number, number] {
|
||||||
const bounds = nodeElement.getBoundingClientRect();
|
if (typeof window === 'undefined' || !window.DOMMatrixReadOnly) {
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
const style = window.getComputedStyle(domNode);
|
||||||
source: getHandleBoundsByHandleType('.source', nodeElement, bounds, scale),
|
const { m41, m42 } = new window.DOMMatrixReadOnly(style.transform);
|
||||||
target: getHandleBoundsByHandleType('.target', nodeElement, bounds, scale),
|
return [m41, m42];
|
||||||
};
|
}
|
||||||
};
|
|
||||||
|
|
||||||
export const getHandleBoundsByHandleType = (
|
export const getHandleBounds = (selector: string, nodeElement: HTMLDivElement): HandleElement[] | null => {
|
||||||
selector: string,
|
|
||||||
nodeElement: HTMLDivElement,
|
|
||||||
parentBounds: DOMRect,
|
|
||||||
k: number
|
|
||||||
): HandleElement[] | null => {
|
|
||||||
const handles = nodeElement.querySelectorAll(selector);
|
const handles = nodeElement.querySelectorAll(selector);
|
||||||
|
|
||||||
if (!handles || !handles.length) {
|
if (!handles || !handles.length) {
|
||||||
@@ -28,17 +24,16 @@ export const getHandleBoundsByHandleType = (
|
|||||||
const handlesArray = Array.from(handles) as HTMLDivElement[];
|
const handlesArray = Array.from(handles) as HTMLDivElement[];
|
||||||
|
|
||||||
return handlesArray.map((handle): HandleElement => {
|
return handlesArray.map((handle): HandleElement => {
|
||||||
const bounds = handle.getBoundingClientRect();
|
// we don't use getBoundingClientRect here, because it includes the transform of the parent (scaled viewport)
|
||||||
const dimensions = getDimensions(handle);
|
// that we would then need to calculate out again in order to get the correct position.
|
||||||
const handleId = handle.getAttribute('data-handleid');
|
const [translateX, translateY] = getTranslateValues(handle);
|
||||||
const handlePosition = handle.getAttribute('data-handlepos') as unknown as Position;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: handleId,
|
id: handle.getAttribute('data-handleid'),
|
||||||
position: handlePosition,
|
position: handle.getAttribute('data-handlepos') as unknown as Position,
|
||||||
x: (bounds.left - parentBounds.left) / k,
|
x: handle.offsetLeft + nodeElement.clientLeft + translateX,
|
||||||
y: (bounds.top - parentBounds.top) / k,
|
y: handle.offsetTop + nodeElement.clientTop + translateY,
|
||||||
...dimensions,
|
...getDimensions(handle),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ const ZoomPane = ({
|
|||||||
noPanClassName,
|
noPanClassName,
|
||||||
}: ZoomPaneProps) => {
|
}: ZoomPaneProps) => {
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
|
const isZoomingOrPanning = useRef(false);
|
||||||
const zoomPane = useRef<HTMLDivElement>(null);
|
const zoomPane = useRef<HTMLDivElement>(null);
|
||||||
const prevTransform = useRef<Viewport>({ x: 0, y: 0, zoom: 0 });
|
const prevTransform = useRef<Viewport>({ x: 0, y: 0, zoom: 0 });
|
||||||
const { d3Zoom, d3Selection, d3ZoomHandler } = useStore(selector, shallow);
|
const { d3Zoom, d3Selection, d3ZoomHandler } = useStore(selector, shallow);
|
||||||
@@ -146,12 +147,11 @@ const ZoomPane = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (d3Zoom) {
|
if (d3Zoom) {
|
||||||
if (selectionKeyPressed) {
|
if (selectionKeyPressed && !isZoomingOrPanning.current) {
|
||||||
d3Zoom.on('zoom', null);
|
d3Zoom.on('zoom', null);
|
||||||
} else {
|
} else if (!selectionKeyPressed) {
|
||||||
d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
||||||
store.setState({ transform: [event.transform.x, event.transform.y, event.transform.k] });
|
store.setState({ transform: [event.transform.x, event.transform.y, event.transform.k] });
|
||||||
|
|
||||||
if (onMove) {
|
if (onMove) {
|
||||||
const flowTransform = eventToFlowTransform(event.transform);
|
const flowTransform = eventToFlowTransform(event.transform);
|
||||||
onMove(event.sourceEvent as MouseEvent | TouchEvent, flowTransform);
|
onMove(event.sourceEvent as MouseEvent | TouchEvent, flowTransform);
|
||||||
@@ -163,33 +163,31 @@ const ZoomPane = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (d3Zoom) {
|
if (d3Zoom) {
|
||||||
if (onMoveStart) {
|
d3Zoom.on('start', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
||||||
d3Zoom.on('start', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
isZoomingOrPanning.current = true;
|
||||||
|
|
||||||
|
if (onMoveStart) {
|
||||||
const flowTransform = eventToFlowTransform(event.transform);
|
const flowTransform = eventToFlowTransform(event.transform);
|
||||||
prevTransform.current = flowTransform;
|
prevTransform.current = flowTransform;
|
||||||
|
|
||||||
onMoveStart(event.sourceEvent as MouseEvent | TouchEvent, flowTransform);
|
onMoveStart(event.sourceEvent as MouseEvent | TouchEvent, flowTransform);
|
||||||
});
|
}
|
||||||
} else {
|
});
|
||||||
d3Zoom.on('start', null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [d3Zoom, onMoveStart]);
|
}, [d3Zoom, onMoveStart]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (d3Zoom) {
|
if (d3Zoom) {
|
||||||
if (onMoveEnd) {
|
d3Zoom.on('end', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
||||||
d3Zoom.on('end', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
isZoomingOrPanning.current = false;
|
||||||
if (viewChanged(prevTransform.current, event.transform)) {
|
|
||||||
const flowTransform = eventToFlowTransform(event.transform);
|
|
||||||
prevTransform.current = flowTransform;
|
|
||||||
|
|
||||||
onMoveEnd(event.sourceEvent as MouseEvent | TouchEvent, flowTransform);
|
if (onMoveEnd && viewChanged(prevTransform.current, event.transform)) {
|
||||||
}
|
const flowTransform = eventToFlowTransform(event.transform);
|
||||||
});
|
prevTransform.current = flowTransform;
|
||||||
} else {
|
|
||||||
d3Zoom.on('end', null);
|
onMoveEnd(event.sourceEvent as MouseEvent | TouchEvent, flowTransform);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [d3Zoom, onMoveEnd]);
|
}, [d3Zoom, onMoveEnd]);
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ function useDrag({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pointerPos = getPointerPosition(event);
|
const pointerPos = getPointerPosition(event);
|
||||||
|
lastPos.current = pointerPos;
|
||||||
dragItems.current = getDragItems(nodeInternals, pointerPos, nodeId);
|
dragItems.current = getDragItems(nodeInternals, pointerPos, nodeId);
|
||||||
|
|
||||||
if (onStart && dragItems.current) {
|
if (onStart && dragItems.current) {
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ export function getDragItems(nodeInternals: NodeInternals, mousePos: XYPosition,
|
|||||||
.filter((n) => (n.selected || n.id === nodeId) && (!n.parentNode || !isParentSelected(n, nodeInternals)))
|
.filter((n) => (n.selected || n.id === nodeId) && (!n.parentNode || !isParentSelected(n, nodeInternals)))
|
||||||
.map((n) => ({
|
.map((n) => ({
|
||||||
id: n.id,
|
id: n.id,
|
||||||
position: n.positionAbsolute || { x: 0, y: 0 },
|
position: n.position || { x: 0, y: 0 },
|
||||||
|
positionAbsolute: n.positionAbsolute || { x: 0, y: 0 },
|
||||||
distance: {
|
distance: {
|
||||||
x: mousePos.x - (n.positionAbsolute?.x ?? 0),
|
x: mousePos.x - (n.positionAbsolute?.x ?? 0),
|
||||||
y: mousePos.y - (n.positionAbsolute?.y ?? 0),
|
y: mousePos.y - (n.positionAbsolute?.y ?? 0),
|
||||||
@@ -94,14 +95,28 @@ export function updatePosition(
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
dragItem.position = currentExtent ? clampPosition(nextPosition, currentExtent as CoordinateExtent) : nextPosition;
|
let parentPosition = { x: 0, y: 0 };
|
||||||
|
|
||||||
|
if (dragItem.parentNode) {
|
||||||
|
const parentNode = nodeInternals.get(dragItem.parentNode);
|
||||||
|
parentPosition = { x: parentNode?.positionAbsolute?.x ?? 0, y: parentNode?.positionAbsolute?.y ?? 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
dragItem.positionAbsolute = currentExtent
|
||||||
|
? clampPosition(nextPosition, currentExtent as CoordinateExtent)
|
||||||
|
: nextPosition;
|
||||||
|
|
||||||
|
dragItem.position = {
|
||||||
|
x: dragItem.positionAbsolute.x - parentPosition.x,
|
||||||
|
y: dragItem.positionAbsolute.y - parentPosition.y,
|
||||||
|
};
|
||||||
|
|
||||||
return dragItem;
|
return dragItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns two params:
|
// returns two params:
|
||||||
// 1. the dragged node (or the first of the list, if we are dragging a node selection)
|
// 1. the dragged node (or the first of the list, if we are dragging a node selection)
|
||||||
// 2. array of selected nodes (handy when multi selection is active)
|
// 2. array of selected nodes (for multi selections)
|
||||||
export function getEventHandlerParams({
|
export function getEventHandlerParams({
|
||||||
nodeId,
|
nodeId,
|
||||||
dragItems,
|
dragItems,
|
||||||
@@ -116,6 +131,8 @@ export function getEventHandlerParams({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...node,
|
...node,
|
||||||
|
position: n.position,
|
||||||
|
positionAbsolute: n.positionAbsolute,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -38,12 +38,12 @@ const useViewportHelper = (): ViewportHelperFunctions => {
|
|||||||
zoomIn: (options) => d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1.2),
|
zoomIn: (options) => d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1.2),
|
||||||
zoomOut: (options) => d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1 / 1.2),
|
zoomOut: (options) => d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1 / 1.2),
|
||||||
zoomTo: (zoomLevel, options) => d3Zoom.scaleTo(getD3Transition(d3Selection, options?.duration), zoomLevel),
|
zoomTo: (zoomLevel, options) => d3Zoom.scaleTo(getD3Transition(d3Selection, options?.duration), zoomLevel),
|
||||||
getZoom: () => {
|
getZoom: () => store.getState().transform[2],
|
||||||
const [, , zoom] = store.getState().transform;
|
|
||||||
return zoom;
|
|
||||||
},
|
|
||||||
setViewport: (transform, options) => {
|
setViewport: (transform, options) => {
|
||||||
const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom);
|
const [x, y, zoom] = store.getState().transform;
|
||||||
|
const nextTransform = zoomIdentity
|
||||||
|
.translate(transform.x ?? x, transform.y ?? y)
|
||||||
|
.scale(transform.zoom ?? zoom);
|
||||||
d3Zoom.transform(getD3Transition(d3Selection, options?.duration), nextTransform);
|
d3Zoom.transform(getD3Transition(d3Selection, options?.duration), nextTransform);
|
||||||
},
|
},
|
||||||
getViewport: () => {
|
getViewport: () => {
|
||||||
|
|||||||
+6
-12
@@ -43,7 +43,7 @@ const createStore = () =>
|
|||||||
set({ nodeInternals, edges: nextEdges, hasDefaultNodes, hasDefaultEdges });
|
set({ nodeInternals, edges: nextEdges, hasDefaultNodes, hasDefaultEdges });
|
||||||
},
|
},
|
||||||
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => {
|
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => {
|
||||||
const { onNodesChange, transform, nodeInternals, fitViewOnInit, fitViewOnInitDone, fitViewOnInitOptions } = get();
|
const { onNodesChange, nodeInternals, fitViewOnInit, fitViewOnInitDone, fitViewOnInitOptions } = get();
|
||||||
|
|
||||||
const changes: NodeDimensionChange[] = updates.reduce<NodeDimensionChange[]>((res, update) => {
|
const changes: NodeDimensionChange[] = updates.reduce<NodeDimensionChange[]>((res, update) => {
|
||||||
const node = nodeInternals.get(update.id);
|
const node = nodeInternals.get(update.id);
|
||||||
@@ -57,12 +57,14 @@ const createStore = () =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (doUpdate) {
|
if (doUpdate) {
|
||||||
const handleBounds = getHandleBounds(update.nodeElement, transform[2]);
|
|
||||||
nodeInternals.set(node.id, {
|
nodeInternals.set(node.id, {
|
||||||
...node,
|
...node,
|
||||||
[internalsSymbol]: {
|
[internalsSymbol]: {
|
||||||
...node[internalsSymbol],
|
...node[internalsSymbol],
|
||||||
handleBounds,
|
handleBounds: {
|
||||||
|
source: getHandleBounds('.source', update.nodeElement),
|
||||||
|
target: getHandleBounds('.target', update.nodeElement),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
...dimensions,
|
...dimensions,
|
||||||
});
|
});
|
||||||
@@ -99,16 +101,8 @@ const createStore = () =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (positionChanged) {
|
if (positionChanged) {
|
||||||
change.positionAbsolute = node.position;
|
change.positionAbsolute = node.positionAbsolute;
|
||||||
change.position = node.position;
|
change.position = node.position;
|
||||||
|
|
||||||
if (node.parentNode) {
|
|
||||||
const parentNode = nodeInternals.get(node.parentNode);
|
|
||||||
change.position = {
|
|
||||||
x: change.position.x - (parentNode?.positionAbsolute?.x ?? 0),
|
|
||||||
y: change.position.y - (parentNode?.positionAbsolute?.y ?? 0),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return change;
|
return change;
|
||||||
|
|||||||
+1
-1
@@ -110,8 +110,8 @@ export type NodeBounds = XYPosition & {
|
|||||||
|
|
||||||
export type NodeDragItem = {
|
export type NodeDragItem = {
|
||||||
id: string;
|
id: string;
|
||||||
// relative node position
|
|
||||||
position: XYPosition;
|
position: XYPosition;
|
||||||
|
positionAbsolute: XYPosition;
|
||||||
// distance from the mouse cursor to the node when start dragging
|
// distance from the mouse cursor to the node when start dragging
|
||||||
distance: XYPosition;
|
distance: XYPosition;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
|
|||||||
Reference in New Issue
Block a user