@neo4j-nvl/react 0.3.1 → 0.3.2-ea823910
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/README.md +7 -8
- package/lib/__tests__/BasicNvlWrapper.test.d.ts +1 -0
- package/lib/__tests__/BasicNvlWrapper.test.js +35 -0
- package/lib/__tests__/InteractiveNvlWrapper.test.d.ts +1 -0
- package/lib/__tests__/InteractiveNvlWrapper.test.js +90 -0
- package/lib/__tests__/index.test.d.ts +1 -0
- package/lib/__tests__/index.test.js +8 -0
- package/lib/basic-wrapper/BasicNvlWrapper.d.ts +27 -19
- package/lib/basic-wrapper/BasicNvlWrapper.js +36 -21
- package/lib/interactive-nvl-wrapper/InteractiveNvlWrapper.d.ts +19 -16
- package/lib/interactive-nvl-wrapper/InteractiveNvlWrapper.js +28 -25
- package/lib/interactive-nvl-wrapper/hooks.d.ts +1 -1
- package/lib/interactive-nvl-wrapper/hooks.js +5 -0
- package/lib/utils/constants.d.ts +2 -0
- package/lib/utils/constants.js +2 -0
- package/lib/utils/hooks.js +1 -0
- package/package.json +21 -5
package/README.md
CHANGED
|
@@ -14,15 +14,10 @@ npm install @neo4j-nvl/react
|
|
|
14
14
|
|
|
15
15
|
### Using the library
|
|
16
16
|
|
|
17
|
-
This is an example on how to use the BasicReactWrapper component:
|
|
17
|
+
This is an example on how to use the BasicReactWrapper component:
|
|
18
18
|
|
|
19
19
|
```tsx
|
|
20
|
-
<BasicNvlWrapper
|
|
21
|
-
nodes={nodes}
|
|
22
|
-
rels={relationships}
|
|
23
|
-
nvlOptions={options}
|
|
24
|
-
nvlCallbacks={callbacks}
|
|
25
|
-
/>
|
|
20
|
+
<BasicNvlWrapper nodes={nodes} rels={relationships} nvlOptions={options} nvlCallbacks={callbacks} />
|
|
26
21
|
```
|
|
27
22
|
|
|
28
23
|
When nodes and/or relationships are updated in the React wrapper, the NVL instance will be updated accordingly:
|
|
@@ -81,5 +76,9 @@ const [multiSelect, setMultiSelect] = useState(false)
|
|
|
81
76
|
</>
|
|
82
77
|
```
|
|
83
78
|
|
|
79
|
+
You can also find more instructions and examples on how to use the NVL React wrappers in the [docs](https://neo4j.com/docs/nvl/current/react-wrappers/).
|
|
84
80
|
|
|
85
|
-
|
|
81
|
+
### Product analytics
|
|
82
|
+
|
|
83
|
+
In order to improve the library we are collecting some information about the usage of NVL. If you prefer to disable this behavior set the parameter `disableTelemetry=true` as a parameter in `nvlOptions`.
|
|
84
|
+
For example: `<BasicNvlWrapper nvlOptions={{ disableTelemetry: true }} nodes=[0,1] rels=[0,1]/>` or `<InteractiveNvlWrapper nvlOptions={{ disableTelemetry: true }} nodes=[0,1] rels=[0,1]/>`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import NVL, { HierarchicalLayoutType } from '@neo4j-nvl/base';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import { render } from '@testing-library/react';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { BasicNvlWrapper } from '../basic-wrapper/BasicNvlWrapper';
|
|
7
|
+
jest.mock('@neo4j-nvl/base');
|
|
8
|
+
jest.mock('@neo4j-nvl/layout-workers');
|
|
9
|
+
describe('BasicNvlWrapper', () => {
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
jest.clearAllMocks();
|
|
12
|
+
});
|
|
13
|
+
test('initialises NVL expectedly with an empty graph object and no other properties', () => {
|
|
14
|
+
render(_jsx("div", { children: _jsx(BasicNvlWrapper, { nodes: [], rels: [] }) }));
|
|
15
|
+
expect(NVL).toHaveBeenCalledWith(expect.any(HTMLDivElement), [], [], {}, {});
|
|
16
|
+
});
|
|
17
|
+
test('initialises NVL expectedly with a graph object and properties', () => {
|
|
18
|
+
const mockLayoutDoneFunction = jest.fn();
|
|
19
|
+
render(_jsx("div", { children: _jsx(BasicNvlWrapper, { nodes: [{ id: '1' }, { id: '2' }], rels: [{ id: '12', from: '1', to: '2' }], layout: HierarchicalLayoutType, layoutOptions: { enableCytoscape: true }, nvlOptions: { renderer: 'canvas' }, nvlCallbacks: { onLayoutDone: mockLayoutDoneFunction } }) }));
|
|
20
|
+
expect(NVL).toHaveBeenCalledWith(expect.any(HTMLDivElement), [{ id: '1' }, { id: '2' }], [{ id: '12', from: '1', to: '2' }], { renderer: 'canvas', layout: HierarchicalLayoutType, layoutOptions: { enableCytoscape: true } }, {
|
|
21
|
+
onLayoutDone: mockLayoutDoneFunction
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
test('destroys NVL on unmount', () => {
|
|
25
|
+
const destroy = jest.fn();
|
|
26
|
+
jest.spyOn(NVL.prototype, 'destroy').mockImplementation(destroy);
|
|
27
|
+
const { unmount } = render(_jsx("div", { children: _jsx(BasicNvlWrapper, { nodes: [], rels: [] }) }));
|
|
28
|
+
unmount();
|
|
29
|
+
expect(destroy).toHaveBeenCalled();
|
|
30
|
+
});
|
|
31
|
+
test('successfully re-initialises NVL when using React StrictMode', () => {
|
|
32
|
+
render(_jsx(React.StrictMode, { children: _jsx("div", { children: _jsx(BasicNvlWrapper, { nodes: [], rels: [] }) }) }));
|
|
33
|
+
expect(NVL).toHaveBeenCalledTimes(2);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import NVL, { HierarchicalLayoutType } from '@neo4j-nvl/base';
|
|
3
|
+
import { BoxSelectInteraction, ClickInteraction, DragNodeInteraction, DrawInteraction, HoverInteraction, LassoInteraction, PanInteraction, ZoomInteraction } from '@neo4j-nvl/interaction-handlers';
|
|
4
|
+
import '@testing-library/jest-dom';
|
|
5
|
+
import { render } from '@testing-library/react';
|
|
6
|
+
import React, { createRef } from 'react';
|
|
7
|
+
import { InteractiveNvlWrapper } from '../interactive-nvl-wrapper/InteractiveNvlWrapper';
|
|
8
|
+
jest.mock('@neo4j-nvl/base');
|
|
9
|
+
jest.mock('@neo4j-nvl/layout-workers');
|
|
10
|
+
jest.mock('@neo4j-nvl/interaction-handlers');
|
|
11
|
+
HoverInteraction.prototype.callbackMap = new Map();
|
|
12
|
+
PanInteraction.prototype.callbackMap = new Map();
|
|
13
|
+
describe('InteractiveNvlWrapper', () => {
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
});
|
|
17
|
+
test('initialises NVL expectedly with an empty graph object and no other properties', () => {
|
|
18
|
+
render(_jsx("div", { children: _jsx(InteractiveNvlWrapper, { nodes: [], rels: [] }) }));
|
|
19
|
+
expect(NVL).toHaveBeenCalledWith(expect.any(HTMLDivElement), [], [], {}, {});
|
|
20
|
+
});
|
|
21
|
+
test('initialises NVL expectedly with a graph object and properties', () => {
|
|
22
|
+
const mockLayoutDoneFunction = jest.fn();
|
|
23
|
+
render(_jsx(InteractiveNvlWrapper, { nodes: [{ id: '1' }, { id: '2' }], rels: [{ id: '12', from: '1', to: '2' }], layout: HierarchicalLayoutType, layoutOptions: { enableCytoscape: true }, nvlOptions: { renderer: 'canvas' }, nvlCallbacks: { onLayoutDone: mockLayoutDoneFunction } }));
|
|
24
|
+
expect(NVL).toHaveBeenCalledWith(expect.any(HTMLDivElement), [{ id: '1' }, { id: '2' }], [{ id: '12', from: '1', to: '2' }], { renderer: 'canvas', layout: HierarchicalLayoutType, layoutOptions: { enableCytoscape: true } }, {
|
|
25
|
+
onLayoutDone: mockLayoutDoneFunction
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
test('initialises expected interaction handlers with correct options', () => {
|
|
29
|
+
const myNvlRef = createRef();
|
|
30
|
+
jest.spyOn(NVL.prototype, 'getContainer').mockImplementation(() => document.createElement('div'));
|
|
31
|
+
render(_jsx(InteractiveNvlWrapper, { nodes: [], rels: [], ref: myNvlRef, mouseEventCallbacks: {
|
|
32
|
+
onHover: true,
|
|
33
|
+
onPan: true
|
|
34
|
+
}, interactionOptions: {
|
|
35
|
+
drawShadowOnHover: true
|
|
36
|
+
} }));
|
|
37
|
+
expect(HoverInteraction).toHaveBeenCalledWith(myNvlRef.current, { drawShadowOnHover: true });
|
|
38
|
+
expect(PanInteraction).toHaveBeenCalledWith(myNvlRef.current, { drawShadowOnHover: true });
|
|
39
|
+
expect(HoverInteraction).toHaveBeenCalledTimes(1);
|
|
40
|
+
expect(PanInteraction).toHaveBeenCalledTimes(1);
|
|
41
|
+
expect(ClickInteraction).not.toHaveBeenCalled();
|
|
42
|
+
expect(DragNodeInteraction).not.toHaveBeenCalled();
|
|
43
|
+
expect(DrawInteraction).not.toHaveBeenCalled();
|
|
44
|
+
expect(ZoomInteraction).not.toHaveBeenCalled();
|
|
45
|
+
expect(BoxSelectInteraction).not.toHaveBeenCalled();
|
|
46
|
+
expect(LassoInteraction).not.toHaveBeenCalled();
|
|
47
|
+
});
|
|
48
|
+
test('destroys NVL and active interaction handlers on unmount', () => {
|
|
49
|
+
const destroy = jest.fn();
|
|
50
|
+
jest.spyOn(NVL.prototype, 'destroy').mockImplementation(destroy);
|
|
51
|
+
const destroyHoverInteraction = jest.fn();
|
|
52
|
+
jest.spyOn(HoverInteraction.prototype, 'destroy').mockImplementation(destroyHoverInteraction);
|
|
53
|
+
const destroyPanInteraction = jest.fn();
|
|
54
|
+
jest.spyOn(PanInteraction.prototype, 'destroy').mockImplementation(destroyPanInteraction);
|
|
55
|
+
jest.spyOn(NVL.prototype, 'getContainer').mockImplementation(() => document.createElement('div'));
|
|
56
|
+
const { unmount } = render(_jsx(InteractiveNvlWrapper, { nodes: [], rels: [], mouseEventCallbacks: {
|
|
57
|
+
onHover: true
|
|
58
|
+
} }));
|
|
59
|
+
unmount();
|
|
60
|
+
expect(destroy).toHaveBeenCalledTimes(1);
|
|
61
|
+
expect(destroyHoverInteraction).toHaveBeenCalledTimes(1);
|
|
62
|
+
expect(destroyPanInteraction).not.toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
test('successfully re-initialises NVL and active interaction handlers when using React StrictMode', () => {
|
|
65
|
+
const destroy = jest.fn();
|
|
66
|
+
jest.spyOn(NVL.prototype, 'destroy').mockImplementation(destroy);
|
|
67
|
+
const destroyHoverInteraction = jest.fn();
|
|
68
|
+
jest.spyOn(HoverInteraction.prototype, 'destroy').mockImplementation(destroyHoverInteraction);
|
|
69
|
+
const destroyPanInteraction = jest.fn();
|
|
70
|
+
jest.spyOn(PanInteraction.prototype, 'destroy').mockImplementation(destroyPanInteraction);
|
|
71
|
+
jest.spyOn(NVL.prototype, 'getContainer').mockImplementation(() => document.createElement('div'));
|
|
72
|
+
const myNvlRef = createRef();
|
|
73
|
+
render(_jsx(React.StrictMode, { children: _jsx(InteractiveNvlWrapper, { nodes: [], rels: [], ref: myNvlRef, mouseEventCallbacks: {
|
|
74
|
+
onHover: true
|
|
75
|
+
}, interactionOptions: {
|
|
76
|
+
drawShadowOnHover: true
|
|
77
|
+
} }) }));
|
|
78
|
+
expect(NVL).toHaveBeenCalledTimes(2);
|
|
79
|
+
expect(HoverInteraction).toHaveBeenCalledTimes(2);
|
|
80
|
+
expect(HoverInteraction).toHaveBeenCalledWith(myNvlRef.current, { drawShadowOnHover: true });
|
|
81
|
+
expect(destroy).toHaveBeenCalledTimes(1);
|
|
82
|
+
expect(destroyHoverInteraction).toHaveBeenCalledTimes(1);
|
|
83
|
+
expect(destroyPanInteraction).not.toHaveBeenCalled();
|
|
84
|
+
expect(DragNodeInteraction).not.toHaveBeenCalled();
|
|
85
|
+
expect(DrawInteraction).not.toHaveBeenCalled();
|
|
86
|
+
expect(ZoomInteraction).not.toHaveBeenCalled();
|
|
87
|
+
expect(BoxSelectInteraction).not.toHaveBeenCalled();
|
|
88
|
+
expect(LassoInteraction).not.toHaveBeenCalled();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as exportedModules from '../index';
|
|
2
|
+
jest.mock('@neo4j-nvl/layout-workers');
|
|
3
|
+
describe('index', () => {
|
|
4
|
+
test('provides the expected components', () => {
|
|
5
|
+
const modules = Object.keys(exportedModules);
|
|
6
|
+
expect(modules).toContain('BasicNvlWrapper');
|
|
7
|
+
});
|
|
8
|
+
});
|
|
@@ -22,8 +22,6 @@ export interface BasicReactWrapperProps {
|
|
|
22
22
|
nvlOptions?: NvlOptions;
|
|
23
23
|
/** A callback to handle any errors that happen during NVL initialization */
|
|
24
24
|
onInitializationError?: (error: unknown) => void;
|
|
25
|
-
/** Any events that should be passed to the NVL instance */
|
|
26
|
-
nvlEvents?: Record<string, (event: unknown) => void>;
|
|
27
25
|
}
|
|
28
26
|
/**
|
|
29
27
|
* A basic React wrapper for the {@link NVL} class.
|
|
@@ -50,25 +48,31 @@ export interface BasicReactWrapperProps {
|
|
|
50
48
|
* When nodes and/or relationships are updated in the React wrapper, the NVL instance will be updated accordingly:
|
|
51
49
|
*
|
|
52
50
|
* ```tsx
|
|
53
|
-
* const
|
|
51
|
+
* const NvlComponent = () => {
|
|
52
|
+
* const [nodes, setNodes] = useState<Node[]>([{ id: '0' }, { id: '1' }])
|
|
54
53
|
*
|
|
55
|
-
*
|
|
56
|
-
* const newNodes = [...nodes, { id: nodes.length }]
|
|
54
|
+
* const addElements = () => {
|
|
55
|
+
* const newNodes = [...nodes, { id: nodes.length.toString() }]
|
|
57
56
|
* setNodes(newNodes)
|
|
58
57
|
* }
|
|
59
58
|
*
|
|
60
|
-
* <div>
|
|
61
|
-
* <BasicNvlWrapper nodes={nodes} />
|
|
59
|
+
* return <div>
|
|
60
|
+
* <BasicNvlWrapper nodes={nodes} rels={[]} />
|
|
62
61
|
* <button onClick={addElements}>Add Graph Elements</button>
|
|
63
62
|
* </div>
|
|
63
|
+
* }
|
|
64
64
|
* ```
|
|
65
65
|
*
|
|
66
66
|
* would be equivalent to
|
|
67
67
|
*
|
|
68
68
|
* ```tsx
|
|
69
|
+
* const container = document.createElement('div')
|
|
70
|
+
* const myButton = document.createElement('button')
|
|
71
|
+
* const nodes = [{ id: '0' }, { id: '1' }]
|
|
69
72
|
* const myNvl = new NVL(container, nodes, [])
|
|
70
73
|
* myButton.addEventListener('click', () => {
|
|
71
|
-
*
|
|
74
|
+
* const newNodes = [...nodes, { id: nodes.length.toString() }]
|
|
75
|
+
* myNvl.addAndUpdateElementsInGraph(newNodes, [])
|
|
72
76
|
* })
|
|
73
77
|
* ```
|
|
74
78
|
*
|
|
@@ -76,16 +80,18 @@ export interface BasicReactWrapperProps {
|
|
|
76
80
|
* If you want to access the NVL class outside of the React wrapper you can use a
|
|
77
81
|
* reference of NVL to call its methods:
|
|
78
82
|
* ```tsx
|
|
79
|
-
* const
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* <
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* </
|
|
83
|
+
* const NvlComponent = () => {
|
|
84
|
+
* const nvlRef = useRef<NVL>()
|
|
85
|
+
*
|
|
86
|
+
* return <div>
|
|
87
|
+
* <BasicNvlWrapper
|
|
88
|
+
* nodes={[{ id: '0' }, { id: '1' }]}
|
|
89
|
+
* rels={[{ from: '0', to: '1', id: '10' }]}
|
|
90
|
+
* ref={nvlRef}
|
|
91
|
+
* />
|
|
92
|
+
* <button onClick={() => nvlRef.current?.fit([0, 1])}>Zoom to Nodes</button>
|
|
93
|
+
* </div>
|
|
94
|
+
* }
|
|
89
95
|
* ```
|
|
90
96
|
*
|
|
91
97
|
* @example
|
|
@@ -102,7 +108,9 @@ export interface BasicReactWrapperProps {
|
|
|
102
108
|
* would be equivalent to
|
|
103
109
|
*
|
|
104
110
|
* ```tsx
|
|
105
|
-
* const
|
|
111
|
+
* const container = document.createElement('div')
|
|
112
|
+
* const nodes = [{ id: '0' }, { id: '1' }]
|
|
113
|
+
* const myNvl = new NVL(container, nodes, [])
|
|
106
114
|
* container.addEventListener('click', (event) => console.log(event))
|
|
107
115
|
* ```
|
|
108
116
|
*
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import NVL from '@neo4j-nvl/base';
|
|
3
3
|
import { forwardRef, memo, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
4
|
+
import { BASIC_WRAPPER_ID } from '../utils/constants';
|
|
4
5
|
import { getMapDifferences, getNodeAttributeDifferences } from '../utils/graph-comparison';
|
|
5
6
|
import { useDeepCompareEffect } from '../utils/hooks';
|
|
6
7
|
/**
|
|
@@ -28,25 +29,31 @@ import { useDeepCompareEffect } from '../utils/hooks';
|
|
|
28
29
|
* When nodes and/or relationships are updated in the React wrapper, the NVL instance will be updated accordingly:
|
|
29
30
|
*
|
|
30
31
|
* ```tsx
|
|
31
|
-
* const
|
|
32
|
+
* const NvlComponent = () => {
|
|
33
|
+
* const [nodes, setNodes] = useState<Node[]>([{ id: '0' }, { id: '1' }])
|
|
32
34
|
*
|
|
33
|
-
*
|
|
34
|
-
* const newNodes = [...nodes, { id: nodes.length }]
|
|
35
|
+
* const addElements = () => {
|
|
36
|
+
* const newNodes = [...nodes, { id: nodes.length.toString() }]
|
|
35
37
|
* setNodes(newNodes)
|
|
36
38
|
* }
|
|
37
39
|
*
|
|
38
|
-
* <div>
|
|
39
|
-
* <BasicNvlWrapper nodes={nodes} />
|
|
40
|
+
* return <div>
|
|
41
|
+
* <BasicNvlWrapper nodes={nodes} rels={[]} />
|
|
40
42
|
* <button onClick={addElements}>Add Graph Elements</button>
|
|
41
43
|
* </div>
|
|
44
|
+
* }
|
|
42
45
|
* ```
|
|
43
46
|
*
|
|
44
47
|
* would be equivalent to
|
|
45
48
|
*
|
|
46
49
|
* ```tsx
|
|
50
|
+
* const container = document.createElement('div')
|
|
51
|
+
* const myButton = document.createElement('button')
|
|
52
|
+
* const nodes = [{ id: '0' }, { id: '1' }]
|
|
47
53
|
* const myNvl = new NVL(container, nodes, [])
|
|
48
54
|
* myButton.addEventListener('click', () => {
|
|
49
|
-
*
|
|
55
|
+
* const newNodes = [...nodes, { id: nodes.length.toString() }]
|
|
56
|
+
* myNvl.addAndUpdateElementsInGraph(newNodes, [])
|
|
50
57
|
* })
|
|
51
58
|
* ```
|
|
52
59
|
*
|
|
@@ -54,16 +61,18 @@ import { useDeepCompareEffect } from '../utils/hooks';
|
|
|
54
61
|
* If you want to access the NVL class outside of the React wrapper you can use a
|
|
55
62
|
* reference of NVL to call its methods:
|
|
56
63
|
* ```tsx
|
|
57
|
-
* const
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* <
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* </
|
|
64
|
+
* const NvlComponent = () => {
|
|
65
|
+
* const nvlRef = useRef<NVL>()
|
|
66
|
+
*
|
|
67
|
+
* return <div>
|
|
68
|
+
* <BasicNvlWrapper
|
|
69
|
+
* nodes={[{ id: '0' }, { id: '1' }]}
|
|
70
|
+
* rels={[{ from: '0', to: '1', id: '10' }]}
|
|
71
|
+
* ref={nvlRef}
|
|
72
|
+
* />
|
|
73
|
+
* <button onClick={() => nvlRef.current?.fit([0, 1])}>Zoom to Nodes</button>
|
|
74
|
+
* </div>
|
|
75
|
+
* }
|
|
67
76
|
* ```
|
|
68
77
|
*
|
|
69
78
|
* @example
|
|
@@ -80,7 +89,9 @@ import { useDeepCompareEffect } from '../utils/hooks';
|
|
|
80
89
|
* would be equivalent to
|
|
81
90
|
*
|
|
82
91
|
* ```tsx
|
|
83
|
-
* const
|
|
92
|
+
* const container = document.createElement('div')
|
|
93
|
+
* const nodes = [{ id: '0' }, { id: '1' }]
|
|
94
|
+
* const myNvl = new NVL(container, nodes, [])
|
|
84
95
|
* container.addEventListener('click', (event) => console.log(event))
|
|
85
96
|
* ```
|
|
86
97
|
*
|
|
@@ -94,9 +105,13 @@ export const BasicNvlWrapper = memo(forwardRef(({ nodes, rels, layout, layoutOpt
|
|
|
94
105
|
const nvlMethods = Object.getOwnPropertyNames(NVL.prototype);
|
|
95
106
|
return nvlMethods.reduce((current, method) => ({
|
|
96
107
|
...current,
|
|
97
|
-
[method]: (...args) =>
|
|
98
|
-
|
|
99
|
-
|
|
108
|
+
[method]: (...args) => {
|
|
109
|
+
if (nvlRef.current === null) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
// @ts-ignore suppress the type casting error on spreading
|
|
113
|
+
return nvlRef.current[method](...args);
|
|
114
|
+
}
|
|
100
115
|
}),
|
|
101
116
|
// eslint-disable-next-line max-len
|
|
102
117
|
// eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter, @typescript-eslint/consistent-type-assertions
|
|
@@ -178,5 +193,5 @@ export const BasicNvlWrapper = memo(forwardRef(({ nodes, rels, layout, layoutOpt
|
|
|
178
193
|
}
|
|
179
194
|
nvlRef.current.setDisableWebGL(nvlOptions.disableWebGL);
|
|
180
195
|
}, [nvlOptions.disableWebGL]);
|
|
181
|
-
return _jsx("div", { ref: containerRef, style: { height: '100%', outline: '0' }, ...nvlEvents });
|
|
196
|
+
return _jsx("div", { id: BASIC_WRAPPER_ID, ref: containerRef, style: { height: '100%', outline: '0' }, ...nvlEvents });
|
|
182
197
|
}));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type NVL from '@neo4j-nvl/base';
|
|
2
|
+
import type { HTMLProps } from 'react';
|
|
2
3
|
import React from 'react';
|
|
3
4
|
import type { InteractiveNvlWrapperProps } from './types';
|
|
4
5
|
/**
|
|
@@ -13,21 +14,23 @@ import type { InteractiveNvlWrapperProps } from './types';
|
|
|
13
14
|
*
|
|
14
15
|
* @example
|
|
15
16
|
* ```tsx
|
|
16
|
-
* const
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* {
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
17
|
+
* const InteractiveNvlComponent = () => {
|
|
18
|
+
* const [boxSelect, setBoxSelect] = useState(false)
|
|
19
|
+
* return <>
|
|
20
|
+
* <button onClick={() => setBoxSelect(!boxSelect)}>
|
|
21
|
+
* {boxSelect ? 'Disable' : 'Enable'} box-select
|
|
22
|
+
* </button>
|
|
23
|
+
* <InteractiveNvlWrapper
|
|
24
|
+
* nodes={nodes}
|
|
25
|
+
* rels={relationships}
|
|
26
|
+
* mouseEventCallbacks={{
|
|
27
|
+
* onHover: (element) => console.log(element),
|
|
28
|
+
* onNodeClick: (node) => console.log(node),
|
|
29
|
+
* onBoxSelect: boxSelect
|
|
30
|
+
* }}
|
|
31
|
+
* />
|
|
32
|
+
* </>
|
|
33
|
+
* }
|
|
31
34
|
* ```
|
|
32
35
|
*/
|
|
33
|
-
export declare const InteractiveNvlWrapper: React.MemoExoticComponent<React.ForwardRefExoticComponent<InteractiveNvlWrapperProps & React.RefAttributes<NVL>>>;
|
|
36
|
+
export declare const InteractiveNvlWrapper: React.MemoExoticComponent<React.ForwardRefExoticComponent<Omit<InteractiveNvlWrapperProps & HTMLProps<HTMLDivElement>, "ref"> & React.RefAttributes<NVL>>>;
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { BoxSelectInteraction, ClickInteraction, DragNodeInteraction, DrawInteraction, HoverInteraction, LassoInteraction, PanInteraction, ZoomInteraction } from '@neo4j-nvl/interaction-handlers';
|
|
3
3
|
import { forwardRef, memo, useEffect, useRef } from 'react';
|
|
4
4
|
import { BasicNvlWrapper } from '../basic-wrapper/BasicNvlWrapper';
|
|
5
|
+
import { INTERACTIVE_WRAPPER_ID } from '../utils/constants';
|
|
5
6
|
import { destroyInteraction, useInteraction } from './hooks';
|
|
6
7
|
const options = {
|
|
7
8
|
selectOnClick: false,
|
|
@@ -21,35 +22,37 @@ const options = {
|
|
|
21
22
|
*
|
|
22
23
|
* @example
|
|
23
24
|
* ```tsx
|
|
24
|
-
* const
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* {
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
25
|
+
* const InteractiveNvlComponent = () => {
|
|
26
|
+
* const [boxSelect, setBoxSelect] = useState(false)
|
|
27
|
+
* return <>
|
|
28
|
+
* <button onClick={() => setBoxSelect(!boxSelect)}>
|
|
29
|
+
* {boxSelect ? 'Disable' : 'Enable'} box-select
|
|
30
|
+
* </button>
|
|
31
|
+
* <InteractiveNvlWrapper
|
|
32
|
+
* nodes={nodes}
|
|
33
|
+
* rels={relationships}
|
|
34
|
+
* mouseEventCallbacks={{
|
|
35
|
+
* onHover: (element) => console.log(element),
|
|
36
|
+
* onNodeClick: (node) => console.log(node),
|
|
37
|
+
* onBoxSelect: boxSelect
|
|
38
|
+
* }}
|
|
39
|
+
* />
|
|
40
|
+
* </>
|
|
41
|
+
* }
|
|
39
42
|
* ```
|
|
40
43
|
*/
|
|
41
|
-
export const InteractiveNvlWrapper = memo(forwardRef(({ nodes, rels, layout, layoutOptions, mouseEventCallbacks = {}, nvlCallbacks = {}, nvlOptions = {}, interactionOptions = options }, nvlRef) => {
|
|
44
|
+
export const InteractiveNvlWrapper = memo(forwardRef(({ nodes, rels, layout, layoutOptions, onInitializationError, mouseEventCallbacks = {}, nvlCallbacks = {}, nvlOptions = {}, interactionOptions = options, ...nvlEvents }, nvlRef) => {
|
|
42
45
|
const newNvlRef = useRef(null);
|
|
43
46
|
const myNvlRef = nvlRef ?? newNvlRef;
|
|
44
47
|
const { onHover, onNodeClick, onNodeDoubleClick, onNodeRightClick, onRelationshipClick, onRelationshipDoubleClick, onRelationshipRightClick, onCanvasClick, onCanvasRightClick, onPan, onZoom, onDrag, onDragStart, onDragEnd, onDrawEnd, onHoverNodeMargin, onBoxStarted, onBoxSelect, onLassoStarted, onLassoSelect } = mouseEventCallbacks;
|
|
45
|
-
const hoverInteraction = useRef();
|
|
46
|
-
const clickInteraction = useRef();
|
|
47
|
-
const panInteraction = useRef();
|
|
48
|
-
const zoomInteraction = useRef();
|
|
49
|
-
const dragNodeInteraction = useRef();
|
|
50
|
-
const drawInteraction = useRef();
|
|
51
|
-
const multiSelectInteraction = useRef();
|
|
52
|
-
const lassoInteraction = useRef();
|
|
48
|
+
const hoverInteraction = useRef(null);
|
|
49
|
+
const clickInteraction = useRef(null);
|
|
50
|
+
const panInteraction = useRef(null);
|
|
51
|
+
const zoomInteraction = useRef(null);
|
|
52
|
+
const dragNodeInteraction = useRef(null);
|
|
53
|
+
const drawInteraction = useRef(null);
|
|
54
|
+
const multiSelectInteraction = useRef(null);
|
|
55
|
+
const lassoInteraction = useRef(null);
|
|
53
56
|
useInteraction(HoverInteraction, hoverInteraction, onHover, 'onHover', myNvlRef, interactionOptions);
|
|
54
57
|
useInteraction(ClickInteraction, clickInteraction, onNodeClick, 'onNodeClick', myNvlRef, interactionOptions);
|
|
55
58
|
useInteraction(ClickInteraction, clickInteraction, onNodeDoubleClick, 'onNodeDoubleClick', myNvlRef, interactionOptions);
|
|
@@ -80,5 +83,5 @@ export const InteractiveNvlWrapper = memo(forwardRef(({ nodes, rels, layout, lay
|
|
|
80
83
|
destroyInteraction(multiSelectInteraction);
|
|
81
84
|
destroyInteraction(lassoInteraction);
|
|
82
85
|
}, []);
|
|
83
|
-
return (_jsx(BasicNvlWrapper, { ref: myNvlRef, nodes: nodes, rels: rels, nvlOptions: nvlOptions, nvlCallbacks: nvlCallbacks, layout: layout, layoutOptions: layoutOptions }));
|
|
86
|
+
return (_jsx(BasicNvlWrapper, { ref: myNvlRef, nodes: nodes, id: INTERACTIVE_WRAPPER_ID, rels: rels, nvlOptions: nvlOptions, nvlCallbacks: nvlCallbacks, layout: layout, layoutOptions: layoutOptions, onInitializationError: onInitializationError, ...nvlEvents }));
|
|
84
87
|
}));
|
|
@@ -2,4 +2,4 @@ import type NVL from '@neo4j-nvl/base';
|
|
|
2
2
|
import type { MutableRefObject } from 'react';
|
|
3
3
|
import type { InteractionOptions, MouseEvent, MouseInteraction, MouseInteractionModule } from './types';
|
|
4
4
|
export declare const destroyInteraction: (interactionRef: MutableRefObject<MouseInteraction | null>) => void;
|
|
5
|
-
export declare const useInteraction: (Interaction: MouseInteractionModule, interactionRef: MutableRefObject<MouseInteraction>, callback: ((...args: unknown[]) => void) | boolean | undefined, eventName: MouseEvent, nvlRef: MutableRefObject<NVL | null>, interactionOptions: InteractionOptions) => void;
|
|
5
|
+
export declare const useInteraction: (Interaction: MouseInteractionModule, interactionRef: MutableRefObject<MouseInteraction | null>, callback: ((...args: unknown[]) => void) | boolean | undefined, eventName: MouseEvent, nvlRef: MutableRefObject<NVL | null>, interactionOptions: InteractionOptions) => void;
|
|
@@ -6,6 +6,11 @@ export const destroyInteraction = (interactionRef) => {
|
|
|
6
6
|
};
|
|
7
7
|
export const useInteraction = (Interaction, interactionRef, callback, eventName, nvlRef, interactionOptions) => {
|
|
8
8
|
useEffect(() => {
|
|
9
|
+
const container = nvlRef.current?.getContainer();
|
|
10
|
+
// Make sure we are not mounting interaction handlers when there is NVL setup
|
|
11
|
+
if (isNil(container)) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
9
14
|
if ((callback === true || typeof callback === 'function') && !isNil(nvlRef.current)) {
|
|
10
15
|
if (isNil(interactionRef.current)) {
|
|
11
16
|
interactionRef.current = new Interaction(nvlRef.current, interactionOptions);
|
package/lib/utils/hooks.js
CHANGED
|
@@ -11,5 +11,6 @@ function useDeepCompareMemoize(value) {
|
|
|
11
11
|
return ref.current;
|
|
12
12
|
}
|
|
13
13
|
export const useDeepCompareEffect = (callback, dependencies) => {
|
|
14
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
14
15
|
useEffect(callback, dependencies.map(useDeepCompareMemoize));
|
|
15
16
|
};
|
package/package.json
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neo4j-nvl/react",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2-ea823910",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
|
+
"homepage": "https://neo4j.com/docs/nvl/current/",
|
|
5
6
|
"license": "SEE LICENSE IN 'LICENSE.txt'",
|
|
7
|
+
"description": "React wrappers for the Neo4j Visualization Library",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"neo4j",
|
|
10
|
+
"visualization",
|
|
11
|
+
"graph",
|
|
12
|
+
"react"
|
|
13
|
+
],
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://community.neo4j.com/c/neo4j-graph-platform/neo4j-bloom"
|
|
16
|
+
},
|
|
6
17
|
"scripts": {
|
|
7
18
|
"build": "tsc",
|
|
19
|
+
"watch": "tsc -w",
|
|
8
20
|
"test": "jest",
|
|
9
21
|
"prepack": "cp ../../LICENSE.txt ./",
|
|
10
|
-
"postpack": "rm LICENSE.txt"
|
|
22
|
+
"postpack": "rm LICENSE.txt",
|
|
23
|
+
"eslint": "eslint ./src/"
|
|
11
24
|
},
|
|
12
25
|
"files": [
|
|
13
26
|
"LICENSE.txt",
|
|
@@ -25,18 +38,21 @@
|
|
|
25
38
|
"@types/lodash": "4.14.202",
|
|
26
39
|
"@types/react": "18.2.58",
|
|
27
40
|
"babel-eslint": "^10.1.0",
|
|
41
|
+
"eslint": "8.38.0",
|
|
28
42
|
"jest": "^29.7.0",
|
|
29
43
|
"typescript": "^5.4.5"
|
|
30
44
|
},
|
|
31
45
|
"dependencies": {
|
|
32
|
-
"@neo4j-nvl/base": "
|
|
33
|
-
"@neo4j-nvl/interaction-handlers": "
|
|
46
|
+
"@neo4j-nvl/base": "0.3.2-ea823910",
|
|
47
|
+
"@neo4j-nvl/interaction-handlers": "0.3.2-ea823910",
|
|
34
48
|
"lodash": "4.17.21",
|
|
35
49
|
"react": "^18.2.0",
|
|
36
50
|
"react-dom": "^18.2.0"
|
|
37
51
|
},
|
|
38
52
|
"peerDependencies": {
|
|
53
|
+
"eslint": "*",
|
|
39
54
|
"jest": "*",
|
|
40
55
|
"typescript": "*"
|
|
41
|
-
}
|
|
56
|
+
},
|
|
57
|
+
"stableVersion": "0.3.2"
|
|
42
58
|
}
|