@neo4j-nvl/interaction-handlers 0.3.1 → 0.3.2-e0e72a58

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.
Files changed (28) hide show
  1. package/README.md +1 -1
  2. package/lib/__tests__/click-interaction.test.d.ts +1 -0
  3. package/lib/__tests__/click-interaction.test.js +319 -0
  4. package/lib/__tests__/drag-node-interaction.test.d.ts +1 -0
  5. package/lib/__tests__/drag-node-interaction.test.js +131 -0
  6. package/lib/__tests__/hover-interaction.test.d.ts +1 -0
  7. package/lib/__tests__/hover-interaction.test.js +306 -0
  8. package/lib/__tests__/lasso-interaction.test.d.ts +1 -0
  9. package/lib/__tests__/lasso-interaction.test.js +79 -0
  10. package/lib/interaction-handlers/base.js +2 -2
  11. package/lib/interaction-handlers/box-select-interaction.d.ts +5 -1
  12. package/lib/interaction-handlers/box-select-interaction.js +5 -1
  13. package/lib/interaction-handlers/click-interaction.d.ts +1 -1
  14. package/lib/interaction-handlers/click-interaction.js +1 -1
  15. package/lib/interaction-handlers/drag-node-interaction.d.ts +4 -1
  16. package/lib/interaction-handlers/drag-node-interaction.js +4 -1
  17. package/lib/interaction-handlers/draw-interaction.d.ts +5 -0
  18. package/lib/interaction-handlers/draw-interaction.js +44 -2
  19. package/lib/interaction-handlers/hover-interaction.d.ts +5 -2
  20. package/lib/interaction-handlers/hover-interaction.js +6 -3
  21. package/lib/interaction-handlers/lasso-interaction.d.ts +6 -2
  22. package/lib/interaction-handlers/lasso-interaction.js +6 -2
  23. package/lib/interaction-handlers/pan-interaction.d.ts +11 -1
  24. package/lib/interaction-handlers/pan-interaction.js +11 -1
  25. package/lib/interaction-handlers/zoom-interaction.d.ts +4 -1
  26. package/lib/interaction-handlers/zoom-interaction.js +4 -1
  27. package/lib/overlay-renderer/overlay-renderer.js +3 -3
  28. package/package.json +19 -4
package/README.md CHANGED
@@ -34,4 +34,4 @@ clickInteraction.updateCallback('onNodeClick', (node) => {
34
34
 
35
35
  If you are using React and want to add interactivity to your graph, you can also make use of the InteractiveReactWrapper.
36
36
 
37
- You can find more instructions and examples on how to use NVL in the docs.
37
+ You can find more instructions and examples on how to use NVL interaction handlers in the [docs](https://neo4j.com/docs/nvl/current/interaction-handlers/).
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -0,0 +1,319 @@
1
+ import NVL from '@neo4j-nvl/base';
2
+ import '@testing-library/jest-dom';
3
+ import { ClickInteraction } from '../interaction-handlers/click-interaction';
4
+ jest.mock('@neo4j-nvl/layout-workers');
5
+ describe('ClickInteraction', () => {
6
+ let clickInteraction;
7
+ let myNVL;
8
+ const callbackMock = jest.fn();
9
+ beforeEach(() => {
10
+ myNVL = new NVL(document.createElement('div'), [
11
+ { id: '0', x: 100, y: 100 },
12
+ { id: '1', x: 200, y: 200 }
13
+ ], [{ id: '10', from: '0', to: '1' }], { disableWebGL: true, initialZoom: 1, layout: 'free' });
14
+ clickInteraction = new ClickInteraction(myNVL, { selectOnClick: true });
15
+ });
16
+ afterEach(() => {
17
+ clickInteraction.destroy();
18
+ myNVL.destroy();
19
+ callbackMock.mockReset();
20
+ });
21
+ test('clicking outside of nodes and relationships should not select anything', () => {
22
+ clickInteraction.updateCallback('onCanvasClick', callbackMock);
23
+ expect(myNVL.getSelectedNodes()).toHaveLength(0);
24
+ let mouseEvent = new MouseEvent('mousedown', {
25
+ clientX: 10,
26
+ clientY: 10
27
+ });
28
+ myNVL.getContainer().dispatchEvent(mouseEvent);
29
+ mouseEvent = new MouseEvent('click', {
30
+ clientX: 10,
31
+ clientY: 10
32
+ });
33
+ myNVL.getContainer().dispatchEvent(mouseEvent);
34
+ return new Promise((resolve) => {
35
+ expect(callbackMock).toHaveBeenCalledWith(mouseEvent);
36
+ expect(callbackMock).toHaveBeenCalledTimes(1);
37
+ expect(myNVL.getSelectedNodes()).toHaveLength(0);
38
+ resolve();
39
+ });
40
+ });
41
+ test('clicking outside of nodes and relationships should de-select everything', () => {
42
+ clickInteraction.updateCallback('onCanvasClick', callbackMock);
43
+ myNVL.updateElementsInGraph([
44
+ { id: '0', selected: true },
45
+ { id: '1', selected: true }
46
+ ], []);
47
+ expect(myNVL.getSelectedNodes()).toHaveLength(2);
48
+ let mouseEvent = new MouseEvent('mousedown', {
49
+ clientX: 10,
50
+ clientY: 10
51
+ });
52
+ myNVL.getContainer().dispatchEvent(mouseEvent);
53
+ mouseEvent = new MouseEvent('click', {
54
+ clientX: 10,
55
+ clientY: 10
56
+ });
57
+ myNVL.getContainer().dispatchEvent(mouseEvent);
58
+ return new Promise((resolve) => {
59
+ expect(callbackMock).toHaveBeenCalledWith(mouseEvent);
60
+ expect(callbackMock).toHaveBeenCalledTimes(1);
61
+ expect(myNVL.getSelectedNodes()).toHaveLength(0);
62
+ resolve();
63
+ });
64
+ });
65
+ test(`clicking outside of nodes and relationships should not de-select anything
66
+ when selection mode is turned off but still trigger callback`, () => {
67
+ clickInteraction.destroy();
68
+ clickInteraction = new ClickInteraction(myNVL, { selectOnClick: false });
69
+ clickInteraction.updateCallback('onCanvasClick', callbackMock);
70
+ myNVL.updateElementsInGraph([
71
+ { id: '0', selected: true },
72
+ { id: '1', selected: true }
73
+ ], []);
74
+ expect(myNVL.getSelectedNodes()).toHaveLength(2);
75
+ let mouseEvent = new MouseEvent('mousedown', {
76
+ clientX: 10,
77
+ clientY: 10
78
+ });
79
+ myNVL.getContainer().dispatchEvent(mouseEvent);
80
+ mouseEvent = new MouseEvent('click', {
81
+ clientX: 10,
82
+ clientY: 10
83
+ });
84
+ myNVL.getContainer().dispatchEvent(mouseEvent);
85
+ return new Promise((resolve) => {
86
+ expect(callbackMock).toHaveBeenCalledWith(mouseEvent);
87
+ expect(callbackMock).toHaveBeenCalledTimes(1);
88
+ expect(myNVL.getSelectedNodes()).toHaveLength(2);
89
+ resolve();
90
+ });
91
+ });
92
+ test('clicking on a node should select it and trigger callback', () => {
93
+ clickInteraction.updateCallback('onNodeClick', callbackMock);
94
+ myNVL.getContainer().dispatchEvent(new MouseEvent('mousedown', {
95
+ clientX: 100,
96
+ clientY: 100
97
+ }));
98
+ const mouseEvent = new MouseEvent('click', {
99
+ clientX: 100,
100
+ clientY: 100
101
+ });
102
+ myNVL.getContainer().dispatchEvent(mouseEvent);
103
+ return new Promise((resolve) => {
104
+ expect(callbackMock).toHaveBeenCalledWith({ id: '0', selected: true, x: 100, y: 100 }, {
105
+ nodes: [
106
+ {
107
+ data: { id: '0', selected: true, x: 100, y: 100 },
108
+ distance: 0,
109
+ distanceVector: { x: 0, y: 0 },
110
+ insideNode: true,
111
+ pointerCoordinates: { x: 100, y: 100 },
112
+ targetCoordinates: { x: 100, y: 100 }
113
+ }
114
+ ],
115
+ relationships: [
116
+ {
117
+ data: { id: '10', from: '0', to: '1' },
118
+ distance: 0,
119
+ pointerCoordinates: { x: 100, y: 100 },
120
+ fromTargetCoordinates: { x: 100, y: 100 },
121
+ toTargetCoordinates: { x: 200, y: 200 }
122
+ }
123
+ ]
124
+ }, mouseEvent);
125
+ expect(callbackMock).toHaveBeenCalledTimes(1);
126
+ resolve();
127
+ });
128
+ });
129
+ test('clicking on a relationship should select it and trigger callback', () => {
130
+ clickInteraction.updateCallback('onRelationshipClick', callbackMock);
131
+ myNVL.getContainer().dispatchEvent(new MouseEvent('mousedown', {
132
+ clientX: 150,
133
+ clientY: 150
134
+ }));
135
+ const mouseEvent = new MouseEvent('click', {
136
+ clientX: 150,
137
+ clientY: 150
138
+ });
139
+ myNVL.getContainer().dispatchEvent(mouseEvent);
140
+ return new Promise((resolve) => {
141
+ expect(callbackMock).toHaveBeenCalledWith({ id: '10', selected: true, from: '0', to: '1' }, {
142
+ nodes: [],
143
+ relationships: [
144
+ {
145
+ data: { id: '10', selected: true, from: '0', to: '1' },
146
+ distance: 0,
147
+ pointerCoordinates: { x: 150, y: 150 },
148
+ fromTargetCoordinates: { x: 100, y: 100 },
149
+ toTargetCoordinates: { x: 200, y: 200 }
150
+ }
151
+ ]
152
+ }, mouseEvent);
153
+ expect(callbackMock).toHaveBeenCalledTimes(1);
154
+ resolve();
155
+ });
156
+ });
157
+ test('clicking on a node should not select it if selection mode is off but still trigger callback', () => {
158
+ clickInteraction.destroy();
159
+ clickInteraction = new ClickInteraction(myNVL, { selectOnClick: false });
160
+ clickInteraction.updateCallback('onNodeClick', callbackMock);
161
+ myNVL.getContainer().dispatchEvent(new MouseEvent('mousedown', {
162
+ clientX: 100,
163
+ clientY: 100
164
+ }));
165
+ const mouseEvent = new MouseEvent('click', {
166
+ clientX: 100,
167
+ clientY: 100
168
+ });
169
+ myNVL.getContainer().dispatchEvent(mouseEvent);
170
+ return new Promise((resolve) => {
171
+ expect(callbackMock).toHaveBeenCalledWith({ id: '0', x: 100, y: 100 }, {
172
+ nodes: [
173
+ {
174
+ data: { id: '0', x: 100, y: 100 },
175
+ distance: 0,
176
+ distanceVector: { x: 0, y: 0 },
177
+ insideNode: true,
178
+ pointerCoordinates: { x: 100, y: 100 },
179
+ targetCoordinates: { x: 100, y: 100 }
180
+ }
181
+ ],
182
+ relationships: [
183
+ {
184
+ data: { id: '10', from: '0', to: '1' },
185
+ distance: 0,
186
+ pointerCoordinates: { x: 100, y: 100 },
187
+ fromTargetCoordinates: { x: 100, y: 100 },
188
+ toTargetCoordinates: { x: 200, y: 200 }
189
+ }
190
+ ]
191
+ }, mouseEvent);
192
+ expect(callbackMock).toHaveBeenCalledTimes(1);
193
+ resolve();
194
+ });
195
+ });
196
+ test('clicking on a node should de-select other selected nodes and trigger callback', () => {
197
+ clickInteraction.updateCallback('onNodeClick', callbackMock);
198
+ myNVL.updateElementsInGraph([{ id: '0' }, { id: '1', selected: true }], []);
199
+ expect(myNVL.getSelectedNodes()).toEqual([{ id: '1', selected: true, x: 200, y: 200 }]);
200
+ myNVL.getContainer().dispatchEvent(new MouseEvent('mousedown', {
201
+ clientX: 100,
202
+ clientY: 100
203
+ }));
204
+ const mouseEvent = new MouseEvent('click', {
205
+ clientX: 100,
206
+ clientY: 100
207
+ });
208
+ myNVL.getContainer().dispatchEvent(mouseEvent);
209
+ return new Promise((resolve) => {
210
+ expect(callbackMock).toHaveBeenCalledWith({ id: '0', selected: true, x: 100, y: 100 }, {
211
+ nodes: [
212
+ {
213
+ data: { id: '0', selected: true, x: 100, y: 100 },
214
+ distance: 0,
215
+ distanceVector: { x: 0, y: 0 },
216
+ insideNode: true,
217
+ pointerCoordinates: { x: 100, y: 100 },
218
+ targetCoordinates: { x: 100, y: 100 }
219
+ }
220
+ ],
221
+ relationships: [
222
+ {
223
+ data: { id: '10', from: '0', to: '1' },
224
+ distance: 0,
225
+ pointerCoordinates: { x: 100, y: 100 },
226
+ fromTargetCoordinates: { x: 100, y: 100 },
227
+ toTargetCoordinates: { x: 200, y: 200 }
228
+ }
229
+ ]
230
+ }, mouseEvent);
231
+ expect(callbackMock).toHaveBeenCalledTimes(1);
232
+ expect(myNVL.getSelectedNodes()).toEqual([{ id: '0', selected: true, x: 100, y: 100 }]);
233
+ resolve();
234
+ });
235
+ });
236
+ test('context menu event on canvas should trigger canvas right-click callback', () => {
237
+ clickInteraction.updateCallback('onCanvasRightClick', callbackMock);
238
+ const mouseEvent = new MouseEvent('contextmenu', {
239
+ clientX: 50,
240
+ clientY: 50
241
+ });
242
+ myNVL.getContainer().dispatchEvent(mouseEvent);
243
+ return new Promise((resolve) => {
244
+ expect(callbackMock).toHaveBeenCalledWith(mouseEvent);
245
+ expect(callbackMock).toHaveBeenCalledTimes(1);
246
+ resolve();
247
+ });
248
+ });
249
+ test('context menu event on a node should trigger node right-click callback', () => {
250
+ clickInteraction.updateCallback('onNodeRightClick', callbackMock);
251
+ const mouseEvent = new MouseEvent('contextmenu', {
252
+ clientX: 100,
253
+ clientY: 100
254
+ });
255
+ myNVL.getContainer().dispatchEvent(mouseEvent);
256
+ return new Promise((resolve) => {
257
+ expect(callbackMock).toHaveBeenCalledWith({ id: '0', x: 100, y: 100 }, {
258
+ nodes: [
259
+ {
260
+ data: { id: '0', x: 100, y: 100 },
261
+ distance: 0,
262
+ distanceVector: { x: 0, y: 0 },
263
+ insideNode: true,
264
+ pointerCoordinates: { x: 100, y: 100 },
265
+ targetCoordinates: { x: 100, y: 100 }
266
+ }
267
+ ],
268
+ relationships: [
269
+ {
270
+ data: { id: '10', from: '0', to: '1' },
271
+ distance: 0,
272
+ pointerCoordinates: { x: 100, y: 100 },
273
+ fromTargetCoordinates: { x: 100, y: 100 },
274
+ toTargetCoordinates: { x: 200, y: 200 }
275
+ }
276
+ ]
277
+ }, mouseEvent);
278
+ expect(callbackMock).toHaveBeenCalledTimes(1);
279
+ resolve();
280
+ });
281
+ });
282
+ test('context menu event on a relationship should trigger relationship right-click callback', () => {
283
+ clickInteraction.updateCallback('onRelationshipRightClick', callbackMock);
284
+ const mouseEvent = new MouseEvent('contextmenu', {
285
+ clientX: 150,
286
+ clientY: 150
287
+ });
288
+ myNVL.getContainer().dispatchEvent(mouseEvent);
289
+ return new Promise((resolve) => {
290
+ expect(callbackMock).toHaveBeenCalledWith({ id: '10', from: '0', to: '1' }, {
291
+ nodes: [],
292
+ relationships: [
293
+ {
294
+ data: { id: '10', from: '0', to: '1' },
295
+ distance: 0,
296
+ pointerCoordinates: { x: 150, y: 150 },
297
+ fromTargetCoordinates: { x: 100, y: 100 },
298
+ toTargetCoordinates: { x: 200, y: 200 }
299
+ }
300
+ ]
301
+ }, mouseEvent);
302
+ expect(callbackMock).toHaveBeenCalledTimes(1);
303
+ resolve();
304
+ });
305
+ });
306
+ test('releasing right mouse button should not select anything and trigger no callbacks', () => {
307
+ clickInteraction.updateCallback('onNodeClick', callbackMock);
308
+ const mouseEvent = new MouseEvent('mouseup', {
309
+ clientX: 10,
310
+ clientY: 10,
311
+ button: 2
312
+ });
313
+ myNVL.getContainer().dispatchEvent(mouseEvent);
314
+ return new Promise((resolve) => {
315
+ expect(callbackMock).toHaveBeenCalledTimes(0);
316
+ resolve();
317
+ });
318
+ });
319
+ });
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -0,0 +1,131 @@
1
+ import NVL from '@neo4j-nvl/base';
2
+ import '@testing-library/jest-dom';
3
+ import { DragNodeInteraction } from '../interaction-handlers/drag-node-interaction';
4
+ jest.mock('@neo4j-nvl/layout-workers');
5
+ describe('DragNodeInteraction', () => {
6
+ let dragNodeInteraction;
7
+ let myNVL;
8
+ const dragStartCallbackMock = jest.fn();
9
+ const dragCallbackMock = jest.fn();
10
+ const dragEndCallbackMock = jest.fn();
11
+ beforeEach(() => {
12
+ myNVL = new NVL(document.createElement('div'), [
13
+ { id: '0', x: 10, y: 10 },
14
+ { id: '1', x: 200, y: 200 }
15
+ ], [{ id: '10', from: '0', to: '1' }], { disableWebGL: true, initialZoom: 1, layout: 'free' });
16
+ dragNodeInteraction = new DragNodeInteraction(myNVL);
17
+ });
18
+ afterEach(() => {
19
+ dragNodeInteraction.destroy();
20
+ myNVL.destroy();
21
+ dragStartCallbackMock.mockReset();
22
+ dragCallbackMock.mockReset();
23
+ dragEndCallbackMock.mockReset();
24
+ });
25
+ test('performing a simple drag operation should invoke all expected callbacks', () => {
26
+ dragNodeInteraction.updateCallback('onDragStart', dragStartCallbackMock);
27
+ dragNodeInteraction.updateCallback('onDrag', dragCallbackMock);
28
+ dragNodeInteraction.updateCallback('onDragEnd', dragEndCallbackMock);
29
+ expect(myNVL.getSelectedNodes()).toHaveLength(0);
30
+ const mouseDownEvent = new MouseEvent('mousedown', {
31
+ clientX: 10,
32
+ clientY: 10
33
+ });
34
+ const mouseMoveEvent = new MouseEvent('mousemove', {
35
+ buttons: 1,
36
+ clientX: 20,
37
+ clientY: 20
38
+ });
39
+ const mouseUpEvent = new MouseEvent('mouseup', {
40
+ clientX: 20,
41
+ clientY: 20
42
+ });
43
+ const container = myNVL.getContainer();
44
+ container.dispatchEvent(mouseDownEvent);
45
+ container.dispatchEvent(mouseMoveEvent);
46
+ container.dispatchEvent(mouseUpEvent);
47
+ return new Promise((resolve) => {
48
+ expect(dragStartCallbackMock).toHaveBeenCalledTimes(1);
49
+ expect(dragStartCallbackMock).toHaveBeenCalledWith([{ id: '0', x: 10, y: 10 }], mouseMoveEvent);
50
+ expect(dragCallbackMock).toHaveBeenCalledTimes(1);
51
+ expect(dragCallbackMock).toHaveBeenCalledWith([{ id: '0', x: 10, y: 10 }], mouseMoveEvent);
52
+ expect(dragEndCallbackMock).toHaveBeenCalledTimes(1);
53
+ expect(dragEndCallbackMock).toHaveBeenCalledWith([{ id: '0', x: 10, y: 10 }], mouseUpEvent);
54
+ resolve();
55
+ });
56
+ });
57
+ test('performing dragging on selection of nodes should invoke all expected callbacks', () => {
58
+ dragNodeInteraction.updateCallback('onDragStart', dragStartCallbackMock);
59
+ dragNodeInteraction.updateCallback('onDrag', dragCallbackMock);
60
+ dragNodeInteraction.updateCallback('onDragEnd', dragEndCallbackMock);
61
+ myNVL.updateElementsInGraph([
62
+ { id: '0', selected: true },
63
+ { id: '1', selected: true }
64
+ ], []);
65
+ expect(myNVL.getSelectedNodes()).toHaveLength(2);
66
+ const mouseDownEvent = new MouseEvent('mousedown', {
67
+ clientX: 10,
68
+ clientY: 10
69
+ });
70
+ const mouseMoveEvent = new MouseEvent('mousemove', {
71
+ buttons: 1,
72
+ clientX: 20,
73
+ clientY: 20
74
+ });
75
+ const mouseUpEvent = new MouseEvent('mouseup', {
76
+ clientX: 20,
77
+ clientY: 20
78
+ });
79
+ const container = myNVL.getContainer();
80
+ container.dispatchEvent(mouseDownEvent);
81
+ container.dispatchEvent(mouseMoveEvent);
82
+ container.dispatchEvent(mouseUpEvent);
83
+ return new Promise((resolve) => {
84
+ expect(dragStartCallbackMock).toHaveBeenCalledTimes(1);
85
+ expect(dragStartCallbackMock).toHaveBeenCalledWith([
86
+ { id: '0', selected: true, x: 10, y: 10 },
87
+ { id: '1', selected: true, x: 200, y: 200 }
88
+ ], mouseMoveEvent);
89
+ expect(dragCallbackMock).toHaveBeenCalledTimes(1);
90
+ expect(dragCallbackMock).toHaveBeenCalledWith([
91
+ { id: '0', selected: true, x: 10, y: 10 },
92
+ { id: '1', selected: true, x: 200, y: 200 }
93
+ ], mouseMoveEvent);
94
+ expect(dragEndCallbackMock).toHaveBeenCalledTimes(1);
95
+ expect(dragEndCallbackMock).toHaveBeenCalledWith([
96
+ { id: '0', selected: true, x: 10, y: 10 },
97
+ { id: '1', selected: true, x: 200, y: 200 }
98
+ ], mouseUpEvent);
99
+ resolve();
100
+ });
101
+ });
102
+ test('dragging should not be invoked when the mouse is moved less than the drag threshold', () => {
103
+ dragNodeInteraction.updateCallback('onDragStart', dragStartCallbackMock);
104
+ dragNodeInteraction.updateCallback('onDrag', dragCallbackMock);
105
+ dragNodeInteraction.updateCallback('onDragEnd', dragEndCallbackMock);
106
+ expect(myNVL.getSelectedNodes()).toHaveLength(0);
107
+ const mouseDownEvent = new MouseEvent('mousedown', {
108
+ clientX: 10,
109
+ clientY: 10
110
+ });
111
+ const mouseMoveEvent = new MouseEvent('mousemove', {
112
+ buttons: 1,
113
+ clientX: 12,
114
+ clientY: 12
115
+ });
116
+ const mouseUpEvent = new MouseEvent('mouseup', {
117
+ clientX: 12,
118
+ clientY: 12
119
+ });
120
+ const container = myNVL.getContainer();
121
+ container.dispatchEvent(mouseDownEvent);
122
+ container.dispatchEvent(mouseMoveEvent);
123
+ container.dispatchEvent(mouseUpEvent);
124
+ return new Promise((resolve) => {
125
+ expect(dragStartCallbackMock).toHaveBeenCalledTimes(0);
126
+ expect(dragCallbackMock).toHaveBeenCalledTimes(0);
127
+ expect(dragEndCallbackMock).toHaveBeenCalledTimes(0);
128
+ resolve();
129
+ });
130
+ });
131
+ });
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -0,0 +1,306 @@
1
+ import NVL from '@neo4j-nvl/base';
2
+ import '@testing-library/jest-dom';
3
+ import { HoverInteraction } from '../interaction-handlers/hover-interaction';
4
+ // jest.mock('@neo4j-nvl/base')
5
+ jest.mock('@neo4j-nvl/layout-workers');
6
+ describe('HoverInteraction', () => {
7
+ let hoverInteraction;
8
+ let myNVL;
9
+ const callbackMock = jest.fn();
10
+ beforeEach(() => {
11
+ myNVL = new NVL(document.createElement('div'), [
12
+ { id: '0', x: 100, y: 100 },
13
+ { id: '1', x: 200, y: 200 }
14
+ ], [{ id: '10', from: '0', to: '1' }], { disableWebGL: true, initialZoom: 1 });
15
+ hoverInteraction = new HoverInteraction(myNVL, { drawShadowOnHover: true });
16
+ hoverInteraction.updateCallback('onHover', callbackMock);
17
+ });
18
+ afterEach(() => {
19
+ hoverInteraction.destroy();
20
+ myNVL.destroy();
21
+ callbackMock.mockReset();
22
+ });
23
+ describe('handleHover', () => {
24
+ it('should register hover event on canvas', () => {
25
+ const mouseEvent = new MouseEvent('mousemove', {
26
+ clientX: 10,
27
+ clientY: 10
28
+ });
29
+ hoverInteraction.handleHover(mouseEvent);
30
+ expect(callbackMock).toHaveBeenCalledWith(undefined, { nodes: [], relationships: [] }, mouseEvent);
31
+ expect(callbackMock).toHaveBeenCalledTimes(1);
32
+ });
33
+ it('should register hover event for node', () => {
34
+ const mouseEvent = new MouseEvent('mousemove', {
35
+ clientX: 100,
36
+ clientY: 100
37
+ });
38
+ hoverInteraction.handleHover(mouseEvent);
39
+ expect(callbackMock).toHaveBeenCalledWith({ id: '0', x: 100, y: 100, hovered: true }, {
40
+ nodes: [
41
+ {
42
+ data: { id: '0', x: 100, y: 100, hovered: true },
43
+ distance: 0,
44
+ targetCoordinates: { x: 100, y: 100 },
45
+ pointerCoordinates: { x: 100, y: 100 },
46
+ distanceVector: { x: 0, y: 0 },
47
+ insideNode: true
48
+ }
49
+ ],
50
+ relationships: [
51
+ {
52
+ data: { id: '10', from: '0', to: '1' },
53
+ distance: 0,
54
+ fromTargetCoordinates: { x: 100, y: 100 },
55
+ toTargetCoordinates: { x: 200, y: 200 },
56
+ pointerCoordinates: { x: 100, y: 100 }
57
+ }
58
+ ]
59
+ }, mouseEvent);
60
+ expect(callbackMock).toHaveBeenCalledTimes(1);
61
+ });
62
+ it('should register hover event for relationship', () => {
63
+ const mouseEvent = new MouseEvent('mousemove', {
64
+ clientX: 150,
65
+ clientY: 150
66
+ });
67
+ hoverInteraction.handleHover(mouseEvent);
68
+ expect(callbackMock).toHaveBeenCalledWith({ id: '10', from: '0', to: '1', hovered: true }, {
69
+ nodes: [],
70
+ relationships: [
71
+ {
72
+ data: { id: '10', from: '0', to: '1', hovered: true },
73
+ distance: 0,
74
+ fromTargetCoordinates: { x: 100, y: 100 },
75
+ toTargetCoordinates: { x: 200, y: 200 },
76
+ pointerCoordinates: { x: 150, y: 150 }
77
+ }
78
+ ]
79
+ }, mouseEvent);
80
+ expect(callbackMock).toHaveBeenCalledTimes(1);
81
+ });
82
+ it('should register un-hover event for node', () => {
83
+ const mouseEvent = new MouseEvent('mousemove', {
84
+ clientX: 100,
85
+ clientY: 100
86
+ });
87
+ hoverInteraction.handleHover(mouseEvent);
88
+ expect(callbackMock).toHaveBeenCalledWith({ id: '0', x: 100, y: 100, hovered: true }, {
89
+ nodes: [
90
+ {
91
+ data: { id: '0', x: 100, y: 100, hovered: true },
92
+ distance: 0,
93
+ targetCoordinates: { x: 100, y: 100 },
94
+ pointerCoordinates: { x: 100, y: 100 },
95
+ distanceVector: { x: 0, y: 0 },
96
+ insideNode: true
97
+ }
98
+ ],
99
+ relationships: [
100
+ {
101
+ data: { id: '10', from: '0', to: '1' },
102
+ distance: 0,
103
+ fromTargetCoordinates: { x: 100, y: 100 },
104
+ toTargetCoordinates: { x: 200, y: 200 },
105
+ pointerCoordinates: { x: 100, y: 100 }
106
+ }
107
+ ]
108
+ }, mouseEvent);
109
+ expect(callbackMock).toHaveBeenCalledTimes(1);
110
+ const mouseEvent2 = new MouseEvent('mousemove', {
111
+ clientX: 10,
112
+ clientY: 10
113
+ });
114
+ hoverInteraction.handleHover(mouseEvent2);
115
+ expect(callbackMock).toHaveBeenCalledWith({ id: '0', x: 100, y: 100, hovered: false }, {
116
+ nodes: [
117
+ {
118
+ data: { id: '0', x: 100, y: 100, hovered: false },
119
+ distance: 0,
120
+ targetCoordinates: { x: 100, y: 100 },
121
+ pointerCoordinates: { x: 100, y: 100 },
122
+ distanceVector: { x: 0, y: 0 },
123
+ insideNode: true
124
+ }
125
+ ],
126
+ relationships: [
127
+ {
128
+ data: { id: '10', from: '0', to: '1' },
129
+ distance: 0,
130
+ fromTargetCoordinates: { x: 100, y: 100 },
131
+ toTargetCoordinates: { x: 200, y: 200 },
132
+ pointerCoordinates: { x: 100, y: 100 }
133
+ }
134
+ ]
135
+ }, mouseEvent2);
136
+ expect(callbackMock).toHaveBeenCalledWith(undefined, { nodes: [], relationships: [] }, mouseEvent2);
137
+ expect(callbackMock).toHaveBeenCalledTimes(2);
138
+ });
139
+ it('should register un-hover event for relationship', () => {
140
+ const mouseEvent = new MouseEvent('mousemove', {
141
+ clientX: 150,
142
+ clientY: 150
143
+ });
144
+ hoverInteraction.handleHover(mouseEvent);
145
+ expect(callbackMock).toHaveBeenCalledWith({ id: '10', from: '0', to: '1', hovered: true }, {
146
+ nodes: [],
147
+ relationships: [
148
+ {
149
+ data: { id: '10', from: '0', to: '1', hovered: true },
150
+ distance: 0,
151
+ fromTargetCoordinates: { x: 100, y: 100 },
152
+ toTargetCoordinates: { x: 200, y: 200 },
153
+ pointerCoordinates: { x: 150, y: 150 }
154
+ }
155
+ ]
156
+ }, mouseEvent);
157
+ expect(callbackMock).toHaveBeenCalledTimes(1);
158
+ const mouseEvent2 = new MouseEvent('mousemove', {
159
+ clientX: 10,
160
+ clientY: 10
161
+ });
162
+ hoverInteraction.handleHover(mouseEvent2);
163
+ expect(callbackMock).toHaveBeenCalledWith({ id: '10', from: '0', to: '1', hovered: false }, {
164
+ nodes: [],
165
+ relationships: [
166
+ {
167
+ data: { id: '10', from: '0', to: '1', hovered: false },
168
+ distance: 0,
169
+ fromTargetCoordinates: { x: 100, y: 100 },
170
+ toTargetCoordinates: { x: 200, y: 200 },
171
+ pointerCoordinates: { x: 150, y: 150 }
172
+ }
173
+ ]
174
+ }, mouseEvent2);
175
+ expect(callbackMock).toHaveBeenCalledWith(undefined, { nodes: [], relationships: [] }, mouseEvent2);
176
+ expect(callbackMock).toHaveBeenCalledTimes(2);
177
+ });
178
+ it('should un-hover node and hover relationships when moving mouse from node to relationship', () => {
179
+ const mouseEvent = new MouseEvent('mousemove', {
180
+ clientX: 100,
181
+ clientY: 100
182
+ });
183
+ hoverInteraction.handleHover(mouseEvent);
184
+ expect(callbackMock).toHaveBeenCalledWith({ id: '0', x: 100, y: 100, hovered: true }, {
185
+ nodes: [
186
+ {
187
+ data: { id: '0', x: 100, y: 100, hovered: true },
188
+ distance: 0,
189
+ targetCoordinates: { x: 100, y: 100 },
190
+ pointerCoordinates: { x: 100, y: 100 },
191
+ distanceVector: { x: 0, y: 0 },
192
+ insideNode: true
193
+ }
194
+ ],
195
+ relationships: [
196
+ {
197
+ data: { id: '10', from: '0', to: '1' },
198
+ distance: 0,
199
+ fromTargetCoordinates: { x: 100, y: 100 },
200
+ toTargetCoordinates: { x: 200, y: 200 },
201
+ pointerCoordinates: { x: 100, y: 100 }
202
+ }
203
+ ]
204
+ }, mouseEvent);
205
+ expect(callbackMock).toHaveBeenCalledTimes(1);
206
+ const mouseEvent2 = new MouseEvent('mousemove', {
207
+ clientX: 150,
208
+ clientY: 150
209
+ });
210
+ hoverInteraction.handleHover(mouseEvent2);
211
+ expect(callbackMock).toHaveBeenCalledWith({ id: '0', x: 100, y: 100, hovered: false }, {
212
+ nodes: [
213
+ {
214
+ data: { id: '0', x: 100, y: 100, hovered: false },
215
+ distance: 0,
216
+ targetCoordinates: { x: 100, y: 100 },
217
+ pointerCoordinates: { x: 100, y: 100 },
218
+ distanceVector: { x: 0, y: 0 },
219
+ insideNode: true
220
+ }
221
+ ],
222
+ relationships: [
223
+ {
224
+ data: { id: '10', from: '0', to: '1', hovered: true },
225
+ distance: 0,
226
+ fromTargetCoordinates: { x: 100, y: 100 },
227
+ toTargetCoordinates: { x: 200, y: 200 },
228
+ pointerCoordinates: { x: 100, y: 100 }
229
+ }
230
+ ]
231
+ }, mouseEvent2);
232
+ expect(callbackMock).toHaveBeenCalledWith({ id: '10', from: '0', to: '1', hovered: true }, {
233
+ nodes: [],
234
+ relationships: [
235
+ {
236
+ data: { id: '10', from: '0', to: '1', hovered: true },
237
+ distance: 0,
238
+ fromTargetCoordinates: { x: 100, y: 100 },
239
+ toTargetCoordinates: { x: 200, y: 200 },
240
+ pointerCoordinates: { x: 150, y: 150 }
241
+ }
242
+ ]
243
+ }, mouseEvent2);
244
+ expect(callbackMock).toHaveBeenCalledTimes(2);
245
+ });
246
+ it('should un-hover relationship and hover node when moving mouse from relationship to node', () => {
247
+ const mouseEvent = new MouseEvent('mousemove', {
248
+ clientX: 150,
249
+ clientY: 150
250
+ });
251
+ hoverInteraction.handleHover(mouseEvent);
252
+ expect(callbackMock).toHaveBeenCalledWith({ id: '10', from: '0', to: '1', hovered: true }, {
253
+ nodes: [],
254
+ relationships: [
255
+ {
256
+ data: { id: '10', from: '0', to: '1', hovered: true },
257
+ distance: 0,
258
+ fromTargetCoordinates: { x: 100, y: 100 },
259
+ toTargetCoordinates: { x: 200, y: 200 },
260
+ pointerCoordinates: { x: 150, y: 150 }
261
+ }
262
+ ]
263
+ }, mouseEvent);
264
+ expect(callbackMock).toHaveBeenCalledTimes(1);
265
+ const mouseEvent2 = new MouseEvent('mousemove', {
266
+ clientX: 100,
267
+ clientY: 100
268
+ });
269
+ hoverInteraction.handleHover(mouseEvent2);
270
+ expect(callbackMock).toHaveBeenCalledWith({ id: '0', x: 100, y: 100, hovered: true }, {
271
+ nodes: [
272
+ {
273
+ data: { id: '0', x: 100, y: 100, hovered: true },
274
+ distance: 0,
275
+ targetCoordinates: { x: 100, y: 100 },
276
+ pointerCoordinates: { x: 100, y: 100 },
277
+ distanceVector: { x: 0, y: 0 },
278
+ insideNode: true
279
+ }
280
+ ],
281
+ relationships: [
282
+ {
283
+ data: { id: '10', from: '0', to: '1', hovered: false },
284
+ distance: 0,
285
+ fromTargetCoordinates: { x: 100, y: 100 },
286
+ toTargetCoordinates: { x: 200, y: 200 },
287
+ pointerCoordinates: { x: 100, y: 100 }
288
+ }
289
+ ]
290
+ }, mouseEvent2);
291
+ expect(callbackMock).toHaveBeenCalledWith({ id: '10', from: '0', to: '1', hovered: false }, {
292
+ nodes: [],
293
+ relationships: [
294
+ {
295
+ data: { id: '10', from: '0', to: '1', hovered: false },
296
+ distance: 0,
297
+ fromTargetCoordinates: { x: 100, y: 100 },
298
+ toTargetCoordinates: { x: 200, y: 200 },
299
+ pointerCoordinates: { x: 150, y: 150 }
300
+ }
301
+ ]
302
+ }, mouseEvent2);
303
+ expect(callbackMock).toHaveBeenCalledTimes(2);
304
+ });
305
+ });
306
+ });
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -0,0 +1,79 @@
1
+ import NVL from '@neo4j-nvl/base';
2
+ import '@testing-library/jest-dom';
3
+ import { LassoInteraction } from '../interaction-handlers/lasso-interaction';
4
+ jest.mock('@neo4j-nvl/layout-workers');
5
+ const testNodes = [
6
+ { id: '0', x: 10, y: 10 },
7
+ { id: '1', x: 200, y: 200 }
8
+ ];
9
+ const testRels = [{ id: '10', from: '0', to: '1' }];
10
+ const createMouseEvent = (type, x, y) => {
11
+ return new MouseEvent(type, { button: 0, buttons: 1, clientX: x, clientY: y });
12
+ };
13
+ describe('LassoInteraction', () => {
14
+ let lassoInteraction;
15
+ let myNVL;
16
+ const startCallback = jest.fn();
17
+ const selectCallback = jest.fn();
18
+ beforeEach(() => {
19
+ myNVL = new NVL(document.createElement('div'), testNodes, testRels, {
20
+ disableWebGL: true,
21
+ initialZoom: 1,
22
+ layout: 'free'
23
+ });
24
+ lassoInteraction = new LassoInteraction(myNVL);
25
+ });
26
+ afterEach(() => {
27
+ lassoInteraction.destroy();
28
+ myNVL.destroy();
29
+ startCallback.mockReset();
30
+ selectCallback.mockReset();
31
+ });
32
+ test('can select individual nodes', () => {
33
+ lassoInteraction.updateCallback('onLassoStarted', startCallback);
34
+ lassoInteraction.updateCallback('onLassoSelect', selectCallback);
35
+ expect(myNVL.getSelectedNodes()).toHaveLength(0);
36
+ const gesture1 = [
37
+ createMouseEvent('mousedown', 0, 0),
38
+ createMouseEvent('mousemove', 30, 0),
39
+ createMouseEvent('mousemove', 20, 20),
40
+ createMouseEvent('mouseup', 0, 0)
41
+ ];
42
+ const gesture2 = [
43
+ createMouseEvent('mousedown', 200, 180),
44
+ createMouseEvent('mousemove', 210, 210),
45
+ createMouseEvent('mouseup', 190, 210)
46
+ ];
47
+ const container = myNVL.getContainer();
48
+ gesture1.forEach((e) => container.dispatchEvent(e));
49
+ gesture2.forEach((e) => container.dispatchEvent(e));
50
+ return new Promise((resolve) => {
51
+ expect(startCallback).toHaveBeenCalledTimes(2);
52
+ expect(selectCallback).toHaveBeenCalledTimes(2);
53
+ expect(selectCallback).toHaveBeenNthCalledWith(1, { nodes: [testNodes[0]], rels: [] }, gesture1[3]);
54
+ expect(selectCallback).toHaveBeenNthCalledWith(2, { nodes: [testNodes[1]], rels: [] }, gesture2[2]);
55
+ resolve();
56
+ });
57
+ });
58
+ test('can select relationships', () => {
59
+ lassoInteraction.updateCallback('onLassoStarted', startCallback);
60
+ lassoInteraction.updateCallback('onLassoSelect', selectCallback);
61
+ expect(myNVL.getSelectedNodes()).toHaveLength(0);
62
+ const gesture = [
63
+ createMouseEvent('mousedown', 0, 0),
64
+ createMouseEvent('mousemove', 250, 0),
65
+ createMouseEvent('mousemove', 240, 240),
66
+ createMouseEvent('mousemove', 100, 200),
67
+ createMouseEvent('mousemove', 50, 0),
68
+ createMouseEvent('mouseup', 0, 0)
69
+ ];
70
+ const container = myNVL.getContainer();
71
+ gesture.forEach((e) => container.dispatchEvent(e));
72
+ return new Promise((resolve) => {
73
+ expect(startCallback).toHaveBeenCalledTimes(1);
74
+ expect(selectCallback).toHaveBeenCalledTimes(1);
75
+ expect(selectCallback).toHaveBeenCalledWith({ nodes: [testNodes[0], testNodes[1]], rels: [testRels[0]] }, gesture[5]);
76
+ resolve();
77
+ });
78
+ });
79
+ });
@@ -39,13 +39,13 @@ class BaseInteraction {
39
39
  * @internal
40
40
  */
41
41
  addEventListener = (type, listener, options) => {
42
- this.container.addEventListener(type, listener, options);
42
+ this.container?.addEventListener(type, listener, options);
43
43
  };
44
44
  /**
45
45
  * @internal
46
46
  */
47
47
  removeEventListener = (type, listener, options) => {
48
- this.container.removeEventListener(type, listener, options);
48
+ this.container?.removeEventListener(type, listener, options);
49
49
  };
50
50
  /**
51
51
  * @internal
@@ -33,12 +33,16 @@ export type BoxSelectInteractionCallbacks = {
33
33
  *
34
34
  * @example
35
35
  * ```js
36
- * const nvl = new NVL(container, nodes, relationships)
36
+ * import { BoxSelectInteraction } from '@neo4j-nvl/interaction-handlers'
37
+ * import { NVL } from '@neo4j-nvl/base'
38
+ *
39
+ * const nvl = new NVL(document.createElement('div'), [{ id: '0' }], [])
37
40
  * const boxSelectInteraction = new BoxSelectInteraction(nvl)
38
41
  *
39
42
  * boxSelectInteraction.updateCallback('onBoxSelect', ({ nodes, rels }) => {
40
43
  * console.log('Selected elements:', nodes, rels)
41
44
  * })
45
+ * ```
42
46
  */
43
47
  export declare class BoxSelectInteraction extends BaseInteraction<BoxSelectInteractionCallbacks, BoxSelectInteractionOptions> {
44
48
  private mousePosition;
@@ -9,12 +9,16 @@ import { getCanvasPosition, getWorldPosition } from './utils';
9
9
  *
10
10
  * @example
11
11
  * ```js
12
- * const nvl = new NVL(container, nodes, relationships)
12
+ * import { BoxSelectInteraction } from '@neo4j-nvl/interaction-handlers'
13
+ * import { NVL } from '@neo4j-nvl/base'
14
+ *
15
+ * const nvl = new NVL(document.createElement('div'), [{ id: '0' }], [])
13
16
  * const boxSelectInteraction = new BoxSelectInteraction(nvl)
14
17
  *
15
18
  * boxSelectInteraction.updateCallback('onBoxSelect', ({ nodes, rels }) => {
16
19
  * console.log('Selected elements:', nodes, rels)
17
20
  * })
21
+ * ```
18
22
  */
19
23
  export class BoxSelectInteraction extends BaseInteraction {
20
24
  mousePosition;
@@ -81,7 +81,7 @@ export type ClickInteractionCallbacks = {
81
81
  * import { ClickInteraction } from '@neo4j-nvl/interaction-handlers'
82
82
  * import { NVL } from '@neo4j-nvl/base'
83
83
  *
84
- * const nvl = new NVL(nodes, relationships, options)
84
+ * const nvl = new NVL(document.createElement('div'), [{ id: '0' }], [])
85
85
  * const clickInteraction = new ClickInteraction(nvl)
86
86
  * clickInteraction.updateCallback('onNodeClick', (node) => {
87
87
  * console.log('Node clicked', node)
@@ -9,7 +9,7 @@ import { isDraggingMovement } from './utils';
9
9
  * import { ClickInteraction } from '@neo4j-nvl/interaction-handlers'
10
10
  * import { NVL } from '@neo4j-nvl/base'
11
11
  *
12
- * const nvl = new NVL(nodes, relationships, options)
12
+ * const nvl = new NVL(document.createElement('div'), [{ id: '0' }], [])
13
13
  * const clickInteraction = new ClickInteraction(nvl)
14
14
  * clickInteraction.updateCallback('onNodeClick', (node) => {
15
15
  * console.log('Node clicked', node)
@@ -30,7 +30,10 @@ export type DragNodeInteractionCallbacks = {
30
30
  *
31
31
  * @example
32
32
  * ```js
33
- * const nvl = new NVL(container, nodes, relationships)
33
+ * import { NVL } from '@neo4j-nvl/base'
34
+ * import { DragNodeInteraction } from '@neo4j-nvl/interaction-handlers'
35
+ *
36
+ * const nvl = new NVL(document.createElement('div'), [{ id: '0' }], [])
34
37
  * const dragNodeInteraction = new DragNodeInteraction(nvl)
35
38
  *
36
39
  * dragNodeInteraction.updateCallback('onDrag', (nodes) => {
@@ -8,7 +8,10 @@ import { isDraggingMovement } from './utils';
8
8
  *
9
9
  * @example
10
10
  * ```js
11
- * const nvl = new NVL(container, nodes, relationships)
11
+ * import { NVL } from '@neo4j-nvl/base'
12
+ * import { DragNodeInteraction } from '@neo4j-nvl/interaction-handlers'
13
+ *
14
+ * const nvl = new NVL(document.createElement('div'), [{ id: '0' }], [])
12
15
  * const dragNodeInteraction = new DragNodeInteraction(nvl)
13
16
  *
14
17
  * dragNodeInteraction.updateCallback('onDrag', (nodes) => {
@@ -29,7 +29,12 @@ export declare class DrawInteraction extends BaseInteraction<DrawInteractionCall
29
29
  private newTempSelfReferredRelationship;
30
30
  private newTargetNodeToAdd;
31
31
  private newRelationshipToAdd;
32
+ private mouseOutsideOfNvlArea;
32
33
  constructor(nvl: NVL, options?: DrawInteractionOptions);
34
+ private cancelDrawing;
35
+ private handleMouseUpGlobal;
36
+ private handleMouseLeaveNvl;
37
+ private handleMouseEnterNvl;
33
38
  private handleMouseMove;
34
39
  private setNewRegularRelationship;
35
40
  private setNewRegularRelationshipToNewTempTargetNode;
@@ -27,6 +27,7 @@ export class DrawInteraction extends BaseInteraction {
27
27
  newTempSelfReferredRelationship;
28
28
  newTargetNodeToAdd;
29
29
  newRelationshipToAdd;
30
+ mouseOutsideOfNvlArea;
30
31
  constructor(nvl, options = {}) {
31
32
  super(nvl, options);
32
33
  this.isMoved = false;
@@ -36,7 +37,37 @@ export class DrawInteraction extends BaseInteraction {
36
37
  this.addEventListener('mousemove', this.handleMouseMove, true);
37
38
  this.addEventListener('mousedown', this.handleMouseDown, true);
38
39
  this.addEventListener('mouseup', this.handleMouseUp, true);
40
+ this.containerInstance?.addEventListener('mouseleave', this.handleMouseLeaveNvl);
41
+ this.containerInstance?.addEventListener('mouseenter', this.handleMouseEnterNvl);
42
+ document.addEventListener('mouseup', this.handleMouseUpGlobal, true);
39
43
  }
44
+ cancelDrawing = () => {
45
+ this.nvlInstance.removeRelationshipsWithIds([
46
+ this.newTempRegularRelationshipToNewTempTargetNode?.id,
47
+ this.newTempRegularRelationshipToExistingNode?.id,
48
+ this.newTempSelfReferredRelationship?.id
49
+ ].filter((id) => Boolean(id)));
50
+ this.nvlInstance.removeNodesWithIds(this.newTempTargetNode?.id ? [this.newTempTargetNode?.id] : []);
51
+ this.newTempTargetNode = null;
52
+ this.newTempRegularRelationshipToNewTempTargetNode = null;
53
+ this.newTempRegularRelationshipToExistingNode = null;
54
+ this.newTempSelfReferredRelationship = null;
55
+ this.isMoved = false;
56
+ this.isDrawing = false;
57
+ this.isDraggingNode = false;
58
+ };
59
+ handleMouseUpGlobal = (event) => {
60
+ // If mouse up outside of nvl area while drawing, cancel drawing.
61
+ if (this.isDrawing && this.mouseOutsideOfNvlArea) {
62
+ this.cancelDrawing();
63
+ }
64
+ };
65
+ handleMouseLeaveNvl = () => {
66
+ this.mouseOutsideOfNvlArea = true;
67
+ };
68
+ handleMouseEnterNvl = () => {
69
+ this.mouseOutsideOfNvlArea = false;
70
+ };
40
71
  handleMouseMove = (event) => {
41
72
  this.isMoved = true;
42
73
  if (this.isDrawing) {
@@ -174,11 +205,19 @@ export class DrawInteraction extends BaseInteraction {
174
205
  const hits = this.nvlInstance.getHits(event, ['node'], { hitNodeMarginWidth: NODE_EDGE_WIDTH });
175
206
  const hitNodes = hits.nvlTargets.nodes.filter((node) => node.insideNode);
176
207
  const hitNodeEdges = hits.nvlTargets.nodes.filter((node) => !node.insideNode);
177
- if (hitNodes.length > 0) {
208
+ const startDragging = hitNodes.length > 0;
209
+ const startDrawing = hitNodeEdges.length > 0;
210
+ if (startDragging || startDrawing) {
211
+ // Prevent default behavior to avoid interactions with other elements outside of nvl, like text selection,
212
+ // but still assign focus to nvl container on click
213
+ event.preventDefault();
214
+ this.containerInstance?.focus();
215
+ }
216
+ if (startDragging) {
178
217
  this.isDraggingNode = true;
179
218
  this.isDrawing = false;
180
219
  }
181
- else if (hitNodeEdges.length > 0) {
220
+ else if (startDrawing) {
182
221
  this.isDrawing = true;
183
222
  this.isDraggingNode = false;
184
223
  this.mouseDownNode = hitNodeEdges[0];
@@ -237,5 +276,8 @@ export class DrawInteraction extends BaseInteraction {
237
276
  this.removeEventListener('mousemove', this.handleMouseMove, true);
238
277
  this.removeEventListener('mousedown', this.handleMouseDown, true);
239
278
  this.removeEventListener('mouseup', this.handleMouseUp, true);
279
+ this.containerInstance?.removeEventListener('mouseleave', this.handleMouseLeaveNvl);
280
+ this.containerInstance?.removeEventListener('mouseenter', this.handleMouseEnterNvl);
281
+ document.removeEventListener('mouseup', this.handleMouseUpGlobal, true);
240
282
  };
241
283
  }
@@ -28,8 +28,11 @@ export type HoverInteractionCallbacks = {
28
28
  * Interaction handler for hovering nodes and relationships.
29
29
  *
30
30
  * @example
31
- * ```js
32
- * const nvl = new NVL(container, nodes, relationships)
31
+ * ```ts
32
+ * import { NVL } from '@neo4j-nvl/base'
33
+ * import { HoverInteraction } from '@neo4j-nvl/interaction-handlers'
34
+ *
35
+ * const nvl = new NVL(document.createElement('div'), [{ id: '0' }], [])
33
36
  * const hoverInteraction = new HoverInteraction(nvl)
34
37
  *
35
38
  * hoverInteraction.updateCallback('onHover', (element, hitElements, event) => {
@@ -3,8 +3,11 @@ import { BaseInteraction } from './base';
3
3
  * Interaction handler for hovering nodes and relationships.
4
4
  *
5
5
  * @example
6
- * ```js
7
- * const nvl = new NVL(container, nodes, relationships)
6
+ * ```ts
7
+ * import { NVL } from '@neo4j-nvl/base'
8
+ * import { HoverInteraction } from '@neo4j-nvl/interaction-handlers'
9
+ *
10
+ * const nvl = new NVL(document.createElement('div'), [{ id: '0' }], [])
8
11
  * const hoverInteraction = new HoverInteraction(nvl)
9
12
  *
10
13
  * hoverInteraction.updateCallback('onHover', (element, hitElements, event) => {
@@ -33,7 +36,7 @@ export class HoverInteraction extends BaseInteraction {
33
36
  handleHover = (event) => {
34
37
  const { nvlTargets } = this.nvlInstance.getHits(event);
35
38
  const { nodes = [], relationships = [] } = nvlTargets;
36
- const mainTarget = nodes[0] !== undefined ? nodes[0] : relationships[0];
39
+ const mainTarget = nodes[0] ?? relationships[0];
37
40
  const hoveredElement = mainTarget?.data;
38
41
  if (this.currentElementNeedsUnHover(hoveredElement?.id)) {
39
42
  this.unHoverCurrentElement();
@@ -32,13 +32,17 @@ export type LassoInteractionCallbacks = {
32
32
  * area will be selected.
33
33
  *
34
34
  * @example
35
- * ```js
36
- * const nvl = new NVL(container, nodes, relationships)
35
+ * ```ts
36
+ * import { NVL } from '@neo4j-nvl/base'
37
+ * import { LassoInteraction } from '@neo4j-nvl/interaction-handlers'
38
+ *
39
+ * const nvl = new NVL(document.createElement('div'), [{ id: '0' }], [])
37
40
  * const lassoInteraction = new LassoInteraction(nvl)
38
41
  *
39
42
  * lassoInteraction.updateCallback('onLassoSelect', ({ nodes, rels }) => {
40
43
  * console.log('Selected elements:', nodes, rels)
41
44
  * })
45
+ * ```
42
46
  */
43
47
  export declare class LassoInteraction extends BaseInteraction<LassoInteractionCallbacks, LassoInteractionOptions> {
44
48
  private active;
@@ -10,13 +10,17 @@ const shapeShowTime = 500;
10
10
  * area will be selected.
11
11
  *
12
12
  * @example
13
- * ```js
14
- * const nvl = new NVL(container, nodes, relationships)
13
+ * ```ts
14
+ * import { NVL } from '@neo4j-nvl/base'
15
+ * import { LassoInteraction } from '@neo4j-nvl/interaction-handlers'
16
+ *
17
+ * const nvl = new NVL(document.createElement('div'), [{ id: '0' }], [])
15
18
  * const lassoInteraction = new LassoInteraction(nvl)
16
19
  *
17
20
  * lassoInteraction.updateCallback('onLassoSelect', ({ nodes, rels }) => {
18
21
  * console.log('Selected elements:', nodes, rels)
19
22
  * })
23
+ * ```
20
24
  */
21
25
  export class LassoInteraction extends BaseInteraction {
22
26
  active;
@@ -30,12 +30,16 @@ export type PanInteractionCallbacks = {
30
30
  *
31
31
  * @example
32
32
  * ```js
33
- * const nvl = new NVL(container, nodes, relationships)
33
+ * import { NVL } from '@neo4j-nvl/base'
34
+ * import { PanInteraction } from '@neo4j-nvl/interaction-handlers'
35
+ *
36
+ * const nvl = new NVL(document.createElement('div'), [{ id: '0' }], [])
34
37
  * const panInteraction = new PanInteraction(nvl)
35
38
  *
36
39
  * panInteraction.updateCallback('onPan', (panning) => {
37
40
  * console.log('Panning:', panning)
38
41
  * })
42
+ * ```
39
43
  */
40
44
  export declare class PanInteraction extends BaseInteraction<PanInteractionCallbacks, PanInteractionOptions> {
41
45
  private mousePosition;
@@ -54,6 +58,12 @@ export declare class PanInteraction extends BaseInteraction<PanInteractionCallba
54
58
  *
55
59
  * @example
56
60
  * ```js
61
+ * import { NVL } from '@neo4j-nvl/base'
62
+ * import { PanInteraction } from '@neo4j-nvl/interaction-handlers'
63
+ *
64
+ * const nvl = new NVL(document.createElement('div'), [{ id: '0' }], [])
65
+ * const panInteraction = new PanInteraction(nvl)
66
+ *
57
67
  * // Pan canvas even when dragging on nodes and relationships
58
68
  * panInteraction.updateTargets([], true)
59
69
  * ```
@@ -7,12 +7,16 @@ import { BaseInteraction } from './base';
7
7
  *
8
8
  * @example
9
9
  * ```js
10
- * const nvl = new NVL(container, nodes, relationships)
10
+ * import { NVL } from '@neo4j-nvl/base'
11
+ * import { PanInteraction } from '@neo4j-nvl/interaction-handlers'
12
+ *
13
+ * const nvl = new NVL(document.createElement('div'), [{ id: '0' }], [])
11
14
  * const panInteraction = new PanInteraction(nvl)
12
15
  *
13
16
  * panInteraction.updateCallback('onPan', (panning) => {
14
17
  * console.log('Panning:', panning)
15
18
  * })
19
+ * ```
16
20
  */
17
21
  export class PanInteraction extends BaseInteraction {
18
22
  mousePosition;
@@ -39,6 +43,12 @@ export class PanInteraction extends BaseInteraction {
39
43
  *
40
44
  * @example
41
45
  * ```js
46
+ * import { NVL } from '@neo4j-nvl/base'
47
+ * import { PanInteraction } from '@neo4j-nvl/interaction-handlers'
48
+ *
49
+ * const nvl = new NVL(document.createElement('div'), [{ id: '0' }], [])
50
+ * const panInteraction = new PanInteraction(nvl)
51
+ *
42
52
  * // Pan canvas even when dragging on nodes and relationships
43
53
  * panInteraction.updateTargets([], true)
44
54
  * ```
@@ -17,7 +17,10 @@ export type ZoomInteractionCallbacks = {
17
17
  *
18
18
  * @example
19
19
  * ```js
20
- * const nvl = new NVL(container, nodes, relationships)
20
+ * import { NVL } from '@neo4j-nvl/base'
21
+ * import { ZoomInteraction } from '@neo4j-nvl/interaction-handlers'
22
+ *
23
+ * const nvl = new NVL(document.createElement('div'), [{ id: '0' }], [])
21
24
  * const zoomInteraction = new ZoomInteraction(nvl)
22
25
  *
23
26
  * zoomInteraction.updateCallback('onZoom', (zoomLevel) => {
@@ -7,7 +7,10 @@ import { getCanvasCenterOffset } from './utils';
7
7
  *
8
8
  * @example
9
9
  * ```js
10
- * const nvl = new NVL(container, nodes, relationships)
10
+ * import { NVL } from '@neo4j-nvl/base'
11
+ * import { ZoomInteraction } from '@neo4j-nvl/interaction-handlers'
12
+ *
13
+ * const nvl = new NVL(document.createElement('div'), [{ id: '0' }], [])
11
14
  * const zoomInteraction = new ZoomInteraction(nvl)
12
15
  *
13
16
  * zoomInteraction.updateCallback('onZoom', (zoomLevel) => {
@@ -18,15 +18,15 @@ export class OverlayRenderer {
18
18
  canvas.style.right = '0';
19
19
  // These are needed for touchpad zoom on Edge
20
20
  canvas.style.touchAction = 'none';
21
- canvasParent.appendChild(canvas);
21
+ canvasParent?.appendChild(canvas);
22
22
  const context = canvas.getContext('2d');
23
23
  this.ctx = context;
24
24
  this.canvas = canvas;
25
25
  const handleResize = () => {
26
26
  this.fixCanvasSize(canvas);
27
27
  };
28
- canvasParent.addEventListener('resize', handleResize);
29
- this.removeResizeListener = () => canvasParent.removeEventListener('resize', handleResize);
28
+ canvasParent?.addEventListener('resize', handleResize);
29
+ this.removeResizeListener = () => canvasParent?.removeEventListener('resize', handleResize);
30
30
  this.fixCanvasSize(canvas);
31
31
  }
32
32
  fixCanvasSize(canvas) {
package/package.json CHANGED
@@ -1,7 +1,17 @@
1
1
  {
2
2
  "name": "@neo4j-nvl/interaction-handlers",
3
- "version": "0.3.1",
3
+ "version": "0.3.2-e0e72a58",
4
4
  "license": "SEE LICENSE IN 'LICENSE.txt'",
5
+ "homepage": "https://neo4j.com/docs/nvl/current/",
6
+ "description": "Interaction handlers for the Neo4j Visualization Library",
7
+ "keywords": [
8
+ "neo4j",
9
+ "visualization",
10
+ "graph"
11
+ ],
12
+ "bugs": {
13
+ "url": "https://community.neo4j.com/c/neo4j-graph-platform/neo4j-bloom"
14
+ },
5
15
  "main": "lib/index.js",
6
16
  "types": "lib/index.d.ts",
7
17
  "files": [
@@ -10,9 +20,11 @@
10
20
  ],
11
21
  "scripts": {
12
22
  "build": "tsc",
23
+ "watch": "tsc -w",
13
24
  "test": "jest",
14
25
  "prepack": "cp ../../LICENSE.txt ./",
15
- "postpack": "rm LICENSE.txt"
26
+ "postpack": "rm LICENSE.txt",
27
+ "eslint": "eslint ./src/"
16
28
  },
17
29
  "typedoc": {
18
30
  "entryPoint": "./src/index.ts",
@@ -21,7 +33,7 @@
21
33
  "tsconfig": "./tsconfig.json"
22
34
  },
23
35
  "dependencies": {
24
- "@neo4j-nvl/base": "^0.3.1",
36
+ "@neo4j-nvl/base": "^0.3.2-e0e72a58",
25
37
  "concaveman": "^1.2.1",
26
38
  "lodash": "4.17.21"
27
39
  },
@@ -30,11 +42,14 @@
30
42
  "@testing-library/react": "^13.4.0",
31
43
  "@types/concaveman": "1.1.6",
32
44
  "@types/lodash": "4.14.202",
45
+ "eslint": "8.38.0",
33
46
  "jest": "^29.7.0",
34
47
  "typescript": "^5.4.5"
35
48
  },
36
49
  "peerDependencies": {
50
+ "eslint": "*",
37
51
  "jest": "*",
38
52
  "typescript": "*"
39
- }
53
+ },
54
+ "stableVersion": "0.3.2"
40
55
  }