@neo4j-nvl/interaction-handlers 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  All notable changes to NVL will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
4
4
 
5
+ ## [1.2.1] - 2026-07-28
6
+
7
+ This 1.2.1 patch release contains several fixes and accessibility improvements.
8
+
9
+ ### Added
10
+ * Outline when the canvas is focused while using the keyboard interaction handlers.
11
+
12
+ ### Changed
13
+ * Renamed links in README from Explore to Neo4j Bloom.
14
+
15
+ ### Fixed
16
+ * Aligned padding on svg and png export.
17
+ * Aligned start behaviour of box and lasso select interaction handlers.
18
+ * Fixed static image wrapper React component returning incorrect height when using SVG format.
19
+ * Fixed self-referencing relationship captions being misplaced on downward curves.
20
+
5
21
  ## [1.2.0] - 2026-05-27
6
22
 
7
23
  This 1.2.0 release introduces a new keyboard interaction handler and SVG support for the static image wrapper React component. It also includes several performance improvements for clustering and text/icon rendering, as well as several bug fixes.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Neo4j Visualization Library
2
2
 
3
- Welcome to the Neo4j Visualization Library, NVL for short. NVL is a collection of libraries that can be used to build custom graph visualizations like those used in [Neo4j Bloom and Explore(powered by Bloom)](https://neo4j.com/product/bloom/). NVL is written in TypeScript and can be used in any JavaScript project. It is also available as a React component that can be used in React applications.
3
+ Welcome to the Neo4j Visualization Library, NVL for short. NVL is a collection of libraries that can be used to build custom graph visualizations like those used in [Neo4j Bloom](https://neo4j.com/product/bloom/). NVL is written in TypeScript and can be used in any JavaScript project. It is also available as a React component that can be used in React applications.
4
4
 
5
5
  This module is a collection of decorator classes that can be used to add interaction events to an NVL instance. The decorators are applied to the NVL class. You can find more information about the NVL class in the [NVL base library](https://www.npmjs.com/package/@neo4j-nvl/base).
6
6
 
@@ -50,14 +50,15 @@ describe('BoxSelectInteraction', () => {
50
50
  resolve();
51
51
  });
52
52
  });
53
- test.failing('will not start when node is below pointer when dragging', () => {
53
+ test('will not start when node is below pointer when dragging', () => {
54
54
  boxSelectInteraction.updateCallback('onBoxStarted', startCallback);
55
55
  boxSelectInteraction.updateCallback('onBoxSelect', selectCallback);
56
56
  expect(myNVL.getSelectedNodes()).toHaveLength(0);
57
57
  const gesture = [
58
58
  createMouseEvent('mousedown', 25, 25),
59
59
  createMouseEvent('mousemove', 50, 50),
60
- createMouseEvent('mouseup', 50, 50)
60
+ createMouseEvent('mousemove', 200, 200),
61
+ createMouseEvent('mouseup', 200, 200)
61
62
  ];
62
63
  const container = myNVL.getContainer();
63
64
  gesture.forEach((e) => container.dispatchEvent(e));
@@ -67,6 +68,31 @@ describe('BoxSelectInteraction', () => {
67
68
  resolve();
68
69
  });
69
70
  });
71
+ test('can start box select when only a relationship is under the pointer', () => {
72
+ boxSelectInteraction.updateCallback('onBoxStarted', startCallback);
73
+ boxSelectInteraction.updateCallback('onBoxSelect', selectCallback);
74
+ const gesture = [
75
+ createMouseEvent('mousedown', 100, 100),
76
+ createMouseEvent('mousemove', 150, 100),
77
+ createMouseEvent('mouseup', 150, 100)
78
+ ];
79
+ const container = myNVL.getContainer();
80
+ gesture.forEach((e) => container.dispatchEvent(e));
81
+ return new Promise((resolve) => {
82
+ expect(startCallback).toHaveBeenCalledTimes(1);
83
+ expect(selectCallback).toHaveBeenCalledTimes(1);
84
+ resolve();
85
+ });
86
+ });
87
+ test('does not fire callbacks when clicking directly on a relationship without dragging', () => {
88
+ boxSelectInteraction.updateCallback('onBoxStarted', startCallback);
89
+ boxSelectInteraction.updateCallback('onBoxSelect', selectCallback);
90
+ const container = myNVL.getContainer();
91
+ container.dispatchEvent(createMouseEvent('mousedown', 112, 112));
92
+ container.dispatchEvent(createMouseEvent('mouseup', 112, 112));
93
+ expect(startCallback).not.toHaveBeenCalled();
94
+ expect(selectCallback).not.toHaveBeenCalled();
95
+ });
70
96
  test('box selection should disable text selection until mouse release', () => {
71
97
  const container = myNVL.getContainer();
72
98
  boxSelectInteraction.updateCallback('onBoxSelect', boxSelectCallbackMock);
@@ -78,12 +78,39 @@ describe('LassoInteraction', () => {
78
78
  resolve();
79
79
  });
80
80
  });
81
+ test('does not fire started or select callbacks when movement stays below drag threshold', () => {
82
+ lassoInteraction.updateCallback('onLassoStarted', startCallback);
83
+ lassoInteraction.updateCallback('onLassoSelect', selectCallback);
84
+ const container = myNVL.getContainer();
85
+ container.dispatchEvent(createMouseEvent('mousedown', 100, 100));
86
+ container.dispatchEvent(createMouseEvent('mousemove', 100, 103));
87
+ container.dispatchEvent(createMouseEvent('mouseup', 100, 103));
88
+ expect(startCallback).not.toHaveBeenCalled();
89
+ expect(selectCallback).not.toHaveBeenCalled();
90
+ });
91
+ test('can start lasso when only a relationship is under the pointer', () => {
92
+ lassoInteraction.updateCallback('onLassoStarted', startCallback);
93
+ lassoInteraction.updateCallback('onLassoSelect', selectCallback);
94
+ const gesture = [
95
+ createMouseEvent('mousedown', 100, 100),
96
+ createMouseEvent('mousemove', 150, 100),
97
+ createMouseEvent('mousemove', 50, 150),
98
+ createMouseEvent('mouseup', 100, 100)
99
+ ];
100
+ const container = myNVL.getContainer();
101
+ gesture.forEach((e) => container.dispatchEvent(e));
102
+ return new Promise((resolve) => {
103
+ expect(startCallback).toHaveBeenCalledTimes(1);
104
+ expect(selectCallback).toHaveBeenCalledTimes(1);
105
+ resolve();
106
+ });
107
+ });
81
108
  test('will not start when node is below pointer when dragging', () => {
82
109
  lassoInteraction.updateCallback('onLassoStarted', startCallback);
83
110
  lassoInteraction.updateCallback('onLassoSelect', selectCallback);
84
111
  expect(myNVL.getSelectedNodes()).toHaveLength(0);
85
112
  const gesture1 = [
86
- createMouseEvent('mousedown', 10, 10),
113
+ createMouseEvent('mousedown', 25, 25),
87
114
  createMouseEvent('mousemove', 50, 0),
88
115
  createMouseEvent('mousemove', 20, 50),
89
116
  createMouseEvent('mouseup', 0, 0)
@@ -95,6 +122,15 @@ describe('LassoInteraction', () => {
95
122
  resolve();
96
123
  });
97
124
  });
125
+ test('does not fire callbacks when clicking directly on a relationship without dragging', () => {
126
+ lassoInteraction.updateCallback('onLassoStarted', startCallback);
127
+ lassoInteraction.updateCallback('onLassoSelect', selectCallback);
128
+ const container = myNVL.getContainer();
129
+ container.dispatchEvent(createMouseEvent('mousedown', 112, 112));
130
+ container.dispatchEvent(createMouseEvent('mouseup', 112, 112));
131
+ expect(startCallback).not.toHaveBeenCalled();
132
+ expect(selectCallback).not.toHaveBeenCalled();
133
+ });
98
134
  test('test for line crossing utility function', () => {
99
135
  const res1 = checkLinesCrossing([0, 0], [10, 10], [10, 0], [0, 10]);
100
136
  expect(res1).toBeTruthy();
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { isDraggingDistance, isDraggingMovement } from '../interaction-handlers/utils';
2
+ describe('interaction utils', () => {
3
+ test('returns true when movement reaches threshold on either axis', () => {
4
+ expect(isDraggingDistance({ x: 0, y: 0 }, { x: 10, y: 0 })).toBe(true);
5
+ expect(isDraggingDistance({ x: 0, y: 0 }, { x: 0, y: 10 })).toBe(true);
6
+ });
7
+ test('returns true for diagonal movement whose combined movement reaches threshold', () => {
8
+ expect(isDraggingDistance({ x: 0, y: 0 }, { x: 2, y: 2 })).toBe(false);
9
+ expect(isDraggingDistance({ x: 0, y: 0 }, { x: 7, y: 7 })).toBe(true);
10
+ expect(isDraggingDistance({ x: 0, y: 0 }, { x: 8, y: 8 })).toBe(true);
11
+ });
12
+ test('applies the same threshold logic to mouse events', () => {
13
+ expect(isDraggingMovement(new MouseEvent('mousemove', { clientX: 10, clientY: 0 }), { x: 0, y: 0 })).toBe(true);
14
+ expect(isDraggingMovement(new MouseEvent('mousemove', { clientX: 0, clientY: 10 }), { x: 0, y: 0 })).toBe(true);
15
+ expect(isDraggingMovement(new MouseEvent('mousemove', { clientX: 2, clientY: 2 }), { x: 0, y: 0 })).toBe(false);
16
+ expect(isDraggingMovement(new MouseEvent('mousemove', { clientX: 7, clientY: 7 }), { x: 0, y: 0 })).toBe(true);
17
+ expect(isDraggingMovement(new MouseEvent('mousemove', { clientX: 8, clientY: 8 }), { x: 0, y: 0 })).toBe(true);
18
+ });
19
+ });
@@ -38,8 +38,10 @@ export type BoxSelectInteractionCallbacks = {
38
38
  export declare class BoxSelectInteraction extends BaseInteraction<BoxSelectInteractionOptions> {
39
39
  private mousePosition;
40
40
  private startWorldPosition;
41
+ private startClientPosition;
41
42
  private overlayRenderer;
42
43
  private isBoxSelecting;
44
+ private boxSelectPending;
43
45
  /**
44
46
  * Creates a new instance of the multi-select interaction handler.
45
47
  * @param nvl - The NVL instance to attach the interaction handler to.
@@ -54,5 +56,6 @@ export declare class BoxSelectInteraction extends BaseInteraction<BoxSelectInter
54
56
  * Removes all related event listeners and the overlay renderer for the box.
55
57
  */
56
58
  destroy(): void;
59
+ private activateBoxSelect;
57
60
  private turnOnBoxSelect;
58
61
  }
@@ -1,7 +1,7 @@
1
1
  import { NODE_EDGE_WIDTH } from '../constants';
2
2
  import { OverlayRenderer } from '../overlay-renderer/overlay-renderer';
3
3
  import { BaseInteraction } from './base';
4
- import { getCanvasPosition, getWorldPosition } from './utils';
4
+ import { getCanvasPosition, getWorldPosition, isDraggingMovement } from './utils';
5
5
  /**
6
6
  * An interaction handler for multi-selecting nodes and relationships.
7
7
  * When dragging the cursor, it draws a box on the scene and all nodes and relationships inside the box are selected.
@@ -11,8 +11,10 @@ import { getCanvasPosition, getWorldPosition } from './utils';
11
11
  export class BoxSelectInteraction extends BaseInteraction {
12
12
  mousePosition = { x: 0, y: 0 };
13
13
  startWorldPosition = { x: 0, y: 0 };
14
+ startClientPosition = { x: 0, y: 0 };
14
15
  overlayRenderer;
15
16
  isBoxSelecting = false;
17
+ boxSelectPending = false;
16
18
  /**
17
19
  * Creates a new instance of the multi-select interaction handler.
18
20
  * @param nvl - The NVL instance to attach the interaction handler to.
@@ -28,6 +30,7 @@ export class BoxSelectInteraction extends BaseInteraction {
28
30
  handleMouseDown = (event) => {
29
31
  if (event.button !== 0) {
30
32
  this.isBoxSelecting = false;
33
+ this.boxSelectPending = false;
31
34
  return;
32
35
  }
33
36
  this.turnOnBoxSelect(event);
@@ -36,9 +39,12 @@ export class BoxSelectInteraction extends BaseInteraction {
36
39
  if (this.isBoxSelecting) {
37
40
  const curPos = getCanvasPosition(this.containerInstance, event);
38
41
  this.overlayRenderer.drawBox(this.mousePosition.x, this.mousePosition.y, curPos.x, curPos.y);
42
+ return;
39
43
  }
40
- else if (event.buttons === 1) {
41
- this.turnOnBoxSelect(event);
44
+ if (this.boxSelectPending && event.buttons === 1) {
45
+ if (isDraggingMovement(event, this.startClientPosition)) {
46
+ this.activateBoxSelect(event);
47
+ }
42
48
  }
43
49
  };
44
50
  getHitsInBox = (startPos, endPos) => {
@@ -71,6 +77,7 @@ export class BoxSelectInteraction extends BaseInteraction {
71
77
  };
72
78
  endBoxSelect = (event) => {
73
79
  if (!this.isBoxSelecting) {
80
+ this.boxSelectPending = false;
74
81
  return;
75
82
  }
76
83
  this.isBoxSelecting = false;
@@ -94,20 +101,28 @@ export class BoxSelectInteraction extends BaseInteraction {
94
101
  this.removeEventListener('mouseup', this.endBoxSelect, true);
95
102
  this.overlayRenderer.destroy();
96
103
  }
104
+ activateBoxSelect = (event) => {
105
+ this.boxSelectPending = false;
106
+ this.isBoxSelecting = true;
107
+ this.toggleGlobalTextSelection(false, this.endBoxSelect);
108
+ this.callCallbackIfRegistered('onBoxStarted', event);
109
+ if (this.currentOptions.selectOnRelease === true) {
110
+ this.nvlInstance.deselectAll();
111
+ }
112
+ const curPos = getCanvasPosition(this.containerInstance, event);
113
+ this.overlayRenderer.drawBox(this.mousePosition.x, this.mousePosition.y, curPos.x, curPos.y);
114
+ };
97
115
  turnOnBoxSelect(event) {
98
116
  this.mousePosition = getCanvasPosition(this.containerInstance, event);
99
117
  this.startWorldPosition = getWorldPosition(this.nvlInstance, this.mousePosition);
100
118
  const hits = this.nvlInstance.getHits(event, ['node'], { hitNodeMarginWidth: NODE_EDGE_WIDTH });
101
119
  if (hits.nvlTargets.nodes.length > 0) {
102
120
  this.isBoxSelecting = false;
121
+ this.boxSelectPending = false;
103
122
  }
104
123
  else {
105
- this.isBoxSelecting = true;
106
- this.toggleGlobalTextSelection(false, this.endBoxSelect);
107
- this.callCallbackIfRegistered('onBoxStarted', event);
108
- if (this.currentOptions.selectOnRelease === true) {
109
- this.nvlInstance.deselectAll();
110
- }
124
+ this.startClientPosition = { x: event.clientX, y: event.clientY };
125
+ this.boxSelectPending = true;
111
126
  }
112
127
  }
113
128
  }
@@ -27,6 +27,8 @@ export type KeyboardInteractionOptions = {
27
27
  exitGraphKey?: string[];
28
28
  /** Key combo for opening the context menu on the focused element. Default: `['Shift', 'F10']` */
29
29
  contextMenuKey?: string[];
30
+ /** The color of the outline shown when the canvas receives keyboard focus. Default: `'#30839D'` */
31
+ canvasOutlineColor?: string;
30
32
  };
31
33
  /**
32
34
  * Callbacks for the keyboard interaction handler.
@@ -114,6 +116,8 @@ export declare class KeyboardInteraction extends BaseInteraction<KeyboardInterac
114
116
  * Returns the currently focused element, if any.
115
117
  */
116
118
  getFocused(): Node | Relationship | undefined;
119
+ private setCanvasOutline;
120
+ private clearCanvasOutline;
117
121
  private handleFocusIn;
118
122
  private handleFocusOut;
119
123
  private handleKeyDown;
@@ -4,7 +4,8 @@ const DEFAULT_OPTIONS = {
4
4
  navigateForwardKey: ['Tab'],
5
5
  navigateBackwardKey: ['Shift', 'Tab'],
6
6
  exitGraphKey: ['Escape'],
7
- contextMenuKey: ['Shift', 'F10']
7
+ contextMenuKey: ['Shift', 'F10'],
8
+ canvasOutlineColor: '#30839D'
8
9
  };
9
10
  /**
10
11
  * Checks whether a keyboard event matches a key combo array.
@@ -79,11 +80,25 @@ export class KeyboardInteraction extends BaseInteraction {
79
80
  }
80
81
  return this.nvlInstance.getRelationships().find((r) => r.id === this.focusedElementId);
81
82
  }
83
+ setCanvasOutline() {
84
+ const container = this.containerInstance;
85
+ if (container.matches(':focus-visible')) {
86
+ container.style.outline = `2px solid ${this.resolvedOptions.canvasOutlineColor}`;
87
+ container.style.outlineOffset = '-2px';
88
+ }
89
+ }
90
+ clearCanvasOutline() {
91
+ const container = this.containerInstance;
92
+ container.style.outline = 'none';
93
+ container.style.outlineOffset = '';
94
+ }
82
95
  handleFocusIn = (event) => {
96
+ this.setCanvasOutline();
83
97
  this.callCallbackIfRegistered('onCanvasFocus', event);
84
98
  };
85
99
  handleFocusOut = (event) => {
86
100
  this.callCallbackIfRegistered('onCanvasBlur', event);
101
+ this.clearCanvasOutline();
87
102
  };
88
103
  handleKeyDown = (event) => {
89
104
  const focusedElement = this.getFocused();
@@ -108,6 +123,7 @@ export class KeyboardInteraction extends BaseInteraction {
108
123
  }
109
124
  }
110
125
  else if (matchesKeyCombo(event, exitGraphKey)) {
126
+ this.setCanvasOutline();
111
127
  if (focusedElement !== undefined) {
112
128
  event.preventDefault();
113
129
  this.clearFocus(event);
@@ -150,6 +166,7 @@ export class KeyboardInteraction extends BaseInteraction {
150
166
  // Notify about the newly focused element
151
167
  const newElement = this.getFocused();
152
168
  if (newElement !== undefined) {
169
+ this.clearCanvasOutline();
153
170
  if (isNode) {
154
171
  this.callCallbackIfRegistered('onNodeFocus', newElement, event);
155
172
  }
@@ -179,6 +196,7 @@ export class KeyboardInteraction extends BaseInteraction {
179
196
  this.removeEventListener('keyup', this.handleKeyUp);
180
197
  this.removeEventListener('focusin', this.handleFocusIn);
181
198
  this.removeEventListener('focusout', this.handleFocusOut);
199
+ this.clearCanvasOutline();
182
200
  if (this.addedTabindex) {
183
201
  this.containerInstance.removeAttribute('tabindex');
184
202
  }
@@ -60,6 +60,9 @@ export declare const checkPointInside: (x: number, y: number, vs: Point[]) => bo
60
60
  */
61
61
  export declare class LassoInteraction extends BaseInteraction<LassoInteractionOptions> {
62
62
  private active;
63
+ private lassoPending;
64
+ private startClientPosition;
65
+ private pendingPointerCanvas;
63
66
  private points;
64
67
  private overlayRenderer;
65
68
  /**
@@ -71,6 +74,7 @@ export declare class LassoInteraction extends BaseInteraction<LassoInteractionOp
71
74
  private startLasso;
72
75
  private handleMouseDown;
73
76
  private handleDrag;
77
+ private activateLasso;
74
78
  private handleMouseUp;
75
79
  private getLassoItems;
76
80
  private endLasso;
@@ -2,7 +2,7 @@ import concaveman from 'concaveman';
2
2
  import { NODE_EDGE_WIDTH } from '../constants';
3
3
  import { OverlayRenderer } from '../overlay-renderer/overlay-renderer';
4
4
  import { BaseInteraction } from './base';
5
- import { getCanvasPosition, getWorldPosition } from './utils';
5
+ import { getCanvasPosition, getWorldPosition, isDraggingMovement } from './utils';
6
6
  const pointDist = 10;
7
7
  const shapeShowTime = 500;
8
8
  /**
@@ -73,6 +73,9 @@ export const checkPointInside = (x, y, vs) => {
73
73
  */
74
74
  export class LassoInteraction extends BaseInteraction {
75
75
  active = false;
76
+ lassoPending = false;
77
+ startClientPosition = { x: 0, y: 0 };
78
+ pendingPointerCanvas = { x: 0, y: 0 };
76
79
  points = [];
77
80
  overlayRenderer;
78
81
  /**
@@ -91,19 +94,16 @@ export class LassoInteraction extends BaseInteraction {
91
94
  const hits = this.nvlInstance.getHits(event, ['node'], { hitNodeMarginWidth: NODE_EDGE_WIDTH });
92
95
  if (hits.nvlTargets.nodes.length > 0) {
93
96
  this.active = false;
97
+ this.lassoPending = false;
94
98
  }
95
99
  else {
96
- this.active = true;
97
- this.points = [getCanvasPosition(this.containerInstance, event)];
98
- this.toggleGlobalTextSelection(false, this.endLasso);
99
- this.callCallbackIfRegistered('onLassoStarted', event);
100
- if (this.currentOptions.selectOnRelease === true) {
101
- this.nvlInstance.deselectAll();
102
- }
100
+ this.lassoPending = true;
101
+ this.startClientPosition = { x: event.clientX, y: event.clientY };
102
+ this.pendingPointerCanvas = getCanvasPosition(this.containerInstance, event);
103
103
  }
104
104
  };
105
105
  handleMouseDown = (event) => {
106
- if (event.button === 0 && !this.active) {
106
+ if (event.button === 0 && !this.active && !this.lassoPending) {
107
107
  this.startLasso(event);
108
108
  }
109
109
  };
@@ -120,9 +120,31 @@ export class LassoInteraction extends BaseInteraction {
120
120
  this.points.push(pos);
121
121
  this.overlayRenderer.drawLasso(this.points, true, false);
122
122
  }
123
+ return;
124
+ }
125
+ if (this.lassoPending && event.buttons === 1) {
126
+ if (isDraggingMovement(event, this.startClientPosition)) {
127
+ this.activateLasso(event);
128
+ }
129
+ }
130
+ };
131
+ activateLasso = (event) => {
132
+ this.lassoPending = false;
133
+ this.active = true;
134
+ const pos = getCanvasPosition(this.containerInstance, event);
135
+ this.points = [this.pendingPointerCanvas, pos];
136
+ this.toggleGlobalTextSelection(false, this.endLasso);
137
+ this.callCallbackIfRegistered('onLassoStarted', event);
138
+ if (this.currentOptions.selectOnRelease === true) {
139
+ this.nvlInstance.deselectAll();
123
140
  }
141
+ this.overlayRenderer.drawLasso(this.points, true, false);
124
142
  };
125
143
  handleMouseUp = (event) => {
144
+ if (!this.active) {
145
+ this.lassoPending = false;
146
+ return;
147
+ }
126
148
  this.points.push(getCanvasPosition(this.containerInstance, event));
127
149
  this.endLasso(event);
128
150
  };
@@ -1,6 +1,7 @@
1
1
  import type { NVL, Point } from '@neo4j-nvl/base';
2
2
  export declare const generateUniqueId: (digit: number) => string;
3
- export declare const isDraggingMovement: (event: MouseEvent, originalPosition: Point) => boolean;
3
+ export declare const isDraggingDistance: (startPoint: Point, endPoint: Point) => boolean;
4
+ export declare const isDraggingMovement: (event: MouseEvent, point: Point) => boolean;
4
5
  export declare const getCanvasPosition: (canvas: HTMLElement, mouseEvent: MouseEvent) => Point;
5
6
  export declare const getCanvasCenterOffset: (canvas: HTMLElement, mouseEvent: MouseEvent) => Point;
6
7
  export declare const getWorldPosition: (nvl: NVL, pos: Point) => Point;
@@ -1,14 +1,17 @@
1
1
  import { DRAG_THRESHOLD } from '../constants';
2
2
  export const generateUniqueId = (digit) => Math.floor(Math.random() * Math.pow(10, digit)).toString();
3
- export const isDraggingMovement = (event, originalPosition) => {
4
- const diffX = Math.abs(event.clientX - originalPosition.x);
5
- const diffY = Math.abs(event.clientY - originalPosition.y);
3
+ export const isDraggingDistance = (startPoint, endPoint) => {
4
+ const diffX = Math.abs(endPoint.x - startPoint.x);
5
+ const diffY = Math.abs(endPoint.y - startPoint.y);
6
6
  if (diffX > DRAG_THRESHOLD || diffY > DRAG_THRESHOLD) {
7
7
  return true;
8
8
  }
9
9
  const distanceSquared = Math.pow(diffX, 2) + Math.pow(diffY, 2);
10
10
  return distanceSquared > DRAG_THRESHOLD;
11
11
  };
12
+ export const isDraggingMovement = (event, point) => {
13
+ return isDraggingDistance(point, { x: event.clientX, y: event.clientY });
14
+ };
12
15
  export const getCanvasPosition = (canvas, mouseEvent) => {
13
16
  const rect = canvas.getBoundingClientRect();
14
17
  const devicePixelRatio = window.devicePixelRatio || 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neo4j-nvl/interaction-handlers",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "license": "SEE LICENSE IN 'LICENSE.txt'",
5
5
  "homepage": "https://neo4j.com/docs/nvl/current/",
6
6
  "description": "Interaction handlers for the Neo4j Visualization Library",
@@ -27,7 +27,7 @@
27
27
  "eslint": "yarn global:eslint ./src/"
28
28
  },
29
29
  "dependencies": {
30
- "@neo4j-nvl/base": "1.2.0",
30
+ "@neo4j-nvl/base": "1.2.1",
31
31
  "concaveman": "1.2.1",
32
32
  "lodash": "4.18.1"
33
33
  },