@elabs-ai/components-flow 4.0.0 → 4.1.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 +8 -8
- package/dist/index.d.ts +734 -20
- package/dist/index.js +976 -178
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/canvas-shell/canvas-shell.tsx +116 -1
- package/src/canvas-shell/use-measured-nodes.ts +101 -0
- package/src/flow-button-edge/flow-button-edge.stories.tsx +13 -0
- package/src/flow-button-edge/flow-button-edge.tsx +8 -10
- package/src/flow-edge/flow-edge.stories.tsx +20 -0
- package/src/flow-edge/flow-edge.tsx +10 -3
- package/src/flow-edge-path/flow-edge-path.tsx +149 -0
- package/src/flow-edge-path/index.ts +1 -0
- package/src/flow-edge-path/no-raw-base-edge.test.ts +45 -0
- package/src/flow-floating-edge/flow-floating-edge.tsx +7 -3
- package/src/flow-group-node/flow-group-node.stories.tsx +1 -1
- package/src/flow-group-node/flow-group-node.tsx +24 -8
- package/src/flow-handle/flow-handle-anchor.test.tsx +97 -0
- package/src/flow-handle/flow-handle-anchor.ts +36 -0
- package/src/flow-handle/index.ts +1 -0
- package/src/flow-layout/flow-layout.stories.tsx +2 -2
- package/src/flow-layout/flow-layout.test.tsx +91 -0
- package/src/flow-layout/flow-layout.ts +77 -1
- package/src/flow-layout/layout-graph.test.ts +83 -2
- package/src/flow-layout/layout-graph.ts +23 -15
- package/src/flow-mini-map/flow-mini-map.stories.tsx +86 -0
- package/src/flow-node/flow-node.stories.tsx +151 -0
- package/src/flow-node/flow-node.tsx +56 -1
- package/src/flow-placeholder-node/flow-placeholder-node.tsx +5 -2
- package/src/flow-self-loop-edge/flow-self-loop-edge.stories.tsx +275 -0
- package/src/flow-self-loop-edge/flow-self-loop-edge.test.tsx +196 -0
- package/src/flow-self-loop-edge/flow-self-loop-edge.tsx +172 -0
- package/src/flow-self-loop-edge/index.ts +13 -0
- package/src/flow-self-loop-edge/self-loop-geometry.test.ts +128 -0
- package/src/flow-self-loop-edge/self-loop-geometry.ts +165 -0
- package/src/flow-smart-edge/flow-smart-edge.stories.tsx +55 -8
- package/src/flow-smart-edge/flow-smart-edge.tsx +125 -35
- package/src/flow-smart-edge/index.ts +5 -1
- package/src/flow-smart-edge/smart-edge-geometry.test.ts +88 -47
- package/src/flow-smart-edge/smart-edge-geometry.ts +69 -33
- package/src/flow-weighted-edge/back-edge-geometry.test.ts +54 -0
- package/src/flow-weighted-edge/back-edge-geometry.ts +60 -0
- package/src/flow-weighted-edge/edge-aria.test.ts +108 -0
- package/src/flow-weighted-edge/edge-aria.ts +117 -0
- package/src/flow-weighted-edge/edge-label-pill.test.tsx +65 -0
- package/src/flow-weighted-edge/edge-label-pill.tsx +82 -0
- package/src/flow-weighted-edge/flow-weighted-edge.stories.tsx +691 -0
- package/src/flow-weighted-edge/flow-weighted-edge.test.tsx +405 -0
- package/src/flow-weighted-edge/flow-weighted-edge.tsx +308 -0
- package/src/flow-weighted-edge/index.ts +18 -0
- package/src/flow-weighted-edge/weight-scale.test.ts +92 -0
- package/src/flow-weighted-edge/weight-scale.ts +86 -0
- package/src/index.ts +9 -0
- package/src/inspector-panel/inspector-panel.stories.tsx +1 -1
- package/src/inspector-panel/inspector-panel.test.tsx +20 -0
- package/src/inspector-panel/inspector-panel.tsx +13 -3
- package/src/legend/index.ts +7 -1
- package/src/legend/legend.stories.tsx +126 -0
- package/src/legend/legend.test.tsx +169 -0
- package/src/legend/legend.tsx +214 -3
- package/src/templates-flow-workspace.stories.tsx +1 -1
- package/src/testing/canvas-framing.test.ts +107 -0
- package/src/testing/canvas-framing.ts +396 -0
- package/src/testing/edge-anchors.ts +107 -0
- package/src/testing/index.ts +36 -0
- package/src/zoom-controls/zoom-controls.tsx +1 -1
package/dist/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/canvas-shell/canvas-shell.tsx
|
|
4
|
-
import "react";
|
|
4
|
+
import { useEffect } from "react";
|
|
5
5
|
import {
|
|
6
6
|
Background,
|
|
7
7
|
ReactFlow,
|
|
8
|
-
ReactFlowProvider
|
|
8
|
+
ReactFlowProvider,
|
|
9
|
+
useNodesInitialized,
|
|
10
|
+
useReactFlow as useReactFlow2,
|
|
11
|
+
useStoreApi
|
|
9
12
|
} from "@xyflow/react";
|
|
10
13
|
import { cn as cn2 } from "@elabs-ai/components-ui/lib/cn";
|
|
11
14
|
|
|
@@ -149,6 +152,51 @@ function useHelperLines(onNodesChange, options = {}) {
|
|
|
149
152
|
return { onNodesChange: handleNodesChange, helperLineHorizontal, helperLineVertical };
|
|
150
153
|
}
|
|
151
154
|
|
|
155
|
+
// src/canvas-shell/use-measured-nodes.ts
|
|
156
|
+
import { useCallback as useCallback2, useMemo, useRef, useState as useState2 } from "react";
|
|
157
|
+
function useMeasuredNodes(nodes, onNodesChange) {
|
|
158
|
+
const measured = useRef(/* @__PURE__ */ new Map());
|
|
159
|
+
const [version, setVersion] = useState2(0);
|
|
160
|
+
const handleNodesChange = useCallback2(
|
|
161
|
+
(changes) => {
|
|
162
|
+
let touched = false;
|
|
163
|
+
for (const change of changes) {
|
|
164
|
+
if (change.type !== "dimensions" || !change.dimensions) continue;
|
|
165
|
+
const previous = measured.current.get(change.id);
|
|
166
|
+
if (previous?.width === change.dimensions.width && previous.height === change.dimensions.height) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
measured.current.set(change.id, { ...change.dimensions });
|
|
170
|
+
touched = true;
|
|
171
|
+
}
|
|
172
|
+
onNodesChange?.(changes);
|
|
173
|
+
if (touched) setVersion((n) => n + 1);
|
|
174
|
+
},
|
|
175
|
+
[onNodesChange]
|
|
176
|
+
);
|
|
177
|
+
const mergedNodes = useMemo(() => {
|
|
178
|
+
if (!nodes) return nodes;
|
|
179
|
+
void version;
|
|
180
|
+
let changed = false;
|
|
181
|
+
const next = nodes.map((node) => {
|
|
182
|
+
const dimensions = measured.current.get(node.id);
|
|
183
|
+
if (!dimensions) return node;
|
|
184
|
+
if (node.measured?.width === dimensions.width && node.measured?.height === dimensions.height) {
|
|
185
|
+
return node;
|
|
186
|
+
}
|
|
187
|
+
changed = true;
|
|
188
|
+
return { ...node, measured: dimensions };
|
|
189
|
+
});
|
|
190
|
+
return changed ? next : nodes;
|
|
191
|
+
}, [nodes, version]);
|
|
192
|
+
return {
|
|
193
|
+
nodes: mergedNodes,
|
|
194
|
+
// Only take the handler over when there is something to measure INTO. An uncontrolled
|
|
195
|
+
// canvas is left exactly as it was.
|
|
196
|
+
onNodesChange: nodes ? handleNodesChange : onNodesChange
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
152
200
|
// src/canvas-shell/canvas-shell.tsx
|
|
153
201
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
154
202
|
var DEFAULT_ARIA_LABEL_CONFIG = {
|
|
@@ -177,22 +225,65 @@ function CanvasShellBase({
|
|
|
177
225
|
children,
|
|
178
226
|
className,
|
|
179
227
|
ariaLabelConfig,
|
|
228
|
+
nodes,
|
|
229
|
+
onNodesChange,
|
|
230
|
+
fitViewKey,
|
|
231
|
+
fitViewKeyOptions,
|
|
180
232
|
...props
|
|
181
233
|
}) {
|
|
234
|
+
const measured = useMeasuredNodes(nodes, onNodesChange);
|
|
182
235
|
return /* @__PURE__ */ jsx2("div", { className: cn2("h-full w-full bg-canvas", className), children: /* @__PURE__ */ jsxs2(
|
|
183
236
|
ReactFlow,
|
|
184
237
|
{
|
|
185
238
|
fitView: true,
|
|
186
239
|
proOptions: { hideAttribution: true },
|
|
187
240
|
ariaLabelConfig: { ...DEFAULT_ARIA_LABEL_CONFIG, ...ariaLabelConfig },
|
|
241
|
+
nodes: measured.nodes,
|
|
242
|
+
onNodesChange: measured.onNodesChange,
|
|
188
243
|
...props,
|
|
189
244
|
children: [
|
|
190
245
|
background ? /* @__PURE__ */ jsx2(Background, { gap: 20, size: 1, color: "var(--canvas-grid)" }) : null,
|
|
246
|
+
fitViewKey === void 0 ? null : /* @__PURE__ */ jsx2(FitViewOnKey, { fitViewKey, options: fitViewKeyOptions }),
|
|
191
247
|
children
|
|
192
248
|
]
|
|
193
249
|
}
|
|
194
250
|
) });
|
|
195
251
|
}
|
|
252
|
+
function FitViewOnKey({
|
|
253
|
+
fitViewKey,
|
|
254
|
+
options
|
|
255
|
+
}) {
|
|
256
|
+
const { fitView, getNodes, getNodesBounds: getNodesBounds2, setViewport } = useReactFlow2();
|
|
257
|
+
const store = useStoreApi();
|
|
258
|
+
const nodesInitialized = useNodesInitialized();
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
if (!nodesInitialized) return;
|
|
261
|
+
void fitView(options).then(
|
|
262
|
+
() => anchorToStartWhenClamped(store, setViewport, getNodesBounds2, getNodes(), options)
|
|
263
|
+
);
|
|
264
|
+
}, [fitViewKey, nodesInitialized, fitView, getNodes, getNodesBounds2, setViewport, store]);
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
function anchorToStartWhenClamped(store, setViewport, getNodesBounds2, nodes, options) {
|
|
268
|
+
if (nodes.length === 0) return;
|
|
269
|
+
const { width, height, transform, panZoom } = store.getState();
|
|
270
|
+
const [x, y, zoom] = transform;
|
|
271
|
+
if (!width || !height || !zoom) return;
|
|
272
|
+
const bounds = getNodesBounds2(nodes);
|
|
273
|
+
const padding = typeof options?.padding === "number" ? options.padding : 0.1;
|
|
274
|
+
const padX = width * padding;
|
|
275
|
+
const padY = height * padding;
|
|
276
|
+
const overflowsX = bounds.width * zoom > width - padX * 2 + 1;
|
|
277
|
+
const overflowsY = bounds.height * zoom > height - padY * 2 + 1;
|
|
278
|
+
if (!overflowsX && !overflowsY) return;
|
|
279
|
+
const next = {
|
|
280
|
+
x: overflowsX ? padX - bounds.x * zoom : x,
|
|
281
|
+
y: overflowsY ? padY - bounds.y * zoom : y,
|
|
282
|
+
zoom
|
|
283
|
+
};
|
|
284
|
+
if (!panZoom) return;
|
|
285
|
+
void setViewport(next);
|
|
286
|
+
}
|
|
196
287
|
function CanvasShellWithHelperLines({
|
|
197
288
|
onNodesChange,
|
|
198
289
|
children,
|
|
@@ -209,6 +300,9 @@ function CanvasShellWithHelperLines({
|
|
|
209
300
|
] });
|
|
210
301
|
}
|
|
211
302
|
|
|
303
|
+
// src/flow-handle/flow-handle-anchor.ts
|
|
304
|
+
var FLOW_HANDLE_ANCHOR_CLASS = "transition-none";
|
|
305
|
+
|
|
212
306
|
// src/flow-node/flow-node.tsx
|
|
213
307
|
import "react";
|
|
214
308
|
import { Handle, Position } from "@xyflow/react";
|
|
@@ -251,7 +345,7 @@ var sidePosition = {
|
|
|
251
345
|
bottom: Position.Bottom,
|
|
252
346
|
left: Position.Left
|
|
253
347
|
};
|
|
254
|
-
var handleClassName =
|
|
348
|
+
var handleClassName = `!size-2 !border-2 !border-flow-edge !bg-flow-node ${FLOW_HANDLE_ANCHOR_CLASS}`;
|
|
255
349
|
function FlowNode({
|
|
256
350
|
data,
|
|
257
351
|
selected,
|
|
@@ -263,11 +357,13 @@ function FlowNode({
|
|
|
263
357
|
return /* @__PURE__ */ jsxs3(
|
|
264
358
|
"div",
|
|
265
359
|
{
|
|
360
|
+
"data-slot": "flow-node",
|
|
266
361
|
"data-tone": tone,
|
|
267
362
|
className: cn3(
|
|
268
363
|
"min-w-44 rounded-lg border bg-flow-node px-3 py-2 text-flow-node-foreground shadow-sm transition-[box-shadow,border-color] duration-fast ease-standard",
|
|
269
364
|
toneRing[tone],
|
|
270
|
-
selected && "ring-2 ring-ring"
|
|
365
|
+
selected && "ring-2 ring-ring",
|
|
366
|
+
"[[data-id]:focus-visible_&]:focus-ring-static"
|
|
271
367
|
),
|
|
272
368
|
children: [
|
|
273
369
|
data.handles ? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
@@ -318,6 +414,7 @@ function FlowNode({
|
|
|
318
414
|
] }),
|
|
319
415
|
ToneIcon ? /* @__PURE__ */ jsx3(ToneIcon, { "aria-hidden": "true", className: cn3("size-3.5 shrink-0", toneIconColor[tone]) }) : null
|
|
320
416
|
] }),
|
|
417
|
+
data.footer ? /* @__PURE__ */ jsx3("div", { className: "mt-2", children: data.footer }) : null,
|
|
321
418
|
toneLabel[tone] ? /* @__PURE__ */ jsx3("span", { className: "sr-only", children: toneLabel[tone] }) : null
|
|
322
419
|
]
|
|
323
420
|
}
|
|
@@ -325,8 +422,81 @@ function FlowNode({
|
|
|
325
422
|
}
|
|
326
423
|
|
|
327
424
|
// src/flow-edge/flow-edge.tsx
|
|
328
|
-
import {
|
|
329
|
-
|
|
425
|
+
import { getBezierPath } from "@xyflow/react";
|
|
426
|
+
|
|
427
|
+
// src/flow-edge-path/flow-edge-path.tsx
|
|
428
|
+
import { BaseEdge } from "@xyflow/react";
|
|
429
|
+
import { cn as cn4 } from "@elabs-ai/components-ui/lib/cn";
|
|
430
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
431
|
+
var FLOW_EDGE_FOCUS_RING_WIDTH = 3;
|
|
432
|
+
var FLOW_EDGE_FOCUS_CONTOUR_WIDTH = 6;
|
|
433
|
+
function FlowEdgePath({
|
|
434
|
+
path,
|
|
435
|
+
stroke,
|
|
436
|
+
strokeWidth,
|
|
437
|
+
strokeDasharray,
|
|
438
|
+
strokeOpacity,
|
|
439
|
+
markerEnd,
|
|
440
|
+
markerStart,
|
|
441
|
+
interactionWidth,
|
|
442
|
+
className,
|
|
443
|
+
style,
|
|
444
|
+
...props
|
|
445
|
+
}) {
|
|
446
|
+
const focusDash = strokeDasharray ?? "none";
|
|
447
|
+
return /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
448
|
+
/* @__PURE__ */ jsx4(
|
|
449
|
+
"path",
|
|
450
|
+
{
|
|
451
|
+
d: path,
|
|
452
|
+
fill: "none",
|
|
453
|
+
"aria-hidden": "true",
|
|
454
|
+
"data-slot": "flow-edge-focus-contour",
|
|
455
|
+
className: "pointer-events-none stroke-foreground opacity-0 [.react-flow\\_\\_edge:focus-visible_&]:opacity-100",
|
|
456
|
+
strokeWidth: strokeWidth + FLOW_EDGE_FOCUS_CONTOUR_WIDTH,
|
|
457
|
+
strokeDasharray: focusDash,
|
|
458
|
+
strokeLinecap: "round"
|
|
459
|
+
}
|
|
460
|
+
),
|
|
461
|
+
/* @__PURE__ */ jsx4(
|
|
462
|
+
"path",
|
|
463
|
+
{
|
|
464
|
+
d: path,
|
|
465
|
+
fill: "none",
|
|
466
|
+
"aria-hidden": "true",
|
|
467
|
+
"data-slot": "flow-edge-focus-ring",
|
|
468
|
+
className: "pointer-events-none stroke-ring opacity-0 [.react-flow\\_\\_edge:focus-visible_&]:opacity-100",
|
|
469
|
+
strokeWidth: strokeWidth + FLOW_EDGE_FOCUS_RING_WIDTH,
|
|
470
|
+
strokeDasharray: focusDash,
|
|
471
|
+
strokeLinecap: "round"
|
|
472
|
+
}
|
|
473
|
+
),
|
|
474
|
+
/* @__PURE__ */ jsx4(
|
|
475
|
+
BaseEdge,
|
|
476
|
+
{
|
|
477
|
+
...props,
|
|
478
|
+
path,
|
|
479
|
+
markerEnd,
|
|
480
|
+
markerStart,
|
|
481
|
+
interactionWidth,
|
|
482
|
+
className: cn4(
|
|
483
|
+
"transition-[stroke,stroke-width] duration-fast ease-standard motion-reduce:transition-none",
|
|
484
|
+
className
|
|
485
|
+
),
|
|
486
|
+
style: {
|
|
487
|
+
stroke,
|
|
488
|
+
strokeWidth,
|
|
489
|
+
...strokeDasharray ? { strokeDasharray } : null,
|
|
490
|
+
...strokeOpacity !== void 0 ? { strokeOpacity } : null,
|
|
491
|
+
...style
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
)
|
|
495
|
+
] });
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// src/flow-edge/flow-edge.tsx
|
|
499
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
330
500
|
function FlowEdge({
|
|
331
501
|
id,
|
|
332
502
|
sourceX,
|
|
@@ -346,20 +516,22 @@ function FlowEdge({
|
|
|
346
516
|
sourcePosition,
|
|
347
517
|
targetPosition
|
|
348
518
|
});
|
|
349
|
-
return /* @__PURE__ */
|
|
350
|
-
|
|
519
|
+
return /* @__PURE__ */ jsx5(
|
|
520
|
+
FlowEdgePath,
|
|
351
521
|
{
|
|
352
522
|
id,
|
|
353
523
|
path: edgePath,
|
|
354
524
|
markerEnd,
|
|
355
|
-
|
|
525
|
+
"data-slot": "flow-edge",
|
|
526
|
+
stroke: "var(--flow-edge)",
|
|
527
|
+
strokeWidth: 1.5,
|
|
528
|
+
style
|
|
356
529
|
}
|
|
357
530
|
);
|
|
358
531
|
}
|
|
359
532
|
|
|
360
533
|
// src/flow-smart-edge/flow-smart-edge.tsx
|
|
361
534
|
import {
|
|
362
|
-
BaseEdge as BaseEdge2,
|
|
363
535
|
getBezierPath as getBezierPath2,
|
|
364
536
|
useInternalNode
|
|
365
537
|
} from "@xyflow/react";
|
|
@@ -373,23 +545,28 @@ var sideToPosition = {
|
|
|
373
545
|
bottom: Position2.Bottom,
|
|
374
546
|
left: Position2.Left
|
|
375
547
|
};
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
548
|
+
var positionToSide = {
|
|
549
|
+
[Position2.Top]: "top",
|
|
550
|
+
[Position2.Right]: "right",
|
|
551
|
+
[Position2.Bottom]: "bottom",
|
|
552
|
+
[Position2.Left]: "left"
|
|
553
|
+
};
|
|
554
|
+
function pickClosestAnchors(sources, targets) {
|
|
555
|
+
if (!sources.length || !targets.length) return void 0;
|
|
556
|
+
let best;
|
|
557
|
+
let bestDist = Infinity;
|
|
558
|
+
for (const source of sources) {
|
|
559
|
+
for (const target of targets) {
|
|
560
|
+
const dx = target.x - source.x;
|
|
561
|
+
const dy = target.y - source.y;
|
|
562
|
+
const dist = dx * dx + dy * dy;
|
|
563
|
+
if (dist < bestDist) {
|
|
564
|
+
bestDist = dist;
|
|
565
|
+
best = { source, target };
|
|
566
|
+
}
|
|
567
|
+
}
|
|
392
568
|
}
|
|
569
|
+
return best;
|
|
393
570
|
}
|
|
394
571
|
function handlePoint(rect, side) {
|
|
395
572
|
switch (side) {
|
|
@@ -425,7 +602,7 @@ function pickClosestHandles(source, sourceSides, target, targetSides) {
|
|
|
425
602
|
}
|
|
426
603
|
|
|
427
604
|
// src/flow-smart-edge/flow-smart-edge.tsx
|
|
428
|
-
import { jsx as
|
|
605
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
429
606
|
function toRect(node) {
|
|
430
607
|
return {
|
|
431
608
|
x: node.internals.positionAbsolute.x,
|
|
@@ -434,43 +611,87 @@ function toRect(node) {
|
|
|
434
611
|
height: node.measured.height ?? 0
|
|
435
612
|
};
|
|
436
613
|
}
|
|
437
|
-
function
|
|
614
|
+
function anchorsOf(node, kind, handleId) {
|
|
615
|
+
const bounds = node.internals.handleBounds?.[kind] ?? [];
|
|
616
|
+
const origin = node.internals.positionAbsolute;
|
|
617
|
+
return bounds.filter((handle) => handleId ? handle.id === handleId : true).map((handle) => ({
|
|
618
|
+
id: handle.id ?? null,
|
|
619
|
+
x: origin.x + handle.x + handle.width / 2,
|
|
620
|
+
y: origin.y + handle.y + handle.height / 2,
|
|
621
|
+
side: positionToSide[handle.position]
|
|
622
|
+
}));
|
|
623
|
+
}
|
|
624
|
+
function declaredSides(node, kind) {
|
|
625
|
+
const userNode = node.internals.userNode;
|
|
626
|
+
const data = userNode.data;
|
|
627
|
+
const configured = data?.handles?.[kind];
|
|
628
|
+
if (configured?.length) return [...configured];
|
|
629
|
+
const position = kind === "target" ? userNode.targetPosition : userNode.sourcePosition;
|
|
630
|
+
if (position) return [positionToSide[position]];
|
|
631
|
+
return [kind === "target" ? "top" : "bottom"];
|
|
632
|
+
}
|
|
633
|
+
function FlowSmartEdge({
|
|
634
|
+
id,
|
|
635
|
+
source,
|
|
636
|
+
target,
|
|
637
|
+
sourceHandleId,
|
|
638
|
+
targetHandleId,
|
|
639
|
+
markerEnd,
|
|
640
|
+
style
|
|
641
|
+
}) {
|
|
438
642
|
const sourceNode = useInternalNode(source);
|
|
439
643
|
const targetNode = useInternalNode(target);
|
|
440
644
|
if (!sourceNode || !targetNode) return null;
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const targetRect = toRect(targetNode);
|
|
445
|
-
const { sourceSide, targetSide } = pickClosestHandles(
|
|
446
|
-
sourceRect,
|
|
447
|
-
sourceData?.handles?.source ?? [],
|
|
448
|
-
targetRect,
|
|
449
|
-
targetData?.handles?.target ?? []
|
|
645
|
+
const picked = pickClosestAnchors(
|
|
646
|
+
anchorsOf(sourceNode, "source", sourceHandleId),
|
|
647
|
+
anchorsOf(targetNode, "target", targetHandleId)
|
|
450
648
|
);
|
|
451
|
-
|
|
452
|
-
|
|
649
|
+
let sx;
|
|
650
|
+
let sy;
|
|
651
|
+
let tx;
|
|
652
|
+
let ty;
|
|
653
|
+
let sourcePosition;
|
|
654
|
+
let targetPosition;
|
|
655
|
+
if (picked) {
|
|
656
|
+
({ x: sx, y: sy } = picked.source);
|
|
657
|
+
({ x: tx, y: ty } = picked.target);
|
|
658
|
+
sourcePosition = sideToPosition[picked.source.side];
|
|
659
|
+
targetPosition = sideToPosition[picked.target.side];
|
|
660
|
+
} else {
|
|
661
|
+
const fallback = pickClosestHandles(
|
|
662
|
+
toRect(sourceNode),
|
|
663
|
+
declaredSides(sourceNode, "source"),
|
|
664
|
+
toRect(targetNode),
|
|
665
|
+
declaredSides(targetNode, "target")
|
|
666
|
+
);
|
|
667
|
+
({ sx, sy, tx, ty } = fallback);
|
|
668
|
+
sourcePosition = sideToPosition[fallback.sourceSide];
|
|
669
|
+
targetPosition = sideToPosition[fallback.targetSide];
|
|
670
|
+
}
|
|
453
671
|
const [edgePath] = getBezierPath2({
|
|
454
|
-
sourceX:
|
|
455
|
-
sourceY:
|
|
456
|
-
sourcePosition
|
|
457
|
-
targetX:
|
|
458
|
-
targetY:
|
|
459
|
-
targetPosition
|
|
672
|
+
sourceX: sx,
|
|
673
|
+
sourceY: sy,
|
|
674
|
+
sourcePosition,
|
|
675
|
+
targetX: tx,
|
|
676
|
+
targetY: ty,
|
|
677
|
+
targetPosition
|
|
460
678
|
});
|
|
461
|
-
return /* @__PURE__ */
|
|
462
|
-
|
|
679
|
+
return /* @__PURE__ */ jsx6(
|
|
680
|
+
FlowEdgePath,
|
|
463
681
|
{
|
|
464
682
|
id,
|
|
465
683
|
path: edgePath,
|
|
466
684
|
markerEnd,
|
|
467
|
-
|
|
685
|
+
"data-slot": "flow-smart-edge",
|
|
686
|
+
stroke: "var(--flow-edge)",
|
|
687
|
+
strokeWidth: 1.5,
|
|
688
|
+
style
|
|
468
689
|
}
|
|
469
690
|
);
|
|
470
691
|
}
|
|
471
692
|
|
|
472
693
|
// src/flow-floating-edge/flow-floating-edge.tsx
|
|
473
|
-
import {
|
|
694
|
+
import { getBezierPath as getBezierPath3, useInternalNode as useInternalNode2 } from "@xyflow/react";
|
|
474
695
|
|
|
475
696
|
// src/flow-floating-edge/floating-edge-geometry.ts
|
|
476
697
|
import { Position as Position3 } from "@xyflow/react";
|
|
@@ -539,7 +760,7 @@ function getEdgeParams(source, target) {
|
|
|
539
760
|
}
|
|
540
761
|
|
|
541
762
|
// src/flow-floating-edge/flow-floating-edge.tsx
|
|
542
|
-
import { Fragment as
|
|
763
|
+
import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
543
764
|
var ANCHOR_RADIUS = 4;
|
|
544
765
|
function FlowFloatingEdge({ id, source, target, markerEnd, style, data }) {
|
|
545
766
|
const sourceNode = useInternalNode2(source);
|
|
@@ -555,24 +776,27 @@ function FlowFloatingEdge({ id, source, target, markerEnd, style, data }) {
|
|
|
555
776
|
targetPosition: targetPos
|
|
556
777
|
});
|
|
557
778
|
const showAnchors = data?.anchors !== false;
|
|
558
|
-
return /* @__PURE__ */
|
|
559
|
-
/* @__PURE__ */
|
|
560
|
-
|
|
779
|
+
return /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
780
|
+
/* @__PURE__ */ jsx7(
|
|
781
|
+
FlowEdgePath,
|
|
561
782
|
{
|
|
562
783
|
id,
|
|
563
784
|
path: edgePath,
|
|
564
785
|
markerEnd,
|
|
565
|
-
|
|
786
|
+
"data-slot": "flow-floating-edge",
|
|
787
|
+
stroke: "var(--flow-edge)",
|
|
788
|
+
strokeWidth: 1.5,
|
|
789
|
+
style
|
|
566
790
|
}
|
|
567
791
|
),
|
|
568
|
-
showAnchors ? /* @__PURE__ */
|
|
792
|
+
showAnchors ? /* @__PURE__ */ jsxs5(
|
|
569
793
|
"g",
|
|
570
794
|
{
|
|
571
795
|
className: "brand-floating-edge__anchors",
|
|
572
796
|
fill: "var(--flow-node)",
|
|
573
797
|
stroke: "var(--flow-edge)",
|
|
574
798
|
children: [
|
|
575
|
-
/* @__PURE__ */
|
|
799
|
+
/* @__PURE__ */ jsx7(
|
|
576
800
|
"circle",
|
|
577
801
|
{
|
|
578
802
|
cx: sx,
|
|
@@ -583,7 +807,7 @@ function FlowFloatingEdge({ id, source, target, markerEnd, style, data }) {
|
|
|
583
807
|
"aria-hidden": "true"
|
|
584
808
|
}
|
|
585
809
|
),
|
|
586
|
-
/* @__PURE__ */
|
|
810
|
+
/* @__PURE__ */ jsx7(
|
|
587
811
|
"circle",
|
|
588
812
|
{
|
|
589
813
|
cx: tx,
|
|
@@ -603,33 +827,33 @@ function FlowFloatingEdge({ id, source, target, markerEnd, style, data }) {
|
|
|
603
827
|
// src/flow-placeholder-node/flow-placeholder-node.tsx
|
|
604
828
|
import { Handle as Handle2, Position as Position4 } from "@xyflow/react";
|
|
605
829
|
import { Plus } from "lucide-react";
|
|
606
|
-
import { cn as
|
|
607
|
-
import { jsx as
|
|
830
|
+
import { cn as cn5 } from "@elabs-ai/components-ui/lib/cn";
|
|
831
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
608
832
|
function FlowPlaceholderNode({ data }) {
|
|
609
833
|
const label = data.label ?? "Add node";
|
|
610
|
-
return /* @__PURE__ */
|
|
611
|
-
/* @__PURE__ */
|
|
834
|
+
return /* @__PURE__ */ jsxs6("div", { className: "relative", children: [
|
|
835
|
+
/* @__PURE__ */ jsx8(
|
|
612
836
|
Handle2,
|
|
613
837
|
{
|
|
614
838
|
type: "target",
|
|
615
839
|
position: Position4.Top,
|
|
616
|
-
className:
|
|
840
|
+
className: `!size-2 !border-2 !border-flow-edge !bg-flow-node ${FLOW_HANDLE_ANCHOR_CLASS}`
|
|
617
841
|
}
|
|
618
842
|
),
|
|
619
|
-
/* @__PURE__ */
|
|
843
|
+
/* @__PURE__ */ jsxs6(
|
|
620
844
|
"button",
|
|
621
845
|
{
|
|
622
846
|
type: "button",
|
|
623
847
|
"aria-label": label,
|
|
624
848
|
onClick: () => data.onActivate?.(),
|
|
625
|
-
className:
|
|
849
|
+
className: cn5(
|
|
626
850
|
"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",
|
|
627
851
|
"transition-colors duration-fast ease-standard hover:bg-accent hover:text-accent-foreground",
|
|
628
|
-
"focus-
|
|
852
|
+
"focus-ring"
|
|
629
853
|
),
|
|
630
854
|
children: [
|
|
631
|
-
/* @__PURE__ */
|
|
632
|
-
/* @__PURE__ */
|
|
855
|
+
/* @__PURE__ */ jsx8(Plus, { className: "size-4", "aria-hidden": "true" }),
|
|
856
|
+
/* @__PURE__ */ jsx8("span", { className: "text-body", children: label })
|
|
633
857
|
]
|
|
634
858
|
}
|
|
635
859
|
)
|
|
@@ -637,13 +861,9 @@ function FlowPlaceholderNode({ data }) {
|
|
|
637
861
|
}
|
|
638
862
|
|
|
639
863
|
// src/flow-button-edge/flow-button-edge.tsx
|
|
640
|
-
import {
|
|
641
|
-
BaseEdge as BaseEdge4,
|
|
642
|
-
EdgeLabelRenderer,
|
|
643
|
-
getBezierPath as getBezierPath4
|
|
644
|
-
} from "@xyflow/react";
|
|
864
|
+
import { EdgeLabelRenderer, getBezierPath as getBezierPath4 } from "@xyflow/react";
|
|
645
865
|
import { Plus as Plus2 } from "lucide-react";
|
|
646
|
-
import { Fragment as
|
|
866
|
+
import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
647
867
|
function FlowButtonEdge({
|
|
648
868
|
id,
|
|
649
869
|
sourceX,
|
|
@@ -665,17 +885,20 @@ function FlowButtonEdge({
|
|
|
665
885
|
targetPosition
|
|
666
886
|
});
|
|
667
887
|
const label = data?.label ?? "Insert node on edge";
|
|
668
|
-
return /* @__PURE__ */
|
|
669
|
-
/* @__PURE__ */
|
|
670
|
-
|
|
888
|
+
return /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
889
|
+
/* @__PURE__ */ jsx9(
|
|
890
|
+
FlowEdgePath,
|
|
671
891
|
{
|
|
672
892
|
id,
|
|
673
893
|
path: edgePath,
|
|
674
894
|
markerEnd,
|
|
675
|
-
|
|
895
|
+
"data-slot": "flow-button-edge",
|
|
896
|
+
stroke: "var(--flow-edge)",
|
|
897
|
+
strokeWidth: 1.5,
|
|
898
|
+
style
|
|
676
899
|
}
|
|
677
900
|
),
|
|
678
|
-
/* @__PURE__ */
|
|
901
|
+
/* @__PURE__ */ jsx9(EdgeLabelRenderer, { children: /* @__PURE__ */ jsx9(
|
|
679
902
|
"div",
|
|
680
903
|
{
|
|
681
904
|
style: {
|
|
@@ -683,14 +906,14 @@ function FlowButtonEdge({
|
|
|
683
906
|
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`
|
|
684
907
|
},
|
|
685
908
|
className: "nodrag nopan pointer-events-auto",
|
|
686
|
-
children: /* @__PURE__ */
|
|
909
|
+
children: /* @__PURE__ */ jsx9(
|
|
687
910
|
"button",
|
|
688
911
|
{
|
|
689
912
|
type: "button",
|
|
690
913
|
"aria-label": label,
|
|
691
914
|
onClick: () => data?.onInsert?.(),
|
|
692
|
-
className: "flex size-5 items-center justify-center rounded-full border border-flow-group-border bg-flow-node text-flow-node-foreground shadow-sm transition-colors duration-fast ease-standard hover:bg-accent hover:text-accent-foreground focus-
|
|
693
|
-
children: /* @__PURE__ */
|
|
915
|
+
className: "flex size-5 items-center justify-center rounded-full border border-flow-group-border bg-flow-node text-flow-node-foreground shadow-sm transition-colors duration-fast ease-standard hover:bg-accent hover:text-accent-foreground focus-ring",
|
|
916
|
+
children: /* @__PURE__ */ jsx9(Plus2, { className: "size-3", "aria-hidden": "true" })
|
|
694
917
|
}
|
|
695
918
|
)
|
|
696
919
|
}
|
|
@@ -724,10 +947,16 @@ function layoutFlow(nodes, edges, options = {}) {
|
|
|
724
947
|
const { width, height } = nodeSize(node);
|
|
725
948
|
graph.setNode(node.id, { width, height });
|
|
726
949
|
}
|
|
950
|
+
const selfLoops = [];
|
|
727
951
|
for (const edge of edges) {
|
|
952
|
+
if (edge.source === edge.target) {
|
|
953
|
+
selfLoops.push(edge.id);
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
728
956
|
graph.setEdge(edge.source, edge.target);
|
|
729
957
|
}
|
|
730
958
|
dagre.layout(graph);
|
|
959
|
+
const backEdges = collectBackEdges(graph, edges);
|
|
731
960
|
const handles = HANDLE_BY_DIRECTION[direction];
|
|
732
961
|
const layoutedNodes = nodes.map((node) => {
|
|
733
962
|
const dagreNode = graph.node(node.id);
|
|
@@ -744,18 +973,29 @@ function layoutFlow(nodes, edges, options = {}) {
|
|
|
744
973
|
}
|
|
745
974
|
};
|
|
746
975
|
});
|
|
747
|
-
return { nodes: layoutedNodes, edges };
|
|
976
|
+
return { nodes: layoutedNodes, edges, backEdges, selfLoops };
|
|
977
|
+
}
|
|
978
|
+
function collectBackEdges(graph, edges) {
|
|
979
|
+
const backEdges = [];
|
|
980
|
+
for (const edge of edges) {
|
|
981
|
+
if (edge.source === edge.target) continue;
|
|
982
|
+
const sourceRank = graph.node(edge.source)?.rank;
|
|
983
|
+
const targetRank = graph.node(edge.target)?.rank;
|
|
984
|
+
if (typeof sourceRank !== "number" || typeof targetRank !== "number") continue;
|
|
985
|
+
if (sourceRank >= targetRank) backEdges.push(edge.id);
|
|
986
|
+
}
|
|
987
|
+
return backEdges;
|
|
748
988
|
}
|
|
749
989
|
|
|
750
990
|
// src/flow-layout/use-flow-layout.ts
|
|
751
|
-
import { useCallback as
|
|
752
|
-
import { useNodesInitialized, useReactFlow as
|
|
991
|
+
import { useCallback as useCallback3, useRef as useRef2, useState as useState3 } from "react";
|
|
992
|
+
import { useNodesInitialized as useNodesInitialized2, useReactFlow as useReactFlow3 } from "@xyflow/react";
|
|
753
993
|
function useFlowLayout() {
|
|
754
|
-
const { getNodes, getEdges, setNodes, fitView } =
|
|
755
|
-
const nodesInitialized =
|
|
756
|
-
const [layouting, setLayouting] =
|
|
757
|
-
const runningRef =
|
|
758
|
-
const layout =
|
|
994
|
+
const { getNodes, getEdges, setNodes, fitView } = useReactFlow3();
|
|
995
|
+
const nodesInitialized = useNodesInitialized2();
|
|
996
|
+
const [layouting, setLayouting] = useState3(false);
|
|
997
|
+
const runningRef = useRef2(false);
|
|
998
|
+
const layout = useCallback3(
|
|
759
999
|
(direction, options) => {
|
|
760
1000
|
if (!nodesInitialized || runningRef.current) return;
|
|
761
1001
|
runningRef.current = true;
|
|
@@ -803,7 +1043,9 @@ function layoutGraph(nodes, edges, options) {
|
|
|
803
1043
|
if (nodes.length === 0) return [];
|
|
804
1044
|
switch (options.algorithm) {
|
|
805
1045
|
case "layered-lr":
|
|
806
|
-
return
|
|
1046
|
+
return layoutLayered(nodes, edges, options, "LR");
|
|
1047
|
+
case "layered-tb":
|
|
1048
|
+
return layoutLayered(nodes, edges, options, "TB");
|
|
807
1049
|
case "concentric":
|
|
808
1050
|
return layoutConcentric(nodes, edges, options);
|
|
809
1051
|
case "force":
|
|
@@ -816,18 +1058,17 @@ function layoutGraph(nodes, edges, options) {
|
|
|
816
1058
|
}
|
|
817
1059
|
}
|
|
818
1060
|
}
|
|
819
|
-
function
|
|
1061
|
+
function layoutLayered(nodes, edges, options, direction) {
|
|
820
1062
|
const { spacing, nodeSize: nodeSize2 } = options;
|
|
821
1063
|
const sizedNodes = nodeSize2 ? nodes.map((node) => {
|
|
822
1064
|
const { width, height } = nodeSize2(node.id);
|
|
823
1065
|
return { ...node, width, height, measured: { width, height } };
|
|
824
1066
|
}) : nodes;
|
|
1067
|
+
const horizontalRanks = direction === "LR" || direction === "RL";
|
|
825
1068
|
const { nodes: laidOut } = layoutFlow(sizedNodes, edges, {
|
|
826
|
-
direction
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
nodeSpacing: spacing?.y,
|
|
830
|
-
rankSpacing: spacing?.x
|
|
1069
|
+
direction,
|
|
1070
|
+
nodeSpacing: horizontalRanks ? spacing?.y : spacing?.x,
|
|
1071
|
+
rankSpacing: horizontalRanks ? spacing?.x : spacing?.y
|
|
831
1072
|
});
|
|
832
1073
|
return nodes.map((node, i) => ({
|
|
833
1074
|
...node,
|
|
@@ -952,9 +1193,9 @@ function layoutGrid(nodes, options) {
|
|
|
952
1193
|
}
|
|
953
1194
|
|
|
954
1195
|
// src/flow-layout/use-auto-layout.ts
|
|
955
|
-
import { useMemo } from "react";
|
|
1196
|
+
import { useMemo as useMemo2 } from "react";
|
|
956
1197
|
function useAutoLayout(nodes, edges, options) {
|
|
957
|
-
return
|
|
1198
|
+
return useMemo2(
|
|
958
1199
|
() => layoutGraph(nodes, edges, options),
|
|
959
1200
|
[nodes, edges, options]
|
|
960
1201
|
);
|
|
@@ -962,8 +1203,8 @@ function useAutoLayout(nodes, edges, options) {
|
|
|
962
1203
|
|
|
963
1204
|
// src/flow-mini-map/flow-mini-map.tsx
|
|
964
1205
|
import { MiniMap } from "@xyflow/react";
|
|
965
|
-
import { cn as
|
|
966
|
-
import { jsx as
|
|
1206
|
+
import { cn as cn6 } from "@elabs-ai/components-ui/lib/cn";
|
|
1207
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
967
1208
|
function FlowMiniMap({
|
|
968
1209
|
className,
|
|
969
1210
|
nodeColor = "var(--flow-minimap-node)",
|
|
@@ -971,10 +1212,10 @@ function FlowMiniMap({
|
|
|
971
1212
|
maskColor = "var(--flow-minimap-mask)",
|
|
972
1213
|
...props
|
|
973
1214
|
}) {
|
|
974
|
-
return /* @__PURE__ */
|
|
1215
|
+
return /* @__PURE__ */ jsx10(
|
|
975
1216
|
MiniMap,
|
|
976
1217
|
{
|
|
977
|
-
className:
|
|
1218
|
+
className: cn6("!rounded-lg !bg-surface-elevated !shadow-ring-sm", className),
|
|
978
1219
|
nodeColor,
|
|
979
1220
|
nodeStrokeColor,
|
|
980
1221
|
maskColor,
|
|
@@ -986,8 +1227,8 @@ function FlowMiniMap({
|
|
|
986
1227
|
// src/inspector-panel/inspector-panel.tsx
|
|
987
1228
|
import "react";
|
|
988
1229
|
import { Reveal, useCollapsiblePanel } from "@elabs-ai/components-ui";
|
|
989
|
-
import { cn as
|
|
990
|
-
import { jsx as
|
|
1230
|
+
import { cn as cn7 } from "@elabs-ai/components-ui/lib/cn";
|
|
1231
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
991
1232
|
var INSPECTOR_PANEL_WIDTH = "18rem";
|
|
992
1233
|
function InspectorPanel({
|
|
993
1234
|
title = "Inspector",
|
|
@@ -1015,31 +1256,31 @@ function InspectorPanel({
|
|
|
1015
1256
|
right: "right-0 group-data-[state=collapsed]:right-[calc(var(--inspector-panel-width)*-1)]"
|
|
1016
1257
|
}
|
|
1017
1258
|
});
|
|
1018
|
-
return /* @__PURE__ */
|
|
1259
|
+
return /* @__PURE__ */ jsxs8(
|
|
1019
1260
|
"div",
|
|
1020
1261
|
{
|
|
1021
|
-
className:
|
|
1262
|
+
className: cn7("group relative h-full overflow-hidden", className),
|
|
1022
1263
|
style: { "--inspector-panel-width": width },
|
|
1023
1264
|
inert: panel.open ? void 0 : true,
|
|
1024
1265
|
...panel.attrs,
|
|
1025
1266
|
children: [
|
|
1026
|
-
/* @__PURE__ */
|
|
1027
|
-
/* @__PURE__ */
|
|
1267
|
+
/* @__PURE__ */ jsx11("div", { "data-slot": "inspector-panel-gap", className: panel.spacerClassName }),
|
|
1268
|
+
/* @__PURE__ */ jsx11(
|
|
1028
1269
|
"div",
|
|
1029
1270
|
{
|
|
1030
1271
|
"data-slot": "inspector-panel-container",
|
|
1031
|
-
className:
|
|
1032
|
-
children: /* @__PURE__ */
|
|
1033
|
-
/* @__PURE__ */
|
|
1034
|
-
/* @__PURE__ */
|
|
1035
|
-
onClose ? /* @__PURE__ */
|
|
1272
|
+
className: cn7(panel.containerClassName, "absolute inset-y-0 flex h-full border-s"),
|
|
1273
|
+
children: /* @__PURE__ */ jsxs8("div", { className: "flex h-full w-full flex-col bg-card", children: [
|
|
1274
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between border-b px-4 py-3", children: [
|
|
1275
|
+
/* @__PURE__ */ jsx11("h3", { className: "text-body font-semibold text-foreground", children: title }),
|
|
1276
|
+
onClose ? /* @__PURE__ */ jsx11(
|
|
1036
1277
|
"button",
|
|
1037
1278
|
{
|
|
1038
1279
|
type: "button",
|
|
1039
1280
|
"aria-label": "Close inspector",
|
|
1040
1281
|
onClick: onClose,
|
|
1041
|
-
className: "rounded-sm p-1 text-muted-foreground hover:text-foreground focus-
|
|
1042
|
-
children: /* @__PURE__ */
|
|
1282
|
+
className: "rounded-sm p-1 text-muted-foreground hover:text-foreground focus-ring",
|
|
1283
|
+
children: /* @__PURE__ */ jsx11(
|
|
1043
1284
|
"svg",
|
|
1044
1285
|
{
|
|
1045
1286
|
width: "14",
|
|
@@ -1051,22 +1292,23 @@ function InspectorPanel({
|
|
|
1051
1292
|
strokeLinecap: "round",
|
|
1052
1293
|
strokeLinejoin: "round",
|
|
1053
1294
|
"aria-hidden": "true",
|
|
1054
|
-
children: /* @__PURE__ */
|
|
1295
|
+
children: /* @__PURE__ */ jsx11("path", { d: "M18 6 6 18M6 6l12 12" })
|
|
1055
1296
|
}
|
|
1056
1297
|
)
|
|
1057
1298
|
}
|
|
1058
1299
|
) : null
|
|
1059
1300
|
] }),
|
|
1060
|
-
hasSelection ? /* @__PURE__ */
|
|
1301
|
+
hasSelection ? /* @__PURE__ */ jsx11(
|
|
1061
1302
|
Reveal,
|
|
1062
1303
|
{
|
|
1063
1304
|
appear: "fade",
|
|
1064
1305
|
speed: "fast",
|
|
1065
|
-
|
|
1306
|
+
tabIndex: 0,
|
|
1307
|
+
className: "focus-ring flex-1 overflow-y-auto p-4 text-body",
|
|
1066
1308
|
children
|
|
1067
1309
|
},
|
|
1068
1310
|
selectionKey
|
|
1069
|
-
) : /* @__PURE__ */
|
|
1311
|
+
) : /* @__PURE__ */ jsx11("div", { tabIndex: 0, className: "focus-ring flex-1 overflow-y-auto p-4 text-body", children: /* @__PURE__ */ jsx11("p", { className: "text-muted-foreground", children: emptyMessage }) })
|
|
1070
1312
|
] })
|
|
1071
1313
|
}
|
|
1072
1314
|
)
|
|
@@ -1076,20 +1318,64 @@ function InspectorPanel({
|
|
|
1076
1318
|
}
|
|
1077
1319
|
|
|
1078
1320
|
// src/legend/legend.tsx
|
|
1079
|
-
import { cn as
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1321
|
+
import { cn as cn8 } from "@elabs-ai/components-ui/lib/cn";
|
|
1322
|
+
|
|
1323
|
+
// src/flow-weighted-edge/weight-scale.ts
|
|
1324
|
+
var DEFAULT_EDGE_WIDTH_RANGE = [1.5, 8];
|
|
1325
|
+
var DEFAULT_SCALE_GROUP = "__default__";
|
|
1326
|
+
function computeEdgeWeightScale(edges, opts = {}) {
|
|
1327
|
+
const [minWidth, maxWidth] = opts.widthRange ?? DEFAULT_EDGE_WIDTH_RANGE;
|
|
1328
|
+
const result = /* @__PURE__ */ new Map();
|
|
1329
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1330
|
+
for (const edge of edges) {
|
|
1331
|
+
const weight = edge.data?.weight;
|
|
1332
|
+
if (weight === void 0) {
|
|
1333
|
+
result.set(edge.id, minWidth);
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1336
|
+
const group = edge.data?.scaleGroup ?? DEFAULT_SCALE_GROUP;
|
|
1337
|
+
if (opts.scaleGroup !== void 0 && group !== opts.scaleGroup) continue;
|
|
1338
|
+
const list = groups.get(group);
|
|
1339
|
+
if (list) list.push({ id: edge.id, weight });
|
|
1340
|
+
else groups.set(group, [{ id: edge.id, weight }]);
|
|
1341
|
+
}
|
|
1342
|
+
for (const list of groups.values()) {
|
|
1343
|
+
let min = Infinity;
|
|
1344
|
+
let max = -Infinity;
|
|
1345
|
+
for (const { weight } of list) {
|
|
1346
|
+
if (weight < min) min = weight;
|
|
1347
|
+
if (weight > max) max = weight;
|
|
1348
|
+
}
|
|
1349
|
+
const span = max - min;
|
|
1350
|
+
for (const { id, weight } of list) {
|
|
1351
|
+
const width = span === 0 ? (minWidth + maxWidth) / 2 : minWidth + (weight - min) / span * (maxWidth - minWidth);
|
|
1352
|
+
result.set(id, width);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
return result;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// src/legend/legend.tsx
|
|
1359
|
+
import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1360
|
+
var defaultFormat = (value) => value.toLocaleString();
|
|
1361
|
+
function Legend(props) {
|
|
1362
|
+
if (props.variant === "scale") {
|
|
1363
|
+
return /* @__PURE__ */ jsx12(LegendScale, { ...props });
|
|
1364
|
+
}
|
|
1365
|
+
return /* @__PURE__ */ jsx12(LegendCategorical, { ...props });
|
|
1366
|
+
}
|
|
1367
|
+
function LegendCategorical({ items, title, className }) {
|
|
1368
|
+
return /* @__PURE__ */ jsxs9(
|
|
1083
1369
|
"div",
|
|
1084
1370
|
{
|
|
1085
|
-
className:
|
|
1371
|
+
className: cn8(
|
|
1086
1372
|
"rounded-lg bg-surface-elevated/90 p-3 text-xs shadow-ring-sm backdrop-blur",
|
|
1087
1373
|
className
|
|
1088
1374
|
),
|
|
1089
1375
|
children: [
|
|
1090
|
-
title ? /* @__PURE__ */
|
|
1091
|
-
/* @__PURE__ */
|
|
1092
|
-
/* @__PURE__ */
|
|
1376
|
+
title ? /* @__PURE__ */ jsx12("div", { className: "mb-1.5 font-medium text-foreground", children: title }) : null,
|
|
1377
|
+
/* @__PURE__ */ jsx12("ul", { className: "space-y-1", children: items.map((item) => /* @__PURE__ */ jsxs9("li", { className: "flex items-center gap-2 text-muted-foreground", children: [
|
|
1378
|
+
/* @__PURE__ */ jsx12(
|
|
1093
1379
|
"span",
|
|
1094
1380
|
{
|
|
1095
1381
|
"aria-hidden": "true",
|
|
@@ -1103,38 +1389,143 @@ function Legend({ items, title, className }) {
|
|
|
1103
1389
|
}
|
|
1104
1390
|
);
|
|
1105
1391
|
}
|
|
1392
|
+
function computeWidthSamples(domain, ticks) {
|
|
1393
|
+
const [min, max] = domain;
|
|
1394
|
+
const values = ticks === "minmedmax" ? [min, (min + max) / 2, max] : [min, max];
|
|
1395
|
+
const likeEdges = values.map((weight, index) => ({
|
|
1396
|
+
id: String(index),
|
|
1397
|
+
data: { weight }
|
|
1398
|
+
}));
|
|
1399
|
+
const widths = computeEdgeWeightScale(likeEdges);
|
|
1400
|
+
return values.map((value, index) => ({ value, width: widths.get(String(index)) ?? 0 }));
|
|
1401
|
+
}
|
|
1402
|
+
var COLOR_STOP_FRACTIONS = [0, 0.25, 0.5, 0.75, 1];
|
|
1403
|
+
function scaleKindLabel(kind) {
|
|
1404
|
+
return kind === "width" ? "width" : "color";
|
|
1405
|
+
}
|
|
1406
|
+
function LegendScale({
|
|
1407
|
+
kind,
|
|
1408
|
+
domain,
|
|
1409
|
+
format = defaultFormat,
|
|
1410
|
+
ticks = "minmax",
|
|
1411
|
+
title,
|
|
1412
|
+
className
|
|
1413
|
+
}) {
|
|
1414
|
+
const [min, max] = domain;
|
|
1415
|
+
const ariaLabel = `Edge ${scaleKindLabel(kind)} scale, ${format(min)} to ${format(max)}, minimum to maximum`;
|
|
1416
|
+
return /* @__PURE__ */ jsxs9(
|
|
1417
|
+
"div",
|
|
1418
|
+
{
|
|
1419
|
+
"aria-label": ariaLabel,
|
|
1420
|
+
className: cn8(
|
|
1421
|
+
"flex flex-col gap-2 rounded-lg bg-surface-elevated/90 p-3 shadow-ring-sm backdrop-blur",
|
|
1422
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
1423
|
+
className
|
|
1424
|
+
),
|
|
1425
|
+
role: "group",
|
|
1426
|
+
tabIndex: 0,
|
|
1427
|
+
children: [
|
|
1428
|
+
title ? /* @__PURE__ */ jsx12("div", { className: "text-body font-medium text-foreground", children: title }) : null,
|
|
1429
|
+
kind === "width" ? /* @__PURE__ */ jsx12(LegendScaleWidth, { domain, format, ticks }) : /* @__PURE__ */ jsx12(LegendScaleColor, { domain, format })
|
|
1430
|
+
]
|
|
1431
|
+
}
|
|
1432
|
+
);
|
|
1433
|
+
}
|
|
1434
|
+
function LegendScaleWidth({
|
|
1435
|
+
domain,
|
|
1436
|
+
format,
|
|
1437
|
+
ticks
|
|
1438
|
+
}) {
|
|
1439
|
+
const samples = computeWidthSamples(domain, ticks);
|
|
1440
|
+
return /* @__PURE__ */ jsx12("div", { className: "flex flex-col gap-2", children: samples.map((sample, index) => (
|
|
1441
|
+
// Index disambiguates a zero-width domain, where every sample shares
|
|
1442
|
+
// the same value (and therefore the same width).
|
|
1443
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-3", children: [
|
|
1444
|
+
/* @__PURE__ */ jsx12("svg", { "aria-hidden": "true", className: "shrink-0", height: 16, overflow: "visible", width: 40, children: /* @__PURE__ */ jsx12(
|
|
1445
|
+
"line",
|
|
1446
|
+
{
|
|
1447
|
+
stroke: "var(--flow-edge)",
|
|
1448
|
+
strokeLinecap: "round",
|
|
1449
|
+
strokeWidth: sample.width,
|
|
1450
|
+
x1: 2,
|
|
1451
|
+
x2: 38,
|
|
1452
|
+
y1: 8,
|
|
1453
|
+
y2: 8
|
|
1454
|
+
}
|
|
1455
|
+
) }),
|
|
1456
|
+
/* @__PURE__ */ jsx12("span", { className: "min-w-0 truncate text-meta tabular-nums text-muted-foreground", children: format(sample.value) })
|
|
1457
|
+
] }, `${index}-${sample.value}`)
|
|
1458
|
+
)) });
|
|
1459
|
+
}
|
|
1460
|
+
function LegendScaleColor({
|
|
1461
|
+
domain,
|
|
1462
|
+
format
|
|
1463
|
+
}) {
|
|
1464
|
+
const [min, max] = domain;
|
|
1465
|
+
const span = max - min;
|
|
1466
|
+
const stops = COLOR_STOP_FRACTIONS.map((fraction) => ({
|
|
1467
|
+
fraction,
|
|
1468
|
+
value: min + fraction * span
|
|
1469
|
+
}));
|
|
1470
|
+
const gradient = `linear-gradient(to right, ${stops.map(({ fraction }) => {
|
|
1471
|
+
const percent = Math.round(fraction * 100);
|
|
1472
|
+
return `color-mix(in oklch, var(--flow-edge-strong) ${percent}%, var(--flow-edge-weak)) ${percent}%`;
|
|
1473
|
+
}).join(", ")})`;
|
|
1474
|
+
return /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-1.5", children: [
|
|
1475
|
+
/* @__PURE__ */ jsx12(
|
|
1476
|
+
"div",
|
|
1477
|
+
{
|
|
1478
|
+
"aria-hidden": "true",
|
|
1479
|
+
className: "h-3 w-full rounded-full",
|
|
1480
|
+
style: { backgroundImage: gradient }
|
|
1481
|
+
}
|
|
1482
|
+
),
|
|
1483
|
+
/* @__PURE__ */ jsx12("div", { className: "flex justify-between gap-1", children: stops.map((stop, index) => /* @__PURE__ */ jsx12(
|
|
1484
|
+
"span",
|
|
1485
|
+
{
|
|
1486
|
+
className: cn8(
|
|
1487
|
+
"min-w-0 truncate text-meta tabular-nums text-muted-foreground",
|
|
1488
|
+
index === 0 && "text-start",
|
|
1489
|
+
index === stops.length - 1 && "text-end"
|
|
1490
|
+
),
|
|
1491
|
+
children: format(stop.value)
|
|
1492
|
+
},
|
|
1493
|
+
`${index}-${stop.value}`
|
|
1494
|
+
)) })
|
|
1495
|
+
] });
|
|
1496
|
+
}
|
|
1106
1497
|
|
|
1107
1498
|
// src/zoom-controls/zoom-controls.tsx
|
|
1108
|
-
import { Panel, useReactFlow as
|
|
1109
|
-
import { cn as
|
|
1110
|
-
import { jsx as
|
|
1499
|
+
import { Panel, useReactFlow as useReactFlow4 } from "@xyflow/react";
|
|
1500
|
+
import { cn as cn9 } from "@elabs-ai/components-ui/lib/cn";
|
|
1501
|
+
import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1111
1502
|
function ControlButton({
|
|
1112
1503
|
label,
|
|
1113
1504
|
onClick,
|
|
1114
1505
|
children
|
|
1115
1506
|
}) {
|
|
1116
|
-
return /* @__PURE__ */
|
|
1507
|
+
return /* @__PURE__ */ jsx13(
|
|
1117
1508
|
"button",
|
|
1118
1509
|
{
|
|
1119
1510
|
type: "button",
|
|
1120
1511
|
"aria-label": label,
|
|
1121
1512
|
onClick,
|
|
1122
|
-
className: "flex size-8 items-center justify-center text-foreground transition-colors duration-fast hover:bg-surface-muted focus-
|
|
1513
|
+
className: "flex size-8 items-center justify-center text-foreground transition-colors duration-fast hover:bg-surface-muted focus-ring [&_svg]:size-4",
|
|
1123
1514
|
children
|
|
1124
1515
|
}
|
|
1125
1516
|
);
|
|
1126
1517
|
}
|
|
1127
1518
|
function ZoomControls({ position = "bottom-right", className }) {
|
|
1128
|
-
const { zoomIn, zoomOut, fitView } =
|
|
1129
|
-
return /* @__PURE__ */
|
|
1519
|
+
const { zoomIn, zoomOut, fitView } = useReactFlow4();
|
|
1520
|
+
return /* @__PURE__ */ jsx13(Panel, { position, children: /* @__PURE__ */ jsxs10(
|
|
1130
1521
|
"div",
|
|
1131
1522
|
{
|
|
1132
|
-
className:
|
|
1523
|
+
className: cn9(
|
|
1133
1524
|
"flex divide-x overflow-hidden rounded-lg bg-surface-elevated shadow-ring-sm",
|
|
1134
1525
|
className
|
|
1135
1526
|
),
|
|
1136
1527
|
children: [
|
|
1137
|
-
/* @__PURE__ */
|
|
1528
|
+
/* @__PURE__ */ jsx13(ControlButton, { label: "Zoom in", onClick: () => zoomIn(), children: /* @__PURE__ */ jsx13(
|
|
1138
1529
|
"svg",
|
|
1139
1530
|
{
|
|
1140
1531
|
viewBox: "0 0 24 24",
|
|
@@ -1142,10 +1533,10 @@ function ZoomControls({ position = "bottom-right", className }) {
|
|
|
1142
1533
|
stroke: "currentColor",
|
|
1143
1534
|
strokeWidth: "2",
|
|
1144
1535
|
strokeLinecap: "round",
|
|
1145
|
-
children: /* @__PURE__ */
|
|
1536
|
+
children: /* @__PURE__ */ jsx13("path", { d: "M12 5v14M5 12h14" })
|
|
1146
1537
|
}
|
|
1147
1538
|
) }),
|
|
1148
|
-
/* @__PURE__ */
|
|
1539
|
+
/* @__PURE__ */ jsx13(ControlButton, { label: "Zoom out", onClick: () => zoomOut(), children: /* @__PURE__ */ jsx13(
|
|
1149
1540
|
"svg",
|
|
1150
1541
|
{
|
|
1151
1542
|
viewBox: "0 0 24 24",
|
|
@@ -1153,10 +1544,10 @@ function ZoomControls({ position = "bottom-right", className }) {
|
|
|
1153
1544
|
stroke: "currentColor",
|
|
1154
1545
|
strokeWidth: "2",
|
|
1155
1546
|
strokeLinecap: "round",
|
|
1156
|
-
children: /* @__PURE__ */
|
|
1547
|
+
children: /* @__PURE__ */ jsx13("path", { d: "M5 12h14" })
|
|
1157
1548
|
}
|
|
1158
1549
|
) }),
|
|
1159
|
-
/* @__PURE__ */
|
|
1550
|
+
/* @__PURE__ */ jsx13(ControlButton, { label: "Fit view", onClick: () => fitView(), children: /* @__PURE__ */ jsx13(
|
|
1160
1551
|
"svg",
|
|
1161
1552
|
{
|
|
1162
1553
|
viewBox: "0 0 24 24",
|
|
@@ -1165,7 +1556,7 @@ function ZoomControls({ position = "bottom-right", className }) {
|
|
|
1165
1556
|
strokeWidth: "2",
|
|
1166
1557
|
strokeLinecap: "round",
|
|
1167
1558
|
strokeLinejoin: "round",
|
|
1168
|
-
children: /* @__PURE__ */
|
|
1559
|
+
children: /* @__PURE__ */ jsx13("path", { d: "M8 3H5a2 2 0 0 0-2 2v3M21 8V5a2 2 0 0 0-2-2h-3M16 21h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3" })
|
|
1169
1560
|
}
|
|
1170
1561
|
) })
|
|
1171
1562
|
]
|
|
@@ -1177,11 +1568,11 @@ function ZoomControls({ position = "bottom-right", className }) {
|
|
|
1177
1568
|
import "react";
|
|
1178
1569
|
import { Handle as Handle3, NodeResizer, Position as Position6, useNodes } from "@xyflow/react";
|
|
1179
1570
|
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
1180
|
-
import { cn as
|
|
1571
|
+
import { cn as cn10 } from "@elabs-ai/components-ui/lib/cn";
|
|
1181
1572
|
|
|
1182
1573
|
// src/use-flow-groups/use-flow-groups.ts
|
|
1183
|
-
import { useCallback as
|
|
1184
|
-
import { useReactFlow as
|
|
1574
|
+
import { useCallback as useCallback4 } from "react";
|
|
1575
|
+
import { useReactFlow as useReactFlow5 } from "@xyflow/react";
|
|
1185
1576
|
|
|
1186
1577
|
// src/use-flow-groups/group-operations.ts
|
|
1187
1578
|
import { getNodesBounds } from "@xyflow/react";
|
|
@@ -1375,15 +1766,15 @@ function generateGroupId() {
|
|
|
1375
1766
|
return `flow-group-${Math.random().toString(36).slice(2, 9)}`;
|
|
1376
1767
|
}
|
|
1377
1768
|
function useFlowGroups() {
|
|
1378
|
-
const { getNodes, getEdges, setNodes, setEdges } =
|
|
1379
|
-
const apply =
|
|
1769
|
+
const { getNodes, getEdges, setNodes, setEdges } = useReactFlow5();
|
|
1770
|
+
const apply = useCallback4(
|
|
1380
1771
|
(result) => {
|
|
1381
1772
|
setNodes(result.nodes);
|
|
1382
1773
|
setEdges(result.edges);
|
|
1383
1774
|
},
|
|
1384
1775
|
[setNodes, setEdges]
|
|
1385
1776
|
);
|
|
1386
|
-
const groupNodes2 =
|
|
1777
|
+
const groupNodes2 = useCallback4(
|
|
1387
1778
|
(nodeIds, options) => {
|
|
1388
1779
|
if (nodeIds.length === 0) return void 0;
|
|
1389
1780
|
const groupId = options?.groupId ?? generateGroupId();
|
|
@@ -1396,26 +1787,26 @@ function useFlowGroups() {
|
|
|
1396
1787
|
},
|
|
1397
1788
|
[getNodes, getEdges, apply]
|
|
1398
1789
|
);
|
|
1399
|
-
const groupSelection =
|
|
1790
|
+
const groupSelection = useCallback4(
|
|
1400
1791
|
(options) => {
|
|
1401
1792
|
const selectedIds = getNodes().filter((n) => n.selected && !n.parentId).map((n) => n.id);
|
|
1402
1793
|
return groupNodes2(selectedIds, options);
|
|
1403
1794
|
},
|
|
1404
1795
|
[getNodes, groupNodes2]
|
|
1405
1796
|
);
|
|
1406
|
-
const ungroup2 =
|
|
1797
|
+
const ungroup2 = useCallback4(
|
|
1407
1798
|
(groupId) => apply(ungroup(getNodes(), getEdges(), groupId)),
|
|
1408
1799
|
[getNodes, getEdges, apply]
|
|
1409
1800
|
);
|
|
1410
|
-
const collapseGroup2 =
|
|
1801
|
+
const collapseGroup2 = useCallback4(
|
|
1411
1802
|
(groupId) => apply(collapseGroup(getNodes(), getEdges(), groupId)),
|
|
1412
1803
|
[getNodes, getEdges, apply]
|
|
1413
1804
|
);
|
|
1414
|
-
const expandGroup2 =
|
|
1805
|
+
const expandGroup2 = useCallback4(
|
|
1415
1806
|
(groupId) => apply(expandGroup(getNodes(), getEdges(), groupId)),
|
|
1416
1807
|
[getNodes, getEdges, apply]
|
|
1417
1808
|
);
|
|
1418
|
-
const toggleCollapse =
|
|
1809
|
+
const toggleCollapse = useCallback4(
|
|
1419
1810
|
(groupId) => apply(toggleGroupCollapsed(getNodes(), getEdges(), groupId)),
|
|
1420
1811
|
[getNodes, getEdges, apply]
|
|
1421
1812
|
);
|
|
@@ -1430,18 +1821,22 @@ function useFlowGroups() {
|
|
|
1430
1821
|
}
|
|
1431
1822
|
|
|
1432
1823
|
// src/flow-group-node/flow-group-node.tsx
|
|
1433
|
-
import { jsx as
|
|
1434
|
-
var
|
|
1824
|
+
import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1825
|
+
var toneMark = {
|
|
1435
1826
|
default: "text-muted-foreground",
|
|
1436
|
-
// #399 — the group LABEL is text on the canvas, so it takes the on-surface
|
|
1437
|
-
// `-text` rung. (The status rows below stay on their fill rungs; those are a
|
|
1438
|
-
// separate, still-open question, not part of #399's enumerated call sites.)
|
|
1439
1827
|
accent: "text-primary-text",
|
|
1440
1828
|
success: "text-success",
|
|
1441
1829
|
warning: "text-warning",
|
|
1442
1830
|
destructive: "text-destructive"
|
|
1443
1831
|
};
|
|
1444
|
-
var
|
|
1832
|
+
var toneInk = {
|
|
1833
|
+
default: "text-muted-foreground",
|
|
1834
|
+
accent: "text-primary-text",
|
|
1835
|
+
success: "text-success-text",
|
|
1836
|
+
warning: "text-warning-text",
|
|
1837
|
+
destructive: "text-destructive-text"
|
|
1838
|
+
};
|
|
1839
|
+
var handleClassName2 = `!size-2 !border-2 !border-flow-group-border !bg-flow-group ${FLOW_HANDLE_ANCHOR_CLASS}`;
|
|
1445
1840
|
function FlowGroupNode({ id, data, selected }) {
|
|
1446
1841
|
const { toggleCollapse } = useFlowGroups();
|
|
1447
1842
|
const nodes = useNodes();
|
|
@@ -1449,17 +1844,17 @@ function FlowGroupNode({ id, data, selected }) {
|
|
|
1449
1844
|
const tone = data.tone ?? "default";
|
|
1450
1845
|
const liveChildCount = nodes.filter((n) => n.parentId === id).length;
|
|
1451
1846
|
const childCount = liveChildCount || data.childCount || 0;
|
|
1452
|
-
return /* @__PURE__ */
|
|
1847
|
+
return /* @__PURE__ */ jsxs11(
|
|
1453
1848
|
"div",
|
|
1454
1849
|
{
|
|
1455
|
-
className:
|
|
1850
|
+
className: cn10(
|
|
1456
1851
|
"flex h-full w-full flex-col rounded-lg border bg-flow-group/60 text-foreground",
|
|
1457
1852
|
"border-flow-group-border transition-[box-shadow,border-color] duration-fast ease-standard motion-reduce:transition-none",
|
|
1458
1853
|
collapsed ? "shadow-sm" : "shadow-none",
|
|
1459
1854
|
selected && "ring-2 ring-ring"
|
|
1460
1855
|
),
|
|
1461
1856
|
children: [
|
|
1462
|
-
!collapsed && selected ? /* @__PURE__ */
|
|
1857
|
+
!collapsed && selected ? /* @__PURE__ */ jsx14(
|
|
1463
1858
|
NodeResizer,
|
|
1464
1859
|
{
|
|
1465
1860
|
minWidth: 160,
|
|
@@ -1468,39 +1863,39 @@ function FlowGroupNode({ id, data, selected }) {
|
|
|
1468
1863
|
handleClassName: handleClassName2
|
|
1469
1864
|
}
|
|
1470
1865
|
) : null,
|
|
1471
|
-
/* @__PURE__ */
|
|
1472
|
-
/* @__PURE__ */
|
|
1473
|
-
/* @__PURE__ */
|
|
1866
|
+
/* @__PURE__ */ jsx14(Handle3, { type: "target", position: Position6.Top, className: handleClassName2 }),
|
|
1867
|
+
/* @__PURE__ */ jsx14(Handle3, { type: "source", position: Position6.Bottom, className: handleClassName2 }),
|
|
1868
|
+
/* @__PURE__ */ jsxs11(
|
|
1474
1869
|
"div",
|
|
1475
1870
|
{
|
|
1476
|
-
className:
|
|
1871
|
+
className: cn10(
|
|
1477
1872
|
"flex items-center gap-2 px-3 py-2",
|
|
1478
1873
|
!collapsed && "border-b border-flow-group-border"
|
|
1479
1874
|
),
|
|
1480
1875
|
children: [
|
|
1481
|
-
/* @__PURE__ */
|
|
1876
|
+
/* @__PURE__ */ jsx14(
|
|
1482
1877
|
"button",
|
|
1483
1878
|
{
|
|
1484
1879
|
type: "button",
|
|
1485
1880
|
onClick: () => toggleCollapse(id),
|
|
1486
1881
|
"aria-label": collapsed ? `Expand group ${data.title}` : `Collapse group ${data.title}`,
|
|
1487
1882
|
"aria-expanded": !collapsed,
|
|
1488
|
-
className:
|
|
1883
|
+
className: cn10(
|
|
1489
1884
|
"-ml-1 grid size-5 shrink-0 place-items-center rounded",
|
|
1490
1885
|
"text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
1491
|
-
"focus-
|
|
1886
|
+
"focus-ring"
|
|
1492
1887
|
),
|
|
1493
|
-
children: collapsed ? /* @__PURE__ */
|
|
1888
|
+
children: collapsed ? /* @__PURE__ */ jsx14(ChevronRight, { "aria-hidden": "true", className: "size-4" }) : /* @__PURE__ */ jsx14(ChevronDown, { "aria-hidden": "true", className: "size-4" })
|
|
1494
1889
|
}
|
|
1495
1890
|
),
|
|
1496
|
-
data.icon ? /* @__PURE__ */
|
|
1497
|
-
/* @__PURE__ */
|
|
1498
|
-
/* @__PURE__ */
|
|
1891
|
+
data.icon ? /* @__PURE__ */ jsx14("span", { className: cn10("[&_svg]:size-4", toneMark[tone]), children: data.icon }) : null,
|
|
1892
|
+
/* @__PURE__ */ jsx14("span", { className: "min-w-0 flex-1 truncate text-body font-medium", children: data.title }),
|
|
1893
|
+
/* @__PURE__ */ jsx14(
|
|
1499
1894
|
"span",
|
|
1500
1895
|
{
|
|
1501
|
-
className:
|
|
1896
|
+
className: cn10(
|
|
1502
1897
|
"shrink-0 rounded-full bg-muted px-1.5 py-0.5 text-meta font-medium tabular-nums",
|
|
1503
|
-
|
|
1898
|
+
toneInk[tone]
|
|
1504
1899
|
),
|
|
1505
1900
|
"aria-label": `${childCount} ${childCount === 1 ? "node" : "nodes"}`,
|
|
1506
1901
|
children: childCount
|
|
@@ -1520,40 +1915,438 @@ import {
|
|
|
1520
1915
|
Controls,
|
|
1521
1916
|
MiniMap as MiniMap2,
|
|
1522
1917
|
Panel as Panel2,
|
|
1523
|
-
Position as
|
|
1918
|
+
Position as Position8,
|
|
1524
1919
|
ReactFlow as ReactFlow2,
|
|
1525
1920
|
ReactFlowProvider as ReactFlowProvider2,
|
|
1526
|
-
useReactFlow as
|
|
1921
|
+
useReactFlow as useReactFlow6,
|
|
1527
1922
|
useNodesState,
|
|
1528
1923
|
useEdgesState,
|
|
1529
1924
|
addEdge
|
|
1530
1925
|
} from "@xyflow/react";
|
|
1926
|
+
|
|
1927
|
+
// src/flow-weighted-edge/flow-weighted-edge.tsx
|
|
1928
|
+
import { useCallback as useCallback5, useMemo as useMemo3 } from "react";
|
|
1929
|
+
import {
|
|
1930
|
+
Position as Position7,
|
|
1931
|
+
getBezierPath as getBezierPath5,
|
|
1932
|
+
getSmoothStepPath,
|
|
1933
|
+
useEdges,
|
|
1934
|
+
useStore
|
|
1935
|
+
} from "@xyflow/react";
|
|
1936
|
+
import { resolveTokenColor } from "@elabs-ai/components-tokens";
|
|
1937
|
+
|
|
1938
|
+
// src/flow-weighted-edge/edge-label-pill.tsx
|
|
1939
|
+
import { EdgeLabelRenderer as EdgeLabelRenderer2 } from "@xyflow/react";
|
|
1940
|
+
import { cn as cn11 } from "@elabs-ai/components-ui/lib/cn";
|
|
1941
|
+
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1942
|
+
function EdgeLabelPill({
|
|
1943
|
+
label,
|
|
1944
|
+
secondaryLabel,
|
|
1945
|
+
x,
|
|
1946
|
+
y,
|
|
1947
|
+
selected,
|
|
1948
|
+
className,
|
|
1949
|
+
...props
|
|
1950
|
+
}) {
|
|
1951
|
+
if (!label && !secondaryLabel) return null;
|
|
1952
|
+
const accessibleName = [label, secondaryLabel].filter(Boolean).join(" \xB7 ");
|
|
1953
|
+
return /* @__PURE__ */ jsx15(EdgeLabelRenderer2, { children: /* @__PURE__ */ jsx15(
|
|
1954
|
+
"div",
|
|
1955
|
+
{
|
|
1956
|
+
style: {
|
|
1957
|
+
position: "absolute",
|
|
1958
|
+
transform: `translate(-50%, -50%) translate(${x}px, ${y}px)`,
|
|
1959
|
+
pointerEvents: "none"
|
|
1960
|
+
},
|
|
1961
|
+
className: "nodrag nopan",
|
|
1962
|
+
"data-slot": "edge-label-pill-anchor",
|
|
1963
|
+
children: /* @__PURE__ */ jsxs12(
|
|
1964
|
+
"button",
|
|
1965
|
+
{
|
|
1966
|
+
type: "button",
|
|
1967
|
+
"aria-label": accessibleName,
|
|
1968
|
+
"data-slot": "edge-label-pill",
|
|
1969
|
+
...props,
|
|
1970
|
+
className: cn11(
|
|
1971
|
+
"pointer-events-auto flex items-center gap-1 rounded-full border bg-flow-node px-2 py-0.5",
|
|
1972
|
+
"text-meta font-medium text-flow-node-foreground shadow-sm",
|
|
1973
|
+
"transition-colors duration-fast ease-standard",
|
|
1974
|
+
"focus-ring",
|
|
1975
|
+
selected ? "border-ring" : "border-flow-group-border",
|
|
1976
|
+
className
|
|
1977
|
+
),
|
|
1978
|
+
children: [
|
|
1979
|
+
label ? /* @__PURE__ */ jsx15("span", { "aria-hidden": "true", children: label }) : null,
|
|
1980
|
+
secondaryLabel ? /* @__PURE__ */ jsx15("span", { "aria-hidden": "true", className: "text-flow-node-foreground/70 tabular-nums", children: secondaryLabel }) : null
|
|
1981
|
+
]
|
|
1982
|
+
}
|
|
1983
|
+
)
|
|
1984
|
+
}
|
|
1985
|
+
) });
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
// src/flow-weighted-edge/back-edge-geometry.ts
|
|
1989
|
+
function backEdgeDetour(rects, axis, span, clearance) {
|
|
1990
|
+
const [lo, hi] = span[0] <= span[1] ? span : [span[1], span[0]];
|
|
1991
|
+
let far = Number.NEGATIVE_INFINITY;
|
|
1992
|
+
for (const rect of rects) {
|
|
1993
|
+
if (!(rect.width > 0) || !(rect.height > 0)) continue;
|
|
1994
|
+
const [near, beyond] = axis === "vertical" ? [rect.y, rect.y + rect.height] : [rect.x, rect.x + rect.width];
|
|
1995
|
+
if (beyond < lo || near > hi) continue;
|
|
1996
|
+
far = Math.max(far, axis === "vertical" ? rect.x + rect.width : rect.y + rect.height);
|
|
1997
|
+
}
|
|
1998
|
+
return Number.isFinite(far) ? far + clearance : null;
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
// src/flow-weighted-edge/flow-weighted-edge.tsx
|
|
2002
|
+
import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2003
|
+
var NO_NODES = [];
|
|
2004
|
+
var FALLBACK_WEAK = "#6085a1";
|
|
2005
|
+
var FALLBACK_STRONG = "#496d89";
|
|
2006
|
+
var BACK_EDGE_DASHARRAY = "6 4";
|
|
2007
|
+
var BACK_EDGE_OPACITY = 0.7;
|
|
2008
|
+
var BACK_EDGE_CLEARANCE = 40;
|
|
2009
|
+
var DEFAULT_BACK_EDGE_LABEL = "Back edge \u2014 runs against the process direction";
|
|
2010
|
+
function clamp01(t) {
|
|
2011
|
+
return Math.min(1, Math.max(0, t));
|
|
2012
|
+
}
|
|
2013
|
+
function hexToRgb(hex) {
|
|
2014
|
+
const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i.exec(hex);
|
|
2015
|
+
if (!m) return [0, 0, 0];
|
|
2016
|
+
return [parseInt(m[1], 16), parseInt(m[2], 16), parseInt(m[3], 16)];
|
|
2017
|
+
}
|
|
2018
|
+
function toHexByte(n) {
|
|
2019
|
+
return Math.round(Math.min(255, Math.max(0, n))).toString(16).padStart(2, "0");
|
|
2020
|
+
}
|
|
2021
|
+
function mixHex(a, b, t) {
|
|
2022
|
+
const [ar, ag, ab] = hexToRgb(a);
|
|
2023
|
+
const [br, bg, bb] = hexToRgb(b);
|
|
2024
|
+
const r = ar + (br - ar) * t;
|
|
2025
|
+
const g = ag + (bg - ag) * t;
|
|
2026
|
+
const bl = ab + (bb - ab) * t;
|
|
2027
|
+
return `#${toHexByte(r)}${toHexByte(g)}${toHexByte(bl)}`;
|
|
2028
|
+
}
|
|
2029
|
+
function resolveValueStrokeColor(value, domain) {
|
|
2030
|
+
if (value === void 0 || !domain) return void 0;
|
|
2031
|
+
const [lo, hi] = domain;
|
|
2032
|
+
const t = hi === lo ? 1 : clamp01((value - lo) / (hi - lo));
|
|
2033
|
+
const weak = resolveTokenColor("--flow-edge-weak", { fallback: FALLBACK_WEAK });
|
|
2034
|
+
const strong = resolveTokenColor("--flow-edge-strong", { fallback: FALLBACK_STRONG });
|
|
2035
|
+
return mixHex(weak, strong, t);
|
|
2036
|
+
}
|
|
2037
|
+
function FlowWeightedEdge({
|
|
2038
|
+
id,
|
|
2039
|
+
sourceX,
|
|
2040
|
+
sourceY,
|
|
2041
|
+
targetX,
|
|
2042
|
+
targetY,
|
|
2043
|
+
sourcePosition,
|
|
2044
|
+
targetPosition,
|
|
2045
|
+
markerEnd,
|
|
2046
|
+
style,
|
|
2047
|
+
selected,
|
|
2048
|
+
data
|
|
2049
|
+
}) {
|
|
2050
|
+
const edges = useEdges();
|
|
2051
|
+
const widthByEdgeId = useMemo3(
|
|
2052
|
+
() => computeEdgeWeightScale(edges),
|
|
2053
|
+
[edges]
|
|
2054
|
+
);
|
|
2055
|
+
const variant = data?.variant ?? "forward";
|
|
2056
|
+
const isBack = variant === "back";
|
|
2057
|
+
const nodes = useStore(
|
|
2058
|
+
useCallback5((s) => isBack ? s.nodes : NO_NODES, [isBack])
|
|
2059
|
+
);
|
|
2060
|
+
const pathType = data?.path ?? "bezier";
|
|
2061
|
+
const axis = targetPosition === Position7.Top || targetPosition === Position7.Bottom ? "vertical" : "horizontal";
|
|
2062
|
+
const rects = useMemo3(() => {
|
|
2063
|
+
if (!isBack) return [];
|
|
2064
|
+
const out = [];
|
|
2065
|
+
for (const node of nodes) {
|
|
2066
|
+
if (node.parentId) continue;
|
|
2067
|
+
const width = node.measured?.width ?? node.width;
|
|
2068
|
+
const height = node.measured?.height ?? node.height;
|
|
2069
|
+
if (!width || !height) continue;
|
|
2070
|
+
out.push({ x: node.position.x, y: node.position.y, width, height });
|
|
2071
|
+
}
|
|
2072
|
+
return out;
|
|
2073
|
+
}, [isBack, nodes]);
|
|
2074
|
+
const detour = isBack ? backEdgeDetour(
|
|
2075
|
+
rects,
|
|
2076
|
+
axis,
|
|
2077
|
+
axis === "vertical" ? [sourceY, targetY] : [sourceX, targetX],
|
|
2078
|
+
BACK_EDGE_CLEARANCE
|
|
2079
|
+
) : null;
|
|
2080
|
+
const [edgePath, labelX, labelY] = isBack ? getSmoothStepPath({
|
|
2081
|
+
sourceX,
|
|
2082
|
+
sourceY,
|
|
2083
|
+
sourcePosition,
|
|
2084
|
+
targetX,
|
|
2085
|
+
targetY,
|
|
2086
|
+
targetPosition,
|
|
2087
|
+
offset: BACK_EDGE_CLEARANCE,
|
|
2088
|
+
...detour === null ? {} : axis === "vertical" ? { centerX: detour } : { centerY: detour }
|
|
2089
|
+
}) : pathType === "smoothstep" ? getSmoothStepPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition }) : getBezierPath5({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition });
|
|
2090
|
+
const scaledWidth = widthByEdgeId.get(id) ?? DEFAULT_EDGE_WIDTH_RANGE[0];
|
|
2091
|
+
const valueColor = useMemo3(
|
|
2092
|
+
() => resolveValueStrokeColor(data?.value, data?.valueDomain),
|
|
2093
|
+
[data?.value, data?.valueDomain]
|
|
2094
|
+
);
|
|
2095
|
+
const stroke = selected ? "var(--ring)" : valueColor ?? "var(--flow-edge)";
|
|
2096
|
+
const strokeWidth = selected ? scaledWidth + 1.5 : scaledWidth;
|
|
2097
|
+
const edge = /* @__PURE__ */ jsx16(
|
|
2098
|
+
FlowEdgePath,
|
|
2099
|
+
{
|
|
2100
|
+
id,
|
|
2101
|
+
path: edgePath,
|
|
2102
|
+
markerEnd,
|
|
2103
|
+
"data-slot": "flow-weighted-edge",
|
|
2104
|
+
"data-variant": variant,
|
|
2105
|
+
"data-weight": data?.weight,
|
|
2106
|
+
"data-value": data?.value,
|
|
2107
|
+
stroke,
|
|
2108
|
+
strokeWidth,
|
|
2109
|
+
strokeDasharray: isBack ? BACK_EDGE_DASHARRAY : void 0,
|
|
2110
|
+
strokeOpacity: isBack ? BACK_EDGE_OPACITY : void 0,
|
|
2111
|
+
style
|
|
2112
|
+
}
|
|
2113
|
+
);
|
|
2114
|
+
return /* @__PURE__ */ jsxs13(Fragment5, { children: [
|
|
2115
|
+
isBack ? (
|
|
2116
|
+
// A `data-variant` is invisible to assistive technology, so the back
|
|
2117
|
+
// edge's meaning is also published as a named graphic. Only the back
|
|
2118
|
+
// variant is wrapped — a forward edge's DOM is unchanged from before
|
|
2119
|
+
// this prop existed.
|
|
2120
|
+
/* @__PURE__ */ jsx16("g", { role: "img", "aria-label": data?.variantLabel ?? DEFAULT_BACK_EDGE_LABEL, children: edge })
|
|
2121
|
+
) : edge,
|
|
2122
|
+
/* @__PURE__ */ jsx16(
|
|
2123
|
+
EdgeLabelPill,
|
|
2124
|
+
{
|
|
2125
|
+
label: data?.label,
|
|
2126
|
+
secondaryLabel: data?.secondaryLabel,
|
|
2127
|
+
x: labelX,
|
|
2128
|
+
y: labelY,
|
|
2129
|
+
selected,
|
|
2130
|
+
...data?.labelProps
|
|
2131
|
+
}
|
|
2132
|
+
)
|
|
2133
|
+
] });
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
// src/flow-weighted-edge/edge-aria.ts
|
|
2137
|
+
var DEFAULT_WEIGHT_LABEL = "weight";
|
|
2138
|
+
var DEFAULT_VALUE_LABEL = "value";
|
|
2139
|
+
function defaultNameOf(nodeId) {
|
|
2140
|
+
return nodeId;
|
|
2141
|
+
}
|
|
2142
|
+
function defaultFormatNumber(n) {
|
|
2143
|
+
return String(n);
|
|
2144
|
+
}
|
|
2145
|
+
function buildWeightedEdgeAriaLabel(edge, opts = {}) {
|
|
2146
|
+
if (edge.ariaLabel) return edge.ariaLabel;
|
|
2147
|
+
const { weight, value, label, secondaryLabel } = edge.data ?? {};
|
|
2148
|
+
if (weight === void 0 && value === void 0 && !label && !secondaryLabel) {
|
|
2149
|
+
return void 0;
|
|
2150
|
+
}
|
|
2151
|
+
const {
|
|
2152
|
+
weightLabel = DEFAULT_WEIGHT_LABEL,
|
|
2153
|
+
valueLabel = DEFAULT_VALUE_LABEL,
|
|
2154
|
+
nameOf = defaultNameOf,
|
|
2155
|
+
formatNumber = defaultFormatNumber
|
|
2156
|
+
} = opts;
|
|
2157
|
+
const parts = [`Edge from ${nameOf(edge.source)} to ${nameOf(edge.target)}`];
|
|
2158
|
+
if (weight !== void 0) parts.push(`${weightLabel} ${formatNumber(weight)}`);
|
|
2159
|
+
if (value !== void 0) parts.push(`${valueLabel} ${formatNumber(value)}`);
|
|
2160
|
+
const pillText = [label, secondaryLabel].filter(Boolean).join(" ");
|
|
2161
|
+
if (pillText) parts.push(pillText);
|
|
2162
|
+
return parts.join(", ");
|
|
2163
|
+
}
|
|
2164
|
+
function withWeightedEdgeAria(edges, opts) {
|
|
2165
|
+
return edges.map((edge) => {
|
|
2166
|
+
if (edge.ariaLabel) return edge;
|
|
2167
|
+
const ariaLabel = buildWeightedEdgeAriaLabel(edge, opts);
|
|
2168
|
+
return ariaLabel === void 0 ? edge : { ...edge, ariaLabel };
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
// src/flow-self-loop-edge/flow-self-loop-edge.tsx
|
|
2173
|
+
import { useMemo as useMemo4 } from "react";
|
|
2174
|
+
import { useEdges as useEdges2, useInternalNode as useInternalNode3 } from "@xyflow/react";
|
|
2175
|
+
|
|
2176
|
+
// src/flow-self-loop-edge/self-loop-geometry.ts
|
|
2177
|
+
var DEFAULT_LOOP_RADIUS = 28;
|
|
2178
|
+
var CONTROL_REACH_X = 1.2;
|
|
2179
|
+
var CONTROL_REACH_Y = 2.4;
|
|
2180
|
+
var APEX_FACTOR = 3 / 4 * CONTROL_REACH_Y;
|
|
2181
|
+
function selfLoopPath(anchor, loopRadius) {
|
|
2182
|
+
const cx = Number.isFinite(anchor.centerX) ? anchor.centerX : 0;
|
|
2183
|
+
const ay = Number.isFinite(anchor.topY) ? anchor.topY : 0;
|
|
2184
|
+
const r = Number.isFinite(loopRadius) && loopRadius > 0 ? loopRadius : DEFAULT_LOOP_RADIUS;
|
|
2185
|
+
const startX = cx + r;
|
|
2186
|
+
const endX = cx - r;
|
|
2187
|
+
const controlY = ay - r * CONTROL_REACH_Y;
|
|
2188
|
+
return {
|
|
2189
|
+
path: `M ${startX},${ay} C ${startX + r * CONTROL_REACH_X},${controlY} ${endX - r * CONTROL_REACH_X},${controlY} ${endX},${ay}`,
|
|
2190
|
+
labelX: cx,
|
|
2191
|
+
labelY: ay - r * APEX_FACTOR
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
2194
|
+
function outward(x, y, centerX, centerY, fallback) {
|
|
2195
|
+
const dx = x - centerX;
|
|
2196
|
+
const dy = y - centerY;
|
|
2197
|
+
const length = Math.hypot(dx, dy);
|
|
2198
|
+
if (!Number.isFinite(length) || length < 1e-6) return fallback;
|
|
2199
|
+
return [dx / length, dy / length];
|
|
2200
|
+
}
|
|
2201
|
+
function selfLoopHandleArc(anchor, loopRadius) {
|
|
2202
|
+
const num = (value) => Number.isFinite(value) ? value : 0;
|
|
2203
|
+
const sx = num(anchor.sourceX);
|
|
2204
|
+
const sy = num(anchor.sourceY);
|
|
2205
|
+
const tx = num(anchor.targetX);
|
|
2206
|
+
const ty = num(anchor.targetY);
|
|
2207
|
+
const cx = num(anchor.centerX);
|
|
2208
|
+
const cy = num(anchor.centerY);
|
|
2209
|
+
const width = Math.max(0, num(anchor.width));
|
|
2210
|
+
const height = Math.max(0, num(anchor.height));
|
|
2211
|
+
const r = Number.isFinite(loopRadius) && loopRadius > 0 ? loopRadius : DEFAULT_LOOP_RADIUS;
|
|
2212
|
+
const [osx, osy] = outward(sx, sy, cx, cy, [0, 1]);
|
|
2213
|
+
const [otx, oty] = outward(tx, ty, cx, cy, [-osx, -osy]);
|
|
2214
|
+
const lx = osy;
|
|
2215
|
+
const ly = -osx;
|
|
2216
|
+
const clearance = Math.abs(lx) * (width / 2) + Math.abs(ly) * (height / 2) + r;
|
|
2217
|
+
const reach = 4 / 3 * clearance;
|
|
2218
|
+
const c1x = sx + osx * r + lx * reach;
|
|
2219
|
+
const c1y = sy + osy * r + ly * reach;
|
|
2220
|
+
const c2x = tx + otx * r + lx * reach;
|
|
2221
|
+
const c2y = ty + oty * r + ly * reach;
|
|
2222
|
+
return {
|
|
2223
|
+
path: `M ${sx},${sy} C ${c1x},${c1y} ${c2x},${c2y} ${tx},${ty}`,
|
|
2224
|
+
// A cubic's midpoint is (P0 + 3·C1 + 3·C2 + P3) / 8 — the arc's apex, which `reach`
|
|
2225
|
+
// above places `loopRadius` clear of the card, so the label sits off the node.
|
|
2226
|
+
labelX: (sx + 3 * c1x + 3 * c2x + tx) / 8,
|
|
2227
|
+
labelY: (sy + 3 * c1y + 3 * c2y + ty) / 8
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
// src/flow-self-loop-edge/flow-self-loop-edge.tsx
|
|
2232
|
+
import { Fragment as Fragment6, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2233
|
+
function nodeName(data, fallback) {
|
|
2234
|
+
if (data && typeof data === "object" && "title" in data) {
|
|
2235
|
+
const title = data.title;
|
|
2236
|
+
if (typeof title === "string" && title.length > 0) return title;
|
|
2237
|
+
}
|
|
2238
|
+
return fallback;
|
|
2239
|
+
}
|
|
2240
|
+
function FlowSelfLoopEdge({
|
|
2241
|
+
id,
|
|
2242
|
+
source,
|
|
2243
|
+
sourceX,
|
|
2244
|
+
sourceY,
|
|
2245
|
+
targetX,
|
|
2246
|
+
targetY,
|
|
2247
|
+
markerEnd,
|
|
2248
|
+
style,
|
|
2249
|
+
selected,
|
|
2250
|
+
data
|
|
2251
|
+
}) {
|
|
2252
|
+
const edges = useEdges2();
|
|
2253
|
+
const widthByEdgeId = useMemo4(
|
|
2254
|
+
() => computeEdgeWeightScale(edges),
|
|
2255
|
+
[edges]
|
|
2256
|
+
);
|
|
2257
|
+
const node = useInternalNode3(source);
|
|
2258
|
+
const measuredWidth = node?.measured?.width;
|
|
2259
|
+
const measuredHeight = node?.measured?.height;
|
|
2260
|
+
const loopRadius = data?.loopRadius ?? DEFAULT_LOOP_RADIUS;
|
|
2261
|
+
const { path, labelX, labelY } = useMemo4(() => {
|
|
2262
|
+
if (node && measuredWidth && measuredHeight) {
|
|
2263
|
+
return selfLoopHandleArc(
|
|
2264
|
+
{
|
|
2265
|
+
sourceX,
|
|
2266
|
+
sourceY,
|
|
2267
|
+
targetX,
|
|
2268
|
+
targetY,
|
|
2269
|
+
centerX: node.internals.positionAbsolute.x + measuredWidth / 2,
|
|
2270
|
+
centerY: node.internals.positionAbsolute.y + measuredHeight / 2,
|
|
2271
|
+
width: measuredWidth,
|
|
2272
|
+
height: measuredHeight
|
|
2273
|
+
},
|
|
2274
|
+
loopRadius
|
|
2275
|
+
);
|
|
2276
|
+
}
|
|
2277
|
+
return selfLoopPath(
|
|
2278
|
+
{ centerX: (sourceX + targetX) / 2, topY: Math.min(sourceY, targetY) },
|
|
2279
|
+
loopRadius
|
|
2280
|
+
);
|
|
2281
|
+
}, [node, measuredWidth, measuredHeight, sourceX, sourceY, targetX, targetY, loopRadius]);
|
|
2282
|
+
const scaledWidth = widthByEdgeId.get(id) ?? DEFAULT_EDGE_WIDTH_RANGE[0];
|
|
2283
|
+
const stroke = selected ? "var(--ring)" : "var(--flow-edge)";
|
|
2284
|
+
const strokeWidth = selected ? scaledWidth + 1.5 : scaledWidth;
|
|
2285
|
+
const accessibleName = data?.loopLabel ?? `Self-loop on ${nodeName(node?.data, source)} \u2014 this step repeats`;
|
|
2286
|
+
return /* @__PURE__ */ jsxs14(Fragment6, { children: [
|
|
2287
|
+
/* @__PURE__ */ jsx17("g", { role: "img", "aria-label": accessibleName, children: /* @__PURE__ */ jsx17(
|
|
2288
|
+
FlowEdgePath,
|
|
2289
|
+
{
|
|
2290
|
+
id,
|
|
2291
|
+
path,
|
|
2292
|
+
markerEnd,
|
|
2293
|
+
"data-slot": "flow-self-loop-edge",
|
|
2294
|
+
stroke,
|
|
2295
|
+
strokeWidth,
|
|
2296
|
+
style: { fill: "none", ...style }
|
|
2297
|
+
}
|
|
2298
|
+
) }),
|
|
2299
|
+
/* @__PURE__ */ jsx17(
|
|
2300
|
+
EdgeLabelPill,
|
|
2301
|
+
{
|
|
2302
|
+
label: data?.label,
|
|
2303
|
+
secondaryLabel: data?.secondaryLabel,
|
|
2304
|
+
x: labelX,
|
|
2305
|
+
y: labelY,
|
|
2306
|
+
selected,
|
|
2307
|
+
...data?.labelProps
|
|
2308
|
+
}
|
|
2309
|
+
)
|
|
2310
|
+
] });
|
|
2311
|
+
}
|
|
1531
2312
|
export {
|
|
1532
2313
|
Background2 as Background,
|
|
1533
2314
|
CanvasShell,
|
|
1534
2315
|
Controls,
|
|
2316
|
+
DEFAULT_EDGE_WIDTH_RANGE,
|
|
2317
|
+
DEFAULT_LOOP_RADIUS,
|
|
2318
|
+
EdgeLabelPill,
|
|
1535
2319
|
FLOW_ALL_SIDE_HANDLES,
|
|
2320
|
+
FLOW_EDGE_FOCUS_CONTOUR_WIDTH,
|
|
2321
|
+
FLOW_EDGE_FOCUS_RING_WIDTH,
|
|
1536
2322
|
FLOW_GROUP_NODE_TYPE,
|
|
2323
|
+
FLOW_HANDLE_ANCHOR_CLASS,
|
|
1537
2324
|
FlowButtonEdge,
|
|
1538
2325
|
FlowEdge,
|
|
2326
|
+
FlowEdgePath,
|
|
1539
2327
|
FlowFloatingEdge,
|
|
1540
2328
|
FlowGroupNode,
|
|
1541
2329
|
FlowMiniMap,
|
|
1542
2330
|
FlowNode,
|
|
1543
2331
|
FlowPlaceholderNode,
|
|
2332
|
+
FlowSelfLoopEdge,
|
|
1544
2333
|
FlowSmartEdge,
|
|
2334
|
+
FlowWeightedEdge,
|
|
1545
2335
|
HANDLE_SIDES,
|
|
1546
2336
|
HelperLines,
|
|
1547
2337
|
InspectorPanel,
|
|
1548
2338
|
Legend,
|
|
1549
2339
|
MiniMap2 as MiniMap,
|
|
1550
2340
|
Panel2 as Panel,
|
|
1551
|
-
|
|
2341
|
+
Position8 as Position,
|
|
1552
2342
|
ReactFlow2 as ReactFlow,
|
|
1553
2343
|
ReactFlowProvider2 as ReactFlowProvider,
|
|
1554
2344
|
ZoomControls,
|
|
1555
2345
|
addEdge,
|
|
2346
|
+
backEdgeDetour,
|
|
2347
|
+
buildWeightedEdgeAriaLabel,
|
|
1556
2348
|
collapseGroup,
|
|
2349
|
+
computeEdgeWeightScale,
|
|
1557
2350
|
expandGroup,
|
|
1558
2351
|
getEdgeParams,
|
|
1559
2352
|
getHelperLines,
|
|
@@ -1562,7 +2355,11 @@ export {
|
|
|
1562
2355
|
isFlowGroupProxyEdge,
|
|
1563
2356
|
layoutFlow,
|
|
1564
2357
|
layoutGraph,
|
|
2358
|
+
pickClosestAnchors,
|
|
1565
2359
|
pickClosestHandles,
|
|
2360
|
+
positionToSide,
|
|
2361
|
+
selfLoopHandleArc,
|
|
2362
|
+
selfLoopPath,
|
|
1566
2363
|
sideToPosition,
|
|
1567
2364
|
toggleGroupCollapsed,
|
|
1568
2365
|
ungroup,
|
|
@@ -1572,6 +2369,7 @@ export {
|
|
|
1572
2369
|
useFlowLayout,
|
|
1573
2370
|
useHelperLines,
|
|
1574
2371
|
useNodesState,
|
|
1575
|
-
|
|
2372
|
+
useReactFlow6 as useReactFlow,
|
|
2373
|
+
withWeightedEdgeAria
|
|
1576
2374
|
};
|
|
1577
2375
|
//# sourceMappingURL=index.js.map
|