@lupinum/board-core 1.0.0-beta.2 → 1.0.0-beta.4
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 +24 -0
- package/dist/agent/AGENTS.md +63 -0
- package/dist/agent/manifest.json +320 -0
- package/dist/agent/pages/docs/build-features/connections.md +241 -0
- package/dist/agent/pages/docs/build-features/custom-node-renderers.md +219 -0
- package/dist/agent/pages/docs/build-features/groups-and-nesting.md +172 -0
- package/dist/agent/pages/docs/build-features/performance.md +76 -0
- package/dist/agent/pages/docs/build-features/read-only-and-command-guards.md +52 -0
- package/dist/agent/pages/docs/build-features/save-and-load.md +142 -0
- package/dist/agent/pages/docs/build-features/selection-and-keyboard.md +145 -0
- package/dist/agent/pages/docs/build-features/ssr-and-deterministic-state.md +67 -0
- package/dist/agent/pages/docs/build-features/theming.md +102 -0
- package/dist/agent/pages/docs/build-features/undo-and-redo.md +124 -0
- package/dist/agent/pages/docs/evaluate/design-decisions.md +44 -0
- package/dist/agent/pages/docs/evaluate/how-nuxt-board-works.md +41 -0
- package/dist/agent/pages/docs/evaluate/why-nuxt-board.md +61 -0
- package/dist/agent/pages/docs/project/contributing.md +101 -0
- package/dist/agent/pages/docs/project/support-and-security.md +40 -0
- package/dist/agent/pages/docs/reference/board-core-types.md +82 -0
- package/dist/agent/pages/docs/reference/board-core.md +261 -0
- package/dist/agent/pages/docs/reference/connections.md +531 -0
- package/dist/agent/pages/docs/reference/events-and-errors.md +158 -0
- package/dist/agent/pages/docs/reference/glossary.md +58 -0
- package/dist/agent/pages/docs/reference/history.md +193 -0
- package/dist/agent/pages/docs/reference/minimap.md +157 -0
- package/dist/agent/pages/docs/reference/nuxt-board.md +148 -0
- package/dist/agent/pages/docs/reference/package-overview.md +58 -0
- package/dist/agent/pages/docs/reference/vue-board.md +424 -0
- package/dist/agent/pages/docs/reference/vue-composables.md +293 -0
- package/dist/agent/pages/docs/solutions/mind-map.md +104 -0
- package/dist/agent/pages/docs/solutions/nuxt-application.md +47 -0
- package/dist/agent/pages/docs/solutions/planning-board.md +51 -0
- package/dist/agent/pages/docs/solutions/read-only-viewer.md +61 -0
- package/dist/agent/pages/docs/solutions/workflow-builder.md +124 -0
- package/dist/agent/pages/docs/start-building/add-connections-and-history.md +42 -0
- package/dist/agent/pages/docs/start-building/customize-your-first-node.md +38 -0
- package/dist/agent/pages/docs/start-building/installation.md +76 -0
- package/dist/agent/pages/docs/start-building/your-first-board.md +52 -0
- package/dist/agent/pages/docs/understand-the-system/camera-and-coordinates.md +22 -0
- package/dist/agent/pages/docs/understand-the-system/commands-and-transactions.md +31 -0
- package/dist/agent/pages/docs/understand-the-system/document-and-session-state.md +25 -0
- package/dist/agent/pages/docs/understand-the-system/nodes-and-hierarchy.md +24 -0
- package/dist/agent/pages/docs/understand-the-system/packages-and-plugins.md +29 -0
- package/dist/agent/pages/docs/understand-the-system/persistence-and-json-canvas.md +25 -0
- package/dist/agent/pages/docs/understand-the-system/rendering-and-interaction.md +22 -0
- package/dist/agent/pages/docs/understand-the-system/the-engine.md +28 -0
- package/dist/agent/pages/docs.md +18 -0
- package/dist/engine/transaction.d.ts +2 -2
- package/dist/index.js +18 -7
- package/dist/types.d.ts +2 -2
- package/package.json +3 -2
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "@lupinum/board-connections"
|
|
3
|
+
description: "Edge and connection management plugin with routing, anchors, and an SVG connection layer."
|
|
4
|
+
url: "https://nuxt-board.lupinum.com/docs/reference/connections"
|
|
5
|
+
route: "/docs/reference/connections"
|
|
6
|
+
locale: "en"
|
|
7
|
+
section: "Documentation"
|
|
8
|
+
collection: "docs"
|
|
9
|
+
source: "ginko-content"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# @lupinum/board-connections
|
|
13
|
+
|
|
14
|
+
> Edge and connection management plugin with routing, anchors, and an SVG connection layer.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
<code-group>
|
|
19
|
+
```bash [pnpm]
|
|
20
|
+
pnpm add @lupinum/board-connections @lupinum/board-core @lupinum/vue-board
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```bash [npm]
|
|
24
|
+
npm install @lupinum/board-connections @lupinum/board-core @lupinum/vue-board
|
|
25
|
+
```
|
|
26
|
+
</code-group>
|
|
27
|
+
|
|
28
|
+
## connectionsPlugin
|
|
29
|
+
|
|
30
|
+
Creates the connections plugin. Install it during engine creation via `createBoardEngine({ plugins })`.
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
const plugin = connectionsPlugin({ routing: 'bezier' })
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Options
|
|
37
|
+
|
|
38
|
+
| Name | Type | Default | Description |
|
|
39
|
+
| --- | --- | --- | --- |
|
|
40
|
+
| `routing` | `ConnectionRouting` | `'bezier'` | Default edge routing style. |
|
|
41
|
+
| `endpointMode` | `'auto' \| 'manual'` | `'auto'` | Whether UI-created endpoints adapt or lock to side anchors. |
|
|
42
|
+
| `defaultArrow` | `'none' \| 'start' \| 'end' \| 'both'` | `'end'` | Default arrowhead placement. |
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { createBoardEngine } from '@lupinum/board-core'
|
|
46
|
+
import { connectionsPlugin } from '@lupinum/board-connections'
|
|
47
|
+
|
|
48
|
+
const engine = createBoardEngine({
|
|
49
|
+
plugins: [connectionsPlugin({ routing: 'bezier' })],
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Plugin API
|
|
56
|
+
|
|
57
|
+
After installing the plugin, the connections API is available on `engine.plugins.connections`.
|
|
58
|
+
|
|
59
|
+
### createEdge
|
|
60
|
+
|
|
61
|
+
Creates a new edge between two nodes. Returns the created edge.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
createEdge<T>(input: {
|
|
65
|
+
id?: EdgeId
|
|
66
|
+
from: NodeId
|
|
67
|
+
to: NodeId
|
|
68
|
+
fromAnchor?: AnchorPosition
|
|
69
|
+
toAnchor?: AnchorPosition
|
|
70
|
+
fromEnd?: EdgeEnd
|
|
71
|
+
toEnd?: EdgeEnd
|
|
72
|
+
label?: string
|
|
73
|
+
color?: string
|
|
74
|
+
data: T
|
|
75
|
+
zIndex?: number
|
|
76
|
+
}): BoardEdge<T>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const edge = engine.plugins.connections.createEdge({
|
|
81
|
+
from: nodeA,
|
|
82
|
+
to: nodeB,
|
|
83
|
+
label: 'depends on',
|
|
84
|
+
color: '#0f766e',
|
|
85
|
+
data: {},
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### deleteEdge
|
|
90
|
+
|
|
91
|
+
Removes an edge by ID.
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
deleteEdge(id: EdgeId): void
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### updateEdge
|
|
98
|
+
|
|
99
|
+
Updates an existing edge in place. This is the API used by endpoint reconnect interactions.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
updateEdge<T>(id: EdgeId, patch: BoardEdgePatch<T>): BoardEdge<T>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
engine.plugins.connections.updateEdge(edge.id, {
|
|
107
|
+
to: anotherNodeId,
|
|
108
|
+
toAnchor: undefined,
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### getEdge
|
|
113
|
+
|
|
114
|
+
Returns a single edge by ID.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
getEdge(id: EdgeId): BoardEdge | undefined
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### getEdges
|
|
121
|
+
|
|
122
|
+
Returns all edges.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
getEdges(): BoardEdge[]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### getEdgesFrom
|
|
129
|
+
|
|
130
|
+
Returns all edges originating from a node.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
getEdgesFrom(id: NodeId): BoardEdge[]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### getEdgesTo
|
|
137
|
+
|
|
138
|
+
Returns all edges pointing to a node.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
getEdgesTo(id: NodeId): BoardEdge[]
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### getEdgesBetween
|
|
145
|
+
|
|
146
|
+
Returns all directed edges from `from` to `to`.
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
getEdgesBetween(from: NodeId, to: NodeId): BoardEdge[]
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## BoardConnectionLayer
|
|
155
|
+
|
|
156
|
+
A Vue component that renders edges as SVG paths inside a `BoardRoot`.
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { BoardConnectionLayer } from '@lupinum/board-connections/vue'
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Props
|
|
163
|
+
|
|
164
|
+
| Name | Type | Default | Description |
|
|
165
|
+
| --- | --- | --- | --- |
|
|
166
|
+
| `routing` | `ConnectionRouting \| undefined` | plugin default | Routing style for all edges. |
|
|
167
|
+
| `endpointMode` | `ConnectionEndpointMode \| undefined` | plugin default | UI endpoint behavior: auto side resolution or manual side locking. |
|
|
168
|
+
| `createNodeForConnection` | `(ctx: CreateNodeForConnectionContext) => BoardNode \| null` | `null` | Optional host policy for creating a node when a new connection is dropped on empty space. |
|
|
169
|
+
|
|
170
|
+
### Slots
|
|
171
|
+
|
|
172
|
+
#### edge
|
|
173
|
+
|
|
174
|
+
Custom edge rendering. If not provided, edges render as SVG `<path>` elements.
|
|
175
|
+
|
|
176
|
+
| Prop | Type | Description |
|
|
177
|
+
| --- | --- | --- |
|
|
178
|
+
| `edge` | `BoardEdge` | The edge data. |
|
|
179
|
+
| `source` | `ResolvedConnectionEndpoint` | Resolved source endpoint metadata. |
|
|
180
|
+
| `target` | `ResolvedConnectionEndpoint` | Resolved target endpoint metadata. |
|
|
181
|
+
| `route` | `ConnectionRoute` | Routed geometry, bounds, label point, and path string. |
|
|
182
|
+
|
|
183
|
+
```vue
|
|
184
|
+
<BoardConnectionLayer routing="smooth-step">
|
|
185
|
+
<template #edge="{ edge, route }">
|
|
186
|
+
<path :d="route.path" stroke="blue" stroke-width="2" fill="none" />
|
|
187
|
+
<text :x="route.labelPoint.x" :y="route.labelPoint.y">{{ edge.label }}</text>
|
|
188
|
+
</template>
|
|
189
|
+
</BoardConnectionLayer>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Usage
|
|
193
|
+
|
|
194
|
+
Render `BoardConnectionLayer` under `BoardRoot`. It teleports the SVG layer to the board root and applies the camera transform itself:
|
|
195
|
+
|
|
196
|
+
```vue
|
|
197
|
+
<BoardRoot :engine="engine">
|
|
198
|
+
<BoardConnectionLayer />
|
|
199
|
+
</BoardRoot>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`BoardConnectionLayer` handles both connection creation and reconnect. Hover a card edge to reveal a filled midpoint handle and drag it to another card to create a new edge. Hover or select an existing edge to reveal reconnect handles; dragging either end previews the route live and commits via `updateEdge()` when dropped on another node. Dropping a new connection on empty space cancels unless `createNodeForConnection` returns a node for the layer to connect.
|
|
203
|
+
|
|
204
|
+
By default, UI-created edges use `endpointMode: 'auto'`: they do not store `fromAnchor` or `toAnchor`, so each endpoint resolves to the best node side as nodes move. Dragging an existing endpoint onto a node side stores that endpoint as `{ side, offset }`, where `offset` is the exact point under the pointer. Use `endpointMode: 'manual'` when newly created edges should also lock to the dragged side offsets. Selected manual edges expose reset actions that clear one or both anchors back to auto.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Utility functions
|
|
209
|
+
|
|
210
|
+
### resolveAnchorPoint
|
|
211
|
+
|
|
212
|
+
Resolves an anchor position to a world-space point on a node.
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
function resolveAnchorPoint(
|
|
216
|
+
node: Pick<BoardNode, 'x' | 'y' | 'width' | 'height'>,
|
|
217
|
+
anchor: AnchorPosition,
|
|
218
|
+
): Point
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### resolveAutoAnchorSide
|
|
222
|
+
|
|
223
|
+
Chooses the best side for an auto-routed endpoint and uses a deadband to reduce flicker near diagonals. Auto-routed endpoints then attach at the center of that side.
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
function resolveAutoAnchorSide(
|
|
227
|
+
source: Pick<BoardNode, 'x' | 'y' | 'width' | 'height'>,
|
|
228
|
+
target: Pick<BoardNode, 'x' | 'y' | 'width' | 'height'>,
|
|
229
|
+
role: 'source' | 'target',
|
|
230
|
+
previousSide?: AnchorSide,
|
|
231
|
+
): AnchorSide
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### resolveConnectionEndpoint
|
|
235
|
+
|
|
236
|
+
Resolves one edge endpoint to a node side, normalized side offset, and world-space point. Explicit anchors are preserved; automatic endpoints choose the best side from the paired node.
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
function resolveConnectionEndpoint(
|
|
240
|
+
edge: BoardEdge,
|
|
241
|
+
node: Pick<BoardNode, 'id' | 'x' | 'y' | 'width' | 'height'>,
|
|
242
|
+
otherNode: Pick<BoardNode, 'id' | 'x' | 'y' | 'width' | 'height'>,
|
|
243
|
+
role: 'source' | 'target',
|
|
244
|
+
previousSide?: AnchorSide,
|
|
245
|
+
): ResolvedConnectionEndpoint
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### buildConnectionRoute
|
|
249
|
+
|
|
250
|
+
Builds a routed connection path from fully resolved source and target endpoints.
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
function buildConnectionRoute(input: {
|
|
254
|
+
source: ResolvedConnectionEndpoint
|
|
255
|
+
target: ResolvedConnectionEndpoint
|
|
256
|
+
routing?: ConnectionRouting
|
|
257
|
+
}): ConnectionRoute
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
| Routing | Description |
|
|
261
|
+
| --- | --- |
|
|
262
|
+
| `'bezier'` | Smooth cubic bezier curve (default). |
|
|
263
|
+
| `'smooth-step'` | Rounded orthogonal connector. |
|
|
264
|
+
| `'step'` | Right-angle stepped path. |
|
|
265
|
+
| `'straight'` | Direct straight line. |
|
|
266
|
+
| `'arc'` | Curved arc route. |
|
|
267
|
+
|
|
268
|
+
### buildArcRoute
|
|
269
|
+
|
|
270
|
+
Builds a curved arc route for hand-drawn or sketch-style edge rendering.
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
function buildArcRoute(
|
|
274
|
+
source: ResolvedConnectionEndpoint,
|
|
275
|
+
target: ResolvedConnectionEndpoint,
|
|
276
|
+
options?: ArcOptions,
|
|
277
|
+
): ConnectionRoute
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Edge color helpers
|
|
281
|
+
|
|
282
|
+
The package exports preset helpers for edge UI:
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
import {
|
|
286
|
+
EDGE_COLOR_PRESETS,
|
|
287
|
+
colorForPreset,
|
|
288
|
+
presetForColor,
|
|
289
|
+
resolvePresetColor,
|
|
290
|
+
} from '@lupinum/board-connections'
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Use these helpers when a toolbar stores edge colors as presets but the renderer needs a CSS color string.
|
|
294
|
+
|
|
295
|
+
### resolveFloatingEndpoint
|
|
296
|
+
|
|
297
|
+
Builds a temporary endpoint around a free pointer position for reconnect previews.
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
function resolveFloatingEndpoint(
|
|
301
|
+
point: Point,
|
|
302
|
+
otherPoint: Point,
|
|
303
|
+
role: 'source' | 'target',
|
|
304
|
+
previousSide?: AnchorSide,
|
|
305
|
+
): ResolvedConnectionEndpoint
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### resolveEdgeRenderState
|
|
309
|
+
|
|
310
|
+
Resolves source/target endpoints and the routed path in one step.
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
function resolveEdgeRenderState(
|
|
314
|
+
edge: BoardEdge,
|
|
315
|
+
sourceNode: Pick<BoardNode, 'id' | 'x' | 'y' | 'width' | 'height'>,
|
|
316
|
+
targetNode: Pick<BoardNode, 'id' | 'x' | 'y' | 'width' | 'height'>,
|
|
317
|
+
options?: {
|
|
318
|
+
routing?: ConnectionRouting
|
|
319
|
+
previousSourceSide?: AnchorSide
|
|
320
|
+
previousTargetSide?: AnchorSide
|
|
321
|
+
},
|
|
322
|
+
): {
|
|
323
|
+
source: ResolvedConnectionEndpoint
|
|
324
|
+
target: ResolvedConnectionEndpoint
|
|
325
|
+
route: ConnectionRoute
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### getVisibleEdges
|
|
330
|
+
|
|
331
|
+
Returns edges whose routed path bounds intersect the given viewport bounds.
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
function getVisibleEdges(
|
|
335
|
+
engine: BoardEngine,
|
|
336
|
+
bounds: Bounds,
|
|
337
|
+
routing?: ConnectionRouting,
|
|
338
|
+
): BoardEdge[]
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Types
|
|
344
|
+
|
|
345
|
+
### BoardEdge
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
interface BoardEdge<T = Record<string, unknown>> {
|
|
349
|
+
id: EdgeId
|
|
350
|
+
from: NodeId
|
|
351
|
+
to: NodeId
|
|
352
|
+
fromAnchor?: AnchorPosition // where the edge attaches on the source node
|
|
353
|
+
toAnchor?: AnchorPosition // where the edge attaches on the target node
|
|
354
|
+
fromEnd?: EdgeEnd
|
|
355
|
+
toEnd?: EdgeEnd
|
|
356
|
+
label?: string
|
|
357
|
+
color?: string
|
|
358
|
+
data: T // custom edge payload
|
|
359
|
+
zIndex: number
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### BoardEdgePatch
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
interface BoardEdgePatch<T = Record<string, unknown>> {
|
|
367
|
+
from?: NodeId
|
|
368
|
+
to?: NodeId
|
|
369
|
+
fromAnchor?: AnchorPosition
|
|
370
|
+
toAnchor?: AnchorPosition
|
|
371
|
+
fromEnd?: EdgeEnd
|
|
372
|
+
toEnd?: EdgeEnd
|
|
373
|
+
label?: string
|
|
374
|
+
color?: string
|
|
375
|
+
data?: T
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### AnchorPosition
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
interface AnchorPosition {
|
|
383
|
+
side: AnchorSide // 'top' | 'right' | 'bottom' | 'left'
|
|
384
|
+
offset: number // 0–1 position along the side (0.5 = center)
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
When `fromAnchor` / `toAnchor` are omitted, the connection layer resolves the side automatically and uses `offset = 0.5`.
|
|
389
|
+
|
|
390
|
+
Set an anchor to `undefined` with `updateEdge()` to reset that endpoint to automatic side resolution:
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
engine.plugins.connections.updateEdge(edgeId, {
|
|
394
|
+
fromAnchor: undefined,
|
|
395
|
+
})
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### AnchorSide
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
type AnchorSide = 'top' | 'right' | 'bottom' | 'left'
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### ConnectionRouting
|
|
405
|
+
|
|
406
|
+
```ts
|
|
407
|
+
type ConnectionRouting = 'bezier' | 'smooth-step' | 'step' | 'straight' | 'arc'
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### ConnectionEndpointMode
|
|
411
|
+
|
|
412
|
+
```ts
|
|
413
|
+
type ConnectionEndpointMode = 'auto' | 'manual'
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### EdgeEnd
|
|
417
|
+
|
|
418
|
+
```ts
|
|
419
|
+
type EdgeEnd = 'none' | 'arrow'
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### ConnectionConfig
|
|
423
|
+
|
|
424
|
+
Resolved defaults installed by `connectionsPlugin()` and returned by `engine.plugins.connections.getConfig()`.
|
|
425
|
+
|
|
426
|
+
```ts
|
|
427
|
+
interface ConnectionConfig {
|
|
428
|
+
routing: ConnectionRouting
|
|
429
|
+
endpointMode: ConnectionEndpointMode
|
|
430
|
+
defaultArrow: 'none' | 'start' | 'end' | 'both'
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### CreateNodeForConnectionContext
|
|
435
|
+
|
|
436
|
+
Context passed to `BoardConnectionLayer` when a host opts into creating a node from an empty connection drop.
|
|
437
|
+
|
|
438
|
+
```ts
|
|
439
|
+
interface CreateNodeForConnectionContext {
|
|
440
|
+
sourceNodeId: NodeId
|
|
441
|
+
sourceSide: AnchorSide
|
|
442
|
+
pointerWorld: Point
|
|
443
|
+
candidateAnchor: AnchorPosition | null
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### ResolvedConnectionEndpoint
|
|
448
|
+
|
|
449
|
+
```ts
|
|
450
|
+
interface ResolvedConnectionEndpoint {
|
|
451
|
+
nodeId: NodeId
|
|
452
|
+
node: Pick<BoardNode, 'id' | 'x' | 'y' | 'width' | 'height'>
|
|
453
|
+
side: AnchorSide
|
|
454
|
+
offset: number
|
|
455
|
+
point: Point
|
|
456
|
+
kind: 'explicit' | 'auto'
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### ConnectionRoute
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
interface ConnectionRoute {
|
|
464
|
+
routing: ConnectionRouting
|
|
465
|
+
path: string
|
|
466
|
+
labelPoint: Point
|
|
467
|
+
bounds: Bounds
|
|
468
|
+
waypoints: Point[]
|
|
469
|
+
segments: ConnectionRouteSegment[]
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### ConnectionRouteSegment
|
|
474
|
+
|
|
475
|
+
A routed connection is made of path segments used by custom edge renderers.
|
|
476
|
+
|
|
477
|
+
```ts
|
|
478
|
+
type ConnectionRouteSegment =
|
|
479
|
+
| { type: 'line'; from: Point; to: Point }
|
|
480
|
+
| {
|
|
481
|
+
type: 'cubic'
|
|
482
|
+
from: Point
|
|
483
|
+
control1: Point
|
|
484
|
+
control2: Point
|
|
485
|
+
to: Point
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### ConnectionsApi
|
|
490
|
+
|
|
491
|
+
Installed by `connectionsPlugin()` on `engine.plugins.connections`.
|
|
492
|
+
|
|
493
|
+
```ts
|
|
494
|
+
interface ConnectionsApi {
|
|
495
|
+
createEdge<T>(input: CreateEdgeInput<T>): BoardEdge<T>
|
|
496
|
+
updateEdge<T>(id: EdgeId, patch: BoardEdgePatch<T>): BoardEdge<T>
|
|
497
|
+
deleteEdge(id: EdgeId): void
|
|
498
|
+
getEdge(id: EdgeId): BoardEdge | undefined
|
|
499
|
+
getEdges(): BoardEdge[]
|
|
500
|
+
getEdgesFrom(id: NodeId): BoardEdge[]
|
|
501
|
+
getEdgesTo(id: NodeId): BoardEdge[]
|
|
502
|
+
getEdgesBetween(from: NodeId, to: NodeId): BoardEdge[]
|
|
503
|
+
getConfig(): ConnectionConfig
|
|
504
|
+
}
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## Events
|
|
510
|
+
|
|
511
|
+
The connections plugin adds these events to `BoardEventMap`:
|
|
512
|
+
|
|
513
|
+
| Event | Handler | Description |
|
|
514
|
+
| --- | --- | --- |
|
|
515
|
+
| `edge:created` | `(edge: BoardEdge) => void` | An edge was created. |
|
|
516
|
+
| `edge:updated` | `(edge: BoardEdge, prev: BoardEdge) => void` | An edge was updated or reconnected. |
|
|
517
|
+
| `edge:deleted` | `(edgeId: EdgeId) => void` | An edge was removed. |
|
|
518
|
+
|
|
519
|
+
```ts
|
|
520
|
+
engine.on('edge:created', (edge) => {
|
|
521
|
+
console.log('New edge:', edge.from, '->', edge.to)
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
engine.on('edge:updated', (edge, prev) => {
|
|
525
|
+
console.log('Moved edge:', prev.id, prev.to, '->', edge.to)
|
|
526
|
+
})
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
Edges are automatically deleted when either endpoint node is deleted.
|
|
530
|
+
|
|
531
|
+
---
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Events and subscriptions"
|
|
3
|
+
description: "React to state changes with the event system and subscribables."
|
|
4
|
+
url: "https://nuxt-board.lupinum.com/docs/reference/events-and-errors"
|
|
5
|
+
route: "/docs/reference/events-and-errors"
|
|
6
|
+
locale: "en"
|
|
7
|
+
section: "Documentation"
|
|
8
|
+
collection: "docs"
|
|
9
|
+
source: "ginko-content"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Events and subscriptions
|
|
13
|
+
|
|
14
|
+
> React to state changes with the event system and subscribables.
|
|
15
|
+
|
|
16
|
+
Events tell you what happened. Subscribables tell you what state looks like now. Use events to react to discrete actions, use subscribables to keep your UI in sync.
|
|
17
|
+
|
|
18
|
+
> Component omitted: `event-logger`.
|
|
19
|
+
> This page contains an interactive or site-specific block that has no agent markdown serializer yet.
|
|
20
|
+
|
|
21
|
+
## Events
|
|
22
|
+
|
|
23
|
+
Register a listener with `engine.on()`. It returns an unsubscribe function:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
const unsub = engine.on('node:created', (node) => {
|
|
27
|
+
console.log('Created', node.id)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// Later...
|
|
31
|
+
unsub()
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Event catalog
|
|
35
|
+
|
|
36
|
+
<accordion>
|
|
37
|
+
<accordion-item title="Lifecycle">
|
|
38
|
+
- `destroy` — engine destroyed
|
|
39
|
+
</accordion-item>
|
|
40
|
+
|
|
41
|
+
<accordion-item title="Node events">
|
|
42
|
+
- `node:created(node)` — a node was added
|
|
43
|
+
- `node:updated(node, previousNode)` — a node's data or geometry changed
|
|
44
|
+
- `node:deleted(nodeId, previousNode)` — a node was removed
|
|
45
|
+
- `node:moved(node, delta)` — a node's position changed
|
|
46
|
+
- `node:resized(node, previousBounds)` — a node's dimensions changed
|
|
47
|
+
</accordion-item>
|
|
48
|
+
|
|
49
|
+
<accordion-item title="Selection and interaction">
|
|
50
|
+
- `selection:change(selectedIds, previousIds)` — selection changed
|
|
51
|
+
- `interaction:start(state)` — an interaction began (drag, resize, pan, etc.)
|
|
52
|
+
- `interaction:update(state)` — ongoing interaction position updated (high-frequency)
|
|
53
|
+
- `interaction:end(state)` — interaction finished
|
|
54
|
+
</accordion-item>
|
|
55
|
+
|
|
56
|
+
<accordion-item title="Camera">
|
|
57
|
+
- `camera:change(camera, previousCamera)` — camera position or zoom changed (high-frequency during pan/zoom)
|
|
58
|
+
- `viewport:change(size, previousSize)` — the measured board root size changed
|
|
59
|
+
</accordion-item>
|
|
60
|
+
|
|
61
|
+
<accordion-item title="Guarded commands">
|
|
62
|
+
- `command:before(commandName, args, metadata)` — a validated command has succeeded and is publishing its lifecycle boundary
|
|
63
|
+
- `command:after(commandName, args, duration, metadata)` — a command finished executing
|
|
64
|
+
- `command:blocked(commandName, args, metadata)` — a command was blocked by a guard
|
|
65
|
+
- `validation:failed(failure)` — commit validation rejected an invalid candidate state
|
|
66
|
+
</accordion-item>
|
|
67
|
+
|
|
68
|
+
<accordion-item title="Feature events">
|
|
69
|
+
Internal features add their own namespaced events:
|
|
70
|
+
|
|
71
|
+
- **Connections:** `edge:created(edge)`, `edge:updated(edge, prev)`, `edge:deleted(edgeId)`
|
|
72
|
+
- **History:** `history:push(entry)`, `history:undo(entry | null)`, `history:redo(entry | null)`, `history:clear()`
|
|
73
|
+
</accordion-item>
|
|
74
|
+
</accordion>
|
|
75
|
+
|
|
76
|
+
## Subscribables
|
|
77
|
+
|
|
78
|
+
Six subscribables give you reactive access to engine state. Each fires independently — a camera change does not trigger a `$nodes` notification:
|
|
79
|
+
|
|
80
|
+
<tabs>
|
|
81
|
+
<tab label="$nodes" icon="i-lucide-square">
|
|
82
|
+
```ts
|
|
83
|
+
engine.$nodes.subscribe((nodes) => {
|
|
84
|
+
console.log('Node count:', nodes.size)
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Fires when any node is created, updated, moved, resized, or deleted.
|
|
89
|
+
</tab>
|
|
90
|
+
|
|
91
|
+
<tab label="$camera" icon="i-lucide-move">
|
|
92
|
+
```ts
|
|
93
|
+
engine.$camera.subscribe((camera) => {
|
|
94
|
+
console.log(`Zoom: ${camera.z.toFixed(2)}`)
|
|
95
|
+
})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Fires on every pan and zoom change. High-frequency during interaction.
|
|
99
|
+
</tab>
|
|
100
|
+
|
|
101
|
+
<tab label="$selection" icon="i-lucide-check-square">
|
|
102
|
+
```ts
|
|
103
|
+
engine.$selection.subscribe((ids) => {
|
|
104
|
+
console.log('Selected:', ids.size)
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Fires when the selection set changes.
|
|
109
|
+
</tab>
|
|
110
|
+
|
|
111
|
+
<tab label="$grid" icon="i-lucide-grid-3x3">
|
|
112
|
+
```ts
|
|
113
|
+
engine.$grid.subscribe((grid) => {
|
|
114
|
+
console.log('Grid size:', grid.size)
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Fires when snapping or grid settings change.
|
|
119
|
+
</tab>
|
|
120
|
+
|
|
121
|
+
<tab label="$interaction" icon="i-lucide-hand">
|
|
122
|
+
```ts
|
|
123
|
+
engine.$interaction.subscribe((state) => {
|
|
124
|
+
console.log('Mode:', state.mode)
|
|
125
|
+
})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Fires when the interaction state machine transitions.
|
|
129
|
+
</tab>
|
|
130
|
+
|
|
131
|
+
<tab label="$snapGuides" icon="i-lucide-ruler">
|
|
132
|
+
```ts
|
|
133
|
+
engine.$snapGuides.subscribe((guides) => {
|
|
134
|
+
console.log('Active guides:', guides.length)
|
|
135
|
+
})
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Fires when snap alignment guides appear or disappear.
|
|
139
|
+
</tab>
|
|
140
|
+
</tabs>
|
|
141
|
+
|
|
142
|
+
<tip>
|
|
143
|
+
In Vue, prefer the composables (`useBoardCamera()`, `useBoardNodes()`, `useBoardSelection()`) over raw subscribables. They handle reactivity automatically.
|
|
144
|
+
</tip>
|
|
145
|
+
|
|
146
|
+
<note>
|
|
147
|
+
Inside `engine.batch()`, entity events and subscribable notifications publish only after the outer batch commits. Command lifecycle events describe the outer `batch` boundary. A blocked command and a validation failure remain observable failure telemetry.
|
|
148
|
+
</note>
|
|
149
|
+
|
|
150
|
+
## Error taxonomy
|
|
151
|
+
|
|
152
|
+
- `BoardInputError`: malformed or invalid boundary input.
|
|
153
|
+
- `BoardNotFoundError`: requested entity does not exist.
|
|
154
|
+
- `BoardConflictError`: an ID, plugin name, or other unique identity conflicts.
|
|
155
|
+
- `CommandBlockedError`: a command guard rejected the operation.
|
|
156
|
+
- `BoardDestroyedError`: a command targeted a destroyed engine.
|
|
157
|
+
|
|
158
|
+
Listener, subscriber, and finalized commit-effect errors are reported through `onUnhandledError` after commit. They do not reverse committed state.
|