@neo4j-nvl/react 2.0.0-298fd8c6 → 2.0.0-3b74980d

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 (31) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +8 -0
  3. package/lib/basic-wrapper/BasicNvlWrapper.d.ts +58 -32
  4. package/lib/basic-wrapper/BasicNvlWrapper.js +129 -143
  5. package/lib/index.d.ts +5 -8
  6. package/lib/index.js +3 -3
  7. package/lib/interactive-nvl-wrapper/InteractionHandlers.d.ts +8 -8
  8. package/lib/interactive-nvl-wrapper/InteractionHandlers.js +62 -60
  9. package/lib/interactive-nvl-wrapper/InteractiveNvlWrapper.d.ts +5 -4
  10. package/lib/interactive-nvl-wrapper/InteractiveNvlWrapper.js +55 -42
  11. package/lib/interactive-nvl-wrapper/hooks.d.ts +4 -4
  12. package/lib/interactive-nvl-wrapper/hooks.js +24 -27
  13. package/lib/interactive-nvl-wrapper/types.d.ts +24 -9
  14. package/lib/interactive-nvl-wrapper/types.js +0 -1
  15. package/lib/static-picture-wrapper/StaticPictureWrapper.d.ts +21 -15
  16. package/lib/static-picture-wrapper/StaticPictureWrapper.js +77 -55
  17. package/lib/utils/constants.d.ts +1 -1
  18. package/lib/utils/constants.js +4 -2
  19. package/lib/utils/graph-comparison.d.ts +5 -5
  20. package/lib/utils/graph-comparison.js +53 -68
  21. package/lib/utils/hooks.d.ts +2 -2
  22. package/lib/utils/hooks.js +10 -11
  23. package/package.json +24 -10
  24. package/lib/__tests__/BasicNvlWrapper.test.d.ts +0 -1
  25. package/lib/__tests__/BasicNvlWrapper.test.js +0 -159
  26. package/lib/__tests__/InteractiveNvlWrapper.test.d.ts +0 -1
  27. package/lib/__tests__/InteractiveNvlWrapper.test.js +0 -396
  28. package/lib/__tests__/StaticPictureWrapper.test.d.ts +0 -1
  29. package/lib/__tests__/StaticPictureWrapper.test.js +0 -85
  30. package/lib/__tests__/index.test.d.ts +0 -1
  31. package/lib/__tests__/index.test.js +0 -8
@@ -1,6 +1,6 @@
1
- import type NVL from '@neo4j-nvl/base';
2
- import type { HTMLProps } from 'react';
3
- import type { InteractiveNvlWrapperProps } from './types';
1
+ import { InteractiveNvlWrapperProps } from "./types.js";
2
+ import NVL from "@neo4j-nvl/base";
3
+ import { HTMLProps } from "react";
4
4
  /**
5
5
  * The interactive React wrapper component contains a collection of interaction handlers by default
6
6
  * and provides a variety of common functionality and callbacks.
@@ -9,7 +9,8 @@ import type { InteractiveNvlWrapperProps } from './types';
9
9
  *
10
10
  * The mouseEventCallbacks property takes an object where various callbacks can be defined
11
11
  * and behavior can be toggled on and off.
12
+ * See {@link MouseEventCallbacks} for which key enables which handler.
12
13
  *
13
14
  * For examples, head to the {@link https://neo4j.com/docs/nvl/current/react-wrappers/#_interactive_react_wrapper Interactive React wrapper documentation page}.
14
15
  */
15
- export declare const InteractiveNvlWrapper: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Omit<InteractiveNvlWrapperProps & HTMLProps<HTMLDivElement>, "ref"> & import("react").RefAttributes<NVL>>>;
16
+ export declare const InteractiveNvlWrapper: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Omit<InteractiveNvlWrapperProps & HTMLProps<HTMLDivElement>, "ref"> & import("react").RefAttributes<NVL>>>;
@@ -1,46 +1,59 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { forwardRef, memo, useCallback, useRef, useState } from 'react';
3
- import { BasicNvlWrapper } from '../basic-wrapper/BasicNvlWrapper';
4
- import { INTERACTIVE_WRAPPER_ID } from '../utils/constants';
5
- import { InteractionHandlers } from './InteractionHandlers';
1
+ import { INTERACTIVE_WRAPPER_ID } from "../utils/constants.js";
2
+ import { BasicNvlWrapper } from "../basic-wrapper/BasicNvlWrapper.js";
3
+ import { InteractionHandlers } from "./InteractionHandlers.js";
4
+ import { forwardRef, memo, useCallback, useRef, useState } from "react";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ // src/interactive-nvl-wrapper/InteractiveNvlWrapper.tsx
6
7
  const options = {
7
- selectOnClick: false,
8
- drawShadowOnHover: true,
9
- selectOnRelease: false,
10
- excludeNodeMargin: true
8
+ selectOnClick: false,
9
+ drawShadowOnHover: true,
10
+ selectOnRelease: false,
11
+ excludeNodeMargin: true
11
12
  };
12
13
  /**
13
- * The interactive React wrapper component contains a collection of interaction handlers by default
14
- * and provides a variety of common functionality and callbacks.
15
- * It is an extension of the {@link BasicNvlWrapper} component and incorporates the
16
- * @neo4j-nvl/interaction-handlers module's decorators to provide a set of interaction events.
17
- *
18
- * The mouseEventCallbacks property takes an object where various callbacks can be defined
19
- * and behavior can be toggled on and off.
20
- *
21
- * For examples, head to the {@link https://neo4j.com/docs/nvl/current/react-wrappers/#_interactive_react_wrapper Interactive React wrapper documentation page}.
22
- */
23
- export const InteractiveNvlWrapper = memo(forwardRef(({ nodes, rels, onInitializationError, mouseEventCallbacks = {}, keyboardEventCallbacks = {}, nvlCallbacks = {}, nvlOptions = {}, interactionOptions = options, ...nvlEvents }, nvlRef) => {
24
- const newNvlRef = useRef(null);
25
- const myNvlRef = nvlRef ?? newNvlRef;
26
- const [isNvlInitialized, setIsNvlInitialized] = useState(false);
27
- const handleInitialization = useCallback(() => {
28
- setIsNvlInitialized(true);
29
- }, []);
30
- const handleInitializationError = useCallback((error) => {
31
- setIsNvlInitialized(false);
32
- if (onInitializationError) {
33
- onInitializationError(error);
34
- }
35
- }, [onInitializationError]);
36
- const setupInteractions = isNvlInitialized && myNvlRef.current !== null;
37
- return (_jsxs(_Fragment, { children: [_jsx(BasicNvlWrapper, { ref: myNvlRef, nodes: nodes, id: INTERACTIVE_WRAPPER_ID, rels: rels, nvlOptions: nvlOptions, nvlCallbacks: {
38
- ...nvlCallbacks,
39
- onInitialization: () => {
40
- if (nvlCallbacks.onInitialization !== undefined) {
41
- nvlCallbacks.onInitialization();
42
- }
43
- handleInitialization();
44
- }
45
- }, onInitializationError: handleInitializationError, ...nvlEvents }), setupInteractions && (_jsx(InteractionHandlers, { nvlRef: myNvlRef, mouseEventCallbacks: mouseEventCallbacks, keyboardEventCallbacks: keyboardEventCallbacks, interactionOptions: interactionOptions }))] }));
14
+ * The interactive React wrapper component contains a collection of interaction handlers by default
15
+ * and provides a variety of common functionality and callbacks.
16
+ * It is an extension of the {@link BasicNvlWrapper} component and incorporates the
17
+ * @neo4j-nvl/interaction-handlers module's decorators to provide a set of interaction events.
18
+ *
19
+ * The mouseEventCallbacks property takes an object where various callbacks can be defined
20
+ * and behavior can be toggled on and off.
21
+ * See {@link MouseEventCallbacks} for which key enables which handler.
22
+ *
23
+ * For examples, head to the {@link https://neo4j.com/docs/nvl/current/react-wrappers/#_interactive_react_wrapper Interactive React wrapper documentation page}.
24
+ */
25
+ const InteractiveNvlWrapper = memo(forwardRef(({ nodes, rels, onInitializationError, mouseEventCallbacks = {}, keyboardEventCallbacks = {}, nvlCallbacks = {}, nvlOptions = {}, interactionOptions = options, ...nvlEvents }, nvlRef) => {
26
+ const newNvlRef = useRef(null);
27
+ const myNvlRef = nvlRef ?? newNvlRef;
28
+ const [isNvlInitialized, setIsNvlInitialized] = useState(false);
29
+ const handleInitialization = useCallback(() => {
30
+ setIsNvlInitialized(true);
31
+ }, []);
32
+ const handleInitializationError = useCallback((error) => {
33
+ setIsNvlInitialized(false);
34
+ if (onInitializationError) onInitializationError(error);
35
+ }, [onInitializationError]);
36
+ const setupInteractions = isNvlInitialized && myNvlRef.current !== null;
37
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(BasicNvlWrapper, {
38
+ ref: myNvlRef,
39
+ nodes,
40
+ id: INTERACTIVE_WRAPPER_ID,
41
+ rels,
42
+ nvlOptions,
43
+ nvlCallbacks: {
44
+ ...nvlCallbacks,
45
+ onInitialization: () => {
46
+ if (nvlCallbacks.onInitialization !== void 0) nvlCallbacks.onInitialization();
47
+ handleInitialization();
48
+ }
49
+ },
50
+ onInitializationError: handleInitializationError,
51
+ ...nvlEvents
52
+ }), setupInteractions && /* @__PURE__ */ jsx(InteractionHandlers, {
53
+ nvlRef: myNvlRef,
54
+ mouseEventCallbacks,
55
+ keyboardEventCallbacks,
56
+ interactionOptions
57
+ })] });
46
58
  }));
59
+ export { InteractiveNvlWrapper };
@@ -1,5 +1,5 @@
1
- import type NVL from '@neo4j-nvl/base';
2
- import type { MutableRefObject } from 'react';
3
- import type { Interaction, InteractionModule, InteractionOptions, KeyboardEvent, MouseEvent } from './types';
1
+ import { Interaction, InteractionModule, InteractionOptions, KeyboardEvent, MouseEvent } from "./types.js";
2
+ import NVL from "@neo4j-nvl/base";
3
+ import { MutableRefObject } from "react";
4
4
  export declare const destroyInteraction: (interactionRef: MutableRefObject<Interaction | null>) => void;
5
- export declare const useInteraction: (Interaction: InteractionModule, interactionRef: MutableRefObject<Interaction | null>, callback: ((...args: unknown[]) => void) | boolean | undefined, eventName: MouseEvent | KeyboardEvent, nvlRef: MutableRefObject<NVL | null>, interactionOptions: InteractionOptions) => void;
5
+ export declare const useInteraction: (Interaction: InteractionModule, interactionRef: MutableRefObject<Interaction | null>, callback: ((...args: unknown[]) => void) | boolean | undefined, eventName: MouseEvent | KeyboardEvent, nvlRef: MutableRefObject<NVL | null>, interactionOptions: InteractionOptions) => void;
@@ -1,29 +1,26 @@
1
- import { isNil } from 'lodash';
2
- import { useEffect } from 'react';
3
- export const destroyInteraction = (interactionRef) => {
4
- interactionRef.current?.destroy();
5
- interactionRef.current = null;
1
+ import { useEffect } from "react";
2
+ import { isNil } from "lodash-es";
3
+ // src/interactive-nvl-wrapper/hooks.ts
4
+ const destroyInteraction = (interactionRef) => {
5
+ interactionRef.current?.destroy();
6
+ interactionRef.current = null;
6
7
  };
7
- export const useInteraction = (Interaction, interactionRef, callback, eventName, nvlRef, interactionOptions) => {
8
- useEffect(() => {
9
- const nvl = nvlRef.current;
10
- // Make sure we are not mounting interaction handlers when there is NVL setup
11
- if (isNil(nvl) || isNil(nvl.getContainer())) {
12
- return;
13
- }
14
- if (callback === true || typeof callback === 'function') {
15
- if (isNil(interactionRef.current)) {
16
- interactionRef.current = new Interaction(nvl, interactionOptions);
17
- }
18
- if (typeof callback === 'function') {
19
- interactionRef.current.updateCallback(eventName, callback);
20
- }
21
- else if (!isNil(interactionRef.current.callbackMap[eventName])) {
22
- interactionRef.current.removeCallback(eventName);
23
- }
24
- }
25
- else if (callback === false) {
26
- destroyInteraction(interactionRef);
27
- }
28
- }, [Interaction, callback, eventName, interactionOptions, interactionRef, nvlRef]);
8
+ const useInteraction = (Interaction, interactionRef, callback, eventName, nvlRef, interactionOptions) => {
9
+ useEffect(() => {
10
+ const nvl = nvlRef.current;
11
+ if (isNil(nvl) || isNil(nvl.getContainer())) return;
12
+ if (callback === true || typeof callback === "function") {
13
+ if (isNil(interactionRef.current)) interactionRef.current = new Interaction(nvl, interactionOptions);
14
+ if (typeof callback === "function") interactionRef.current.updateCallback(eventName, callback);
15
+ else if (!isNil(interactionRef.current.callbackMap[eventName])) interactionRef.current.removeCallback(eventName);
16
+ } else if (callback === false) destroyInteraction(interactionRef);
17
+ }, [
18
+ Interaction,
19
+ callback,
20
+ eventName,
21
+ interactionOptions,
22
+ interactionRef,
23
+ nvlRef
24
+ ]);
29
25
  };
26
+ export { destroyInteraction, useInteraction };
@@ -1,10 +1,17 @@
1
- import type { BoxSelectInteraction, BoxSelectInteractionCallbacks, BoxSelectInteractionOptions, ClickInteraction, ClickInteractionCallbacks, ClickInteractionOptions, DragNodeInteraction, DragNodeInteractionCallbacks, DrawInteraction, DrawInteractionCallbacks, DrawInteractionOptions, HoverInteraction, HoverInteractionCallbacks, HoverInteractionOptions, KeyboardInteraction, KeyboardInteractionCallbacks, KeyboardInteractionOptions, LassoInteraction, LassoInteractionCallbacks, LassoInteractionOptions, PanInteraction, PanInteractionCallbacks, PanInteractionOptions, ZoomInteraction, ZoomInteractionCallbacks, ZoomInteractionOptions } from '@neo4j-nvl/interaction-handlers';
2
- import type { BasicReactWrapperProps } from '../basic-wrapper/BasicNvlWrapper';
1
+ import { BasicReactWrapperProps } from "../basic-wrapper/BasicNvlWrapper.js";
2
+ import { BoxSelectInteraction, BoxSelectInteractionCallbacks, BoxSelectInteractionOptions, ClickInteraction, ClickInteractionCallbacks, ClickInteractionOptions, DragNodeInteraction, DragNodeInteractionCallbacks, DrawInteraction, DrawInteractionCallbacks, DrawInteractionOptions, HoverInteraction, HoverInteractionCallbacks, HoverInteractionOptions, KeyboardInteraction, KeyboardInteractionCallbacks, KeyboardInteractionOptions, LassoInteraction, LassoInteractionCallbacks, LassoInteractionOptions, PanInteraction, PanInteractionCallbacks, PanInteractionOptions, ZoomInteraction, ZoomInteractionCallbacks, ZoomInteractionOptions } from "@neo4j-nvl/interaction-handlers";
3
3
  export type InteractionModule = typeof HoverInteraction | typeof ClickInteraction | typeof PanInteraction | typeof ZoomInteraction | typeof DragNodeInteraction | typeof DrawInteraction | typeof BoxSelectInteraction | typeof LassoInteraction | typeof KeyboardInteraction;
4
4
  export type Interaction = HoverInteraction | ClickInteraction | PanInteraction | ZoomInteraction | DragNodeInteraction | DrawInteraction | BoxSelectInteraction | LassoInteraction | KeyboardInteraction;
5
5
  /**
6
6
  * Collection of mouse event callbacks that can be used with
7
7
  * the {@link InteractiveNvlWrapper} component.
8
+ *
9
+ * Each key enables the interaction handler that owns it: a function enables the handler and is
10
+ * called.
11
+ * `true` enables it without a callback and `false` destroys the handler (and all its other callbacks).
12
+ *
13
+ * @remarks Zoom is only enabled through `onZoomAndPan`. There is no `onZoom` key.
14
+ * Keyboard callbacks use the separate {@link KeyboardEventCallbacks} prop.
8
15
  */
9
16
  export type MouseEventCallbacks = ClickInteractionCallbacks & DragNodeInteractionCallbacks & HoverInteractionCallbacks & PanInteractionCallbacks & ZoomInteractionCallbacks & BoxSelectInteractionCallbacks & DrawInteractionCallbacks & LassoInteractionCallbacks;
10
17
  /**
@@ -22,15 +29,23 @@ export type InteractionOptions = ClickInteractionOptions & BoxSelectInteractionO
22
29
  * to turn on/off certain events for the {@link InteractiveNvlWrapper} component.
23
30
  */
24
31
  export type MouseEvent = keyof MouseEventCallbacks;
32
+ /**
33
+ * The events that can be passed to the {@link KeyboardEventCallbacks} object
34
+ * to turn on/off certain events for the {@link InteractiveNvlWrapper} component.
35
+ */
25
36
  export type KeyboardEvent = keyof KeyboardEventCallbacks;
26
37
  /**
27
38
  * The properties that can be passed to the {@link InteractiveNvlWrapper} component.
28
39
  */
29
40
  export interface InteractiveNvlWrapperProps extends BasicReactWrapperProps {
30
- /** {@link MouseEventCallbacks} containing functions for callbacks on certain actions */
31
- mouseEventCallbacks?: MouseEventCallbacks;
32
- /** {@link KeyboardEventCallbacks} containing functions for callbacks on keyboard actions */
33
- keyboardEventCallbacks?: KeyboardEventCallbacks;
34
- /** {@link InteractionOptions} for the underlying interaction handlers */
35
- interactionOptions?: InteractionOptions;
36
- }
41
+ /** {@link MouseEventCallbacks} containing functions for callbacks on certain actions */
42
+ mouseEventCallbacks?: MouseEventCallbacks;
43
+ /** {@link KeyboardEventCallbacks} containing functions for callbacks on keyboard actions */
44
+ keyboardEventCallbacks?: KeyboardEventCallbacks;
45
+ /**
46
+ * {@link InteractionOptions} for the underlying interaction handlers.
47
+ * @remarks Options are read when a handler is
48
+ * first created and changing them later does not affect existing handlers.
49
+ */
50
+ interactionOptions?: InteractionOptions;
51
+ }
@@ -1 +0,0 @@
1
- export {};
@@ -1,21 +1,21 @@
1
- import type { Node, NvlOptions, Relationship } from '@neo4j-nvl/base';
1
+ import { Node, NvlOptions, Relationship } from "@neo4j-nvl/base";
2
2
  /**
3
3
  * The properties that can be passed to the StaticPictureWrapper component.
4
4
  */
5
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 in pixels. */
13
- width?: number;
14
- /** The height of the static picture in pixels. */
15
- height?: number;
16
- /** The format of the static picture: 'png' or 'svg'.
17
- * Defaults to 'png'. */
18
- format?: 'png' | 'svg';
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 in pixels. */
13
+ width?: number;
14
+ /** The height of the static picture in pixels. */
15
+ height?: number;
16
+ /** The format of the static picture: 'png' or 'svg'.
17
+ * Defaults to 'png'. */
18
+ format?: 'png' | 'svg';
19
19
  };
20
20
  /**
21
21
  * A React component that creates a static picture of a graph using NVL.
@@ -23,6 +23,12 @@ export type StaticPictureWrapperProps = {
23
23
  * The component automatically fits all nodes in the viewport before capturing the image
24
24
  * NVL is automatically destroyed after capturing the image to free up resources.
25
25
  *
26
+ * @remarks
27
+ * When showing many pictures at once, either set `disableWebGL: true` in `nvlOptions` or mount the components in
28
+ * batches to avoid exceeding the WebGL context limit of the browser.
29
+ * Otherwise, the browser may drop the oldest contexts and warn "Too many active WebGL contexts" when reaching its limit
30
+ * and some pictures can fail to render.
31
+ *
26
32
  * @param props - The component properties
27
33
  * @returns An img element displaying the graph visualization, or null during rendering
28
34
  *
@@ -36,4 +42,4 @@ export type StaticPictureWrapperProps = {
36
42
  * />
37
43
  * ```
38
44
  */
39
- export declare const StaticPictureWrapper: ({ nodes, rels, nvlOptions, width, height, format }: StaticPictureWrapperProps) => import("react/jsx-runtime").JSX.Element | null;
45
+ export declare const StaticPictureWrapper: ({ nodes, rels, nvlOptions, width, height, format }: StaticPictureWrapperProps) => import("react/jsx-runtime").JSX.Element | null;
@@ -1,57 +1,79 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import NVL from '@neo4j-nvl/base';
3
- import { useEffect, useState } from 'react';
1
+ import NVL from "@neo4j-nvl/base";
2
+ import { useEffect, useState } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ // src/static-picture-wrapper/StaticPictureWrapper.tsx
4
5
  /**
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
- * The component automatically fits all nodes in the viewport before capturing the image
8
- * NVL is automatically destroyed after capturing the image to free up resources.
9
- *
10
- * @param props - The component properties
11
- * @returns An img element displaying the graph visualization, or null during rendering
12
- *
13
- * @example
14
- * ```tsx
15
- * <StaticPictureWrapper
16
- * nodes={nodes}
17
- * rels={relationships}
18
- * width={500}
19
- * height={500}
20
- * />
21
- * ```
22
- */
23
- export const StaticPictureWrapper = ({ nodes, rels, nvlOptions = {}, width = 500, height = 500, format = 'png' }) => {
24
- const [imgSrc, setImgSrc] = useState();
25
- useEffect(() => {
26
- const div = document.createElement('div');
27
- div.style.width = `${width}px`;
28
- div.style.height = `${height}px`;
29
- const myNvl = new NVL(div, nodes, rels, nvlOptions, {
30
- onLayoutDone: () => {
31
- myNvl.fit(myNvl.getNodes().map((n) => n.id));
32
- },
33
- onZoomTransitionDone: () => {
34
- const fetchDataUrl = async () => {
35
- try {
36
- const dataUrl = format === 'svg' ? await myNvl.getSvgDataUrl() : myNvl.getImageDataUrl();
37
- setImgSrc(dataUrl);
38
- }
39
- catch (err) {
40
- console.error('Failed to generate image data URL', err);
41
- }
42
- finally {
43
- setTimeout(() => {
44
- myNvl.destroy();
45
- });
46
- }
47
- };
48
- void fetchDataUrl();
49
- }
50
- });
51
- return () => {
52
- myNvl?.destroy();
53
- };
54
- // eslint-disable-next-line react-hooks/exhaustive-deps
55
- }, [nodes, rels, width, height, format]);
56
- return imgSrc !== undefined ? (_jsx("img", { src: imgSrc, width: width, height: height, style: { display: 'block', width, height, objectFit: 'contain' }, alt: "Graph" })) : null;
6
+ * A React component that creates a static picture of a graph using NVL.
7
+ * This component is useful for generating static visualizations of graphs without requiring user interaction.
8
+ * The component automatically fits all nodes in the viewport before capturing the image
9
+ * NVL is automatically destroyed after capturing the image to free up resources.
10
+ *
11
+ * @remarks
12
+ * When showing many pictures at once, either set `disableWebGL: true` in `nvlOptions` or mount the components in
13
+ * batches to avoid exceeding the WebGL context limit of the browser.
14
+ * Otherwise, the browser may drop the oldest contexts and warn "Too many active WebGL contexts" when reaching its limit
15
+ * and some pictures can fail to render.
16
+ *
17
+ * @param props - The component properties
18
+ * @returns An img element displaying the graph visualization, or null during rendering
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * <StaticPictureWrapper
23
+ * nodes={nodes}
24
+ * rels={relationships}
25
+ * width={500}
26
+ * height={500}
27
+ * />
28
+ * ```
29
+ */
30
+ const StaticPictureWrapper = ({ nodes, rels, nvlOptions = {}, width = 500, height = 500, format = "png" }) => {
31
+ const [imgSrc, setImgSrc] = useState();
32
+ useEffect(() => {
33
+ const div = document.createElement("div");
34
+ div.style.width = `${width}px`;
35
+ div.style.height = `${height}px`;
36
+ const myNvl = new NVL(div, nodes, rels, nvlOptions, {
37
+ onLayoutDone: () => {
38
+ myNvl.fit(myNvl.getNodes().map((n) => n.id));
39
+ },
40
+ onZoomTransitionDone: () => {
41
+ const fetchDataUrl = async () => {
42
+ try {
43
+ const dataUrl = format === "svg" ? await myNvl.getSvgDataUrl() : myNvl.getImageDataUrl();
44
+ setImgSrc(dataUrl);
45
+ } catch (err) {
46
+ console.error("Failed to generate image data URL", err);
47
+ } finally {
48
+ setTimeout(() => {
49
+ myNvl.destroy();
50
+ });
51
+ }
52
+ };
53
+ fetchDataUrl();
54
+ }
55
+ });
56
+ return () => {
57
+ myNvl?.destroy();
58
+ };
59
+ }, [
60
+ nodes,
61
+ rels,
62
+ width,
63
+ height,
64
+ format
65
+ ]);
66
+ return imgSrc !== void 0 ? /* @__PURE__ */ jsx("img", {
67
+ src: imgSrc,
68
+ width,
69
+ height,
70
+ style: {
71
+ display: "block",
72
+ width,
73
+ height,
74
+ objectFit: "contain"
75
+ },
76
+ alt: "Graph"
77
+ }) : null;
57
78
  };
79
+ export { StaticPictureWrapper };
@@ -1,2 +1,2 @@
1
1
  export declare const BASIC_WRAPPER_ID = "NVL_basic-wrapper";
2
- export declare const INTERACTIVE_WRAPPER_ID = "NVL_interactive-wrapper";
2
+ export declare const INTERACTIVE_WRAPPER_ID = "NVL_interactive-wrapper";
@@ -1,2 +1,4 @@
1
- export const BASIC_WRAPPER_ID = 'NVL_basic-wrapper';
2
- export const INTERACTIVE_WRAPPER_ID = 'NVL_interactive-wrapper';
1
+ // src/utils/constants.ts
2
+ const BASIC_WRAPPER_ID = "NVL_basic-wrapper";
3
+ const INTERACTIVE_WRAPPER_ID = "NVL_interactive-wrapper";
4
+ export { BASIC_WRAPPER_ID, INTERACTIVE_WRAPPER_ID };
@@ -1,8 +1,8 @@
1
- import type { Node, Relationship } from '@neo4j-nvl/base';
1
+ import { Node, Relationship } from "@neo4j-nvl/base";
2
2
  declare const getMapDifferences: <T extends Node | Relationship>(prevGraphElements: T[], newGraphElements: T[]) => {
3
- added: T[];
4
- removed: T[];
5
- updated: T[];
3
+ added: T[];
4
+ removed: T[];
5
+ updated: T[];
6
6
  };
7
7
  declare const getNodeAttributeDifferences: (prevNodes: Node[], newNodes: Node[]) => Node[];
8
- export { getNodeAttributeDifferences, getMapDifferences };
8
+ export { getMapDifferences, getNodeAttributeDifferences };
@@ -1,72 +1,57 @@
1
- import { isEqual, isNil, keyBy, keys, sortBy, transform } from 'lodash';
1
+ import { isEqual, isNil, keyBy, keys, sortBy, transform } from "lodash-es";
2
+ // src/utils/graph-comparison.ts
2
3
  const getMapDifferences = (prevGraphElements, newGraphElements) => {
3
- const prevMap = keyBy(prevGraphElements, 'id');
4
- const currentMap = keyBy(newGraphElements, 'id');
5
- const prevIds = sortBy(keys(prevMap));
6
- const currentIds = sortBy(keys(currentMap));
7
- const added = [];
8
- const removed = [];
9
- const updated = [];
10
- let i = 0;
11
- let j = 0;
12
- while (i < prevIds.length && j < currentIds.length) {
13
- const prevId = prevIds[i];
14
- const currId = currentIds[j];
15
- if (prevId === undefined || currId === undefined) {
16
- continue;
17
- }
18
- if (prevId === currId) {
19
- if (!isEqual(prevMap[prevId], currentMap[currId])) {
20
- updated.push(currId);
21
- }
22
- i += 1;
23
- j += 1;
24
- }
25
- else if (prevId < currId) {
26
- removed.push(prevId);
27
- i += 1;
28
- }
29
- else {
30
- added.push(currId);
31
- j += 1;
32
- }
33
- }
34
- while (i < prevIds.length) {
35
- const prevId = prevIds[i];
36
- if (prevId === undefined) {
37
- continue;
38
- }
39
- removed.push(prevId);
40
- i += 1;
41
- }
42
- while (j < currentIds.length) {
43
- const currId = currentIds[j];
44
- if (currId === undefined) {
45
- continue;
46
- }
47
- added.push(currId);
48
- j += 1;
49
- }
50
- return {
51
- added: added.map((id) => currentMap[id]).filter((n) => !isNil(n)),
52
- removed: removed.map((id) => prevMap[id]).filter((n) => !isNil(n)),
53
- updated: updated.map((id) => currentMap[id]).filter((n) => !isNil(n))
54
- };
4
+ const prevMap = keyBy(prevGraphElements, "id");
5
+ const currentMap = keyBy(newGraphElements, "id");
6
+ const prevIds = sortBy(keys(prevMap));
7
+ const currentIds = sortBy(keys(currentMap));
8
+ const added = [];
9
+ const removed = [];
10
+ const updated = [];
11
+ let i = 0;
12
+ let j = 0;
13
+ while (i < prevIds.length && j < currentIds.length) {
14
+ const prevId = prevIds[i];
15
+ const currId = currentIds[j];
16
+ if (prevId === void 0 || currId === void 0) continue;
17
+ if (prevId === currId) {
18
+ if (!isEqual(prevMap[prevId], currentMap[currId])) updated.push(currId);
19
+ i += 1;
20
+ j += 1;
21
+ } else if (prevId < currId) {
22
+ removed.push(prevId);
23
+ i += 1;
24
+ } else {
25
+ added.push(currId);
26
+ j += 1;
27
+ }
28
+ }
29
+ while (i < prevIds.length) {
30
+ const prevId = prevIds[i];
31
+ if (prevId === void 0) continue;
32
+ removed.push(prevId);
33
+ i += 1;
34
+ }
35
+ while (j < currentIds.length) {
36
+ const currId = currentIds[j];
37
+ if (currId === void 0) continue;
38
+ added.push(currId);
39
+ j += 1;
40
+ }
41
+ return {
42
+ added: added.map((id) => currentMap[id]).filter((n) => !isNil(n)),
43
+ removed: removed.map((id) => prevMap[id]).filter((n) => !isNil(n)),
44
+ updated: updated.map((id) => currentMap[id]).filter((n) => !isNil(n))
45
+ };
55
46
  };
56
47
  const getNodeAttributeDifferences = (prevNodes, newNodes) => {
57
- const prevNodeMap = keyBy(prevNodes, 'id');
58
- return newNodes
59
- .map((nodeToUpdate) => {
60
- const previousNode = prevNodeMap[nodeToUpdate.id];
61
- if (previousNode === undefined) {
62
- return null;
63
- }
64
- return transform(nodeToUpdate, (result, value, key) => {
65
- if (key === 'id' || value !== previousNode[key]) {
66
- Object.assign(result, { [key]: value });
67
- }
68
- });
69
- })
70
- .filter((n) => n !== null && Object.keys(n).length > 1);
48
+ const prevNodeMap = keyBy(prevNodes, "id");
49
+ return newNodes.map((nodeToUpdate) => {
50
+ const previousNode = prevNodeMap[nodeToUpdate.id];
51
+ if (previousNode === void 0) return null;
52
+ return transform(nodeToUpdate, (result, value, key) => {
53
+ if (key === "id" || value !== previousNode[key]) Object.assign(result, { [key]: value });
54
+ });
55
+ }).filter((n) => n !== null && Object.keys(n).length > 1);
71
56
  };
72
- export { getNodeAttributeDifferences, getMapDifferences };
57
+ export { getMapDifferences, getNodeAttributeDifferences };
@@ -1,2 +1,2 @@
1
- import type { EffectCallback } from 'react';
2
- export declare const useDeepCompareEffect: (callback: EffectCallback, dependencies: unknown[]) => void;
1
+ import { EffectCallback } from "react";
2
+ export declare const useDeepCompareEffect: (callback: EffectCallback, dependencies: unknown[]) => void;
@@ -1,16 +1,15 @@
1
- import { isEqual } from 'lodash';
2
- import { useEffect, useRef } from 'react';
1
+ import { useEffect, useRef } from "react";
2
+ import { isEqual } from "lodash-es";
3
+ // src/utils/hooks.ts
3
4
  const deepCompareEquals = (a, b) => {
4
- return isEqual(a, b);
5
+ return isEqual(a, b);
5
6
  };
6
7
  const useDeepCompareMemoize = (value) => {
7
- const ref = useRef();
8
- if (!deepCompareEquals(value, ref.current)) {
9
- ref.current = value;
10
- }
11
- return ref.current;
8
+ const ref = useRef();
9
+ if (!deepCompareEquals(value, ref.current)) ref.current = value;
10
+ return ref.current;
12
11
  };
13
- export const useDeepCompareEffect = (callback, dependencies) => {
14
- // eslint-disable-next-line react-hooks/exhaustive-deps
15
- useEffect(callback, dependencies.map(useDeepCompareMemoize));
12
+ const useDeepCompareEffect = (callback, dependencies) => {
13
+ useEffect(callback, dependencies.map(useDeepCompareMemoize));
16
14
  };
15
+ export { useDeepCompareEffect };