@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 +30 -0
- package/dist/context.js +34 -0
- package/dist/elements/Background.d.ts +11 -0
- package/dist/elements/Background.js +11 -0
- package/dist/elements/Controls.d.ts +11 -0
- package/dist/elements/Controls.js +11 -0
- package/dist/elements/MiniMap.d.ts +11 -0
- package/dist/elements/MiniMap.js +11 -0
- package/dist/elements/Panel.d.ts +13 -0
- package/dist/elements/Panel.js +13 -0
- package/dist/elements/World.d.ts +8 -4
- package/dist/elements/World.js +28 -21
- package/package.json +2 -2
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;
|
package/dist/elements/MiniMap.js
CHANGED
|
@@ -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();
|
package/dist/elements/Panel.d.ts
CHANGED
|
@@ -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;
|
package/dist/elements/Panel.js
CHANGED
|
@@ -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) {
|
package/dist/elements/World.d.ts
CHANGED
|
@@ -48,12 +48,16 @@ interface LinkData {
|
|
|
48
48
|
* tree with the internal headless engine.
|
|
49
49
|
*
|
|
50
50
|
* **Behaviors:**
|
|
51
|
-
* 1. **Zoom/Pan:**
|
|
52
|
-
* 2. **Selection:** Multi-select with Shift + Click
|
|
53
|
-
* 3. **
|
|
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
|
-
*
|
|
60
|
+
* 5. **Spatial Culling:** Automatically uses `useVisibleNodes` to optimize rendering.
|
|
57
61
|
*
|
|
58
62
|
* **Usage:**
|
|
59
63
|
* ```tsx
|
package/dist/elements/World.js
CHANGED
|
@@ -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:**
|
|
36
|
-
* 2. **Selection:** Multi-select with Shift + Click
|
|
37
|
-
* 3. **
|
|
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
|
-
*
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const
|
|
440
|
-
const
|
|
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
|
-
|
|
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.
|
|
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.
|
|
34
|
+
"@stuly/anode": "^0.1.2"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/react": "^19.2.14",
|