diff --git a/examples/react/src/examples/Figma/index.tsx b/examples/react/src/examples/Figma/index.tsx
index f6daab75..136d0629 100644
--- a/examples/react/src/examples/Figma/index.tsx
+++ b/examples/react/src/examples/Figma/index.tsx
@@ -34,7 +34,7 @@ const BasicFlow = () => {
selectionMode={SelectionMode.Partial}
panOnDrag={panOnDrag}
panOnScroll
- paneClickDistance={2}
+ paneClickDistance={100}
zoomActivationKeyCode="Meta"
multiSelectionKeyCode={MULTI_SELECT_KEY}
onPaneContextMenu={onPaneContextMenu}
@@ -44,6 +44,7 @@ const BasicFlow = () => {
onMoveStart={onMoveStart}
onMove={onMove}
onMoveEnd={onMoveEnd}
+ onPaneClick={(e) => console.log('pane click', e)}
>
diff --git a/examples/svelte/src/routes/examples/figma/+page.svelte b/examples/svelte/src/routes/examples/figma/+page.svelte
index 35a694ac..0b4c0b6a 100644
--- a/examples/svelte/src/routes/examples/figma/+page.svelte
+++ b/examples/svelte/src/routes/examples/figma/+page.svelte
@@ -9,11 +9,6 @@
import '@xyflow/svelte/dist/style.css';
- const onPaneContextMenu = (e: any) => {
- e.preventDefault();
- console.log('context menu');
- };
-
const panOnDrag = [1, 2];
const onmovestart = (e: any) => console.log('move start', e);
@@ -46,10 +41,12 @@
selectionMode={SelectionMode.Partial}
selectionOnDrag
panOnScroll
+ paneClickDistance={100}
{panOnDrag}
{onmovestart}
{onmove}
{onmoveend}
+ onpaneclick={(e) => console.log('on pane click', e)}
>
diff --git a/packages/react/src/container/Pane/index.tsx b/packages/react/src/container/Pane/index.tsx
index aadac2e0..f952434b 100644
--- a/packages/react/src/container/Pane/index.tsx
+++ b/packages/react/src/container/Pane/index.tsx
@@ -67,6 +67,7 @@ export function Pane({
selectionKeyPressed,
selectionMode = SelectionMode.Full,
panOnDrag,
+ paneClickDistance = 0,
selectionOnDrag,
onSelectionStart,
onSelectionEnd,
@@ -117,14 +118,9 @@ export function Pane({
const onWheel = onPaneScroll ? (event: React.WheelEvent) => onPaneScroll(event) : undefined;
const onClickCapture = (event: ReactMouseEvent) => {
- const isSelectionOnDragActive =
- (selectionOnDrag && container.current === event.target) || !selectionOnDrag || selectionKeyPressed;
-
- if (!isSelectionOnDragActive) {
- return;
+ if (selectionInProgress.current) {
+ event.stopPropagation();
}
-
- event.stopPropagation();
};
// We are using capture here in order to prevent other pointer events
@@ -149,9 +145,6 @@ export function Pane({
return;
}
- event.stopPropagation();
- event.preventDefault();
-
(event.target as Partial)?.setPointerCapture?.(event.pointerId);
selectionStarted.current = true;
@@ -159,8 +152,6 @@ export function Pane({
const { x, y } = getEventPosition(event.nativeEvent, containerBounds.current);
- resetSelectedElements();
-
store.setState({
userSelectionRect: {
width: 0,
@@ -172,7 +163,14 @@ export function Pane({
},
});
- onSelectionStart?.(event);
+ if (event.target !== container.current || paneClickDistance === 0) {
+ event.stopPropagation();
+ event.preventDefault();
+
+ resetSelectedElements();
+
+ onSelectionStart?.(event);
+ }
};
const onPointerMove = (event: ReactPointerEvent): void => {
@@ -191,11 +189,23 @@ export function Pane({
return;
}
- selectionInProgress.current = true;
-
const { x: mouseX, y: mouseY } = getEventPosition(event.nativeEvent, containerBounds.current);
const { startX, startY } = userSelectionRect;
+ if (
+ !selectionInProgress.current &&
+ event.target === container.current &&
+ !selectionKeyPressed &&
+ paneClickDistance > 0
+ ) {
+ const distance = Math.hypot(mouseX - startX, mouseY - startY);
+ if (distance <= paneClickDistance) {
+ return;
+ }
+ }
+
+ selectionInProgress.current = true;
+
const nextUserSelectRect = {
startX,
startY,
diff --git a/packages/svelte/src/lib/container/Pane/Pane.svelte b/packages/svelte/src/lib/container/Pane/Pane.svelte
index ef77b1de..fed9d005 100644
--- a/packages/svelte/src/lib/container/Pane/Pane.svelte
+++ b/packages/svelte/src/lib/container/Pane/Pane.svelte
@@ -47,6 +47,7 @@
let {
store = $bindable(),
panOnDrag = true,
+ paneClickDistance = 1,
selectionOnDrag,
onpaneclick,
onpanecontextmenu,
@@ -67,7 +68,7 @@
let panOnDragActive = $derived(store.panActivationKeyPressed || panOnDrag);
let isSelecting = $derived(
store.selectionKeyPressed ||
- store.selectionRect ||
+ !!store.selectionRect ||
(selectionOnDrag && panOnDragActive !== true)
);
let hasActiveSelection = $derived(
@@ -76,19 +77,7 @@
// Used to prevent click events when the user lets go of the selectionKey during a selection
let selectionInProgress = false;
-
- function onClick(event: MouseEvent) {
- // We prevent click events when the user let go of the selectionKey during a selection
- // We also prevent click events when a connection is in progress
- if (selectionInProgress || store.connection.inProgress) {
- selectionInProgress = false;
- return;
- }
-
- onpaneclick?.({ event });
- store.unselectNodesAndEdges();
- store.selectionRectMode = null;
- }
+ let selectionStarted = false;
// We start the selection process when the user clicks down on the pane
function onPointerDownCapture(event: PointerEvent) {
@@ -114,14 +103,12 @@
return;
}
- event.stopPropagation();
- event.preventDefault();
-
(event.target as Partial)?.setPointerCapture?.(event.pointerId);
- const { x, y } = getEventPosition(event, containerBounds);
+ selectionStarted = true;
+ selectionInProgress = false;
- store.unselectNodesAndEdges();
+ const { x, y } = getEventPosition(event, containerBounds);
store.selectionRect = {
width: 0,
@@ -132,7 +119,14 @@
y
};
- onselectionstart?.(event);
+ if (event.target !== container || paneClickDistance === 0) {
+ event.stopPropagation();
+ event.preventDefault();
+
+ store.unselectNodesAndEdges();
+
+ onselectionstart?.(event);
+ }
}
function onPointerMove(event: PointerEvent) {
@@ -140,11 +134,23 @@
return;
}
- selectionInProgress = true;
-
const mousePos = getEventPosition(event, containerBounds);
const { startX = 0, startY = 0 } = store.selectionRect;
+ if (
+ !selectionInProgress &&
+ event.target === container &&
+ !store.selectionKeyPressed &&
+ paneClickDistance > 0
+ ) {
+ const distance = Math.hypot(mousePos.x - startX, mousePos.y - startY);
+ if (distance <= paneClickDistance) {
+ return;
+ }
+ }
+
+ selectionInProgress = true;
+
const nextUserSelectRect = {
...store.selectionRect,
x: mousePos.x < startX ? mousePos.x : startX,
@@ -195,7 +201,7 @@
}
function onPointerUp(event: PointerEvent) {
- if (event.button !== 0) {
+ if (event.button !== 0 || !selectionStarted) {
return;
}
@@ -203,22 +209,20 @@
// We only want to trigger click functions when in selection mode if
// the user did not move the mouse.
- if (!isSelecting && store.selectionRectMode === 'user' && event.target === container) {
+
+ if (!selectionInProgress && event.target === container) {
onClick?.(event);
}
+
store.selectionRect = null;
- if (selectedNodeIds.size > 0) {
+ if (selectionInProgress && selectedNodeIds.size > 0) {
store.selectionRectMode = 'nodes';
}
- // If the user kept holding the selectionKey during the selection,
- // we need to reset the selectionInProgress, so the next click event is not prevented
- if (store.selectionKeyPressed) {
- selectionInProgress = false;
- }
-
onselectionend?.(event);
+
+ selectionStarted = false;
}
const onContextMenu = (event: MouseEvent) => {
@@ -231,17 +235,25 @@
};
const onClickCapture = (event: MouseEvent) => {
- const isSelectionActive =
- (selectionOnDrag && container === event.target) ||
- !selectionOnDrag ||
- store.selectionKeyPressed;
+ if (selectionInProgress) {
+ event.stopPropagation();
+ selectionInProgress = false;
+ }
+ };
- if (!isSelectionActive) {
+ function onClick(event: MouseEvent) {
+ // We prevent click events when the user let go of the selectionKey during a selection
+ // We also prevent click events when a connection is in progress
+ if (selectionInProgress || store.connection.inProgress) {
+ selectionInProgress = false;
return;
}
- event.stopPropagation();
- };
+ onpaneclick?.({ event });
+ store.unselectNodesAndEdges();
+ store.selectionRectMode = null;
+ store.selectionRect = null;
+ }