From ea8b9e005083244bf5891db39c248f07bdd68522 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Thu, 22 Aug 2024 11:00:16 +0200 Subject: [PATCH] clamp position on adoptUserNode --- .../react/src/components/NodeWrapper/index.tsx | 13 ++++--------- packages/react/src/store/index.ts | 7 ++++++- packages/svelte/src/lib/store/initial-store.ts | 2 +- packages/svelte/src/lib/store/utils.ts | 8 ++++++-- packages/system/src/utils/store.ts | 17 +++++++++++++++-- 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/packages/react/src/components/NodeWrapper/index.tsx b/packages/react/src/components/NodeWrapper/index.tsx index 7dcff4d9..61e13166 100644 --- a/packages/react/src/components/NodeWrapper/index.tsx +++ b/packages/react/src/components/NodeWrapper/index.tsx @@ -2,7 +2,6 @@ import { type MouseEvent, type KeyboardEvent } from 'react'; import cc from 'classcat'; import { shallow } from 'zustand/shallow'; import { - clampPosition, elementSelectionKeys, errorMessages, getNodeDimensions, @@ -88,10 +87,6 @@ export function NodeWrapper({ const nodeDimensions = getNodeDimensions(node); const inlineDimensions = getNodeInlineStyleDimensions(node); - // TODO: clamping should happen earlier - const clampedPosition = nodeExtent - ? clampPosition(internals.positionAbsolute, nodeExtent, nodeDimensions) - : internals.positionAbsolute; const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave; @@ -147,7 +142,7 @@ export function NodeWrapper({ store.setState({ ariaLiveMessage: `Moved selected node ${event.key .replace('Arrow', '') - .toLowerCase()}. New position, x: ${~~clampedPosition.x}, y: ${~~clampedPosition.y}`, + .toLowerCase()}. New position, x: ${~~internals.positionAbsolute.x}, y: ${~~internals.positionAbsolute.y}`, }); moveSelectedNodes({ @@ -178,7 +173,7 @@ export function NodeWrapper({ ref={nodeRef} style={{ zIndex: internals.z, - transform: `translate(${clampedPosition.x}px,${clampedPosition.y}px)`, + transform: `translate(${internals.positionAbsolute.x}px,${internals.positionAbsolute.y}px)`, pointerEvents: hasPointerEvents ? 'all' : 'none', visibility: hasDimensions ? 'visible' : 'hidden', ...node.style, @@ -203,8 +198,8 @@ export function NodeWrapper({ id={id} data={node.data} type={nodeType} - positionAbsoluteX={clampedPosition.x} - positionAbsoluteY={clampedPosition.y} + positionAbsoluteX={internals.positionAbsolute.x} + positionAbsoluteY={internals.positionAbsolute.y} selected={node.selected} selectable={isSelectable} draggable={isDraggable} diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index 46ed38a6..5990c2d7 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -54,7 +54,12 @@ const createStore = ({ // // When this happens, we take the note objects passed by the user and extend them with fields // relevant for internal React Flow operations. - adoptUserNodes(nodes, nodeLookup, parentLookup, { nodeOrigin, elevateNodesOnSelect, checkEquality: true }); + adoptUserNodes(nodes, nodeLookup, parentLookup, { + nodeOrigin, + nodeExtent, + elevateNodesOnSelect, + checkEquality: true, + }); set({ nodes }); }, diff --git a/packages/svelte/src/lib/store/initial-store.ts b/packages/svelte/src/lib/store/initial-store.ts index ffd7703a..142460a7 100644 --- a/packages/svelte/src/lib/store/initial-store.ts +++ b/packages/svelte/src/lib/store/initial-store.ts @@ -114,7 +114,7 @@ export const getInitialStore = ({ return { flowId: writable(null), - nodes: createNodesStore(nodes, nodeLookup, parentLookup, storeNodeOrigin), + nodes: createNodesStore(nodes, nodeLookup, parentLookup, storeNodeOrigin, storeNodeExtent), nodeLookup: readable>(nodeLookup), parentLookup: readable>(parentLookup), edgeLookup: readable>(edgeLookup), diff --git a/packages/svelte/src/lib/store/utils.ts b/packages/svelte/src/lib/store/utils.ts index e0b53c46..30af8f73 100644 --- a/packages/svelte/src/lib/store/utils.ts +++ b/packages/svelte/src/lib/store/utils.ts @@ -15,7 +15,9 @@ import { type EdgeLookup, type NodeLookup, type ParentLookup, - type NodeOrigin + type NodeOrigin, + infiniteExtent, + type CoordinateExtent } from '@xyflow/system'; import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, InternalNode, Node } from '$lib/types'; @@ -131,7 +133,8 @@ export const createNodesStore = ( nodes: Node[], nodeLookup: NodeLookup, parentLookup: ParentLookup, - nodeOrigin: NodeOrigin = [0, 0] + nodeOrigin: NodeOrigin = [0, 0], + nodeExtent: CoordinateExtent = infiniteExtent ): { subscribe: (this: void, run: Subscriber) => Unsubscriber; update: (this: void, updater: Updater) => void; @@ -148,6 +151,7 @@ export const createNodesStore = ( adoptUserNodes(nds, nodeLookup, parentLookup, { elevateNodesOnSelect, nodeOrigin, + nodeExtent, defaults, checkEquality: false }); diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index d1bd40fc..8f8749d8 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -18,7 +18,14 @@ import { ParentLookup, } from '../types'; import { getDimensions, getHandleBounds } from './dom'; -import { getBoundsOfRects, getNodeDimensions, isNumeric, nodeToRect } from './general'; +import { + clampPosition, + getBoundsOfRects, + getNodeDimensions, + isCoordinateExtent, + isNumeric, + nodeToRect, +} from './general'; import { getNodePositionWithOrigin } from './graph'; import { ParentExpandChild } from './types'; @@ -28,6 +35,7 @@ const defaultOptions = { elevateNodesOnSelect: true, defaults: {}, }; + const adoptUserNodesDefaultOptions = { ...defaultOptions, checkEquality: true, @@ -63,6 +71,7 @@ export function adoptUserNodes( options?: UpdateNodesOptions ) { const _options = { ...adoptUserNodesDefaultOptions, ...options }; + const tmpLookup = new Map(nodeLookup); nodeLookup.clear(); parentLookup.clear(); @@ -74,6 +83,10 @@ export function adoptUserNodes( if (_options.checkEquality && userNode === internalNode?.internals.userNode) { nodeLookup.set(userNode.id, internalNode); } else { + const positionOrigin = getNodePositionWithOrigin(userNode, _options.nodeOrigin); + const extent = isCoordinateExtent(userNode.extent) ? userNode.extent : _options.nodeExtent; + const positionAbsolute = clampPosition(positionOrigin, extent, getNodeDimensions(userNode)); + internalNode = { ..._options.defaults, ...userNode, @@ -82,7 +95,7 @@ export function adoptUserNodes( height: userNode.measured?.height, }, internals: { - positionAbsolute: getNodePositionWithOrigin(userNode, _options.nodeOrigin), + positionAbsolute, // if user re-initializes the node or removes `measured` for whatever reason, we reset the handleBounds so that the node gets re-measured handleBounds: !userNode.measured ? undefined : internalNode?.internals.handleBounds, z: calculateZ(userNode, selectedNodeZ),