@3plate/graph-react 0.1.17 → 0.1.19

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/dist/index.cjs CHANGED
@@ -40,9 +40,10 @@ var import_react = __toESM(require("react"), 1);
40
40
  var import_client = require("react-dom/client");
41
41
  var import_react_dom = require("react-dom");
42
42
  var import_graph_core = require("@3plate/graph-core");
43
- function buildCoreOptions(options, pendingMounts) {
43
+ function buildCoreOptions(options, roots) {
44
44
  const userRenderNode = options?.canvas?.renderNode;
45
45
  if (!userRenderNode) return options;
46
+ const pending = /* @__PURE__ */ new Map();
46
47
  return {
47
48
  ...options,
48
49
  canvas: {
@@ -51,16 +52,20 @@ function buildCoreOptions(options, pendingMounts) {
51
52
  const result = userRenderNode(node, nodeProps);
52
53
  if (result instanceof HTMLElement) return result;
53
54
  const el = document.createElement("div");
54
- pendingMounts.set(el, result);
55
+ pending.set(el, result);
55
56
  return el;
56
57
  },
57
- mountNode: (node, el) => {
58
- const reactNode = pendingMounts.get(el);
59
- if (reactNode === void 0) return;
60
- pendingMounts.delete(el);
58
+ mountNode: (_node, el) => {
59
+ const content = pending.get(el);
60
+ if (content === void 0) return;
61
+ pending.delete(el);
61
62
  const root = (0, import_client.createRoot)(el);
62
- (0, import_react_dom.flushSync)(() => root.render(reactNode));
63
- return () => root.unmount();
63
+ (0, import_react_dom.flushSync)(() => root.render(content));
64
+ roots.set(el, root);
65
+ return () => {
66
+ roots.delete(el);
67
+ setTimeout(() => root.unmount(), 0);
68
+ };
64
69
  }
65
70
  }
66
71
  };
@@ -69,31 +74,35 @@ function Graph(props) {
69
74
  const rootRef = (0, import_react.useRef)(null);
70
75
  const apiRef = (0, import_react.useRef)(null);
71
76
  const rootIdRef = (0, import_react.useRef)(`graph-${Math.random().toString(36).slice(2, 11)}`);
72
- const pendingMounts = (0, import_react.useRef)(/* @__PURE__ */ new Map());
77
+ const reactRoots = (0, import_react.useRef)(/* @__PURE__ */ new Map());
73
78
  (0, import_react.useEffect)(() => {
74
79
  if (!rootRef.current || apiRef.current) return;
75
80
  rootRef.current.id = rootIdRef.current;
76
- (0, import_graph_core.graph)({
77
- root: rootIdRef.current,
78
- nodes: props.nodes,
79
- edges: props.edges,
80
- history: props.history,
81
- ingestion: props.ingestion,
82
- options: buildCoreOptions(props.options, pendingMounts.current),
83
- events: props.events
84
- }).then((api) => {
85
- apiRef.current = api;
86
- });
81
+ const timer = setTimeout(() => {
82
+ (0, import_graph_core.graph)({
83
+ root: rootIdRef.current,
84
+ nodes: props.nodes,
85
+ edges: props.edges,
86
+ history: props.history,
87
+ ingestion: props.ingestion,
88
+ options: buildCoreOptions(props.options, reactRoots.current),
89
+ events: props.events
90
+ }).then((api) => {
91
+ apiRef.current = api;
92
+ });
93
+ }, 0);
87
94
  return () => {
95
+ clearTimeout(timer);
96
+ for (const root of reactRoots.current.values()) {
97
+ setTimeout(() => root.unmount(), 0);
98
+ }
99
+ reactRoots.current.clear();
88
100
  if (apiRef.current) {
89
101
  apiRef.current.destroy();
90
102
  apiRef.current = null;
91
103
  }
92
104
  if (rootRef.current) {
93
- const canvas = rootRef.current.querySelector("canvas, svg");
94
- if (canvas) {
95
- canvas.remove();
96
- }
105
+ rootRef.current.innerHTML = "";
97
106
  }
98
107
  };
99
108
  }, []);
@@ -101,7 +110,7 @@ function Graph(props) {
101
110
  if (!apiRef.current) return;
102
111
  apiRef.current.applyProps({
103
112
  ...props,
104
- options: buildCoreOptions(props.options, pendingMounts.current)
113
+ options: buildCoreOptions(props.options, reactRoots.current)
105
114
  });
106
115
  }, [props.nodes, props.edges, props.history, props.options]);
107
116
  return /* @__PURE__ */ import_react.default.createElement("div", { ref: rootRef, style: { width: "100%", height: "100%" } });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/Graph.tsx","../src/Playground.tsx"],"sourcesContent":["/**\n * @3plate/graph-react - React components for @3plate/graph graph visualization\n */\n\nexport { Graph } from './Graph'\nexport { Playground } from './Playground'\n\nexport type { GraphProps, APIOptions } from './Graph'\nexport type { PlaygroundProps } from './Playground'\n\n// Re-export types from core for convenience\nexport type {\n // API types\n API,\n APIArguments,\n Update,\n IngestionConfig,\n EventsOptions,\n // Callback parameter types\n NewNode,\n NewEdge,\n NodeProps,\n EdgeProps,\n PortProps,\n RenderNode,\n // Ingestion types\n IngestMessage,\n SnapshotMessage,\n UpdateMessage,\n HistoryMessage,\n // WebSocket types\n WebSocketStatus,\n WebSocketStatusListener,\n // Theming types\n ColorMode,\n ThemeVars,\n CanvasTheme,\n NodeTheme,\n PortTheme,\n EdgeTheme,\n // Common types\n Orientation,\n NodeAlign,\n PortStyle,\n} from '@3plate/graph-core'\n","import React, { useEffect, useRef, type ReactNode } from 'react'\nimport { createRoot } from 'react-dom/client'\nimport { flushSync } from 'react-dom'\nimport { graph, type API } from '@3plate/graph-core'\nimport type {\n APIArguments,\n APIOptions as APIOptions_,\n CanvasOptions,\n NodeProps,\n Update,\n IngestionConfig,\n} from '@3plate/graph-core'\n\n/** Extends the core renderNode signature to also accept React nodes */\ntype ReactRenderNode<N> = (node: N, props?: NodeProps<N>) => ReactNode | HTMLElement\n\n/** CanvasOptions with an extended renderNode that accepts React nodes */\ntype ReactCanvasOptions<N> = Omit<CanvasOptions<N>, 'renderNode'> & {\n renderNode?: ReactRenderNode<N>\n}\n\n/** APIOptions with the React-extended canvas options */\nexport type APIOptions<N, E> = Omit<APIOptions_<N, E>, 'canvas'> & {\n canvas?: ReactCanvasOptions<N>\n}\n\nexport type GraphProps<N, E> = {\n /** Initial nodes */\n nodes?: N[]\n /** Initial edges */\n edges?: E[]\n /** Initial history */\n history?: Update<N, E>[]\n /** Ingestion source configuration (alternative to nodes/edges/history) */\n ingestion?: IngestionConfig\n /** Options */\n options?: APIOptions<N, E>\n /** Events */\n events?: APIArguments<N, E>['events']\n}\n\n/**\n * Converts APIOptions into core APIOptions.\n * When renderNode returns a ReactNode, a placeholder element is produced and\n * mountNode renders the React content into it synchronously via flushSync.\n */\nfunction buildCoreOptions<N, E>(\n options: APIOptions<N, E> | undefined,\n pendingMounts: Map<HTMLElement, ReactNode>,\n): APIOptions_<N, E> | undefined {\n const userRenderNode = options?.canvas?.renderNode\n if (!userRenderNode) return options as unknown as APIOptions_<N, E> | undefined\n\n return {\n ...options,\n canvas: {\n ...options?.canvas,\n renderNode: (node: N, nodeProps?: NodeProps<N>): HTMLElement => {\n const result = userRenderNode(node, nodeProps)\n if (result instanceof HTMLElement) return result\n const el = document.createElement('div')\n pendingMounts.set(el, result)\n return el\n },\n mountNode: (node: N, el: HTMLElement): (() => void) | void => {\n const reactNode = pendingMounts.get(el)\n if (reactNode === undefined) return\n pendingMounts.delete(el)\n const root = createRoot(el)\n flushSync(() => root.render(reactNode as ReactNode))\n return () => root.unmount()\n },\n },\n } as unknown as APIOptions_<N, E>\n}\n\n/**\n * Graph component - renders a graph visualization.\n * Intelligently handles prop changes by diffing nodes, edges, and history.\n *\n * The `options.canvas.renderNode` function may return either an HTMLElement\n * or a React node (JSX). When JSX is returned the wrapper handles mounting\n * and cleanup automatically.\n */\nexport function Graph<N, E>(props: GraphProps<N, E>) {\n const rootRef = useRef<HTMLDivElement>(null)\n const apiRef = useRef<API<N, E> | null>(null)\n const rootIdRef = useRef<string>(`graph-${Math.random().toString(36).slice(2, 11)}`)\n const pendingMounts = useRef(new Map<HTMLElement, ReactNode>())\n\n // Initialize API once\n useEffect(() => {\n if (!rootRef.current || apiRef.current) return\n\n rootRef.current.id = rootIdRef.current\n\n graph({\n root: rootIdRef.current,\n nodes: props.nodes,\n edges: props.edges,\n history: props.history,\n ingestion: props.ingestion,\n options: buildCoreOptions(props.options, pendingMounts.current),\n events: props.events,\n }).then(api => {\n apiRef.current = api\n })\n\n return () => {\n // Cleanup\n if (apiRef.current) {\n apiRef.current.destroy()\n apiRef.current = null\n }\n if (rootRef.current) {\n // Remove canvas from DOM\n const canvas = rootRef.current.querySelector('canvas, svg')\n if (canvas) {\n canvas.remove()\n }\n }\n }\n }, []) // Only run once on mount\n\n // Handle prop changes using the centralized applyProps method\n useEffect(() => {\n if (!apiRef.current) return\n apiRef.current.applyProps({\n ...props,\n options: buildCoreOptions(props.options, pendingMounts.current),\n })\n }, [props.nodes, props.edges, props.history, props.options])\n\n return <div ref={rootRef} style={{ width: '100%', height: '100%' }} />\n}\n","import React, { useEffect, useRef } from 'react'\nimport { Playground as PlaygroundClass, type PlaygroundOptions, type Example } from '@3plate/graph-core'\n\nexport type PlaygroundProps = {\n /** Examples to display */\n examples: PlaygroundOptions['examples']\n /** Default example key */\n defaultExample?: string\n}\n\n/**\n * Playground component - renders the interactive playground with examples\n */\nexport function Playground(props: PlaygroundProps) {\n const rootRef = useRef<HTMLDivElement>(null)\n const playgroundRef = useRef<PlaygroundClass | null>(null)\n const rootIdRef = useRef<string>(`playground-${Math.random().toString(36).slice(2, 11)}`)\n const prevExamplesRef = useRef<Record<string, Example>>({})\n\n useEffect(() => {\n if (!rootRef.current || playgroundRef.current) return\n\n rootRef.current.id = rootIdRef.current\n\n const playground = new PlaygroundClass({\n root: rootIdRef.current,\n examples: props.examples,\n defaultExample: props.defaultExample,\n })\n playgroundRef.current = playground\n prevExamplesRef.current = { ...props.examples }\n playground.init()\n\n return () => {\n // Cleanup if needed\n playgroundRef.current = null\n }\n }, []) // Only initialize once\n\n // Handle examples changes\n useEffect(() => {\n if (!playgroundRef.current) return\n\n const playground = playgroundRef.current\n const prev = prevExamplesRef.current\n const current = props.examples\n\n // Get all keys from both previous and current\n const allKeys = new Set([...Object.keys(prev), ...Object.keys(current)])\n\n for (const key of allKeys) {\n const prevExample = prev[key]\n const currentExample = current[key]\n\n if (!prevExample && currentExample) {\n // Example was added\n playground.addExample(key, currentExample)\n } else if (prevExample && !currentExample) {\n // Example was removed\n playground.removeExample(key)\n } else if (prevExample && currentExample && !shallowEqualExample(prevExample, currentExample)) {\n // Example was modified\n playground.addExample(key, currentExample)\n }\n }\n\n prevExamplesRef.current = { ...current }\n }, [props.examples])\n\n return <div ref={rootRef} style={{ width: '100%', height: '100%' }} />\n}\n\n/**\n * Shallow comparison of two Example objects\n */\nfunction shallowEqualExample(a: Example, b: Example): boolean {\n if (a === b) return true\n if (a.name !== b.name) return false\n if (a.description !== b.description) return false\n if (!shallowEqualArray(a.nodes, b.nodes)) return false\n if (!shallowEqualArray(a.edges, b.edges)) return false\n if (!shallowEqualOptions(a.options, b.options)) return false\n if (!shallowEqualSource(a.source, b.source)) return false\n return true\n}\n\n/**\n * Shallow comparison of arrays\n */\nfunction shallowEqualArray<T>(a: T[] | undefined, b: T[] | undefined): boolean {\n if (a === b) return true\n if (!a || !b) return false\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false\n }\n return true\n}\n\n/**\n * Shallow comparison of ExampleOptions\n */\nfunction shallowEqualOptions(\n a: Example['options'],\n b: Example['options']\n): boolean {\n if (a === b) return true\n if (!a || !b) return false\n // For now, do a simple reference check on options\n // Can be enhanced if needed\n return a === b\n}\n\n/**\n * Shallow comparison of ExampleSource\n */\nfunction shallowEqualSource(\n a: Example['source'],\n b: Example['source']\n): boolean {\n if (a === b) return true\n if (!a || !b) return false\n if (a.type !== b.type) return false\n if (a.type === 'file' && b.type === 'file') {\n return a.path === b.path\n }\n if (a.type === 'websocket' && b.type === 'websocket') {\n return a.url === b.url\n }\n return false\n}\n"],"mappings":"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","names":["React","import_react","import_graph_core","PlaygroundClass","React"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/Graph.tsx","../src/Playground.tsx"],"sourcesContent":["/**\n * @3plate/graph-react - React components for @3plate/graph graph visualization\n */\n\nexport { Graph } from './Graph'\nexport { Playground } from './Playground'\n\nexport type { GraphProps, APIOptions } from './Graph'\nexport type { PlaygroundProps } from './Playground'\n\n// Re-export types from core for convenience\nexport type {\n // API types\n API,\n APIArguments,\n Update,\n IngestionConfig,\n EventsOptions,\n // Callback parameter types\n NewNode,\n NewEdge,\n NodeProps,\n EdgeProps,\n PortProps,\n RenderNode,\n // Ingestion types\n IngestMessage,\n SnapshotMessage,\n UpdateMessage,\n HistoryMessage,\n // WebSocket types\n WebSocketStatus,\n WebSocketStatusListener,\n // Theming types\n ColorMode,\n ThemeVars,\n CanvasTheme,\n NodeTheme,\n PortTheme,\n EdgeTheme,\n // Common types\n Orientation,\n NodeAlign,\n PortStyle,\n} from '@3plate/graph-core'\n","import React, { useEffect, useRef, type ReactNode } from 'react'\nimport { createRoot, type Root } from 'react-dom/client'\nimport { flushSync } from 'react-dom'\nimport { graph, type API } from '@3plate/graph-core'\nimport type {\n APIArguments,\n APIOptions as APIOptions_,\n CanvasOptions,\n NodeProps,\n Update,\n IngestionConfig,\n} from '@3plate/graph-core'\n\n/** Extends the core renderNode signature to also accept React nodes */\ntype ReactRenderNode<N> = (node: N, props?: NodeProps<N>) => ReactNode | HTMLElement\n\n/** CanvasOptions with an extended renderNode that accepts React nodes */\ntype ReactCanvasOptions<N> = Omit<CanvasOptions<N>, 'renderNode'> & {\n renderNode?: ReactRenderNode<N>\n}\n\n/** APIOptions with the React-extended canvas options */\nexport type APIOptions<N, E> = Omit<APIOptions_<N, E>, 'canvas'> & {\n canvas?: ReactCanvasOptions<N>\n}\n\nexport type GraphProps<N, E> = {\n /** Initial nodes */\n nodes?: N[]\n /** Initial edges */\n edges?: E[]\n /** Initial history */\n history?: Update<N, E>[]\n /** Ingestion source configuration (alternative to nodes/edges/history) */\n ingestion?: IngestionConfig\n /** Options */\n options?: APIOptions<N, E>\n /** Events */\n events?: APIArguments<N, E>['events']\n}\n\n/**\n * Converts APIOptions into core APIOptions.\n *\n * When renderNode returns a ReactNode, a placeholder div is produced and\n * mountNode renders the content into it synchronously via flushSync + createRoot.\n *\n * flushSync is safe here because graph() is called from a setTimeout,\n * not directly inside a React lifecycle method.\n */\nfunction buildCoreOptions<N, E>(\n options: APIOptions<N, E> | undefined,\n roots: Map<HTMLElement, Root>,\n): APIOptions_<N, E> | undefined {\n const userRenderNode = options?.canvas?.renderNode\n if (!userRenderNode) return options as unknown as APIOptions_<N, E> | undefined\n\n const pending = new Map<HTMLElement, ReactNode>()\n\n return {\n ...options,\n canvas: {\n ...options?.canvas,\n renderNode: (node: N, nodeProps?: NodeProps<N>): HTMLElement => {\n const result = userRenderNode(node, nodeProps)\n if (result instanceof HTMLElement) return result\n const el = document.createElement('div')\n pending.set(el, result)\n return el\n },\n mountNode: (_node: N, el: HTMLElement): (() => void) | void => {\n const content = pending.get(el)\n if (content === undefined) return\n pending.delete(el)\n const root = createRoot(el)\n flushSync(() => root.render(content as ReactNode))\n roots.set(el, root)\n return () => {\n roots.delete(el)\n setTimeout(() => root.unmount(), 0)\n }\n },\n },\n } as unknown as APIOptions_<N, E>\n}\n\n/**\n * Graph component - renders a graph visualization.\n * Intelligently handles prop changes by diffing nodes, edges, and history.\n *\n * The `options.canvas.renderNode` function may return either an HTMLElement\n * or a React node (JSX). When JSX is returned the wrapper handles mounting\n * and cleanup automatically.\n */\nexport function Graph<N, E>(props: GraphProps<N, E>) {\n const rootRef = useRef<HTMLDivElement>(null)\n const apiRef = useRef<API<N, E> | null>(null)\n const rootIdRef = useRef<string>(`graph-${Math.random().toString(36).slice(2, 11)}`)\n const reactRoots = useRef(new Map<HTMLElement, Root>())\n\n // Initialize API once\n useEffect(() => {\n if (!rootRef.current || apiRef.current) return\n\n rootRef.current.id = rootIdRef.current\n\n // Use setTimeout to escape the React lifecycle context.\n // This allows flushSync inside mountNode to work without\n // \"flushSync was called from inside a lifecycle method\" errors.\n const timer = setTimeout(() => {\n graph({\n root: rootIdRef.current,\n nodes: props.nodes,\n edges: props.edges,\n history: props.history,\n ingestion: props.ingestion,\n options: buildCoreOptions(props.options, reactRoots.current),\n events: props.events,\n }).then(api => {\n apiRef.current = api\n })\n }, 0)\n\n return () => {\n clearTimeout(timer)\n for (const root of reactRoots.current.values()) {\n setTimeout(() => root.unmount(), 0)\n }\n reactRoots.current.clear()\n if (apiRef.current) {\n apiRef.current.destroy()\n apiRef.current = null\n }\n if (rootRef.current) {\n rootRef.current.innerHTML = ''\n }\n }\n }, []) // Only run once on mount\n\n // Handle prop changes using the centralized applyProps method\n useEffect(() => {\n if (!apiRef.current) return\n apiRef.current.applyProps({\n ...props,\n options: buildCoreOptions(props.options, reactRoots.current),\n })\n }, [props.nodes, props.edges, props.history, props.options])\n\n return <div ref={rootRef} style={{ width: '100%', height: '100%' }} />\n}\n","import React, { useEffect, useRef } from 'react'\nimport { Playground as PlaygroundClass, type PlaygroundOptions, type Example } from '@3plate/graph-core'\n\nexport type PlaygroundProps = {\n /** Examples to display */\n examples: PlaygroundOptions['examples']\n /** Default example key */\n defaultExample?: string\n}\n\n/**\n * Playground component - renders the interactive playground with examples\n */\nexport function Playground(props: PlaygroundProps) {\n const rootRef = useRef<HTMLDivElement>(null)\n const playgroundRef = useRef<PlaygroundClass | null>(null)\n const rootIdRef = useRef<string>(`playground-${Math.random().toString(36).slice(2, 11)}`)\n const prevExamplesRef = useRef<Record<string, Example>>({})\n\n useEffect(() => {\n if (!rootRef.current || playgroundRef.current) return\n\n rootRef.current.id = rootIdRef.current\n\n const playground = new PlaygroundClass({\n root: rootIdRef.current,\n examples: props.examples,\n defaultExample: props.defaultExample,\n })\n playgroundRef.current = playground\n prevExamplesRef.current = { ...props.examples }\n playground.init()\n\n return () => {\n // Cleanup if needed\n playgroundRef.current = null\n }\n }, []) // Only initialize once\n\n // Handle examples changes\n useEffect(() => {\n if (!playgroundRef.current) return\n\n const playground = playgroundRef.current\n const prev = prevExamplesRef.current\n const current = props.examples\n\n // Get all keys from both previous and current\n const allKeys = new Set([...Object.keys(prev), ...Object.keys(current)])\n\n for (const key of allKeys) {\n const prevExample = prev[key]\n const currentExample = current[key]\n\n if (!prevExample && currentExample) {\n // Example was added\n playground.addExample(key, currentExample)\n } else if (prevExample && !currentExample) {\n // Example was removed\n playground.removeExample(key)\n } else if (prevExample && currentExample && !shallowEqualExample(prevExample, currentExample)) {\n // Example was modified\n playground.addExample(key, currentExample)\n }\n }\n\n prevExamplesRef.current = { ...current }\n }, [props.examples])\n\n return <div ref={rootRef} style={{ width: '100%', height: '100%' }} />\n}\n\n/**\n * Shallow comparison of two Example objects\n */\nfunction shallowEqualExample(a: Example, b: Example): boolean {\n if (a === b) return true\n if (a.name !== b.name) return false\n if (a.description !== b.description) return false\n if (!shallowEqualArray(a.nodes, b.nodes)) return false\n if (!shallowEqualArray(a.edges, b.edges)) return false\n if (!shallowEqualOptions(a.options, b.options)) return false\n if (!shallowEqualSource(a.source, b.source)) return false\n return true\n}\n\n/**\n * Shallow comparison of arrays\n */\nfunction shallowEqualArray<T>(a: T[] | undefined, b: T[] | undefined): boolean {\n if (a === b) return true\n if (!a || !b) return false\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false\n }\n return true\n}\n\n/**\n * Shallow comparison of ExampleOptions\n */\nfunction shallowEqualOptions(\n a: Example['options'],\n b: Example['options']\n): boolean {\n if (a === b) return true\n if (!a || !b) return false\n // For now, do a simple reference check on options\n // Can be enhanced if needed\n return a === b\n}\n\n/**\n * Shallow comparison of ExampleSource\n */\nfunction shallowEqualSource(\n a: Example['source'],\n b: Example['source']\n): boolean {\n if (a === b) return true\n if (!a || !b) return false\n if (a.type !== b.type) return false\n if (a.type === 'file' && b.type === 'file') {\n return a.path === b.path\n }\n if (a.type === 'websocket' && b.type === 'websocket') {\n return a.url === b.url\n }\n return false\n}\n"],"mappings":"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","names":["React","import_react","import_graph_core","PlaygroundClass","React"]}
package/dist/index.js CHANGED
@@ -3,9 +3,10 @@ import React, { useEffect, useRef } from "react";
3
3
  import { createRoot } from "react-dom/client";
4
4
  import { flushSync } from "react-dom";
5
5
  import { graph } from "@3plate/graph-core";
6
- function buildCoreOptions(options, pendingMounts) {
6
+ function buildCoreOptions(options, roots) {
7
7
  const userRenderNode = options?.canvas?.renderNode;
8
8
  if (!userRenderNode) return options;
9
+ const pending = /* @__PURE__ */ new Map();
9
10
  return {
10
11
  ...options,
11
12
  canvas: {
@@ -14,16 +15,20 @@ function buildCoreOptions(options, pendingMounts) {
14
15
  const result = userRenderNode(node, nodeProps);
15
16
  if (result instanceof HTMLElement) return result;
16
17
  const el = document.createElement("div");
17
- pendingMounts.set(el, result);
18
+ pending.set(el, result);
18
19
  return el;
19
20
  },
20
- mountNode: (node, el) => {
21
- const reactNode = pendingMounts.get(el);
22
- if (reactNode === void 0) return;
23
- pendingMounts.delete(el);
21
+ mountNode: (_node, el) => {
22
+ const content = pending.get(el);
23
+ if (content === void 0) return;
24
+ pending.delete(el);
24
25
  const root = createRoot(el);
25
- flushSync(() => root.render(reactNode));
26
- return () => root.unmount();
26
+ flushSync(() => root.render(content));
27
+ roots.set(el, root);
28
+ return () => {
29
+ roots.delete(el);
30
+ setTimeout(() => root.unmount(), 0);
31
+ };
27
32
  }
28
33
  }
29
34
  };
@@ -32,31 +37,35 @@ function Graph(props) {
32
37
  const rootRef = useRef(null);
33
38
  const apiRef = useRef(null);
34
39
  const rootIdRef = useRef(`graph-${Math.random().toString(36).slice(2, 11)}`);
35
- const pendingMounts = useRef(/* @__PURE__ */ new Map());
40
+ const reactRoots = useRef(/* @__PURE__ */ new Map());
36
41
  useEffect(() => {
37
42
  if (!rootRef.current || apiRef.current) return;
38
43
  rootRef.current.id = rootIdRef.current;
39
- graph({
40
- root: rootIdRef.current,
41
- nodes: props.nodes,
42
- edges: props.edges,
43
- history: props.history,
44
- ingestion: props.ingestion,
45
- options: buildCoreOptions(props.options, pendingMounts.current),
46
- events: props.events
47
- }).then((api) => {
48
- apiRef.current = api;
49
- });
44
+ const timer = setTimeout(() => {
45
+ graph({
46
+ root: rootIdRef.current,
47
+ nodes: props.nodes,
48
+ edges: props.edges,
49
+ history: props.history,
50
+ ingestion: props.ingestion,
51
+ options: buildCoreOptions(props.options, reactRoots.current),
52
+ events: props.events
53
+ }).then((api) => {
54
+ apiRef.current = api;
55
+ });
56
+ }, 0);
50
57
  return () => {
58
+ clearTimeout(timer);
59
+ for (const root of reactRoots.current.values()) {
60
+ setTimeout(() => root.unmount(), 0);
61
+ }
62
+ reactRoots.current.clear();
51
63
  if (apiRef.current) {
52
64
  apiRef.current.destroy();
53
65
  apiRef.current = null;
54
66
  }
55
67
  if (rootRef.current) {
56
- const canvas = rootRef.current.querySelector("canvas, svg");
57
- if (canvas) {
58
- canvas.remove();
59
- }
68
+ rootRef.current.innerHTML = "";
60
69
  }
61
70
  };
62
71
  }, []);
@@ -64,7 +73,7 @@ function Graph(props) {
64
73
  if (!apiRef.current) return;
65
74
  apiRef.current.applyProps({
66
75
  ...props,
67
- options: buildCoreOptions(props.options, pendingMounts.current)
76
+ options: buildCoreOptions(props.options, reactRoots.current)
68
77
  });
69
78
  }, [props.nodes, props.edges, props.history, props.options]);
70
79
  return /* @__PURE__ */ React.createElement("div", { ref: rootRef, style: { width: "100%", height: "100%" } });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/Graph.tsx","../src/Playground.tsx"],"sourcesContent":["import React, { useEffect, useRef, type ReactNode } from 'react'\nimport { createRoot } from 'react-dom/client'\nimport { flushSync } from 'react-dom'\nimport { graph, type API } from '@3plate/graph-core'\nimport type {\n APIArguments,\n APIOptions as APIOptions_,\n CanvasOptions,\n NodeProps,\n Update,\n IngestionConfig,\n} from '@3plate/graph-core'\n\n/** Extends the core renderNode signature to also accept React nodes */\ntype ReactRenderNode<N> = (node: N, props?: NodeProps<N>) => ReactNode | HTMLElement\n\n/** CanvasOptions with an extended renderNode that accepts React nodes */\ntype ReactCanvasOptions<N> = Omit<CanvasOptions<N>, 'renderNode'> & {\n renderNode?: ReactRenderNode<N>\n}\n\n/** APIOptions with the React-extended canvas options */\nexport type APIOptions<N, E> = Omit<APIOptions_<N, E>, 'canvas'> & {\n canvas?: ReactCanvasOptions<N>\n}\n\nexport type GraphProps<N, E> = {\n /** Initial nodes */\n nodes?: N[]\n /** Initial edges */\n edges?: E[]\n /** Initial history */\n history?: Update<N, E>[]\n /** Ingestion source configuration (alternative to nodes/edges/history) */\n ingestion?: IngestionConfig\n /** Options */\n options?: APIOptions<N, E>\n /** Events */\n events?: APIArguments<N, E>['events']\n}\n\n/**\n * Converts APIOptions into core APIOptions.\n * When renderNode returns a ReactNode, a placeholder element is produced and\n * mountNode renders the React content into it synchronously via flushSync.\n */\nfunction buildCoreOptions<N, E>(\n options: APIOptions<N, E> | undefined,\n pendingMounts: Map<HTMLElement, ReactNode>,\n): APIOptions_<N, E> | undefined {\n const userRenderNode = options?.canvas?.renderNode\n if (!userRenderNode) return options as unknown as APIOptions_<N, E> | undefined\n\n return {\n ...options,\n canvas: {\n ...options?.canvas,\n renderNode: (node: N, nodeProps?: NodeProps<N>): HTMLElement => {\n const result = userRenderNode(node, nodeProps)\n if (result instanceof HTMLElement) return result\n const el = document.createElement('div')\n pendingMounts.set(el, result)\n return el\n },\n mountNode: (node: N, el: HTMLElement): (() => void) | void => {\n const reactNode = pendingMounts.get(el)\n if (reactNode === undefined) return\n pendingMounts.delete(el)\n const root = createRoot(el)\n flushSync(() => root.render(reactNode as ReactNode))\n return () => root.unmount()\n },\n },\n } as unknown as APIOptions_<N, E>\n}\n\n/**\n * Graph component - renders a graph visualization.\n * Intelligently handles prop changes by diffing nodes, edges, and history.\n *\n * The `options.canvas.renderNode` function may return either an HTMLElement\n * or a React node (JSX). When JSX is returned the wrapper handles mounting\n * and cleanup automatically.\n */\nexport function Graph<N, E>(props: GraphProps<N, E>) {\n const rootRef = useRef<HTMLDivElement>(null)\n const apiRef = useRef<API<N, E> | null>(null)\n const rootIdRef = useRef<string>(`graph-${Math.random().toString(36).slice(2, 11)}`)\n const pendingMounts = useRef(new Map<HTMLElement, ReactNode>())\n\n // Initialize API once\n useEffect(() => {\n if (!rootRef.current || apiRef.current) return\n\n rootRef.current.id = rootIdRef.current\n\n graph({\n root: rootIdRef.current,\n nodes: props.nodes,\n edges: props.edges,\n history: props.history,\n ingestion: props.ingestion,\n options: buildCoreOptions(props.options, pendingMounts.current),\n events: props.events,\n }).then(api => {\n apiRef.current = api\n })\n\n return () => {\n // Cleanup\n if (apiRef.current) {\n apiRef.current.destroy()\n apiRef.current = null\n }\n if (rootRef.current) {\n // Remove canvas from DOM\n const canvas = rootRef.current.querySelector('canvas, svg')\n if (canvas) {\n canvas.remove()\n }\n }\n }\n }, []) // Only run once on mount\n\n // Handle prop changes using the centralized applyProps method\n useEffect(() => {\n if (!apiRef.current) return\n apiRef.current.applyProps({\n ...props,\n options: buildCoreOptions(props.options, pendingMounts.current),\n })\n }, [props.nodes, props.edges, props.history, props.options])\n\n return <div ref={rootRef} style={{ width: '100%', height: '100%' }} />\n}\n","import React, { useEffect, useRef } from 'react'\nimport { Playground as PlaygroundClass, type PlaygroundOptions, type Example } from '@3plate/graph-core'\n\nexport type PlaygroundProps = {\n /** Examples to display */\n examples: PlaygroundOptions['examples']\n /** Default example key */\n defaultExample?: string\n}\n\n/**\n * Playground component - renders the interactive playground with examples\n */\nexport function Playground(props: PlaygroundProps) {\n const rootRef = useRef<HTMLDivElement>(null)\n const playgroundRef = useRef<PlaygroundClass | null>(null)\n const rootIdRef = useRef<string>(`playground-${Math.random().toString(36).slice(2, 11)}`)\n const prevExamplesRef = useRef<Record<string, Example>>({})\n\n useEffect(() => {\n if (!rootRef.current || playgroundRef.current) return\n\n rootRef.current.id = rootIdRef.current\n\n const playground = new PlaygroundClass({\n root: rootIdRef.current,\n examples: props.examples,\n defaultExample: props.defaultExample,\n })\n playgroundRef.current = playground\n prevExamplesRef.current = { ...props.examples }\n playground.init()\n\n return () => {\n // Cleanup if needed\n playgroundRef.current = null\n }\n }, []) // Only initialize once\n\n // Handle examples changes\n useEffect(() => {\n if (!playgroundRef.current) return\n\n const playground = playgroundRef.current\n const prev = prevExamplesRef.current\n const current = props.examples\n\n // Get all keys from both previous and current\n const allKeys = new Set([...Object.keys(prev), ...Object.keys(current)])\n\n for (const key of allKeys) {\n const prevExample = prev[key]\n const currentExample = current[key]\n\n if (!prevExample && currentExample) {\n // Example was added\n playground.addExample(key, currentExample)\n } else if (prevExample && !currentExample) {\n // Example was removed\n playground.removeExample(key)\n } else if (prevExample && currentExample && !shallowEqualExample(prevExample, currentExample)) {\n // Example was modified\n playground.addExample(key, currentExample)\n }\n }\n\n prevExamplesRef.current = { ...current }\n }, [props.examples])\n\n return <div ref={rootRef} style={{ width: '100%', height: '100%' }} />\n}\n\n/**\n * Shallow comparison of two Example objects\n */\nfunction shallowEqualExample(a: Example, b: Example): boolean {\n if (a === b) return true\n if (a.name !== b.name) return false\n if (a.description !== b.description) return false\n if (!shallowEqualArray(a.nodes, b.nodes)) return false\n if (!shallowEqualArray(a.edges, b.edges)) return false\n if (!shallowEqualOptions(a.options, b.options)) return false\n if (!shallowEqualSource(a.source, b.source)) return false\n return true\n}\n\n/**\n * Shallow comparison of arrays\n */\nfunction shallowEqualArray<T>(a: T[] | undefined, b: T[] | undefined): boolean {\n if (a === b) return true\n if (!a || !b) return false\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false\n }\n return true\n}\n\n/**\n * Shallow comparison of ExampleOptions\n */\nfunction shallowEqualOptions(\n a: Example['options'],\n b: Example['options']\n): boolean {\n if (a === b) return true\n if (!a || !b) return false\n // For now, do a simple reference check on options\n // Can be enhanced if needed\n return a === b\n}\n\n/**\n * Shallow comparison of ExampleSource\n */\nfunction shallowEqualSource(\n a: Example['source'],\n b: Example['source']\n): boolean {\n if (a === b) return true\n if (!a || !b) return false\n if (a.type !== b.type) return false\n if (a.type === 'file' && b.type === 'file') {\n return a.path === b.path\n }\n if (a.type === 'websocket' && b.type === 'websocket') {\n return a.url === b.url\n }\n return false\n}\n"],"mappings":"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","names":["React","useEffect","useRef"]}
1
+ {"version":3,"sources":["../src/Graph.tsx","../src/Playground.tsx"],"sourcesContent":["import React, { useEffect, useRef, type ReactNode } from 'react'\nimport { createRoot, type Root } from 'react-dom/client'\nimport { flushSync } from 'react-dom'\nimport { graph, type API } from '@3plate/graph-core'\nimport type {\n APIArguments,\n APIOptions as APIOptions_,\n CanvasOptions,\n NodeProps,\n Update,\n IngestionConfig,\n} from '@3plate/graph-core'\n\n/** Extends the core renderNode signature to also accept React nodes */\ntype ReactRenderNode<N> = (node: N, props?: NodeProps<N>) => ReactNode | HTMLElement\n\n/** CanvasOptions with an extended renderNode that accepts React nodes */\ntype ReactCanvasOptions<N> = Omit<CanvasOptions<N>, 'renderNode'> & {\n renderNode?: ReactRenderNode<N>\n}\n\n/** APIOptions with the React-extended canvas options */\nexport type APIOptions<N, E> = Omit<APIOptions_<N, E>, 'canvas'> & {\n canvas?: ReactCanvasOptions<N>\n}\n\nexport type GraphProps<N, E> = {\n /** Initial nodes */\n nodes?: N[]\n /** Initial edges */\n edges?: E[]\n /** Initial history */\n history?: Update<N, E>[]\n /** Ingestion source configuration (alternative to nodes/edges/history) */\n ingestion?: IngestionConfig\n /** Options */\n options?: APIOptions<N, E>\n /** Events */\n events?: APIArguments<N, E>['events']\n}\n\n/**\n * Converts APIOptions into core APIOptions.\n *\n * When renderNode returns a ReactNode, a placeholder div is produced and\n * mountNode renders the content into it synchronously via flushSync + createRoot.\n *\n * flushSync is safe here because graph() is called from a setTimeout,\n * not directly inside a React lifecycle method.\n */\nfunction buildCoreOptions<N, E>(\n options: APIOptions<N, E> | undefined,\n roots: Map<HTMLElement, Root>,\n): APIOptions_<N, E> | undefined {\n const userRenderNode = options?.canvas?.renderNode\n if (!userRenderNode) return options as unknown as APIOptions_<N, E> | undefined\n\n const pending = new Map<HTMLElement, ReactNode>()\n\n return {\n ...options,\n canvas: {\n ...options?.canvas,\n renderNode: (node: N, nodeProps?: NodeProps<N>): HTMLElement => {\n const result = userRenderNode(node, nodeProps)\n if (result instanceof HTMLElement) return result\n const el = document.createElement('div')\n pending.set(el, result)\n return el\n },\n mountNode: (_node: N, el: HTMLElement): (() => void) | void => {\n const content = pending.get(el)\n if (content === undefined) return\n pending.delete(el)\n const root = createRoot(el)\n flushSync(() => root.render(content as ReactNode))\n roots.set(el, root)\n return () => {\n roots.delete(el)\n setTimeout(() => root.unmount(), 0)\n }\n },\n },\n } as unknown as APIOptions_<N, E>\n}\n\n/**\n * Graph component - renders a graph visualization.\n * Intelligently handles prop changes by diffing nodes, edges, and history.\n *\n * The `options.canvas.renderNode` function may return either an HTMLElement\n * or a React node (JSX). When JSX is returned the wrapper handles mounting\n * and cleanup automatically.\n */\nexport function Graph<N, E>(props: GraphProps<N, E>) {\n const rootRef = useRef<HTMLDivElement>(null)\n const apiRef = useRef<API<N, E> | null>(null)\n const rootIdRef = useRef<string>(`graph-${Math.random().toString(36).slice(2, 11)}`)\n const reactRoots = useRef(new Map<HTMLElement, Root>())\n\n // Initialize API once\n useEffect(() => {\n if (!rootRef.current || apiRef.current) return\n\n rootRef.current.id = rootIdRef.current\n\n // Use setTimeout to escape the React lifecycle context.\n // This allows flushSync inside mountNode to work without\n // \"flushSync was called from inside a lifecycle method\" errors.\n const timer = setTimeout(() => {\n graph({\n root: rootIdRef.current,\n nodes: props.nodes,\n edges: props.edges,\n history: props.history,\n ingestion: props.ingestion,\n options: buildCoreOptions(props.options, reactRoots.current),\n events: props.events,\n }).then(api => {\n apiRef.current = api\n })\n }, 0)\n\n return () => {\n clearTimeout(timer)\n for (const root of reactRoots.current.values()) {\n setTimeout(() => root.unmount(), 0)\n }\n reactRoots.current.clear()\n if (apiRef.current) {\n apiRef.current.destroy()\n apiRef.current = null\n }\n if (rootRef.current) {\n rootRef.current.innerHTML = ''\n }\n }\n }, []) // Only run once on mount\n\n // Handle prop changes using the centralized applyProps method\n useEffect(() => {\n if (!apiRef.current) return\n apiRef.current.applyProps({\n ...props,\n options: buildCoreOptions(props.options, reactRoots.current),\n })\n }, [props.nodes, props.edges, props.history, props.options])\n\n return <div ref={rootRef} style={{ width: '100%', height: '100%' }} />\n}\n","import React, { useEffect, useRef } from 'react'\nimport { Playground as PlaygroundClass, type PlaygroundOptions, type Example } from '@3plate/graph-core'\n\nexport type PlaygroundProps = {\n /** Examples to display */\n examples: PlaygroundOptions['examples']\n /** Default example key */\n defaultExample?: string\n}\n\n/**\n * Playground component - renders the interactive playground with examples\n */\nexport function Playground(props: PlaygroundProps) {\n const rootRef = useRef<HTMLDivElement>(null)\n const playgroundRef = useRef<PlaygroundClass | null>(null)\n const rootIdRef = useRef<string>(`playground-${Math.random().toString(36).slice(2, 11)}`)\n const prevExamplesRef = useRef<Record<string, Example>>({})\n\n useEffect(() => {\n if (!rootRef.current || playgroundRef.current) return\n\n rootRef.current.id = rootIdRef.current\n\n const playground = new PlaygroundClass({\n root: rootIdRef.current,\n examples: props.examples,\n defaultExample: props.defaultExample,\n })\n playgroundRef.current = playground\n prevExamplesRef.current = { ...props.examples }\n playground.init()\n\n return () => {\n // Cleanup if needed\n playgroundRef.current = null\n }\n }, []) // Only initialize once\n\n // Handle examples changes\n useEffect(() => {\n if (!playgroundRef.current) return\n\n const playground = playgroundRef.current\n const prev = prevExamplesRef.current\n const current = props.examples\n\n // Get all keys from both previous and current\n const allKeys = new Set([...Object.keys(prev), ...Object.keys(current)])\n\n for (const key of allKeys) {\n const prevExample = prev[key]\n const currentExample = current[key]\n\n if (!prevExample && currentExample) {\n // Example was added\n playground.addExample(key, currentExample)\n } else if (prevExample && !currentExample) {\n // Example was removed\n playground.removeExample(key)\n } else if (prevExample && currentExample && !shallowEqualExample(prevExample, currentExample)) {\n // Example was modified\n playground.addExample(key, currentExample)\n }\n }\n\n prevExamplesRef.current = { ...current }\n }, [props.examples])\n\n return <div ref={rootRef} style={{ width: '100%', height: '100%' }} />\n}\n\n/**\n * Shallow comparison of two Example objects\n */\nfunction shallowEqualExample(a: Example, b: Example): boolean {\n if (a === b) return true\n if (a.name !== b.name) return false\n if (a.description !== b.description) return false\n if (!shallowEqualArray(a.nodes, b.nodes)) return false\n if (!shallowEqualArray(a.edges, b.edges)) return false\n if (!shallowEqualOptions(a.options, b.options)) return false\n if (!shallowEqualSource(a.source, b.source)) return false\n return true\n}\n\n/**\n * Shallow comparison of arrays\n */\nfunction shallowEqualArray<T>(a: T[] | undefined, b: T[] | undefined): boolean {\n if (a === b) return true\n if (!a || !b) return false\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false\n }\n return true\n}\n\n/**\n * Shallow comparison of ExampleOptions\n */\nfunction shallowEqualOptions(\n a: Example['options'],\n b: Example['options']\n): boolean {\n if (a === b) return true\n if (!a || !b) return false\n // For now, do a simple reference check on options\n // Can be enhanced if needed\n return a === b\n}\n\n/**\n * Shallow comparison of ExampleSource\n */\nfunction shallowEqualSource(\n a: Example['source'],\n b: Example['source']\n): boolean {\n if (a === b) return true\n if (!a || !b) return false\n if (a.type !== b.type) return false\n if (a.type === 'file' && b.type === 'file') {\n return a.path === b.path\n }\n if (a.type === 'websocket' && b.type === 'websocket') {\n return a.url === b.url\n }\n return false\n}\n"],"mappings":"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","names":["React","useEffect","useRef"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@3plate/graph-react",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "type": "module",
5
5
  "license": "GPL-3.0",
6
6
  "repository": {
@@ -29,7 +29,7 @@
29
29
  "react-dom": "^18.0.0 || ^19.0.0"
30
30
  },
31
31
  "dependencies": {
32
- "@3plate/graph-core": "0.1.16"
32
+ "@3plate/graph-core": "0.1.18"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@testing-library/jest-dom": "^6.6.3",