@stuly/anode-react 0.1.0 → 0.1.1

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).
@@ -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 };
@@ -4,41 +4,78 @@ 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:** Implements standard wheel and touch interaction.
52
+ * 2. **Selection:** Multi-select with Shift + Click, Box select with Alt + Click.
53
+ * 3. **Declarative Sync:** If `nodes` or `links` are passed, the core engine
54
+ * automatically mirrors these arrays. Changes made directly in the UI
55
+ * (dragging, deleting) will trigger the corresponding `onNodesChange` callback.
56
+ * 4. **Spatial Culling:** Automatically uses `useVisibleNodes` to optimize rendering.
57
+ *
58
+ * **Usage:**
59
+ * ```tsx
60
+ * <World
61
+ * nodeTypes={{ math: MathNode }}
62
+ * nodes={state.nodes}
63
+ * onNodesChange={newNodes => setState({ nodes: newNodes })}
64
+ * />
65
+ * ```
66
+ */
30
67
  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;
68
+ /** Elements to overlay on the world (Background, MiniMap, etc.). */children?: React.ReactNode; /** Styles applied to the outer container. */
69
+ style?: React.CSSProperties; /** Styles applied to the selection box overlay. */
70
+ selectionBoxStyle?: React.CSSProperties; /** Map of custom node components by their `type` key. */
71
+ nodeTypes?: Record<string, React.ComponentType<NodeComponentProps>>; /** Map of custom link overlays/UI components by their `type` key. */
72
+ linkTypes?: Record<string, React.ComponentType<LinkComponentProps>>; /** Default style for newly created links. */
73
+ defaultLinkKind?: LinkKind; /** Callback triggered when a user completes a connection via drag-and-drop. */
74
+ onConnect?: (fromId: number, toId: number, ctx: Context<any>) => void; /** Custom validation for link creation. Return false to prevent a connection. */
75
+ isValidConnection?: (from: any, to: any, ctx: Context<any>) => boolean; /** Declarative list of nodes. Use for state-controlled synchronization. */
76
+ nodes?: NodeData[]; /** Declarative list of links. Use for state-controlled synchronization. */
77
+ links?: LinkData[]; /** Callback triggered when nodes are moved or dropped. */
78
+ onNodesChange?: (nodes: NodeData[], ctx: Context<any>) => void; /** Callback triggered when links are updated or deleted. */
42
79
  onLinksChange?: (links: LinkData[], ctx: Context<any>) => void;
43
80
  }>;
44
81
  //#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,28 @@ 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:** Implements standard wheel and touch interaction.
36
+ * 2. **Selection:** Multi-select with Shift + Click, Box select with Alt + Click.
37
+ * 3. **Declarative Sync:** If `nodes` or `links` are passed, the core engine
38
+ * automatically mirrors these arrays. Changes made directly in the UI
39
+ * (dragging, deleting) will trigger the corresponding `onNodesChange` callback.
40
+ * 4. **Spatial Culling:** Automatically uses `useVisibleNodes` to optimize rendering.
41
+ *
42
+ * **Usage:**
43
+ * ```tsx
44
+ * <World
45
+ * nodeTypes={{ math: MathNode }}
46
+ * nodes={state.nodes}
47
+ * onNodesChange={newNodes => setState({ nodes: newNodes })}
48
+ * />
49
+ * ```
50
+ */
29
51
  const World = ({ children, style, nodeTypes = {}, linkTypes = {}, defaultLinkKind = LinkKind.BEZIER, onConnect, isValidConnection, selectionBoxStyle, nodes, links: linksProp, onNodesChange, onLinksChange }) => {
30
52
  const ctx = useAnode();
31
53
  const { viewport: transform, setViewport: setTransform, screenToWorld } = useViewport();
@@ -595,7 +617,7 @@ const World = ({ children, style, nodeTypes = {}, linkTypes = {}, defaultLinkKin
595
617
  zIndex: 1,
596
618
  pointerEvents: "none"
597
619
  },
598
- children: [groups.map((group) => /* @__PURE__ */ jsx(Group, { id: group.id }, group.id)), entities.map((entity) => {
620
+ children: [groups.map((group) => /* @__PURE__ */ jsx(Group$1, { id: group.id }, group.id)), entities.map((entity) => {
599
621
  const Component = nodeTypes[entity.inner?.type || "default"] || DefaultNode;
600
622
  return /* @__PURE__ */ jsx(Node, {
601
623
  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.1",
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.1"
21
35
  },
22
36
  "devDependencies": {
23
37
  "@types/react": "^19.2.14",