@neo4j-nvl/react 0.3.9-93453bb0 → 0.3.9-9837b281
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/lib/__tests__/BasicNvlWrapper.test.js +30 -0
- package/lib/__tests__/InteractiveNvlWrapper.test.js +26 -8
- package/lib/basic-wrapper/BasicNvlWrapper.d.ts +14 -8
- package/lib/basic-wrapper/BasicNvlWrapper.js +17 -7
- package/lib/index.d.ts +4 -2
- package/lib/index.js +2 -1
- package/lib/static-picture-wrapper/StaticPictureWrapper.d.ts +22 -0
- package/lib/static-picture-wrapper/StaticPictureWrapper.js +32 -0
- package/lib/utils/hooks.js +4 -4
- package/package.json +12 -8
|
@@ -36,4 +36,34 @@ describe('BasicNvlWrapper', () => {
|
|
|
36
36
|
expect(NVL).toHaveBeenCalledTimes(2);
|
|
37
37
|
expect(destroy).toHaveBeenCalledTimes(1);
|
|
38
38
|
});
|
|
39
|
+
test('calls setZoom when zoom property is provided', () => {
|
|
40
|
+
const setZoom = jest.fn();
|
|
41
|
+
jest.spyOn(NVL.prototype, 'setZoom').mockImplementation(setZoom);
|
|
42
|
+
render(_jsx("div", { children: _jsx(BasicNvlWrapper, { nodes: [], rels: [], zoom: 1.5 }) }));
|
|
43
|
+
expect(setZoom).toHaveBeenCalledWith(1.5);
|
|
44
|
+
});
|
|
45
|
+
test('calls setPan when pan property is provided', () => {
|
|
46
|
+
const setPan = jest.fn();
|
|
47
|
+
jest.spyOn(NVL.prototype, 'setPan').mockImplementation(setPan);
|
|
48
|
+
render(_jsx("div", { children: _jsx(BasicNvlWrapper, { nodes: [], rels: [], pan: { x: 10, y: 20 } }) }));
|
|
49
|
+
expect(setPan).toHaveBeenCalledWith(10, 20);
|
|
50
|
+
});
|
|
51
|
+
test('calls setZoomAndPan when both zoom and pan properties are provided', () => {
|
|
52
|
+
const setZoomAndPan = jest.fn();
|
|
53
|
+
jest.spyOn(NVL.prototype, 'setZoomAndPan').mockImplementation(setZoomAndPan);
|
|
54
|
+
render(_jsx("div", { children: _jsx(BasicNvlWrapper, { nodes: [], rels: [], zoom: 1.5, pan: { x: 10, y: 20 } }) }));
|
|
55
|
+
expect(setZoomAndPan).toHaveBeenCalledWith(1.5, 10, 20);
|
|
56
|
+
});
|
|
57
|
+
test('does not call zoom/pan methods when properties are undefined', () => {
|
|
58
|
+
const setZoom = jest.fn();
|
|
59
|
+
jest.spyOn(NVL.prototype, 'setZoom').mockImplementation(setZoom);
|
|
60
|
+
const setPan = jest.fn();
|
|
61
|
+
jest.spyOn(NVL.prototype, 'setPan').mockImplementation(setPan);
|
|
62
|
+
const setZoomAndPan = jest.fn();
|
|
63
|
+
jest.spyOn(NVL.prototype, 'setZoomAndPan').mockImplementation(setZoomAndPan);
|
|
64
|
+
render(_jsx("div", { children: _jsx(BasicNvlWrapper, { nodes: [], rels: [] }) }));
|
|
65
|
+
expect(setZoom).not.toHaveBeenCalled();
|
|
66
|
+
expect(setPan).not.toHaveBeenCalled();
|
|
67
|
+
expect(setZoomAndPan).not.toHaveBeenCalled();
|
|
68
|
+
});
|
|
39
69
|
});
|
|
@@ -2,12 +2,13 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import NVL, { HierarchicalLayoutType } from '@neo4j-nvl/base';
|
|
3
3
|
import { BoxSelectInteraction, ClickInteraction, DragNodeInteraction, DrawInteraction, HoverInteraction, LassoInteraction, PanInteraction, ZoomInteraction } from '@neo4j-nvl/interaction-handlers';
|
|
4
4
|
import '@testing-library/jest-dom';
|
|
5
|
-
import {
|
|
6
|
-
import React, { createRef } from 'react';
|
|
5
|
+
import { render } from '@testing-library/react';
|
|
6
|
+
import React, { act, createRef } from 'react';
|
|
7
7
|
import { InteractiveNvlWrapper } from '../interactive-nvl-wrapper/InteractiveNvlWrapper';
|
|
8
8
|
jest.mock('@neo4j-nvl/base');
|
|
9
9
|
jest.mock('@neo4j-nvl/layout-workers');
|
|
10
10
|
jest.mock('@neo4j-nvl/interaction-handlers');
|
|
11
|
+
const MockedNVL = NVL;
|
|
11
12
|
let mockOnInitialization;
|
|
12
13
|
HoverInteraction.prototype.callbackMap = new Map();
|
|
13
14
|
PanInteraction.prototype.callbackMap = new Map();
|
|
@@ -17,10 +18,7 @@ let destroyPanInteraction;
|
|
|
17
18
|
describe('InteractiveNvlWrapper', () => {
|
|
18
19
|
beforeEach(() => {
|
|
19
20
|
jest.spyOn(NVL.prototype, 'getContainer').mockImplementation(() => document.createElement('div'));
|
|
20
|
-
|
|
21
|
-
// @ts-expect-error
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
23
|
-
NVL.mockImplementation((_container, _nodes, _rels, _options, callbacks) => {
|
|
21
|
+
MockedNVL.mockImplementation((_container, _nodes, _rels, _options, callbacks) => {
|
|
24
22
|
mockOnInitialization = callbacks.onInitialization;
|
|
25
23
|
return {
|
|
26
24
|
getContainer: () => document.createElement('div'),
|
|
@@ -42,7 +40,7 @@ describe('InteractiveNvlWrapper', () => {
|
|
|
42
40
|
});
|
|
43
41
|
test('initialises NVL expectedly with an empty graph object and no other properties', () => {
|
|
44
42
|
render(_jsx("div", { children: _jsx(InteractiveNvlWrapper, { nodes: [], rels: [] }) }));
|
|
45
|
-
expect(NVL).toHaveBeenCalledWith(expect.any(HTMLDivElement), [], [], {}, { onInitialization:
|
|
43
|
+
expect(NVL).toHaveBeenCalledWith(expect.any(HTMLDivElement), [], [], {}, { onInitialization: mockOnInitialization });
|
|
46
44
|
});
|
|
47
45
|
test('initialises NVL expectedly with a graph object and properties', () => {
|
|
48
46
|
const mockLayoutDoneFunction = jest.fn();
|
|
@@ -52,7 +50,7 @@ describe('InteractiveNvlWrapper', () => {
|
|
|
52
50
|
});
|
|
53
51
|
expect(NVL).toHaveBeenCalledWith(expect.any(HTMLDivElement), [{ id: '1' }, { id: '2' }], [{ id: '12', from: '1', to: '2' }], { renderer: 'canvas', layout: HierarchicalLayoutType, layoutOptions: { enableCytoscape: true } }, {
|
|
54
52
|
onLayoutDone: mockLayoutDoneFunction,
|
|
55
|
-
onInitialization:
|
|
53
|
+
onInitialization: mockOnInitialization
|
|
56
54
|
});
|
|
57
55
|
});
|
|
58
56
|
test('initialises expected interaction handlers with correct options', () => {
|
|
@@ -132,4 +130,24 @@ describe('InteractiveNvlWrapper', () => {
|
|
|
132
130
|
});
|
|
133
131
|
expect(customOnInitializationCallback).toHaveBeenCalledTimes(1);
|
|
134
132
|
});
|
|
133
|
+
test('passes zoom and pan properties to BasicNvlWrapper', () => {
|
|
134
|
+
const setZoom = jest.fn();
|
|
135
|
+
const setPan = jest.fn();
|
|
136
|
+
const setZoomAndPan = jest.fn();
|
|
137
|
+
MockedNVL.mockImplementation(() => ({
|
|
138
|
+
getContainer: () => document.createElement('div'),
|
|
139
|
+
destroy: jest.fn(),
|
|
140
|
+
setLayout: jest.fn(),
|
|
141
|
+
setLayoutOptions: jest.fn(),
|
|
142
|
+
setNodePositions: jest.fn(),
|
|
143
|
+
setZoom,
|
|
144
|
+
setPan,
|
|
145
|
+
setZoomAndPan
|
|
146
|
+
}));
|
|
147
|
+
render(_jsx(InteractiveNvlWrapper, { nodes: [], rels: [], zoom: 1.5, pan: { x: 10, y: 20 } }));
|
|
148
|
+
act(() => {
|
|
149
|
+
mockOnInitialization?.();
|
|
150
|
+
});
|
|
151
|
+
expect(setZoomAndPan).toHaveBeenCalledWith(1.5, 10, 20);
|
|
152
|
+
});
|
|
135
153
|
});
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import type { ExternalCallbacks, Layout, LayoutOptions, Node, NvlOptions, Relationship } from '@neo4j-nvl/base';
|
|
2
2
|
import NVL from '@neo4j-nvl/base';
|
|
3
3
|
import { type HTMLProps } from 'react';
|
|
4
|
-
/**
|
|
5
|
-
* @hidden
|
|
6
|
-
*/
|
|
7
|
-
type IncludeMethods<T> = Pick<T, {
|
|
8
|
-
[K in keyof T]: T[K] extends (_: unknown) => unknown ? K : never;
|
|
9
|
-
}[keyof T]>;
|
|
10
4
|
/**
|
|
11
5
|
* The properties that can be passed to the {@link BasicNvlWrapper} component.
|
|
12
6
|
*/
|
|
@@ -25,6 +19,19 @@ export interface BasicReactWrapperProps {
|
|
|
25
19
|
nvlOptions?: NvlOptions;
|
|
26
20
|
/** Sets the positions of the nodes in the graph using the {@link NVL.setNodePositions} method. */
|
|
27
21
|
positions?: Node[];
|
|
22
|
+
/**
|
|
23
|
+
* Sets the zoom level of the viewport using the {@link NVL.setZoom} method.
|
|
24
|
+
* @note If both zoom and pan are provided, the {@link NVL.setZoomAndPan} method will be used.
|
|
25
|
+
*/
|
|
26
|
+
zoom?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Sets the pan coordinates of the viewport using the {@link NVL.setPan} method.
|
|
29
|
+
* @note If both zoom and pan are provided, the {@link NVL.setZoomAndPan} method will be used.
|
|
30
|
+
*/
|
|
31
|
+
pan?: {
|
|
32
|
+
x: number;
|
|
33
|
+
y: number;
|
|
34
|
+
};
|
|
28
35
|
/** A callback to handle any errors that happen during NVL initialization */
|
|
29
36
|
onInitializationError?: (error: unknown) => void;
|
|
30
37
|
}
|
|
@@ -36,5 +43,4 @@ export interface BasicReactWrapperProps {
|
|
|
36
43
|
*
|
|
37
44
|
* For examples, head to the {@link https://neo4j.com/docs/nvl/current/react-wrappers/#_basic_react_wrapper Basic React wrapper documentation page}.
|
|
38
45
|
*/
|
|
39
|
-
export declare const BasicNvlWrapper: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Omit<BasicReactWrapperProps & HTMLProps<HTMLDivElement>, "ref"> & import("react").RefAttributes<
|
|
40
|
-
export {};
|
|
46
|
+
export declare const BasicNvlWrapper: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Omit<BasicReactWrapperProps & HTMLProps<HTMLDivElement>, "ref"> & import("react").RefAttributes<Partial<Pick<NVL, "performance" | "restart" | "destroy" | "addAndUpdateElementsInGraph" | "getSelectedNodes" | "getSelectedRelationships" | "removeNodesWithIds" | "removeRelationshipsWithIds" | "getNodes" | "getRelationships" | "getNodeById" | "getRelationshipById" | "getPositionById" | "getCurrentOptions" | "deselectAll" | "fit" | "resetZoom" | "setUseWebGLRenderer" | "setRenderer" | "setDisableWebGL" | "pinNode" | "unPinNode" | "setLayout" | "setLayoutOptions" | "getNodesOnScreen" | "getNodePositions" | "setNodePositions" | "isLayoutMoving" | "saveToFile" | "getImageDataUrl" | "saveFullGraphToLargeFile" | "setZoom" | "getScale" | "getPan" | "getHits" | "getContainer">>>>>;
|
|
@@ -12,10 +12,9 @@ import { useDeepCompareEffect } from '../utils/hooks';
|
|
|
12
12
|
*
|
|
13
13
|
* For examples, head to the {@link https://neo4j.com/docs/nvl/current/react-wrappers/#_basic_react_wrapper Basic React wrapper documentation page}.
|
|
14
14
|
*/
|
|
15
|
-
export const BasicNvlWrapper = memo(forwardRef(({ nodes, rels, layout, layoutOptions, nvlCallbacks = {}, nvlOptions = {}, positions = [], onInitializationError, ...nvlEvents }, ref) => {
|
|
15
|
+
export const BasicNvlWrapper = memo(forwardRef(({ nodes, rels, layout, layoutOptions, nvlCallbacks = {}, nvlOptions = {}, positions = [], zoom, pan, onInitializationError, ...nvlEvents }, ref) => {
|
|
16
16
|
const nvlRef = useRef(null);
|
|
17
17
|
useImperativeHandle(ref, () => {
|
|
18
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
19
18
|
const nvlMethods = Object.getOwnPropertyNames(NVL.prototype);
|
|
20
19
|
return nvlMethods.reduce((current, method) => ({
|
|
21
20
|
...current,
|
|
@@ -23,13 +22,10 @@ export const BasicNvlWrapper = memo(forwardRef(({ nodes, rels, layout, layoutOpt
|
|
|
23
22
|
if (nvlRef.current === null) {
|
|
24
23
|
return null;
|
|
25
24
|
}
|
|
26
|
-
// @
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
27
26
|
return nvlRef.current[method](...args);
|
|
28
27
|
}
|
|
29
|
-
}),
|
|
30
|
-
// eslint-disable-next-line max-len
|
|
31
|
-
// eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter, @typescript-eslint/consistent-type-assertions
|
|
32
|
-
{});
|
|
28
|
+
}), {});
|
|
33
29
|
});
|
|
34
30
|
const containerRef = useRef(null);
|
|
35
31
|
const [currentNodes, setCurrentNodes] = useState(nodes);
|
|
@@ -128,5 +124,19 @@ export const BasicNvlWrapper = memo(forwardRef(({ nodes, rels, layout, layoutOpt
|
|
|
128
124
|
}
|
|
129
125
|
nvlRef.current.setNodePositions(positions);
|
|
130
126
|
}, [positions]);
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (nvlRef.current === null) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (zoom !== undefined && pan !== undefined) {
|
|
132
|
+
nvlRef.current.setZoomAndPan(zoom, pan.x, pan.y);
|
|
133
|
+
}
|
|
134
|
+
else if (zoom !== undefined) {
|
|
135
|
+
nvlRef.current.setZoom(zoom);
|
|
136
|
+
}
|
|
137
|
+
else if (pan !== undefined) {
|
|
138
|
+
nvlRef.current.setPan(pan.x, pan.y);
|
|
139
|
+
}
|
|
140
|
+
}, [zoom, pan]);
|
|
131
141
|
return _jsx("div", { id: BASIC_WRAPPER_ID, ref: containerRef, style: { height: '100%', outline: '0' }, ...nvlEvents });
|
|
132
142
|
}));
|
package/lib/index.d.ts
CHANGED
|
@@ -2,5 +2,7 @@ import { BasicNvlWrapper } from './basic-wrapper/BasicNvlWrapper';
|
|
|
2
2
|
import type { BasicReactWrapperProps } from './basic-wrapper/BasicNvlWrapper';
|
|
3
3
|
import { InteractiveNvlWrapper } from './interactive-nvl-wrapper/InteractiveNvlWrapper';
|
|
4
4
|
import type { InteractionOptions, InteractiveNvlWrapperProps, MouseEvent, MouseEventCallbacks } from './interactive-nvl-wrapper/types';
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import type { StaticPictureWrapperProps } from './static-picture-wrapper/StaticPictureWrapper';
|
|
6
|
+
import { StaticPictureWrapper } from './static-picture-wrapper/StaticPictureWrapper';
|
|
7
|
+
export { BasicNvlWrapper, InteractiveNvlWrapper, StaticPictureWrapper };
|
|
8
|
+
export type { MouseEventCallbacks, MouseEvent, BasicReactWrapperProps, InteractionOptions, InteractiveNvlWrapperProps, StaticPictureWrapperProps };
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { BasicNvlWrapper } from './basic-wrapper/BasicNvlWrapper';
|
|
2
2
|
import { InteractiveNvlWrapper } from './interactive-nvl-wrapper/InteractiveNvlWrapper';
|
|
3
|
-
|
|
3
|
+
import { StaticPictureWrapper } from './static-picture-wrapper/StaticPictureWrapper';
|
|
4
|
+
export { BasicNvlWrapper, InteractiveNvlWrapper, StaticPictureWrapper };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Node, NvlOptions, Relationship } from '@neo4j-nvl/base';
|
|
2
|
+
/**
|
|
3
|
+
* The properties that can be passed to the StaticPictureWrapper component.
|
|
4
|
+
*/
|
|
5
|
+
export type StaticPictureWrapperProps = {
|
|
6
|
+
/** The nodes to be displayed in the graph. */
|
|
7
|
+
nodes: Node[];
|
|
8
|
+
/** The relationships to be displayed in the graph. */
|
|
9
|
+
rels: Relationship[];
|
|
10
|
+
/** Options to customize the NVL instance. */
|
|
11
|
+
nvlOptions?: NvlOptions;
|
|
12
|
+
/** The width of the static picture. */
|
|
13
|
+
width?: number;
|
|
14
|
+
/** The height of the static picture. */
|
|
15
|
+
height?: number;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* A React component that creates a static picture of a graph using NVL.
|
|
19
|
+
* This component is useful for generating static visualizations of graphs without requiring user interaction.
|
|
20
|
+
* NVL is destroyed after capturing the image to free up resources.
|
|
21
|
+
*/
|
|
22
|
+
export declare const StaticPictureWrapper: ({ nodes, rels, nvlOptions, width, height }: StaticPictureWrapperProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import NVL from '@neo4j-nvl/base';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
/**
|
|
5
|
+
* A React component that creates a static picture of a graph using NVL.
|
|
6
|
+
* This component is useful for generating static visualizations of graphs without requiring user interaction.
|
|
7
|
+
* NVL is destroyed after capturing the image to free up resources.
|
|
8
|
+
*/
|
|
9
|
+
export const StaticPictureWrapper = ({ nodes, rels, nvlOptions = {}, width = 500, height = 500 }) => {
|
|
10
|
+
const [imgSrc, setImgSrc] = useState();
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const div = document.createElement('div');
|
|
13
|
+
div.style.width = `${width / window.devicePixelRatio}px`;
|
|
14
|
+
div.style.height = `${height / window.devicePixelRatio}px`;
|
|
15
|
+
const myNvl = new NVL(div, nodes, rels, nvlOptions, {
|
|
16
|
+
onLayoutDone: () => {
|
|
17
|
+
myNvl.fit(myNvl.getNodes().map((n) => n.id));
|
|
18
|
+
},
|
|
19
|
+
onZoomTransitionDone: () => {
|
|
20
|
+
setImgSrc(myNvl.getImageDataUrl());
|
|
21
|
+
setTimeout(() => {
|
|
22
|
+
myNvl.destroy();
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return () => {
|
|
27
|
+
myNvl?.destroy();
|
|
28
|
+
};
|
|
29
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
30
|
+
}, [nodes, rels, width, height]);
|
|
31
|
+
return imgSrc !== undefined ? _jsx("img", { src: imgSrc, width: width, height: height, alt: "Graph" }) : null;
|
|
32
|
+
};
|
package/lib/utils/hooks.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { isEqual } from 'lodash';
|
|
2
2
|
import { useEffect, useRef } from 'react';
|
|
3
|
-
|
|
3
|
+
const deepCompareEquals = (a, b) => {
|
|
4
4
|
return isEqual(a, b);
|
|
5
|
-
}
|
|
6
|
-
|
|
5
|
+
};
|
|
6
|
+
const useDeepCompareMemoize = (value) => {
|
|
7
7
|
const ref = useRef();
|
|
8
8
|
if (!deepCompareEquals(value, ref.current)) {
|
|
9
9
|
ref.current = value;
|
|
10
10
|
}
|
|
11
11
|
return ref.current;
|
|
12
|
-
}
|
|
12
|
+
};
|
|
13
13
|
export const useDeepCompareEffect = (callback, dependencies) => {
|
|
14
14
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
15
15
|
useEffect(callback, dependencies.map(useDeepCompareMemoize));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neo4j-nvl/react",
|
|
3
|
-
"version": "0.3.9-
|
|
3
|
+
"version": "0.3.9-9837b281",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"homepage": "https://neo4j.com/docs/nvl/current/",
|
|
6
6
|
"license": "SEE LICENSE IN 'LICENSE.txt'",
|
|
@@ -27,18 +27,22 @@
|
|
|
27
27
|
"lib"
|
|
28
28
|
],
|
|
29
29
|
"devDependencies": {
|
|
30
|
+
"@testing-library/dom": "^10.4.0",
|
|
30
31
|
"@testing-library/jest-dom": "^5.16.5",
|
|
31
|
-
"@testing-library/react": "^
|
|
32
|
+
"@testing-library/react": "^16.3.0",
|
|
32
33
|
"@types/lodash": "4.14.202",
|
|
33
34
|
"@types/react": "^18.2.58",
|
|
34
|
-
"
|
|
35
|
+
"react": "^19.1.1",
|
|
36
|
+
"react-dom": "^19.1.1"
|
|
35
37
|
},
|
|
36
38
|
"dependencies": {
|
|
37
|
-
"@neo4j-nvl/base": "0.3.9-
|
|
38
|
-
"@neo4j-nvl/interaction-handlers": "0.3.9-
|
|
39
|
-
"lodash": "4.17.21"
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
"@neo4j-nvl/base": "0.3.9-9837b281",
|
|
40
|
+
"@neo4j-nvl/interaction-handlers": "0.3.9-9837b281",
|
|
41
|
+
"lodash": "4.17.21"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
45
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
42
46
|
},
|
|
43
47
|
"stableVersion": "0.3.9"
|
|
44
48
|
}
|