@xui/node-graph 2.0.0-alpha.11

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.
@@ -0,0 +1,1172 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { Signal, ValueProvider, WritableSignal, OnInit } from '@angular/core';
3
+ import { BooleanInput, NumberInput } from '@angular/cdk/coercion';
4
+ import { ClassValue } from 'clsx';
5
+ import * as _xui_node_graph from '@xui/node-graph';
6
+ import * as class_variance_authority_types from 'class-variance-authority/types';
7
+ import { VariantProps } from 'class-variance-authority';
8
+
9
+ /** A point in *graph space* — the untransformed coordinate system nodes live in. */
10
+ interface XuiGraphPoint {
11
+ x: number;
12
+ y: number;
13
+ }
14
+ /** A size in graph space. Screen pixels only match at zoom 1. */
15
+ interface XuiGraphSize {
16
+ width: number;
17
+ height: number;
18
+ }
19
+ interface XuiGraphRect extends XuiGraphPoint, XuiGraphSize {
20
+ }
21
+ /**
22
+ * The pan/zoom state of the canvas. `x`/`y` are a *screen-pixel* translation
23
+ * applied after scaling, so `screen = graph * zoom + pan`.
24
+ */
25
+ interface XuiGraphViewport extends XuiGraphPoint {
26
+ zoom: number;
27
+ }
28
+ /**
29
+ * Which way data flows through a port. `inout` is the bidirectional case that
30
+ * electrical schematics need — a terminal is neither a source nor a sink, and a
31
+ * wire may be drawn from either end.
32
+ */
33
+ type XuiGraphPortDirection = 'input' | 'output' | 'inout';
34
+ /** Which edge of the node the connector sits on. Drives layout and edge routing. */
35
+ type XuiGraphPortSide = 'left' | 'right' | 'top' | 'bottom';
36
+ /**
37
+ * The connector glyph. `circle` reads as a data socket (VFX/shader graphs),
38
+ * `pin` as a component lead (schematics), and the rest are available to encode a
39
+ * second dimension — by convention `diamond` marks an optional/nullable input.
40
+ */
41
+ type XuiGraphPortShape = 'circle' | 'square' | 'diamond' | 'triangle' | 'pin';
42
+ /** How ports are arranged inside a node's body. */
43
+ type XuiGraphPortLayout = 'stacked' | 'columns';
44
+ /**
45
+ * Edge geometry.
46
+ *
47
+ * - `bezier` — cubic curve leaving each port along its normal. The node-editor default.
48
+ * - `orthogonal` — axis-aligned segments with sharp corners. The schematic default.
49
+ * - `smoothstep` — orthogonal with rounded corners.
50
+ * - `straight` — a single line.
51
+ */
52
+ type XuiGraphRouting = 'bezier' | 'orthogonal' | 'smoothstep' | 'straight';
53
+ /** Decoration drawn at the target end of an edge. */
54
+ type XuiGraphMarker = 'none' | 'arrow' | 'arrow-closed' | 'dot';
55
+ /** The canvas backdrop. `grid` draws a minor/major rule pair, as schematic paper does. */
56
+ type XuiGraphBackground = 'none' | 'dots' | 'grid';
57
+ /** Addresses one connector: a port is only unique within its node. */
58
+ interface XuiGraphPortRef {
59
+ nodeId: string;
60
+ portId: string;
61
+ }
62
+ /** A connection the user has drawn but that the model does not contain yet. */
63
+ interface XuiGraphConnection {
64
+ source: XuiGraphPortRef;
65
+ target: XuiGraphPortRef;
66
+ }
67
+ /**
68
+ * A wire between two ports. Purely declarative and serialisable — geometry is
69
+ * derived from the live position of the two ports, never stored here.
70
+ */
71
+ interface XuiGraphEdge {
72
+ id: string;
73
+ /** Where the wire starts. For `inout` ports this is simply the end drawn first. */
74
+ source: XuiGraphPortRef;
75
+ target: XuiGraphPortRef;
76
+ /** Overrides the routing the graph is configured with, per edge. */
77
+ routing?: XuiGraphRouting;
78
+ /** Overrides the colour inherited from the source port's data type. */
79
+ color?: string;
80
+ /** Text rendered at the midpoint of the wire. */
81
+ label?: string;
82
+ /** Marching-ants animation, for showing that data or current is flowing. */
83
+ animated?: boolean;
84
+ dashed?: boolean;
85
+ /** Stroke width in graph units. Defaults to the graph's `edgeWidth`. */
86
+ width?: number;
87
+ marker?: XuiGraphMarker;
88
+ /** Arbitrary payload carried through selection and click events. */
89
+ data?: unknown;
90
+ }
91
+ /**
92
+ * A flattened, framework-free view of a port, handed to
93
+ * {@link XuiGraphConnectionValidator} so validation logic never touches
94
+ * component instances.
95
+ */
96
+ interface XuiGraphPortDescriptor extends XuiGraphPortRef {
97
+ direction: XuiGraphPortDirection;
98
+ side: XuiGraphPortSide;
99
+ dataType: string | undefined;
100
+ maxConnections: number;
101
+ disabled: boolean;
102
+ /** How many edges already terminate here. */
103
+ connections: number;
104
+ }
105
+ /** The two ends of a proposed connection, resolved against the live port registry. */
106
+ interface XuiGraphConnectionContext {
107
+ source: XuiGraphPortDescriptor;
108
+ target: XuiGraphPortDescriptor;
109
+ edges: readonly XuiGraphEdge[];
110
+ }
111
+ /**
112
+ * Returns whether a connection may be made. Runs *after* the built-in rules
113
+ * (direction, arity, duplicates, data type), so it can only narrow them.
114
+ */
115
+ type XuiGraphConnectionValidator = (connection: XuiGraphConnection, context: XuiGraphConnectionContext) => boolean;
116
+ /** Emitted when a wire is dropped on empty canvas, for "drag out to create a node". */
117
+ interface XuiGraphConnectionDrop {
118
+ source: XuiGraphPortRef;
119
+ /**
120
+ * The port the wire came from, described in full so a create menu can filter
121
+ * itself to what would actually connect without looking the port up again.
122
+ */
123
+ port: XuiGraphPortDescriptor;
124
+ /** Where the pointer was released, in graph space — where a new node belongs. */
125
+ position: XuiGraphPoint;
126
+ /**
127
+ * The same release point relative to the canvas element's top-left corner, so
128
+ * a popup can be placed without converting through the viewport.
129
+ */
130
+ surfacePosition: XuiGraphPoint;
131
+ }
132
+ /** One node's resting place after a drag. */
133
+ interface XuiGraphNodeMoved {
134
+ nodeId: string;
135
+ position: XuiGraphPoint;
136
+ /**
137
+ * Innermost group frame the node's centre now sits inside, ignoring the nodes
138
+ * that moved with it — the hook for "drag a node into a frame to join it".
139
+ *
140
+ * `undefined` when it came to rest outside every frame, and always `undefined`
141
+ * for a group drag, where no node changed hands.
142
+ */
143
+ group: string | undefined;
144
+ }
145
+ /** Emitted whenever a drag settles, with the final position of every moved node. */
146
+ interface XuiGraphNodeMove {
147
+ /** Whether the gesture started on a node (or node selection) or on a group frame. */
148
+ source: 'node' | 'group';
149
+ nodes: readonly XuiGraphNodeMoved[];
150
+ }
151
+ /**
152
+ * The contract {@link XuiGraphNode} fulfils for the store.
153
+ *
154
+ * Declared here rather than imported from the component so that the store, the
155
+ * routing helpers and the node itself do not form an import cycle.
156
+ *
157
+ * @internal
158
+ */
159
+ interface XuiGraphNodeHandle {
160
+ readonly nodeId: Signal<string>;
161
+ readonly position: Signal<XuiGraphPoint>;
162
+ readonly size: Signal<XuiGraphSize>;
163
+ readonly locked: Signal<boolean>;
164
+ /** Id of the group frame this node belongs to, if any. */
165
+ readonly group: Signal<string | undefined>;
166
+ readonly element: HTMLElement;
167
+ moveTo(point: XuiGraphPoint): void;
168
+ }
169
+ /**
170
+ * The contract {@link XuiGraphGroup} fulfils for the store.
171
+ *
172
+ * A group owns no geometry of its own — it is sized from its members — so the
173
+ * store keeps only the knobs it needs in order to compute that box itself.
174
+ *
175
+ * @internal
176
+ */
177
+ interface XuiGraphGroupHandle {
178
+ readonly groupId: Signal<string>;
179
+ /** Blank margin between the members' bounding box and the frame, in graph units. */
180
+ readonly padding: Signal<number>;
181
+ /** Extra room reserved above the members for the frame's title bar. */
182
+ readonly headerHeight: Signal<number>;
183
+ readonly locked: Signal<boolean>;
184
+ }
185
+ /**
186
+ * The contract {@link XuiGraphPort} fulfils for the store.
187
+ *
188
+ * @internal
189
+ */
190
+ interface XuiGraphPortHandle {
191
+ readonly key: string;
192
+ readonly nodeId: Signal<string>;
193
+ readonly portId: Signal<string>;
194
+ readonly direction: Signal<XuiGraphPortDirection>;
195
+ readonly resolvedSide: Signal<XuiGraphPortSide>;
196
+ readonly dataType: Signal<string | undefined>;
197
+ readonly resolvedMaxConnections: Signal<number>;
198
+ readonly disabled: Signal<boolean>;
199
+ readonly resolvedColor: Signal<string>;
200
+ /** Connector centre relative to the owning node's top-left corner, in graph units. */
201
+ readonly offset: Signal<XuiGraphPoint>;
202
+ }
203
+ /**
204
+ * Composite key for the port registry. A port id is only unique within its node.
205
+ *
206
+ * Length-prefixed so the pair can never be ambiguous: a node called `a` with a
207
+ * port called `b:c` and a node called `a:b` with a port called `c` must not
208
+ * collide, which any plain separator would allow.
209
+ */
210
+ declare function xuiGraphPortKey(nodeId: string, portId: string): string;
211
+
212
+ /**
213
+ * Presentation attached to a port *data type* — the string that decides which
214
+ * ports may legally be wired together.
215
+ *
216
+ * Colouring by data type rather than by port is the whole point: in a shader or
217
+ * VFX graph the colour is how you read compatibility at a glance, so it must be
218
+ * declared once per type and not repeated at every call site.
219
+ */
220
+ interface XuiGraphPortType {
221
+ /** Any CSS colour. Prefer a theme token so it follows light/dark. */
222
+ color: string;
223
+ /** Connector glyph for ports of this type. Falls back to the graph default. */
224
+ shape?: XuiGraphPortShape;
225
+ /** Human-readable name, used for the connector's tooltip. */
226
+ label?: string;
227
+ }
228
+ /**
229
+ * Application-wide defaults for the node graph.
230
+ *
231
+ * Provide it once at the app root to give every graph the same wiring style;
232
+ * individual inputs still override the configured value.
233
+ */
234
+ interface XuiNodeGraphConfig {
235
+ routing: XuiGraphRouting;
236
+ background: XuiGraphBackground;
237
+ marker: XuiGraphMarker;
238
+ /** Spacing of the background rule and of `snapToGrid`, in graph units. */
239
+ gridSize: number;
240
+ /** How many minor cells make one emphasised major cell in the `grid` backdrop. */
241
+ gridMajorEvery: number;
242
+ snapToGrid: boolean;
243
+ minZoom: number;
244
+ maxZoom: number;
245
+ /** Multiplier applied per zoom step, both for the wheel and the zoom buttons. */
246
+ zoomStep: number;
247
+ /** Straight run out of a port before an orthogonal wire may turn. */
248
+ stubLength: number;
249
+ /** Corner rounding for `smoothstep` routing. */
250
+ cornerRadius: number;
251
+ /** Bezier slack, as a fraction of the port separation. */
252
+ curvature: number;
253
+ edgeWidth: number;
254
+ /** Connector diameter in graph units. */
255
+ portSize: number;
256
+ portShape: XuiGraphPortShape;
257
+ /** Colour for a port that declares no data type, and for edges leaving one. */
258
+ portColor: string;
259
+ /** Data-type registry. Keys are matched against a port's `dataType`. */
260
+ portTypes: Record<string, XuiGraphPortType>;
261
+ /** Whether a wire may start and end on the same node. */
262
+ allowSelfConnection: boolean;
263
+ /**
264
+ * Require both ends of a wire to declare the same `dataType`. A port with no
265
+ * declared type is a wildcard and always passes.
266
+ */
267
+ enforcePortTypes: boolean;
268
+ }
269
+ declare function provideXuiNodeGraphConfig(config: Partial<XuiNodeGraphConfig>): ValueProvider;
270
+ declare function injectXuiNodeGraphConfig(): XuiNodeGraphConfig;
271
+ /**
272
+ * A ready-made data-type palette drawn from the categorical chart ramp, for
273
+ * graphs that need distinguishable typed ports without designing a palette.
274
+ *
275
+ * ```ts
276
+ * provideXuiNodeGraphConfig({ portTypes: { ...xuiGraphChartPortTypes(['float', 'vector', 'color']) } })
277
+ * ```
278
+ */
279
+ declare function xuiGraphChartPortTypes(types: readonly string[]): Record<string, XuiGraphPortType>;
280
+
281
+ /**
282
+ * The subset of the graph's inputs that the store, the nodes and the ports all
283
+ * need to see. Owned by {@link XuiNodeGraph}, which hands the store a signal of
284
+ * it rather than pushing values in from an effect.
285
+ *
286
+ * @internal
287
+ */
288
+ interface XuiGraphSettings {
289
+ routing: XuiGraphRouting;
290
+ gridSize: number;
291
+ snapToGrid: boolean;
292
+ minZoom: number;
293
+ maxZoom: number;
294
+ stubLength: number;
295
+ cornerRadius: number;
296
+ curvature: number;
297
+ edgeWidth: number;
298
+ portSize: number;
299
+ portShape: XuiGraphPortShape;
300
+ portColor: string;
301
+ portTypes: Record<string, XuiGraphPortType>;
302
+ allowSelfConnection: boolean;
303
+ enforcePortTypes: boolean;
304
+ isValidConnection: XuiGraphConnectionValidator | undefined;
305
+ locked: boolean;
306
+ }
307
+ /** A wire being dragged out of a port. @internal */
308
+ interface XuiGraphLinking {
309
+ /** The port the drag started at — not necessarily the source end. */
310
+ from: XuiGraphPortRef;
311
+ fromKey: string;
312
+ /**
313
+ * `true` when the drag began at an input, so the fixed end is the *target* of
314
+ * the connection that will be produced. Dragging backwards off an input is how
315
+ * every node editor lets you re-route an existing wire.
316
+ */
317
+ reversed: boolean;
318
+ /** Live pointer position in graph space. */
319
+ pointer: XuiGraphPoint;
320
+ /** Key of the port under the pointer, if it is a legal landing spot. */
321
+ hoverKey: string | null;
322
+ }
323
+ /** A drag in flight, with the group frames frozen as they stood when it began. @internal */
324
+ interface XuiGraphDragState {
325
+ source: 'node' | 'group';
326
+ /** Ids of the nodes actually moving — locked ones are left out. */
327
+ ids: ReadonlySet<string>;
328
+ frames: ReadonlyMap<string, XuiGraphRect>;
329
+ }
330
+ /** How a port should render while a wire is being dragged. */
331
+ type XuiGraphPortLinkState = 'idle' | 'source' | 'valid' | 'invalid';
332
+ /** Callbacks {@link XuiNodeGraph} registers so the store can raise its outputs. @internal */
333
+ interface XuiGraphStoreListeners {
334
+ connect(connection: XuiGraphConnection): void;
335
+ connectionDrop(drop: XuiGraphConnectionDrop): void;
336
+ nodeMove(move: XuiGraphNodeMove): void;
337
+ selectionChange(): void;
338
+ }
339
+ /**
340
+ * Reactive state shared by a graph and everything inside it.
341
+ *
342
+ * Provided by {@link XuiNodeGraph}, so every node, port and edge in one canvas
343
+ * sees the same instance and two graphs on a page never interfere.
344
+ */
345
+ declare class XuiNodeGraphStore {
346
+ private settingsSource;
347
+ private listeners;
348
+ private surface;
349
+ /** Node top-left positions captured at the start of a drag, keyed by node id. */
350
+ private dragOrigins;
351
+ private dragMoved;
352
+ /**
353
+ * The drag in flight, including every group frame as it stood when the drag
354
+ * began. Reactive, because those frozen frames are what the groups *render*
355
+ * while a node is being dragged, not merely what the drop is judged against.
356
+ *
357
+ * Both uses come from the same problem: a frame is sized from its members, so
358
+ * a live frame stretches to follow the very node being dragged out of it and
359
+ * then snaps back on release. Pinning the frame for the duration makes the
360
+ * node visibly leave a box that stays put — and makes the box you can see the
361
+ * box that decides where the node lands.
362
+ */
363
+ private readonly drag;
364
+ /**
365
+ * The graph binds its own `viewport` model and `edges` input here, so those
366
+ * stay the single source of truth and the store never has to mirror them
367
+ * through an effect that could fight the host for ownership.
368
+ */
369
+ private viewportSource;
370
+ private edgesSource;
371
+ private readonly ownViewport;
372
+ readonly viewport: Signal<XuiGraphViewport>;
373
+ readonly edges: Signal<readonly XuiGraphEdge[]>;
374
+ readonly selectedNodes: WritableSignal<ReadonlySet<string>>;
375
+ readonly selectedEdges: WritableSignal<ReadonlySet<string>>;
376
+ readonly linking: WritableSignal<XuiGraphLinking | null>;
377
+ /** Size of the visible canvas in screen pixels; kept current by a ResizeObserver. */
378
+ readonly surfaceSize: WritableSignal<{
379
+ width: number;
380
+ height: number;
381
+ }>;
382
+ private readonly nodeMap;
383
+ private readonly portMap;
384
+ private readonly groupMap;
385
+ readonly nodes: Signal<XuiGraphNodeHandle[]>;
386
+ readonly ports: Signal<XuiGraphPortHandle[]>;
387
+ readonly groups: Signal<XuiGraphGroupHandle[]>;
388
+ readonly settings: Signal<XuiGraphSettings>;
389
+ /** How many edges terminate at each port, keyed the same way as the registry. */
390
+ private readonly connectionCounts;
391
+ /**
392
+ * Wire the store to the graph that provides it. Called from the graph's
393
+ * constructor, before anything can read the bound signals.
394
+ *
395
+ * @internal
396
+ */
397
+ configure(bindings: {
398
+ settings: Signal<XuiGraphSettings>;
399
+ viewport: WritableSignal<XuiGraphViewport>;
400
+ edges: Signal<readonly XuiGraphEdge[]>;
401
+ listeners: XuiGraphStoreListeners;
402
+ }): void;
403
+ /** Writes through to whichever signal is bound as the viewport. */
404
+ private updateViewport;
405
+ /** @internal */
406
+ setSurface(element: HTMLElement): void;
407
+ /** @internal */
408
+ registerNode(node: XuiGraphNodeHandle): void;
409
+ /** @internal */
410
+ unregisterNode(node: XuiGraphNodeHandle): void;
411
+ /** @internal */
412
+ registerPort(port: XuiGraphPortHandle): void;
413
+ /** @internal */
414
+ unregisterPort(port: XuiGraphPortHandle): void;
415
+ /** @internal */
416
+ registerGroup(group: XuiGraphGroupHandle): void;
417
+ /** @internal */
418
+ unregisterGroup(group: XuiGraphGroupHandle): void;
419
+ node(nodeId: string): XuiGraphNodeHandle | undefined;
420
+ port(key: string): XuiGraphPortHandle | undefined;
421
+ /**
422
+ * Absolute position of a connector in graph space.
423
+ *
424
+ * Composed from the node's position and the port's node-relative offset rather
425
+ * than measured directly, so dragging a node re-routes its wires from a single
426
+ * signal write with no DOM reads.
427
+ */
428
+ portPoint(key: string): XuiGraphPoint | null;
429
+ descriptor(key: string): XuiGraphPortDescriptor | null;
430
+ connectionCount(key: string): number;
431
+ /** Client (viewport) coordinates to graph space. */
432
+ toGraphPoint(clientX: number, clientY: number): XuiGraphPoint;
433
+ /** Graph space to coordinates relative to the canvas element's top-left corner. */
434
+ toSurfacePoint(point: XuiGraphPoint): XuiGraphPoint;
435
+ /** Rounds to the nearest grid intersection when snapping is on. */
436
+ snap(point: XuiGraphPoint): XuiGraphPoint;
437
+ panBy(dx: number, dy: number): void;
438
+ /**
439
+ * Scale about a fixed point, given in surface coordinates.
440
+ *
441
+ * Holding that point still is what makes wheel-zoom feel anchored to the
442
+ * cursor instead of to the centre of the canvas.
443
+ */
444
+ zoomTo(zoom: number, anchor?: XuiGraphPoint): void;
445
+ zoomBy(factor: number, anchor?: XuiGraphPoint): void;
446
+ /** Bounding box of every node, or `null` while the graph is empty. */
447
+ contentBounds(): XuiGraphRect | null;
448
+ /** Frame every node, leaving `padding` screen pixels of margin. */
449
+ fitView(padding?: number): void;
450
+ /** Centre the viewport on a node without changing the zoom. */
451
+ centerOn(nodeId: string): void;
452
+ isNodeSelected(nodeId: string): boolean;
453
+ isEdgeSelected(edgeId: string): boolean;
454
+ selectNode(nodeId: string, additive?: boolean): void;
455
+ selectEdge(edgeId: string, additive?: boolean): void;
456
+ setSelection(nodeIds: Iterable<string>, edgeIds?: Iterable<string>): void;
457
+ clearSelection(): void;
458
+ selectAll(): void;
459
+ /** Select every node whose box intersects `rect`, in graph space. */
460
+ selectInRect(rect: XuiGraphRect, additive?: boolean): void;
461
+ /** The nodes that declare themselves members of a group, in registration order. */
462
+ nodesInGroup(groupId: string): XuiGraphNodeHandle[];
463
+ /**
464
+ * The frame a group draws: its members' bounding box, grown by the group's own
465
+ * padding and title bar. A group owns no geometry, so an empty one has no box
466
+ * at all and renders nothing.
467
+ *
468
+ */
469
+ groupBounds(groupId: string): XuiGraphRect | null;
470
+ /**
471
+ * The box a frame should be drawn at right now: its live bounds, except while a
472
+ * node drag is in flight, when it is pinned to where it stood as that drag
473
+ * began. A group being dragged is never pinned — it has to travel with the
474
+ * members it is carrying.
475
+ */
476
+ groupFrame(groupId: string): XuiGraphRect | null;
477
+ /**
478
+ * Frames that would take one of the nodes being dragged if it were released
479
+ * now. Groups render this as a highlight, so which frame is about to claim the
480
+ * node is visible before the button comes up rather than after.
481
+ */
482
+ readonly dropTargetGroups: Signal<ReadonlySet<string>>;
483
+ /**
484
+ * Innermost group frame containing a point. Smallest-first, so a frame nested
485
+ * inside another wins the node that lands in the overlap.
486
+ */
487
+ groupAt(point: XuiGraphPoint): string | undefined;
488
+ /**
489
+ * Capture the starting position of everything a drag will move.
490
+ *
491
+ * Deltas are applied to these captured origins rather than accumulated onto
492
+ * live positions, so snapping cannot compound rounding error over a long drag.
493
+ */
494
+ beginNodeDrag(nodeId: string): void;
495
+ /**
496
+ * Drag a whole group: every member moves together, and the frame follows them
497
+ * because it is sized from where they are.
498
+ *
499
+ * @internal
500
+ */
501
+ beginGroupDrag(groupId: string): void;
502
+ private captureDrag;
503
+ /** Offset every dragged node from its captured origin, in graph units. */
504
+ dragNodesBy(delta: XuiGraphPoint): void;
505
+ /** Ends the drag and raises `nodeMove`. Returns whether anything actually moved. */
506
+ endNodeDrag(): boolean;
507
+ /** @internal */
508
+ beginLink(port: XuiGraphPortHandle, pointer: XuiGraphPoint): void;
509
+ /** @internal */
510
+ moveLink(pointer: XuiGraphPoint): void;
511
+ /** @internal */
512
+ setLinkHover(key: string | null): void;
513
+ /**
514
+ * Finish a link. Dropping on a legal port raises `connect`; dropping anywhere
515
+ * else raises `connectionDrop` so the host can offer to create a node there.
516
+ *
517
+ * @internal
518
+ */
519
+ endLink(dropKey: string | null): void;
520
+ /** Resolve which end of a dragged wire is the source. */
521
+ private orient;
522
+ /**
523
+ * How a port should render while a wire is in flight — the affordance that
524
+ * tells you where a wire may land before you release it.
525
+ */
526
+ linkState(key: string): XuiGraphPortLinkState;
527
+ /**
528
+ * The full connection rule set: existence, arity, direction, duplicates and
529
+ * data type, then the host's own validator, which can only narrow the result.
530
+ */
531
+ canConnect(connection: XuiGraphConnection): boolean;
532
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<XuiNodeGraphStore, never>;
533
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<XuiNodeGraphStore>;
534
+ }
535
+ /**
536
+ * The store for the enclosing `<xui-node-graph>`.
537
+ *
538
+ * Use it to drive the canvas from outside — `fitView()`, `zoomBy()`,
539
+ * `setSelection()` — from a toolbar or a host component.
540
+ */
541
+ declare function injectXuiNodeGraphStore(): XuiNodeGraphStore;
542
+
543
+ /**
544
+ * Zoom and framing buttons for the enclosing canvas.
545
+ *
546
+ * ```html
547
+ * <xui-node-graph>
548
+ * …
549
+ * <xui-graph-controls class="bottom-4 left-4" />
550
+ * </xui-node-graph>
551
+ * ```
552
+ *
553
+ * Placed anywhere inside `<xui-node-graph>`; it is projected into the overlay
554
+ * layer, above the canvas and outside its transform, so it neither pans nor
555
+ * scales with the content.
556
+ */
557
+ declare class XuiGraphControls {
558
+ private readonly config;
559
+ protected readonly store: XuiNodeGraphStore;
560
+ protected readonly step: number;
561
+ protected readonly buttonClass: string;
562
+ readonly class: _angular_core.InputSignal<ClassValue>;
563
+ readonly showFit: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
564
+ readonly showZoomLevel: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
565
+ /** Lay the buttons out in a row rather than a column. */
566
+ readonly horizontal: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
567
+ protected readonly zoomPercent: _angular_core.Signal<number>;
568
+ protected readonly computedClass: _angular_core.Signal<string>;
569
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<XuiGraphControls, never>;
570
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<XuiGraphControls, "xui-graph-controls", never, { "class": { "alias": "class"; "required": false; "isSignal": true; }; "showFit": { "alias": "showFit"; "required": false; "isSignal": true; }; "showZoomLevel": { "alias": "showZoomLevel"; "required": false; "isSignal": true; }; "horizontal": { "alias": "horizontal"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
571
+ }
572
+
573
+ /**
574
+ * A frame around a set of nodes that moves them all together.
575
+ *
576
+ * ```html
577
+ * <xui-node-graph>
578
+ * <xui-graph-group groupId="filter" label="Filter stage" color="var(--color-chart-1)" />
579
+ *
580
+ * <xui-graph-node nodeId="lp" group="filter" [(position)]="a">…</xui-graph-node>
581
+ * <xui-graph-node nodeId="hp" group="filter" [(position)]="b">…</xui-graph-node>
582
+ * </xui-node-graph>
583
+ * ```
584
+ *
585
+ * Membership is declared by the nodes, not by nesting them inside the frame:
586
+ * a node keeps its own place in the template and its own `[(position)]`, and the
587
+ * frame is derived from wherever its members happen to be. That is what lets a
588
+ * group be added, removed or re-membered without moving anything in the DOM, and
589
+ * why dragging a member out of a frame is just an ordinary node drag.
590
+ *
591
+ * A group with no members has no box, so it renders nothing.
592
+ *
593
+ * To let a node join a frame by being dropped on it, read `group` off the graph's
594
+ * `nodeMove` event and reassign the node's own `group` input.
595
+ */
596
+ declare class XuiGraphGroup implements XuiGraphGroupHandle, OnInit {
597
+ private readonly store;
598
+ private readonly element;
599
+ private readonly dragOrigin;
600
+ /** Matches the `group` input on the nodes that belong to this frame. */
601
+ readonly groupId: _angular_core.InputSignal<string>;
602
+ readonly class: _angular_core.InputSignal<ClassValue>;
603
+ readonly label: _angular_core.InputSignal<string>;
604
+ /** Border and title colour; a tint of it fills the frame. Any CSS colour. */
605
+ readonly color: _angular_core.InputSignal<string>;
606
+ /** Blank margin between the members' bounding box and the frame. */
607
+ readonly padding: _angular_core.InputSignalWithTransform<number, NumberInput>;
608
+ readonly selectable: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
609
+ readonly draggable: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
610
+ /** Pins the frame and everything in it. */
611
+ readonly locked: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
612
+ /**
613
+ * `group` drags from anywhere on the frame, as a node editor's frames do.
614
+ * `header` restricts it to the title bar, which leaves the frame's interior
615
+ * free for panning and marquee selection.
616
+ */
617
+ readonly dragHandle: _angular_core.InputSignal<"group" | "header">;
618
+ /**
619
+ * @internal Part of {@link XuiGraphGroupHandle}.
620
+ *
621
+ * A frame only reserves room for a title bar when it has a label — an unlabelled
622
+ * frame is a plain box, and projected content rides along inside the labelled one.
623
+ */
624
+ readonly headerHeight: _angular_core.Signal<0 | 26>;
625
+ /**
626
+ * The frame, in graph space. `null` while the group has no members.
627
+ *
628
+ * Pinned to where it stood at the start of a node drag, so a member dragged out
629
+ * leaves a box that stays still instead of stretching it and snapping back.
630
+ */
631
+ readonly bounds: _angular_core.Signal<_xui_node_graph.XuiGraphRect | null>;
632
+ /** `true` while a dragged node hovers over this frame and would land in it. */
633
+ readonly dropTarget: _angular_core.Signal<boolean>;
634
+ readonly members: _angular_core.Signal<string[]>;
635
+ /** A frame reads as selected only when its whole membership is. */
636
+ readonly selected: _angular_core.Signal<boolean>;
637
+ protected readonly immovable: _angular_core.Signal<boolean>;
638
+ protected readonly transform: _angular_core.Signal<string | null>;
639
+ protected readonly cursor: _angular_core.Signal<"grabbing" | "grab" | null>;
640
+ protected readonly background: _angular_core.Signal<string>;
641
+ protected readonly computedClass: _angular_core.Signal<string>;
642
+ constructor();
643
+ ngOnInit(): void;
644
+ protected onPointerDown(event: PointerEvent): void;
645
+ protected onPointerMove(event: PointerEvent): void;
646
+ protected onPointerUp(event: PointerEvent): void;
647
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<XuiGraphGroup, never>;
648
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<XuiGraphGroup, "xui-graph-group", never, { "groupId": { "alias": "groupId"; "required": true; "isSignal": true; }; "class": { "alias": "class"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "color": { "alias": "color"; "required": false; "isSignal": true; }; "padding": { "alias": "padding"; "required": false; "isSignal": true; }; "selectable": { "alias": "selectable"; "required": false; "isSignal": true; }; "draggable": { "alias": "draggable"; "required": false; "isSignal": true; }; "locked": { "alias": "locked"; "required": false; "isSignal": true; }; "dragHandle": { "alias": "dragHandle"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
649
+ }
650
+
651
+ /**
652
+ * An overview of the whole graph with the current viewport drawn on it.
653
+ *
654
+ * ```html
655
+ * <xui-node-graph>
656
+ * …
657
+ * <xui-graph-minimap class="right-4 bottom-4" />
658
+ * </xui-node-graph>
659
+ * ```
660
+ *
661
+ * Click or drag inside it to move the view. Like the controls, it is projected
662
+ * into the overlay layer and so stays put while the canvas pans.
663
+ */
664
+ declare class XuiGraphMinimap {
665
+ private readonly store;
666
+ private readonly element;
667
+ private dragging;
668
+ readonly class: _angular_core.InputSignal<ClassValue>;
669
+ /** Blank margin around the content, in graph units. */
670
+ readonly padding: _angular_core.InputSignalWithTransform<number, NumberInput>;
671
+ protected readonly nodes: _angular_core.Signal<{
672
+ id: string;
673
+ x: number;
674
+ y: number;
675
+ width: number;
676
+ height: number;
677
+ selected: boolean;
678
+ }[]>;
679
+ /**
680
+ * The extent the minimap shows: everything drawn, unioned with wherever the
681
+ * viewport currently is, so panning off into empty space still tells you where
682
+ * you are instead of silently clamping.
683
+ */
684
+ private readonly extent;
685
+ protected readonly viewportRect: _angular_core.Signal<XuiGraphRect | null>;
686
+ protected readonly viewBox: _angular_core.Signal<string>;
687
+ /** Keep hairlines and corners visually constant however far the extent zooms out. */
688
+ private readonly unitScale;
689
+ protected readonly strokeWidth: _angular_core.Signal<number>;
690
+ protected readonly cornerRadius: _angular_core.Signal<number>;
691
+ protected readonly computedClass: _angular_core.Signal<string>;
692
+ protected onPointerDown(event: PointerEvent): void;
693
+ protected onPointerMove(event: PointerEvent): void;
694
+ protected onPointerUp(event: PointerEvent): void;
695
+ /**
696
+ * Map the pointer through the SVG's `preserveAspectRatio` fit and centre the
697
+ * canvas there. The fit letterboxes on one axis, so the scale has to be taken
698
+ * from the tighter of the two — using the element's own aspect ratio would put
699
+ * the target off by the size of the letterbox.
700
+ */
701
+ private centerOnPointer;
702
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<XuiGraphMinimap, never>;
703
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<XuiGraphMinimap, "xui-graph-minimap", never, { "class": { "alias": "class"; "required": false; "isSignal": true; }; "padding": { "alias": "padding"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
704
+ }
705
+
706
+ /**
707
+ * A connector on a node, together with the row of content that belongs to it.
708
+ *
709
+ * ```html
710
+ * <xui-graph-node nodeId="mul" label="Multiply" [(position)]="position">
711
+ * <xui-graph-port portId="a" direction="input" dataType="float" label="A" />
712
+ * <xui-graph-port portId="b" direction="input" dataType="float" label="B" />
713
+ * <xui-graph-port portId="out" direction="output" dataType="float" label="Result" />
714
+ * </xui-graph-node>
715
+ * ```
716
+ *
717
+ * The host element is the whole row, so anything projected into it — a numeric
718
+ * input, a colour swatch, a dropdown — sits inline with the connector, the way a
719
+ * shader or VFX editor lays out its sockets. The connector itself is pinned to
720
+ * the node's border, because that is what a wire attaches to.
721
+ *
722
+ * A hollow connector has nothing attached to it; a filled one is wired up.
723
+ */
724
+ declare class XuiGraphPort implements XuiGraphPortHandle, OnInit {
725
+ private readonly store;
726
+ private readonly node;
727
+ private readonly document;
728
+ private readonly connectorRef;
729
+ private readonly offsetState;
730
+ /**
731
+ * Registry identity. Empty until `ngOnInit`, because a required input is not
732
+ * readable during construction.
733
+ */
734
+ key: string;
735
+ /** Unique within the owning node — the node id supplies the rest of the identity. */
736
+ readonly portId: _angular_core.InputSignal<string>;
737
+ readonly class: _angular_core.InputSignal<ClassValue>;
738
+ /**
739
+ * Which way data flows. `inout` covers terminals that are neither source nor
740
+ * sink — a component lead in a schematic, where either end of a wire may be
741
+ * drawn first.
742
+ */
743
+ readonly direction: _angular_core.InputSignal<XuiGraphPortDirection>;
744
+ readonly label: _angular_core.InputSignal<string>;
745
+ /**
746
+ * The compatibility key. Two ports may only be wired together when their data
747
+ * types match, and the type also picks the connector's colour — see
748
+ * `provideXuiNodeGraphConfig({ portTypes })`. Leaving it unset makes the port a
749
+ * wildcard that connects to anything.
750
+ */
751
+ readonly dataType: _angular_core.InputSignal<string | undefined>;
752
+ /** Overrides the colour inherited from {@link dataType}. */
753
+ readonly color: _angular_core.InputSignal<string | undefined>;
754
+ readonly disabled: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
755
+ /**
756
+ * Which node edge the connector sits on. Leave unset to take it from
757
+ * {@link direction} — inputs on the left, outputs on the right.
758
+ */
759
+ readonly side: _angular_core.InputSignal<XuiGraphPortSide | undefined>;
760
+ readonly shape: _angular_core.InputSignal<XuiGraphPortShape | undefined>;
761
+ /**
762
+ * How many wires may terminate here. Defaults to 1 for an input — the usual
763
+ * rule that a value has exactly one producer — and unlimited for outputs and
764
+ * `inout` terminals, which fan out freely.
765
+ */
766
+ readonly maxConnections: _angular_core.InputSignalWithTransform<number, NumberInput>;
767
+ readonly nodeId: _angular_core.Signal<string>;
768
+ readonly resolvedSide: _angular_core.Signal<XuiGraphPortSide>;
769
+ readonly resolvedMaxConnections: _angular_core.Signal<number>;
770
+ /** Connector centre relative to the node's top-left corner, in graph units. */
771
+ readonly offset: _angular_core.Signal<XuiGraphPoint>;
772
+ readonly resolvedColor: _angular_core.Signal<string>;
773
+ protected readonly resolvedShape: _angular_core.Signal<XuiGraphPortShape>;
774
+ protected readonly horizontal: _angular_core.Signal<boolean>;
775
+ private readonly size;
776
+ protected readonly connectorWidth: _angular_core.Signal<number>;
777
+ protected readonly connectorHeight: _angular_core.Signal<number>;
778
+ protected readonly connected: _angular_core.Signal<boolean>;
779
+ protected readonly linkState: _angular_core.Signal<_xui_node_graph.XuiGraphPortLinkState>;
780
+ /** Position among the ports sharing this side; drives rails and column rows. */
781
+ private readonly indexOnSide;
782
+ private readonly countOnSide;
783
+ /**
784
+ * Top and bottom ports spread evenly along their edge instead of stacking,
785
+ * because a schematic reads those terminals as a rail, not as a list.
786
+ */
787
+ protected readonly railOffset: _angular_core.Signal<number | null>;
788
+ protected readonly order: _angular_core.Signal<1 | 0 | null>;
789
+ protected readonly gridColumn: _angular_core.Signal<2 | 1 | null>;
790
+ protected readonly gridRow: _angular_core.Signal<number | null>;
791
+ protected readonly tooltip: _angular_core.Signal<string | null>;
792
+ protected readonly ariaLabel: _angular_core.Signal<string>;
793
+ protected readonly computedClass: _angular_core.Signal<string>;
794
+ protected readonly connectorClass: _angular_core.Signal<string>;
795
+ constructor();
796
+ ngOnInit(): void;
797
+ private measure;
798
+ protected onPointerDown(event: PointerEvent): void;
799
+ protected onPointerMove(event: PointerEvent): void;
800
+ protected onPointerUp(event: PointerEvent): void;
801
+ /**
802
+ * Keyboard equivalent of dragging a wire: commit on one port, move focus,
803
+ * commit on the other. Without it the graph cannot be wired without a mouse.
804
+ */
805
+ protected onKeydown(event: KeyboardEvent): void;
806
+ /**
807
+ * Which port, if any, lies under a screen point. Used for drop targeting.
808
+ *
809
+ * The two halves of the identity are read back as separate attributes rather
810
+ * than as one composite key, so the registry is free to key ports however it
811
+ * likes without that encoding having to survive a DOM round-trip.
812
+ */
813
+ private portKeyAt;
814
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<XuiGraphPort, never>;
815
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<XuiGraphPort, "xui-graph-port", never, { "portId": { "alias": "portId"; "required": true; "isSignal": true; }; "class": { "alias": "class"; "required": false; "isSignal": true; }; "direction": { "alias": "direction"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "dataType": { "alias": "dataType"; "required": false; "isSignal": true; }; "color": { "alias": "color"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "side": { "alias": "side"; "required": false; "isSignal": true; }; "shape": { "alias": "shape"; "required": false; "isSignal": true; }; "maxConnections": { "alias": "maxConnections"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
816
+ }
817
+
818
+ declare const graphNodeVariants: (props?: ({
819
+ selected?: boolean | null | undefined;
820
+ locked?: boolean | null | undefined;
821
+ dimmed?: boolean | null | undefined;
822
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
823
+ type XuiGraphNodeVariants = VariantProps<typeof graphNodeVariants>;
824
+ /**
825
+ * A card on the canvas: a header, an optional body, and a set of ports.
826
+ *
827
+ * ```html
828
+ * <xui-graph-node nodeId="osc" label="Oscillator" [(position)]="position" accent="var(--color-chart-4)">
829
+ * <xui-graph-port portId="freq" direction="input" dataType="float" label="Frequency" />
830
+ * <xui-graph-port portId="out" direction="output" dataType="signal" label="Out" />
831
+ * </xui-graph-node>
832
+ * ```
833
+ *
834
+ * The node is deliberately unopinionated about its body — projected content
835
+ * renders below the ports, so the same component serves a shader node, a
836
+ * two-terminal resistor and a form-like settings card. What it does own is the
837
+ * geometry: its position drives every wire attached to it, and its border is
838
+ * where connectors sit.
839
+ *
840
+ * `position` is a model, so `[(position)]` keeps the host's own state authoritative
841
+ * — nothing is mutated behind the caller's back.
842
+ */
843
+ declare class XuiGraphNode implements XuiGraphNodeHandle, OnInit {
844
+ private readonly store;
845
+ private readonly sizeState;
846
+ private readonly layoutEpochState;
847
+ private dragOrigin;
848
+ /** Host element. Ports measure their connector offsets against this box. */
849
+ readonly element: HTMLElement;
850
+ /** Identifies the node to edges and to the selection. Must be stable and unique. */
851
+ readonly nodeId: _angular_core.InputSignal<string>;
852
+ readonly class: _angular_core.InputSignal<ClassValue>;
853
+ /** Top-left corner in graph space. Two-way bound so the host owns the value. */
854
+ readonly position: _angular_core.ModelSignal<XuiGraphPoint>;
855
+ readonly label: _angular_core.InputSignal<string>;
856
+ /** Fixed width in graph units. Leave unset to size to content. */
857
+ readonly width: _angular_core.InputSignalWithTransform<number | undefined, NumberInput>;
858
+ /**
859
+ * Accent colour for the header's top rule — the usual way a node editor shows
860
+ * which category a node belongs to. Any CSS colour; prefer a theme token.
861
+ */
862
+ readonly accent: _angular_core.InputSignal<string | undefined>;
863
+ /**
864
+ * Id of the `<xui-graph-group>` this node belongs to. Membership lives on the
865
+ * node rather than in the frame, so a node never has to move in the template to
866
+ * join or leave one.
867
+ */
868
+ readonly group: _angular_core.InputSignal<string | undefined>;
869
+ /** Arrangement of the port rows. */
870
+ readonly portLayout: _angular_core.InputSignal<XuiGraphPortLayout>;
871
+ /**
872
+ * Hides the body and pulls every connector onto the header, so a finished
873
+ * subtree can be folded away without breaking its wiring.
874
+ */
875
+ readonly collapsed: _angular_core.ModelSignal<boolean>;
876
+ readonly collapsible: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
877
+ readonly selectable: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
878
+ readonly draggable: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
879
+ /** Pins the node in place while leaving it selectable. */
880
+ readonly locked: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
881
+ /** Restrict dragging to the header, as a window manager does. */
882
+ readonly dragHandle: _angular_core.InputSignal<"node" | "header">;
883
+ /** Renders the node faded — for a disabled branch or a bypassed effect. */
884
+ readonly dimmed: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
885
+ /** Double-click on the header. The usual gesture for entering a subgraph. */
886
+ readonly activated: _angular_core.OutputEmitterRef<void>;
887
+ /** Measured border-box size in graph units, from a ResizeObserver. */
888
+ readonly size: _angular_core.Signal<XuiGraphSize>;
889
+ /** @internal Bumped on reflow so ports know to re-measure. */
890
+ readonly layoutEpoch: _angular_core.Signal<number>;
891
+ /** @internal Ports in DOM order, for side-relative indexing. */
892
+ readonly portSlots: _angular_core.Signal<readonly XuiGraphPort[]>;
893
+ readonly selected: _angular_core.Signal<boolean>;
894
+ protected readonly immovable: _angular_core.Signal<boolean>;
895
+ protected readonly transform: _angular_core.Signal<string>;
896
+ protected readonly dragCursor: _angular_core.Signal<"grab" | null>;
897
+ protected readonly computedClass: _angular_core.Signal<string>;
898
+ protected readonly headerClass: _angular_core.Signal<string>;
899
+ protected readonly portsClass: _angular_core.Signal<string>;
900
+ constructor();
901
+ ngOnInit(): void;
902
+ /** @internal Called by the store while dragging a selection. */
903
+ moveTo(point: XuiGraphPoint): void;
904
+ protected onPointerDown(event: PointerEvent): void;
905
+ protected onPointerMove(event: PointerEvent): void;
906
+ protected onPointerUp(event: PointerEvent): void;
907
+ protected onHeaderDoubleClick(event: MouseEvent): void;
908
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<XuiGraphNode, never>;
909
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<XuiGraphNode, "xui-graph-node", never, { "nodeId": { "alias": "nodeId"; "required": true; "isSignal": true; }; "class": { "alias": "class"; "required": false; "isSignal": true; }; "position": { "alias": "position"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "accent": { "alias": "accent"; "required": false; "isSignal": true; }; "group": { "alias": "group"; "required": false; "isSignal": true; }; "portLayout": { "alias": "portLayout"; "required": false; "isSignal": true; }; "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; "collapsible": { "alias": "collapsible"; "required": false; "isSignal": true; }; "selectable": { "alias": "selectable"; "required": false; "isSignal": true; }; "draggable": { "alias": "draggable"; "required": false; "isSignal": true; }; "locked": { "alias": "locked"; "required": false; "isSignal": true; }; "dragHandle": { "alias": "dragHandle"; "required": false; "isSignal": true; }; "dimmed": { "alias": "dimmed"; "required": false; "isSignal": true; }; }, { "position": "positionChange"; "collapsed": "collapsedChange"; "activated": "activated"; }, ["portSlots"], ["xui-graph-node-header", "xui-graph-node-actions", "xui-graph-node-preview", "xui-graph-port", "*"], true, never>;
910
+ }
911
+
912
+ /**
913
+ * Replaces a node's default header. Use it when the title needs an icon, a
914
+ * badge, or anything beyond the `label` input.
915
+ *
916
+ * ```html
917
+ * <xui-graph-node nodeId="n1" [(position)]="position">
918
+ * <xui-graph-node-header><ng-icon name="matBolt" /> Amplifier</xui-graph-node-header>
919
+ * </xui-graph-node>
920
+ * ```
921
+ */
922
+ declare class XuiGraphNodeHeader {
923
+ readonly class: _angular_core.InputSignal<ClassValue>;
924
+ protected readonly computedClass: _angular_core.Signal<string>;
925
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<XuiGraphNodeHeader, never>;
926
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<XuiGraphNodeHeader, "xui-graph-node-header", never, { "class": { "alias": "class"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
927
+ }
928
+ /** Trailing controls in a node header — a menu button, a toggle, a status dot. */
929
+ declare class XuiGraphNodeActions {
930
+ readonly class: _angular_core.InputSignal<ClassValue>;
931
+ protected readonly computedClass: _angular_core.Signal<string>;
932
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<XuiGraphNodeActions, never>;
933
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<XuiGraphNodeActions, "xui-graph-node-actions", never, { "class": { "alias": "class"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
934
+ }
935
+ /**
936
+ * A full-bleed block between the header and the ports — a thumbnail, a preview
937
+ * render, a waveform. Sits above the port rows, as it does in a VFX graph.
938
+ */
939
+ declare class XuiGraphNodePreview {
940
+ readonly class: _angular_core.InputSignal<ClassValue>;
941
+ protected readonly computedClass: _angular_core.Signal<string>;
942
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<XuiGraphNodePreview, never>;
943
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<XuiGraphNodePreview, "xui-graph-node-preview", never, { "class": { "alias": "class"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
944
+ }
945
+ /**
946
+ * Furniture layered over the canvas — a legend, a toolbar, a context menu.
947
+ *
948
+ * It sits outside the canvas transform, so it neither pans nor scales with the
949
+ * content, and positions itself against the graph's own box:
950
+ *
951
+ * ```html
952
+ * <xui-node-graph>
953
+ * …
954
+ * <xui-graph-overlay class="top-4 right-4">Read only</xui-graph-overlay>
955
+ * </xui-node-graph>
956
+ * ```
957
+ *
958
+ * Keep the element itself outside any `@if` or `@for`, and put the condition on
959
+ * its contents instead. A control-flow block is projected as one embedded view,
960
+ * so anything wrapped in one goes to the canvas layer whatever its own selector
961
+ * says — an overlay put there pans and zooms away with the content:
962
+ *
963
+ * ```html
964
+ * <xui-graph-overlay class="inset-0" [class.pointer-events-none]="!menu()">
965
+ * @if (menu(); as open) { … }
966
+ * </xui-graph-overlay>
967
+ * ```
968
+ */
969
+ declare class XuiGraphOverlay {
970
+ readonly class: _angular_core.InputSignal<ClassValue>;
971
+ protected readonly computedClass: _angular_core.Signal<string>;
972
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<XuiGraphOverlay, never>;
973
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<XuiGraphOverlay, "xui-graph-overlay", never, { "class": { "alias": "class"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
974
+ }
975
+ /**
976
+ * Marks a subtree inside a node as not draggable, so pointer gestures on it
977
+ * belong to the control rather than moving the node.
978
+ *
979
+ * Native form controls, buttons and links are exempt already; this is for
980
+ * anything custom — a slider, a curve editor, a colour wheel.
981
+ */
982
+ declare class XuiGraphNoDrag {
983
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<XuiGraphNoDrag, never>;
984
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<XuiGraphNoDrag, "[xuiGraphNoDrag]", never, {}, {}, never, never, true, never>;
985
+ }
986
+
987
+ /** Everything the template needs to draw one wire. */
988
+ interface RenderedEdge {
989
+ edge: XuiGraphEdge;
990
+ d: string;
991
+ color: string;
992
+ width: number;
993
+ selected: boolean;
994
+ dashArray: string | null;
995
+ animated: boolean;
996
+ markerId: string | null;
997
+ label: string | null;
998
+ labelPosition: XuiGraphPoint;
999
+ }
1000
+ /**
1001
+ * A pannable, zoomable canvas for node-based editors and schematics.
1002
+ *
1003
+ * ```html
1004
+ * <xui-node-graph class="h-[600px]" [edges]="edges()" routing="orthogonal" snapToGrid (connect)="add($event)">
1005
+ * @for (node of nodes(); track node.id) {
1006
+ * <xui-graph-node [nodeId]="node.id" [label]="node.label" [(position)]="node.position">
1007
+ * <xui-graph-port portId="in" direction="input" />
1008
+ * <xui-graph-port portId="out" direction="output" />
1009
+ * </xui-graph-node>
1010
+ * }
1011
+ * </xui-node-graph>
1012
+ * ```
1013
+ *
1014
+ * Nodes are ordinary projected content, so they keep Angular's template model —
1015
+ * `@for`, `@if`, your own components — instead of being described by a
1016
+ * configuration object. Edges are an input rather than content because a wire
1017
+ * has no content of its own: its whole geometry is derived from the two ports it
1018
+ * joins, which the graph already tracks.
1019
+ *
1020
+ * The graph never mutates `edges`. `connect` reports an approved connection and
1021
+ * the host decides what to add, which is what keeps undo/redo and persistence in
1022
+ * the host's hands.
1023
+ */
1024
+ declare class XuiNodeGraph {
1025
+ private readonly config;
1026
+ private readonly element;
1027
+ private readonly uid;
1028
+ /** Pointer position where the current gesture began, in client coordinates. */
1029
+ private gestureOrigin;
1030
+ private gesture;
1031
+ private gestureMoved;
1032
+ private readonly marqueeState;
1033
+ /** The store for this canvas. Use it to drive the view from a host component. */
1034
+ readonly store: XuiNodeGraphStore;
1035
+ readonly class: _angular_core.InputSignal<ClassValue>;
1036
+ /** The wires to draw. Never mutated — see `connect`. */
1037
+ readonly edges: _angular_core.InputSignal<readonly XuiGraphEdge[]>;
1038
+ /** Pan and zoom. Two-way bindable, so a host can save and restore the view. */
1039
+ readonly viewport: _angular_core.ModelSignal<XuiGraphViewport>;
1040
+ readonly routing: _angular_core.InputSignal<XuiGraphRouting>;
1041
+ readonly background: _angular_core.InputSignal<XuiGraphBackground>;
1042
+ readonly marker: _angular_core.InputSignal<XuiGraphMarker>;
1043
+ readonly gridSize: _angular_core.InputSignalWithTransform<number, NumberInput>;
1044
+ readonly snapToGrid: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
1045
+ readonly minZoom: _angular_core.InputSignalWithTransform<number, NumberInput>;
1046
+ readonly maxZoom: _angular_core.InputSignalWithTransform<number, NumberInput>;
1047
+ readonly edgeWidth: _angular_core.InputSignalWithTransform<number, NumberInput>;
1048
+ readonly portSize: _angular_core.InputSignalWithTransform<number, NumberInput>;
1049
+ readonly portShape: _angular_core.InputSignal<XuiGraphPortShape>;
1050
+ readonly portColor: _angular_core.InputSignal<string>;
1051
+ /** Data-type registry: colour, glyph and label per `dataType`. */
1052
+ readonly portTypes: _angular_core.InputSignal<Record<string, XuiGraphPortType>>;
1053
+ readonly allowSelfConnection: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
1054
+ readonly enforcePortTypes: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
1055
+ /** Last say on whether a connection may be made. Runs after the built-in rules. */
1056
+ readonly isValidConnection: _angular_core.InputSignal<XuiGraphConnectionValidator | undefined>;
1057
+ /** Read-only canvas: pan and zoom still work, editing does not. */
1058
+ readonly locked: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
1059
+ /** Dragging empty canvas pans. Turn it off to make dragging a marquee select instead. */
1060
+ readonly panOnDrag: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
1061
+ /**
1062
+ * `wheel` zooms on a bare wheel, as node editors do. `ctrl-wheel` reserves the
1063
+ * bare wheel for panning and zooms only with the modifier, as design tools do.
1064
+ */
1065
+ readonly zoomActivation: _angular_core.InputSignal<"wheel" | "ctrl-wheel">;
1066
+ readonly zoomOnScroll: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
1067
+ /** A connection that passed every rule. The host decides whether to add it. */
1068
+ readonly connect: _angular_core.OutputEmitterRef<XuiGraphConnection>;
1069
+ /** A wire dropped on empty canvas — the hook for "drag out to create a node". */
1070
+ readonly connectionDrop: _angular_core.OutputEmitterRef<XuiGraphConnectionDrop>;
1071
+ readonly edgeClick: _angular_core.OutputEmitterRef<XuiGraphEdge>;
1072
+ /** Fired once per drag, when the pointer is released. */
1073
+ readonly nodeMove: _angular_core.OutputEmitterRef<XuiGraphNodeMove>;
1074
+ readonly selectionChange: _angular_core.OutputEmitterRef<{
1075
+ nodes: string[];
1076
+ edges: string[];
1077
+ }>;
1078
+ /** Delete or Backspace over the canvas. The host performs the removal. */
1079
+ readonly deleteSelection: _angular_core.OutputEmitterRef<{
1080
+ nodes: string[];
1081
+ edges: string[];
1082
+ }>;
1083
+ readonly canvasContextMenu: _angular_core.OutputEmitterRef<{
1084
+ event: MouseEvent;
1085
+ position: XuiGraphPoint;
1086
+ }>;
1087
+ protected readonly Math: Math;
1088
+ protected readonly arrowId: string;
1089
+ protected readonly arrowClosedId: string;
1090
+ protected readonly dotId: string;
1091
+ protected readonly marqueeRect: _angular_core.Signal<{
1092
+ x: number;
1093
+ y: number;
1094
+ width: number;
1095
+ height: number;
1096
+ } | null>;
1097
+ private readonly settings;
1098
+ private readonly routeOptions;
1099
+ protected readonly canvasTransform: _angular_core.Signal<string>;
1100
+ protected readonly renderedEdges: _angular_core.Signal<RenderedEdge[]>;
1101
+ /** The wire that follows the pointer while a connection is being drawn. */
1102
+ protected readonly linkPreview: _angular_core.Signal<{
1103
+ d: string;
1104
+ color: string;
1105
+ } | null>;
1106
+ protected readonly backgroundImage: _angular_core.Signal<string | null>;
1107
+ protected readonly backgroundSize: _angular_core.Signal<string>;
1108
+ protected readonly backgroundPosition: _angular_core.Signal<string>;
1109
+ protected readonly computedClass: _angular_core.Signal<string>;
1110
+ constructor();
1111
+ /** Frame every node, leaving `padding` screen pixels of margin. */
1112
+ fitView(padding?: number): void;
1113
+ zoomIn(): void;
1114
+ zoomOut(): void;
1115
+ /** Return to 1:1 without moving the centre of the view. */
1116
+ resetZoom(): void;
1117
+ private markerUrl;
1118
+ protected onEdgePointerDown(edge: XuiGraphEdge, event: PointerEvent): void;
1119
+ /**
1120
+ * Presses that reach here landed on empty canvas — nodes, ports and edges all
1121
+ * stop propagation — so this only ever starts a pan or a marquee.
1122
+ */
1123
+ protected onPointerDown(event: PointerEvent): void;
1124
+ protected onPointerMove(event: PointerEvent): void;
1125
+ protected onPointerUp(event: PointerEvent): void;
1126
+ protected onWheel(event: WheelEvent): void;
1127
+ protected onKeydown(event: KeyboardEvent): void;
1128
+ protected onContextMenu(event: MouseEvent): void;
1129
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<XuiNodeGraph, never>;
1130
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<XuiNodeGraph, "xui-node-graph", never, { "class": { "alias": "class"; "required": false; "isSignal": true; }; "edges": { "alias": "edges"; "required": false; "isSignal": true; }; "viewport": { "alias": "viewport"; "required": false; "isSignal": true; }; "routing": { "alias": "routing"; "required": false; "isSignal": true; }; "background": { "alias": "background"; "required": false; "isSignal": true; }; "marker": { "alias": "marker"; "required": false; "isSignal": true; }; "gridSize": { "alias": "gridSize"; "required": false; "isSignal": true; }; "snapToGrid": { "alias": "snapToGrid"; "required": false; "isSignal": true; }; "minZoom": { "alias": "minZoom"; "required": false; "isSignal": true; }; "maxZoom": { "alias": "maxZoom"; "required": false; "isSignal": true; }; "edgeWidth": { "alias": "edgeWidth"; "required": false; "isSignal": true; }; "portSize": { "alias": "portSize"; "required": false; "isSignal": true; }; "portShape": { "alias": "portShape"; "required": false; "isSignal": true; }; "portColor": { "alias": "portColor"; "required": false; "isSignal": true; }; "portTypes": { "alias": "portTypes"; "required": false; "isSignal": true; }; "allowSelfConnection": { "alias": "allowSelfConnection"; "required": false; "isSignal": true; }; "enforcePortTypes": { "alias": "enforcePortTypes"; "required": false; "isSignal": true; }; "isValidConnection": { "alias": "isValidConnection"; "required": false; "isSignal": true; }; "locked": { "alias": "locked"; "required": false; "isSignal": true; }; "panOnDrag": { "alias": "panOnDrag"; "required": false; "isSignal": true; }; "zoomActivation": { "alias": "zoomActivation"; "required": false; "isSignal": true; }; "zoomOnScroll": { "alias": "zoomOnScroll"; "required": false; "isSignal": true; }; }, { "viewport": "viewportChange"; "connect": "connect"; "connectionDrop": "connectionDrop"; "edgeClick": "edgeClick"; "nodeMove": "nodeMove"; "selectionChange": "selectionChange"; "deleteSelection": "deleteSelection"; "canvasContextMenu": "canvasContextMenu"; }, never, ["*", "xui-graph-overlay, xui-graph-controls, xui-graph-minimap"], true, never>;
1131
+ }
1132
+
1133
+ declare function xuiGraphPortNormal(side: XuiGraphPortSide): XuiGraphPoint;
1134
+ interface XuiGraphRouteOptions {
1135
+ /**
1136
+ * How far a wire runs straight out of a port before it may turn, in graph
1137
+ * units. Keeps the first segment perpendicular to the node edge, which is what
1138
+ * makes a schematic readable.
1139
+ */
1140
+ stubLength: number;
1141
+ /** Corner rounding for `smoothstep`, in graph units. */
1142
+ cornerRadius: number;
1143
+ /**
1144
+ * Bezier control-point pull as a fraction of the port-to-port distance along
1145
+ * the port normal. `0` degenerates to a straight line; ~0.5 matches the slack
1146
+ * of a real cable.
1147
+ */
1148
+ curvature: number;
1149
+ }
1150
+ declare const XUI_GRAPH_DEFAULT_ROUTE_OPTIONS: XuiGraphRouteOptions;
1151
+ /**
1152
+ * Build the `d` attribute for a wire between two ports.
1153
+ *
1154
+ * The two sides matter as much as the two points: an edge leaving a right-hand
1155
+ * port must set off rightwards even when its target sits to the left, otherwise
1156
+ * it appears to pass through the node it came from.
1157
+ */
1158
+ declare function xuiGraphEdgePath(source: XuiGraphPoint, sourceSide: XuiGraphPortSide, target: XuiGraphPoint, targetSide: XuiGraphPortSide, routing: XuiGraphRouting, options?: XuiGraphRouteOptions): string;
1159
+ /**
1160
+ * The point at the middle of a route, used to place edge labels.
1161
+ *
1162
+ * For a bezier this is the curve's t=0.5 point; for the orthogonal families it
1163
+ * is the point half the total run from the source. Picking the longest segment
1164
+ * instead would flip the label between two legs of equal length as the wire
1165
+ * moved, and the halfway point is where a reader looks anyway.
1166
+ */
1167
+ declare function xuiGraphEdgeMidpoint(source: XuiGraphPoint, sourceSide: XuiGraphPortSide, target: XuiGraphPoint, targetSide: XuiGraphPortSide, routing: XuiGraphRouting, options?: XuiGraphRouteOptions): XuiGraphPoint;
1168
+
1169
+ declare const XuiNodeGraphImports: readonly [typeof XuiNodeGraph, typeof XuiGraphNode, typeof XuiGraphPort, typeof XuiGraphGroup, typeof XuiGraphControls, typeof XuiGraphMinimap, typeof XuiGraphNodeHeader, typeof XuiGraphNodeActions, typeof XuiGraphNodePreview, typeof XuiGraphNoDrag, typeof XuiGraphOverlay];
1170
+
1171
+ export { XUI_GRAPH_DEFAULT_ROUTE_OPTIONS, XuiGraphControls, XuiGraphGroup, XuiGraphMinimap, XuiGraphNoDrag, XuiGraphNode, XuiGraphNodeActions, XuiGraphNodeHeader, XuiGraphNodePreview, XuiGraphOverlay, XuiGraphPort, XuiNodeGraph, XuiNodeGraphImports, XuiNodeGraphStore, graphNodeVariants, injectXuiNodeGraphConfig, injectXuiNodeGraphStore, provideXuiNodeGraphConfig, xuiGraphChartPortTypes, xuiGraphEdgeMidpoint, xuiGraphEdgePath, xuiGraphPortKey, xuiGraphPortNormal };
1172
+ export type { XuiGraphBackground, XuiGraphConnection, XuiGraphConnectionContext, XuiGraphConnectionDrop, XuiGraphConnectionValidator, XuiGraphDragState, XuiGraphEdge, XuiGraphGroupHandle, XuiGraphLinking, XuiGraphMarker, XuiGraphNodeHandle, XuiGraphNodeMove, XuiGraphNodeMoved, XuiGraphNodeVariants, XuiGraphPoint, XuiGraphPortDescriptor, XuiGraphPortDirection, XuiGraphPortHandle, XuiGraphPortLayout, XuiGraphPortLinkState, XuiGraphPortRef, XuiGraphPortShape, XuiGraphPortSide, XuiGraphPortType, XuiGraphRect, XuiGraphRouteOptions, XuiGraphRouting, XuiGraphSettings, XuiGraphSize, XuiGraphStoreListeners, XuiGraphViewport, XuiNodeGraphConfig };