From e3b6befc5bfa5c947adb63fd6ceaa315036cbc45 Mon Sep 17 00:00:00 2001 From: Furkan Kalaycioglu Date: Sun, 24 Apr 2022 02:40:19 +0300 Subject: [PATCH 1/6] Implemented handleSelector functionality --- src/components/Nodes/wrapNode.tsx | 1 + src/hooks/useDrag.ts | 91 +++++++++++++++++++++---------- 2 files changed, 62 insertions(+), 30 deletions(-) diff --git a/src/components/Nodes/wrapNode.tsx b/src/components/Nodes/wrapNode.tsx index c4ca752e..401d7712 100644 --- a/src/components/Nodes/wrapNode.tsx +++ b/src/components/Nodes/wrapNode.tsx @@ -171,6 +171,7 @@ export default (NodeComponent: ComponentType) => { dragging: false, }); + // @TODO: Fix the bug "onNodeDragStop not getting called on first render" if (onNodeDragStop && node) { onNodeDragStop(event.sourceEvent as MouseEvent, { ...node, dragging: false }); } diff --git a/src/hooks/useDrag.ts b/src/hooks/useDrag.ts index c697ff98..3f20adfc 100644 --- a/src/hooks/useDrag.ts +++ b/src/hooks/useDrag.ts @@ -16,7 +16,6 @@ type UseDragParams = { nodeRef: RefObject; disabled?: boolean; noDragClassName?: string; - // @TODO: implement handleSelector functionality handleSelector?: string; nodeId?: string; }; @@ -42,7 +41,27 @@ function getParentNodePosition(nodeInternals: NodeInternals, nodeId?: string): X }; } -function useDrag({ onStart, onDrag, onStop, nodeRef, disabled = false, noDragClassName, nodeId }: UseDragParams) { +export function selectorExistsTargetToNode(target: Element, selector: string, nodeRef: RefObject): boolean { + let current = target; + do { + if (current?.matches(selector)) return true; + if (current === nodeRef.current) return false; + current = current.parentElement as Element; + } while (current); + + return false; +} + +function useDrag({ + onStart, + onDrag, + onStop, + nodeRef, + disabled = false, + noDragClassName, + handleSelector, + nodeId, +}: UseDragParams) { const store = useStoreApi(); const startPos = useRef({ x: 0, y: 0 }); const lastPos = useRef<{ x: number | null; y: number | null }>({ x: null, y: null }); @@ -52,48 +71,60 @@ function useDrag({ onStart, onDrag, onStop, nodeRef, disabled = false, noDragCla if (nodeRef?.current) { const selection = select(nodeRef.current); + let isDragAllowedBySelector = true; if (disabled) { selection.on('.drag', null); } else { const dragHandler = drag() .on('start', (event: UseDragEvent) => { - const { transform, nodeInternals } = store.getState(); - const offset = getOffset(event, nodeRef); - parentPos.current = getParentNodePosition(nodeInternals, nodeId); + if (handleSelector) { + isDragAllowedBySelector = selectorExistsTargetToNode(event.sourceEvent.target, handleSelector, nodeRef); + } + if (isDragAllowedBySelector) { + const { transform, nodeInternals } = store.getState(); + const offset = getOffset(event, nodeRef); + parentPos.current = getParentNodePosition(nodeInternals, nodeId); - startPos.current = { - x: offset.x - transform[0], - y: offset.y - transform[1], - }; + startPos.current = { + x: offset.x - transform[0], + y: offset.y - transform[1], + }; - onStart(event); + onStart(event); + } }) .on('drag', (event: UseDragEvent) => { - const { transform, snapGrid, snapToGrid } = store.getState(); - const pos = pointToRendererPoint( - { - x: event.x - startPos.current.x, - y: event.y - startPos.current.y, - }, - transform, - snapToGrid, - snapGrid - ); + if (isDragAllowedBySelector) { + const { transform, snapGrid, snapToGrid } = store.getState(); + const pos = pointToRendererPoint( + { + x: event.x - startPos.current.x, + y: event.y - startPos.current.y, + }, + transform, + snapToGrid, + snapGrid + ); - pos.x -= parentPos.current.x; - pos.y -= parentPos.current.y; + pos.x -= parentPos.current.x; + pos.y -= parentPos.current.y; - // skip events without movement - if (lastPos.current.x !== pos.x || lastPos.current.y !== pos.y) { - lastPos.current = pos; + // skip events without movement + if (lastPos.current.x !== pos.x || lastPos.current.y !== pos.y) { + lastPos.current = pos; - onDrag(event, { - dx: pos.x, - dy: pos.y, - }); + onDrag(event, { + dx: pos.x, + dy: pos.y, + }); + } + } + }) + .on('end', (event) => { + if (isDragAllowedBySelector) { + onStop(event); } }) - .on('end', onStop) .filter((event: any) => !event.ctrlKey && !event.button && !event.target.className.includes(noDragClassName)); selection.call(dragHandler); From 73118c356ca8e9faceea84809f6b854edc120686 Mon Sep 17 00:00:00 2001 From: Furkan Kalaycioglu Date: Sun, 24 Apr 2022 02:58:46 +0300 Subject: [PATCH 2/6] Removed redundant export --- src/hooks/useDrag.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useDrag.ts b/src/hooks/useDrag.ts index 3f20adfc..878a966d 100644 --- a/src/hooks/useDrag.ts +++ b/src/hooks/useDrag.ts @@ -41,7 +41,7 @@ function getParentNodePosition(nodeInternals: NodeInternals, nodeId?: string): X }; } -export function selectorExistsTargetToNode(target: Element, selector: string, nodeRef: RefObject): boolean { +function selectorExistsTargetToNode(target: Element, selector: string, nodeRef: RefObject): boolean { let current = target; do { if (current?.matches(selector)) return true; From f075e0742e02a013ad1ffe80792d51bc7b65c803 Mon Sep 17 00:00:00 2001 From: Furkan Kalaycioglu Date: Wed, 27 Apr 2022 20:37:59 +0300 Subject: [PATCH 3/6] Fixed the bug 'onNodeDragStop' not getting triggered Got rid of 'dragging' in the global node state --- .../Nodes/useMemoizedMouseHandler.ts | 5 +- src/components/Nodes/wrapNode.tsx | 65 ++++++++----------- src/components/NodesSelection/index.tsx | 5 -- src/container/NodeRenderer/index.tsx | 1 - src/hooks/useDrag.ts | 13 ++-- src/store/index.ts | 10 +-- src/store/utils.ts | 5 +- src/types/changes.ts | 1 - src/types/nodes.ts | 3 - src/utils/changes.ts | 4 -- src/utils/graph.ts | 4 +- 11 files changed, 41 insertions(+), 75 deletions(-) diff --git a/src/components/Nodes/useMemoizedMouseHandler.ts b/src/components/Nodes/useMemoizedMouseHandler.ts index 36334695..7d3067f3 100644 --- a/src/components/Nodes/useMemoizedMouseHandler.ts +++ b/src/components/Nodes/useMemoizedMouseHandler.ts @@ -5,18 +5,17 @@ import { ReactFlowState, Node } from '../../types'; function useMemoizedMouseHandler( id: string, - dragging: boolean, getState: GetState, handler?: (event: MouseEvent, node: Node) => void ) { const memoizedHandler = useCallback( (event: MouseEvent) => { - if (typeof handler !== 'undefined' && !dragging) { + if (typeof handler !== 'undefined') { const node = getState().nodeInternals.get(id)!; handler(event, { ...node }); } }, - [handler, dragging, id] + [handler, id] ); return memoizedHandler; diff --git a/src/components/Nodes/wrapNode.tsx b/src/components/Nodes/wrapNode.tsx index 401d7712..ab0e73bb 100644 --- a/src/components/Nodes/wrapNode.tsx +++ b/src/components/Nodes/wrapNode.tsx @@ -1,4 +1,14 @@ -import React, { useEffect, useRef, memo, ComponentType, CSSProperties, useMemo, MouseEvent, useCallback } from 'react'; +import React, { + useEffect, + useRef, + memo, + ComponentType, + CSSProperties, + useMemo, + MouseEvent, + useCallback, + useState, +} from 'react'; import cc from 'classcat'; import shallow from 'zustand/shallow'; @@ -41,7 +51,6 @@ export default (NodeComponent: ComponentType) => { sourcePosition, targetPosition, hidden, - dragging, resizeObserver, dragHandle, zIndex, @@ -69,11 +78,11 @@ export default (NodeComponent: ComponentType) => { [zIndex, xPos, yPos, hasPointerEvents, style] ); - const onMouseEnterHandler = useMemoizedMouseHandler(id, dragging, store.getState, onMouseEnter); - const onMouseMoveHandler = useMemoizedMouseHandler(id, dragging, store.getState, onMouseMove); - const onMouseLeaveHandler = useMemoizedMouseHandler(id, dragging, store.getState, onMouseLeave); - const onContextMenuHandler = useMemoizedMouseHandler(id, false, store.getState, onContextMenu); - const onNodeDoubleClickHandler = useMemoizedMouseHandler(id, false, store.getState, onNodeDoubleClick); + const onMouseEnterHandler = useMemoizedMouseHandler(id, store.getState, onMouseEnter); + const onMouseMoveHandler = useMemoizedMouseHandler(id, store.getState, onMouseMove); + const onMouseLeaveHandler = useMemoizedMouseHandler(id, store.getState, onMouseLeave); + const onContextMenuHandler = useMemoizedMouseHandler(id, store.getState, onContextMenu); + const onNodeDoubleClickHandler = useMemoizedMouseHandler(id, store.getState, onNodeDoubleClick); const onSelectNodeHandler = useCallback( (event: MouseEvent) => { @@ -121,15 +130,17 @@ export default (NodeComponent: ComponentType) => { [id, selected, selectNodesOnDrag, isSelectable, onNodeDragStart] ); + // As one of the props passed to a custom node + const [dragging, setDragging] = useState(false); + const onDrag = useCallback( (event: UseDragEvent, dragPos: UseDragData) => { - updateNodePosition({ id, dragging: true, diff: { x: dragPos.dx, y: dragPos.dy } }); - + updateNodePosition({ id, diff: { x: dragPos.dx, y: dragPos.dy } }); + setDragging(true); if (onNodeDrag) { const node = store.getState().nodeInternals.get(id)!; onNodeDrag(event.sourceEvent as MouseEvent, { ...node, - dragging: true, position: { x: node.position.x + dragPos.dx, y: node.position.y + dragPos.dy, @@ -146,37 +157,13 @@ export default (NodeComponent: ComponentType) => { const onDragStop = useCallback( (event: UseDragEvent) => { - // onDragStop also gets called when user just clicks on a node. - // Because of that we set dragging to true inside the onDrag handler and handle the click here - let node; - - if (onClick || onNodeDragStop) { - node = store.getState().nodeInternals.get(id)!; - } - - if (!dragging) { - if (isSelectable && !selectNodesOnDrag && !selected) { - addSelectedNodes([id]); - } - - if (onClick && node) { - onClick(event.sourceEvent as MouseEvent, { ...node }); - } - - return; - } - - updateNodePosition({ - id, - dragging: false, - }); - - // @TODO: Fix the bug "onNodeDragStop not getting called on first render" - if (onNodeDragStop && node) { - onNodeDragStop(event.sourceEvent as MouseEvent, { ...node, dragging: false }); + setDragging(false); + if (onNodeDragStop) { + const node = store.getState().nodeInternals.get(id)!; + onNodeDragStop(event.sourceEvent as MouseEvent, { ...node }); } }, - [id, isSelectable, selectNodesOnDrag, onClick, onNodeDragStop, dragging, selected] + [id, onNodeDragStop] ); useEffect(() => { diff --git a/src/components/NodesSelection/index.tsx b/src/components/NodesSelection/index.tsx index 735db606..494ea639 100644 --- a/src/components/NodesSelection/index.tsx +++ b/src/components/NodesSelection/index.tsx @@ -74,7 +74,6 @@ function NodesSelection({ x: data.dx, y: data.dy, }, - dragging: true, }); onSelectionDrag?.(event.sourceEvent, selectedNodes); @@ -84,10 +83,6 @@ function NodesSelection({ const onStop = useCallback( (event: UseDragEvent) => { - updateNodePosition({ - dragging: false, - }); - onSelectionDragStop?.(event.sourceEvent, selectedNodes); }, [selectedNodes, onSelectionDragStop] diff --git a/src/container/NodeRenderer/index.tsx b/src/container/NodeRenderer/index.tsx index 0a4be46f..7ff1cfa6 100644 --- a/src/container/NodeRenderer/index.tsx +++ b/src/container/NodeRenderer/index.tsx @@ -88,7 +88,6 @@ const NodeRenderer = (props: NodeRendererProps) => { hidden={node.hidden} xPos={node.positionAbsolute?.x ?? 0} yPos={node.positionAbsolute?.y ?? 0} - dragging={!!node.dragging} selectNodesOnDrag={props.selectNodesOnDrag} onClick={props.onNodeClick} onMouseEnter={props.onNodeMouseEnter} diff --git a/src/hooks/useDrag.ts b/src/hooks/useDrag.ts index 878a966d..f698af02 100644 --- a/src/hooks/useDrag.ts +++ b/src/hooks/useDrag.ts @@ -118,11 +118,12 @@ function useDrag({ dy: pos.y, }); } - } - }) - .on('end', (event) => { - if (isDragAllowedBySelector) { - onStop(event); + + event.on('end', (event) => { + if (isDragAllowedBySelector) { + onStop(event); + } + }); } }) .filter((event: any) => !event.ctrlKey && !event.button && !event.target.className.includes(noDragClassName)); @@ -134,7 +135,7 @@ function useDrag({ }; } } - }, [disabled, noDragClassName, nodeId]); + }, [onStart, onDrag, onStop, nodeRef, disabled, noDragClassName, handleSelector, nodeId]); return null; } diff --git a/src/store/index.ts b/src/store/index.ts index a1855a3d..d1c1e9c7 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -96,7 +96,7 @@ const createStore = () => onNodesChange?.(changes); } }, - updateNodePosition: ({ id, diff, dragging }: NodeDiffUpdate) => { + updateNodePosition: ({ id, diff }: NodeDiffUpdate) => { const { onNodesChange, nodeExtent, nodeInternals, hasDefaultNodes, snapGrid, snapToGrid } = get(); if (hasDefaultNodes || onNodesChange) { @@ -105,14 +105,10 @@ const createStore = () => nodeInternals.forEach((node) => { if (node.selected) { if (!node.parentNode || !isParentSelected(node, nodeInternals)) { - changes.push( - createPositionChange({ node, diff, dragging, nodeExtent, nodeInternals, snapToGrid, snapGrid }) - ); + changes.push(createPositionChange({ node, diff, nodeExtent, nodeInternals, snapToGrid, snapGrid })); } } else if (node.id === id) { - changes.push( - createPositionChange({ node, diff, dragging, nodeExtent, nodeInternals, snapToGrid, snapGrid }) - ); + changes.push(createPositionChange({ node, diff, nodeExtent, nodeInternals, snapToGrid, snapGrid })); } }); diff --git a/src/store/utils.ts b/src/store/utils.ts index 49ad662f..0d8796ec 100644 --- a/src/store/utils.ts +++ b/src/store/utils.ts @@ -44,7 +44,7 @@ export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals) const parentNodes: ParentNodes = {}; nodes.forEach((node) => { - const z = isNumeric(node.zIndex) ? node.zIndex : node.dragging || node.selected ? 1000 : 0; + const z = isNumeric(node.zIndex) ? node.zIndex : node.selected ? 1000 : 0; const internals: Node = { ...nodeInternals.get(node.id), @@ -112,7 +112,6 @@ type CreatePostionChangeParams = { nodeExtent: CoordinateExtent; nodeInternals: NodeInternals; diff?: XYPosition; - dragging?: boolean; snapToGrid?: boolean; snapGrid?: SnapGrid; }; @@ -120,7 +119,6 @@ type CreatePostionChangeParams = { export function createPositionChange({ node, diff, - dragging, nodeExtent, nodeInternals, snapToGrid, @@ -129,7 +127,6 @@ export function createPositionChange({ const change: NodePositionChange = { id: node.id, type: 'position', - dragging: !!dragging, }; if (diff) { diff --git a/src/types/changes.ts b/src/types/changes.ts index d896d03e..f833fbf0 100644 --- a/src/types/changes.ts +++ b/src/types/changes.ts @@ -13,7 +13,6 @@ export type NodePositionChange = { id: string; type: 'position'; position?: XYPosition; - dragging?: boolean; }; export type NodeSelectionChange = { diff --git a/src/types/nodes.ts b/src/types/nodes.ts index d8592cc4..4f0262b7 100644 --- a/src/types/nodes.ts +++ b/src/types/nodes.ts @@ -15,7 +15,6 @@ export interface Node { sourcePosition?: Position; hidden?: boolean; selected?: boolean; - dragging?: boolean; draggable?: boolean; selectable?: boolean; connectable?: boolean; @@ -79,7 +78,6 @@ export interface WrapNodeProps { sourcePosition: Position; targetPosition: Position; hidden?: boolean; - dragging: boolean; resizeObserver: ResizeObserver | null; dragHandle?: string; zIndex: number; @@ -96,7 +94,6 @@ export type NodeHandleBounds = { export type NodeDiffUpdate = { id?: string; diff?: XYPosition; - dragging?: boolean; }; export type NodeDimensionUpdate = { diff --git a/src/utils/changes.ts b/src/utils/changes.ts index e5278a6f..9279a3f0 100644 --- a/src/utils/changes.ts +++ b/src/utils/changes.ts @@ -69,10 +69,6 @@ function applyChanges(changes: any[], elements: any[]): any[] { updateItem.position = currentChange.position; } - if (typeof currentChange.dragging !== 'undefined') { - updateItem.dragging = currentChange.dragging; - } - if (updateItem.expandParent) { handleParentExpand(res, updateItem); } diff --git a/src/utils/graph.ts b/src/utils/graph.ts index a0927dbc..b71325e9 100644 --- a/src/utils/graph.ts +++ b/src/utils/graph.ts @@ -161,7 +161,7 @@ export const getNodesInside = ( const visibleNodes: Node[] = []; nodeInternals.forEach((node) => { - const { positionAbsolute, width, height, dragging, selectable = true } = node; + const { positionAbsolute, width, height, selectable = true } = node; if (excludeNonSelectableNodes && !selectable) { return false; @@ -172,7 +172,7 @@ export const getNodesInside = ( const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y)); const overlappingArea = Math.ceil(xOverlap * yOverlap); const notInitialized = - typeof width === 'undefined' || typeof height === 'undefined' || width === null || height === null || dragging; + typeof width === 'undefined' || typeof height === 'undefined' || width === null || height === null; const partiallyVisible = partially && overlappingArea > 0; const area = (width || 0) * (height || 0); From 5e501fab064d3b215be5e11bd7481fd7e505169a Mon Sep 17 00:00:00 2001 From: Furkan Kalaycioglu Date: Wed, 27 Apr 2022 20:40:38 +0300 Subject: [PATCH 4/6] Fixed cypress drag command to behave correctly with d3-drag --- cypress/support/commands.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index e79261d9..1331f17c 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -24,10 +24,14 @@ // -- This is will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) - Cypress.Commands.add('drag', (selector, { x, y }) => { - return cy.get(selector) - .trigger('mousedown', { which: 1 }) - .trigger('mousemove', { clientX: x, clientY: y }) - .trigger('mouseup', { force: true }); + return cy + .window() + .then((window) => + cy + .get(selector) + .trigger('mousedown', { which: 1, view: window }) + .trigger('mousemove', { clientX: x, clientY: y, force: true }) + .trigger('mouseup', { view: window }) + ); }); From 5737d133d409983578085517946ec1d51cbc841f Mon Sep 17 00:00:00 2001 From: Furkan Kalaycioglu Date: Wed, 27 Apr 2022 22:43:55 +0300 Subject: [PATCH 5/6] Cleanup of cond. statements by leveraging d3.drag.filter for handleSelector in useDrag --- src/hooks/useDrag.ts | 73 +++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/src/hooks/useDrag.ts b/src/hooks/useDrag.ts index f698af02..89b7fc9c 100644 --- a/src/hooks/useDrag.ts +++ b/src/hooks/useDrag.ts @@ -71,62 +71,57 @@ function useDrag({ if (nodeRef?.current) { const selection = select(nodeRef.current); - let isDragAllowedBySelector = true; if (disabled) { selection.on('.drag', null); } else { const dragHandler = drag() .on('start', (event: UseDragEvent) => { - if (handleSelector) { - isDragAllowedBySelector = selectorExistsTargetToNode(event.sourceEvent.target, handleSelector, nodeRef); - } - if (isDragAllowedBySelector) { - const { transform, nodeInternals } = store.getState(); - const offset = getOffset(event, nodeRef); - parentPos.current = getParentNodePosition(nodeInternals, nodeId); + const { transform, nodeInternals } = store.getState(); + const offset = getOffset(event, nodeRef); + parentPos.current = getParentNodePosition(nodeInternals, nodeId); - startPos.current = { - x: offset.x - transform[0], - y: offset.y - transform[1], - }; + startPos.current = { + x: offset.x - transform[0], + y: offset.y - transform[1], + }; - onStart(event); - } + onStart(event); }) .on('drag', (event: UseDragEvent) => { - if (isDragAllowedBySelector) { - const { transform, snapGrid, snapToGrid } = store.getState(); - const pos = pointToRendererPoint( - { - x: event.x - startPos.current.x, - y: event.y - startPos.current.y, - }, - transform, - snapToGrid, - snapGrid - ); + const { transform, snapGrid, snapToGrid } = store.getState(); + const pos = pointToRendererPoint( + { + x: event.x - startPos.current.x, + y: event.y - startPos.current.y, + }, + transform, + snapToGrid, + snapGrid + ); - pos.x -= parentPos.current.x; - pos.y -= parentPos.current.y; + pos.x -= parentPos.current.x; + pos.y -= parentPos.current.y; - // skip events without movement - if (lastPos.current.x !== pos.x || lastPos.current.y !== pos.y) { - lastPos.current = pos; + // skip events without movement + if (lastPos.current.x !== pos.x || lastPos.current.y !== pos.y) { + lastPos.current = pos; - onDrag(event, { - dx: pos.x, - dy: pos.y, - }); - } + onDrag(event, { + dx: pos.x, + dy: pos.y, + }); event.on('end', (event) => { - if (isDragAllowedBySelector) { - onStop(event); - } + onStop(event); }); } }) - .filter((event: any) => !event.ctrlKey && !event.button && !event.target.className.includes(noDragClassName)); + .filter((event: any) => { + const filter = !event.ctrlKey && !event.button && !event.target.className.includes(noDragClassName); + return handleSelector + ? selectorExistsTargetToNode(event.target, handleSelector, nodeRef) && filter + : filter; + }); selection.call(dragHandler); From fa582daa7a710087ed76aec53d79c9313a3896f0 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 10 May 2022 15:35:40 +0200 Subject: [PATCH 6/6] fix(wrapNode): repair onClick --- src/components/Nodes/wrapNode.tsx | 31 +++++++++++++++++++++++++------ src/hooks/useDrag.ts | 7 +++---- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/components/Nodes/wrapNode.tsx b/src/components/Nodes/wrapNode.tsx index ab0e73bb..0c113b55 100644 --- a/src/components/Nodes/wrapNode.tsx +++ b/src/components/Nodes/wrapNode.tsx @@ -58,6 +58,8 @@ export default (NodeComponent: ComponentType) => { noPanClassName, noDragClassName, }: WrapNodeProps) => { + const draggingRef = useRef(false); + const [dragging, setDragging] = useState(false); const store = useStoreApi(); const { addSelectedNodes, unselectNodesAndEdges, updateNodePosition, updateNodeDimensions } = useStore( selector, @@ -130,13 +132,12 @@ export default (NodeComponent: ComponentType) => { [id, selected, selectNodesOnDrag, isSelectable, onNodeDragStart] ); - // As one of the props passed to a custom node - const [dragging, setDragging] = useState(false); - const onDrag = useCallback( (event: UseDragEvent, dragPos: UseDragData) => { updateNodePosition({ id, diff: { x: dragPos.dx, y: dragPos.dy } }); setDragging(true); + draggingRef.current = true; + if (onNodeDrag) { const node = store.getState().nodeInternals.get(id)!; onNodeDrag(event.sourceEvent as MouseEvent, { @@ -157,13 +158,31 @@ export default (NodeComponent: ComponentType) => { const onDragStop = useCallback( (event: UseDragEvent) => { + let node; + + if (onClick || onNodeDragStop) { + node = store.getState().nodeInternals.get(id)!; + } + + if (!draggingRef.current) { + if (isSelectable && !selectNodesOnDrag && !selected) { + addSelectedNodes([id]); + } + + if (onClick && node) { + onClick(event.sourceEvent as MouseEvent, { ...node }); + } + + return; + } + draggingRef.current = false; setDragging(false); - if (onNodeDragStop) { - const node = store.getState().nodeInternals.get(id)!; + + if (onNodeDragStop && node) { onNodeDragStop(event.sourceEvent as MouseEvent, { ...node }); } }, - [id, onNodeDragStop] + [id, onNodeDragStop, onClick] ); useEffect(() => { diff --git a/src/hooks/useDrag.ts b/src/hooks/useDrag.ts index 89b7fc9c..f4aacfbf 100644 --- a/src/hooks/useDrag.ts +++ b/src/hooks/useDrag.ts @@ -110,12 +110,11 @@ function useDrag({ dx: pos.x, dy: pos.y, }); - - event.on('end', (event) => { - onStop(event); - }); } }) + .on('end', (event) => { + onStop(event); + }) .filter((event: any) => { const filter = !event.ctrlKey && !event.button && !event.target.className.includes(noDragClassName); return handleSelector