@elabs-ai/components-flow 4.2.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/canvas-shell/canvas-shell.js +13 -0
- package/dist/canvas-shell/canvas-shell.js.map +1 -0
- package/dist/canvas-shell/clamped-fit-offset.js +8 -0
- package/dist/canvas-shell/clamped-fit-offset.js.map +1 -0
- package/dist/canvas-shell/index.js +14 -0
- package/dist/canvas-shell/index.js.map +1 -0
- package/dist/canvas-shell/use-measured-nodes.js +8 -0
- package/dist/canvas-shell/use-measured-nodes.js.map +1 -0
- package/dist/chunk-3BTU2VWZ.js +42 -0
- package/dist/chunk-3BTU2VWZ.js.map +1 -0
- package/dist/chunk-3LK6OOXE.js +2 -0
- package/dist/chunk-3LK6OOXE.js.map +1 -0
- package/dist/chunk-3M3FQAD4.js +105 -0
- package/dist/chunk-3M3FQAD4.js.map +1 -0
- package/dist/chunk-3Y5TCGC3.js +2 -0
- package/dist/chunk-3Y5TCGC3.js.map +1 -0
- package/dist/chunk-4VYHZM3S.js +108 -0
- package/dist/chunk-4VYHZM3S.js.map +1 -0
- package/dist/chunk-5TVVM4GR.js +45 -0
- package/dist/chunk-5TVVM4GR.js.map +1 -0
- package/dist/chunk-5XT73MQQ.js +2 -0
- package/dist/chunk-5XT73MQQ.js.map +1 -0
- package/dist/chunk-6LQD5Y5R.js +24 -0
- package/dist/chunk-6LQD5Y5R.js.map +1 -0
- package/dist/chunk-6SR26QY2.js +18 -0
- package/dist/chunk-6SR26QY2.js.map +1 -0
- package/dist/chunk-75JJK6BH.js +157 -0
- package/dist/chunk-75JJK6BH.js.map +1 -0
- package/dist/chunk-A66JBW4X.js +2 -0
- package/dist/chunk-A66JBW4X.js.map +1 -0
- package/dist/chunk-A7AFM3LI.js +72 -0
- package/dist/chunk-A7AFM3LI.js.map +1 -0
- package/dist/chunk-ADHLQF56.js +2 -0
- package/dist/chunk-ADHLQF56.js.map +1 -0
- package/dist/chunk-ATO4LXJF.js +75 -0
- package/dist/chunk-ATO4LXJF.js.map +1 -0
- package/dist/chunk-ATXVXTMZ.js +227 -0
- package/dist/chunk-ATXVXTMZ.js.map +1 -0
- package/dist/chunk-B6IUPZU7.js +2 -0
- package/dist/chunk-B6IUPZU7.js.map +1 -0
- package/dist/chunk-BJ7UMCD3.js +53 -0
- package/dist/chunk-BJ7UMCD3.js.map +1 -0
- package/dist/chunk-BWGYV4JZ.js +2 -0
- package/dist/chunk-BWGYV4JZ.js.map +1 -0
- package/dist/chunk-CWNO63LQ.js +109 -0
- package/dist/chunk-CWNO63LQ.js.map +1 -0
- package/dist/chunk-DHPUXKCZ.js +76 -0
- package/dist/chunk-DHPUXKCZ.js.map +1 -0
- package/dist/chunk-DNX7G3KA.js +185 -0
- package/dist/chunk-DNX7G3KA.js.map +1 -0
- package/dist/chunk-DNZRAJAQ.js +41 -0
- package/dist/chunk-DNZRAJAQ.js.map +1 -0
- package/dist/chunk-ELCLRVD4.js +2 -0
- package/dist/chunk-ELCLRVD4.js.map +1 -0
- package/dist/chunk-ELOADZSX.js +2 -0
- package/dist/chunk-ELOADZSX.js.map +1 -0
- package/dist/chunk-EUNV2MR5.js +21 -0
- package/dist/chunk-EUNV2MR5.js.map +1 -0
- package/dist/chunk-FAFQ5KLB.js +43 -0
- package/dist/chunk-FAFQ5KLB.js.map +1 -0
- package/dist/chunk-FIRO6Y3R.js +128 -0
- package/dist/chunk-FIRO6Y3R.js.map +1 -0
- package/dist/chunk-GE73WQUK.js +2 -0
- package/dist/chunk-GE73WQUK.js.map +1 -0
- package/dist/chunk-GIARAOU6.js +39 -0
- package/dist/chunk-GIARAOU6.js.map +1 -0
- package/dist/chunk-HTLTCCFA.js +73 -0
- package/dist/chunk-HTLTCCFA.js.map +1 -0
- package/dist/chunk-I53W3CFN.js +56 -0
- package/dist/chunk-I53W3CFN.js.map +1 -0
- package/dist/chunk-IJVFPUXO.js +2 -0
- package/dist/chunk-IJVFPUXO.js.map +1 -0
- package/dist/chunk-JUPMOISE.js +51 -0
- package/dist/chunk-JUPMOISE.js.map +1 -0
- package/dist/chunk-K5RPSVO6.js +167 -0
- package/dist/chunk-K5RPSVO6.js.map +1 -0
- package/dist/chunk-K6U27YJ6.js +78 -0
- package/dist/chunk-K6U27YJ6.js.map +1 -0
- package/dist/chunk-KDVMGMAK.js +2 -0
- package/dist/chunk-KDVMGMAK.js.map +1 -0
- package/dist/chunk-LQOZCOK3.js +74 -0
- package/dist/chunk-LQOZCOK3.js.map +1 -0
- package/dist/chunk-NAQWNX2A.js +102 -0
- package/dist/chunk-NAQWNX2A.js.map +1 -0
- package/dist/chunk-NVJVWGUS.js +152 -0
- package/dist/chunk-NVJVWGUS.js.map +1 -0
- package/dist/chunk-O2F3R3PV.js +2 -0
- package/dist/chunk-O2F3R3PV.js.map +1 -0
- package/dist/chunk-OMFQHMA6.js +2 -0
- package/dist/chunk-OMFQHMA6.js.map +1 -0
- package/dist/chunk-PE6JXVOX.js +2 -0
- package/dist/chunk-PE6JXVOX.js.map +1 -0
- package/dist/chunk-ROJEIIA4.js +52 -0
- package/dist/chunk-ROJEIIA4.js.map +1 -0
- package/dist/chunk-ROYAQKJA.js +9 -0
- package/dist/chunk-ROYAQKJA.js.map +1 -0
- package/dist/chunk-SHCZK4DO.js +45 -0
- package/dist/chunk-SHCZK4DO.js.map +1 -0
- package/dist/chunk-SXXJV6YJ.js +79 -0
- package/dist/chunk-SXXJV6YJ.js.map +1 -0
- package/dist/chunk-T7S7NJFD.js +287 -0
- package/dist/chunk-T7S7NJFD.js.map +1 -0
- package/dist/chunk-TEZ3OXAY.js +63 -0
- package/dist/chunk-TEZ3OXAY.js.map +1 -0
- package/dist/chunk-TGWPM2CG.js +199 -0
- package/dist/chunk-TGWPM2CG.js.map +1 -0
- package/dist/chunk-TSB6RE7L.js +2 -0
- package/dist/chunk-TSB6RE7L.js.map +1 -0
- package/dist/chunk-UPV2MT4O.js +2 -0
- package/dist/chunk-UPV2MT4O.js.map +1 -0
- package/dist/chunk-UZJZY4A5.js +2 -0
- package/dist/chunk-UZJZY4A5.js.map +1 -0
- package/dist/chunk-W3TQMUGX.js +2 -0
- package/dist/chunk-W3TQMUGX.js.map +1 -0
- package/dist/chunk-WN46RIOC.js +29 -0
- package/dist/chunk-WN46RIOC.js.map +1 -0
- package/dist/chunk-XZFE2YYH.js +70 -0
- package/dist/chunk-XZFE2YYH.js.map +1 -0
- package/dist/chunk-YG3KXTZW.js +2 -0
- package/dist/chunk-YG3KXTZW.js.map +1 -0
- package/dist/chunk-ZATVF42E.js +65 -0
- package/dist/chunk-ZATVF42E.js.map +1 -0
- package/dist/chunk-ZXZTLQIX.js +19 -0
- package/dist/chunk-ZXZTLQIX.js.map +1 -0
- package/dist/flow-button-edge/flow-button-edge.js +10 -0
- package/dist/flow-button-edge/flow-button-edge.js.map +1 -0
- package/dist/flow-button-edge/index.js +11 -0
- package/dist/flow-button-edge/index.js.map +1 -0
- package/dist/flow-edge/flow-edge.js +10 -0
- package/dist/flow-edge/flow-edge.js.map +1 -0
- package/dist/flow-edge/index.js +11 -0
- package/dist/flow-edge/index.js.map +1 -0
- package/dist/flow-edge-path/flow-edge-path.js +12 -0
- package/dist/flow-edge-path/flow-edge-path.js.map +1 -0
- package/dist/flow-edge-path/index.js +13 -0
- package/dist/flow-edge-path/index.js.map +1 -0
- package/dist/flow-edge-tokens/edge-path-sampler.js +10 -0
- package/dist/flow-edge-tokens/edge-path-sampler.js.map +1 -0
- package/dist/flow-edge-tokens/flow-edge-tokens.js +12 -0
- package/dist/flow-edge-tokens/flow-edge-tokens.js.map +1 -0
- package/dist/flow-edge-tokens/index.js +17 -0
- package/dist/flow-edge-tokens/index.js.map +1 -0
- package/dist/flow-floating-edge/floating-edge-geometry.js +8 -0
- package/dist/flow-floating-edge/floating-edge-geometry.js.map +1 -0
- package/dist/flow-floating-edge/flow-floating-edge.js +11 -0
- package/dist/flow-floating-edge/flow-floating-edge.js.map +1 -0
- package/dist/flow-floating-edge/index.js +15 -0
- package/dist/flow-floating-edge/index.js.map +1 -0
- package/dist/flow-group-node/flow-group-node.js +12 -0
- package/dist/flow-group-node/flow-group-node.js.map +1 -0
- package/dist/flow-group-node/index.js +13 -0
- package/dist/flow-group-node/index.js.map +1 -0
- package/dist/flow-handle/flow-handle-anchor.js +8 -0
- package/dist/flow-handle/flow-handle-anchor.js.map +1 -0
- package/dist/flow-handle/index.js +9 -0
- package/dist/flow-handle/index.js.map +1 -0
- package/dist/flow-layout/backbone.js +8 -0
- package/dist/flow-layout/backbone.js.map +1 -0
- package/dist/flow-layout/flow-layout.js +10 -0
- package/dist/flow-layout/flow-layout.js.map +1 -0
- package/dist/flow-layout/index.js +21 -0
- package/dist/flow-layout/index.js.map +1 -0
- package/dist/flow-layout/layout-flow-elk.js +9 -0
- package/dist/flow-layout/layout-flow-elk.js.map +1 -0
- package/dist/flow-layout/layout-graph.js +9 -0
- package/dist/flow-layout/layout-graph.js.map +1 -0
- package/dist/flow-layout/use-auto-layout.js +10 -0
- package/dist/flow-layout/use-auto-layout.js.map +1 -0
- package/dist/flow-layout/use-flow-layout.js +9 -0
- package/dist/flow-layout/use-flow-layout.js.map +1 -0
- package/dist/flow-mini-map/flow-mini-map.js +8 -0
- package/dist/flow-mini-map/flow-mini-map.js.map +1 -0
- package/dist/flow-mini-map/index.js +9 -0
- package/dist/flow-mini-map/index.js.map +1 -0
- package/dist/flow-node/flow-node.js +11 -0
- package/dist/flow-node/flow-node.js.map +1 -0
- package/dist/flow-node/index.js +12 -0
- package/dist/flow-node/index.js.map +1 -0
- package/dist/flow-placeholder-node/flow-placeholder-node.js +9 -0
- package/dist/flow-placeholder-node/flow-placeholder-node.js.map +1 -0
- package/dist/flow-placeholder-node/index.js +10 -0
- package/dist/flow-placeholder-node/index.js.map +1 -0
- package/dist/flow-self-loop-edge/flow-self-loop-edge.js +20 -0
- package/dist/flow-self-loop-edge/flow-self-loop-edge.js.map +1 -0
- package/dist/flow-self-loop-edge/index.js +28 -0
- package/dist/flow-self-loop-edge/index.js.map +1 -0
- package/dist/flow-self-loop-edge/self-loop-geometry.js +12 -0
- package/dist/flow-self-loop-edge/self-loop-geometry.js.map +1 -0
- package/dist/flow-smart-edge/flow-smart-edge.js +11 -0
- package/dist/flow-smart-edge/flow-smart-edge.js.map +1 -0
- package/dist/flow-smart-edge/index.js +25 -0
- package/dist/flow-smart-edge/index.js.map +1 -0
- package/dist/flow-smart-edge/smart-edge-geometry.js +18 -0
- package/dist/flow-smart-edge/smart-edge-geometry.js.map +1 -0
- package/dist/flow-weighted-edge/back-edge-geometry.js +8 -0
- package/dist/flow-weighted-edge/back-edge-geometry.js.map +1 -0
- package/dist/flow-weighted-edge/edge-aria.js +10 -0
- package/dist/flow-weighted-edge/edge-aria.js.map +1 -0
- package/dist/flow-weighted-edge/edge-label-pill.js +8 -0
- package/dist/flow-weighted-edge/edge-label-pill.js.map +1 -0
- package/dist/flow-weighted-edge/flow-weighted-edge.js +16 -0
- package/dist/flow-weighted-edge/flow-weighted-edge.js.map +1 -0
- package/dist/flow-weighted-edge/index.js +34 -0
- package/dist/flow-weighted-edge/index.js.map +1 -0
- package/dist/flow-weighted-edge/weight-scale.js +10 -0
- package/dist/flow-weighted-edge/weight-scale.js.map +1 -0
- package/dist/helper-lines/get-helper-lines.js +8 -0
- package/dist/helper-lines/get-helper-lines.js.map +1 -0
- package/dist/helper-lines/helper-lines.js +9 -0
- package/dist/helper-lines/helper-lines.js.map +1 -0
- package/dist/helper-lines/index.js +17 -0
- package/dist/helper-lines/index.js.map +1 -0
- package/dist/helper-lines/use-helper-lines.js +10 -0
- package/dist/helper-lines/use-helper-lines.js.map +1 -0
- package/dist/index.d.ts +236 -1
- package/dist/index.js +167 -2311
- package/dist/index.js.map +1 -1
- package/dist/inspector-panel/index.js +9 -0
- package/dist/inspector-panel/index.js.map +1 -0
- package/dist/inspector-panel/inspector-panel.js +8 -0
- package/dist/inspector-panel/inspector-panel.js.map +1 -0
- package/dist/legend/index.js +10 -0
- package/dist/legend/index.js.map +1 -0
- package/dist/legend/legend.js +9 -0
- package/dist/legend/legend.js.map +1 -0
- package/dist/use-flow-groups/group-operations.js +20 -0
- package/dist/use-flow-groups/group-operations.js.map +1 -0
- package/dist/use-flow-groups/index.js +25 -0
- package/dist/use-flow-groups/index.js.map +1 -0
- package/dist/use-flow-groups/use-flow-groups.js +9 -0
- package/dist/use-flow-groups/use-flow-groups.js.map +1 -0
- package/dist/zoom-controls/index.js +9 -0
- package/dist/zoom-controls/index.js.map +1 -0
- package/dist/zoom-controls/zoom-controls.js +8 -0
- package/dist/zoom-controls/zoom-controls.js.map +1 -0
- package/package.json +13 -6
- package/src/canvas-shell/canvas-shell.stories.tsx +1 -0
- package/src/canvas-shell/canvas-shell.tsx +51 -4
- package/src/canvas-shell/clamped-fit-offset.test.ts +25 -0
- package/src/canvas-shell/clamped-fit-offset.ts +30 -0
- package/src/custom-nodes/custom-nodes.stories.tsx +718 -0
- package/src/flow-edge-tokens/edge-path-sampler.test.ts +108 -0
- package/src/flow-edge-tokens/edge-path-sampler.ts +362 -0
- package/src/flow-edge-tokens/flow-edge-tokens.stories.tsx +312 -0
- package/src/flow-edge-tokens/flow-edge-tokens.test.tsx +115 -0
- package/src/flow-edge-tokens/flow-edge-tokens.tsx +94 -0
- package/src/flow-edge-tokens/index.ts +12 -0
- package/src/flow-layout/backbone.test.ts +88 -0
- package/src/flow-layout/backbone.ts +58 -0
- package/src/flow-layout/layout-flow-elk.stories.tsx +154 -0
- package/src/flow-layout/layout-flow-elk.test.ts +152 -0
- package/src/flow-layout/layout-flow-elk.ts +399 -0
- package/src/flow-mini-map/flow-mini-map.stories.tsx +10 -7
- package/src/flow-node/flow-node.stories.tsx +40 -1
- package/src/flow-self-loop-edge/flow-self-loop-edge.test.tsx +38 -0
- package/src/flow-self-loop-edge/flow-self-loop-edge.tsx +7 -0
- package/src/flow-weighted-edge/edge-label-pill.test.tsx +20 -0
- package/src/flow-weighted-edge/flow-weighted-edge.stories.tsx +1 -0
- package/src/flow-weighted-edge/flow-weighted-edge.test.tsx +40 -0
- package/src/flow-weighted-edge/flow-weighted-edge.tsx +7 -0
- package/src/index.ts +13 -0
- package/src/templates-flow-workspace.stories.tsx +7 -2
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ no token required. See `docs/CONSUMING.md`.
|
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
pnpm add @elabs-ai/components-tokens @elabs-ai/components-flow
|
|
17
|
+
pnpm add elkjs@"^0.12.0" # optional peer
|
|
17
18
|
```
|
|
18
19
|
|
|
19
20
|
## Set up styling (do not skip)
|
|
@@ -44,7 +45,7 @@ import { ThemeProvider } from "@elabs-ai/components-tokens";
|
|
|
44
45
|
|
|
45
46
|
## What's in it
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
34 exported components — including `Background`, `CanvasShell`, `Controls`, `DEFAULT_EDGE_TOKEN_RADIUS`, `DEFAULT_EDGE_WIDTH_RANGE`.
|
|
48
49
|
|
|
49
50
|
Don't guess the API — ask the CLI:
|
|
50
51
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
CanvasShell
|
|
4
|
+
} from "../chunk-75JJK6BH.js";
|
|
5
|
+
import "../chunk-ZATVF42E.js";
|
|
6
|
+
import "../chunk-DNZRAJAQ.js";
|
|
7
|
+
import "../chunk-BJ7UMCD3.js";
|
|
8
|
+
import "../chunk-EUNV2MR5.js";
|
|
9
|
+
import "../chunk-JUPMOISE.js";
|
|
10
|
+
export {
|
|
11
|
+
CanvasShell
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=canvas-shell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "../chunk-OMFQHMA6.js";
|
|
3
|
+
import {
|
|
4
|
+
CanvasShell
|
|
5
|
+
} from "../chunk-75JJK6BH.js";
|
|
6
|
+
import "../chunk-ZATVF42E.js";
|
|
7
|
+
import "../chunk-DNZRAJAQ.js";
|
|
8
|
+
import "../chunk-BJ7UMCD3.js";
|
|
9
|
+
import "../chunk-EUNV2MR5.js";
|
|
10
|
+
import "../chunk-JUPMOISE.js";
|
|
11
|
+
export {
|
|
12
|
+
CanvasShell
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/flow-weighted-edge/weight-scale.ts
|
|
4
|
+
var DEFAULT_EDGE_WIDTH_RANGE = [1.5, 8];
|
|
5
|
+
var DEFAULT_SCALE_GROUP = "__default__";
|
|
6
|
+
function computeEdgeWeightScale(edges, opts = {}) {
|
|
7
|
+
const [minWidth, maxWidth] = opts.widthRange ?? DEFAULT_EDGE_WIDTH_RANGE;
|
|
8
|
+
const result = /* @__PURE__ */ new Map();
|
|
9
|
+
const groups = /* @__PURE__ */ new Map();
|
|
10
|
+
for (const edge of edges) {
|
|
11
|
+
const weight = edge.data?.weight;
|
|
12
|
+
if (weight === void 0) {
|
|
13
|
+
result.set(edge.id, minWidth);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const group = edge.data?.scaleGroup ?? DEFAULT_SCALE_GROUP;
|
|
17
|
+
if (opts.scaleGroup !== void 0 && group !== opts.scaleGroup) continue;
|
|
18
|
+
const list = groups.get(group);
|
|
19
|
+
if (list) list.push({ id: edge.id, weight });
|
|
20
|
+
else groups.set(group, [{ id: edge.id, weight }]);
|
|
21
|
+
}
|
|
22
|
+
for (const list of groups.values()) {
|
|
23
|
+
let min = Infinity;
|
|
24
|
+
let max = -Infinity;
|
|
25
|
+
for (const { weight } of list) {
|
|
26
|
+
if (weight < min) min = weight;
|
|
27
|
+
if (weight > max) max = weight;
|
|
28
|
+
}
|
|
29
|
+
const span = max - min;
|
|
30
|
+
for (const { id, weight } of list) {
|
|
31
|
+
const width = span === 0 ? (minWidth + maxWidth) / 2 : minWidth + (weight - min) / span * (maxWidth - minWidth);
|
|
32
|
+
result.set(id, width);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
DEFAULT_EDGE_WIDTH_RANGE,
|
|
40
|
+
computeEdgeWeightScale
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=chunk-3BTU2VWZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/flow-weighted-edge/weight-scale.ts"],"sourcesContent":["/**\n * weight-scale — pure, framework-free \"edge weight → stroke width\" scale.\n *\n * `FlowWeightedEdge` calls this with every edge in the flow (via `useEdges()`,\n * memoized) so a group of edges can be min-maxed against ONE shared domain\n * instead of each edge picking its own arbitrary width. No React, no DOM: a\n * sibling (e.g. RM-045's continuous `Legend`) can call it directly to render\n * the same ramp the edges use, and it is trivially unit-testable.\n */\n\n/** Minimal edge shape the scale needs — a subset of `Edge<FlowWeightedEdgeData>`. */\nexport interface WeightedEdgeLike {\n id: string;\n data?: {\n weight?: number;\n scaleGroup?: string;\n };\n}\n\nexport interface EdgeWeightScaleOptions {\n /** Output stroke-width range, in px. @default [1.5, 8] */\n widthRange?: [number, number];\n /**\n * Restrict the domain calculation (and the returned map) to edges whose\n * `data.scaleGroup` equals this value — edges in a different group are\n * skipped entirely. Omit to compute one scale per distinct `scaleGroup`\n * present in `edges` (edges with no `scaleGroup` share one implicit\n * default group, so a flow that never sets it still gets one shared\n * domain — \"all edges in the same `<ReactFlow>`\").\n */\n scaleGroup?: string;\n}\n\n/** Matches today's fixed 1.5px `FlowEdge` stroke, so an unweighted edge is unchanged. */\nexport const DEFAULT_EDGE_WIDTH_RANGE: [number, number] = [1.5, 8];\n\nconst DEFAULT_SCALE_GROUP = \"__default__\";\n\n/**\n * Resolve every edge's `data.weight` into a stroke-width, linearly min-maxed\n * into `widthRange` per `scaleGroup`. An edge with no `data.weight` gets the\n * range floor — the existing fixed 1.5px `FlowEdge` already draws, so a plain\n * edge renders unchanged. An edge that is the only member of its group (or\n * whose group has zero weight variance) gets the midpoint of the range —\n * there is no domain to compare it against.\n */\nexport function computeEdgeWeightScale(\n edges: WeightedEdgeLike[],\n opts: EdgeWeightScaleOptions = {},\n): Map<string, number> {\n const [minWidth, maxWidth] = opts.widthRange ?? DEFAULT_EDGE_WIDTH_RANGE;\n const result = new Map<string, number>();\n const groups = new Map<string, { id: string; weight: number }[]>();\n\n for (const edge of edges) {\n const weight = edge.data?.weight;\n if (weight === undefined) {\n result.set(edge.id, minWidth);\n continue;\n }\n const group = edge.data?.scaleGroup ?? DEFAULT_SCALE_GROUP;\n if (opts.scaleGroup !== undefined && group !== opts.scaleGroup) continue;\n const list = groups.get(group);\n if (list) list.push({ id: edge.id, weight });\n else groups.set(group, [{ id: edge.id, weight }]);\n }\n\n for (const list of groups.values()) {\n let min = Infinity;\n let max = -Infinity;\n for (const { weight } of list) {\n if (weight < min) min = weight;\n if (weight > max) max = weight;\n }\n const span = max - min;\n for (const { id, weight } of list) {\n const width =\n span === 0\n ? (minWidth + maxWidth) / 2\n : minWidth + ((weight - min) / span) * (maxWidth - minWidth);\n result.set(id, width);\n }\n }\n\n return result;\n}\n"],"mappings":";;;AAkCO,IAAM,2BAA6C,CAAC,KAAK,CAAC;AAEjE,IAAM,sBAAsB;AAUrB,SAAS,uBACd,OACA,OAA+B,CAAC,GACX;AACrB,QAAM,CAAC,UAAU,QAAQ,IAAI,KAAK,cAAc;AAChD,QAAM,SAAS,oBAAI,IAAoB;AACvC,QAAM,SAAS,oBAAI,IAA8C;AAEjE,aAAW,QAAQ,OAAO;AACxB,UAAM,SAAS,KAAK,MAAM;AAC1B,QAAI,WAAW,QAAW;AACxB,aAAO,IAAI,KAAK,IAAI,QAAQ;AAC5B;AAAA,IACF;AACA,UAAM,QAAQ,KAAK,MAAM,cAAc;AACvC,QAAI,KAAK,eAAe,UAAa,UAAU,KAAK,WAAY;AAChE,UAAM,OAAO,OAAO,IAAI,KAAK;AAC7B,QAAI,KAAM,MAAK,KAAK,EAAE,IAAI,KAAK,IAAI,OAAO,CAAC;AAAA,QACtC,QAAO,IAAI,OAAO,CAAC,EAAE,IAAI,KAAK,IAAI,OAAO,CAAC,CAAC;AAAA,EAClD;AAEA,aAAW,QAAQ,OAAO,OAAO,GAAG;AAClC,QAAI,MAAM;AACV,QAAI,MAAM;AACV,eAAW,EAAE,OAAO,KAAK,MAAM;AAC7B,UAAI,SAAS,IAAK,OAAM;AACxB,UAAI,SAAS,IAAK,OAAM;AAAA,IAC1B;AACA,UAAM,OAAO,MAAM;AACnB,eAAW,EAAE,IAAI,OAAO,KAAK,MAAM;AACjC,YAAM,QACJ,SAAS,KACJ,WAAW,YAAY,IACxB,YAAa,SAAS,OAAO,QAAS,WAAW;AACvD,aAAO,IAAI,IAAI,KAAK;AAAA,IACtB;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
useFlowGroups
|
|
4
|
+
} from "./chunk-LQOZCOK3.js";
|
|
5
|
+
import {
|
|
6
|
+
FLOW_HANDLE_ANCHOR_CLASS
|
|
7
|
+
} from "./chunk-ROYAQKJA.js";
|
|
8
|
+
|
|
9
|
+
// src/flow-group-node/flow-group-node.tsx
|
|
10
|
+
import "react";
|
|
11
|
+
import { Handle, NodeResizer, Position, useNodes } from "@xyflow/react";
|
|
12
|
+
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
13
|
+
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
14
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
var toneMark = {
|
|
16
|
+
default: "text-muted-foreground",
|
|
17
|
+
accent: "text-primary-text",
|
|
18
|
+
success: "text-success",
|
|
19
|
+
warning: "text-warning",
|
|
20
|
+
destructive: "text-destructive"
|
|
21
|
+
};
|
|
22
|
+
var toneInk = {
|
|
23
|
+
default: "text-muted-foreground",
|
|
24
|
+
accent: "text-primary-text",
|
|
25
|
+
success: "text-success-text",
|
|
26
|
+
warning: "text-warning-text",
|
|
27
|
+
destructive: "text-destructive-text"
|
|
28
|
+
};
|
|
29
|
+
var handleClassName = `!size-2 !border-2 !border-flow-group-border !bg-flow-group ${FLOW_HANDLE_ANCHOR_CLASS}`;
|
|
30
|
+
function FlowGroupNode({ id, data, selected }) {
|
|
31
|
+
const { toggleCollapse } = useFlowGroups();
|
|
32
|
+
const nodes = useNodes();
|
|
33
|
+
const collapsed = data.collapsed ?? false;
|
|
34
|
+
const tone = data.tone ?? "default";
|
|
35
|
+
const liveChildCount = nodes.filter((n) => n.parentId === id).length;
|
|
36
|
+
const childCount = liveChildCount || data.childCount || 0;
|
|
37
|
+
return /* @__PURE__ */ jsxs(
|
|
38
|
+
"div",
|
|
39
|
+
{
|
|
40
|
+
className: cn(
|
|
41
|
+
"flex h-full w-full flex-col rounded-lg border bg-flow-group/60 text-foreground",
|
|
42
|
+
"border-flow-group-border transition-[box-shadow,border-color] duration-fast ease-standard motion-reduce:transition-none",
|
|
43
|
+
collapsed ? "shadow-sm" : "shadow-none",
|
|
44
|
+
selected && "ring-2 ring-ring"
|
|
45
|
+
),
|
|
46
|
+
children: [
|
|
47
|
+
!collapsed && selected ? /* @__PURE__ */ jsx(
|
|
48
|
+
NodeResizer,
|
|
49
|
+
{
|
|
50
|
+
minWidth: 160,
|
|
51
|
+
minHeight: 96,
|
|
52
|
+
lineClassName: "!border-flow-group-border",
|
|
53
|
+
handleClassName
|
|
54
|
+
}
|
|
55
|
+
) : null,
|
|
56
|
+
/* @__PURE__ */ jsx(Handle, { type: "target", position: Position.Top, className: handleClassName }),
|
|
57
|
+
/* @__PURE__ */ jsx(Handle, { type: "source", position: Position.Bottom, className: handleClassName }),
|
|
58
|
+
/* @__PURE__ */ jsxs(
|
|
59
|
+
"div",
|
|
60
|
+
{
|
|
61
|
+
className: cn(
|
|
62
|
+
"flex items-center gap-2 px-3 py-2",
|
|
63
|
+
!collapsed && "border-b border-flow-group-border"
|
|
64
|
+
),
|
|
65
|
+
children: [
|
|
66
|
+
/* @__PURE__ */ jsx(
|
|
67
|
+
"button",
|
|
68
|
+
{
|
|
69
|
+
type: "button",
|
|
70
|
+
onClick: () => toggleCollapse(id),
|
|
71
|
+
"aria-label": collapsed ? `Expand group ${data.title}` : `Collapse group ${data.title}`,
|
|
72
|
+
"aria-expanded": !collapsed,
|
|
73
|
+
className: cn(
|
|
74
|
+
"-ml-1 grid size-5 shrink-0 place-items-center rounded",
|
|
75
|
+
"text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
76
|
+
"focus-ring"
|
|
77
|
+
),
|
|
78
|
+
children: collapsed ? /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true", className: "size-4" }) : /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": "true", className: "size-4" })
|
|
79
|
+
}
|
|
80
|
+
),
|
|
81
|
+
data.icon ? /* @__PURE__ */ jsx("span", { className: cn("[&_svg]:size-4", toneMark[tone]), children: data.icon }) : null,
|
|
82
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-body font-medium", children: data.title }),
|
|
83
|
+
/* @__PURE__ */ jsx(
|
|
84
|
+
"span",
|
|
85
|
+
{
|
|
86
|
+
className: cn(
|
|
87
|
+
"shrink-0 rounded-full bg-muted px-1.5 py-0.5 text-meta font-medium tabular-nums",
|
|
88
|
+
toneInk[tone]
|
|
89
|
+
),
|
|
90
|
+
"aria-label": `${childCount} ${childCount === 1 ? "node" : "nodes"}`,
|
|
91
|
+
children: childCount
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export {
|
|
103
|
+
FlowGroupNode
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=chunk-3M3FQAD4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/flow-group-node/flow-group-node.tsx"],"sourcesContent":["import { type ReactNode } from \"react\";\nimport { Handle, NodeResizer, Position, useNodes, type Node, type NodeProps } from \"@xyflow/react\";\nimport { ChevronDown, ChevronRight } from \"lucide-react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { useFlowGroups } from \"../use-flow-groups\";\nimport { FLOW_HANDLE_ANCHOR_CLASS } from \"../flow-handle/flow-handle-anchor\";\n\n/** Visual accent for a group container. */\nexport type FlowGroupTone = \"default\" | \"accent\" | \"success\" | \"warning\" | \"destructive\";\n\nexport interface FlowGroupNodeData extends Record<string, unknown> {\n /** Header label for the group. */\n title: string;\n /** Optional leading glyph (a Lucide icon element, etc.). */\n icon?: ReactNode;\n /**\n * Whether the group is collapsed to an overview chip. Managed by\n * `useFlowGroups().collapseGroup` / `expandGroup`; the header toggle flips it.\n */\n collapsed?: boolean;\n /**\n * Fallback count shown next to the title. When omitted, the node counts its\n * live direct children from the store, so the badge stays accurate as nodes\n * are added or removed.\n */\n childCount?: number;\n /** Accent tone for the header rule + count badge. */\n tone?: FlowGroupTone;\n}\n\nexport type BrandFlowGroupNode = Node<FlowGroupNodeData, \"group\">;\n\n// #124 split this map in two: it used to serve both the icon slot (a MARK,\n// >=3:1) and the child-count text badge (running text, >=4.5:1) from one set\n// of values, which is exactly the fill-rung-as-text shape #124 fixes\n// elsewhere. `accent` is unchanged (`#399` already put it on the ink rung for\n// both slots — legal, since the ink rung clears the 3:1 mark bar trivially).\n\n/** Icon-slot tone (the leading glyph next to the title) — the 3:1 mark rung. */\nconst toneMark: Record<FlowGroupTone, string> = {\n default: \"text-muted-foreground\",\n accent: \"text-primary-text\",\n success: \"text-success\",\n warning: \"text-warning\",\n destructive: \"text-destructive\",\n};\n\n/** Child-count badge tone — running text, so it takes the >=4.5:1 ink rung. */\nconst toneInk: Record<FlowGroupTone, string> = {\n default: \"text-muted-foreground\",\n accent: \"text-primary-text\",\n success: \"text-success-text\",\n warning: \"text-warning-text\",\n destructive: \"text-destructive-text\",\n};\n\n// `FLOW_HANDLE_ANCHOR_CLASS` last: a connector dot must never be in flight when React\n// Flow measures it. See `flow-handle/flow-handle-anchor.ts`.\nconst handleClassName = `!size-2 !border-2 !border-flow-group-border !bg-flow-group ${FLOW_HANDLE_ANCHOR_CLASS}`;\n\n/**\n * Branded group container node. Register it as `nodeTypes={{ group: FlowGroupNode }}`\n * and create nodes with `type: \"group\"` + typed `data: FlowGroupNodeData`. Children\n * are re-parented onto it (`parentId`, `extent: \"parent\"`) — see `useFlowGroups`.\n *\n * The header carries the title, a live child-count badge and a collapse/expand\n * toggle (a real `<button>` with `aria-expanded`) that drives\n * `useFlowGroups().collapseGroup` / `expandGroup`. When selected (and expanded)\n * a `<NodeResizer>` lets the group be resized.\n *\n * The surface uses the FL-01 tokens `--flow-group` (fill) + `--flow-group-border`\n * (border). Under a theme whose `--flow-group` is near-transparent, the\n * drawn border does the work.\n */\nexport function FlowGroupNode({ id, data, selected }: NodeProps<BrandFlowGroupNode>) {\n const { toggleCollapse } = useFlowGroups();\n const nodes = useNodes();\n const collapsed = data.collapsed ?? false;\n const tone = data.tone ?? \"default\";\n\n // Live count of direct children; fall back to the stored count before the\n // store settles (or in a non-interactive preview).\n const liveChildCount = nodes.filter((n) => n.parentId === id).length;\n const childCount = liveChildCount || data.childCount || 0;\n\n return (\n <div\n className={cn(\n \"flex h-full w-full flex-col rounded-lg border bg-flow-group/60 text-foreground\",\n \"border-flow-group-border transition-[box-shadow,border-color] duration-fast ease-standard motion-reduce:transition-none\",\n collapsed ? \"shadow-sm\" : \"shadow-none\",\n selected && \"ring-2 ring-ring\",\n )}\n >\n {!collapsed && selected ? (\n <NodeResizer\n minWidth={160}\n minHeight={96}\n lineClassName=\"!border-flow-group-border\"\n handleClassName={handleClassName}\n />\n ) : null}\n\n {/* Handles so proxy edges can attach to the group when collapsed. */}\n <Handle type=\"target\" position={Position.Top} className={handleClassName} />\n <Handle type=\"source\" position={Position.Bottom} className={handleClassName} />\n\n <div\n className={cn(\n \"flex items-center gap-2 px-3 py-2\",\n !collapsed && \"border-b border-flow-group-border\",\n )}\n >\n <button\n type=\"button\"\n onClick={() => toggleCollapse(id)}\n aria-label={collapsed ? `Expand group ${data.title}` : `Collapse group ${data.title}`}\n aria-expanded={!collapsed}\n className={cn(\n \"-ml-1 grid size-5 shrink-0 place-items-center rounded\",\n \"text-muted-foreground hover:bg-muted hover:text-foreground\",\n \"focus-ring\",\n )}\n >\n {collapsed ? (\n <ChevronRight aria-hidden=\"true\" className=\"size-4\" />\n ) : (\n <ChevronDown aria-hidden=\"true\" className=\"size-4\" />\n )}\n </button>\n\n {data.icon ? (\n <span className={cn(\"[&_svg]:size-4\", toneMark[tone])}>{data.icon}</span>\n ) : null}\n\n <span className=\"min-w-0 flex-1 truncate text-body font-medium\">{data.title}</span>\n\n <span\n className={cn(\n \"shrink-0 rounded-full bg-muted px-1.5 py-0.5 text-meta font-medium tabular-nums\",\n toneInk[tone],\n )}\n aria-label={`${childCount} ${childCount === 1 ? \"node\" : \"nodes\"}`}\n >\n {childCount}\n </span>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAAA,OAA+B;AAC/B,SAAS,QAAQ,aAAa,UAAU,gBAA2C;AACnF,SAAS,aAAa,oBAAoB;AAC1C,SAAS,UAAU;AA4FX,cAYF,YAZE;AAxDR,IAAM,WAA0C;AAAA,EAC9C,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,aAAa;AACf;AAGA,IAAM,UAAyC;AAAA,EAC7C,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,aAAa;AACf;AAIA,IAAM,kBAAkB,8DAA8D,wBAAwB;AAgBvG,SAAS,cAAc,EAAE,IAAI,MAAM,SAAS,GAAkC;AACnF,QAAM,EAAE,eAAe,IAAI,cAAc;AACzC,QAAM,QAAQ,SAAS;AACvB,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,OAAO,KAAK,QAAQ;AAI1B,QAAM,iBAAiB,MAAM,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE;AAC9D,QAAM,aAAa,kBAAkB,KAAK,cAAc;AAExD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA,YAAY,cAAc;AAAA,QAC1B,YAAY;AAAA,MACd;AAAA,MAEC;AAAA,SAAC,aAAa,WACb;AAAA,UAAC;AAAA;AAAA,YACC,UAAU;AAAA,YACV,WAAW;AAAA,YACX,eAAc;AAAA,YACd;AAAA;AAAA,QACF,IACE;AAAA,QAGJ,oBAAC,UAAO,MAAK,UAAS,UAAU,SAAS,KAAK,WAAW,iBAAiB;AAAA,QAC1E,oBAAC,UAAO,MAAK,UAAS,UAAU,SAAS,QAAQ,WAAW,iBAAiB;AAAA,QAE7E;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,CAAC,aAAa;AAAA,YAChB;AAAA,YAEA;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,SAAS,MAAM,eAAe,EAAE;AAAA,kBAChC,cAAY,YAAY,gBAAgB,KAAK,KAAK,KAAK,kBAAkB,KAAK,KAAK;AAAA,kBACnF,iBAAe,CAAC;AAAA,kBAChB,WAAW;AAAA,oBACT;AAAA,oBACA;AAAA,oBACA;AAAA,kBACF;AAAA,kBAEC,sBACC,oBAAC,gBAAa,eAAY,QAAO,WAAU,UAAS,IAEpD,oBAAC,eAAY,eAAY,QAAO,WAAU,UAAS;AAAA;AAAA,cAEvD;AAAA,cAEC,KAAK,OACJ,oBAAC,UAAK,WAAW,GAAG,kBAAkB,SAAS,IAAI,CAAC,GAAI,eAAK,MAAK,IAChE;AAAA,cAEJ,oBAAC,UAAK,WAAU,iDAAiD,eAAK,OAAM;AAAA,cAE5E;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAW;AAAA,oBACT;AAAA,oBACA,QAAQ,IAAI;AAAA,kBACd;AAAA,kBACA,cAAY,GAAG,UAAU,IAAI,eAAe,IAAI,SAAS,OAAO;AAAA,kBAE/D;AAAA;AAAA,cACH;AAAA;AAAA;AAAA,QACF;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
pickClosestAnchors,
|
|
4
|
+
pickClosestHandles,
|
|
5
|
+
positionToSide,
|
|
6
|
+
sideToPosition
|
|
7
|
+
} from "./chunk-DHPUXKCZ.js";
|
|
8
|
+
import {
|
|
9
|
+
FlowEdgePath
|
|
10
|
+
} from "./chunk-SXXJV6YJ.js";
|
|
11
|
+
|
|
12
|
+
// src/flow-smart-edge/flow-smart-edge.tsx
|
|
13
|
+
import {
|
|
14
|
+
getBezierPath,
|
|
15
|
+
useInternalNode
|
|
16
|
+
} from "@xyflow/react";
|
|
17
|
+
import { jsx } from "react/jsx-runtime";
|
|
18
|
+
function toRect(node) {
|
|
19
|
+
return {
|
|
20
|
+
x: node.internals.positionAbsolute.x,
|
|
21
|
+
y: node.internals.positionAbsolute.y,
|
|
22
|
+
width: node.measured.width ?? 0,
|
|
23
|
+
height: node.measured.height ?? 0
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function anchorsOf(node, kind, handleId) {
|
|
27
|
+
const bounds = node.internals.handleBounds?.[kind] ?? [];
|
|
28
|
+
const origin = node.internals.positionAbsolute;
|
|
29
|
+
return bounds.filter((handle) => handleId ? handle.id === handleId : true).map((handle) => ({
|
|
30
|
+
id: handle.id ?? null,
|
|
31
|
+
x: origin.x + handle.x + handle.width / 2,
|
|
32
|
+
y: origin.y + handle.y + handle.height / 2,
|
|
33
|
+
side: positionToSide[handle.position]
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
function declaredSides(node, kind) {
|
|
37
|
+
const userNode = node.internals.userNode;
|
|
38
|
+
const data = userNode.data;
|
|
39
|
+
const configured = data?.handles?.[kind];
|
|
40
|
+
if (configured?.length) return [...configured];
|
|
41
|
+
const position = kind === "target" ? userNode.targetPosition : userNode.sourcePosition;
|
|
42
|
+
if (position) return [positionToSide[position]];
|
|
43
|
+
return [kind === "target" ? "top" : "bottom"];
|
|
44
|
+
}
|
|
45
|
+
function FlowSmartEdge({
|
|
46
|
+
id,
|
|
47
|
+
source,
|
|
48
|
+
target,
|
|
49
|
+
sourceHandleId,
|
|
50
|
+
targetHandleId,
|
|
51
|
+
markerEnd,
|
|
52
|
+
style
|
|
53
|
+
}) {
|
|
54
|
+
const sourceNode = useInternalNode(source);
|
|
55
|
+
const targetNode = useInternalNode(target);
|
|
56
|
+
if (!sourceNode || !targetNode) return null;
|
|
57
|
+
const picked = pickClosestAnchors(
|
|
58
|
+
anchorsOf(sourceNode, "source", sourceHandleId),
|
|
59
|
+
anchorsOf(targetNode, "target", targetHandleId)
|
|
60
|
+
);
|
|
61
|
+
let sx;
|
|
62
|
+
let sy;
|
|
63
|
+
let tx;
|
|
64
|
+
let ty;
|
|
65
|
+
let sourcePosition;
|
|
66
|
+
let targetPosition;
|
|
67
|
+
if (picked) {
|
|
68
|
+
({ x: sx, y: sy } = picked.source);
|
|
69
|
+
({ x: tx, y: ty } = picked.target);
|
|
70
|
+
sourcePosition = sideToPosition[picked.source.side];
|
|
71
|
+
targetPosition = sideToPosition[picked.target.side];
|
|
72
|
+
} else {
|
|
73
|
+
const fallback = pickClosestHandles(
|
|
74
|
+
toRect(sourceNode),
|
|
75
|
+
declaredSides(sourceNode, "source"),
|
|
76
|
+
toRect(targetNode),
|
|
77
|
+
declaredSides(targetNode, "target")
|
|
78
|
+
);
|
|
79
|
+
({ sx, sy, tx, ty } = fallback);
|
|
80
|
+
sourcePosition = sideToPosition[fallback.sourceSide];
|
|
81
|
+
targetPosition = sideToPosition[fallback.targetSide];
|
|
82
|
+
}
|
|
83
|
+
const [edgePath] = getBezierPath({
|
|
84
|
+
sourceX: sx,
|
|
85
|
+
sourceY: sy,
|
|
86
|
+
sourcePosition,
|
|
87
|
+
targetX: tx,
|
|
88
|
+
targetY: ty,
|
|
89
|
+
targetPosition
|
|
90
|
+
});
|
|
91
|
+
return /* @__PURE__ */ jsx(
|
|
92
|
+
FlowEdgePath,
|
|
93
|
+
{
|
|
94
|
+
id,
|
|
95
|
+
path: edgePath,
|
|
96
|
+
markerEnd,
|
|
97
|
+
"data-slot": "flow-smart-edge",
|
|
98
|
+
stroke: "var(--flow-edge)",
|
|
99
|
+
strokeWidth: 1.5,
|
|
100
|
+
style
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export {
|
|
106
|
+
FlowSmartEdge
|
|
107
|
+
};
|
|
108
|
+
//# sourceMappingURL=chunk-4VYHZM3S.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/flow-smart-edge/flow-smart-edge.tsx"],"sourcesContent":["import {\n getBezierPath,\n useInternalNode,\n type EdgeProps,\n type InternalNode,\n type Node,\n type Position,\n} from \"@xyflow/react\";\nimport { FlowEdgePath } from \"../flow-edge-path\";\nimport type { FlowHandleSide, FlowNodeData } from \"../flow-node\";\nimport {\n pickClosestAnchors,\n pickClosestHandles,\n positionToSide,\n sideToPosition,\n type HandleAnchor,\n type NodeRect,\n} from \"./smart-edge-geometry\";\n\ntype HandleKind = \"source\" | \"target\";\n\nfunction toRect(node: InternalNode): NodeRect {\n return {\n x: node.internals.positionAbsolute.x,\n y: node.internals.positionAbsolute.y,\n width: node.measured.width ?? 0,\n height: node.measured.height ?? 0,\n };\n}\n\n/**\n * The candidate anchors for one end of the edge, read from React Flow's\n * **measured** handle bounds — the DOM truth about where each dot was painted.\n *\n * `handleBounds` entries are node-relative, so the node's absolute position is\n * added back; the dot's centre is the entry's own box centre, which is why this\n * tracks any handle size or offset the node's CSS chooses instead of assuming\n * the dot sits on the side's midpoint.\n *\n * `handleId` pins the result to one handle when the edge names one\n * (`sourceHandle`/`targetHandle`), so an explicitly wired edge is never\n * re-routed to a different dot.\n */\nfunction anchorsOf(\n node: InternalNode,\n kind: HandleKind,\n handleId: string | null | undefined,\n): HandleAnchor[] {\n const bounds = node.internals.handleBounds?.[kind] ?? [];\n const origin = node.internals.positionAbsolute;\n return bounds\n .filter((handle) => (handleId ? handle.id === handleId : true))\n .map((handle) => ({\n id: handle.id ?? null,\n x: origin.x + handle.x + handle.width / 2,\n y: origin.y + handle.y + handle.height / 2,\n side: positionToSide[handle.position],\n }));\n}\n\n/**\n * The sides a `FlowNode` genuinely renders a handle on, for the pre-measurement\n * fallback only: its `data.handles` config when it has one, otherwise the\n * single default handle React Flow's layout direction placed\n * (`targetPosition`/`sourcePosition`, defaulting to top-in / bottom-out).\n */\nfunction declaredSides(node: InternalNode, kind: HandleKind): FlowHandleSide[] {\n const userNode = node.internals.userNode as Node<Record<string, unknown>>;\n const data = userNode.data as FlowNodeData | undefined;\n const configured = data?.handles?.[kind];\n if (configured?.length) return [...configured];\n const position = (kind === \"target\" ? userNode.targetPosition : userNode.sourcePosition) as\n | Position\n | undefined;\n if (position) return [positionToSide[position]];\n return [kind === \"target\" ? \"top\" : \"bottom\"];\n}\n\n/**\n * Branded edge that picks the closest source/target **handle** pair and routes a\n * bezier between them. Anchors are recomputed every render, so they flip as\n * nodes are dragged. Register it in `edgeTypes={{ smart: FlowSmartEdge }}`; give\n * the connected nodes a `data.handles` config (e.g. `FLOW_ALL_SIDE_HANDLES`) to\n * offer it more than the default two anchors. Uses the `--flow-edge` token,\n * matching `FlowEdge`.\n *\n * ## The path terminates ON the handle dot\n *\n * The endpoints come from React Flow's **measured `handleBounds`** — the centre\n * of the rendered dot — not from a point derived from the node's rectangle.\n * That is a correctness property, not a refinement: an earlier version slid each\n * anchor along the chosen side toward the other node (to fan out edges sharing a\n * side) and picked sides from a four-way fallback list, so a line could meet the\n * node up to half a side away from any dot — measured at ~22px on multi-side\n * nodes and ~124px on nodes carrying only the default top/bottom handles, where\n * a left/right side with no handle at all could be chosen. Two edges leaving the\n * same handle now leave from the same point and diverge, exactly as React Flow's\n * own edges do.\n */\nexport function FlowSmartEdge({\n id,\n source,\n target,\n sourceHandleId,\n targetHandleId,\n markerEnd,\n style,\n}: EdgeProps) {\n const sourceNode = useInternalNode(source);\n const targetNode = useInternalNode(target);\n if (!sourceNode || !targetNode) return null;\n\n const picked = pickClosestAnchors(\n anchorsOf(sourceNode, \"source\", sourceHandleId),\n anchorsOf(targetNode, \"target\", targetHandleId),\n );\n\n let sx: number;\n let sy: number;\n let tx: number;\n let ty: number;\n let sourcePosition: Position;\n let targetPosition: Position;\n\n if (picked) {\n ({ x: sx, y: sy } = picked.source);\n ({ x: tx, y: ty } = picked.target);\n sourcePosition = sideToPosition[picked.source.side];\n targetPosition = sideToPosition[picked.target.side];\n } else {\n // Handles not measured yet (first render). Fall back to the side midpoints\n // of the sides the nodes actually declare, so the edge is drawn sanely for\n // the frame or two before React Flow reports real handle bounds.\n const fallback = pickClosestHandles(\n toRect(sourceNode),\n declaredSides(sourceNode, \"source\"),\n toRect(targetNode),\n declaredSides(targetNode, \"target\"),\n );\n ({ sx, sy, tx, ty } = fallback);\n sourcePosition = sideToPosition[fallback.sourceSide];\n targetPosition = sideToPosition[fallback.targetSide];\n }\n\n const [edgePath] = getBezierPath({\n sourceX: sx,\n sourceY: sy,\n sourcePosition,\n targetX: tx,\n targetY: ty,\n targetPosition,\n });\n\n return (\n <FlowEdgePath\n id={id}\n path={edgePath}\n markerEnd={markerEnd}\n data-slot=\"flow-smart-edge\"\n stroke=\"var(--flow-edge)\"\n strokeWidth={1.5}\n style={style}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,OAKK;AAmJH;AArIJ,SAAS,OAAO,MAA8B;AAC5C,SAAO;AAAA,IACL,GAAG,KAAK,UAAU,iBAAiB;AAAA,IACnC,GAAG,KAAK,UAAU,iBAAiB;AAAA,IACnC,OAAO,KAAK,SAAS,SAAS;AAAA,IAC9B,QAAQ,KAAK,SAAS,UAAU;AAAA,EAClC;AACF;AAeA,SAAS,UACP,MACA,MACA,UACgB;AAChB,QAAM,SAAS,KAAK,UAAU,eAAe,IAAI,KAAK,CAAC;AACvD,QAAM,SAAS,KAAK,UAAU;AAC9B,SAAO,OACJ,OAAO,CAAC,WAAY,WAAW,OAAO,OAAO,WAAW,IAAK,EAC7D,IAAI,CAAC,YAAY;AAAA,IAChB,IAAI,OAAO,MAAM;AAAA,IACjB,GAAG,OAAO,IAAI,OAAO,IAAI,OAAO,QAAQ;AAAA,IACxC,GAAG,OAAO,IAAI,OAAO,IAAI,OAAO,SAAS;AAAA,IACzC,MAAM,eAAe,OAAO,QAAQ;AAAA,EACtC,EAAE;AACN;AAQA,SAAS,cAAc,MAAoB,MAAoC;AAC7E,QAAM,WAAW,KAAK,UAAU;AAChC,QAAM,OAAO,SAAS;AACtB,QAAM,aAAa,MAAM,UAAU,IAAI;AACvC,MAAI,YAAY,OAAQ,QAAO,CAAC,GAAG,UAAU;AAC7C,QAAM,WAAY,SAAS,WAAW,SAAS,iBAAiB,SAAS;AAGzE,MAAI,SAAU,QAAO,CAAC,eAAe,QAAQ,CAAC;AAC9C,SAAO,CAAC,SAAS,WAAW,QAAQ,QAAQ;AAC9C;AAuBO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAc;AACZ,QAAM,aAAa,gBAAgB,MAAM;AACzC,QAAM,aAAa,gBAAgB,MAAM;AACzC,MAAI,CAAC,cAAc,CAAC,WAAY,QAAO;AAEvC,QAAM,SAAS;AAAA,IACb,UAAU,YAAY,UAAU,cAAc;AAAA,IAC9C,UAAU,YAAY,UAAU,cAAc;AAAA,EAChD;AAEA,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI;AAEJ,MAAI,QAAQ;AACV,KAAC,EAAE,GAAG,IAAI,GAAG,GAAG,IAAI,OAAO;AAC3B,KAAC,EAAE,GAAG,IAAI,GAAG,GAAG,IAAI,OAAO;AAC3B,qBAAiB,eAAe,OAAO,OAAO,IAAI;AAClD,qBAAiB,eAAe,OAAO,OAAO,IAAI;AAAA,EACpD,OAAO;AAIL,UAAM,WAAW;AAAA,MACf,OAAO,UAAU;AAAA,MACjB,cAAc,YAAY,QAAQ;AAAA,MAClC,OAAO,UAAU;AAAA,MACjB,cAAc,YAAY,QAAQ;AAAA,IACpC;AACA,KAAC,EAAE,IAAI,IAAI,IAAI,GAAG,IAAI;AACtB,qBAAiB,eAAe,SAAS,UAAU;AACnD,qBAAiB,eAAe,SAAS,UAAU;AAAA,EACrD;AAEA,QAAM,CAAC,QAAQ,IAAI,cAAc;AAAA,IAC/B,SAAS;AAAA,IACT,SAAS;AAAA,IACT;AAAA,IACA,SAAS;AAAA,IACT,SAAS;AAAA,IACT;AAAA,EACF,CAAC;AAED,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA,aAAU;AAAA,MACV,QAAO;AAAA,MACP,aAAa;AAAA,MACb;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
FLOW_HANDLE_ANCHOR_CLASS
|
|
4
|
+
} from "./chunk-ROYAQKJA.js";
|
|
5
|
+
|
|
6
|
+
// src/flow-placeholder-node/flow-placeholder-node.tsx
|
|
7
|
+
import { Handle, Position } from "@xyflow/react";
|
|
8
|
+
import { Plus } from "lucide-react";
|
|
9
|
+
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
function FlowPlaceholderNode({ data }) {
|
|
12
|
+
const label = data.label ?? "Add node";
|
|
13
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
14
|
+
/* @__PURE__ */ jsx(
|
|
15
|
+
Handle,
|
|
16
|
+
{
|
|
17
|
+
type: "target",
|
|
18
|
+
position: Position.Top,
|
|
19
|
+
className: `!size-2 !border-2 !border-flow-edge !bg-flow-node ${FLOW_HANDLE_ANCHOR_CLASS}`
|
|
20
|
+
}
|
|
21
|
+
),
|
|
22
|
+
/* @__PURE__ */ jsxs(
|
|
23
|
+
"button",
|
|
24
|
+
{
|
|
25
|
+
type: "button",
|
|
26
|
+
"aria-label": label,
|
|
27
|
+
onClick: () => data.onActivate?.(),
|
|
28
|
+
className: cn(
|
|
29
|
+
"flex min-w-44 items-center justify-center gap-1.5 rounded-lg border border-dashed border-flow-group-border bg-flow-group px-3 py-2 text-muted-foreground",
|
|
30
|
+
"transition-colors duration-fast ease-standard hover:bg-accent hover:text-accent-foreground",
|
|
31
|
+
"focus-ring"
|
|
32
|
+
),
|
|
33
|
+
children: [
|
|
34
|
+
/* @__PURE__ */ jsx(Plus, { className: "size-4", "aria-hidden": "true" }),
|
|
35
|
+
/* @__PURE__ */ jsx("span", { className: "text-body", children: label })
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
] });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
FlowPlaceholderNode
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=chunk-5TVVM4GR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/flow-placeholder-node/flow-placeholder-node.tsx"],"sourcesContent":["import { Handle, Position, type Node, type NodeProps } from \"@xyflow/react\";\nimport { Plus } from \"lucide-react\";\nimport { FLOW_HANDLE_ANCHOR_CLASS } from \"../flow-handle/flow-handle-anchor\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nexport interface FlowPlaceholderNodeData extends Record<string, unknown> {\n /** Label rendered inside the placeholder. @default \"Add node\" */\n label?: string;\n /** Called when the placeholder is activated (click, Enter, or Space). */\n onActivate?: () => void;\n}\n\nexport type BrandFlowPlaceholderNode = Node<FlowPlaceholderNodeData, \"placeholder\">;\n\n/**\n * Dashed, muted \"add here\" node — a clickable affordance that grows the\n * graph. Register it in `nodeTypes={{ placeholder: FlowPlaceholderNode }}`\n * and create nodes with `type: \"placeholder\"` and `data: FlowPlaceholderNodeData`.\n *\n * Renders a real `<button>` (keyboard-activatable via native Enter/Space) with\n * an `aria-label`, and a single **target** `<Handle>` (top) so an existing\n * edge can point at it. Fires `data.onActivate?.()` on activation — the\n * typical handler converts the placeholder into a real `FlowNode` and grows a\n * fresh placeholder beneath it (see the \"Placeholder tail\" story).\n */\nexport function FlowPlaceholderNode({ data }: NodeProps<BrandFlowPlaceholderNode>) {\n const label = data.label ?? \"Add node\";\n return (\n <div className=\"relative\">\n <Handle\n type=\"target\"\n position={Position.Top}\n // `FLOW_HANDLE_ANCHOR_CLASS`: a connector dot must never be in flight when\n // React Flow measures it. See `flow-handle/flow-handle-anchor.ts`.\n className={`!size-2 !border-2 !border-flow-edge !bg-flow-node ${FLOW_HANDLE_ANCHOR_CLASS}`}\n />\n <button\n type=\"button\"\n aria-label={label}\n onClick={() => data.onActivate?.()}\n className={cn(\n \"flex min-w-44 items-center justify-center gap-1.5 rounded-lg border border-dashed border-flow-group-border bg-flow-group px-3 py-2 text-muted-foreground\",\n \"transition-colors duration-fast ease-standard hover:bg-accent hover:text-accent-foreground\",\n \"focus-ring\",\n )}\n >\n <Plus className=\"size-4\" aria-hidden=\"true\" />\n <span className=\"text-body\">{label}</span>\n </button>\n </div>\n );\n}\n"],"mappings":";;;;;;AAAA,SAAS,QAAQ,gBAA2C;AAC5D,SAAS,YAAY;AAErB,SAAS,UAAU;AA0Bb,cAOA,YAPA;AAJC,SAAS,oBAAoB,EAAE,KAAK,GAAwC;AACjF,QAAM,QAAQ,KAAK,SAAS;AAC5B,SACE,qBAAC,SAAI,WAAU,YACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,UAAU,SAAS;AAAA,QAGnB,WAAW,qDAAqD,wBAAwB;AAAA;AAAA,IAC1F;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAY;AAAA,QACZ,SAAS,MAAM,KAAK,aAAa;AAAA,QACjC,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,QAEA;AAAA,8BAAC,QAAK,WAAU,UAAS,eAAY,QAAO;AAAA,UAC5C,oBAAC,UAAK,WAAU,aAAa,iBAAM;AAAA;AAAA;AAAA,IACrC;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/flow-layout/backbone.ts
|
|
4
|
+
function pinBackbone(nodes, edges, variant) {
|
|
5
|
+
const known = new Set(nodes.map((node) => node.id));
|
|
6
|
+
const nodeIds = [];
|
|
7
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8
|
+
for (const id of variant) {
|
|
9
|
+
if (!known.has(id) || seen.has(id)) continue;
|
|
10
|
+
seen.add(id);
|
|
11
|
+
nodeIds.push(id);
|
|
12
|
+
}
|
|
13
|
+
const edgeIds = [];
|
|
14
|
+
for (let i = 0; i < nodeIds.length - 1; i += 1) {
|
|
15
|
+
const edge = edges.find((e) => e.source === nodeIds[i] && e.target === nodeIds[i + 1]);
|
|
16
|
+
if (edge) edgeIds.push(edge.id);
|
|
17
|
+
}
|
|
18
|
+
return { nodeIds, edgeIds };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
pinBackbone
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=chunk-6LQD5Y5R.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/flow-layout/backbone.ts"],"sourcesContent":["import type { Edge, Node } from \"@xyflow/react\";\n\n/**\n * One path through the graph to lay out on a single straight line (RM-067).\n *\n * Produced by {@link pinBackbone}, consumed by `layoutFlowElk`'s `backbone` option. Plain\n * data, so it clones, serializes and memoizes like any other layout input.\n */\nexport interface FlowElkBackbone {\n /** Node ids on the pinned path, in path order. */\n nodeIds: string[];\n /** Ids of the edges joining consecutive pinned nodes, in path order. */\n edgeIds: string[];\n}\n\n/**\n * Pin a variant's activity sequence to one straight row (LR/RL) or column (TB/BT) — the\n * Apromore-style \"backbone\" layout (RM-067).\n *\n * A pre-pass, not a layout: hand the result to `layoutFlowElk` as `backbone` and the rest of\n * the graph lays out around it. ELK gets the joining edges at the highest straightness\n * priority, then every layer holding a pinned node is translated as a whole along the\n * cross axis so the pinned nodes share one line — a whole-layer shift, so no two nodes of\n * a layer can ever be pushed onto each other.\n *\n * `variant` is the sequence of NODE IDS — for a process graph, the most frequent variant's\n * activities, which are the activity nodes' ids. An id with no node is skipped; a repeated\n * activity (rework inside the variant) is pinned once, at its first occurrence, because a\n * node cannot sit on a straight line twice.\n *\n * @example\n * ```ts\n * const backbone = pinBackbone(nodes, edges, variants[0].activities);\n * const { nodes: laidOut } = await layoutFlowElk(nodes, edges, { direction: \"LR\", backbone });\n * ```\n */\nexport function pinBackbone(\n nodes: readonly Node[],\n edges: readonly Edge[],\n variant: readonly string[],\n): FlowElkBackbone {\n const known = new Set(nodes.map((node) => node.id));\n const nodeIds: string[] = [];\n const seen = new Set<string>();\n for (const id of variant) {\n if (!known.has(id) || seen.has(id)) continue;\n seen.add(id);\n nodeIds.push(id);\n }\n\n const edgeIds: string[] = [];\n for (let i = 0; i < nodeIds.length - 1; i += 1) {\n const edge = edges.find((e) => e.source === nodeIds[i] && e.target === nodeIds[i + 1]);\n if (edge) edgeIds.push(edge.id);\n }\n\n return { nodeIds, edgeIds };\n}\n"],"mappings":";;;AAoCO,SAAS,YACd,OACA,OACA,SACiB;AACjB,QAAM,QAAQ,IAAI,IAAI,MAAM,IAAI,CAAC,SAAS,KAAK,EAAE,CAAC;AAClD,QAAM,UAAoB,CAAC;AAC3B,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,MAAM,SAAS;AACxB,QAAI,CAAC,MAAM,IAAI,EAAE,KAAK,KAAK,IAAI,EAAE,EAAG;AACpC,SAAK,IAAI,EAAE;AACX,YAAQ,KAAK,EAAE;AAAA,EACjB;AAEA,QAAM,UAAoB,CAAC;AAC3B,WAAS,IAAI,GAAG,IAAI,QAAQ,SAAS,GAAG,KAAK,GAAG;AAC9C,UAAM,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,WAAW,QAAQ,CAAC,KAAK,EAAE,WAAW,QAAQ,IAAI,CAAC,CAAC;AACrF,QAAI,KAAM,SAAQ,KAAK,KAAK,EAAE;AAAA,EAChC;AAEA,SAAO,EAAE,SAAS,QAAQ;AAC5B;","names":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
layoutGraph
|
|
4
|
+
} from "./chunk-DNX7G3KA.js";
|
|
5
|
+
|
|
6
|
+
// src/flow-layout/use-auto-layout.ts
|
|
7
|
+
import { useMemo } from "react";
|
|
8
|
+
function useAutoLayout(nodes, edges, options) {
|
|
9
|
+
return useMemo(
|
|
10
|
+
() => layoutGraph(nodes, edges, options),
|
|
11
|
+
[nodes, edges, options]
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
useAutoLayout
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=chunk-6SR26QY2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/flow-layout/use-auto-layout.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport type { Edge, Node } from \"@xyflow/react\";\nimport { layoutGraph, type LayoutOptions } from \"./layout-graph\";\n\n/**\n * Memoized `layoutGraph` — pure graph-geometry layout (no live canvas access,\n * no side effects). Recomputes only when `nodes`, `edges`, or `options`\n * change; pass stable references (e.g. from `useNodesState`/`useEdgesState`\n * and a memoized `options` object) to avoid recomputing every render.\n *\n * Use this when you want laid-out nodes to render with (e.g. feeding\n * `useNodesState`'s initial value, or a derived/read-only view). To apply a\n * layout to a *live* React Flow instance (read current nodes, set them back,\n * fit the view), call `layoutGraph` directly from an event handler instead —\n * see the `LayoutGraph` story.\n */\nexport function useAutoLayout<NodeType extends Node = Node, EdgeType extends Edge = Edge>(\n nodes: NodeType[],\n edges: EdgeType[],\n options: LayoutOptions,\n): NodeType[] {\n return useMemo(\n () => layoutGraph<NodeType, EdgeType>(nodes, edges, options),\n [nodes, edges, options],\n );\n}\n"],"mappings":";;;;;;AAAA,SAAS,eAAe;AAgBjB,SAAS,cACd,OACA,OACA,SACY;AACZ,SAAO;AAAA,IACL,MAAM,YAAgC,OAAO,OAAO,OAAO;AAAA,IAC3D,CAAC,OAAO,OAAO,OAAO;AAAA,EACxB;AACF;","names":[]}
|