diff --git a/.changeset/tidy-onions-talk.md b/.changeset/tidy-onions-talk.md new file mode 100644 index 00000000..42f6cdcf --- /dev/null +++ b/.changeset/tidy-onions-talk.md @@ -0,0 +1,7 @@ +--- +'@xyflow/system': patch +'@xyflow/react': patch +'@xyflow/svelte': patch +--- + +Fix node dragging & resizing while zooming on flow that does not cover whole browser window. diff --git a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx index 63128c09..0876c994 100644 --- a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx +++ b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx @@ -56,13 +56,14 @@ function ResizeControl({ domNode: resizeControlRef.current, nodeId: id, getStoreItems: () => { - const { nodeLookup, transform, snapGrid, snapToGrid, nodeOrigin } = store.getState(); + const { nodeLookup, transform, snapGrid, snapToGrid, nodeOrigin, domNode } = store.getState(); return { nodeLookup, transform, snapGrid, snapToGrid, nodeOrigin, + paneDomNode: domNode, }; }, onChange: (change: XYResizerChange, childChanges: XYResizerChildChange[]) => { diff --git a/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte b/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte index 1e5f39e2..7e686880 100644 --- a/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte +++ b/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte @@ -35,7 +35,7 @@ let className: $$Props['class'] = ''; export { className as class }; - const { nodeLookup, snapGrid, viewport, nodes, nodeOrigin } = useStore(); + const { nodeLookup, snapGrid, viewport, nodes, nodeOrigin, domNode } = useStore(); const contextNodeId = getContext('svelteflow__node_id'); $: id = typeof nodeId === 'string' ? nodeId : contextNodeId; @@ -65,7 +65,8 @@ transform: [$viewport.x, $viewport.y, $viewport.zoom], snapGrid: $snapGrid ?? undefined, snapToGrid: !!$snapGrid, - nodeOrigin: $nodeOrigin + nodeOrigin: $nodeOrigin, + paneDomNode: $domNode }; }, onChange: (change: XYResizerChange, childChanges: XYResizerChildChange[]) => { diff --git a/packages/system/src/utils/dom.ts b/packages/system/src/utils/dom.ts index 2cd2bf5c..88654772 100644 --- a/packages/system/src/utils/dom.ts +++ b/packages/system/src/utils/dom.ts @@ -5,14 +5,18 @@ export type GetPointerPositionParams = { transform: Transform; snapGrid?: SnapGrid; snapToGrid?: boolean; + containerBounds: DOMRect | null; }; export function getPointerPosition( event: MouseEvent | TouchEvent, - { snapGrid = [0, 0], snapToGrid = false, transform }: GetPointerPositionParams + { snapGrid = [0, 0], snapToGrid = false, transform, containerBounds }: GetPointerPositionParams ): XYPosition & { xSnapped: number; ySnapped: number } { const { x, y } = getEventPosition(event); - const pointerPos = pointToRendererPoint({ x, y }, transform); + const pointerPos = pointToRendererPoint( + { x: x - (containerBounds?.left ?? 0), y: y - (containerBounds?.top ?? 0) }, + transform + ); const { x: xSnapped, y: ySnapped } = snapToGrid ? snapPosition(pointerPos, snapGrid) : pointerPos; // we need the snapped position in order to be able to skip unnecessary drag events diff --git a/packages/system/src/xydrag/XYDrag.ts b/packages/system/src/xydrag/XYDrag.ts index 688d3e09..adf72e5c 100644 --- a/packages/system/src/xydrag/XYDrag.ts +++ b/packages/system/src/xydrag/XYDrag.ts @@ -257,7 +257,7 @@ export function XYDrag voi onNodeMouseDown?.(nodeId); } - const pointerPos = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid }); + const pointerPos = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid, containerBounds }); lastPos = pointerPos; dragItems = getDragItems(nodeLookup, nodesDraggable, pointerPos, nodeId); @@ -281,6 +281,7 @@ export function XYDrag voi .clickDistance(nodeClickDistance) .on('start', (event: UseDragEvent) => { const { domNode, nodeDragThreshold, transform, snapGrid, snapToGrid } = getStoreItems(); + containerBounds = domNode?.getBoundingClientRect() || null; abortDrag = false; @@ -288,14 +289,13 @@ export function XYDrag voi startDrag(event); } - const pointerPos = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid }); + const pointerPos = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid, containerBounds }); lastPos = pointerPos; - containerBounds = domNode?.getBoundingClientRect() || null; mousePosition = getEventPosition(event.sourceEvent, containerBounds!); }) .on('drag', (event: UseDragEvent) => { const { autoPanOnNodeDrag, transform, snapGrid, snapToGrid, nodeDragThreshold, nodeLookup } = getStoreItems(); - const pointerPos = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid }); + const pointerPos = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid, containerBounds }); if ( (event.sourceEvent.type === 'touchmove' && event.sourceEvent.touches.length > 1) || diff --git a/packages/system/src/xyresizer/XYResizer.ts b/packages/system/src/xyresizer/XYResizer.ts index c9dae4aa..1f4ba321 100644 --- a/packages/system/src/xyresizer/XYResizer.ts +++ b/packages/system/src/xyresizer/XYResizer.ts @@ -45,6 +45,7 @@ type XYResizerParams = { snapGrid?: [number, number]; snapToGrid: boolean; nodeOrigin: NodeOrigin; + paneDomNode: HTMLDivElement | null; }; onChange: (changes: XYResizerChange, childChanges: XYResizerChildChange[]) => void; onEnd?: () => void; @@ -109,6 +110,7 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X const controlDirection = getControlDirection(controlPosition); let node: InternalNodeBase | undefined = undefined; + let containerBounds: DOMRect | null = null; let childNodes: XYResizerChildChange[] = []; let parentNode: InternalNodeBase | undefined = undefined; // Needed to fix expandParent let parentExtent: CoordinateExtent | undefined = undefined; @@ -116,14 +118,20 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X const dragHandler = drag() .on('start', (event: ResizeDragEvent) => { - const { nodeLookup, transform, snapGrid, snapToGrid, nodeOrigin } = getStoreItems(); + const { nodeLookup, transform, snapGrid, snapToGrid, nodeOrigin, paneDomNode } = getStoreItems(); node = nodeLookup.get(nodeId); if (!node) { return; } - const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid }); + containerBounds = paneDomNode?.getBoundingClientRect() ?? null; + const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { + transform, + snapGrid, + snapToGrid, + containerBounds, + }); prevValues = { width: node.measured.width ?? 0, @@ -178,7 +186,13 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X }) .on('drag', (event: ResizeDragEvent) => { const { transform, snapGrid, snapToGrid, nodeOrigin: storeNodeOrigin } = getStoreItems(); - const pointerPosition = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid }); + + const pointerPosition = getPointerPosition(event.sourceEvent, { + transform, + snapGrid, + snapToGrid, + containerBounds, + }); const childChanges: XYResizerChildChange[] = []; if (!node) {