fix(handle-calculation): dont rely on current viewport #2032 #2276

This commit is contained in:
moklick
2022-07-12 15:46:23 +02:00
parent 481420c930
commit 30cd89917e
4 changed files with 32 additions and 40 deletions
+7 -9
View File
@@ -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 });
+3 -6
View File
@@ -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>
); );
+17 -22
View File
@@ -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),
}; };
}); });
}; };
+5 -3
View File
@@ -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,
}); });