@stuly/anode-react 0.1.1 → 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/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,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) {
@@ -48,12 +48,16 @@ interface LinkData {
48
48
  * tree with the internal headless engine.
49
49
  *
50
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
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
54
58
  * automatically mirrors these arrays. Changes made directly in the UI
55
59
  * (dragging, deleting) will trigger the corresponding `onNodesChange` callback.
56
- * 4. **Spatial Culling:** Automatically uses `useVisibleNodes` to optimize rendering.
60
+ * 5. **Spatial Culling:** Automatically uses `useVisibleNodes` to optimize rendering.
57
61
  *
58
62
  * **Usage:**
59
63
  * ```tsx
@@ -32,12 +32,16 @@ const getCenter = (t1, t2) => {
32
32
  * tree with the internal headless engine.
33
33
  *
34
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
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
38
42
  * automatically mirrors these arrays. Changes made directly in the UI
39
43
  * (dragging, deleting) will trigger the corresponding `onNodesChange` callback.
40
- * 4. **Spatial Culling:** Automatically uses `useVisibleNodes` to optimize rendering.
44
+ * 5. **Spatial Culling:** Automatically uses `useVisibleNodes` to optimize rendering.
41
45
  *
42
46
  * **Usage:**
43
47
  * ```tsx
@@ -414,6 +418,8 @@ const World = ({ children, style, nodeTypes = {}, linkTypes = {}, defaultLinkKin
414
418
  const rect = worldRef.current.getBoundingClientRect();
415
419
  const startX = e.clientX - rect.left;
416
420
  const startY = e.clientY - rect.top;
421
+ let currentX = startX;
422
+ let currentY = startY;
417
423
  setSelectionBox({
418
424
  startX,
419
425
  startY,
@@ -421,33 +427,34 @@ const World = ({ children, style, nodeTypes = {}, linkTypes = {}, defaultLinkKin
421
427
  endY: startY
422
428
  });
423
429
  const onMouseMove = (moveEvent) => {
424
- setSelectionBox((prev) => prev ? {
425
- ...prev,
426
- endX: moveEvent.clientX - rect.left,
427
- endY: moveEvent.clientY - rect.top
428
- } : 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
+ });
429
438
  };
430
439
  const onMouseUp = () => {
431
440
  document.removeEventListener("mousemove", onMouseMove);
432
441
  document.removeEventListener("mouseup", onMouseUp);
433
- setSelectionBox((prev) => {
434
- if (!prev) return null;
435
- const rect = worldRef.current?.getBoundingClientRect();
436
- if (!rect) return null;
437
- const x1 = Math.min(prev.startX, prev.endX);
438
- const y1 = Math.min(prev.startY, prev.endY);
439
- const x2 = Math.max(prev.startX, prev.endX);
440
- const y2 = Math.max(prev.startY, prev.endY);
441
- const worldTopLeft = screenToWorld(x1 + rect.left, y1 + rect.top);
442
- 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);
443
450
  const queryRect = new Rect(worldTopLeft.x, worldTopLeft.y, worldBottomRight.x - worldTopLeft.x, worldBottomRight.y - worldTopLeft.y);
444
451
  const selectedIds = ctx.quadTree.query(queryRect);
445
452
  setSelection({
446
453
  nodes: new Set(selectedIds),
447
454
  links: /* @__PURE__ */ new Set()
448
455
  });
449
- return null;
450
- });
456
+ }
457
+ setSelectionBox(null);
451
458
  };
452
459
  document.addEventListener("mousemove", onMouseMove);
453
460
  document.addEventListener("mouseup", onMouseUp);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stuly/anode-react",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
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",
@@ -31,7 +31,7 @@
31
31
  "license": "MIT",
32
32
  "peerDependencies": {
33
33
  "react": "^19.2.4",
34
- "@stuly/anode": "^0.1.1"
34
+ "@stuly/anode": "^0.1.2"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/react": "^19.2.14",