@stuly/anode-react 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,6 @@
1
- # anode-react
1
+ # @stuly/anode-react
2
2
 
3
- React bindings and components for Anode, providing a declarative layer over
4
- the headless core engine.
3
+ React bindings and components for Anode, providing a declarative layer over the headless core engine.
5
4
 
6
5
  ## Installation
7
6
 
@@ -9,14 +8,26 @@ the headless core engine.
9
8
  npm install @stuly/anode-react @stuly/anode
10
9
  ```
11
10
 
12
- ## Components & Hooks
11
+ ## Quick Start
13
12
 
14
- - **World:** The primary canvas component for rendering the node graph.
15
- - **Socket:** A component for rendering connection points within nodes.
16
- - **Hooks:**
17
- - `useAnode()`: Access the core engine.
18
- - `useSocketValue()`: Subscribe to reactive data flow.
19
- - `useVisibleNodes()`: Optimized spatial culling.
20
- - `useEntitySockets()`: Reactive socket management.
13
+ ```tsx
14
+ import { AnodeProvider, World } from '@stuly/anode-react';
21
15
 
22
- For detailed documentation and usage examples, see the [root README](../../README.md).
16
+ export default function App() {
17
+ return (
18
+ <AnodeProvider>
19
+ <World />
20
+ </AnodeProvider>
21
+ );
22
+ }
23
+ ```
24
+
25
+ ## Key Components & Hooks
26
+
27
+ - **`World`**: Primary canvas component.
28
+ - **`AnodeProvider`**: Context provider for the engine.
29
+ - **`useAnode()`**: Access the core engine instance.
30
+ - **`useSocketValue()`**: Subscribe to reactive data flow.
31
+ - **`useVisibleNodes()`**: Optimized spatial culling for large graphs.
32
+
33
+ For detailed documentation, usage examples, and core principles, see the [Full README](https://github.com/stulyproject/anode?tab=readme-ov-file).
package/dist/context.d.ts CHANGED
@@ -7,7 +7,18 @@ interface Viewport {
7
7
  y: number;
8
8
  k: number;
9
9
  }
10
+ /**
11
+ * Accesses the underlying headless Anode engine instance.
12
+ *
13
+ * @returns The `Context` instance for direct graph manipulation.
14
+ */
10
15
  declare const useAnode: () => Context<any>;
16
+ /**
17
+ * Accesses the current viewport transformation (pan/zoom) and coordinate
18
+ * conversion utilities.
19
+ *
20
+ * @returns An object containing the current `viewport` and functions to update it.
21
+ */
11
22
  declare const useViewport: () => {
12
23
  viewport: Viewport;
13
24
  setViewport: (v: Viewport) => void;
@@ -16,6 +27,11 @@ declare const useViewport: () => {
16
27
  y: number;
17
28
  };
18
29
  };
30
+ /**
31
+ * Accesses the current selection state for nodes and links.
32
+ *
33
+ * @returns An object containing the `selection` sets and a `setSelection` updater.
34
+ */
19
35
  declare const useSelection: () => {
20
36
  selection: {
21
37
  nodes: Set<number>;
@@ -26,6 +42,20 @@ declare const useSelection: () => {
26
42
  links: Set<number>;
27
43
  }>>;
28
44
  };
45
+ /**
46
+ * The root provider for any Anode React application.
47
+ * Wraps the internal headless engine and provides reactive state for
48
+ * viewport and selection.
49
+ *
50
+ * **Usage:**
51
+ * ```tsx
52
+ * <AnodeProvider>
53
+ * <World>
54
+ * <Background />
55
+ * </World>
56
+ * </AnodeProvider>
57
+ * ```
58
+ */
29
59
  declare const AnodeProvider: React.FC<{
30
60
  children: React.ReactNode;
31
61
  context?: Context;
package/dist/context.js CHANGED
@@ -3,12 +3,27 @@ import { Context } from "@stuly/anode";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
 
5
5
  //#region src/context.tsx
6
+ /**
7
+ * Internal context for Anode's React state, including the engine instance,
8
+ * viewport transformation, and selection state.
9
+ */
6
10
  const AnodeReactContext = createContext(null);
11
+ /**
12
+ * Accesses the underlying headless Anode engine instance.
13
+ *
14
+ * @returns The `Context` instance for direct graph manipulation.
15
+ */
7
16
  const useAnode = () => {
8
17
  const value = useContext(AnodeReactContext);
9
18
  if (!value) throw new Error("useAnode must be used within an AnodeProvider");
10
19
  return value.ctx;
11
20
  };
21
+ /**
22
+ * Accesses the current viewport transformation (pan/zoom) and coordinate
23
+ * conversion utilities.
24
+ *
25
+ * @returns An object containing the current `viewport` and functions to update it.
26
+ */
12
27
  const useViewport = () => {
13
28
  const value = useContext(AnodeReactContext);
14
29
  if (!value) throw new Error("useViewport must be used within an AnodeProvider");
@@ -18,6 +33,11 @@ const useViewport = () => {
18
33
  screenToWorld: value.screenToWorld
19
34
  };
20
35
  };
36
+ /**
37
+ * Accesses the current selection state for nodes and links.
38
+ *
39
+ * @returns An object containing the `selection` sets and a `setSelection` updater.
40
+ */
21
41
  const useSelection = () => {
22
42
  const value = useContext(AnodeReactContext);
23
43
  if (!value) throw new Error("useSelection must be used within an AnodeProvider");
@@ -26,6 +46,20 @@ const useSelection = () => {
26
46
  setSelection: value.setSelection
27
47
  };
28
48
  };
49
+ /**
50
+ * The root provider for any Anode React application.
51
+ * Wraps the internal headless engine and provides reactive state for
52
+ * viewport and selection.
53
+ *
54
+ * **Usage:**
55
+ * ```tsx
56
+ * <AnodeProvider>
57
+ * <World>
58
+ * <Background />
59
+ * </World>
60
+ * </AnodeProvider>
61
+ * ```
62
+ */
29
63
  const AnodeProvider = ({ children, context }) => {
30
64
  const [ctx] = useState(() => context ?? new Context());
31
65
  const [viewport, setViewport] = useState({
@@ -7,6 +7,17 @@ interface BackgroundProps {
7
7
  gap?: number;
8
8
  pattern?: 'dots' | 'lines';
9
9
  }
10
+ /**
11
+ * A decorative grid or dot pattern overlay for the canvas.
12
+ * Automatically pans and scales with the viewport.
13
+ *
14
+ * **Usage:**
15
+ * ```tsx
16
+ * <World>
17
+ * <Background pattern="dots" color="#ccc" />
18
+ * </World>
19
+ * ```
20
+ */
10
21
  declare const Background: React.FC<BackgroundProps>;
11
22
  //#endregion
12
23
  export { Background };
@@ -3,6 +3,17 @@ import React from "react";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
 
5
5
  //#region src/elements/Background.tsx
6
+ /**
7
+ * A decorative grid or dot pattern overlay for the canvas.
8
+ * Automatically pans and scales with the viewport.
9
+ *
10
+ * **Usage:**
11
+ * ```tsx
12
+ * <World>
13
+ * <Background pattern="dots" color="#ccc" />
14
+ * </World>
15
+ * ```
16
+ */
6
17
  const Background = ({ color = "#cbd5e1", size = 1, gap = 20, pattern = "dots" }) => {
7
18
  const { viewport } = useViewport();
8
19
  const scaledGap = gap * viewport.k;
@@ -1,6 +1,17 @@
1
1
  import React from "react";
2
2
 
3
3
  //#region src/elements/Controls.d.ts
4
+ /**
5
+ * A floating UI control panel providing standard canvas interactions
6
+ * like zooming in/out and fitting all nodes into the current view.
7
+ *
8
+ * **Usage:**
9
+ * ```tsx
10
+ * <World>
11
+ * <Controls />
12
+ * </World>
13
+ * ```
14
+ */
4
15
  declare const Controls: React.FC<{
5
16
  style?: React.CSSProperties;
6
17
  }>;
@@ -3,6 +3,17 @@ import React from "react";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
4
 
5
5
  //#region src/elements/Controls.tsx
6
+ /**
7
+ * A floating UI control panel providing standard canvas interactions
8
+ * like zooming in/out and fitting all nodes into the current view.
9
+ *
10
+ * **Usage:**
11
+ * ```tsx
12
+ * <World>
13
+ * <Controls />
14
+ * </World>
15
+ * ```
16
+ */
6
17
  const Controls = ({ style }) => {
7
18
  const { viewport, setViewport } = useViewport();
8
19
  const ctx = useAnode();
@@ -1,10 +1,10 @@
1
1
  import { useAnode, useViewport } from "../context.js";
2
2
  import React, { useState } from "react";
3
- import { Rect, Vec2 } from "@stuly/anode";
3
+ import { Group, Rect, Vec2 } from "@stuly/anode";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
 
6
6
  //#region src/elements/Group.tsx
7
- const Group = ({ id, children }) => {
7
+ const Group$1 = ({ id, children }) => {
8
8
  const ctx = useAnode();
9
9
  const { viewport } = useViewport();
10
10
  const group = ctx.groups.get(id);
@@ -83,4 +83,4 @@ const Group = ({ id, children }) => {
83
83
  };
84
84
 
85
85
  //#endregion
86
- export { Group };
86
+ export { Group$1 as Group };
@@ -1,6 +1,17 @@
1
1
  import React from "react";
2
2
 
3
3
  //#region src/elements/MiniMap.d.ts
4
+ /**
5
+ * A simplified bird's-eye view of the entire graph, providing
6
+ * context and a visual indicator of the current viewport.
7
+ *
8
+ * **Usage:**
9
+ * ```tsx
10
+ * <World>
11
+ * <MiniMap width={200} height={150} />
12
+ * </World>
13
+ * ```
14
+ */
4
15
  declare const MiniMap: React.FC<{
5
16
  width?: number;
6
17
  height?: number;
@@ -3,6 +3,17 @@ import React, { useMemo } from "react";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
4
 
5
5
  //#region src/elements/MiniMap.tsx
6
+ /**
7
+ * A simplified bird's-eye view of the entire graph, providing
8
+ * context and a visual indicator of the current viewport.
9
+ *
10
+ * **Usage:**
11
+ * ```tsx
12
+ * <World>
13
+ * <MiniMap width={200} height={150} />
14
+ * </World>
15
+ * ```
16
+ */
6
17
  const MiniMap = ({ width = 200, height = 150, style }) => {
7
18
  const ctx = useAnode();
8
19
  const { viewport } = useViewport();
@@ -2,6 +2,19 @@ import React from "react";
2
2
 
3
3
  //#region src/elements/Panel.d.ts
4
4
  type PanelPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
5
+ /**
6
+ * A helper component to overlay custom UI elements (like toolbars or sidebars)
7
+ * at specific anchor points on the canvas.
8
+ *
9
+ * **Usage:**
10
+ * ```tsx
11
+ * <World>
12
+ * <Panel position="top-right">
13
+ * <button>Custom Action</button>
14
+ * </Panel>
15
+ * </World>
16
+ * ```
17
+ */
5
18
  declare const Panel: React.FC<{
6
19
  position?: PanelPosition;
7
20
  children?: React.ReactNode;
@@ -2,6 +2,19 @@ import React from "react";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
 
4
4
  //#region src/elements/Panel.tsx
5
+ /**
6
+ * A helper component to overlay custom UI elements (like toolbars or sidebars)
7
+ * at specific anchor points on the canvas.
8
+ *
9
+ * **Usage:**
10
+ * ```tsx
11
+ * <World>
12
+ * <Panel position="top-right">
13
+ * <button>Custom Action</button>
14
+ * </Panel>
15
+ * </World>
16
+ * ```
17
+ */
5
18
  const Panel = ({ position = "top-left", children, style }) => {
6
19
  const getPositionStyle = () => {
7
20
  switch (position) {
@@ -4,41 +4,82 @@ import React from "react";
4
4
  import { Context, LinkKind } from "@stuly/anode";
5
5
 
6
6
  //#region src/elements/World.d.ts
7
+ /** Represents the minimal data for a node in declarative sync mode. */
7
8
  interface NodeData {
9
+ /** The unique ID for the node. */
8
10
  id: number;
11
+ /** The absolute world position. */
9
12
  position: {
10
13
  x: number;
11
14
  y: number;
12
15
  };
16
+ /** Type key for the `nodeTypes` map. */
13
17
  type?: string;
18
+ /** Custom data stored in `entity.inner`. */
14
19
  data?: any;
15
20
  }
21
+ /** Represents the minimal data for a link in declarative sync mode. */
16
22
  interface LinkData {
23
+ /** The unique ID for the link. */
17
24
  id: number;
25
+ /** Source entity ID. */
18
26
  source: number;
27
+ /** Name of the source socket. */
19
28
  sourceHandle: string;
29
+ /** Target entity ID. */
20
30
  target: number;
31
+ /** Name of the target socket. */
21
32
  targetHandle: string;
33
+ /** Type key for the `linkTypes` map. */
22
34
  type?: string;
35
+ /** Custom data stored in `link.inner`. */
23
36
  data?: any;
37
+ /** Routing style for the link. */
24
38
  kind?: LinkKind;
39
+ /** Optional custom waypoints. */
25
40
  waypoints?: {
26
41
  x: number;
27
42
  y: number;
28
43
  }[];
29
44
  }
45
+ /**
46
+ * The primary canvas component for Anode.
47
+ * Handles interaction (zoom, pan, selection) and synchronizes the React component
48
+ * tree with the internal headless engine.
49
+ *
50
+ * **Behaviors:**
51
+ * 1. **Zoom/Pan:** Standard wheel zoom and drag-to-pan interaction.
52
+ * 2. **Selection:** Multi-select with `Shift + Click`, Box select with `Alt + Click`.
53
+ * 3. **Keybindings:**
54
+ * - `Backspace` / `Delete`: Remove selected nodes and links.
55
+ * - `Ctrl + Z` / `Cmd + Z`: Undo the last action.
56
+ * - `Ctrl + Shift + Z` / `Ctrl + Y`: Redo the last undone action.
57
+ * 4. **Declarative Sync:** If `nodes` or `links` are passed, the core engine
58
+ * automatically mirrors these arrays. Changes made directly in the UI
59
+ * (dragging, deleting) will trigger the corresponding `onNodesChange` callback.
60
+ * 5. **Spatial Culling:** Automatically uses `useVisibleNodes` to optimize rendering.
61
+ *
62
+ * **Usage:**
63
+ * ```tsx
64
+ * <World
65
+ * nodeTypes={{ math: MathNode }}
66
+ * nodes={state.nodes}
67
+ * onNodesChange={newNodes => setState({ nodes: newNodes })}
68
+ * />
69
+ * ```
70
+ */
30
71
  declare const World: React.FC<{
31
- children?: React.ReactNode;
32
- style?: React.CSSProperties;
33
- selectionBoxStyle?: React.CSSProperties;
34
- nodeTypes?: Record<string, React.ComponentType<NodeComponentProps>>;
35
- linkTypes?: Record<string, React.ComponentType<LinkComponentProps>>;
36
- defaultLinkKind?: LinkKind;
37
- onConnect?: (fromId: number, toId: number, ctx: Context<any>) => void;
38
- isValidConnection?: (from: any, to: any, ctx: Context<any>) => boolean;
39
- nodes?: NodeData[];
40
- links?: LinkData[];
41
- onNodesChange?: (nodes: NodeData[], ctx: Context<any>) => void;
72
+ /** Elements to overlay on the world (Background, MiniMap, etc.). */children?: React.ReactNode; /** Styles applied to the outer container. */
73
+ style?: React.CSSProperties; /** Styles applied to the selection box overlay. */
74
+ selectionBoxStyle?: React.CSSProperties; /** Map of custom node components by their `type` key. */
75
+ nodeTypes?: Record<string, React.ComponentType<NodeComponentProps>>; /** Map of custom link overlays/UI components by their `type` key. */
76
+ linkTypes?: Record<string, React.ComponentType<LinkComponentProps>>; /** Default style for newly created links. */
77
+ defaultLinkKind?: LinkKind; /** Callback triggered when a user completes a connection via drag-and-drop. */
78
+ onConnect?: (fromId: number, toId: number, ctx: Context<any>) => void; /** Custom validation for link creation. Return false to prevent a connection. */
79
+ isValidConnection?: (from: any, to: any, ctx: Context<any>) => boolean; /** Declarative list of nodes. Use for state-controlled synchronization. */
80
+ nodes?: NodeData[]; /** Declarative list of links. Use for state-controlled synchronization. */
81
+ links?: LinkData[]; /** Callback triggered when nodes are moved or dropped. */
82
+ onNodesChange?: (nodes: NodeData[], ctx: Context<any>) => void; /** Callback triggered when links are updated or deleted. */
42
83
  onLinksChange?: (links: LinkData[], ctx: Context<any>) => void;
43
84
  }>;
44
85
  //#endregion
@@ -1,7 +1,7 @@
1
1
  import { AnodeReactContext, useAnode, useSelection, useViewport } from "../context.js";
2
2
  import { useEdges, useGroups, useVisibleNodes } from "../hooks.js";
3
3
  import { Node } from "./Node.js";
4
- import { Group } from "./Group.js";
4
+ import { Group as Group$1 } from "./Group.js";
5
5
  import { Link as Link$1 } from "./Link.js";
6
6
  import React, { useContext, useEffect, useRef, useState } from "react";
7
7
  import { Context, LinkKind, Rect, Vec2 } from "@stuly/anode";
@@ -26,6 +26,32 @@ const getCenter = (t1, t2) => {
26
26
  y: (t1.clientY + t2.clientY) / 2
27
27
  };
28
28
  };
29
+ /**
30
+ * The primary canvas component for Anode.
31
+ * Handles interaction (zoom, pan, selection) and synchronizes the React component
32
+ * tree with the internal headless engine.
33
+ *
34
+ * **Behaviors:**
35
+ * 1. **Zoom/Pan:** Standard wheel zoom and drag-to-pan interaction.
36
+ * 2. **Selection:** Multi-select with `Shift + Click`, Box select with `Alt + Click`.
37
+ * 3. **Keybindings:**
38
+ * - `Backspace` / `Delete`: Remove selected nodes and links.
39
+ * - `Ctrl + Z` / `Cmd + Z`: Undo the last action.
40
+ * - `Ctrl + Shift + Z` / `Ctrl + Y`: Redo the last undone action.
41
+ * 4. **Declarative Sync:** If `nodes` or `links` are passed, the core engine
42
+ * automatically mirrors these arrays. Changes made directly in the UI
43
+ * (dragging, deleting) will trigger the corresponding `onNodesChange` callback.
44
+ * 5. **Spatial Culling:** Automatically uses `useVisibleNodes` to optimize rendering.
45
+ *
46
+ * **Usage:**
47
+ * ```tsx
48
+ * <World
49
+ * nodeTypes={{ math: MathNode }}
50
+ * nodes={state.nodes}
51
+ * onNodesChange={newNodes => setState({ nodes: newNodes })}
52
+ * />
53
+ * ```
54
+ */
29
55
  const World = ({ children, style, nodeTypes = {}, linkTypes = {}, defaultLinkKind = LinkKind.BEZIER, onConnect, isValidConnection, selectionBoxStyle, nodes, links: linksProp, onNodesChange, onLinksChange }) => {
30
56
  const ctx = useAnode();
31
57
  const { viewport: transform, setViewport: setTransform, screenToWorld } = useViewport();
@@ -392,6 +418,8 @@ const World = ({ children, style, nodeTypes = {}, linkTypes = {}, defaultLinkKin
392
418
  const rect = worldRef.current.getBoundingClientRect();
393
419
  const startX = e.clientX - rect.left;
394
420
  const startY = e.clientY - rect.top;
421
+ let currentX = startX;
422
+ let currentY = startY;
395
423
  setSelectionBox({
396
424
  startX,
397
425
  startY,
@@ -399,33 +427,34 @@ const World = ({ children, style, nodeTypes = {}, linkTypes = {}, defaultLinkKin
399
427
  endY: startY
400
428
  });
401
429
  const onMouseMove = (moveEvent) => {
402
- setSelectionBox((prev) => prev ? {
403
- ...prev,
404
- endX: moveEvent.clientX - rect.left,
405
- endY: moveEvent.clientY - rect.top
406
- } : null);
430
+ currentX = moveEvent.clientX - rect.left;
431
+ currentY = moveEvent.clientY - rect.top;
432
+ setSelectionBox({
433
+ startX,
434
+ startY,
435
+ endX: currentX,
436
+ endY: currentY
437
+ });
407
438
  };
408
439
  const onMouseUp = () => {
409
440
  document.removeEventListener("mousemove", onMouseMove);
410
441
  document.removeEventListener("mouseup", onMouseUp);
411
- setSelectionBox((prev) => {
412
- if (!prev) return null;
413
- const rect = worldRef.current?.getBoundingClientRect();
414
- if (!rect) return null;
415
- const x1 = Math.min(prev.startX, prev.endX);
416
- const y1 = Math.min(prev.startY, prev.endY);
417
- const x2 = Math.max(prev.startX, prev.endX);
418
- const y2 = Math.max(prev.startY, prev.endY);
419
- const worldTopLeft = screenToWorld(x1 + rect.left, y1 + rect.top);
420
- const worldBottomRight = screenToWorld(x2 + rect.left, y2 + rect.top);
442
+ const x1 = Math.min(startX, currentX);
443
+ const y1 = Math.min(startY, currentY);
444
+ const x2 = Math.max(startX, currentX);
445
+ const y2 = Math.max(startY, currentY);
446
+ const worldRect = worldRef.current?.getBoundingClientRect();
447
+ if (worldRect) {
448
+ const worldTopLeft = screenToWorld(x1 + worldRect.left, y1 + worldRect.top);
449
+ const worldBottomRight = screenToWorld(x2 + worldRect.left, y2 + worldRect.top);
421
450
  const queryRect = new Rect(worldTopLeft.x, worldTopLeft.y, worldBottomRight.x - worldTopLeft.x, worldBottomRight.y - worldTopLeft.y);
422
451
  const selectedIds = ctx.quadTree.query(queryRect);
423
452
  setSelection({
424
453
  nodes: new Set(selectedIds),
425
454
  links: /* @__PURE__ */ new Set()
426
455
  });
427
- return null;
428
- });
456
+ }
457
+ setSelectionBox(null);
429
458
  };
430
459
  document.addEventListener("mousemove", onMouseMove);
431
460
  document.addEventListener("mouseup", onMouseUp);
@@ -595,7 +624,7 @@ const World = ({ children, style, nodeTypes = {}, linkTypes = {}, defaultLinkKin
595
624
  zIndex: 1,
596
625
  pointerEvents: "none"
597
626
  },
598
- children: [groups.map((group) => /* @__PURE__ */ jsx(Group, { id: group.id }, group.id)), entities.map((entity) => {
627
+ children: [groups.map((group) => /* @__PURE__ */ jsx(Group$1, { id: group.id }, group.id)), entities.map((entity) => {
599
628
  const Component = nodeTypes[entity.inner?.type || "default"] || DefaultNode;
600
629
  return /* @__PURE__ */ jsx(Node, {
601
630
  id: entity.id,
package/dist/hooks.d.ts CHANGED
@@ -1,15 +1,59 @@
1
- import * as _stuly_anode0 from "@stuly/anode";
2
- import { Entity, Link } from "@stuly/anode";
1
+ import { Entity, Group, Link } from "@stuly/anode";
3
2
 
4
3
  //#region src/hooks.d.ts
4
+ /**
5
+ * Returns an array of all entities currently in the graph.
6
+ * Reactively updates when nodes are created, deleted, or moved.
7
+ *
8
+ * **Usage:**
9
+ * ```tsx
10
+ * const nodes = useNodes();
11
+ * ```
12
+ */
5
13
  declare const useNodes: () => Entity<any>[];
14
+ /**
15
+ * Optimized hook for rendering a large-scale node graph.
16
+ * Performs a spatial query on the QuadTree to return only the nodes
17
+ * that are currently within the user's viewport (with padding).
18
+ *
19
+ * **Side Effects:** Triggers re-renders only when nodes enter or exit the viewport
20
+ * OR when a visible node is moved.
21
+ *
22
+ * @param containerRect The dimensions of the canvas container. If omitted, defaults to window size.
23
+ * @returns Array of visible Entity objects.
24
+ */
6
25
  declare const useVisibleNodes: (containerRect?: {
7
26
  width: number;
8
27
  height: number;
9
28
  }) => Entity<any>[];
29
+ /**
30
+ * Returns an array of all links currently in the graph.
31
+ * Reactively updates when links are created, deleted, or updated,
32
+ * or when an endpoint entity moves (triggering path recalculation).
33
+ */
10
34
  declare const useEdges: () => Link<any>[];
35
+ /**
36
+ * Returns an array of all sockets associated with a specific entity.
37
+ * Reactively updates when sockets are added or removed from the entity.
38
+ *
39
+ * @param entityId The unique ID of the entity.
40
+ */
11
41
  declare const useEntitySockets: (entityId: number) => any[];
42
+ /**
43
+ * Subscribes to the reactive value of a specific socket.
44
+ *
45
+ * **Cause:** Triggers a re-render only when the value of the socket
46
+ * changes due to engine propagation.
47
+ *
48
+ * @template T The type of the value held by the socket.
49
+ * @param socketId The unique ID of the socket.
50
+ * @returns The current socket value, or null if the socket doesn't exist.
51
+ */
12
52
  declare function useSocketValue<T = any>(socketId: number | null): T;
13
- declare const useGroups: () => _stuly_anode0.Group[];
53
+ /**
54
+ * Returns an array of all groups currently in the graph.
55
+ * Reactively updates when groups are created or deleted.
56
+ */
57
+ declare const useGroups: () => Group[];
14
58
  //#endregion
15
59
  export { useEdges, useEntitySockets, useGroups, useNodes, useSocketValue, useVisibleNodes };
package/dist/hooks.js CHANGED
@@ -1,8 +1,17 @@
1
1
  import { useAnode, useViewport } from "./context.js";
2
- import React, { useMemo, useSyncExternalStore } from "react";
3
- import { Entity, Link, Rect } from "@stuly/anode";
2
+ import { useMemo, useSyncExternalStore } from "react";
3
+ import { Entity, Group, Link, Rect } from "@stuly/anode";
4
4
 
5
5
  //#region src/hooks.ts
6
+ /**
7
+ * Returns an array of all entities currently in the graph.
8
+ * Reactively updates when nodes are created, deleted, or moved.
9
+ *
10
+ * **Usage:**
11
+ * ```tsx
12
+ * const nodes = useNodes();
13
+ * ```
14
+ */
6
15
  const useNodes = () => {
7
16
  const ctx = useAnode();
8
17
  const store = useMemo(() => {
@@ -27,6 +36,17 @@ const useNodes = () => {
27
36
  }, [ctx]);
28
37
  return useSyncExternalStore(store.subscribe, store.getSnapshot);
29
38
  };
39
+ /**
40
+ * Optimized hook for rendering a large-scale node graph.
41
+ * Performs a spatial query on the QuadTree to return only the nodes
42
+ * that are currently within the user's viewport (with padding).
43
+ *
44
+ * **Side Effects:** Triggers re-renders only when nodes enter or exit the viewport
45
+ * OR when a visible node is moved.
46
+ *
47
+ * @param containerRect The dimensions of the canvas container. If omitted, defaults to window size.
48
+ * @returns Array of visible Entity objects.
49
+ */
30
50
  const useVisibleNodes = (containerRect) => {
31
51
  const ctx = useAnode();
32
52
  const { viewport } = useViewport();
@@ -45,6 +65,11 @@ const useVisibleNodes = (containerRect) => {
45
65
  useNodes()
46
66
  ]);
47
67
  };
68
+ /**
69
+ * Returns an array of all links currently in the graph.
70
+ * Reactively updates when links are created, deleted, or updated,
71
+ * or when an endpoint entity moves (triggering path recalculation).
72
+ */
48
73
  const useEdges = () => {
49
74
  const ctx = useAnode();
50
75
  const store = useMemo(() => {
@@ -69,6 +94,12 @@ const useEdges = () => {
69
94
  }, [ctx]);
70
95
  return useSyncExternalStore(store.subscribe, store.getSnapshot);
71
96
  };
97
+ /**
98
+ * Returns an array of all sockets associated with a specific entity.
99
+ * Reactively updates when sockets are added or removed from the entity.
100
+ *
101
+ * @param entityId The unique ID of the entity.
102
+ */
72
103
  const useEntitySockets = (entityId) => {
73
104
  const ctx = useAnode();
74
105
  const store = useMemo(() => {
@@ -104,6 +135,16 @@ const useEntitySockets = (entityId) => {
104
135
  }, [ctx, entityId]);
105
136
  return useSyncExternalStore(store.subscribe, store.getSnapshot);
106
137
  };
138
+ /**
139
+ * Subscribes to the reactive value of a specific socket.
140
+ *
141
+ * **Cause:** Triggers a re-render only when the value of the socket
142
+ * changes due to engine propagation.
143
+ *
144
+ * @template T The type of the value held by the socket.
145
+ * @param socketId The unique ID of the socket.
146
+ * @returns The current socket value, or null if the socket doesn't exist.
147
+ */
107
148
  function useSocketValue(socketId) {
108
149
  const ctx = useAnode();
109
150
  const store = useMemo(() => {
@@ -127,6 +168,10 @@ function useSocketValue(socketId) {
127
168
  }, [ctx, socketId]);
128
169
  return useSyncExternalStore(store.subscribe, store.getSnapshot);
129
170
  }
171
+ /**
172
+ * Returns an array of all groups currently in the graph.
173
+ * Reactively updates when groups are created or deleted.
174
+ */
130
175
  const useGroups = () => {
131
176
  const ctx = useAnode();
132
177
  const store = useMemo(() => {
package/package.json CHANGED
@@ -1,23 +1,37 @@
1
1
  {
2
2
  "name": "@stuly/anode-react",
3
- "version": "0.1.0",
4
- "description": "Anode react wrapper",
3
+ "version": "0.1.2",
4
+ "description": "First-class React bindings for building interactive, declarative node editors with Anode.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "publishConfig": {
9
9
  "access": "public"
10
10
  },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/stulyproject/anode.git"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/stulyproject/anode/issues"
17
+ },
18
+ "homepage": "https://github.com/stulyproject/anode#readme",
11
19
  "files": [
12
20
  "dist"
13
21
  ],
14
- "keywords": [],
22
+ "keywords": [
23
+ "react",
24
+ "node-editor",
25
+ "node-graph",
26
+ "anode",
27
+ "declarative"
28
+ ],
15
29
  "author": "",
16
30
  "type": "module",
17
31
  "license": "MIT",
18
32
  "peerDependencies": {
19
33
  "react": "^19.2.4",
20
- "@stuly/anode": "^0.1.0"
34
+ "@stuly/anode": "^0.1.2"
21
35
  },
22
36
  "devDependencies": {
23
37
  "@types/react": "^19.2.14",