@ixfx/components 0.16.5 → 0.17.1
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/bundle/index.d.ts +4093 -1746
- package/bundle/index.js +1083 -226
- package/bundle/rolldown-runtime-CAoDHAWh.js +1 -0
- package/dist/ac-text.d.ts +11 -14
- package/dist/ac-text.js +8 -9
- package/dist/{ac-token-qEs3FsQv.d.ts → ac-token-CTBa93hF.d.ts} +2 -6
- package/dist/ac-token.d.ts +1 -1
- package/dist/ac-token.js +8 -9
- package/dist/{button-ikByJJPN.js → button-BOX1dfyd.js} +5 -7
- package/dist/{button-N92DKoWb.d.ts → button-DyJyyCsb.d.ts} +0 -4
- package/dist/button.d.ts +1 -1
- package/dist/button.js +1 -2
- package/dist/checkbox.d.ts +42 -9
- package/dist/checkbox.js +4 -5
- package/dist/{checked-select-CBUidG7n.js → checked-select-BmwqpgGr.js} +8 -10
- package/dist/{checked-select-CglRukbP.d.ts → checked-select-CNv5zNvR.d.ts} +0 -4
- package/dist/checked-select.d.ts +33 -1
- package/dist/checked-select.js +1 -2
- package/dist/{colour-picker-DwAa6RDh.js → colour-picker-D8g-bTSF.js} +6 -9
- package/dist/colour-picker.d.ts +1 -1
- package/dist/colour-picker.js +1 -2
- package/dist/{crumb-navigation-D3ndPgFg.js → crumb-navigation-D7dAC0H-.js} +11 -13
- package/dist/{crumb-navigation-BbqfmkYs.d.ts → crumb-navigation-Tm4fnsvN.d.ts} +6 -10
- package/dist/crumbs.d.ts +1 -1
- package/dist/crumbs.js +1 -2
- package/dist/{data-controller-DOlmqNKb.js → data-controller-q-rXRMzJ.js} +1 -2
- package/dist/data-display.d.ts +5 -8
- package/dist/data-display.js +3 -4
- package/dist/{data-grid-8TsiF2ES.js → data-grid-BcO3SMac.js} +20 -23
- package/dist/{data-grid-CslzF16R.d.ts → data-grid-vasJtjFP.d.ts} +9 -15
- package/dist/data-grid.d.ts +2 -2
- package/dist/data-grid.js +1 -2
- package/dist/{data-provider-35TyWclX.js → data-provider-C6ZNAcaT.js} +1 -2
- package/dist/{data-provider-BAn_-oR1.d.ts → data-provider-Cq_ylpET.d.ts} +0 -2
- package/dist/date-input.d.ts +9 -12
- package/dist/date-input.js +3 -4
- package/dist/{decorate-VRK8rslU.js → decorate-Dxs8sFX8.js} +2 -4
- package/dist/{defaults-B4ePs4LB.js → defaults-_6ELsqLb.js} +2 -3
- package/dist/{detail-list-n1MbNbhF.d.ts → detail-list-E9WdMVbE.d.ts} +10 -16
- package/dist/{detail-list-w3GXKO2k.js → detail-list-vPVwE1CP.js} +17 -20
- package/dist/detail-list.d.ts +5 -5
- package/dist/detail-list.js +1 -2
- package/dist/{drag-ghost-DtSDvjri.js → drag-ghost-DNWu_RyN.js} +1 -2
- package/dist/{editable-label-rzXkjf1H.d.ts → editable-label-CiHCDlhC.d.ts} +0 -2
- package/dist/editable-label.d.ts +31 -1
- package/dist/editable-label.js +3 -4
- package/dist/{fallbacks-DQ_5OsmR.js → fallbacks-BPjjBoED.js} +1 -2
- package/dist/{flat-list-D_4y3wgn.d.ts → flat-list-CfutGqKY.d.ts} +8 -12
- package/dist/flat-list.d.ts +1 -1
- package/dist/flat-list.js +10 -11
- package/dist/{form-CY0nibZ5.js → form-Cxx2sO8_.js} +5 -9
- package/dist/{form-BGZK3jZd.d.ts → form-raTQNAWF.d.ts} +0 -4
- package/dist/form.d.ts +1 -1
- package/dist/form.js +1 -2
- package/dist/{grid-list-Cz2tKcNZ.js → grid-list-D2hinM64.js} +275 -41
- package/dist/{grid-list-CqYS28NY.d.ts → grid-list-OCdjuhng.d.ts} +60 -24
- package/dist/grid-list.d.ts +5 -5
- package/dist/grid-list.js +1 -2
- package/dist/{grouped-item-lister-CS4yInuw.d.ts → grouped-item-lister-1MlvJORP.d.ts} +2 -6
- package/dist/grouped-item-lister.d.ts +1 -1
- package/dist/grouped-item-lister.js +3 -4
- package/dist/{grouping-CUKgmkMm.d.ts → grouping-OoQ8WaKc.d.ts} +0 -2
- package/dist/{grouping-RYPnHkrS.js → grouping-kENdqzSV.js} +1 -2
- package/dist/{handle-DAXs7kbJ.js → handle-BVUWv97X.js} +1 -2
- package/dist/{hex-editor-Caq8vNQk.d.ts → hex-editor-BCBKRU9D.d.ts} +0 -4
- package/dist/{hex-editor-DiYpgIKs.js → hex-editor-DephDhuu.js} +12 -13
- package/dist/hex.d.ts +1 -1
- package/dist/hex.js +1 -2
- package/dist/{highlight-B-eQrhhb.js → highlight-noLHFSKZ.js} +1 -2
- package/dist/{icon-CDGprSdE.d.ts → icon-Bhz0c6Yf.d.ts} +0 -2
- package/dist/{icon-1gpwtPx8.js → icon-DvTQuylG.js} +4 -5
- package/dist/icons.d.ts +2 -2
- package/dist/icons.js +4 -5
- package/dist/{incr-search-Dc6gYpJG.js → incr-search-C5114RxZ.js} +14 -26
- package/dist/incr-search.d.ts +1 -1
- package/dist/incr-search.js +2 -2
- package/dist/{index-ZaA90u09.d.ts → index-0smh4WCH.d.ts} +3 -21
- package/dist/{index-VAs9akt4.d.ts → index-88ra6YHP.d.ts} +8 -22
- package/dist/{index-UvnoZUAo.d.ts → index-B7odjrlg.d.ts} +4 -8
- package/dist/{index-Bm9a9S3_.d.ts → index-CRLTVAyt.d.ts} +4 -30
- package/dist/{index-9ttcdwlL.d.ts → index-Cdcek1Oj.d.ts} +1 -3
- package/dist/index-Cn0qsz9a.d.ts +1632 -0
- package/dist/{index-YCDugjdu.d.ts → index-CzhYJJsx.d.ts} +7 -17
- package/dist/{index-CYndvTWH.d.ts → index-D6N4Lzek.d.ts} +1 -5
- package/dist/{index-CoqtMSd9.d.ts → index-DN6kC6an.d.ts} +1 -3
- package/dist/{index-DV3iaUBW2.d.ts → index-DrYQBts2.d.ts} +0 -14
- package/dist/{index-o-CWDt11.d.ts → index-DzocgUef.d.ts} +0 -4
- package/dist/{index-DN2BWctP.d.ts → index-PIrgX1bP.d.ts} +1 -7
- package/dist/index.d.ts +364 -491
- package/dist/index.js +139 -207
- package/dist/interaction-8Kbbf6xN.js +3 -0
- package/dist/{item-appearance-BjcMyauw.js → item-appearance-FRO3TRFI.js} +1 -2
- package/dist/{keyboard-B5xD7Fz-.js → keyboard-DMwjIYh7.js} +0 -1
- package/dist/{labelled-input-base-BFa4xlYV.js → labelled-input-base-KUhD02dh.js} +3 -5
- package/dist/{labelled-input-base-DiTme1JO.d.ts → labelled-input-base-jyr_SR6t.d.ts} +0 -2
- package/dist/labelled-radial-input.d.ts +4 -7
- package/dist/labelled-radial-input.js +5 -6
- package/dist/labelled-range-input.d.ts +1 -1
- package/dist/labelled-range-input.js +3 -4
- package/dist/{labelled-slider-input-FdCRi6st.js → labelled-slider-input-CfRKd5kC.js} +9 -11
- package/dist/labelled-slider-input.d.ts +1 -1
- package/dist/labelled-slider-input.js +1 -2
- package/dist/led.d.ts +3 -6
- package/dist/led.js +2 -3
- package/dist/list-group-header.d.ts +3 -6
- package/dist/list-group-header.js +3 -4
- package/dist/{list-native-drag-controller-CcXoS0YX.js → list-native-drag-controller-ByftBooJ.js} +5 -9
- package/dist/{list-native-drag-controller-D_UrXipS.d.ts → list-native-drag-controller-CgXKdmjr.d.ts} +1 -3
- package/dist/{list-selection-controller-D4iIrvJH.js → list-selection-controller-Dkh3m3hX.js} +3 -5
- package/dist/{list-selection-types-Dnamh2Lu.d.ts → list-selection-types-CKTt7Rrw.d.ts} +0 -2
- package/dist/{list-viewport-types-BRZsLcvp.d.ts → list-viewport-types-BJ8c6qur.d.ts} +0 -2
- package/dist/{menu-BX1FHxY8.js → menu-C3DhTjHx.js} +17 -27
- package/dist/{menu-item-oxU1VKiJ.js → menu-item-BSxhlWnW.js} +30 -15
- package/dist/{menu-item-bF6narIz.d.ts → menu-item-DJhaWy9z.d.ts} +10 -7
- package/dist/menu.d.ts +2 -2
- package/dist/menu.js +2 -2
- package/dist/{miller-BrmBywNZ.js → miller-DqDEaXqT.js} +15 -19
- package/dist/miller.d.ts +3 -3
- package/dist/miller.js +1 -2
- package/dist/narrowed-text.d.ts +1 -1
- package/dist/narrowed-text.js +8 -9
- package/dist/node-graph-DqEGv5QR.js +5369 -0
- package/dist/node-graph.d.ts +2 -0
- package/dist/node-graph.js +1 -0
- package/dist/{notification-pill-DyZydXoZ.d.ts → notification-pill-BiHEJk-o.d.ts} +0 -4
- package/dist/notifier.d.ts +1 -1
- package/dist/notifier.js +4 -5
- package/dist/{pan-text-CoYmt2qd.d.ts → pan-text-DKe2zinH.d.ts} +0 -2
- package/dist/{pan-text-CqtvjPyK.js → pan-text-DrJYKQKN.js} +3 -4
- package/dist/panel.d.ts +11 -14
- package/dist/panel.js +3 -4
- package/dist/{partial-keys-Ck6CiprB.js → partial-keys-D4l_ede_.js} +2 -3
- package/dist/{pending-BGidHppL.js → pending-Afl4LT64.js} +1 -2
- package/dist/plots.d.ts +1 -1
- package/dist/plots.js +1 -2
- package/dist/polar-pad.d.ts +5 -7
- package/dist/polar-pad.js +6 -7
- package/dist/{popup-D80sj2QC.js → popup-D8IxN1Eq.js} +1 -2
- package/dist/{prominence-DlFShic3.js → prominence-BOOQVWF-.js} +1 -2
- package/dist/{radial-input-KMVltQfe.js → radial-input-Cxys4TOG.js} +7 -9
- package/dist/{radial-input-DU6t2hYO.d.ts → radial-input-DNCSwRGF.d.ts} +1 -5
- package/dist/radial-input.d.ts +10 -1
- package/dist/radial-input.js +1 -2
- package/dist/range-input.d.ts +4 -6
- package/dist/range-input.js +5 -6
- package/dist/range.d.ts +4 -6
- package/dist/range.js +6 -7
- package/dist/{registry-BEtb8_wB.js → registry-DcF0fO7l.js} +1 -2
- package/dist/rolldown-runtime-D3KUYPsf.js +12 -0
- package/dist/{scroll-fade-Xx28D7mH.js → scroll-fade-DH8OXSqL.js} +1 -2
- package/dist/{scroll-hover-guard-Cjw6179Q.js → scroll-hover-guard-BGAjtyXT.js} +1 -2
- package/dist/select-horiz.d.ts +7 -9
- package/dist/select-horiz.js +8 -9
- package/dist/{slider-input-ByHdotzp.js → slider-input-C3mbUBZi.js} +6 -8
- package/dist/{slider-input-BxURAAmD.d.ts → slider-input-DHLGJKWF.d.ts} +1 -5
- package/dist/slider-input.d.ts +34 -1
- package/dist/slider-input.js +1 -2
- package/dist/snackbar.d.ts +5 -8
- package/dist/snackbar.js +4 -5
- package/dist/snap-container.d.ts +8 -11
- package/dist/snap-container.js +2 -3
- package/dist/{split-layout-DTY4PJRp.js → split-layout-BMN_s6d7.js} +10 -15
- package/dist/{split-layout-CophkNeU.d.ts → split-layout-HytcG1Bn.d.ts} +2 -8
- package/dist/split-layout.d.ts +1 -1
- package/dist/split-layout.js +1 -2
- package/dist/sunburst-tree.d.ts +1 -1
- package/dist/sunburst-tree.js +1 -2
- package/dist/{sunbursttree-list-DQF6DcsS.js → sunbursttree-list-C-FuZB5z.js} +23 -32
- package/dist/{sunbursttree-list-Bk20giTF.d.ts → sunbursttree-list-CsrmVSB8.d.ts} +5 -11
- package/dist/swipe.d.ts +7 -9
- package/dist/swipe.js +5 -6
- package/dist/{tab-list-Df371rqw.d.ts → tab-list-BXMRYJeR.d.ts} +1 -5
- package/dist/{tab-list-R_bGbDvO.js → tab-list-CFJy3hVm.js} +6 -15
- package/dist/tabs.d.ts +1 -1
- package/dist/tabs.js +1 -2
- package/dist/{tickled-controller-D-HZymaZ.js → tickled-controller-BVhGKzjZ.js} +1 -2
- package/dist/{tickled-controller-DodxD3eQ.d.ts → tickled-controller-DSPoG551.d.ts} +0 -2
- package/dist/{tickled-styles-BrAHEj1q.js → tickled-styles-pnieYweS.js} +1 -2
- package/dist/{timeline-BP1Ux2EV.js → timeline-Br5JIeLI.js} +9 -15
- package/dist/timeline.d.ts +1 -1
- package/dist/timeline.js +1 -2
- package/dist/{titlebar-Tqu-VvMU.d.ts → titlebar-BtLAF2ws.d.ts} +1 -3
- package/dist/titlebar.d.ts +1 -1
- package/dist/titlebar.js +4 -5
- package/dist/{tooltip-CJt3WLmN.js → tooltip-B7t_u6EY.js} +1 -2
- package/dist/{tooltip-BlvBUQxx.d.ts → tooltip-Bl4HpBue.d.ts} +0 -2
- package/dist/transitory-label.d.ts +3 -6
- package/dist/transitory-label.js +2 -3
- package/dist/{tree-BaXYyt5U.js → tree-BP5fxvEX.js} +24 -34
- package/dist/{tree-CftHwtEb.js → tree-DUc-iQcg.js} +2 -3
- package/dist/{tree-component-DgfkxmVY.d.ts → tree-component-DNuzfo6R.d.ts} +2 -8
- package/dist/tree.d.ts +3 -3
- package/dist/tree.js +1 -2
- package/dist/{types-LU1u0CB8.d.ts → types-16GC5k1z.d.ts} +0 -2
- package/dist/{types-Jky6oNAp.d.ts → types-6hpXRKt7.d.ts} +1 -3
- package/dist/unit-input.d.ts +11 -14
- package/dist/unit-input.js +3 -4
- package/dist/{vertical-list-o5hfqH87.js → vertical-list-B9z1lbjM.js} +13 -15
- package/dist/{vertical-list-DtHX26JO.d.ts → vertical-list-BzeXehBt.d.ts} +7 -13
- package/dist/vertical-list.d.ts +4 -4
- package/dist/vertical-list.js +1 -2
- package/dist/{view-state-controller-BdyXWUUM.js → view-state-controller-DiPAPiA7.js} +3 -6
- package/dist/{wheel-nudge-D5rmht3v.js → wheel-nudge-DgJzrL28.js} +2 -3
- package/dist/{wheel-nudge-By5s_ioH.d.ts → wheel-nudge-oMFKat9P.d.ts} +1 -3
- package/dist/{xy-axis-DQqd2yXJ.d.ts → xy-axis-Bf6WfOS2.d.ts} +0 -12
- package/dist/{xy-axis-CyMd_Bn1.js → xy-axis-CBpml-3S.js} +11 -20
- package/dist/xy-pad.d.ts +7 -9
- package/dist/xy-pad.js +6 -7
- package/docs-user/README.md +1 -0
- package/docs-user/grid-list.md +65 -0
- package/docs-user/index.json +7 -2
- package/docs-user/llms.txt +1 -0
- package/docs-user/node-graph.md +376 -0
- package/llms.txt +1 -0
- package/package.json +39 -24
- package/bundle/rolldown-runtime-CEoa-xHZ.js +0 -1
- package/dist/interaction-E-uMGHkJ.js +0 -3
- package/dist/rolldown-runtime-D7D4PA-g.js +0 -13
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# `ixfx-node-graph`
|
|
2
|
+
|
|
3
|
+
A Svelte-Flow-style node editor: nodes on a pan/zoom canvas, wires between typed ports, and a magnetic connect gesture. Nodes are ordinary light-DOM elements, so you can put anything inside them — including other ixfx controls.
|
|
4
|
+
|
|
5
|
+
Three elements:
|
|
6
|
+
|
|
7
|
+
- `<ixfx-node-graph>` — the canvas: pan/zoom, wires, gestures, selection
|
|
8
|
+
- `<ixfx-graph-node>` — one node (`key`, `x`/`y`, optional `label` header)
|
|
9
|
+
- `<ixfx-graph-port>` — one connectable dot (`key`, `dir`, `type`, …)
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<ixfx-node-graph acyclic>
|
|
13
|
+
<ixfx-graph-node key="osc" x="40" y="60" label="Oscillator">
|
|
14
|
+
<ixfx-graph-port key="freq" dir="in" type="number" label="Freq"></ixfx-graph-port>
|
|
15
|
+
<ixfx-slider-input min="0" max="100"></ixfx-slider-input>
|
|
16
|
+
<ixfx-graph-port key="out" dir="out" type="signal" label="Out"></ixfx-graph-port>
|
|
17
|
+
</ixfx-graph-node>
|
|
18
|
+
<ixfx-graph-node key="amp" x="300" y="60" label="Amp">
|
|
19
|
+
<ixfx-graph-port key="in" dir="in" type="signal" label="In"></ixfx-graph-port>
|
|
20
|
+
<ixfx-graph-port key="out" dir="out" type="signal" label="Out"></ixfx-graph-port>
|
|
21
|
+
</ixfx-graph-node>
|
|
22
|
+
</ixfx-node-graph>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
const graph = document.querySelector('ixfx-node-graph');
|
|
27
|
+
|
|
28
|
+
// Wire ports programmatically — same rules and events as a drag.
|
|
29
|
+
// Either argument order works; it commits out → in.
|
|
30
|
+
graph.connect({ node: 'osc', port: 'out' }, { node: 'amp', port: 'in' });
|
|
31
|
+
|
|
32
|
+
// The wire list (plain immutable data; ids are deterministic)
|
|
33
|
+
console.log(graph.edges);
|
|
34
|
+
// [{ id: 'osc/out->amp/in', from: { node: 'osc', port: 'out' }, to: { node: 'amp', port: 'in' } }]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Nodes are light-DOM children. A port that is a *direct child* of a node is auto-assigned to the node's left (`in`) or right (`out`) rail based on `dir`. A port nested deeper — inside the body content — stays where you put it, and wires follow its measured position (Svelte Flow's `<Handle>` pattern). A direct child with an explicit `slot` attribute (use `slot=""` for the body) is left where it is too. Set `side` to match where the port actually sits, e.g. a port pinned to the top edge with CSS wants `side="top"`. Otherwise the wire arrives from the wrong direction, through the node body.
|
|
38
|
+
|
|
39
|
+
## Graph model
|
|
40
|
+
|
|
41
|
+
The **port graph** is the source of truth: a `Graphs.Directed` graph (from `@ixfx/collections`) whose vertices are ports and whose edges are wires. Wires always run from an `out` port to an `in` port. Because vertices are ports — not nodes — two wires between the same pair of nodes never collide, and duplicate/max-connection checks are plain graph reads.
|
|
42
|
+
|
|
43
|
+
The `edges` property is a derived view of that graph, and `getNodeGraph()` returns it collapsed to node keys. That collapsed graph points in the direction of data flow (`a → b` means *b reads from a*), which makes it directly usable as a `Graphs.Dependencies.DependencyGraph` — evaluation order, cycle detection and reachability come for free:
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
const ng = graph.getNodeGraph();
|
|
47
|
+
const order = Graphs.Dependencies.recalculationOrder(ng, ['output']);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Port and wire ids escape their keys, so `node: 'a/b'` is safe. Use the exported `portId` / `parsePortId` helpers to build or read vertex ids — never hand-split on `/`.
|
|
51
|
+
|
|
52
|
+
> **Reset vs. mutate.** The element owns its graph. Read it via `edges` / `getGraph()` / `getPortGraph()`; set `edges` only to reset the whole wiring wholesale (it rebuilds the port graph from the plain array). In particular, if a framework re-renders around the graph, do *not* re-assign `edges` with the array you read back after every `graph-edges-change` — that throws away in-flight gesture state. Subscribe and read, or reset deliberately.
|
|
53
|
+
|
|
54
|
+
## `<ixfx-node-graph>` API
|
|
55
|
+
|
|
56
|
+
### Properties / attributes
|
|
57
|
+
|
|
58
|
+
| Attribute / property | Type | Default | Description |
|
|
59
|
+
|----------------------|---------------------------|----------|-------------------------------------------------------------------------|
|
|
60
|
+
| `edges` | `readonly GraphEdge[]` | `[]` | Wire list. Setting it rebuilds the graph (see warning above). |
|
|
61
|
+
| `viewport` | `{x, y, zoom}` | `0,0,1` | Pan/zoom transform of the canvas. |
|
|
62
|
+
| `min-zoom` | `number` | `0.2` | Zoom clamp for gestures and `fitView()`. |
|
|
63
|
+
| `max-zoom` | `number` | `3` | Zoom clamp for gestures and `fitView()`. |
|
|
64
|
+
| `wheel` | `'zoom' \| 'pan'` | `'zoom'` | Plain wheel behaviour; ctrl/cmd + wheel always zooms. |
|
|
65
|
+
| `snap-radius` | `number` | `32` | Magnet radius (screen px) when releasing a wire near a compatible port. |
|
|
66
|
+
| `allow-self` | `boolean` | `false` | Allow a wire between two ports of the same node. |
|
|
67
|
+
| `acyclic` | `boolean` | `false` | Reject wires that would create a cycle at the node level. |
|
|
68
|
+
| `port-layout` | `'rail' \| 'full'` | `'rail'` | How nodes list ports: inputs left and outputs right (`rail`), or a full-width row per port (`full`). A node's own `port-layout` wins. See [Port layouts](#port-layouts). |
|
|
69
|
+
| `dblclick-toggle` | `boolean` | `false` | Double-clicking a node's title bar collapses or expands it. The `graph-node-title-dblclick` event fires either way. |
|
|
70
|
+
| `available-ports` | `'show' \| 'hide'` | `'show'` | Show or tuck away every node's available (unconnected) ports. A node's own `available-ports` wins. See [Port states](#port-states). |
|
|
71
|
+
| `drag-by` | `'node' \| 'handle'` | `'node'` | What starts moving a node: anywhere on it, or only its title-bar grip. A node's own `drag-by` wins. |
|
|
72
|
+
| `easy-connect` | `boolean` | `false` | Drag from anywhere inside a node to start a wire; drop anywhere on a node to finish one. |
|
|
73
|
+
| `easy-connect-margin`| `number` | `10` | Width (flow px) of the ring around a node that still moves it under `easy-connect`. |
|
|
74
|
+
| `proximity-connect` | `number` | `0` | While dragging a node, offer a wire to the nearest node within this many flow px (`0` = off); made on drop. |
|
|
75
|
+
| `bridge-on-delete` | `boolean` | `false` | Deleting a node in the middle of a flow reconnects what fed it to what it fed. |
|
|
76
|
+
| `nodeFactory` | `(ctx) => GraphNodeElement \| null` | — | Opt-in: called when a wire is dropped on empty space; the returned node is added there and wired. Property only. |
|
|
77
|
+
| `edgeLabels` | `Record<string, string>` | `{}` | Wire id → label text. Property only; prefer `setEdgeLabel()`. |
|
|
78
|
+
| `touch-mode` | `'pan' \| 'select'` | `'pan'` | What a touch/pen drag on empty space does: pan, or draw a selection rectangle. Mouse is unaffected. |
|
|
79
|
+
| `isValidConnection` | `(from, to, graph) => boolean` | — | Extra veto hook, called after the built-in rules pass. Property only. |
|
|
80
|
+
| `wireFlow` | `Record<string, number>` | `{}` | Wire id → flow speed (flow-space px/s). See [Data-flow animation](#data-flow-animation). Property only. |
|
|
81
|
+
|
|
82
|
+
### Methods
|
|
83
|
+
|
|
84
|
+
| Method | Returns | Description |
|
|
85
|
+
|---------------------------|--------------------------------|----------------------------------------------------------|
|
|
86
|
+
| `connect(from, to)` | `boolean` | Wire two ports (either order); `false` if the rules reject it. |
|
|
87
|
+
| `disconnect(id)` | `boolean` | Remove a wire by id. |
|
|
88
|
+
| `setEdgeLabel(id, text)` | `void` | Set or clear (`''` / `undefined`) a wire's label. |
|
|
89
|
+
| `setWireFlow(id, speed)` | `void` | Animate one wire at `speed` px/s; `0` / `undefined` stops it. |
|
|
90
|
+
| `getGraph()` | `GraphSnapshot` | Plain-JSON snapshot: nodes with positions and ports, plus edges. |
|
|
91
|
+
| `getSubflowEdges(groupKey)` | `{ incoming, outgoing, internal }` | The wires crossing a sub flow's boundary (the group and everything in it): in from outside, out to outside, and the ones entirely inside. |
|
|
92
|
+
| `getPortGraph()` | `Graphs.Directed.DirectedGraph`| The source-of-truth port graph. |
|
|
93
|
+
| `getNodeGraph()` | `Graphs.Directed.DirectedGraph`| Port graph collapsed to node keys (= dependency graph). |
|
|
94
|
+
| `setViewport(v)` | `void` | Set pan/zoom (zoom is clamped). |
|
|
95
|
+
| `fitView(padding?)` | `void` | Pan/zoom so all nodes fit (default 50px margin). |
|
|
96
|
+
| `screenToFlow(p)` | `Point` | Client coordinates → flow coordinates. |
|
|
97
|
+
| `flowToScreen(p)` | `Point` | Flow coordinates → client coordinates. |
|
|
98
|
+
| `contextTarget` | `GraphContextMenuDetail?` | What the last right-click landed on (for `menu-command` handlers). |
|
|
99
|
+
| `getViewState(options?)` | `NodeGraphViewState` | Viewport + selected wires (see `docs/infra-view-state.md`). |
|
|
100
|
+
| `setViewState(state)` | `void` | Restore a view state. |
|
|
101
|
+
|
|
102
|
+
View state `kind` is `'node-graph'`; `clearPendingViewState()` is a no-op — this component has no pending view state to clear.
|
|
103
|
+
|
|
104
|
+
### Events
|
|
105
|
+
|
|
106
|
+
All events are composed and (except where noted) cancelable.
|
|
107
|
+
|
|
108
|
+
| Event | Detail | Cancelable | Description |
|
|
109
|
+
|---------------------------|---------------------------------------------|------------|-------------------------------------------------------------|
|
|
110
|
+
| `graph-connect` | `{ edge, replaces? }` | yes | A wire is about to be committed (drag or `connect()`). |
|
|
111
|
+
| `graph-disconnect` | `{ edges, reason }` | yes | Wires removed; reason `delete` \| `detach` \| `replace` \| `api`. |
|
|
112
|
+
| `graph-connect-end` | `{ from, point, client }` | no | A dragged wire was released on empty space (e.g. to open a create-node menu). |
|
|
113
|
+
| `graph-delete` | `{ nodes, edges, bridged }` | yes | Selection delete is about to run. `nodes` includes a deleted group's children; `bridged` lists wires added by `bridge-on-delete`. |
|
|
114
|
+
| `graph-node-move` | `{ moves }` | no | Node positions during a drag. |
|
|
115
|
+
| `graph-node-moved` | `{ moves }` | no | Node positions after a drag commit. |
|
|
116
|
+
| `graph-node-resize` / `graph-node-resized` | `{ key, x, y, width, height }` | no | While resizing / on release. `x`, `y` are relative to the parent. |
|
|
117
|
+
| `graph-node-title-dblclick` | `{ key, collapsed }` | yes | A node's title bar was double-clicked (not its collapse button, menu or grip). `collapsed` is the state before. With `dblclick-toggle` on, `preventDefault()` stops the toggle. |
|
|
118
|
+
| `graph-node-collapse` | `{ key, collapsed }` | no | A node was collapsed or expanded (bubbles from the node). |
|
|
119
|
+
| `graph-port-change` | `{ node, port, availability }` | no | The user added a port (`available`) or hid one (`potential`). |
|
|
120
|
+
| `graph-node-added` | `{ key, from, connectedTo? }` | no | A `nodeFactory` node was added (and wired, if a compatible port existed). |
|
|
121
|
+
| `graph-context-menu` | `{ kind, id?, client, point }` | yes | Right-click on a `node`, `edge` or `pane` that has a menu. Cancel to suppress it. |
|
|
122
|
+
| `graph-selection-change` | `{ nodes, edges }` | no | Selection changed (key arrays). |
|
|
123
|
+
| `graph-viewport-change` | `{x, y, zoom}` | no | Pan/zoom committed. |
|
|
124
|
+
| `graph-edges-change` | `{ edges, graph }` | no | The graph changed; `graph` is the new port graph (for undo stacks). |
|
|
125
|
+
|
|
126
|
+
Veto a change by calling `event.preventDefault()` on the cancelable ones — the graph state is only mutated if the event isn't canceled.
|
|
127
|
+
|
|
128
|
+
## Gestures & keyboard
|
|
129
|
+
|
|
130
|
+
- **Connect** — drag from a port. Any compatible port under the pointer glows when within magnet range (`data-magnet="active"`, `--magnet-proximity`); release to connect. Releasing on empty space fires `graph-connect-end`. Dragging backwards from an `in` port works; the wire commits out → in. Dragging an existing wire off a port detaches it (its far end follows the pointer).
|
|
131
|
+
- **Pan** — drag the background, drag with the middle button anywhere, or Space + drag. **Zoom** — wheel, ctrl/cmd + wheel (trackpad pinch), or two-finger touch pinch.
|
|
132
|
+
- **Select** — click a node or wire; shift/cmd-click to toggle. Drag a selected node to move the whole selection. Click the background to clear.
|
|
133
|
+
- **Select a region** — hold **Shift** and drag on empty space with the mouse to draw a selection rectangle; every node it overlaps (even partly) is selected live. Add **Ctrl/Cmd** as well to add to the current selection instead of replacing it. Touch and pen can't hold a key, so they follow `touch-mode`: `pan` (default) drags the canvas, `select` draws the rectangle. A second finger during a touch selection cancels it and becomes a pinch, so you can still zoom in `select` mode. Then drag any selected node to move them all together. Only nodes are selected, not wires.
|
|
134
|
+
- **Delete** — `Delete` / `Backspace` deletes the selection (veto with `graph-delete`).
|
|
135
|
+
- **Escape** — cancels an in-flight wire drag, detach or selection rectangle (the selection goes back to what it was).
|
|
136
|
+
|
|
137
|
+
### Escape hatches
|
|
138
|
+
|
|
139
|
+
Elements inside nodes opt out of gestures with plain attributes (checked across shadow boundaries):
|
|
140
|
+
|
|
141
|
+
- `data-nodrag` — pressing it never starts a node drag (embedded controls, buttons)
|
|
142
|
+
- `data-nowheel` — wheel over it never zooms/pans the canvas
|
|
143
|
+
|
|
144
|
+
Native form controls and `[contenteditable]` are always excluded from both.
|
|
145
|
+
|
|
146
|
+
## `<ixfx-graph-node>`
|
|
147
|
+
|
|
148
|
+
| Attribute | Type | Default | Description |
|
|
149
|
+
|-----------|------|---------|-------------|
|
|
150
|
+
| `key` | `string` | — | Unique within its graph. Required. |
|
|
151
|
+
| `x`, `y` | `number` | `0` | Position in flow coordinates; relative to the parent when `parent` is set. |
|
|
152
|
+
| `label` | `string` | `''` | Title-bar text; omit for a headerless node. |
|
|
153
|
+
| `selected` | `boolean` | | Set by the graph, for styling (`:host([selected])`). |
|
|
154
|
+
| `port-layout` | `rail` \| `full` | inherits | Override the graph's `port-layout` for this node. |
|
|
155
|
+
| `icon` | `string` | `''` | Name of an icon in the icon registry, shown before the title. |
|
|
156
|
+
| `available-ports` | `show` \| `hide` | inherits | Show or tuck away this node's available ports. |
|
|
157
|
+
| `shape` | `rect` `rounded` `pill` `circle` `diamond` `hexagon` | `rounded` | Outline. See [Shapes and borders](#shapes-and-borders). |
|
|
158
|
+
| `collapsed` | `boolean` | `false` | Show only the title bar. Needs a `label`; the disclosure widget in the title bar toggles it. |
|
|
159
|
+
| `no-collapse` | `boolean` | `false` | Hide the disclosure widget. |
|
|
160
|
+
| `resizable` | `boolean` | `false` | Edge and corner resize grips (plain CSS, not the ixfx handles). |
|
|
161
|
+
| `width`, `height` | `number` | `0` | Explicit size in flow px (`0` = content / `--graph-node-width`). Set by resizing. |
|
|
162
|
+
| `min-width`, `min-height` | `number` | `80`, `40` | Resize limits. |
|
|
163
|
+
| `max-width`, `max-height` | `number` | `0` | Resize limits (`0` = unlimited). |
|
|
164
|
+
| `drag-by` | `node` \| `handle` | inherits | `handle`: only the title-bar grip starts a drag. |
|
|
165
|
+
| `easy-connect` | `on` \| `off` | inherits | Override the graph's `easy-connect` for this node. |
|
|
166
|
+
| `connect-port` | `string` | first suitable | Key of the port easy-connect wires from. |
|
|
167
|
+
| `parent` | `string` | | Key of a parent (group) node. |
|
|
168
|
+
| `group` | `boolean` | `false` | A container for child nodes. |
|
|
169
|
+
| `extent` | `parent` | | Keep this node inside its parent while dragging. |
|
|
170
|
+
| `toolbar-visible` | `selected` \| `always` \| `hover` | `selected` | When the `toolbar` slot shows. |
|
|
171
|
+
| `toolbar-position` | `top` \| `bottom` \| `left` \| `right` | `top` | Where it sits. |
|
|
172
|
+
|
|
173
|
+
Shadow parts: `shape`, `surface`, `header`, `disclosure`, `menu`, `drag-handle`, `rail-in`, `rail-out`, `body`, `toolbar`, `resize-n` … `resize-sw`. Size it with `--graph-node-width` (default `180px`); height is content-driven unless `height` is set.
|
|
174
|
+
|
|
175
|
+
### Shapes and borders
|
|
176
|
+
|
|
177
|
+
`rect`, `rounded`, `pill` and `circle` are box shapes with a real CSS border, so any `border-style` works. `diamond` and `hexagon` are clipped with `clip-path`, so their border is a layered solid colour (its thickness is approximate on non-square nodes; no dashed or dotted style). Content in `circle`, `diamond` and `hexagon` is centred, and ports stay on the left/right rails inside the outline.
|
|
178
|
+
|
|
179
|
+
The look is CSS variables, read with fallbacks, so the same variable works **for all nodes** (set it on the graph) or **for one node** (set it in that node's `style`, or with a CSS rule on it):
|
|
180
|
+
|
|
181
|
+
```css
|
|
182
|
+
ixfx-node-graph { --graph-node-border-width: 2px; } /* every node */
|
|
183
|
+
ixfx-graph-node[key=warning] { --graph-node-border-color: orange; /* just this one */
|
|
184
|
+
--graph-node-border-style: dashed; }
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Toolbar, context menu, drag handle, groups
|
|
188
|
+
|
|
189
|
+
- **Toolbar** — a child with `slot="toolbar"` (an `ixfx-toolbar` with `ixfx-button`s) appears above the node while it is selected (or `always` / on `hover`). It never starts a node drag.
|
|
190
|
+
- **Context menu** — a three-dot button in the title bar; see [Context menus](#context-menus).
|
|
191
|
+
- **Title bar** — left to right: the drag grip (with `drag-by="handle"`), the disclosure widget, an optional `icon` (the name of any icon in the icon registry), the title, and the menu button.
|
|
192
|
+
- **Collapse** — every titled node has a disclosure widget at the left of its title bar. Collapsed, a node shows only its title bar and its wires attach to the edge of that bar; it has no effect on a node without a `label`. Collapsing a group hides all its children, but keeps its outside connections: every wire into the sub flow is drawn to **one virtual in port** on the group's left edge, and every wire out of it from **one virtual out port** on the right (a dashed dot with a tooltip such as "2 incoming connections"). Wires with both ends inside the sub flow are hidden. `graph.getSubflowEdges(key)` lists the boundary wires, collapsed or not. Fires `graph-node-collapse` (`{ key, collapsed }`).
|
|
193
|
+
- **Drag handle** — `drag-by="handle"` (on the node, or on the graph for all) shows the shared ixfx grip in the title bar; only it moves the node. Mark any element `data-drag-handle` to make it a grip too.
|
|
194
|
+
- **Sub flows** — a node with `parent="groupKey"` is positioned relative to that group and moves with it, and is **clipped to the group's rectangle** (a child poking out is cut off at the edge; a child fully inside is left alone, so its outline and toolbar still show). A group (`group`, usually `resizable`) can hold children; deleting it deletes them. `extent="parent"` clamps a child inside. If a group and its child are both selected, the child is moved once, with the group.
|
|
195
|
+
|
|
196
|
+
## Port layouts
|
|
197
|
+
|
|
198
|
+
`port-layout` (on the graph, or on a node to override it) chooses how a node lists its ports:
|
|
199
|
+
|
|
200
|
+
- **`rail`** (default): inputs down the left edge, outputs down the right, beside the content, as before.
|
|
201
|
+
- **`full`**: every input and then every output is a **full-width row** (except that a direction with a single, unlabeled port is not given a row: see below), with the label in bold and the port's `description` as a byline under it, a divider between rows, and the dot centred on the node's left (input) or right (output) edge. The whole row is the port, so a wire can start from, or be dropped on, anywhere in it.
|
|
202
|
+
|
|
203
|
+
**A lone unlabeled port stays compact.** In the `full` layout, if a node has exactly one input (or exactly one output) and that port has no `label`, it is not shown as a row: it is drawn as in `rail`, a dot on the node's edge. This is decided per direction, so a node can list its outputs as rows and keep a single unnamed input as a plain dot. (A lone port that has a label still gets a row, and so does any direction with two or more ports, labeled or not.)
|
|
204
|
+
|
|
205
|
+
Content slots, in both layouts, top to bottom: the **title bar** (shaded: `--graph-node-header-bg`), `slot="before"`, the default slot (the node body: beside the rails in `rail`, above the ports in `full`), the ports, then `slot="after"`.
|
|
206
|
+
|
|
207
|
+
```html
|
|
208
|
+
<ixfx-graph-node key="model" label="Model: Status" icon="info" port-layout="full">
|
|
209
|
+
<ul slot="before">…a note…</ul>
|
|
210
|
+
<ixfx-graph-port key="data" dir="in" label="Data" description="The complete record"></ixfx-graph-port>
|
|
211
|
+
<ixfx-graph-port key="out" dir="out" label="Output" description="The validated record"></ixfx-graph-port>
|
|
212
|
+
<a slot="after">Copy fields as Markdown</a>
|
|
213
|
+
</ixfx-graph-node>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Port shape and colour.** `--node-graph-port-radius` is the dot's corner radius: `50%` (default) a circle, `3px` a rounded square, `0` a square. `--node-graph-port-size` is its size. `--node-graph-port-connected-color` and `--node-graph-port-available-color` colour a port by state (both default to `--node-graph-port-color`). All of these can be set on the graph, a node, or a single port.
|
|
217
|
+
|
|
218
|
+
## Port states
|
|
219
|
+
|
|
220
|
+
Every port is in one of three states:
|
|
221
|
+
|
|
222
|
+
| State | Meaning |
|
|
223
|
+
|---|---|
|
|
224
|
+
| **connected** | A wire is attached. Always visible, in the node's port column. |
|
|
225
|
+
| **available** | Visible, not connected. |
|
|
226
|
+
| **potential** | Not connected and hidden away. Never drawn as a port; only the **+** button can add it. |
|
|
227
|
+
|
|
228
|
+
A port's `availability` (`available` or `potential`) is what the author or user chose for it while it is unconnected; the graph works out `connected` from the wires (`data-status` on the port shows the result).
|
|
229
|
+
|
|
230
|
+
**Hiding available ports.** `available-ports="hide"` on the graph (the default for every node) or on one node tucks its available ports away so the node stays compact (`show` is the default). A tucked-away port is revealed as an overlay on the node's edge, below the ports that are always shown, so nothing reflows. Both inputs and outputs are revealed:
|
|
231
|
+
|
|
232
|
+
- while the node is **selected** (so this works on touch too, where there is no hover);
|
|
233
|
+
- while the **mouse is over** the node, including over the revealed ports, and for a moment after it leaves;
|
|
234
|
+
- while a **wire is being dragged** toward the node's left (in) or right (out) edge area, for as long as the pointer stays there or over the revealed list of ports (which can extend below and beside the node). The revealed ports become connection targets;
|
|
235
|
+
- after you **add** a port, until you press elsewhere.
|
|
236
|
+
|
|
237
|
+
**Adding and hiding.** If a node has potential ports on a side, a circle with a **+** appears at the bottom of that side's port column (always **after** the list of ports, below any revealed ones) while the node is selected, or when the pointer hovers there. Clicking it lists the potential ports; choosing one makes it `available`. If the node tucks its available ports away, the new port stays shown until you press elsewhere, so you can see it. Right-clicking an available port offers **Hide port**, which makes it `potential` again. A connected port can't be hidden. Both fire `graph-port-change`, and the port's `availability` attribute reflects the new state, so you can save it.
|
|
238
|
+
|
|
239
|
+
Ports are drawn **centred on the node's edge**, not inside it.
|
|
240
|
+
|
|
241
|
+
## Context menus
|
|
242
|
+
|
|
243
|
+
Author a menu with the ixfx menu primitives and give it a slot. A node's menu is a **three-dot (`ellipsis`) button in its title bar**: use a default-mode `ixfx-menu-trigger` with `icon-name="ellipsis"`, which renders the button. Right-click on the node opens the same menu at the pointer. The graph's `edge-menu` and `pane-menu` have no button (use `mode="dropdown"`) and open on right-click of a wire or the background. With no menu for a target, the browser's own menu is left alone. Right-clicking a node or wire selects it first.
|
|
244
|
+
|
|
245
|
+
```html
|
|
246
|
+
<ixfx-node-graph>
|
|
247
|
+
<ixfx-graph-node key="a">
|
|
248
|
+
<ixfx-menu-trigger slot="context-menu" icon-name="ellipsis">
|
|
249
|
+
<ixfx-menu-container slot="menu"><ixfx-menu-item command="remove" label="Remove"></ixfx-menu-item></ixfx-menu-container>
|
|
250
|
+
</ixfx-menu-trigger>
|
|
251
|
+
</ixfx-graph-node>
|
|
252
|
+
<ixfx-menu-trigger slot="edge-menu" mode="dropdown">…</ixfx-menu-trigger>
|
|
253
|
+
<ixfx-menu-trigger slot="pane-menu" mode="dropdown">…</ixfx-menu-trigger>
|
|
254
|
+
</ixfx-node-graph>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Commands arrive as the menu's own `menu-command` event; `graph.contextTarget` says what was clicked (`{ kind, id, point }`, where `point` is in flow coordinates, handy for "add node here"). Listen for the cancelable `graph-context-menu` to adjust or suppress a menu first. (`ixfx-menu-trigger` gained a public `openAt(x, y)` for this.)
|
|
258
|
+
|
|
259
|
+
## Connecting
|
|
260
|
+
|
|
261
|
+
- **Connection limits** — `max` on a port caps its wires (an `in` port defaults to one, which a new wire replaces). Over-limit and incompatible ports dim during a drag. `isValidConnection` adds your own veto.
|
|
262
|
+
- **Easy connect** — with `easy-connect`, drag from anywhere inside a node to start a wire from its first `out` port (or `connect-port`), and drop anywhere on a node to connect to its best valid port. The `easy-connect-margin` ring still moves the node; the title-bar grip, controls, group nodes and `data-nodrag` areas never start a wire.
|
|
263
|
+
- **Proximity connect** — with `proximity-connect="70"`, dragging a node within 70 flow px of another shows a dashed wire (the node on the left is the source, then the reverse) and makes it on release, through the normal `graph-connect` event and rules.
|
|
264
|
+
- **Add node on drop** — set `graph.nodeFactory = ({ from, point }) => node`. It runs when a wire is dropped on empty space; the node is placed so its connecting port sits under the pointer and is wired to the dragged port. Return `null` to add nothing. A node with no compatible port is added but left unwired. `graph-connect-end` still fires first.
|
|
265
|
+
- **Delete in the middle** — with `bridge-on-delete`, deleting `B` in `A → B → C` leaves `A → C`. For each wire that entered the node and each that left it, a wire is added when the port types pass through (`in` type compatible with `out` type) and the normal rules allow it (max, cycles, `isValidConnection`); a bridge never replaces another wire. Only nodes you deleted are bridged, not a deleted group's children. `graph-delete.bridged` lists them; cancel the event to veto everything.
|
|
266
|
+
|
|
267
|
+
## Edge labels
|
|
268
|
+
|
|
269
|
+
`graph.setEdgeLabel(edge.id, 'text')` draws text at the middle of a wire (`edgeLabels` takes a whole map). It has a halo in the canvas colour instead of a box, so nothing needs measuring. Style with `--node-graph-edge-label-color`, `--node-graph-edge-label-size`, `--node-graph-edge-label-bg`. Labels are text only; there is no HTML label.
|
|
270
|
+
|
|
271
|
+
## Port location
|
|
272
|
+
|
|
273
|
+
`port-location` (on the graph; a node's own attribute wins) is `vertical` (default: inputs on the left edge, outputs on the right) or `horizontal` (inputs on the top edge, outputs on the bottom). Wires leave horizontal ports up / down, and revealing hidden ports, the + button and a collapsed group's virtual ports all follow. In `horizontal` mode only the dots are shown (port labels and descriptions are in the tooltip) and `port-layout="full"` is silently ignored. `flip-ports` (graph or node) swaps the two ends — inputs right / bottom, outputs left / top — for flows that run right-to-left or bottom-to-top. A port's own `side` still wins for its wire tangent.
|
|
274
|
+
|
|
275
|
+
## Edge routing and hints
|
|
276
|
+
|
|
277
|
+
`edge-style` (`bezier` default, `straight`, `step`) sets how every wire is drawn. Steer single wires with `graph.setEdgeHint(edge.id, { style?, waypoints? })` (`edgeHints` takes a whole map): `style` overrides the graph style for that wire; `waypoints` (flow coordinates) is a list of points the wire passes through.
|
|
278
|
+
|
|
279
|
+
For full control set `graph.edgeRouter = (request) => Map<edgeId, Point[]>`. The request lists every wire (both ends: node, port, centre, outgoing `side`) and every shown node's rectangle as an obstacle; return the full point list for the wires you route (others are drawn normally). A router wins over hints, which win over styles. It runs synchronously on every redraw, and errors are caught and warned once. `demo/libavoid-router.ts` adapts [libavoid-js](https://github.com/Aksem/libavoid-js) (WASM, orthogonal routing around nodes) to this shape.
|
|
280
|
+
|
|
281
|
+
## Layout helpers
|
|
282
|
+
|
|
283
|
+
`getNodeRects()` returns every shown node's `{key, rect}` and `setNodePositions({key: {x, y}})` moves many nodes at once (no events), which is all a layout algorithm needs: `demo/node-graph-layout.ts` feeds them to [dagre](https://github.com/dagrejs/dagre) for layered layouts. `force-layout.ts` is a small immutable force-directed layout (`forceState`, `forceStep`, `forcePositions`, `forcePin`, `forceMovement`) to run once per animation frame.
|
|
284
|
+
|
|
285
|
+
## `<ixfx-graph-port>`
|
|
286
|
+
|
|
287
|
+
| Attribute | Type | Default | Description |
|
|
288
|
+
|-----------|---------------------------------|------------------|--------------------------------------------------------------------|
|
|
289
|
+
| `key` | `string` | — | Unique identifier within its node. Required. Used in wires and events. |
|
|
290
|
+
| `dir` | `'in' \| 'out'` | `'in'` | Wire direction. |
|
|
291
|
+
| `type` | `string` | `''` | Compatibility tag; empty or `'*'` matches anything. |
|
|
292
|
+
| `max` | `number` | `0` | Max connections; `0` = default (1 for `in`, unlimited for `out`). |
|
|
293
|
+
| `side` | `'left' \| 'right' \| 'top' \| 'bottom'` | from `dir` | Wire tangent. Mostly useful for inline (nested) ports. |
|
|
294
|
+
| `label` | `string` | `''` | Human-visible name, shown next to the dot. |
|
|
295
|
+
| `description` | `string` | `''` | Optional longer explanation, shown in the tooltip (the label is the tooltip when there is none). |
|
|
296
|
+
| `availability` | `'available' \| 'potential'` | `'available'` | For an unconnected port: visible, or hidden away until added with the node's + button. A wired port is always `connected`. |
|
|
297
|
+
| `blink` | `number` | `0` | Blink rate in Hz (`in` or `out` ports). `0` = steady. Clamped to 10 Hz. |
|
|
298
|
+
|
|
299
|
+
Connection rules, in order: directions must differ → no same-node wire (unless `allow-self`) → types must match → `in` port not full → exact wire must not already exist → no node-level cycle (if `acyclic`) → `isValidConnection`. An `in` port at `max === 1` silently *replaces* its existing wire (reported via `graph-connect`'s `replaces` and `graph-disconnect` reason `replace`).
|
|
300
|
+
|
|
301
|
+
During a wire drag the graph sets state on ports for CSS: `data-connect="valid|invalid"` and `data-magnet="active"` plus `--magnet-proximity` (0–1).
|
|
302
|
+
|
|
303
|
+
## Data-flow animation
|
|
304
|
+
|
|
305
|
+
Two independent ways to show data moving, both pure CSS (no per-frame JS):
|
|
306
|
+
|
|
307
|
+
- **Wires** — `graph.setWireFlow(edge.id, 120)` draws a dashed highlight that runs along the wire, from the `out` port to the `in` port, at 120 flow-space px/second. Speed is per wire (`wireFlow` takes a whole map). Because speed is in px/s, a long and a short wire at the same speed move at the same pace. `0` / `undefined` stops it. Wire ids are deterministic (`edgeId(from, to)`), so an entry survives the wire being removed and re-created. Only animated wires cost anything, since the overlay exists only while a speed is set.
|
|
308
|
+
- **Ports** — `<ixfx-graph-port blink="2">` blinks the dot twice a second. Set `blink` from code to follow live activity.
|
|
309
|
+
|
|
310
|
+
Both stop under `prefers-reduced-motion: reduce` (the wire overlay stays as static dashes, ports stay steady).
|
|
311
|
+
|
|
312
|
+
The wire effect is a moving dash pattern, not a colour gradient: an SVG gradient can't follow a curved path, only a straight line. Tune it with the `--node-graph-wire-flow-*` variables below.
|
|
313
|
+
|
|
314
|
+
## CSS custom properties
|
|
315
|
+
|
|
316
|
+
On `<ixfx-node-graph>`:
|
|
317
|
+
|
|
318
|
+
| Property | Default | Description |
|
|
319
|
+
|---------------------------------|----------------------|------------------------------------|
|
|
320
|
+
| `--node-graph-bg` | `var(--surface-2)` | Canvas background |
|
|
321
|
+
| `--node-graph-grid-color` | `var(--surface-4)` | Dot-grid colour |
|
|
322
|
+
| `--node-graph-grid-gap` | `20px` | Dot-grid spacing |
|
|
323
|
+
| `--node-graph-wire-color` | `var(--surface-6)` | Wire stroke |
|
|
324
|
+
| `--node-graph-wire-width` | `2px` | Wire stroke width |
|
|
325
|
+
| `--node-graph-wire-selected-color` | `var(--accent)` | Selected wire stroke |
|
|
326
|
+
| `--node-graph-port-size` | `10px` | Port dot diameter |
|
|
327
|
+
| `--node-graph-port-color` | `var(--surface-6)` | Port dot fill |
|
|
328
|
+
| `--node-graph-port-radius` | `50%` | Port dot corner radius (circle → square) |
|
|
329
|
+
| `--node-graph-port-connected-color` | `var(--node-graph-port-color)` | Dot of a connected port |
|
|
330
|
+
| `--node-graph-port-available-color` | `var(--node-graph-port-color)` | Dot of an available (unconnected) port |
|
|
331
|
+
| `--node-graph-magnet-color` | `var(--accent)` | Magnet glow / valid-target colour |
|
|
332
|
+
| `--node-graph-marquee-border` | `var(--accent)` | Selection rectangle border |
|
|
333
|
+
| `--node-graph-marquee-fill` | 15% `var(--accent)` | Selection rectangle fill |
|
|
334
|
+
| `--node-graph-wire-flow-color` | `var(--accent)` | Data-flow dash colour |
|
|
335
|
+
| `--node-graph-wire-flow-dash` | `14` | Flow dash length (unitless px) |
|
|
336
|
+
| `--node-graph-wire-flow-gap` | `56` | Gap between flow dashes (unitless px) |
|
|
337
|
+
| `--node-graph-port-blink-dim` | `0.2` | Port opacity in the "off" half of a blink |
|
|
338
|
+
| `--node-graph-edge-label-color` | `var(--surface-text)`| Edge label text |
|
|
339
|
+
| `--node-graph-edge-label-size` | `11px` | Edge label font size |
|
|
340
|
+
| `--node-graph-edge-label-bg` | `var(--node-graph-bg)`| Edge label halo colour |
|
|
341
|
+
|
|
342
|
+
On `<ixfx-graph-node>`:
|
|
343
|
+
|
|
344
|
+
| Property | Default | Description |
|
|
345
|
+
|--------------------------------|----------------------|----------------------------|
|
|
346
|
+
| `--graph-node-width` | `180px` | Node width |
|
|
347
|
+
| `--graph-node-header-bg` | 7% of the text colour| Title bar shading |
|
|
348
|
+
| `--graph-node-port-row-border` | `1px solid` (faint) | Divider between full-width port rows |
|
|
349
|
+
| `--graph-node-bg` | `var(--surface-3)` | Node background |
|
|
350
|
+
| `--graph-node-text` | `var(--surface-3-text)` | Node text colour |
|
|
351
|
+
| `--graph-node-radius` | `var(--radius-m)` | Corner radius (`rounded`) |
|
|
352
|
+
| `--graph-node-border-width` | `1px` | Border width |
|
|
353
|
+
| `--graph-node-border-style` | `solid` | Border style (box shapes only) |
|
|
354
|
+
| `--graph-node-border-color` | `var(--surface-5)` | Border colour |
|
|
355
|
+
| `--graph-node-selected-outline`| `var(--accent)` | Selection outline |
|
|
356
|
+
| `--graph-node-toolbar-offset` | `8px` | Gap between node and toolbar |
|
|
357
|
+
| `--graph-node-selected-outline`| `var(--accent)` | Selection outline colour |
|
|
358
|
+
|
|
359
|
+
Per-type port colours are plain consumer CSS — the `type` attribute reflects, so:
|
|
360
|
+
|
|
361
|
+
```css
|
|
362
|
+
ixfx-graph-port[type=number] { --node-graph-port-color: oklch(70% 0.12 210); }
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## Performance notes
|
|
366
|
+
|
|
367
|
+
- Wire paths are recomputed only when something invalidated them (graph change, node geometry change, viewport change); they render through a keyed `repeat` over deterministic edge ids, so a viewport pan touches no wire DOM beyond the layer transform.
|
|
368
|
+
- Node geometry (position + measured size + port offsets) is cached per node, refreshed by `ResizeObserver` and a `MutationObserver` on node children — no per-frame measuring.
|
|
369
|
+
- Pan/zoom renders through the layer transform only; the dot-grid background is drawn on the pane with `background-position` from the same transform.
|
|
370
|
+
- For very large graphs, remove off-screen nodes rather than hiding them (ports register/unregister with the graph via slot observation, so removal also prunes the model).
|
|
371
|
+
|
|
372
|
+
## Demo
|
|
373
|
+
|
|
374
|
+
`demo/node-graph-layout.html`: edge routing with libavoid-js and waypoint hints, dagre auto layout, and a live force-directed layout.
|
|
375
|
+
|
|
376
|
+
`demo/node-graph.html` (also via the demo index): a live arithmetic patch — two slider inputs, a sum, a scale and an output LED — evaluated with `Graphs.Dependencies.calc` from `getNodeGraph()`, plus event logging and a JSON snapshot.
|
package/llms.txt
CHANGED
|
@@ -43,6 +43,7 @@ import '@ixfx/components/data-grid';
|
|
|
43
43
|
- [miller](docs-user/miller.md): A Miller column component displays hierarchical data as multiple vertical columns, like macOS Finder or email clients. Each column shows the children of the selected item in the previous column.
|
|
44
44
|
- [multicol-list](docs-user/multicol-list.md): A faceted-filtering component: each column shows one property (facet) of the *same*
|
|
45
45
|
- [narrowed-text](docs-user/narrowed-text.md): A filtered text selection component with inline search. Shows slotted items filtered by a query string as the user types.
|
|
46
|
+
- [node-graph](docs-user/node-graph.md): A Svelte-Flow-style node editor: nodes on a pan/zoom canvas, wires between typed ports, and a magnetic connect gesture. Nodes are ordinary light-DOM elements, so you can put anything inside them — inc
|
|
46
47
|
- [notifier](docs-user/notifier.md): A "lookless" app-wide notification system. `NotificationManager` is a singleton that owns notification state, queueing, and timing — it doesn't know how to draw anything. A `NotificationRenderer` (the
|
|
47
48
|
- [pan-text](docs-user/pan-text.md): Pans overflowing text on hover or continuously in a loop. Use when a label is too long for its container and you want to reveal the full text without truncation.
|
|
48
49
|
- [panel](docs-user/panel.md): An collapsible panel component with support for titles, icons, toolbars, and multiple visual variants.
|
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ixfx/components",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.17.1",
|
|
5
5
|
"packageManager": "pnpm@10.0.0",
|
|
6
6
|
"description": "Web components for ixfx — Lit-based UI toolkit",
|
|
7
7
|
"author": "Clint Heyer",
|
|
8
8
|
"license": "MIT",
|
|
9
|
-
"keywords": [
|
|
9
|
+
"keywords": [
|
|
10
|
+
"web-components",
|
|
11
|
+
"lit",
|
|
12
|
+
"ixfx",
|
|
13
|
+
"ui"
|
|
14
|
+
],
|
|
10
15
|
"exports": {
|
|
11
16
|
"./style.css": "./dist/style.css",
|
|
12
17
|
".": {
|
|
@@ -101,6 +106,10 @@
|
|
|
101
106
|
"types": "./dist/narrowed-text.d.ts",
|
|
102
107
|
"import": "./dist/narrowed-text.js"
|
|
103
108
|
},
|
|
109
|
+
"./node-graph": {
|
|
110
|
+
"types": "./dist/node-graph.d.ts",
|
|
111
|
+
"import": "./dist/node-graph.js"
|
|
112
|
+
},
|
|
104
113
|
"./panel": {
|
|
105
114
|
"types": "./dist/panel.d.ts",
|
|
106
115
|
"import": "./dist/panel.js"
|
|
@@ -225,7 +234,9 @@
|
|
|
225
234
|
"docs-user",
|
|
226
235
|
"llms.txt"
|
|
227
236
|
],
|
|
228
|
-
"engines": {
|
|
237
|
+
"engines": {
|
|
238
|
+
"node": ">=20"
|
|
239
|
+
},
|
|
229
240
|
"scripts": {
|
|
230
241
|
"typecheck": "tsc --noEmit",
|
|
231
242
|
"test": "vitest run",
|
|
@@ -252,36 +263,40 @@
|
|
|
252
263
|
"lit": "^3.0.0"
|
|
253
264
|
},
|
|
254
265
|
"peerDependenciesMeta": {
|
|
255
|
-
"lit": {
|
|
266
|
+
"lit": {
|
|
267
|
+
"optional": false
|
|
268
|
+
}
|
|
256
269
|
},
|
|
257
270
|
"dependencies": {
|
|
258
271
|
"@clinth/ui-commands": "1.1.2",
|
|
259
|
-
"@clinth/vcanvas": "0.0.
|
|
260
|
-
"@ixfx/arrays": "0.56.
|
|
261
|
-
"@ixfx/collections": "0.56.
|
|
262
|
-
"@ixfx/core": "0.56.
|
|
263
|
-
"@ixfx/dom": "0.56.
|
|
264
|
-
"@ixfx/flow": "0.56.
|
|
265
|
-
"@ixfx/geometry": "0.56.
|
|
266
|
-
"@ixfx/modulation": "0.56.
|
|
267
|
-
"@ixfx/numbers": "0.56.
|
|
268
|
-
"@ixfx/visual": "0.56.
|
|
272
|
+
"@clinth/vcanvas": "0.0.6",
|
|
273
|
+
"@ixfx/arrays": "0.56.21",
|
|
274
|
+
"@ixfx/collections": "0.56.21",
|
|
275
|
+
"@ixfx/core": "0.56.21",
|
|
276
|
+
"@ixfx/dom": "0.56.21",
|
|
277
|
+
"@ixfx/flow": "0.56.21",
|
|
278
|
+
"@ixfx/geometry": "0.56.21",
|
|
279
|
+
"@ixfx/modulation": "0.56.21",
|
|
280
|
+
"@ixfx/numbers": "0.56.21",
|
|
281
|
+
"@ixfx/visual": "0.56.21",
|
|
269
282
|
"fzf": "0.5.2"
|
|
270
283
|
},
|
|
271
284
|
"devDependencies": {
|
|
272
|
-
"@antfu/eslint-config": "^
|
|
273
|
-
"@
|
|
285
|
+
"@antfu/eslint-config": "^10.0.0",
|
|
286
|
+
"@dagrejs/dagre": "3.1.3",
|
|
287
|
+
"@tsdown/css": "0.23.7",
|
|
274
288
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
275
|
-
"eslint": "10.
|
|
289
|
+
"eslint": "10.14.0",
|
|
276
290
|
"eslint-plugin-format": "2.0.1",
|
|
277
|
-
"globals": "17.
|
|
278
|
-
"happy-dom": "^
|
|
279
|
-
"knip": "6.
|
|
291
|
+
"globals": "17.14.0",
|
|
292
|
+
"happy-dom": "^21.0.4",
|
|
293
|
+
"knip": "6.44.2",
|
|
294
|
+
"libavoid-js": "0.5.0-beta.5",
|
|
280
295
|
"lit": "3.3.3",
|
|
281
|
-
"tsdown": "0.
|
|
296
|
+
"tsdown": "0.23.7",
|
|
282
297
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
283
|
-
"typescript-eslint": "8.
|
|
284
|
-
"vite": "8.
|
|
285
|
-
"vitest": "^5.
|
|
298
|
+
"typescript-eslint": "8.72.2",
|
|
299
|
+
"vite": "8.3.7",
|
|
300
|
+
"vitest": "^5.1.0"
|
|
286
301
|
}
|
|
287
302
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var e=Object.defineProperty,t=(t,n)=>{let r={};for(var i in t)e(r,i,{get:t[i],enumerable:!0});return n||e(r,Symbol.toStringTag,{value:`Module`}),r},n=(e=>typeof require<`u`?require:typeof Proxy<`u`?new Proxy(e,{get:(e,t)=>(typeof require<`u`?require:e)[t]}):e)(function(e){if(typeof require<`u`)return require.apply(this,arguments);throw Error('Calling `require` for "'+e+"\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.")});export{n,t};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
//#region \0rolldown/runtime.js
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __exportAll = (all, no_symbols) => {
|
|
4
|
-
let target = {};
|
|
5
|
-
for (var name in all) __defProp(target, name, {
|
|
6
|
-
get: all[name],
|
|
7
|
-
enumerable: true
|
|
8
|
-
});
|
|
9
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
-
return target;
|
|
11
|
-
};
|
|
12
|
-
//#endregion
|
|
13
|
-
export { __exportAll as t };
|