@skygraph/core 0.0.0-placeholder.0 → 0.5.0
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/LICENSE +21 -0
- package/README.md +42 -6
- package/dist/calendar.cjs +404 -0
- package/dist/calendar.cjs.map +1 -0
- package/dist/calendar.d.cts +209 -0
- package/dist/calendar.d.ts +209 -0
- package/dist/calendar.js +12 -0
- package/dist/calendar.js.map +1 -0
- package/dist/chunk-5CCD5Q4B.js +36 -0
- package/dist/chunk-5CCD5Q4B.js.map +1 -0
- package/dist/chunk-7FA2TBSZ.js +1211 -0
- package/dist/chunk-7FA2TBSZ.js.map +1 -0
- package/dist/chunk-AB3RLBLW.js +357 -0
- package/dist/chunk-AB3RLBLW.js.map +1 -0
- package/dist/chunk-GEMALROQ.js +258 -0
- package/dist/chunk-GEMALROQ.js.map +1 -0
- package/dist/chunk-IY6LJU3L.js +256 -0
- package/dist/chunk-IY6LJU3L.js.map +1 -0
- package/dist/chunk-KMGRNPLG.js +453 -0
- package/dist/chunk-KMGRNPLG.js.map +1 -0
- package/dist/chunk-Y7FTBBYX.js +397 -0
- package/dist/chunk-Y7FTBBYX.js.map +1 -0
- package/dist/chunk-ZWB7JGAJ.js +488 -0
- package/dist/chunk-ZWB7JGAJ.js.map +1 -0
- package/dist/form.cjs +498 -0
- package/dist/form.cjs.map +1 -0
- package/dist/form.d.cts +130 -0
- package/dist/form.d.ts +130 -0
- package/dist/form.js +8 -0
- package/dist/form.js.map +1 -0
- package/dist/graph.cjs +1269 -0
- package/dist/graph.cjs.map +1 -0
- package/dist/graph.d.cts +671 -0
- package/dist/graph.d.ts +671 -0
- package/dist/graph.js +34 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.cjs +3860 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +144 -0
- package/dist/index.d.ts +144 -0
- package/dist/index.js +477 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime-internal.cjs +339 -0
- package/dist/runtime-internal.cjs.map +1 -0
- package/dist/runtime-internal.d.cts +130 -0
- package/dist/runtime-internal.d.ts +130 -0
- package/dist/runtime-internal.js +67 -0
- package/dist/runtime-internal.js.map +1 -0
- package/dist/table.cjs +537 -0
- package/dist/table.cjs.map +1 -0
- package/dist/table.d.cts +215 -0
- package/dist/table.d.ts +215 -0
- package/dist/table.js +16 -0
- package/dist/table.js.map +1 -0
- package/dist/tree.cjs +442 -0
- package/dist/tree.cjs.map +1 -0
- package/dist/tree.d.cts +76 -0
- package/dist/tree.d.ts +76 -0
- package/dist/tree.js +8 -0
- package/dist/tree.js.map +1 -0
- package/dist/types-B-rKQJ4R.d.cts +35 -0
- package/dist/types-B-rKQJ4R.d.ts +35 -0
- package/dist/virtual.cjs +283 -0
- package/dist/virtual.cjs.map +1 -0
- package/dist/virtual.d.cts +96 -0
- package/dist/virtual.d.ts +96 -0
- package/dist/virtual.js +9 -0
- package/dist/virtual.js.map +1 -0
- package/package.json +98 -18
- package/index.js +0 -3
package/dist/graph.d.ts
ADDED
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
import { C as Core } from './types-B-rKQJ4R.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GraphEngine types — the diagram (Visio-like) domain engine.
|
|
5
|
+
*
|
|
6
|
+
* Design intent (see ARCHITECTURE.md / chat-derived spec):
|
|
7
|
+
*
|
|
8
|
+
* - The engine knows ONLY topology + minimal geometry needed for routing /
|
|
9
|
+
* hit-testing. It does NOT know about React, DOM, SVG, canvas — those live
|
|
10
|
+
* in `@skygraph/react/<Diagram component>`.
|
|
11
|
+
* - A Node has an `outline` (rect / ellipse / polygon / flattened path),
|
|
12
|
+
* a `transform` (position in parent space), an optional `parentId` for
|
|
13
|
+
* hierarchy (groups, sub-groups), and an `anchorPolicy` describing how to
|
|
14
|
+
* generate connection points along the outline.
|
|
15
|
+
* - An Edge connects two anchors (each by node id + either an anchor id or
|
|
16
|
+
* a normalized perimeter parameter `s ∈ [0..1]`).
|
|
17
|
+
* - All anchor IDs are stable across `anchorPolicy` changes for the same
|
|
18
|
+
* outline kind, so existing edges do not get "torn off" when the user
|
|
19
|
+
* bumps `k` from 3 to 5.
|
|
20
|
+
*/
|
|
21
|
+
type NodeId = string;
|
|
22
|
+
type EdgeId = string;
|
|
23
|
+
type AnchorId = string;
|
|
24
|
+
/**
|
|
25
|
+
* 2D point in the local coordinate space of an outline.
|
|
26
|
+
* The first element is x, the second is y.
|
|
27
|
+
*/
|
|
28
|
+
type Point = readonly [number, number];
|
|
29
|
+
/**
|
|
30
|
+
* Axis-aligned bounding box in world space (no rotation).
|
|
31
|
+
* Computed from `transform` + `outline`. Used for broad-phase collision /
|
|
32
|
+
* spatial indexing in the routing layer.
|
|
33
|
+
*/
|
|
34
|
+
interface AABB {
|
|
35
|
+
x: number;
|
|
36
|
+
y: number;
|
|
37
|
+
w: number;
|
|
38
|
+
h: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Oriented bounding box in world space — a rectangle that may be rotated
|
|
42
|
+
* relative to the world axes. Returned by {@link GraphEngine.getNodeOBB}
|
|
43
|
+
* and consumed by selection / manipulator overlays that need a tight fit
|
|
44
|
+
* around a rotated node. See `obb.ts` for the helper module.
|
|
45
|
+
*
|
|
46
|
+
* `halfWidth` / `halfHeight` are pre-multiplied by world scale, so an OBB
|
|
47
|
+
* with `angle === 0` describes the same rectangle as the matching AABB
|
|
48
|
+
* (modulo the centre-vs-top-left representation).
|
|
49
|
+
*/
|
|
50
|
+
interface OBB$1 {
|
|
51
|
+
readonly center: Point;
|
|
52
|
+
readonly halfWidth: number;
|
|
53
|
+
readonly halfHeight: number;
|
|
54
|
+
readonly angle: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Visual outline of a node, expressed in the node's local coordinate space.
|
|
58
|
+
*
|
|
59
|
+
* - `rect` / `ellipse` are parametric primitives; the engine generates anchors
|
|
60
|
+
* along their boundary on demand.
|
|
61
|
+
* - `polygon` carries explicit vertices.
|
|
62
|
+
* - `path` is a "flattened approximation" — the visual layer (React) is free
|
|
63
|
+
* to render a curve (SVG path, Bezier, ...) but commits a discrete polyline
|
|
64
|
+
* to the engine. This way the engine never needs to know about cubic Beziers.
|
|
65
|
+
*/
|
|
66
|
+
type Outline = {
|
|
67
|
+
readonly kind: 'rect';
|
|
68
|
+
readonly w: number;
|
|
69
|
+
readonly h: number;
|
|
70
|
+
} | {
|
|
71
|
+
readonly kind: 'ellipse';
|
|
72
|
+
readonly rx: number;
|
|
73
|
+
readonly ry: number;
|
|
74
|
+
} | {
|
|
75
|
+
readonly kind: 'polygon';
|
|
76
|
+
readonly verts: readonly Point[];
|
|
77
|
+
} | {
|
|
78
|
+
readonly kind: 'path';
|
|
79
|
+
readonly flatten: readonly Point[];
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* 2D affine-ish transform of a node within its parent.
|
|
83
|
+
* Only translate is required; rotate/scale are optional for the v0 engine.
|
|
84
|
+
*/
|
|
85
|
+
interface NodeTransform {
|
|
86
|
+
/** X position in the parent's local space. */
|
|
87
|
+
x: number;
|
|
88
|
+
/** Y position in the parent's local space. */
|
|
89
|
+
y: number;
|
|
90
|
+
/** Rotation in radians. Optional; default 0. */
|
|
91
|
+
rot?: number;
|
|
92
|
+
/** Uniform scale factor. Optional; default 1. */
|
|
93
|
+
scale?: number;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Strategy for generating anchor points on a node outline.
|
|
97
|
+
*
|
|
98
|
+
* - `perEdge: { k: 1..10 }` — primitives only (rect, polygon).
|
|
99
|
+
* Corners are always anchors. `k` evenly-spaced anchors per edge,
|
|
100
|
+
* not counting corners (open interval) — bumping `k` does NOT shift
|
|
101
|
+
* existing anchor IDs.
|
|
102
|
+
*
|
|
103
|
+
* - `byLength: { density }` — uniform along the perimeter at the given
|
|
104
|
+
* distance step (in local units). Works for every outline kind.
|
|
105
|
+
*
|
|
106
|
+
* - `manual: anchors[]` — explicit anchor list with stable IDs and
|
|
107
|
+
* normalized perimeter parameter `s ∈ [0..1]`.
|
|
108
|
+
*/
|
|
109
|
+
type AnchorPolicy = {
|
|
110
|
+
readonly kind: 'perEdge';
|
|
111
|
+
readonly k: number;
|
|
112
|
+
} | {
|
|
113
|
+
readonly kind: 'byLength';
|
|
114
|
+
readonly density: number;
|
|
115
|
+
} | {
|
|
116
|
+
readonly kind: 'manual';
|
|
117
|
+
readonly anchors: readonly {
|
|
118
|
+
id: AnchorId;
|
|
119
|
+
s: number;
|
|
120
|
+
}[];
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* A point on the outline where edges can attach.
|
|
124
|
+
* `s` is the normalized perimeter parameter (0..1, monotonically increasing
|
|
125
|
+
* around the outline). Persisting an edge endpoint by `s` makes it stable
|
|
126
|
+
* under outline edits / anchor regeneration.
|
|
127
|
+
*/
|
|
128
|
+
interface Anchor {
|
|
129
|
+
/** Stable id assigned by the anchor generator (e.g. `nw`, `edge-0:1of3`). */
|
|
130
|
+
id: AnchorId;
|
|
131
|
+
/** Normalized perimeter parameter; `0` is the start of the outline traversal. */
|
|
132
|
+
s: number;
|
|
133
|
+
/** Local coordinates on the outline (x, y). */
|
|
134
|
+
point: Point;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* A graph node — building block of the diagram. Can be a "shape" (with a
|
|
138
|
+
* visible outline), a "group" (`outline.kind === 'rect'` with `w=h=0` plus
|
|
139
|
+
* children, or any other size — visual layer decides whether to render
|
|
140
|
+
* border / background), or a leaf widget. The engine itself does NOT
|
|
141
|
+
* distinguish — it only sees outline + transform + children-by-parentId.
|
|
142
|
+
*/
|
|
143
|
+
interface GraphNode {
|
|
144
|
+
/** Stable identity; unique within the graph. */
|
|
145
|
+
id: NodeId;
|
|
146
|
+
/** Parent node id, or `null` for root-level nodes. */
|
|
147
|
+
parentId: NodeId | null;
|
|
148
|
+
/** Position / rotation / scale in parent space. */
|
|
149
|
+
transform: NodeTransform;
|
|
150
|
+
/** Local-space outline. */
|
|
151
|
+
outline: Outline;
|
|
152
|
+
/** How anchors are generated along the outline. */
|
|
153
|
+
anchorPolicy: AnchorPolicy;
|
|
154
|
+
/** Optional application payload; the engine never inspects it. */
|
|
155
|
+
data?: unknown;
|
|
156
|
+
/** Monotonic revision; increments on transform / outline / anchorPolicy change. */
|
|
157
|
+
geomRevision: number;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* An edge is a connection between two anchors on (possibly different) nodes.
|
|
161
|
+
*
|
|
162
|
+
* `endpoint.anchor` may be:
|
|
163
|
+
* - an `AnchorId` referring to an explicit / generated anchor;
|
|
164
|
+
* - a `{ s }` parameter binding the edge to a perimeter position even when
|
|
165
|
+
* the anchor list is regenerated.
|
|
166
|
+
*/
|
|
167
|
+
type EdgeEndpoint = {
|
|
168
|
+
readonly node: NodeId;
|
|
169
|
+
readonly anchor: AnchorId;
|
|
170
|
+
} | {
|
|
171
|
+
readonly node: NodeId;
|
|
172
|
+
readonly anchor: {
|
|
173
|
+
readonly s: number;
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
interface GraphEdge {
|
|
177
|
+
/** Stable identity; unique within the graph. */
|
|
178
|
+
id: EdgeId;
|
|
179
|
+
/** Source endpoint. */
|
|
180
|
+
from: EdgeEndpoint;
|
|
181
|
+
/** Target endpoint. */
|
|
182
|
+
to: EdgeEndpoint;
|
|
183
|
+
/**
|
|
184
|
+
* Routing hint:
|
|
185
|
+
* • `straight` — single L line between the two anchors.
|
|
186
|
+
* • `orthogonal` — axis-aligned polyline (right-angled). Combined
|
|
187
|
+
* with `routeAroundNodes` in `<Diagram>` the
|
|
188
|
+
* router avoids other nodes via A*. The visual
|
|
189
|
+
* layer also draws rounded corners.
|
|
190
|
+
* • `bezier` — single cubic Bezier curve. Control handles
|
|
191
|
+
* extend perpendicular to each node's nearest
|
|
192
|
+
* side, matching React Flow's `getBezierPath`.
|
|
193
|
+
* • `manual` — explicit waypoint polyline.
|
|
194
|
+
*/
|
|
195
|
+
routing: 'straight' | 'orthogonal' | 'bezier' | 'manual';
|
|
196
|
+
/** Optional waypoints for `manual` routing (in world coordinates). */
|
|
197
|
+
waypoints?: readonly Point[];
|
|
198
|
+
/** Optional application payload. */
|
|
199
|
+
data?: unknown;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Snapshot of the current graph state — read-model the engine exposes.
|
|
203
|
+
* This object is immutable from the consumer's perspective; the engine
|
|
204
|
+
* publishes a new snapshot on every commit by writing into the Core store
|
|
205
|
+
* under the `$graph.` prefix.
|
|
206
|
+
*/
|
|
207
|
+
interface GraphState {
|
|
208
|
+
nodes: ReadonlyMap<NodeId, GraphNode>;
|
|
209
|
+
edges: ReadonlyMap<EdgeId, GraphEdge>;
|
|
210
|
+
/** Per-node list of incident edges (incoming + outgoing) for O(degree) lookup. */
|
|
211
|
+
edgesByNode: ReadonlyMap<NodeId, readonly EdgeId[]>;
|
|
212
|
+
}
|
|
213
|
+
/** Patch type for `moveNode` / `updateNode`. */
|
|
214
|
+
interface NodeUpdate {
|
|
215
|
+
transform?: Partial<NodeTransform>;
|
|
216
|
+
outline?: Outline;
|
|
217
|
+
anchorPolicy?: AnchorPolicy;
|
|
218
|
+
data?: unknown;
|
|
219
|
+
}
|
|
220
|
+
/** Options for `addNode`. */
|
|
221
|
+
interface NodeInit {
|
|
222
|
+
/** Optional pre-set id. If omitted, the engine generates one. */
|
|
223
|
+
id?: NodeId;
|
|
224
|
+
/** Parent for hierarchy. `null` (default) places the node at the root. */
|
|
225
|
+
parentId?: NodeId | null;
|
|
226
|
+
/** Transform in parent space. Defaults to `{ x: 0, y: 0 }`. */
|
|
227
|
+
transform?: NodeTransform;
|
|
228
|
+
/** Outline; defaults to a 100×60 rect. */
|
|
229
|
+
outline?: Outline;
|
|
230
|
+
/** Anchor strategy; defaults to `{ kind: 'perEdge', k: 1 }`. */
|
|
231
|
+
anchorPolicy?: AnchorPolicy;
|
|
232
|
+
/** Application payload. */
|
|
233
|
+
data?: unknown;
|
|
234
|
+
}
|
|
235
|
+
/** Options for `addEdge`. */
|
|
236
|
+
interface EdgeInit {
|
|
237
|
+
/** Optional pre-set id. If omitted, the engine generates one. */
|
|
238
|
+
id?: EdgeId;
|
|
239
|
+
from: EdgeEndpoint;
|
|
240
|
+
to: EdgeEndpoint;
|
|
241
|
+
/** Routing strategy. Defaults to `'straight'`. */
|
|
242
|
+
routing?: 'straight' | 'orthogonal' | 'bezier' | 'manual';
|
|
243
|
+
waypoints?: readonly Point[];
|
|
244
|
+
data?: unknown;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Public API of the Graph engine. Created via `createGraph(core, options?)`.
|
|
248
|
+
* The engine is a thin façade over a path-based Core store under `$graph.`.
|
|
249
|
+
*/
|
|
250
|
+
interface GraphEngine {
|
|
251
|
+
/** Add a node and return its assigned id. */
|
|
252
|
+
addNode(init?: NodeInit): NodeId;
|
|
253
|
+
/** Remove a node and all edges incident to it. */
|
|
254
|
+
removeNode(id: NodeId): void;
|
|
255
|
+
/** Patch a node's transform / outline / anchorPolicy / data. */
|
|
256
|
+
updateNode(id: NodeId, patch: NodeUpdate): void;
|
|
257
|
+
/** Move a node by an absolute new (x, y) in parent space. */
|
|
258
|
+
moveNode(id: NodeId, x: number, y: number): void;
|
|
259
|
+
/** Re-parent a node (groups / sub-groups). `null` = root. */
|
|
260
|
+
setParent(id: NodeId, parentId: NodeId | null): void;
|
|
261
|
+
/** Add an edge between two endpoints. */
|
|
262
|
+
addEdge(init: EdgeInit): EdgeId;
|
|
263
|
+
/** Remove an edge by id. */
|
|
264
|
+
removeEdge(id: EdgeId): void;
|
|
265
|
+
/** Get a single node (or undefined). */
|
|
266
|
+
getNode(id: NodeId): GraphNode | undefined;
|
|
267
|
+
/** Get a single edge (or undefined). */
|
|
268
|
+
getEdge(id: EdgeId): GraphEdge | undefined;
|
|
269
|
+
/** Snapshot of the entire graph state. */
|
|
270
|
+
getState(): GraphState;
|
|
271
|
+
/** Direct children (immediate parentId match). */
|
|
272
|
+
childrenOf(id: NodeId | null): NodeId[];
|
|
273
|
+
/** Edges incident to a node (incoming + outgoing). */
|
|
274
|
+
edgesOf(id: NodeId): EdgeId[];
|
|
275
|
+
/** Compute anchors for a node according to its `anchorPolicy`. */
|
|
276
|
+
anchorsOf(id: NodeId): Anchor[];
|
|
277
|
+
/** Compute axis-aligned bounding box of a node in world coordinates. */
|
|
278
|
+
boundsOf(id: NodeId): AABB;
|
|
279
|
+
/**
|
|
280
|
+
* Compute the oriented bounding box of a node in world coordinates.
|
|
281
|
+
*
|
|
282
|
+
* For an axis-aligned node (`transform.rot === 0` everywhere up the
|
|
283
|
+
* parent chain) the OBB and AABB describe the same rectangle. When any
|
|
284
|
+
* ancestor has a non-zero rotation, the OBB is the tight fit and
|
|
285
|
+
* `boundsOf` (AABB) becomes a strict superset.
|
|
286
|
+
*/
|
|
287
|
+
getNodeOBB(id: NodeId): OBB$1;
|
|
288
|
+
/**
|
|
289
|
+
* Subscribe to commits — callback fires after every snapshot publish
|
|
290
|
+
* (any `addNode`, `removeNode`, `addEdge`, etc.). Returns an unsubscribe fn.
|
|
291
|
+
*
|
|
292
|
+
* Prefer this for React adapters: it sidesteps the `$graph.snapshot.<id>`
|
|
293
|
+
* path lookup that varies per engine instance.
|
|
294
|
+
*/
|
|
295
|
+
subscribe(cb: () => void): () => void;
|
|
296
|
+
/** Group multiple mutations into one history entry. Optional `label` is
|
|
297
|
+
* attached to the resulting `HistoryEntry`. */
|
|
298
|
+
transaction(fn: () => void, label?: string): void;
|
|
299
|
+
/**
|
|
300
|
+
* Add a label to the history without changing the visible state.
|
|
301
|
+
*
|
|
302
|
+
* Behaviour depends on context:
|
|
303
|
+
*
|
|
304
|
+
* - **Inside `transaction(fn)`**: commits the in-progress pending
|
|
305
|
+
* entry as a sub-boundary (with `label`) and starts a fresh
|
|
306
|
+
* capture for the rest of the transaction. Use this to split one
|
|
307
|
+
* batch into multiple undo entries.
|
|
308
|
+
*
|
|
309
|
+
* - **Outside a transaction**: re-labels the most recent undo entry.
|
|
310
|
+
* Does NOT push a duplicate snapshot — auto-push has already
|
|
311
|
+
* captured every meaningful boundary, so a standalone push would
|
|
312
|
+
* only clutter the stack.
|
|
313
|
+
*/
|
|
314
|
+
pushHistory(label?: string): void;
|
|
315
|
+
/** Restore the previous state, returning `true` if anything was undone. */
|
|
316
|
+
undo(): boolean;
|
|
317
|
+
/** Re-apply the most recently undone state, returning `true` on success. */
|
|
318
|
+
redo(): boolean;
|
|
319
|
+
/** `true` when there is at least one entry on the undo stack. */
|
|
320
|
+
canUndo(): boolean;
|
|
321
|
+
/** `true` when there is at least one entry on the redo stack. */
|
|
322
|
+
canRedo(): boolean;
|
|
323
|
+
/** Drop all undo and redo entries (state itself is not affected). */
|
|
324
|
+
clearHistory(): void;
|
|
325
|
+
/**
|
|
326
|
+
* Reset the graph (drops all nodes / edges, clears history).
|
|
327
|
+
* @internal Test helper.
|
|
328
|
+
*/
|
|
329
|
+
clear(): void;
|
|
330
|
+
}
|
|
331
|
+
/** Options accepted by `createGraph`. */
|
|
332
|
+
interface GraphEngineOptions {
|
|
333
|
+
/** Optional debug name for the engine instance. */
|
|
334
|
+
name?: string;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Reactive diagram engine — the fourth member of the form / table / tree /
|
|
339
|
+
* **graph** family. State is mirrored into the Core store under `$graph.`
|
|
340
|
+
* for cross-engine coordination, but the in-memory maps are the source of
|
|
341
|
+
* truth and the public read API.
|
|
342
|
+
*
|
|
343
|
+
* v0 scope (initial Graph engine stream — see CHANGELOG):
|
|
344
|
+
* - Node / Edge model + parent hierarchy
|
|
345
|
+
* - addNode / removeNode / updateNode / moveNode / setParent
|
|
346
|
+
* - addEdge / removeEdge
|
|
347
|
+
* - anchor generator (perEdge / byLength / manual)
|
|
348
|
+
* - bounds computation (AABB, no rotation)
|
|
349
|
+
* - geomRevision bump on geometry-affecting updates
|
|
350
|
+
*
|
|
351
|
+
* NOT in v0:
|
|
352
|
+
* - orthogonal routing (with obstacle avoidance)
|
|
353
|
+
* - undo / redo (covered separately by `plugins/history`)
|
|
354
|
+
* - snap-to-grid, drag-and-drop palette
|
|
355
|
+
* - rotated bounding boxes (OBB)
|
|
356
|
+
*/
|
|
357
|
+
declare function createGraph(core: Core, options?: GraphEngineOptions): GraphEngine;
|
|
358
|
+
|
|
359
|
+
/** Options for {@link routeOrthogonal}. */
|
|
360
|
+
interface RouteOrthogonalOptions {
|
|
361
|
+
/**
|
|
362
|
+
* Bend preference for the no-obstacle case.
|
|
363
|
+
* - `auto` (default) picks H-then-V or V-then-H based on which leg is longer.
|
|
364
|
+
* - `hv` forces horizontal first.
|
|
365
|
+
* - `vh` forces vertical first.
|
|
366
|
+
*
|
|
367
|
+
* Ignored when `obstacles` is non-empty — A* picks bends from costs.
|
|
368
|
+
*/
|
|
369
|
+
preferred?: 'auto' | 'hv' | 'vh';
|
|
370
|
+
/**
|
|
371
|
+
* Rectangular obstacles to avoid (in world coordinates). When omitted /
|
|
372
|
+
* empty, the router takes the cheap L-route path.
|
|
373
|
+
*/
|
|
374
|
+
obstacles?: readonly AABB[];
|
|
375
|
+
/**
|
|
376
|
+
* Discretisation step for A*. Smaller values give smoother paths at the
|
|
377
|
+
* cost of more search nodes. Default: 10.
|
|
378
|
+
*/
|
|
379
|
+
gridSize?: number;
|
|
380
|
+
/**
|
|
381
|
+
* Inflate every obstacle by this margin (world units) before clipping.
|
|
382
|
+
* Useful to keep edges from hugging node borders. Default: 0.
|
|
383
|
+
*/
|
|
384
|
+
inflate?: number;
|
|
385
|
+
/**
|
|
386
|
+
* Hard cap on A* expansions. Acts as a safety net so a pathological
|
|
387
|
+
* search never hangs the renderer. Default: 5000.
|
|
388
|
+
*/
|
|
389
|
+
maxNodes?: number;
|
|
390
|
+
/**
|
|
391
|
+
* AABB of the source node. When provided, the route exits the side of
|
|
392
|
+
* the rectangle nearest to `end` with a short perpendicular stub. The
|
|
393
|
+
* raw `start` point is kept as the first polyline vertex so the edge
|
|
394
|
+
* still meets the anchor exactly.
|
|
395
|
+
*/
|
|
396
|
+
sourceBounds?: AABB;
|
|
397
|
+
/**
|
|
398
|
+
* AABB of the target node. When provided, the route enters the side of
|
|
399
|
+
* the rectangle nearest to `start` with a short perpendicular stub. The
|
|
400
|
+
* raw `end` point is kept as the last polyline vertex.
|
|
401
|
+
*/
|
|
402
|
+
targetBounds?: AABB;
|
|
403
|
+
/**
|
|
404
|
+
* Length (world units) of the perpendicular stub generated when
|
|
405
|
+
* `sourceBounds` / `targetBounds` is set. Defaults to `max(20, gridSize)`
|
|
406
|
+
* — matches React Flow's `offset = 20` for smoothstep edges.
|
|
407
|
+
*/
|
|
408
|
+
stubLength?: number;
|
|
409
|
+
/**
|
|
410
|
+
* Snap intermediate waypoints to a grid of this step. Defaults to the
|
|
411
|
+
* effective `gridSize`. Set to `0` to disable snapping in the no-
|
|
412
|
+
* obstacle path. A* already operates on the grid so this option only
|
|
413
|
+
* affects the L-route fallback.
|
|
414
|
+
*/
|
|
415
|
+
snap?: number;
|
|
416
|
+
/**
|
|
417
|
+
* Where the central bend sits on the connector when both
|
|
418
|
+
* `sourceBounds` and `targetBounds` are provided.
|
|
419
|
+
*
|
|
420
|
+
* - `0` → bend right at the source stub
|
|
421
|
+
* - `0.5` → midpoint between the two stubs (default — matches
|
|
422
|
+
* React Flow's `stepPosition`)
|
|
423
|
+
* - `1` → bend right at the target stub
|
|
424
|
+
*
|
|
425
|
+
* Ignored when bounds are missing (we fall back to the legacy L-route)
|
|
426
|
+
* or when A* is engaged (it picks bends from path-cost, not position).
|
|
427
|
+
*/
|
|
428
|
+
stepPosition?: number;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Compute an orthogonal (right-angled) path between two points.
|
|
432
|
+
*
|
|
433
|
+
* - With no `sourceBounds` / `targetBounds` and no obstacles: a single-
|
|
434
|
+
* bend L-route, identical to the v0 implementation.
|
|
435
|
+
* - With `sourceBounds` / `targetBounds`: the route exits/enters from
|
|
436
|
+
* the side of each rectangle nearest to the other endpoint.
|
|
437
|
+
* - With obstacles: 4-connected A* on a grid of step `gridSize`. Uses
|
|
438
|
+
* a turn penalty so straight runs are preferred. Falls back to the
|
|
439
|
+
* L-route if A* cannot find a path within `maxNodes` expansions or
|
|
440
|
+
* either endpoint is unreachable.
|
|
441
|
+
*
|
|
442
|
+
* The third argument also accepts the legacy bare-string form
|
|
443
|
+
* (`'auto' | 'hv' | 'vh'`) for backwards compatibility with the v0 API.
|
|
444
|
+
*/
|
|
445
|
+
declare function routeOrthogonal(start: Point, end: Point, options?: RouteOrthogonalOptions | 'auto' | 'hv' | 'vh'): readonly Point[];
|
|
446
|
+
/**
|
|
447
|
+
* Build an SVG path `d` attribute from a polyline.
|
|
448
|
+
*
|
|
449
|
+
* pointsToPath([[0,0], [10,0], [10,5]]) → "M 0 0 L 10 0 L 10 5"
|
|
450
|
+
*/
|
|
451
|
+
declare function pointsToPath(points: readonly Point[]): string;
|
|
452
|
+
/**
|
|
453
|
+
* Build a rounded-corner SVG path from an orthogonal polyline.
|
|
454
|
+
*
|
|
455
|
+
* Every interior bend (where two consecutive segments meet at a right
|
|
456
|
+
* angle) is replaced with a quadratic Bezier whose control point sits
|
|
457
|
+
* exactly at the corner — this produces the visual signature of
|
|
458
|
+
* "smooth-step" edges found in React Flow / draw.io / Lucidchart
|
|
459
|
+
* without changing the underlying polyline geometry.
|
|
460
|
+
*
|
|
461
|
+
* pointsToRoundedPath([[0,0], [10,0], [10,5]], 4)
|
|
462
|
+
* → "M 0 0 L 6 0 Q 10 0 10 4 L 10 5"
|
|
463
|
+
*
|
|
464
|
+
* `radius` is clamped to half of the shorter adjacent segment so the
|
|
465
|
+
* curves never overshoot. Passing `0` makes it identical to
|
|
466
|
+
* {@link pointsToPath}.
|
|
467
|
+
*/
|
|
468
|
+
declare function pointsToRoundedPath(points: readonly Point[], radius: number): string;
|
|
469
|
+
/** Side of an AABB that an endpoint exits / enters through. */
|
|
470
|
+
type Side = 'top' | 'right' | 'bottom' | 'left';
|
|
471
|
+
interface BezierPathOptions {
|
|
472
|
+
/** Source point — where the curve starts. */
|
|
473
|
+
source: Point;
|
|
474
|
+
/** Side of the source node the curve exits through. */
|
|
475
|
+
sourceSide: Side;
|
|
476
|
+
/** Target point — where the curve ends. */
|
|
477
|
+
target: Point;
|
|
478
|
+
/** Side of the target node the curve enters through. */
|
|
479
|
+
targetSide: Side;
|
|
480
|
+
/**
|
|
481
|
+
* Curve intensity. `0.25` is React Flow's default — strong enough
|
|
482
|
+
* to feel natural, soft enough not to loop. Range: `0..1`.
|
|
483
|
+
*/
|
|
484
|
+
curvature?: number;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Cubic-bezier SVG path between two endpoints, with control handles
|
|
488
|
+
* extending perpendicular to each side. Mirrors React Flow's
|
|
489
|
+
* `getBezierPath` algorithm.
|
|
490
|
+
*
|
|
491
|
+
* `sourceSide` / `targetSide` tell the function which face of each
|
|
492
|
+
* node the endpoint sits on so the curve always exits and enters
|
|
493
|
+
* perpendicular to the node outline — this is the difference between
|
|
494
|
+
* a "wavy" edge and a "deliberate" one.
|
|
495
|
+
*
|
|
496
|
+
* Use {@link nearestSide} to derive the side from an AABB + opposite
|
|
497
|
+
* point when the caller doesn't already know it.
|
|
498
|
+
*/
|
|
499
|
+
declare function getBezierPath(opts: BezierPathOptions): string;
|
|
500
|
+
/**
|
|
501
|
+
* Compute the intersection of the line from `box`'s centre to
|
|
502
|
+
* `opposite` with the rectangle's border. Returns the border point and
|
|
503
|
+
* the side it sits on.
|
|
504
|
+
*
|
|
505
|
+
* This is the "floating anchor" trick used by React Flow's
|
|
506
|
+
* FloatingEdges example and most graph viz libraries: instead of
|
|
507
|
+
* binding an endpoint to a fixed corner or midpoint, you bind it to
|
|
508
|
+
* the centre and let it slide along the perimeter following the
|
|
509
|
+
* direction to the other node. The result is dramatically cleaner on
|
|
510
|
+
* dense / unstructured graphs because every edge picks the closest
|
|
511
|
+
* face automatically as nodes are dragged around.
|
|
512
|
+
*/
|
|
513
|
+
declare function floatingAnchor(box: AABB, opposite: Point): {
|
|
514
|
+
point: Point;
|
|
515
|
+
side: Side;
|
|
516
|
+
};
|
|
517
|
+
/**
|
|
518
|
+
* Pick the side of `box` whose midpoint is closest to `target`. Same
|
|
519
|
+
* logic as the internal helper used by orthogonal routing — exposed
|
|
520
|
+
* publicly so consumers of `getBezierPath` don't need to re-implement
|
|
521
|
+
* it.
|
|
522
|
+
*/
|
|
523
|
+
declare function nearestSide(box: AABB, target: Point): Side;
|
|
524
|
+
/**
|
|
525
|
+
* Resolved edge endpoint geometry — output of {@link resolveEdgeEndpoint}.
|
|
526
|
+
*
|
|
527
|
+
* `point` is in world space, sitting on (or just outside of) the
|
|
528
|
+
* `box` border. `side` records which face the connector exits / enters
|
|
529
|
+
* so downstream consumers (bezier control handles, orthogonal stubs)
|
|
530
|
+
* can stay perpendicular.
|
|
531
|
+
*/
|
|
532
|
+
interface ResolvedEndpoint {
|
|
533
|
+
point: Point;
|
|
534
|
+
side: Side;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Compute the intersection of the segment between the centre of
|
|
538
|
+
* `intersectionBox` and `targetCenter` with the perimeter of
|
|
539
|
+
* `intersectionBox`.
|
|
540
|
+
*
|
|
541
|
+
* **Source / credit.** Direct port of the algorithm used by xyflow
|
|
542
|
+
* (React Flow) for FloatingEdges and by mxGraph / draw.io's
|
|
543
|
+
* `mxPerimeter.RectanglePerimeter`. The closed-form derivation is in
|
|
544
|
+
* https://math.stackexchange.com/questions/1724792 — it parametrises
|
|
545
|
+
* the rectangle as the manhattan-norm rhombus `|x| + |y| = 1` and
|
|
546
|
+
* solves for the unit intersection in one step, which is faster and
|
|
547
|
+
* numerically more stable than the angle-based formulation.
|
|
548
|
+
*
|
|
549
|
+
* We use this as the canonical endpoint resolver for every routing
|
|
550
|
+
* mode (`straight` / `orthogonal` / `bezier`). The visible benefit is
|
|
551
|
+
* stability: nothing in this function depends on user-supplied
|
|
552
|
+
* anchors — the side, the point, the angle of approach are all pure
|
|
553
|
+
* functions of the two rectangles' bounding boxes, so dragging a node
|
|
554
|
+
* around never produces the "wrong-side-of-the-box" / "edge cuts
|
|
555
|
+
* through the node body" artifacts that the anchor-honouring resolver
|
|
556
|
+
* used to leak.
|
|
557
|
+
*
|
|
558
|
+
* Returns the world-space intersection point. When `box` has zero
|
|
559
|
+
* area (degenerate node) the function returns its centre so callers
|
|
560
|
+
* can still draw a useful edge to it.
|
|
561
|
+
*/
|
|
562
|
+
declare function getNodeIntersection(intersectionBox: AABB, targetCenter: Point): Point;
|
|
563
|
+
/**
|
|
564
|
+
* Classify which face of `box` the `intersectionPoint` sits on.
|
|
565
|
+
* Companion to {@link getNodeIntersection} — together they replace the
|
|
566
|
+
* old anchor-driven endpoint resolver.
|
|
567
|
+
*
|
|
568
|
+
* The function rounds both the box position and the point to integer
|
|
569
|
+
* pixels (matches the xyflow heuristic) before comparing, which keeps
|
|
570
|
+
* floating-point noise from picking the wrong side when an
|
|
571
|
+
* intersection lands almost exactly on a corner.
|
|
572
|
+
*/
|
|
573
|
+
declare function getEdgePosition(box: AABB, intersectionPoint: Point): Side;
|
|
574
|
+
/**
|
|
575
|
+
* Single canonical edge endpoint resolver used by `<Diagram>` for
|
|
576
|
+
* every routing mode. Wraps {@link getNodeIntersection} +
|
|
577
|
+
* {@link getEdgePosition} into the `{ point, side }` shape the
|
|
578
|
+
* adapters expect, and applies a small outward `padding` so the
|
|
579
|
+
* arrowhead marker has room to sit between the path tip and the node
|
|
580
|
+
* border (matches React Flow / draw.io behaviour).
|
|
581
|
+
*
|
|
582
|
+
* **Important: anchor information is intentionally ignored here.**
|
|
583
|
+
* The `<Diagram>` API still accepts `from: { anchor: 'nw' }` etc. for
|
|
584
|
+
* back-compat and semantic intent, but the visual layer now derives
|
|
585
|
+
* the endpoint purely from bounding boxes. This is the floating-edges
|
|
586
|
+
* approach taken by every mainstream diagram library — it's what
|
|
587
|
+
* makes drag-around-the-canvas behaviour feel correct on randomly
|
|
588
|
+
* laid-out graphs.
|
|
589
|
+
*/
|
|
590
|
+
declare function resolveEdgeEndpoint(sourceBox: AABB, targetBox: AABB, padding?: number): ResolvedEndpoint;
|
|
591
|
+
/**
|
|
592
|
+
* Decide which face an endpoint sits on, combining two cues:
|
|
593
|
+
*
|
|
594
|
+
* 1. **Anchor position** — if `anchor` is already at one of `box`'s
|
|
595
|
+
* borders (within `tolerance`), the corresponding face is returned
|
|
596
|
+
* as-is. This lets consumers express intent ("exit from the right
|
|
597
|
+
* side") simply by placing the anchor on that border.
|
|
598
|
+
*
|
|
599
|
+
* 2. **Geometry fallback** — when the anchor is interior to `box`
|
|
600
|
+
* (or no border match) we fall back to {@link nearestSide} pointed
|
|
601
|
+
* at the opposite endpoint. This is the right answer for centre
|
|
602
|
+
* anchors and produces stable sides for big graphs where every
|
|
603
|
+
* pair of nodes has a different relative position.
|
|
604
|
+
*
|
|
605
|
+
* Returns the side along with a `confident` flag — `true` when the
|
|
606
|
+
* decision came from the anchor position (case 1), `false` when it
|
|
607
|
+
* fell back to geometry (case 2). Consumers that want to override the
|
|
608
|
+
* fallback in special cases can branch on the flag.
|
|
609
|
+
*
|
|
610
|
+
* Corner anchors (touching two borders simultaneously like `nw` / `ne`)
|
|
611
|
+
* are disambiguated by which face points more strongly at the opposite
|
|
612
|
+
* endpoint. **The disambiguation is also clamped to faces that face the
|
|
613
|
+
* other node** — a `ne` anchor on the source whose target sits to its
|
|
614
|
+
* left will pick `left` instead of `right`, because using `right` would
|
|
615
|
+
* route the edge AWAY from the target. This is the key fix for the
|
|
616
|
+
* "stub-then-180°-loop" artifact visible on randomly-laid-out graphs.
|
|
617
|
+
*/
|
|
618
|
+
declare function inferSide(box: AABB, anchor: Point, opposite: Point, tolerance?: number): {
|
|
619
|
+
side: Side;
|
|
620
|
+
confident: boolean;
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Oriented Bounding Box — a rectangle in world space described by its centre,
|
|
625
|
+
* half-extents along its local axes, and a rotation angle.
|
|
626
|
+
*
|
|
627
|
+
* Used when a node has a non-zero `transform.rot`: the AABB is no longer a
|
|
628
|
+
* tight fit, so callers that need a tight collision shape (selection
|
|
629
|
+
* outline, manipulator handles, etc.) should use the OBB instead.
|
|
630
|
+
*
|
|
631
|
+
* Conventions:
|
|
632
|
+
* - `center` is the centre of the rectangle in world coordinates.
|
|
633
|
+
* - `halfWidth` / `halfHeight` are the half-extents along the OBB's own
|
|
634
|
+
* local axes (already pre-multiplied by the world scale).
|
|
635
|
+
* - `angle` is the rotation of the OBB's local frame relative to world,
|
|
636
|
+
* in radians, positive counter-clockwise.
|
|
637
|
+
*
|
|
638
|
+
* For an axis-aligned node (`angle === 0`) the OBB and AABB coincide, modulo
|
|
639
|
+
* the (centre + half-extents) vs (top-left + size) representation.
|
|
640
|
+
*/
|
|
641
|
+
interface OBB {
|
|
642
|
+
readonly center: Point;
|
|
643
|
+
readonly halfWidth: number;
|
|
644
|
+
readonly halfHeight: number;
|
|
645
|
+
readonly angle: number;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Compute the four corners of an OBB in world space.
|
|
649
|
+
*
|
|
650
|
+
* Order is: top-left → top-right → bottom-right → bottom-left in OBB-local
|
|
651
|
+
* space, after applying the rotation. Useful for SVG `<polygon>` rendering
|
|
652
|
+
* and for AABB derivation.
|
|
653
|
+
*/
|
|
654
|
+
declare function obbCorners(obb: OBB): readonly [Point, Point, Point, Point];
|
|
655
|
+
/**
|
|
656
|
+
* Build the tightest axis-aligned bounding box that contains a given OBB.
|
|
657
|
+
*
|
|
658
|
+
* For a non-zero `angle` the resulting AABB is strictly larger (in area)
|
|
659
|
+
* than the OBB — it has to enclose all four rotated corners. This is the
|
|
660
|
+
* canonical "broad-phase" box used by the routing layer.
|
|
661
|
+
*/
|
|
662
|
+
declare function aabbFromOBB(obb: OBB): AABB;
|
|
663
|
+
/**
|
|
664
|
+
* Test whether a world-space point lies inside an OBB.
|
|
665
|
+
*
|
|
666
|
+
* Inverse-rotates the point into OBB-local space and checks the
|
|
667
|
+
* half-extents. O(1).
|
|
668
|
+
*/
|
|
669
|
+
declare function obbContainsPoint(obb: OBB, p: Point): boolean;
|
|
670
|
+
|
|
671
|
+
export { type AABB, type Anchor, type AnchorId, type AnchorPolicy, type BezierPathOptions, type EdgeEndpoint, type EdgeId, type EdgeInit, type GraphEdge, type GraphEngine, type GraphEngineOptions, type GraphNode, type GraphState, type NodeId, type NodeInit, type NodeTransform, type NodeUpdate, type OBB$1 as OBB, type Outline, type Point, type ResolvedEndpoint, type RouteOrthogonalOptions, type Side, aabbFromOBB, createGraph, floatingAnchor, getBezierPath, getEdgePosition, getNodeIntersection, inferSide, nearestSide, obbContainsPoint, obbCorners, pointsToPath, pointsToRoundedPath, resolveEdgeEndpoint, routeOrthogonal };
|