From 30cd89917ead06204bff5924e767280add07c7df Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 12 Jul 2022 15:46:23 +0200 Subject: [PATCH] fix(handle-calculation): dont rely on current viewport #2032 #2276 --- example/src/SaveRestore/Controls.tsx | 16 +++++------- example/src/SaveRestore/index.tsx | 9 +++---- src/components/Nodes/utils.ts | 39 ++++++++++++---------------- src/store/index.ts | 8 +++--- 4 files changed, 32 insertions(+), 40 deletions(-) diff --git a/example/src/SaveRestore/Controls.tsx b/example/src/SaveRestore/Controls.tsx index 4c4537c7..c26d55d2 100644 --- a/example/src/SaveRestore/Controls.tsx +++ b/example/src/SaveRestore/Controls.tsx @@ -1,5 +1,5 @@ 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'; localforage.config({ @@ -12,20 +12,17 @@ const flowKey = 'example-flow'; const getNodeId = () => `randomnode_${+new Date()}`; type ControlsProps = { - rfInstance?: ReactFlowInstance; setNodes: Dispatch[]>>; setEdges: Dispatch[]>>; }; -const Controls: FC = ({ rfInstance, setNodes, setEdges }) => { - const { setViewport } = useReactFlow(); +const Controls: FC = ({ setNodes, setEdges }) => { + const { setViewport, toObject } = useReactFlow(); const onSave = useCallback(() => { - if (rfInstance) { - const flow = rfInstance.toObject(); - localforage.setItem(flowKey, flow); - } - }, [rfInstance]); + const flow = toObject(); + localforage.setItem(flowKey, flow); + }, [toObject]); const onRestore = useCallback(() => { const restoreFlow = async () => { @@ -33,6 +30,7 @@ const Controls: FC = ({ rfInstance, setNodes, setEdges }) => { if (flow) { const { x, y, zoom } = flow.viewport; + setNodes(flow.nodes || []); setEdges(flow.edges || []); setViewport({ x, y, zoom: zoom || 0 }); diff --git a/example/src/SaveRestore/index.tsx b/example/src/SaveRestore/index.tsx index c0aacccb..a28f2f4a 100644 --- a/example/src/SaveRestore/index.tsx +++ b/example/src/SaveRestore/index.tsx @@ -1,11 +1,10 @@ -import { useState } from 'react'; +import { useCallback } from 'react'; import ReactFlow, { ReactFlowProvider, Node, addEdge, Connection, Edge, - ReactFlowInstance, useNodesState, useEdgesState, } from 'react-flow-renderer'; @@ -22,10 +21,9 @@ const initialNodes: Node[] = [ const initialEdges: Edge[] = [{ id: 'e1-2', source: '1', target: '2' }]; const SaveRestore = () => { - const [rfInstance, setRfInstance] = useState(); const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); 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 ( @@ -35,9 +33,8 @@ const SaveRestore = () => { onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} onConnect={onConnect} - onInit={setRfInstance} > - + ); diff --git a/src/components/Nodes/utils.ts b/src/components/Nodes/utils.ts index 2b9b5137..a3d1a28f 100644 --- a/src/components/Nodes/utils.ts +++ b/src/components/Nodes/utils.ts @@ -4,21 +4,17 @@ 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(); +function getTranslateValues(domNode: HTMLDivElement): [number, number] { + if (typeof window === 'undefined' || !window.DOMMatrixReadOnly) { + return [0, 0]; + } - return { - source: getHandleBoundsByHandleType('.source', nodeElement, bounds, scale), - target: getHandleBoundsByHandleType('.target', nodeElement, bounds, scale), - }; -}; + const style = window.getComputedStyle(domNode); + const { m41, m42 } = new window.DOMMatrixReadOnly(style.transform); + return [m41, m42]; +} -export const getHandleBoundsByHandleType = ( - selector: string, - nodeElement: HTMLDivElement, - parentBounds: DOMRect, - k: number -): HandleElement[] | null => { +export const getHandleBounds = (selector: string, nodeElement: HTMLDivElement): HandleElement[] | null => { const handles = nodeElement.querySelectorAll(selector); if (!handles || !handles.length) { @@ -28,17 +24,16 @@ export const getHandleBoundsByHandleType = ( const handlesArray = Array.from(handles) as HTMLDivElement[]; 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; + // we don't use getBoundingClientRect here, because it includes the transform of the parent (scaled viewport) + // that we would then need to calculate out again in order to get the correct position. + const [translateX, translateY] = getTranslateValues(handle); 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: handle.offsetLeft + nodeElement.clientLeft + translateX, + y: handle.offsetTop + nodeElement.clientTop + translateY, + ...getDimensions(handle), }; }); }; diff --git a/src/store/index.ts b/src/store/index.ts index ddb09f7b..f67dde4b 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -43,7 +43,7 @@ const createStore = () => set({ nodeInternals, edges: nextEdges, hasDefaultNodes, hasDefaultEdges }); }, updateNodeDimensions: (updates: NodeDimensionUpdate[]) => { - const { onNodesChange, transform, nodeInternals, fitViewOnInit, fitViewOnInitDone, fitViewOnInitOptions } = get(); + const { onNodesChange, nodeInternals, fitViewOnInit, fitViewOnInitDone, fitViewOnInitOptions } = get(); const changes: NodeDimensionChange[] = updates.reduce((res, update) => { const node = nodeInternals.get(update.id); @@ -57,12 +57,14 @@ const createStore = () => ); if (doUpdate) { - const handleBounds = getHandleBounds(update.nodeElement, transform[2]); nodeInternals.set(node.id, { ...node, [internalsSymbol]: { ...node[internalsSymbol], - handleBounds, + handleBounds: { + source: getHandleBounds('.source', update.nodeElement), + target: getHandleBounds('.target', update.nodeElement), + }, }, ...dimensions, });