diff --git a/packages/core/src/composables/useDrag.ts b/packages/core/src/composables/useDrag.ts index 0921799c..f1d7b24a 100644 --- a/packages/core/src/composables/useDrag.ts +++ b/packages/core/src/composables/useDrag.ts @@ -18,26 +18,28 @@ interface UseDragParams { } function useDrag(params: UseDragParams) { - const { - vueFlowRef, - snapToGrid, - snapGrid, - noDragClassName, - nodes, - nodeExtent, - viewport, - autoPanOnNodeDrag, - nodesDraggable, - panBy, - findNode, - multiSelectionActive, - nodesSelectionActive, - selectNodesOnDrag, - removeSelectedElements, - addSelectedNodes, - updateNodePositions, - emits, - } = $(useVueFlow()) + + const { + vueFlowRef, + snapToGrid, + snapGrid, + noDragClassName, + nodes, + nodeExtent, + viewport, + autoPanOnNodeDrag, + nodesDraggable, + panBy, + findNode, + multiSelectionActive, + nodesSelectionActive, + selectNodesOnDrag, + removeSelectedElements, + addSelectedNodes, + updateNodePositions, + emits, + translateExtent, + } = $(useVueFlow()) const { onStart, onDrag, onStop, el, disabled, id, selectable } = params @@ -58,7 +60,7 @@ function useDrag(params: UseDragParams) { const getPointerPosition = useGetPointerPosition() - const updateNodes = ({ x, y }: XYPosition) => { + const updateNodes = ({ x, y }: XYPosition, onBeforeUpdate: (position: XYPosition) => XYPosition = (xyPos) => xyPos) => { lastPos = { x, y } let hasChange = false @@ -71,7 +73,7 @@ function useDrag(params: UseDragParams) { nextPosition.y = snapGrid[1] * Math.round(nextPosition.y / snapGrid[1]) } - const { computedPosition } = calcNextPosition( + let { computedPosition } = calcNextPosition( n, nextPosition, emits.error, @@ -79,7 +81,9 @@ function useDrag(params: UseDragParams) { n.parentNode ? findNode(n.parentNode) : undefined, ) - // we want to make sure that we only fire a change event when there is a changes + computedPosition = onBeforeUpdate(computedPosition) + + // we want to make sure that we only fire a change event when there is a change hasChange = hasChange || n.position.x !== computedPosition.x || n.position.y !== computedPosition.y n.position = computedPosition @@ -108,19 +112,23 @@ function useDrag(params: UseDragParams) { const autoPan = (): void => { if (!containerBounds) { - return - } + return + } const [xMovement, yMovement] = calcAutoPan(mousePosition, containerBounds) if (xMovement !== 0 || yMovement !== 0) { - lastPos.x = (lastPos.x ?? 0) - xMovement / viewport.zoom - lastPos.y = (lastPos.y ?? 0) - yMovement / viewport.zoom + const nextPos = { + x: (lastPos.x ?? 0) - xMovement / viewport.zoom, + y: (lastPos.y ?? 0) - yMovement / viewport.zoom, + } - updateNodes(lastPos as XYPosition) + updateNodes(nextPos) - panBy({ x: xMovement, y: yMovement }) - } + panBy({ x: xMovement, y: yMovement }) + + lastPos = nextPos + } autoPanId = requestAnimationFrame(autoPan) } diff --git a/packages/core/src/store/actions.ts b/packages/core/src/store/actions.ts index 24e30168..98c7ad86 100644 --- a/packages/core/src/store/actions.ts +++ b/packages/core/src/store/actions.ts @@ -629,7 +629,7 @@ export function useActions( return partiallyVisible || overlappingArea >= Number(nodeOrRect.width) * Number(nodeOrRect.height) } - const panBy: Actions['panBy'] = (delta) => { + const panBy: Actions['panBy'] = (delta, onBeforeTransform) => { const { viewport, dimensions, d3Zoom, d3Selection, translateExtent } = state if (!d3Zoom || !d3Selection || (!delta.x && !delta.y)) { @@ -645,6 +645,8 @@ export function useActions( const constrainedTransform = d3Zoom.constrain()(nextTransform, extent, translateExtent) + onBeforeTransform?.(constrainedTransform) + d3Zoom.transform(d3Selection, constrainedTransform) } diff --git a/packages/core/src/types/store.ts b/packages/core/src/types/store.ts index 9b9fa2d8..f9a4b3af 100644 --- a/packages/core/src/types/store.ts +++ b/packages/core/src/types/store.ts @@ -1,5 +1,6 @@ import type { CSSProperties, ComputedRef, ToRefs } from 'vue' import type { KeyFilter } from '@vueuse/core' +import type { ZoomTransform } from 'd3-zoom' import type { Dimensions, ElementData, @@ -269,7 +270,7 @@ export interface Actions extends ViewportFunctions { /** check if a node is intersecting with a defined area */ isNodeIntersecting: IsNodeIntersecting /** pan the viewport */ - panBy: (delta: XYPosition) => void + panBy: (delta: XYPosition, onBeforeTransform?: (transform: ZoomTransform) => void) => void /** reset state to defaults */ $reset: () => void