nexus-shell 0.2.4 → 0.2.6
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 +6 -0
- package/dist/components/graph/GraphCanvas.d.ts +110 -0
- package/dist/components/graph/GraphEdge.d.ts +71 -0
- package/dist/components/graph/GraphMiniMap.d.ts +83 -0
- package/dist/components/graph/GraphNode.d.ts +68 -0
- package/dist/components/graph/NodePalette.d.ts +117 -0
- package/dist/components/graph/index.d.ts +17 -0
- package/dist/components/graph/useGraphKeyboard.d.ts +115 -0
- package/dist/index.d.ts +2 -0
- package/dist/lib/graph.d.ts +165 -0
- package/dist/nexus-shell.es.js +2141 -1241
- package/dist/nexus-shell.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -129,6 +129,12 @@ shell's own frame and keep every registration working.
|
|
|
129
129
|
| `TerminalPane` | Bottom-docked terminal log and input |
|
|
130
130
|
| `ThemeSwitcher` | Segmented theme control |
|
|
131
131
|
| `TreeWidget` | Virtualised tree for any hierarchy, with drag-to-move and a data-driven menu |
|
|
132
|
+
| `GraphCanvas` | Infinite pannable, zoomable field for node-and-edge editing |
|
|
133
|
+
| `GraphNode` | Positioned, focusable node with edge ports |
|
|
134
|
+
| `GraphEdge` | Directed edge with bezier / smoothstep / straight routing |
|
|
135
|
+
| `GraphMiniMap` | Scaled graph overview with a viewport indicator |
|
|
136
|
+
| `NodePalette` | Drag-to-create and keyboard-to-create node types |
|
|
137
|
+
| `useGraphKeyboard` | Spatial keyboard navigation and editing for a graph |
|
|
132
138
|
| `UserProfile` | Avatar and identity widget |
|
|
133
139
|
|
|
134
140
|
## Theming
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { default as React, ReactNode } from 'react';
|
|
2
|
+
import { IGraphNode, IPoint, IViewport } from '../../lib/graph';
|
|
3
|
+
|
|
4
|
+
export interface GraphCanvasHandle {
|
|
5
|
+
/** Current pan and zoom. */
|
|
6
|
+
getViewport: () => IViewport;
|
|
7
|
+
/** Jump to a viewport. */
|
|
8
|
+
setViewport: (viewport: IViewport) => void;
|
|
9
|
+
/** Frame the given nodes, or clear the viewport when the list is empty. */
|
|
10
|
+
fitTo: (nodes: IGraphNode[], padding?: number) => void;
|
|
11
|
+
/** Multiply the zoom about the centre of the element. */
|
|
12
|
+
zoomBy: (factor: number) => void;
|
|
13
|
+
/** Convert a client (page) point into graph space. */
|
|
14
|
+
clientToGraph: (client: IPoint) => IPoint;
|
|
15
|
+
/** The element's pixel size. A minimap needs this to draw its indicator. */
|
|
16
|
+
getSize: () => {
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
};
|
|
20
|
+
/** Move keyboard focus to the canvas. */
|
|
21
|
+
focus: () => void;
|
|
22
|
+
}
|
|
23
|
+
export interface GraphCanvasProps {
|
|
24
|
+
/**
|
|
25
|
+
* Content rendered in graph space. Position children with `GraphNode`, or
|
|
26
|
+
* with your own absolutely-positioned elements in graph coordinates.
|
|
27
|
+
*/
|
|
28
|
+
children?: ReactNode;
|
|
29
|
+
/**
|
|
30
|
+
* Content rendered in screen space, above the graph and unaffected by pan or
|
|
31
|
+
* zoom — toolbars, a minimap, a legend.
|
|
32
|
+
*/
|
|
33
|
+
overlay?: ReactNode;
|
|
34
|
+
/**
|
|
35
|
+
* Take control of pan and zoom. Omit it and the canvas manages its own,
|
|
36
|
+
* which is what most callers want: you should not have to own a viewport
|
|
37
|
+
* just to draw a graph.
|
|
38
|
+
*/
|
|
39
|
+
viewport?: IViewport;
|
|
40
|
+
/** Starting viewport when uncontrolled. Defaults to origin at 1× zoom. */
|
|
41
|
+
defaultViewport?: IViewport;
|
|
42
|
+
/** Called whenever pan or zoom changes, controlled or not. */
|
|
43
|
+
onViewportChange?: (viewport: IViewport) => void;
|
|
44
|
+
/**
|
|
45
|
+
* Called on mount and whenever the element resizes, with its pixel size.
|
|
46
|
+
*
|
|
47
|
+
* Pass this to a {@link GraphMiniMap} as `canvasSize` — the indicator cannot
|
|
48
|
+
* be drawn without knowing how much of the graph is on screen.
|
|
49
|
+
*/
|
|
50
|
+
onSizeChange?: (size: {
|
|
51
|
+
width: number;
|
|
52
|
+
height: number;
|
|
53
|
+
}) => void;
|
|
54
|
+
/**
|
|
55
|
+
* Called with a graph-space point when empty canvas is clicked. Use it to
|
|
56
|
+
* clear selection, or to place a node at the click.
|
|
57
|
+
*/
|
|
58
|
+
onCanvasClick?: (point: IPoint, event: React.MouseEvent) => void;
|
|
59
|
+
/** Called with a graph-space point when empty canvas is double-clicked. */
|
|
60
|
+
onCanvasDoubleClick?: (point: IPoint, event: React.MouseEvent) => void;
|
|
61
|
+
/** Called with a graph-space point on right-click of empty canvas. */
|
|
62
|
+
onCanvasContextMenu?: (point: IPoint, event: React.MouseEvent) => void;
|
|
63
|
+
/**
|
|
64
|
+
* Called when something is dropped on the canvas, with the graph-space point
|
|
65
|
+
* under the cursor. Pair with `NodePalette` for drag-to-create.
|
|
66
|
+
*/
|
|
67
|
+
onDrop?: (point: IPoint, event: React.DragEvent) => void;
|
|
68
|
+
/** Show the dot grid. Defaults to `true`. */
|
|
69
|
+
grid?: boolean;
|
|
70
|
+
/** Grid spacing in graph units. Defaults to `24`. */
|
|
71
|
+
gridSize?: number;
|
|
72
|
+
/** Smallest allowed zoom. Defaults to `0.1`. */
|
|
73
|
+
minScale?: number;
|
|
74
|
+
/** Largest allowed zoom. Defaults to `4`. */
|
|
75
|
+
maxScale?: number;
|
|
76
|
+
/** Allow panning by dragging empty canvas. Defaults to `true`. */
|
|
77
|
+
pannable?: boolean;
|
|
78
|
+
/** Allow zooming with the wheel or trackpad pinch. Defaults to `true`. */
|
|
79
|
+
zoomable?: boolean;
|
|
80
|
+
/** Accessible label for the canvas region. Defaults to `"Graph canvas"`. */
|
|
81
|
+
'aria-label'?: string;
|
|
82
|
+
/** Extra classes merged onto the root element. */
|
|
83
|
+
className?: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* An infinite, pannable, zoomable field to draw a graph on.
|
|
87
|
+
*
|
|
88
|
+
* The canvas owns exactly one thing: the mapping between screen pixels and
|
|
89
|
+
* graph space. It knows nothing about nodes, edges, or what any of it means —
|
|
90
|
+
* children are rendered inside a transformed layer, so anything positioned in
|
|
91
|
+
* graph coordinates lands in the right place.
|
|
92
|
+
*
|
|
93
|
+
* Pan by dragging empty space or with the middle mouse button; zoom with the
|
|
94
|
+
* wheel or a trackpad pinch, always anchored to the cursor. Space-drag pans
|
|
95
|
+
* from anywhere, including over a node.
|
|
96
|
+
*
|
|
97
|
+
* Viewport state is internal by default. Pass `viewport` to take control.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```tsx
|
|
101
|
+
* <GraphCanvas onDrop={(point) => addNodeAt(point)}>
|
|
102
|
+
* {nodes.map((node) => (
|
|
103
|
+
* <GraphNode key={node.id} node={node} onMove={move}>
|
|
104
|
+
* {node.label}
|
|
105
|
+
* </GraphNode>
|
|
106
|
+
* ))}
|
|
107
|
+
* </GraphCanvas>
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export declare const GraphCanvas: React.ForwardRefExoticComponent<GraphCanvasProps & React.RefAttributes<GraphCanvasHandle>>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { GraphPort, IGraphEdge, IGraphNode, IPoint } from '../../lib/graph';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* How an edge gets from one node to the other.
|
|
6
|
+
*
|
|
7
|
+
* - `bezier` — a smooth curve. The default; reads well for dense graphs.
|
|
8
|
+
* - `smoothstep` — right angles with rounded corners, for flowcharts.
|
|
9
|
+
* - `straight` — a direct line.
|
|
10
|
+
*/
|
|
11
|
+
export type EdgeRouting = 'bezier' | 'smoothstep' | 'straight';
|
|
12
|
+
export interface GraphEdgeProps {
|
|
13
|
+
/** The edge to render. */
|
|
14
|
+
edge: IGraphEdge;
|
|
15
|
+
/** The node the edge leaves. */
|
|
16
|
+
source: IGraphNode;
|
|
17
|
+
/** The node the edge enters. */
|
|
18
|
+
target: IGraphNode;
|
|
19
|
+
/** Path shape. Defaults to `"bezier"`. */
|
|
20
|
+
routing?: EdgeRouting;
|
|
21
|
+
/** Draw as selected. */
|
|
22
|
+
selected?: boolean;
|
|
23
|
+
/** Draw an arrowhead at the target end. Defaults to `true`. */
|
|
24
|
+
arrow?: boolean;
|
|
25
|
+
/** Called on click. The wide invisible hit area makes this practical. */
|
|
26
|
+
onSelect?: (id: string, event: React.MouseEvent) => void;
|
|
27
|
+
/** Called on right-click, with the client point for a context menu. */
|
|
28
|
+
onContextMenu?: (id: string, point: IPoint, event: React.MouseEvent) => void;
|
|
29
|
+
/** Extra classes merged onto the visible path. */
|
|
30
|
+
className?: string;
|
|
31
|
+
}
|
|
32
|
+
/** The SVG path for a pair of points and the sides they leave from. */
|
|
33
|
+
export declare const edgePath: (from: IPoint, to: IPoint, fromPort: GraphPort, routing: EdgeRouting) => string;
|
|
34
|
+
/**
|
|
35
|
+
* A directed edge between two nodes, rendered as an SVG path.
|
|
36
|
+
*
|
|
37
|
+
* Which sides the edge leaves and enters is worked out from the nodes'
|
|
38
|
+
* relative positions, so a node needs no handle configuration to be
|
|
39
|
+
* connectable — move a node and the attachment points follow.
|
|
40
|
+
*
|
|
41
|
+
* Render inside {@link GraphEdgeLayer}, which provides the SVG surface and the
|
|
42
|
+
* arrowhead marker.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```tsx
|
|
46
|
+
* <GraphEdgeLayer>
|
|
47
|
+
* {edges.map((edge) => (
|
|
48
|
+
* <GraphEdge
|
|
49
|
+
* key={edge.id}
|
|
50
|
+
* edge={edge}
|
|
51
|
+
* source={byId[edge.source]}
|
|
52
|
+
* target={byId[edge.target]}
|
|
53
|
+
* onSelect={setSelectedEdge}
|
|
54
|
+
* />
|
|
55
|
+
* ))}
|
|
56
|
+
* </GraphEdgeLayer>
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare const GraphEdge: ({ edge, source, target, routing, selected, arrow, onSelect, onContextMenu, className, }: GraphEdgeProps) => import("react/jsx-runtime").JSX.Element;
|
|
60
|
+
export interface GraphEdgeLayerProps {
|
|
61
|
+
children?: React.ReactNode;
|
|
62
|
+
className?: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The SVG surface edges are drawn on.
|
|
66
|
+
*
|
|
67
|
+
* Sits beneath the nodes inside a {@link GraphCanvas}, spanning an effectively
|
|
68
|
+
* unbounded area so edges remain visible wherever the graph is panned. It also
|
|
69
|
+
* defines the shared arrowhead marker.
|
|
70
|
+
*/
|
|
71
|
+
export declare const GraphEdgeLayer: ({ children, className }: GraphEdgeLayerProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { IGraphNode, IViewport } from '../../lib/graph';
|
|
2
|
+
|
|
3
|
+
export interface GraphMiniMapProps {
|
|
4
|
+
/** The nodes to plot. Only position and size are read. */
|
|
5
|
+
nodes: IGraphNode[];
|
|
6
|
+
/** The canvas's current pan and zoom. */
|
|
7
|
+
viewport: IViewport;
|
|
8
|
+
/**
|
|
9
|
+
* Pixel size of the canvas the minimap mirrors. Get it from `GraphCanvas`'s
|
|
10
|
+
* `onSizeChange`; without it the viewport indicator can't be drawn.
|
|
11
|
+
*/
|
|
12
|
+
canvasSize: {
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Called with a viewport that recentres the canvas. Omit it and the minimap
|
|
18
|
+
* becomes a read-only overview.
|
|
19
|
+
*/
|
|
20
|
+
onViewportChange?: (viewport: IViewport) => void;
|
|
21
|
+
/** Minimap width in pixels. Defaults to `180`. */
|
|
22
|
+
width?: number;
|
|
23
|
+
/** Minimap height in pixels. Defaults to `120`. */
|
|
24
|
+
height?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Fill colour for a node, as a CSS colour or Tailwind class. Receives the
|
|
27
|
+
* node, so you can colour by `kind`. Defaults to the muted foreground.
|
|
28
|
+
*/
|
|
29
|
+
nodeColor?: (node: IGraphNode) => string;
|
|
30
|
+
/**
|
|
31
|
+
* Highlight these node ids — a selection, or search results. Drawn in the
|
|
32
|
+
* primary colour above the rest.
|
|
33
|
+
*/
|
|
34
|
+
highlightIds?: string[];
|
|
35
|
+
/** Padding around the plotted content, in minimap pixels. Defaults to `8`. */
|
|
36
|
+
padding?: number;
|
|
37
|
+
/** How far an arrow key pans, in graph units. Defaults to `80`. */
|
|
38
|
+
panDistance?: number;
|
|
39
|
+
/** Accessible label. Defaults to `"Graph minimap"`. */
|
|
40
|
+
'aria-label'?: string;
|
|
41
|
+
/** Extra classes merged onto the root element. */
|
|
42
|
+
className?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A scaled overview of the whole graph, with an indicator showing what the
|
|
46
|
+
* canvas is currently looking at.
|
|
47
|
+
*
|
|
48
|
+
* Click anywhere to jump there, drag to pan continuously, or focus it and use
|
|
49
|
+
* the arrow keys. The zoom level is never changed — a minimap answers "where am
|
|
50
|
+
* I", and changing scale on a click makes that answer harder to trust.
|
|
51
|
+
*
|
|
52
|
+
* The plotted extent is the union of the graph's bounds and the current
|
|
53
|
+
* viewport, so the indicator stays visible even when you have panned far away
|
|
54
|
+
* from every node. Without that, panning into empty space makes the minimap
|
|
55
|
+
* appear broken.
|
|
56
|
+
*
|
|
57
|
+
* Fully controlled: it reads `viewport` and reports where you want to go, but
|
|
58
|
+
* never moves the canvas itself.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```tsx
|
|
62
|
+
* const [viewport, setViewport] = useState(IDENTITY_VIEWPORT);
|
|
63
|
+
* const [size, setSize] = useState({ width: 0, height: 0 });
|
|
64
|
+
*
|
|
65
|
+
* <GraphCanvas
|
|
66
|
+
* viewport={viewport}
|
|
67
|
+
* onViewportChange={setViewport}
|
|
68
|
+
* onSizeChange={setSize}
|
|
69
|
+
* overlay={
|
|
70
|
+
* <GraphMiniMap
|
|
71
|
+
* nodes={nodes}
|
|
72
|
+
* viewport={viewport}
|
|
73
|
+
* canvasSize={size}
|
|
74
|
+
* onViewportChange={setViewport}
|
|
75
|
+
* className="absolute bottom-3 right-3"
|
|
76
|
+
* />
|
|
77
|
+
* }
|
|
78
|
+
* >
|
|
79
|
+
* …
|
|
80
|
+
* </GraphCanvas>
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare const GraphMiniMap: ({ nodes, viewport, canvasSize, onViewportChange, width, height, nodeColor, highlightIds, padding, panDistance, "aria-label": ariaLabel, className, }: GraphMiniMapProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { default as React, ReactNode } from 'react';
|
|
2
|
+
import { GraphPort, IGraphNode, IPoint } from '../../lib/graph';
|
|
3
|
+
|
|
4
|
+
export interface GraphNodeProps {
|
|
5
|
+
/** The node to render. Position and size come from here. */
|
|
6
|
+
node: IGraphNode;
|
|
7
|
+
/** Node body. Anything — a label, a form, a chart. */
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
/**
|
|
10
|
+
* Called with the new top-left position while dragging, and again on
|
|
11
|
+
* release. The component never mutates the node — you apply the move, which
|
|
12
|
+
* is what lets you snap, constrain or reject it.
|
|
13
|
+
*/
|
|
14
|
+
onMove?: (id: string, position: IPoint) => void;
|
|
15
|
+
/** Called when the drag ends, if the node actually moved. */
|
|
16
|
+
onMoveEnd?: (id: string, position: IPoint) => void;
|
|
17
|
+
/** Called on click. */
|
|
18
|
+
onSelect?: (id: string, event: React.MouseEvent) => void;
|
|
19
|
+
/** Called on double-click. Conventionally enters edit mode. */
|
|
20
|
+
onActivate?: (id: string) => void;
|
|
21
|
+
/** Called on right-click, with the client point for a context menu. */
|
|
22
|
+
onContextMenu?: (id: string, point: IPoint, event: React.MouseEvent) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Called when one of the node's ports is dragged, to begin a connection.
|
|
25
|
+
* Providing it is what makes the ports appear.
|
|
26
|
+
*/
|
|
27
|
+
onConnectStart?: (id: string, port: GraphPort) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Called when a pointer is released over this node, with its id — the target
|
|
30
|
+
* end of a connection started elsewhere.
|
|
31
|
+
*/
|
|
32
|
+
onConnectEnd?: (targetId: string) => void;
|
|
33
|
+
/** Draw as selected. */
|
|
34
|
+
selected?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Draw as focused — the keyboard cursor. Distinct from `selected`: focus is
|
|
37
|
+
* where the next keystroke lands, selection is what an action applies to.
|
|
38
|
+
*/
|
|
39
|
+
focused?: boolean;
|
|
40
|
+
/** Show connection ports on hover and while focused. Defaults to `true`. */
|
|
41
|
+
ports?: boolean;
|
|
42
|
+
/** Allow dragging. Defaults to `true`. */
|
|
43
|
+
draggable?: boolean;
|
|
44
|
+
/** Extra classes merged onto the node element. */
|
|
45
|
+
className?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A positioned, focusable node on a {@link GraphCanvas}.
|
|
49
|
+
*
|
|
50
|
+
* Provides placement, dragging, selection and focus affordances, and the four
|
|
51
|
+
* edge ports. What the node *contains* is entirely yours — pass any children.
|
|
52
|
+
*
|
|
53
|
+
* Fully controlled: dragging reports positions through `onMove` but never moves
|
|
54
|
+
* the node itself, so snapping, constraints and undo stay in your hands.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```tsx
|
|
58
|
+
* <GraphNode
|
|
59
|
+
* node={node}
|
|
60
|
+
* focused={node.id === focusedId}
|
|
61
|
+
* onMove={(id, position) => setNodes(move(nodes, id, position))}
|
|
62
|
+
* onActivate={(id) => setEditing(id)}
|
|
63
|
+
* >
|
|
64
|
+
* <p className="text-xs font-medium">{node.data.label}</p>
|
|
65
|
+
* </GraphNode>
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export declare const GraphNode: ({ node, children, onMove, onMoveEnd, onSelect, onActivate, onContextMenu, onConnectStart, onConnectEnd, selected, focused, ports, draggable, className, }: GraphNodeProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { default as React, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
/** The MIME type palette entries put their kind on during a drag. */
|
|
4
|
+
export declare const GRAPH_NODE_MIME = "application/x-nexus-graph-node";
|
|
5
|
+
/**
|
|
6
|
+
* How the palette lays its items out.
|
|
7
|
+
*
|
|
8
|
+
* - `horizontal` — a row, wrapping if it must.
|
|
9
|
+
* - `vertical` — a column.
|
|
10
|
+
* - `auto` — whichever fits the space the palette is given. See
|
|
11
|
+
* {@link NodePalette} for the rule.
|
|
12
|
+
*/
|
|
13
|
+
export type PaletteOrientation = 'horizontal' | 'vertical' | 'auto';
|
|
14
|
+
/** The orientation actually rendered, once `auto` has been resolved. */
|
|
15
|
+
export type ResolvedOrientation = Exclude<PaletteOrientation, 'auto'>;
|
|
16
|
+
/** An entry in a {@link NodePalette}. */
|
|
17
|
+
export interface INodePaletteItem {
|
|
18
|
+
/**
|
|
19
|
+
* The `kind` given to the node this creates. Also the payload carried on the
|
|
20
|
+
* drag, so the drop handler knows what was dragged.
|
|
21
|
+
*/
|
|
22
|
+
kind: string;
|
|
23
|
+
/** Label shown in the palette. */
|
|
24
|
+
label: string;
|
|
25
|
+
/** Optional icon or swatch rendered before the label. */
|
|
26
|
+
icon?: ReactNode;
|
|
27
|
+
/** One line explaining what the node is, shown as a tooltip. */
|
|
28
|
+
description?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface NodePaletteProps {
|
|
31
|
+
/** The node types on offer. */
|
|
32
|
+
items: INodePaletteItem[];
|
|
33
|
+
/**
|
|
34
|
+
* Called when an item is activated by click or Enter, for creating without a
|
|
35
|
+
* drag. Keyboard users need this — a drag-only palette is unreachable.
|
|
36
|
+
*/
|
|
37
|
+
onSelect?: (item: INodePaletteItem) => void;
|
|
38
|
+
/** Called when a drag starts, if you want to show a drop hint. */
|
|
39
|
+
onDragStart?: (item: INodePaletteItem) => void;
|
|
40
|
+
/**
|
|
41
|
+
* Layout axis. Defaults to `"auto"`, which measures the available space and
|
|
42
|
+
* picks whichever axis fits.
|
|
43
|
+
*/
|
|
44
|
+
orientation?: PaletteOrientation;
|
|
45
|
+
/**
|
|
46
|
+
* Called when `auto` settles on an axis, and whenever it changes. Useful for
|
|
47
|
+
* adjusting surrounding layout to match.
|
|
48
|
+
*/
|
|
49
|
+
onOrientationChange?: (orientation: ResolvedOrientation) => void;
|
|
50
|
+
/**
|
|
51
|
+
* Hide labels and show icons only. Useful for a narrow rail; items keep their
|
|
52
|
+
* accessible name, so nothing is lost to a screen reader.
|
|
53
|
+
*/
|
|
54
|
+
iconOnly?: boolean;
|
|
55
|
+
/** Extra classes merged onto the root element. */
|
|
56
|
+
className?: string;
|
|
57
|
+
/** Accessible label for the group. Defaults to `"Node palette"`. */
|
|
58
|
+
'aria-label'?: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A palette of node types that can be dragged onto a {@link GraphCanvas}, or
|
|
62
|
+
* activated with the keyboard.
|
|
63
|
+
*
|
|
64
|
+
* The palette carries only the item's `kind` on the drag; what a node of that
|
|
65
|
+
* kind actually *is* is decided by your drop handler. Pair it with
|
|
66
|
+
* {@link readPaletteDrag} on the canvas.
|
|
67
|
+
*
|
|
68
|
+
* Every item is a real button, so the palette is fully usable without a
|
|
69
|
+
* pointer — `onSelect` fires on click or Enter.
|
|
70
|
+
*
|
|
71
|
+
* ## Orientation
|
|
72
|
+
*
|
|
73
|
+
* `auto` (the default) measures the space the palette's container gives it and
|
|
74
|
+
* picks the axis that fits:
|
|
75
|
+
*
|
|
76
|
+
* 1. a row, if the items fit the available width;
|
|
77
|
+
* 2. otherwise a column, if they fit the available height;
|
|
78
|
+
* 3. otherwise a row that wraps.
|
|
79
|
+
*
|
|
80
|
+
* A container that shrink-wraps its content — an absolutely positioned overlay,
|
|
81
|
+
* say — offers no information about available space, so the palette measures
|
|
82
|
+
* the nearest ancestor that has a size of its own. That is what stops a
|
|
83
|
+
* vertical palette from narrowing its own container and then being unable to
|
|
84
|
+
* discover it has room to go horizontal again.
|
|
85
|
+
*
|
|
86
|
+
* Pass `horizontal` or `vertical` when you already know.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```tsx
|
|
90
|
+
* <GraphCanvas
|
|
91
|
+
* onDrop={(point, event) => {
|
|
92
|
+
* const kind = readPaletteDrag(event);
|
|
93
|
+
* if (kind) addNode({ kind, position: point });
|
|
94
|
+
* }}
|
|
95
|
+
* overlay={
|
|
96
|
+
* <NodePalette
|
|
97
|
+
* items={[{ kind: 'task', label: 'Task' }, { kind: 'gate', label: 'Gate' }]}
|
|
98
|
+
* onSelect={(item) => addNodeAtCentre(item.kind)}
|
|
99
|
+
* />
|
|
100
|
+
* }
|
|
101
|
+
* />
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export declare const NodePalette: ({ items, onSelect, onDragStart, orientation, onOrientationChange, iconOnly, className, "aria-label": ariaLabel, }: NodePaletteProps) => import("react/jsx-runtime").JSX.Element;
|
|
105
|
+
/**
|
|
106
|
+
* Read the node kind from a drop on the canvas, or `null` if the drag did not
|
|
107
|
+
* come from a {@link NodePalette}.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```tsx
|
|
111
|
+
* <GraphCanvas onDrop={(point, event) => {
|
|
112
|
+
* const kind = readPaletteDrag(event);
|
|
113
|
+
* if (kind) createNode(kind, point);
|
|
114
|
+
* }} />
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export declare const readPaletteDrag: (event: React.DragEvent) => string | null;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The graph layer.
|
|
3
|
+
*
|
|
4
|
+
* A decomposed set of primitives for building node-and-edge editors, in the
|
|
5
|
+
* same spirit as the rest of the library: the canvas knows about coordinates,
|
|
6
|
+
* the node knows about placement and focus, the edge knows about geometry, and
|
|
7
|
+
* the keyboard hook knows about traversal. None of them know what a node
|
|
8
|
+
* *means* — that stays in your application.
|
|
9
|
+
*
|
|
10
|
+
* Compose them yourself, or reach for whichever piece you need on its own.
|
|
11
|
+
*/
|
|
12
|
+
export { GraphCanvas, type GraphCanvasProps, type GraphCanvasHandle } from './GraphCanvas';
|
|
13
|
+
export { GraphNode, type GraphNodeProps } from './GraphNode';
|
|
14
|
+
export { GraphEdge, GraphEdgeLayer, edgePath, type GraphEdgeProps, type GraphEdgeLayerProps, type EdgeRouting, } from './GraphEdge';
|
|
15
|
+
export { useGraphKeyboard, DEFAULT_GRAPH_KEYS, type UseGraphKeyboardOptions, type UseGraphKeyboardResult, type GraphKeyboardActions, } from './useGraphKeyboard';
|
|
16
|
+
export { GraphMiniMap, type GraphMiniMapProps } from './GraphMiniMap';
|
|
17
|
+
export { NodePalette, readPaletteDrag, GRAPH_NODE_MIME, type NodePaletteProps, type INodePaletteItem, type PaletteOrientation, type ResolvedOrientation, } from './NodePalette';
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Direction, IGraphEdge, IGraphNode, IPoint } from '../../lib/graph';
|
|
2
|
+
|
|
3
|
+
/** The default keyboard map. Every binding is overridable. */
|
|
4
|
+
export declare const DEFAULT_GRAPH_KEYS: {
|
|
5
|
+
/** Move focus to the nearest node in a direction. */
|
|
6
|
+
readonly moveFocus: readonly ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"];
|
|
7
|
+
/** Create a connected node in a direction. */
|
|
8
|
+
readonly createConnected: "Tab";
|
|
9
|
+
/** Edit the focused node. */
|
|
10
|
+
readonly edit: "Enter";
|
|
11
|
+
/** Leave edit mode, cancel a pending connection, or clear focus. */
|
|
12
|
+
readonly cancel: "Escape";
|
|
13
|
+
/** Delete the focused node. */
|
|
14
|
+
readonly delete: readonly ["Delete", "Backspace"];
|
|
15
|
+
/** Begin connecting from the focused node. */
|
|
16
|
+
readonly connect: "c";
|
|
17
|
+
/** Nudge the focused node. Held with the arrow keys. */
|
|
18
|
+
readonly nudgeModifier: "shiftKey";
|
|
19
|
+
};
|
|
20
|
+
export interface GraphKeyboardActions {
|
|
21
|
+
/**
|
|
22
|
+
* Create a node at a position, returning its id so focus can follow it. The
|
|
23
|
+
* library never invents node content — you decide what a new node is.
|
|
24
|
+
*/
|
|
25
|
+
onCreateNode?: (position: IPoint, context: {
|
|
26
|
+
from?: string;
|
|
27
|
+
direction?: Direction;
|
|
28
|
+
}) => string | undefined;
|
|
29
|
+
/** Connect two nodes. */
|
|
30
|
+
onConnect?: (sourceId: string, targetId: string) => void;
|
|
31
|
+
/** Delete a node and its edges. */
|
|
32
|
+
onDeleteNode?: (id: string) => void;
|
|
33
|
+
/** Move a node, used by nudging. */
|
|
34
|
+
onMoveNode?: (id: string, position: IPoint) => void;
|
|
35
|
+
/** Enter edit mode for a node. */
|
|
36
|
+
onEditNode?: (id: string) => void;
|
|
37
|
+
}
|
|
38
|
+
export interface UseGraphKeyboardOptions extends GraphKeyboardActions {
|
|
39
|
+
/** The current nodes. Traversal and placement are computed from these. */
|
|
40
|
+
nodes: IGraphNode[];
|
|
41
|
+
/** The current edges. Used to reason about existing connections. */
|
|
42
|
+
edges: IGraphEdge[];
|
|
43
|
+
/** Take control of which node has keyboard focus. */
|
|
44
|
+
focusedId?: string | null;
|
|
45
|
+
/** Starting focus when uncontrolled. */
|
|
46
|
+
defaultFocusedId?: string | null;
|
|
47
|
+
/** Called whenever focus moves. */
|
|
48
|
+
onFocusChange?: (id: string | null) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Element the listener attaches to. Bindings only fire while focus is inside
|
|
51
|
+
* it, so several graphs can coexist on one page.
|
|
52
|
+
*/
|
|
53
|
+
targetRef: React.RefObject<HTMLElement | null>;
|
|
54
|
+
/** How far a nudge moves a node, in graph units. Defaults to `16`. */
|
|
55
|
+
nudgeDistance?: number;
|
|
56
|
+
/** Turn the whole thing off. */
|
|
57
|
+
disabled?: boolean;
|
|
58
|
+
}
|
|
59
|
+
export interface UseGraphKeyboardResult {
|
|
60
|
+
/** The node the next keystroke applies to. */
|
|
61
|
+
focusedId: string | null;
|
|
62
|
+
setFocusedId: (id: string | null) => void;
|
|
63
|
+
/** Node currently being edited, if any. */
|
|
64
|
+
editingId: string | null;
|
|
65
|
+
setEditingId: (id: string | null) => void;
|
|
66
|
+
/** Source of a connection awaiting a target, if any. */
|
|
67
|
+
connectingFrom: string | null;
|
|
68
|
+
setConnectingFrom: (id: string | null) => void;
|
|
69
|
+
/** Move focus as an arrow key would. Exposed for toolbar buttons. */
|
|
70
|
+
moveFocus: (direction: Direction) => void;
|
|
71
|
+
/** Create a connected node as Tab would. */
|
|
72
|
+
createConnected: (direction?: Direction) => void;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Keyboard-driven graph navigation and editing.
|
|
76
|
+
*
|
|
77
|
+
* Headless: it owns the focus, editing and connection cursors and reports what
|
|
78
|
+
* should happen, but renders nothing and mutates nothing. That means it works
|
|
79
|
+
* with `GraphNode` or with your own node rendering, and every mutation stays
|
|
80
|
+
* subject to your validation and undo.
|
|
81
|
+
*
|
|
82
|
+
* The default map, all overridable:
|
|
83
|
+
*
|
|
84
|
+
* | Key | Action |
|
|
85
|
+
* | :-- | :-- |
|
|
86
|
+
* | Arrows | Move focus to the nearest node in that direction |
|
|
87
|
+
* | Shift + Arrows | Nudge the focused node |
|
|
88
|
+
* | Tab / Shift+Tab | Create a node connected to the focused one |
|
|
89
|
+
* | Enter | Edit the focused node |
|
|
90
|
+
* | `c` then a node | Connect from the focused node |
|
|
91
|
+
* | Delete / Backspace | Delete the focused node |
|
|
92
|
+
* | Escape | Leave edit mode, cancel a connection, or clear focus |
|
|
93
|
+
*
|
|
94
|
+
* Arrow traversal is spatial rather than by insertion order — it picks the
|
|
95
|
+
* nearest node in the direction travelled, penalising lateral offset, which is
|
|
96
|
+
* what makes a hand-arranged diagram navigable without a mouse.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```tsx
|
|
100
|
+
* const canvasRef = useRef<HTMLDivElement>(null);
|
|
101
|
+
*
|
|
102
|
+
* const keyboard = useGraphKeyboard({
|
|
103
|
+
* nodes, edges, targetRef: canvasRef,
|
|
104
|
+
* onCreateNode: (position) => {
|
|
105
|
+
* const id = nextId('node', nodes);
|
|
106
|
+
* setNodes([...nodes, { id, position, data: { label: 'New' } }]);
|
|
107
|
+
* return id;
|
|
108
|
+
* },
|
|
109
|
+
* onConnect: (source, target) =>
|
|
110
|
+
* setEdges([...edges, { id: nextId('edge', edges), source, target }]),
|
|
111
|
+
* onDeleteNode: (id) => { … },
|
|
112
|
+
* });
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export declare const useGraphKeyboard: ({ nodes, edges, focusedId: controlledFocusedId, defaultFocusedId, onFocusChange, targetRef, nudgeDistance, disabled, onCreateNode, onConnect, onDeleteNode, onMoveNode, onEditNode, }: UseGraphKeyboardOptions) => UseGraphKeyboardResult;
|
package/dist/index.d.ts
CHANGED
|
@@ -56,5 +56,7 @@ export { TerminalPane, type TerminalPaneProps } from './components/widgets/Termi
|
|
|
56
56
|
export { ThemeSwitcher, type ThemeSwitcherProps, type IThemeOption, DEFAULT_THEME_OPTIONS } from './components/widgets/ThemeSwitcher';
|
|
57
57
|
export { TreeWidget, type TreeWidgetProps, type ITreeNode, type ITreeAction, type ITreeContext } from './components/widgets/TreeWidget';
|
|
58
58
|
export { UserProfile, type UserProfileProps, type IUserProfile, type IUserProfileAction } from './components/widgets/UserProfile';
|
|
59
|
+
export * from './components/graph';
|
|
60
|
+
export { DEFAULT_NODE_SIZE, IDENTITY_VIEWPORT, clampScale, edgesOf, findNeighbour, fitViewport, graphBounds, isConnected, nextId, nodeRect, placeRelativeTo, portPoint, rectCenter, removeNode, resolvePorts, toGraphSpace, toScreenSpace, unionRect, viewportRect, centerOn, zoomAt, type Direction, type GraphPort, type IGraphEdge, type IGraphNode, type IPoint, type IRect, type IViewport, } from './lib/graph';
|
|
59
61
|
export { cn } from './lib/cn';
|
|
60
62
|
export { BUNDLED_THEMES, BUNDLED_THEME_CLASSES, themeClass, type IBundledTheme, type BundledThemeId, } from './lib/themes';
|