diff --git a/.changeset/early-walls-attack.md b/.changeset/early-walls-attack.md new file mode 100644 index 000000000..1844df3e7 --- /dev/null +++ b/.changeset/early-walls-attack.md @@ -0,0 +1,5 @@ +--- +"@vue-flow/core": patch +--- + +check if event on drag end is mouse/touch event or a usedrag event diff --git a/packages/core/src/components/Nodes/NodeWrapper.ts b/packages/core/src/components/Nodes/NodeWrapper.ts index 9465525e5..d54d76914 100644 --- a/packages/core/src/components/Nodes/NodeWrapper.ts +++ b/packages/core/src/components/Nodes/NodeWrapper.ts @@ -22,10 +22,11 @@ import { elementSelectionKeys, getXYZPos, handleNodeClick, + snapPosition, } from '../../utils' import { NodeId, NodeRef, Slots } from '../../context' import { isInputDOMNode, useDrag, useNode, useNodeHooks, useUpdateNodePositions, useVueFlow } from '../../composables' -import type { NodeComponent } from '../../types' +import type { MouseTouchEvent, NodeComponent } from '../../types' interface Props { id: string @@ -321,14 +322,15 @@ const NodeWrapper = defineComponent({ } /** this re-calculates the current position, necessary for clamping by a node's extent */ function clampPosition() { - const nextPos = node.computedPosition - - if (snapToGrid.value) { - nextPos.x = snapGrid.value[0] * Math.round(nextPos.x / snapGrid.value[0]) - nextPos.y = snapGrid.value[1] * Math.round(nextPos.y / snapGrid.value[1]) - } - - const { computedPosition, position } = calcNextPosition(node, nextPos, emits.error, nodeExtent.value, parentNode.value) + const nextPosition = node.computedPosition + + const { computedPosition, position } = calcNextPosition( + node, + snapToGrid.value ? snapPosition(nextPosition, snapGrid.value) : nextPosition, + emits.error, + nodeExtent.value, + parentNode.value, + ) // only overwrite positions if there are changes when clamping if (node.computedPosition.x !== computedPosition.x || node.computedPosition.y !== computedPosition.y) { @@ -372,7 +374,7 @@ const NodeWrapper = defineComponent({ return emit.doubleClick({ event, node }) } - function onSelectNode(event: MouseEvent) { + function onSelectNode(event: MouseTouchEvent) { if (isSelectable.value && (!selectNodesOnDrag.value || !isDraggable.value || nodeDragThreshold.value > 0)) { handleNodeClick( node, diff --git a/packages/core/src/composables/useDrag.ts b/packages/core/src/composables/useDrag.ts index b505d334e..65f9fbfb3 100644 --- a/packages/core/src/composables/useDrag.ts +++ b/packages/core/src/composables/useDrag.ts @@ -3,7 +3,7 @@ import { drag } from 'd3-drag' import { select } from 'd3-selection' import type { MaybeRefOrGetter, Ref } from 'vue' import { ref, toValue, watch } from 'vue' -import type { NodeDragEvent, NodeDragItem, XYPosition } from '../types' +import type { MouseTouchEvent, NodeDragEvent, NodeDragItem, XYPosition } from '../types' import { calcAutoPan, calcNextPosition, @@ -12,6 +12,8 @@ import { getEventPosition, handleNodeClick, hasSelector, + isUseDragEvent, + snapPosition, } from '../utils' import { useGetPointerPosition, useVueFlow } from '.' @@ -21,7 +23,7 @@ interface UseDragParams { onStart: (event: NodeDragEvent) => void onDrag: (event: NodeDragEvent) => void onStop: (event: NodeDragEvent) => void - onClick?: (event: MouseEvent) => void + onClick?: (event: MouseTouchEvent) => void el: Ref disabled?: MaybeRefOrGetter selectable?: MaybeRefOrGetter @@ -87,14 +89,9 @@ export function useDrag(params: UseDragParams) { dragItems = dragItems.map((n) => { const nextPosition = { x: x - n.distance.x, y: y - n.distance.y } - if (snapToGrid.value) { - nextPosition.x = snapGrid.value[0] * Math.round(nextPosition.x / snapGrid.value[0]) - nextPosition.y = snapGrid.value[1] * Math.round(nextPosition.y / snapGrid.value[1]) - } - const { computedPosition } = calcNextPosition( n, - nextPosition, + snapToGrid.value ? snapPosition(nextPosition, snapGrid.value) : nextPosition, emits.error, nodeExtent.value, n.parentNode ? findNode(n.parentNode) : undefined, @@ -171,7 +168,7 @@ export function useDrag(params: UseDragParams) { ) } - const pointerPos = getPointerPosition(event) + const pointerPos = getPointerPosition(event.sourceEvent) lastPos = pointerPos dragItems = getDragItems(nodes.value, nodesDraggable.value, pointerPos, findNode, id) @@ -195,14 +192,14 @@ export function useDrag(params: UseDragParams) { startDrag(event, nodeEl) } - lastPos = getPointerPosition(event) + lastPos = getPointerPosition(event.sourceEvent) containerBounds = vueFlowRef.value?.getBoundingClientRect() || null mousePosition = getEventPosition(event.sourceEvent, containerBounds!) } const eventDrag = (event: UseDragEvent, nodeEl: Element) => { - const pointerPos = getPointerPosition(event) + const pointerPos = getPointerPosition(event.sourceEvent) if (!autoPanStarted && dragStarted && autoPanOnNodeDrag.value) { autoPanStarted = true @@ -229,8 +226,10 @@ export function useDrag(params: UseDragParams) { } const eventEnd = (event: UseDragEvent) => { - if (!dragStarted && !dragging.value && !multiSelectionActive.value) { - const pointerPos = getPointerPosition(event) + if (!isUseDragEvent(event) && !dragStarted && !dragging.value && !multiSelectionActive.value) { + const evt = event as MouseTouchEvent + + const pointerPos = getPointerPosition(evt) const x = pointerPos.xSnapped - (lastPos.x ?? 0) const y = pointerPos.ySnapped - (lastPos.y ?? 0) @@ -238,7 +237,7 @@ export function useDrag(params: UseDragParams) { // dispatch a click event if the node was attempted to be dragged but the threshold was not exceeded if (distance !== 0 && distance <= nodeDragThreshold.value) { - onClick?.(event.sourceEvent) + onClick?.(evt) } return diff --git a/packages/core/src/composables/useGetPointerPosition.ts b/packages/core/src/composables/useGetPointerPosition.ts index 7ff500e09..6715b22c7 100644 --- a/packages/core/src/composables/useGetPointerPosition.ts +++ b/packages/core/src/composables/useGetPointerPosition.ts @@ -1,5 +1,7 @@ -import type { UseDragEvent } from './useDrag' +import { getEventPosition, isUseDragEvent, pointToRendererPoint, snapPosition } from '../utils' +import type { MouseTouchEvent } from '../types' import { useVueFlow } from './useVueFlow' +import type { UseDragEvent } from './useDrag' /** * Composable that returns a function to get the pointer position @@ -10,19 +12,17 @@ export function useGetPointerPosition() { const { viewport, snapGrid, snapToGrid } = useVueFlow() // returns the pointer position projected to the VF coordinate system - return ({ sourceEvent }: UseDragEvent) => { - const x = sourceEvent.touches ? sourceEvent.touches[0].clientX : sourceEvent.clientX - const y = sourceEvent.touches ? sourceEvent.touches[0].clientY : sourceEvent.clientY + return (event: UseDragEvent | MouseTouchEvent) => { + const evt = isUseDragEvent(event) ? event.sourceEvent : event - const pointerPos = { - x: (x - viewport.value.x) / viewport.value.zoom, - y: (y - viewport.value.y) / viewport.value.zoom, - } + const { x, y } = getEventPosition(evt) + const pointerPos = pointToRendererPoint({ x, y }, viewport.value) + const { x: xSnapped, y: ySnapped } = snapToGrid.value ? snapPosition(pointerPos, snapGrid.value) : pointerPos // we need the snapped position in order to be able to skip unnecessary drag events return { - xSnapped: snapToGrid.value ? snapGrid.value[0] * Math.round(pointerPos.x / snapGrid.value[0]) : pointerPos.x, - ySnapped: snapToGrid.value ? snapGrid.value[1] * Math.round(pointerPos.y / snapGrid.value[1]) : pointerPos.y, + xSnapped, + ySnapped, ...pointerPos, } } diff --git a/packages/core/src/utils/general.ts b/packages/core/src/utils/general.ts index 53eb4b842..5bc728af9 100644 --- a/packages/core/src/utils/general.ts +++ b/packages/core/src/utils/general.ts @@ -1,13 +1,19 @@ -import type { GraphNode } from '../types' +import type { GraphNode, SnapGrid, XYPosition } from '../types' +import type { UseDragEvent } from '../composables' export function isMouseEvent(event: MouseEvent | TouchEvent): event is MouseEvent { return 'clientX' in event } +export function isUseDragEvent(event: any): event is UseDragEvent { + return 'sourceEvent' in event +} + export function getEventPosition(event: MouseEvent | TouchEvent, bounds?: DOMRect) { - const isMouseTriggered = isMouseEvent(event) - const evtX = isMouseTriggered ? event.clientX : event.touches?.[0].clientX - const evtY = isMouseTriggered ? event.clientY : event.touches?.[0].clientY + const isMouse = isMouseEvent(event) + + const evtX = isMouse ? event.clientX : event.touches?.[0].clientX + const evtY = isMouse ? event.clientY : event.touches?.[0].clientY return { x: evtX - (bounds?.left ?? 0), @@ -23,3 +29,10 @@ export function getNodeDimensions(node: GraphNode): { width: number; height: num height: node.dimensions?.height ?? node.height ?? 0, } } + +export function snapPosition(position: XYPosition, snapGrid: SnapGrid = [1, 1]): XYPosition { + return { + x: snapGrid[0] * Math.round(position.x / snapGrid[0]), + y: snapGrid[1] * Math.round(position.y / snapGrid[1]), + } +} diff --git a/packages/core/src/utils/graph.ts b/packages/core/src/utils/graph.ts index d3e834267..209709b76 100644 --- a/packages/core/src/utils/graph.ts +++ b/packages/core/src/utils/graph.ts @@ -21,7 +21,7 @@ import type { XYPosition, XYZPosition, } from '../types' -import { isDef, warn } from '.' +import { isDef, snapPosition, warn } from '.' export function nodeToRect(node: GraphNode): Rect { return { @@ -299,21 +299,14 @@ export function pointToRendererPoint( { x, y }: XYPosition, { x: tx, y: ty, zoom: tScale }: ViewportTransform, snapToGrid: boolean = false, - [snapX, snapY]: [snapX: number, snapY: number] = [1, 1], + snapGrid: [snapX: number, snapY: number] = [1, 1], ): XYPosition { const position: XYPosition = { x: (x - tx) / tScale, y: (y - ty) / tScale, } - if (snapToGrid) { - return { - x: snapX * Math.round(position.x / snapX), - y: snapY * Math.round(position.y / snapY), - } - } - - return position + return snapToGrid ? snapPosition(position, snapGrid) : position } function getBoundsOfBoxes(box1: Box, box2: Box): Box {