@elabs-ai/components-flow 4.0.0 → 4.2.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 +738 -20
- package/dist/index.js +985 -182
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/__contract__/inspector-panel.contract.test.tsx +49 -0
- package/src/__contract__/legend.contract.test.tsx +49 -0
- 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 +103 -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 +22 -9
- package/src/legend/index.ts +7 -1
- package/src/legend/legend.stories.tsx +126 -0
- package/src/legend/legend.test.tsx +180 -0
- package/src/legend/legend.tsx +222 -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,
|
|
@@ -985,15 +1226,15 @@ function FlowMiniMap({
|
|
|
985
1226
|
|
|
986
1227
|
// src/inspector-panel/inspector-panel.tsx
|
|
987
1228
|
import "react";
|
|
988
|
-
import { Reveal, useCollapsiblePanel } from "@elabs-ai/components-ui";
|
|
989
|
-
import { cn as
|
|
990
|
-
import { jsx as
|
|
1229
|
+
import { Reveal, useCollapsiblePanel, useLocale } from "@elabs-ai/components-ui";
|
|
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
|
-
title
|
|
1234
|
+
title,
|
|
994
1235
|
children,
|
|
995
1236
|
onClose,
|
|
996
|
-
emptyMessage
|
|
1237
|
+
emptyMessage,
|
|
997
1238
|
hasSelection = true,
|
|
998
1239
|
selectionKey,
|
|
999
1240
|
open,
|
|
@@ -1003,6 +1244,9 @@ function InspectorPanel({
|
|
|
1003
1244
|
width = INSPECTOR_PANEL_WIDTH,
|
|
1004
1245
|
className
|
|
1005
1246
|
}) {
|
|
1247
|
+
const { t } = useLocale();
|
|
1248
|
+
const resolvedTitle = title ?? t("flow.inspectorPanel.title");
|
|
1249
|
+
const resolvedEmptyMessage = emptyMessage ?? t("flow.inspectorPanel.emptyMessage");
|
|
1006
1250
|
const panel = useCollapsiblePanel({
|
|
1007
1251
|
side,
|
|
1008
1252
|
open,
|
|
@@ -1015,31 +1259,31 @@ function InspectorPanel({
|
|
|
1015
1259
|
right: "right-0 group-data-[state=collapsed]:right-[calc(var(--inspector-panel-width)*-1)]"
|
|
1016
1260
|
}
|
|
1017
1261
|
});
|
|
1018
|
-
return /* @__PURE__ */
|
|
1262
|
+
return /* @__PURE__ */ jsxs8(
|
|
1019
1263
|
"div",
|
|
1020
1264
|
{
|
|
1021
|
-
className:
|
|
1265
|
+
className: cn7("group relative h-full overflow-hidden", className),
|
|
1022
1266
|
style: { "--inspector-panel-width": width },
|
|
1023
1267
|
inert: panel.open ? void 0 : true,
|
|
1024
1268
|
...panel.attrs,
|
|
1025
1269
|
children: [
|
|
1026
|
-
/* @__PURE__ */
|
|
1027
|
-
/* @__PURE__ */
|
|
1270
|
+
/* @__PURE__ */ jsx11("div", { "data-slot": "inspector-panel-gap", className: panel.spacerClassName }),
|
|
1271
|
+
/* @__PURE__ */ jsx11(
|
|
1028
1272
|
"div",
|
|
1029
1273
|
{
|
|
1030
1274
|
"data-slot": "inspector-panel-container",
|
|
1031
|
-
className:
|
|
1032
|
-
children: /* @__PURE__ */
|
|
1033
|
-
/* @__PURE__ */
|
|
1034
|
-
/* @__PURE__ */
|
|
1035
|
-
onClose ? /* @__PURE__ */
|
|
1275
|
+
className: cn7(panel.containerClassName, "absolute inset-y-0 flex h-full border-s"),
|
|
1276
|
+
children: /* @__PURE__ */ jsxs8("div", { className: "flex h-full w-full flex-col bg-card", children: [
|
|
1277
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between border-b px-4 py-3", children: [
|
|
1278
|
+
/* @__PURE__ */ jsx11("h3", { className: "text-body font-semibold text-foreground", children: resolvedTitle }),
|
|
1279
|
+
onClose ? /* @__PURE__ */ jsx11(
|
|
1036
1280
|
"button",
|
|
1037
1281
|
{
|
|
1038
1282
|
type: "button",
|
|
1039
|
-
"aria-label": "
|
|
1283
|
+
"aria-label": t("flow.inspectorPanel.close"),
|
|
1040
1284
|
onClick: onClose,
|
|
1041
|
-
className: "rounded-sm p-1 text-muted-foreground hover:text-foreground focus-
|
|
1042
|
-
children: /* @__PURE__ */
|
|
1285
|
+
className: "rounded-sm p-1 text-muted-foreground hover:text-foreground focus-ring",
|
|
1286
|
+
children: /* @__PURE__ */ jsx11(
|
|
1043
1287
|
"svg",
|
|
1044
1288
|
{
|
|
1045
1289
|
width: "14",
|
|
@@ -1051,22 +1295,23 @@ function InspectorPanel({
|
|
|
1051
1295
|
strokeLinecap: "round",
|
|
1052
1296
|
strokeLinejoin: "round",
|
|
1053
1297
|
"aria-hidden": "true",
|
|
1054
|
-
children: /* @__PURE__ */
|
|
1298
|
+
children: /* @__PURE__ */ jsx11("path", { d: "M18 6 6 18M6 6l12 12" })
|
|
1055
1299
|
}
|
|
1056
1300
|
)
|
|
1057
1301
|
}
|
|
1058
1302
|
) : null
|
|
1059
1303
|
] }),
|
|
1060
|
-
hasSelection ? /* @__PURE__ */
|
|
1304
|
+
hasSelection ? /* @__PURE__ */ jsx11(
|
|
1061
1305
|
Reveal,
|
|
1062
1306
|
{
|
|
1063
1307
|
appear: "fade",
|
|
1064
1308
|
speed: "fast",
|
|
1065
|
-
|
|
1309
|
+
tabIndex: 0,
|
|
1310
|
+
className: "focus-ring flex-1 overflow-y-auto p-4 text-body",
|
|
1066
1311
|
children
|
|
1067
1312
|
},
|
|
1068
1313
|
selectionKey
|
|
1069
|
-
) : /* @__PURE__ */
|
|
1314
|
+
) : /* @__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: resolvedEmptyMessage }) })
|
|
1070
1315
|
] })
|
|
1071
1316
|
}
|
|
1072
1317
|
)
|
|
@@ -1076,20 +1321,64 @@ function InspectorPanel({
|
|
|
1076
1321
|
}
|
|
1077
1322
|
|
|
1078
1323
|
// src/legend/legend.tsx
|
|
1079
|
-
import {
|
|
1080
|
-
import {
|
|
1081
|
-
|
|
1082
|
-
|
|
1324
|
+
import { useLocale as useLocale2 } from "@elabs-ai/components-ui";
|
|
1325
|
+
import { cn as cn8 } from "@elabs-ai/components-ui/lib/cn";
|
|
1326
|
+
|
|
1327
|
+
// src/flow-weighted-edge/weight-scale.ts
|
|
1328
|
+
var DEFAULT_EDGE_WIDTH_RANGE = [1.5, 8];
|
|
1329
|
+
var DEFAULT_SCALE_GROUP = "__default__";
|
|
1330
|
+
function computeEdgeWeightScale(edges, opts = {}) {
|
|
1331
|
+
const [minWidth, maxWidth] = opts.widthRange ?? DEFAULT_EDGE_WIDTH_RANGE;
|
|
1332
|
+
const result = /* @__PURE__ */ new Map();
|
|
1333
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1334
|
+
for (const edge of edges) {
|
|
1335
|
+
const weight = edge.data?.weight;
|
|
1336
|
+
if (weight === void 0) {
|
|
1337
|
+
result.set(edge.id, minWidth);
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
const group = edge.data?.scaleGroup ?? DEFAULT_SCALE_GROUP;
|
|
1341
|
+
if (opts.scaleGroup !== void 0 && group !== opts.scaleGroup) continue;
|
|
1342
|
+
const list = groups.get(group);
|
|
1343
|
+
if (list) list.push({ id: edge.id, weight });
|
|
1344
|
+
else groups.set(group, [{ id: edge.id, weight }]);
|
|
1345
|
+
}
|
|
1346
|
+
for (const list of groups.values()) {
|
|
1347
|
+
let min = Infinity;
|
|
1348
|
+
let max = -Infinity;
|
|
1349
|
+
for (const { weight } of list) {
|
|
1350
|
+
if (weight < min) min = weight;
|
|
1351
|
+
if (weight > max) max = weight;
|
|
1352
|
+
}
|
|
1353
|
+
const span = max - min;
|
|
1354
|
+
for (const { id, weight } of list) {
|
|
1355
|
+
const width = span === 0 ? (minWidth + maxWidth) / 2 : minWidth + (weight - min) / span * (maxWidth - minWidth);
|
|
1356
|
+
result.set(id, width);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
return result;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
// src/legend/legend.tsx
|
|
1363
|
+
import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1364
|
+
function Legend(props) {
|
|
1365
|
+
if (props.variant === "scale") {
|
|
1366
|
+
return /* @__PURE__ */ jsx12(LegendScale, { ...props });
|
|
1367
|
+
}
|
|
1368
|
+
return /* @__PURE__ */ jsx12(LegendCategorical, { ...props });
|
|
1369
|
+
}
|
|
1370
|
+
function LegendCategorical({ items, title, className }) {
|
|
1371
|
+
return /* @__PURE__ */ jsxs9(
|
|
1083
1372
|
"div",
|
|
1084
1373
|
{
|
|
1085
|
-
className:
|
|
1374
|
+
className: cn8(
|
|
1086
1375
|
"rounded-lg bg-surface-elevated/90 p-3 text-xs shadow-ring-sm backdrop-blur",
|
|
1087
1376
|
className
|
|
1088
1377
|
),
|
|
1089
1378
|
children: [
|
|
1090
|
-
title ? /* @__PURE__ */
|
|
1091
|
-
/* @__PURE__ */
|
|
1092
|
-
/* @__PURE__ */
|
|
1379
|
+
title ? /* @__PURE__ */ jsx12("div", { className: "mb-1.5 font-medium text-foreground", children: title }) : null,
|
|
1380
|
+
/* @__PURE__ */ jsx12("ul", { className: "space-y-1", children: items.map((item) => /* @__PURE__ */ jsxs9("li", { className: "flex items-center gap-2 text-muted-foreground", children: [
|
|
1381
|
+
/* @__PURE__ */ jsx12(
|
|
1093
1382
|
"span",
|
|
1094
1383
|
{
|
|
1095
1384
|
"aria-hidden": "true",
|
|
@@ -1103,38 +1392,145 @@ function Legend({ items, title, className }) {
|
|
|
1103
1392
|
}
|
|
1104
1393
|
);
|
|
1105
1394
|
}
|
|
1395
|
+
function computeWidthSamples(domain, ticks) {
|
|
1396
|
+
const [min, max] = domain;
|
|
1397
|
+
const values = ticks === "minmedmax" ? [min, (min + max) / 2, max] : [min, max];
|
|
1398
|
+
const likeEdges = values.map((weight, index) => ({
|
|
1399
|
+
id: String(index),
|
|
1400
|
+
data: { weight }
|
|
1401
|
+
}));
|
|
1402
|
+
const widths = computeEdgeWeightScale(likeEdges);
|
|
1403
|
+
return values.map((value, index) => ({ value, width: widths.get(String(index)) ?? 0 }));
|
|
1404
|
+
}
|
|
1405
|
+
var COLOR_STOP_FRACTIONS = [0, 0.25, 0.5, 0.75, 1];
|
|
1406
|
+
function scaleKindLabel(kind) {
|
|
1407
|
+
return kind === "width" ? "width" : "color";
|
|
1408
|
+
}
|
|
1409
|
+
function LegendScale({
|
|
1410
|
+
kind,
|
|
1411
|
+
domain,
|
|
1412
|
+
format,
|
|
1413
|
+
ticks = "minmax",
|
|
1414
|
+
title,
|
|
1415
|
+
className
|
|
1416
|
+
}) {
|
|
1417
|
+
const { formatNumber } = useLocale2();
|
|
1418
|
+
const format_ = format ?? formatNumber;
|
|
1419
|
+
const [min, max] = domain;
|
|
1420
|
+
const ariaLabel = `Edge ${scaleKindLabel(kind)} scale, ${format_(min)} to ${format_(max)}, minimum to maximum`;
|
|
1421
|
+
return /* @__PURE__ */ jsxs9(
|
|
1422
|
+
"div",
|
|
1423
|
+
{
|
|
1424
|
+
"aria-label": ariaLabel,
|
|
1425
|
+
className: cn8(
|
|
1426
|
+
"flex flex-col gap-2 rounded-lg bg-surface-elevated/90 p-3 shadow-ring-sm backdrop-blur",
|
|
1427
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
1428
|
+
className
|
|
1429
|
+
),
|
|
1430
|
+
role: "group",
|
|
1431
|
+
tabIndex: 0,
|
|
1432
|
+
children: [
|
|
1433
|
+
title ? /* @__PURE__ */ jsx12("div", { className: "text-body font-medium text-foreground", children: title }) : null,
|
|
1434
|
+
kind === "width" ? /* @__PURE__ */ jsx12(LegendScaleWidth, { domain, format: format_, ticks }) : /* @__PURE__ */ jsx12(LegendScaleColor, { domain, format: format_ })
|
|
1435
|
+
]
|
|
1436
|
+
}
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
function LegendScaleWidth({
|
|
1440
|
+
domain,
|
|
1441
|
+
format,
|
|
1442
|
+
ticks
|
|
1443
|
+
}) {
|
|
1444
|
+
const samples = computeWidthSamples(domain, ticks);
|
|
1445
|
+
return /* @__PURE__ */ jsx12("div", { className: "flex flex-col gap-2", children: samples.map((sample, index) => (
|
|
1446
|
+
// Index disambiguates a zero-width domain, where every sample shares
|
|
1447
|
+
// the same value (and therefore the same width).
|
|
1448
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-3", children: [
|
|
1449
|
+
/* @__PURE__ */ jsx12("svg", { "aria-hidden": "true", className: "shrink-0", height: 16, overflow: "visible", width: 40, children: /* @__PURE__ */ jsx12(
|
|
1450
|
+
"line",
|
|
1451
|
+
{
|
|
1452
|
+
stroke: "var(--flow-edge)",
|
|
1453
|
+
strokeLinecap: "round",
|
|
1454
|
+
strokeWidth: sample.width,
|
|
1455
|
+
x1: 2,
|
|
1456
|
+
x2: 38,
|
|
1457
|
+
y1: 8,
|
|
1458
|
+
y2: 8
|
|
1459
|
+
}
|
|
1460
|
+
) }),
|
|
1461
|
+
/* @__PURE__ */ jsx12("span", { className: "min-w-0 truncate text-meta tabular-nums text-muted-foreground", children: format(sample.value) })
|
|
1462
|
+
] }, `${index}-${sample.value}`)
|
|
1463
|
+
)) });
|
|
1464
|
+
}
|
|
1465
|
+
function LegendScaleColor({
|
|
1466
|
+
domain,
|
|
1467
|
+
format
|
|
1468
|
+
}) {
|
|
1469
|
+
const [min, max] = domain;
|
|
1470
|
+
const span = max - min;
|
|
1471
|
+
const stops = COLOR_STOP_FRACTIONS.map((fraction) => ({
|
|
1472
|
+
fraction,
|
|
1473
|
+
value: min + fraction * span
|
|
1474
|
+
}));
|
|
1475
|
+
const gradient = `linear-gradient(to right, ${stops.map(({ fraction }) => {
|
|
1476
|
+
const percent = Math.round(fraction * 100);
|
|
1477
|
+
return `color-mix(in oklch, var(--flow-edge-strong) ${percent}%, var(--flow-edge-weak)) ${percent}%`;
|
|
1478
|
+
}).join(", ")})`;
|
|
1479
|
+
return /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-1.5", children: [
|
|
1480
|
+
/* @__PURE__ */ jsx12(
|
|
1481
|
+
"div",
|
|
1482
|
+
{
|
|
1483
|
+
"aria-hidden": "true",
|
|
1484
|
+
className: "h-3 w-full rounded-full",
|
|
1485
|
+
style: { backgroundImage: gradient }
|
|
1486
|
+
}
|
|
1487
|
+
),
|
|
1488
|
+
/* @__PURE__ */ jsx12("div", { className: "flex justify-between gap-1", children: stops.map((stop, index) => /* @__PURE__ */ jsx12(
|
|
1489
|
+
"span",
|
|
1490
|
+
{
|
|
1491
|
+
className: cn8(
|
|
1492
|
+
"min-w-0 truncate text-meta tabular-nums text-muted-foreground",
|
|
1493
|
+
index === 0 && "text-start",
|
|
1494
|
+
index === stops.length - 1 && "text-end"
|
|
1495
|
+
),
|
|
1496
|
+
children: format(stop.value)
|
|
1497
|
+
},
|
|
1498
|
+
`${index}-${stop.value}`
|
|
1499
|
+
)) })
|
|
1500
|
+
] });
|
|
1501
|
+
}
|
|
1106
1502
|
|
|
1107
1503
|
// src/zoom-controls/zoom-controls.tsx
|
|
1108
|
-
import { Panel, useReactFlow as
|
|
1109
|
-
import { cn as
|
|
1110
|
-
import { jsx as
|
|
1504
|
+
import { Panel, useReactFlow as useReactFlow4 } from "@xyflow/react";
|
|
1505
|
+
import { cn as cn9 } from "@elabs-ai/components-ui/lib/cn";
|
|
1506
|
+
import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1111
1507
|
function ControlButton({
|
|
1112
1508
|
label,
|
|
1113
1509
|
onClick,
|
|
1114
1510
|
children
|
|
1115
1511
|
}) {
|
|
1116
|
-
return /* @__PURE__ */
|
|
1512
|
+
return /* @__PURE__ */ jsx13(
|
|
1117
1513
|
"button",
|
|
1118
1514
|
{
|
|
1119
1515
|
type: "button",
|
|
1120
1516
|
"aria-label": label,
|
|
1121
1517
|
onClick,
|
|
1122
|
-
className: "flex size-8 items-center justify-center text-foreground transition-colors duration-fast hover:bg-surface-muted focus-
|
|
1518
|
+
className: "flex size-8 items-center justify-center text-foreground transition-colors duration-fast hover:bg-surface-muted focus-ring [&_svg]:size-4",
|
|
1123
1519
|
children
|
|
1124
1520
|
}
|
|
1125
1521
|
);
|
|
1126
1522
|
}
|
|
1127
1523
|
function ZoomControls({ position = "bottom-right", className }) {
|
|
1128
|
-
const { zoomIn, zoomOut, fitView } =
|
|
1129
|
-
return /* @__PURE__ */
|
|
1524
|
+
const { zoomIn, zoomOut, fitView } = useReactFlow4();
|
|
1525
|
+
return /* @__PURE__ */ jsx13(Panel, { position, children: /* @__PURE__ */ jsxs10(
|
|
1130
1526
|
"div",
|
|
1131
1527
|
{
|
|
1132
|
-
className:
|
|
1528
|
+
className: cn9(
|
|
1133
1529
|
"flex divide-x overflow-hidden rounded-lg bg-surface-elevated shadow-ring-sm",
|
|
1134
1530
|
className
|
|
1135
1531
|
),
|
|
1136
1532
|
children: [
|
|
1137
|
-
/* @__PURE__ */
|
|
1533
|
+
/* @__PURE__ */ jsx13(ControlButton, { label: "Zoom in", onClick: () => zoomIn(), children: /* @__PURE__ */ jsx13(
|
|
1138
1534
|
"svg",
|
|
1139
1535
|
{
|
|
1140
1536
|
viewBox: "0 0 24 24",
|
|
@@ -1142,10 +1538,10 @@ function ZoomControls({ position = "bottom-right", className }) {
|
|
|
1142
1538
|
stroke: "currentColor",
|
|
1143
1539
|
strokeWidth: "2",
|
|
1144
1540
|
strokeLinecap: "round",
|
|
1145
|
-
children: /* @__PURE__ */
|
|
1541
|
+
children: /* @__PURE__ */ jsx13("path", { d: "M12 5v14M5 12h14" })
|
|
1146
1542
|
}
|
|
1147
1543
|
) }),
|
|
1148
|
-
/* @__PURE__ */
|
|
1544
|
+
/* @__PURE__ */ jsx13(ControlButton, { label: "Zoom out", onClick: () => zoomOut(), children: /* @__PURE__ */ jsx13(
|
|
1149
1545
|
"svg",
|
|
1150
1546
|
{
|
|
1151
1547
|
viewBox: "0 0 24 24",
|
|
@@ -1153,10 +1549,10 @@ function ZoomControls({ position = "bottom-right", className }) {
|
|
|
1153
1549
|
stroke: "currentColor",
|
|
1154
1550
|
strokeWidth: "2",
|
|
1155
1551
|
strokeLinecap: "round",
|
|
1156
|
-
children: /* @__PURE__ */
|
|
1552
|
+
children: /* @__PURE__ */ jsx13("path", { d: "M5 12h14" })
|
|
1157
1553
|
}
|
|
1158
1554
|
) }),
|
|
1159
|
-
/* @__PURE__ */
|
|
1555
|
+
/* @__PURE__ */ jsx13(ControlButton, { label: "Fit view", onClick: () => fitView(), children: /* @__PURE__ */ jsx13(
|
|
1160
1556
|
"svg",
|
|
1161
1557
|
{
|
|
1162
1558
|
viewBox: "0 0 24 24",
|
|
@@ -1165,7 +1561,7 @@ function ZoomControls({ position = "bottom-right", className }) {
|
|
|
1165
1561
|
strokeWidth: "2",
|
|
1166
1562
|
strokeLinecap: "round",
|
|
1167
1563
|
strokeLinejoin: "round",
|
|
1168
|
-
children: /* @__PURE__ */
|
|
1564
|
+
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
1565
|
}
|
|
1170
1566
|
) })
|
|
1171
1567
|
]
|
|
@@ -1177,11 +1573,11 @@ function ZoomControls({ position = "bottom-right", className }) {
|
|
|
1177
1573
|
import "react";
|
|
1178
1574
|
import { Handle as Handle3, NodeResizer, Position as Position6, useNodes } from "@xyflow/react";
|
|
1179
1575
|
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
1180
|
-
import { cn as
|
|
1576
|
+
import { cn as cn10 } from "@elabs-ai/components-ui/lib/cn";
|
|
1181
1577
|
|
|
1182
1578
|
// src/use-flow-groups/use-flow-groups.ts
|
|
1183
|
-
import { useCallback as
|
|
1184
|
-
import { useReactFlow as
|
|
1579
|
+
import { useCallback as useCallback4 } from "react";
|
|
1580
|
+
import { useReactFlow as useReactFlow5 } from "@xyflow/react";
|
|
1185
1581
|
|
|
1186
1582
|
// src/use-flow-groups/group-operations.ts
|
|
1187
1583
|
import { getNodesBounds } from "@xyflow/react";
|
|
@@ -1375,15 +1771,15 @@ function generateGroupId() {
|
|
|
1375
1771
|
return `flow-group-${Math.random().toString(36).slice(2, 9)}`;
|
|
1376
1772
|
}
|
|
1377
1773
|
function useFlowGroups() {
|
|
1378
|
-
const { getNodes, getEdges, setNodes, setEdges } =
|
|
1379
|
-
const apply =
|
|
1774
|
+
const { getNodes, getEdges, setNodes, setEdges } = useReactFlow5();
|
|
1775
|
+
const apply = useCallback4(
|
|
1380
1776
|
(result) => {
|
|
1381
1777
|
setNodes(result.nodes);
|
|
1382
1778
|
setEdges(result.edges);
|
|
1383
1779
|
},
|
|
1384
1780
|
[setNodes, setEdges]
|
|
1385
1781
|
);
|
|
1386
|
-
const groupNodes2 =
|
|
1782
|
+
const groupNodes2 = useCallback4(
|
|
1387
1783
|
(nodeIds, options) => {
|
|
1388
1784
|
if (nodeIds.length === 0) return void 0;
|
|
1389
1785
|
const groupId = options?.groupId ?? generateGroupId();
|
|
@@ -1396,26 +1792,26 @@ function useFlowGroups() {
|
|
|
1396
1792
|
},
|
|
1397
1793
|
[getNodes, getEdges, apply]
|
|
1398
1794
|
);
|
|
1399
|
-
const groupSelection =
|
|
1795
|
+
const groupSelection = useCallback4(
|
|
1400
1796
|
(options) => {
|
|
1401
1797
|
const selectedIds = getNodes().filter((n) => n.selected && !n.parentId).map((n) => n.id);
|
|
1402
1798
|
return groupNodes2(selectedIds, options);
|
|
1403
1799
|
},
|
|
1404
1800
|
[getNodes, groupNodes2]
|
|
1405
1801
|
);
|
|
1406
|
-
const ungroup2 =
|
|
1802
|
+
const ungroup2 = useCallback4(
|
|
1407
1803
|
(groupId) => apply(ungroup(getNodes(), getEdges(), groupId)),
|
|
1408
1804
|
[getNodes, getEdges, apply]
|
|
1409
1805
|
);
|
|
1410
|
-
const collapseGroup2 =
|
|
1806
|
+
const collapseGroup2 = useCallback4(
|
|
1411
1807
|
(groupId) => apply(collapseGroup(getNodes(), getEdges(), groupId)),
|
|
1412
1808
|
[getNodes, getEdges, apply]
|
|
1413
1809
|
);
|
|
1414
|
-
const expandGroup2 =
|
|
1810
|
+
const expandGroup2 = useCallback4(
|
|
1415
1811
|
(groupId) => apply(expandGroup(getNodes(), getEdges(), groupId)),
|
|
1416
1812
|
[getNodes, getEdges, apply]
|
|
1417
1813
|
);
|
|
1418
|
-
const toggleCollapse =
|
|
1814
|
+
const toggleCollapse = useCallback4(
|
|
1419
1815
|
(groupId) => apply(toggleGroupCollapsed(getNodes(), getEdges(), groupId)),
|
|
1420
1816
|
[getNodes, getEdges, apply]
|
|
1421
1817
|
);
|
|
@@ -1430,18 +1826,22 @@ function useFlowGroups() {
|
|
|
1430
1826
|
}
|
|
1431
1827
|
|
|
1432
1828
|
// src/flow-group-node/flow-group-node.tsx
|
|
1433
|
-
import { jsx as
|
|
1434
|
-
var
|
|
1829
|
+
import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1830
|
+
var toneMark = {
|
|
1435
1831
|
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
1832
|
accent: "text-primary-text",
|
|
1440
1833
|
success: "text-success",
|
|
1441
1834
|
warning: "text-warning",
|
|
1442
1835
|
destructive: "text-destructive"
|
|
1443
1836
|
};
|
|
1444
|
-
var
|
|
1837
|
+
var toneInk = {
|
|
1838
|
+
default: "text-muted-foreground",
|
|
1839
|
+
accent: "text-primary-text",
|
|
1840
|
+
success: "text-success-text",
|
|
1841
|
+
warning: "text-warning-text",
|
|
1842
|
+
destructive: "text-destructive-text"
|
|
1843
|
+
};
|
|
1844
|
+
var handleClassName2 = `!size-2 !border-2 !border-flow-group-border !bg-flow-group ${FLOW_HANDLE_ANCHOR_CLASS}`;
|
|
1445
1845
|
function FlowGroupNode({ id, data, selected }) {
|
|
1446
1846
|
const { toggleCollapse } = useFlowGroups();
|
|
1447
1847
|
const nodes = useNodes();
|
|
@@ -1449,17 +1849,17 @@ function FlowGroupNode({ id, data, selected }) {
|
|
|
1449
1849
|
const tone = data.tone ?? "default";
|
|
1450
1850
|
const liveChildCount = nodes.filter((n) => n.parentId === id).length;
|
|
1451
1851
|
const childCount = liveChildCount || data.childCount || 0;
|
|
1452
|
-
return /* @__PURE__ */
|
|
1852
|
+
return /* @__PURE__ */ jsxs11(
|
|
1453
1853
|
"div",
|
|
1454
1854
|
{
|
|
1455
|
-
className:
|
|
1855
|
+
className: cn10(
|
|
1456
1856
|
"flex h-full w-full flex-col rounded-lg border bg-flow-group/60 text-foreground",
|
|
1457
1857
|
"border-flow-group-border transition-[box-shadow,border-color] duration-fast ease-standard motion-reduce:transition-none",
|
|
1458
1858
|
collapsed ? "shadow-sm" : "shadow-none",
|
|
1459
1859
|
selected && "ring-2 ring-ring"
|
|
1460
1860
|
),
|
|
1461
1861
|
children: [
|
|
1462
|
-
!collapsed && selected ? /* @__PURE__ */
|
|
1862
|
+
!collapsed && selected ? /* @__PURE__ */ jsx14(
|
|
1463
1863
|
NodeResizer,
|
|
1464
1864
|
{
|
|
1465
1865
|
minWidth: 160,
|
|
@@ -1468,39 +1868,39 @@ function FlowGroupNode({ id, data, selected }) {
|
|
|
1468
1868
|
handleClassName: handleClassName2
|
|
1469
1869
|
}
|
|
1470
1870
|
) : null,
|
|
1471
|
-
/* @__PURE__ */
|
|
1472
|
-
/* @__PURE__ */
|
|
1473
|
-
/* @__PURE__ */
|
|
1871
|
+
/* @__PURE__ */ jsx14(Handle3, { type: "target", position: Position6.Top, className: handleClassName2 }),
|
|
1872
|
+
/* @__PURE__ */ jsx14(Handle3, { type: "source", position: Position6.Bottom, className: handleClassName2 }),
|
|
1873
|
+
/* @__PURE__ */ jsxs11(
|
|
1474
1874
|
"div",
|
|
1475
1875
|
{
|
|
1476
|
-
className:
|
|
1876
|
+
className: cn10(
|
|
1477
1877
|
"flex items-center gap-2 px-3 py-2",
|
|
1478
1878
|
!collapsed && "border-b border-flow-group-border"
|
|
1479
1879
|
),
|
|
1480
1880
|
children: [
|
|
1481
|
-
/* @__PURE__ */
|
|
1881
|
+
/* @__PURE__ */ jsx14(
|
|
1482
1882
|
"button",
|
|
1483
1883
|
{
|
|
1484
1884
|
type: "button",
|
|
1485
1885
|
onClick: () => toggleCollapse(id),
|
|
1486
1886
|
"aria-label": collapsed ? `Expand group ${data.title}` : `Collapse group ${data.title}`,
|
|
1487
1887
|
"aria-expanded": !collapsed,
|
|
1488
|
-
className:
|
|
1888
|
+
className: cn10(
|
|
1489
1889
|
"-ml-1 grid size-5 shrink-0 place-items-center rounded",
|
|
1490
1890
|
"text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
1491
|
-
"focus-
|
|
1891
|
+
"focus-ring"
|
|
1492
1892
|
),
|
|
1493
|
-
children: collapsed ? /* @__PURE__ */
|
|
1893
|
+
children: collapsed ? /* @__PURE__ */ jsx14(ChevronRight, { "aria-hidden": "true", className: "size-4" }) : /* @__PURE__ */ jsx14(ChevronDown, { "aria-hidden": "true", className: "size-4" })
|
|
1494
1894
|
}
|
|
1495
1895
|
),
|
|
1496
|
-
data.icon ? /* @__PURE__ */
|
|
1497
|
-
/* @__PURE__ */
|
|
1498
|
-
/* @__PURE__ */
|
|
1896
|
+
data.icon ? /* @__PURE__ */ jsx14("span", { className: cn10("[&_svg]:size-4", toneMark[tone]), children: data.icon }) : null,
|
|
1897
|
+
/* @__PURE__ */ jsx14("span", { className: "min-w-0 flex-1 truncate text-body font-medium", children: data.title }),
|
|
1898
|
+
/* @__PURE__ */ jsx14(
|
|
1499
1899
|
"span",
|
|
1500
1900
|
{
|
|
1501
|
-
className:
|
|
1901
|
+
className: cn10(
|
|
1502
1902
|
"shrink-0 rounded-full bg-muted px-1.5 py-0.5 text-meta font-medium tabular-nums",
|
|
1503
|
-
|
|
1903
|
+
toneInk[tone]
|
|
1504
1904
|
),
|
|
1505
1905
|
"aria-label": `${childCount} ${childCount === 1 ? "node" : "nodes"}`,
|
|
1506
1906
|
children: childCount
|
|
@@ -1520,40 +1920,438 @@ import {
|
|
|
1520
1920
|
Controls,
|
|
1521
1921
|
MiniMap as MiniMap2,
|
|
1522
1922
|
Panel as Panel2,
|
|
1523
|
-
Position as
|
|
1923
|
+
Position as Position8,
|
|
1524
1924
|
ReactFlow as ReactFlow2,
|
|
1525
1925
|
ReactFlowProvider as ReactFlowProvider2,
|
|
1526
|
-
useReactFlow as
|
|
1926
|
+
useReactFlow as useReactFlow6,
|
|
1527
1927
|
useNodesState,
|
|
1528
1928
|
useEdgesState,
|
|
1529
1929
|
addEdge
|
|
1530
1930
|
} from "@xyflow/react";
|
|
1931
|
+
|
|
1932
|
+
// src/flow-weighted-edge/flow-weighted-edge.tsx
|
|
1933
|
+
import { useCallback as useCallback5, useMemo as useMemo3 } from "react";
|
|
1934
|
+
import {
|
|
1935
|
+
Position as Position7,
|
|
1936
|
+
getBezierPath as getBezierPath5,
|
|
1937
|
+
getSmoothStepPath,
|
|
1938
|
+
useEdges,
|
|
1939
|
+
useStore
|
|
1940
|
+
} from "@xyflow/react";
|
|
1941
|
+
import { resolveTokenColor } from "@elabs-ai/components-tokens";
|
|
1942
|
+
|
|
1943
|
+
// src/flow-weighted-edge/edge-label-pill.tsx
|
|
1944
|
+
import { EdgeLabelRenderer as EdgeLabelRenderer2 } from "@xyflow/react";
|
|
1945
|
+
import { cn as cn11 } from "@elabs-ai/components-ui/lib/cn";
|
|
1946
|
+
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1947
|
+
function EdgeLabelPill({
|
|
1948
|
+
label,
|
|
1949
|
+
secondaryLabel,
|
|
1950
|
+
x,
|
|
1951
|
+
y,
|
|
1952
|
+
selected,
|
|
1953
|
+
className,
|
|
1954
|
+
...props
|
|
1955
|
+
}) {
|
|
1956
|
+
if (!label && !secondaryLabel) return null;
|
|
1957
|
+
const accessibleName = [label, secondaryLabel].filter(Boolean).join(" \xB7 ");
|
|
1958
|
+
return /* @__PURE__ */ jsx15(EdgeLabelRenderer2, { children: /* @__PURE__ */ jsx15(
|
|
1959
|
+
"div",
|
|
1960
|
+
{
|
|
1961
|
+
style: {
|
|
1962
|
+
position: "absolute",
|
|
1963
|
+
transform: `translate(-50%, -50%) translate(${x}px, ${y}px)`,
|
|
1964
|
+
pointerEvents: "none"
|
|
1965
|
+
},
|
|
1966
|
+
className: "nodrag nopan",
|
|
1967
|
+
"data-slot": "edge-label-pill-anchor",
|
|
1968
|
+
children: /* @__PURE__ */ jsxs12(
|
|
1969
|
+
"button",
|
|
1970
|
+
{
|
|
1971
|
+
type: "button",
|
|
1972
|
+
"aria-label": accessibleName,
|
|
1973
|
+
"data-slot": "edge-label-pill",
|
|
1974
|
+
...props,
|
|
1975
|
+
className: cn11(
|
|
1976
|
+
"pointer-events-auto flex items-center gap-1 rounded-full border bg-flow-node px-2 py-0.5",
|
|
1977
|
+
"text-meta font-medium text-flow-node-foreground shadow-sm",
|
|
1978
|
+
"transition-colors duration-fast ease-standard",
|
|
1979
|
+
"focus-ring",
|
|
1980
|
+
selected ? "border-ring" : "border-flow-group-border",
|
|
1981
|
+
className
|
|
1982
|
+
),
|
|
1983
|
+
children: [
|
|
1984
|
+
label ? /* @__PURE__ */ jsx15("span", { "aria-hidden": "true", children: label }) : null,
|
|
1985
|
+
secondaryLabel ? /* @__PURE__ */ jsx15("span", { "aria-hidden": "true", className: "text-flow-node-foreground/70 tabular-nums", children: secondaryLabel }) : null
|
|
1986
|
+
]
|
|
1987
|
+
}
|
|
1988
|
+
)
|
|
1989
|
+
}
|
|
1990
|
+
) });
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
// src/flow-weighted-edge/back-edge-geometry.ts
|
|
1994
|
+
function backEdgeDetour(rects, axis, span, clearance) {
|
|
1995
|
+
const [lo, hi] = span[0] <= span[1] ? span : [span[1], span[0]];
|
|
1996
|
+
let far = Number.NEGATIVE_INFINITY;
|
|
1997
|
+
for (const rect of rects) {
|
|
1998
|
+
if (!(rect.width > 0) || !(rect.height > 0)) continue;
|
|
1999
|
+
const [near, beyond] = axis === "vertical" ? [rect.y, rect.y + rect.height] : [rect.x, rect.x + rect.width];
|
|
2000
|
+
if (beyond < lo || near > hi) continue;
|
|
2001
|
+
far = Math.max(far, axis === "vertical" ? rect.x + rect.width : rect.y + rect.height);
|
|
2002
|
+
}
|
|
2003
|
+
return Number.isFinite(far) ? far + clearance : null;
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
// src/flow-weighted-edge/flow-weighted-edge.tsx
|
|
2007
|
+
import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2008
|
+
var NO_NODES = [];
|
|
2009
|
+
var FALLBACK_WEAK = "#6085a1";
|
|
2010
|
+
var FALLBACK_STRONG = "#496d89";
|
|
2011
|
+
var BACK_EDGE_DASHARRAY = "6 4";
|
|
2012
|
+
var BACK_EDGE_OPACITY = 0.7;
|
|
2013
|
+
var BACK_EDGE_CLEARANCE = 40;
|
|
2014
|
+
var DEFAULT_BACK_EDGE_LABEL = "Back edge \u2014 runs against the process direction";
|
|
2015
|
+
function clamp01(t) {
|
|
2016
|
+
return Math.min(1, Math.max(0, t));
|
|
2017
|
+
}
|
|
2018
|
+
function hexToRgb(hex) {
|
|
2019
|
+
const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i.exec(hex);
|
|
2020
|
+
if (!m) return [0, 0, 0];
|
|
2021
|
+
return [parseInt(m[1], 16), parseInt(m[2], 16), parseInt(m[3], 16)];
|
|
2022
|
+
}
|
|
2023
|
+
function toHexByte(n) {
|
|
2024
|
+
return Math.round(Math.min(255, Math.max(0, n))).toString(16).padStart(2, "0");
|
|
2025
|
+
}
|
|
2026
|
+
function mixHex(a, b, t) {
|
|
2027
|
+
const [ar, ag, ab] = hexToRgb(a);
|
|
2028
|
+
const [br, bg, bb] = hexToRgb(b);
|
|
2029
|
+
const r = ar + (br - ar) * t;
|
|
2030
|
+
const g = ag + (bg - ag) * t;
|
|
2031
|
+
const bl = ab + (bb - ab) * t;
|
|
2032
|
+
return `#${toHexByte(r)}${toHexByte(g)}${toHexByte(bl)}`;
|
|
2033
|
+
}
|
|
2034
|
+
function resolveValueStrokeColor(value, domain) {
|
|
2035
|
+
if (value === void 0 || !domain) return void 0;
|
|
2036
|
+
const [lo, hi] = domain;
|
|
2037
|
+
const t = hi === lo ? 1 : clamp01((value - lo) / (hi - lo));
|
|
2038
|
+
const weak = resolveTokenColor("--flow-edge-weak", { fallback: FALLBACK_WEAK });
|
|
2039
|
+
const strong = resolveTokenColor("--flow-edge-strong", { fallback: FALLBACK_STRONG });
|
|
2040
|
+
return mixHex(weak, strong, t);
|
|
2041
|
+
}
|
|
2042
|
+
function FlowWeightedEdge({
|
|
2043
|
+
id,
|
|
2044
|
+
sourceX,
|
|
2045
|
+
sourceY,
|
|
2046
|
+
targetX,
|
|
2047
|
+
targetY,
|
|
2048
|
+
sourcePosition,
|
|
2049
|
+
targetPosition,
|
|
2050
|
+
markerEnd,
|
|
2051
|
+
style,
|
|
2052
|
+
selected,
|
|
2053
|
+
data
|
|
2054
|
+
}) {
|
|
2055
|
+
const edges = useEdges();
|
|
2056
|
+
const widthByEdgeId = useMemo3(
|
|
2057
|
+
() => computeEdgeWeightScale(edges),
|
|
2058
|
+
[edges]
|
|
2059
|
+
);
|
|
2060
|
+
const variant = data?.variant ?? "forward";
|
|
2061
|
+
const isBack = variant === "back";
|
|
2062
|
+
const nodes = useStore(
|
|
2063
|
+
useCallback5((s) => isBack ? s.nodes : NO_NODES, [isBack])
|
|
2064
|
+
);
|
|
2065
|
+
const pathType = data?.path ?? "bezier";
|
|
2066
|
+
const axis = targetPosition === Position7.Top || targetPosition === Position7.Bottom ? "vertical" : "horizontal";
|
|
2067
|
+
const rects = useMemo3(() => {
|
|
2068
|
+
if (!isBack) return [];
|
|
2069
|
+
const out = [];
|
|
2070
|
+
for (const node of nodes) {
|
|
2071
|
+
if (node.parentId) continue;
|
|
2072
|
+
const width = node.measured?.width ?? node.width;
|
|
2073
|
+
const height = node.measured?.height ?? node.height;
|
|
2074
|
+
if (!width || !height) continue;
|
|
2075
|
+
out.push({ x: node.position.x, y: node.position.y, width, height });
|
|
2076
|
+
}
|
|
2077
|
+
return out;
|
|
2078
|
+
}, [isBack, nodes]);
|
|
2079
|
+
const detour = isBack ? backEdgeDetour(
|
|
2080
|
+
rects,
|
|
2081
|
+
axis,
|
|
2082
|
+
axis === "vertical" ? [sourceY, targetY] : [sourceX, targetX],
|
|
2083
|
+
BACK_EDGE_CLEARANCE
|
|
2084
|
+
) : null;
|
|
2085
|
+
const [edgePath, labelX, labelY] = isBack ? getSmoothStepPath({
|
|
2086
|
+
sourceX,
|
|
2087
|
+
sourceY,
|
|
2088
|
+
sourcePosition,
|
|
2089
|
+
targetX,
|
|
2090
|
+
targetY,
|
|
2091
|
+
targetPosition,
|
|
2092
|
+
offset: BACK_EDGE_CLEARANCE,
|
|
2093
|
+
...detour === null ? {} : axis === "vertical" ? { centerX: detour } : { centerY: detour }
|
|
2094
|
+
}) : pathType === "smoothstep" ? getSmoothStepPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition }) : getBezierPath5({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition });
|
|
2095
|
+
const scaledWidth = widthByEdgeId.get(id) ?? DEFAULT_EDGE_WIDTH_RANGE[0];
|
|
2096
|
+
const valueColor = useMemo3(
|
|
2097
|
+
() => resolveValueStrokeColor(data?.value, data?.valueDomain),
|
|
2098
|
+
[data?.value, data?.valueDomain]
|
|
2099
|
+
);
|
|
2100
|
+
const stroke = selected ? "var(--ring)" : valueColor ?? "var(--flow-edge)";
|
|
2101
|
+
const strokeWidth = selected ? scaledWidth + 1.5 : scaledWidth;
|
|
2102
|
+
const edge = /* @__PURE__ */ jsx16(
|
|
2103
|
+
FlowEdgePath,
|
|
2104
|
+
{
|
|
2105
|
+
id,
|
|
2106
|
+
path: edgePath,
|
|
2107
|
+
markerEnd,
|
|
2108
|
+
"data-slot": "flow-weighted-edge",
|
|
2109
|
+
"data-variant": variant,
|
|
2110
|
+
"data-weight": data?.weight,
|
|
2111
|
+
"data-value": data?.value,
|
|
2112
|
+
stroke,
|
|
2113
|
+
strokeWidth,
|
|
2114
|
+
strokeDasharray: isBack ? BACK_EDGE_DASHARRAY : void 0,
|
|
2115
|
+
strokeOpacity: isBack ? BACK_EDGE_OPACITY : void 0,
|
|
2116
|
+
style
|
|
2117
|
+
}
|
|
2118
|
+
);
|
|
2119
|
+
return /* @__PURE__ */ jsxs13(Fragment5, { children: [
|
|
2120
|
+
isBack ? (
|
|
2121
|
+
// A `data-variant` is invisible to assistive technology, so the back
|
|
2122
|
+
// edge's meaning is also published as a named graphic. Only the back
|
|
2123
|
+
// variant is wrapped — a forward edge's DOM is unchanged from before
|
|
2124
|
+
// this prop existed.
|
|
2125
|
+
/* @__PURE__ */ jsx16("g", { role: "img", "aria-label": data?.variantLabel ?? DEFAULT_BACK_EDGE_LABEL, children: edge })
|
|
2126
|
+
) : edge,
|
|
2127
|
+
/* @__PURE__ */ jsx16(
|
|
2128
|
+
EdgeLabelPill,
|
|
2129
|
+
{
|
|
2130
|
+
label: data?.label,
|
|
2131
|
+
secondaryLabel: data?.secondaryLabel,
|
|
2132
|
+
x: labelX,
|
|
2133
|
+
y: labelY,
|
|
2134
|
+
selected,
|
|
2135
|
+
...data?.labelProps
|
|
2136
|
+
}
|
|
2137
|
+
)
|
|
2138
|
+
] });
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
// src/flow-weighted-edge/edge-aria.ts
|
|
2142
|
+
var DEFAULT_WEIGHT_LABEL = "weight";
|
|
2143
|
+
var DEFAULT_VALUE_LABEL = "value";
|
|
2144
|
+
function defaultNameOf(nodeId) {
|
|
2145
|
+
return nodeId;
|
|
2146
|
+
}
|
|
2147
|
+
function defaultFormatNumber(n) {
|
|
2148
|
+
return String(n);
|
|
2149
|
+
}
|
|
2150
|
+
function buildWeightedEdgeAriaLabel(edge, opts = {}) {
|
|
2151
|
+
if (edge.ariaLabel) return edge.ariaLabel;
|
|
2152
|
+
const { weight, value, label, secondaryLabel } = edge.data ?? {};
|
|
2153
|
+
if (weight === void 0 && value === void 0 && !label && !secondaryLabel) {
|
|
2154
|
+
return void 0;
|
|
2155
|
+
}
|
|
2156
|
+
const {
|
|
2157
|
+
weightLabel = DEFAULT_WEIGHT_LABEL,
|
|
2158
|
+
valueLabel = DEFAULT_VALUE_LABEL,
|
|
2159
|
+
nameOf = defaultNameOf,
|
|
2160
|
+
formatNumber = defaultFormatNumber
|
|
2161
|
+
} = opts;
|
|
2162
|
+
const parts = [`Edge from ${nameOf(edge.source)} to ${nameOf(edge.target)}`];
|
|
2163
|
+
if (weight !== void 0) parts.push(`${weightLabel} ${formatNumber(weight)}`);
|
|
2164
|
+
if (value !== void 0) parts.push(`${valueLabel} ${formatNumber(value)}`);
|
|
2165
|
+
const pillText = [label, secondaryLabel].filter(Boolean).join(" ");
|
|
2166
|
+
if (pillText) parts.push(pillText);
|
|
2167
|
+
return parts.join(", ");
|
|
2168
|
+
}
|
|
2169
|
+
function withWeightedEdgeAria(edges, opts) {
|
|
2170
|
+
return edges.map((edge) => {
|
|
2171
|
+
if (edge.ariaLabel) return edge;
|
|
2172
|
+
const ariaLabel = buildWeightedEdgeAriaLabel(edge, opts);
|
|
2173
|
+
return ariaLabel === void 0 ? edge : { ...edge, ariaLabel };
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
// src/flow-self-loop-edge/flow-self-loop-edge.tsx
|
|
2178
|
+
import { useMemo as useMemo4 } from "react";
|
|
2179
|
+
import { useEdges as useEdges2, useInternalNode as useInternalNode3 } from "@xyflow/react";
|
|
2180
|
+
|
|
2181
|
+
// src/flow-self-loop-edge/self-loop-geometry.ts
|
|
2182
|
+
var DEFAULT_LOOP_RADIUS = 28;
|
|
2183
|
+
var CONTROL_REACH_X = 1.2;
|
|
2184
|
+
var CONTROL_REACH_Y = 2.4;
|
|
2185
|
+
var APEX_FACTOR = 3 / 4 * CONTROL_REACH_Y;
|
|
2186
|
+
function selfLoopPath(anchor, loopRadius) {
|
|
2187
|
+
const cx = Number.isFinite(anchor.centerX) ? anchor.centerX : 0;
|
|
2188
|
+
const ay = Number.isFinite(anchor.topY) ? anchor.topY : 0;
|
|
2189
|
+
const r = Number.isFinite(loopRadius) && loopRadius > 0 ? loopRadius : DEFAULT_LOOP_RADIUS;
|
|
2190
|
+
const startX = cx + r;
|
|
2191
|
+
const endX = cx - r;
|
|
2192
|
+
const controlY = ay - r * CONTROL_REACH_Y;
|
|
2193
|
+
return {
|
|
2194
|
+
path: `M ${startX},${ay} C ${startX + r * CONTROL_REACH_X},${controlY} ${endX - r * CONTROL_REACH_X},${controlY} ${endX},${ay}`,
|
|
2195
|
+
labelX: cx,
|
|
2196
|
+
labelY: ay - r * APEX_FACTOR
|
|
2197
|
+
};
|
|
2198
|
+
}
|
|
2199
|
+
function outward(x, y, centerX, centerY, fallback) {
|
|
2200
|
+
const dx = x - centerX;
|
|
2201
|
+
const dy = y - centerY;
|
|
2202
|
+
const length = Math.hypot(dx, dy);
|
|
2203
|
+
if (!Number.isFinite(length) || length < 1e-6) return fallback;
|
|
2204
|
+
return [dx / length, dy / length];
|
|
2205
|
+
}
|
|
2206
|
+
function selfLoopHandleArc(anchor, loopRadius) {
|
|
2207
|
+
const num = (value) => Number.isFinite(value) ? value : 0;
|
|
2208
|
+
const sx = num(anchor.sourceX);
|
|
2209
|
+
const sy = num(anchor.sourceY);
|
|
2210
|
+
const tx = num(anchor.targetX);
|
|
2211
|
+
const ty = num(anchor.targetY);
|
|
2212
|
+
const cx = num(anchor.centerX);
|
|
2213
|
+
const cy = num(anchor.centerY);
|
|
2214
|
+
const width = Math.max(0, num(anchor.width));
|
|
2215
|
+
const height = Math.max(0, num(anchor.height));
|
|
2216
|
+
const r = Number.isFinite(loopRadius) && loopRadius > 0 ? loopRadius : DEFAULT_LOOP_RADIUS;
|
|
2217
|
+
const [osx, osy] = outward(sx, sy, cx, cy, [0, 1]);
|
|
2218
|
+
const [otx, oty] = outward(tx, ty, cx, cy, [-osx, -osy]);
|
|
2219
|
+
const lx = osy;
|
|
2220
|
+
const ly = -osx;
|
|
2221
|
+
const clearance = Math.abs(lx) * (width / 2) + Math.abs(ly) * (height / 2) + r;
|
|
2222
|
+
const reach = 4 / 3 * clearance;
|
|
2223
|
+
const c1x = sx + osx * r + lx * reach;
|
|
2224
|
+
const c1y = sy + osy * r + ly * reach;
|
|
2225
|
+
const c2x = tx + otx * r + lx * reach;
|
|
2226
|
+
const c2y = ty + oty * r + ly * reach;
|
|
2227
|
+
return {
|
|
2228
|
+
path: `M ${sx},${sy} C ${c1x},${c1y} ${c2x},${c2y} ${tx},${ty}`,
|
|
2229
|
+
// A cubic's midpoint is (P0 + 3·C1 + 3·C2 + P3) / 8 — the arc's apex, which `reach`
|
|
2230
|
+
// above places `loopRadius` clear of the card, so the label sits off the node.
|
|
2231
|
+
labelX: (sx + 3 * c1x + 3 * c2x + tx) / 8,
|
|
2232
|
+
labelY: (sy + 3 * c1y + 3 * c2y + ty) / 8
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
// src/flow-self-loop-edge/flow-self-loop-edge.tsx
|
|
2237
|
+
import { Fragment as Fragment6, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2238
|
+
function nodeName(data, fallback) {
|
|
2239
|
+
if (data && typeof data === "object" && "title" in data) {
|
|
2240
|
+
const title = data.title;
|
|
2241
|
+
if (typeof title === "string" && title.length > 0) return title;
|
|
2242
|
+
}
|
|
2243
|
+
return fallback;
|
|
2244
|
+
}
|
|
2245
|
+
function FlowSelfLoopEdge({
|
|
2246
|
+
id,
|
|
2247
|
+
source,
|
|
2248
|
+
sourceX,
|
|
2249
|
+
sourceY,
|
|
2250
|
+
targetX,
|
|
2251
|
+
targetY,
|
|
2252
|
+
markerEnd,
|
|
2253
|
+
style,
|
|
2254
|
+
selected,
|
|
2255
|
+
data
|
|
2256
|
+
}) {
|
|
2257
|
+
const edges = useEdges2();
|
|
2258
|
+
const widthByEdgeId = useMemo4(
|
|
2259
|
+
() => computeEdgeWeightScale(edges),
|
|
2260
|
+
[edges]
|
|
2261
|
+
);
|
|
2262
|
+
const node = useInternalNode3(source);
|
|
2263
|
+
const measuredWidth = node?.measured?.width;
|
|
2264
|
+
const measuredHeight = node?.measured?.height;
|
|
2265
|
+
const loopRadius = data?.loopRadius ?? DEFAULT_LOOP_RADIUS;
|
|
2266
|
+
const { path, labelX, labelY } = useMemo4(() => {
|
|
2267
|
+
if (node && measuredWidth && measuredHeight) {
|
|
2268
|
+
return selfLoopHandleArc(
|
|
2269
|
+
{
|
|
2270
|
+
sourceX,
|
|
2271
|
+
sourceY,
|
|
2272
|
+
targetX,
|
|
2273
|
+
targetY,
|
|
2274
|
+
centerX: node.internals.positionAbsolute.x + measuredWidth / 2,
|
|
2275
|
+
centerY: node.internals.positionAbsolute.y + measuredHeight / 2,
|
|
2276
|
+
width: measuredWidth,
|
|
2277
|
+
height: measuredHeight
|
|
2278
|
+
},
|
|
2279
|
+
loopRadius
|
|
2280
|
+
);
|
|
2281
|
+
}
|
|
2282
|
+
return selfLoopPath(
|
|
2283
|
+
{ centerX: (sourceX + targetX) / 2, topY: Math.min(sourceY, targetY) },
|
|
2284
|
+
loopRadius
|
|
2285
|
+
);
|
|
2286
|
+
}, [node, measuredWidth, measuredHeight, sourceX, sourceY, targetX, targetY, loopRadius]);
|
|
2287
|
+
const scaledWidth = widthByEdgeId.get(id) ?? DEFAULT_EDGE_WIDTH_RANGE[0];
|
|
2288
|
+
const stroke = selected ? "var(--ring)" : "var(--flow-edge)";
|
|
2289
|
+
const strokeWidth = selected ? scaledWidth + 1.5 : scaledWidth;
|
|
2290
|
+
const accessibleName = data?.loopLabel ?? `Self-loop on ${nodeName(node?.data, source)} \u2014 this step repeats`;
|
|
2291
|
+
return /* @__PURE__ */ jsxs14(Fragment6, { children: [
|
|
2292
|
+
/* @__PURE__ */ jsx17("g", { role: "img", "aria-label": accessibleName, children: /* @__PURE__ */ jsx17(
|
|
2293
|
+
FlowEdgePath,
|
|
2294
|
+
{
|
|
2295
|
+
id,
|
|
2296
|
+
path,
|
|
2297
|
+
markerEnd,
|
|
2298
|
+
"data-slot": "flow-self-loop-edge",
|
|
2299
|
+
stroke,
|
|
2300
|
+
strokeWidth,
|
|
2301
|
+
style: { fill: "none", ...style }
|
|
2302
|
+
}
|
|
2303
|
+
) }),
|
|
2304
|
+
/* @__PURE__ */ jsx17(
|
|
2305
|
+
EdgeLabelPill,
|
|
2306
|
+
{
|
|
2307
|
+
label: data?.label,
|
|
2308
|
+
secondaryLabel: data?.secondaryLabel,
|
|
2309
|
+
x: labelX,
|
|
2310
|
+
y: labelY,
|
|
2311
|
+
selected,
|
|
2312
|
+
...data?.labelProps
|
|
2313
|
+
}
|
|
2314
|
+
)
|
|
2315
|
+
] });
|
|
2316
|
+
}
|
|
1531
2317
|
export {
|
|
1532
2318
|
Background2 as Background,
|
|
1533
2319
|
CanvasShell,
|
|
1534
2320
|
Controls,
|
|
2321
|
+
DEFAULT_EDGE_WIDTH_RANGE,
|
|
2322
|
+
DEFAULT_LOOP_RADIUS,
|
|
2323
|
+
EdgeLabelPill,
|
|
1535
2324
|
FLOW_ALL_SIDE_HANDLES,
|
|
2325
|
+
FLOW_EDGE_FOCUS_CONTOUR_WIDTH,
|
|
2326
|
+
FLOW_EDGE_FOCUS_RING_WIDTH,
|
|
1536
2327
|
FLOW_GROUP_NODE_TYPE,
|
|
2328
|
+
FLOW_HANDLE_ANCHOR_CLASS,
|
|
1537
2329
|
FlowButtonEdge,
|
|
1538
2330
|
FlowEdge,
|
|
2331
|
+
FlowEdgePath,
|
|
1539
2332
|
FlowFloatingEdge,
|
|
1540
2333
|
FlowGroupNode,
|
|
1541
2334
|
FlowMiniMap,
|
|
1542
2335
|
FlowNode,
|
|
1543
2336
|
FlowPlaceholderNode,
|
|
2337
|
+
FlowSelfLoopEdge,
|
|
1544
2338
|
FlowSmartEdge,
|
|
2339
|
+
FlowWeightedEdge,
|
|
1545
2340
|
HANDLE_SIDES,
|
|
1546
2341
|
HelperLines,
|
|
1547
2342
|
InspectorPanel,
|
|
1548
2343
|
Legend,
|
|
1549
2344
|
MiniMap2 as MiniMap,
|
|
1550
2345
|
Panel2 as Panel,
|
|
1551
|
-
|
|
2346
|
+
Position8 as Position,
|
|
1552
2347
|
ReactFlow2 as ReactFlow,
|
|
1553
2348
|
ReactFlowProvider2 as ReactFlowProvider,
|
|
1554
2349
|
ZoomControls,
|
|
1555
2350
|
addEdge,
|
|
2351
|
+
backEdgeDetour,
|
|
2352
|
+
buildWeightedEdgeAriaLabel,
|
|
1556
2353
|
collapseGroup,
|
|
2354
|
+
computeEdgeWeightScale,
|
|
1557
2355
|
expandGroup,
|
|
1558
2356
|
getEdgeParams,
|
|
1559
2357
|
getHelperLines,
|
|
@@ -1562,7 +2360,11 @@ export {
|
|
|
1562
2360
|
isFlowGroupProxyEdge,
|
|
1563
2361
|
layoutFlow,
|
|
1564
2362
|
layoutGraph,
|
|
2363
|
+
pickClosestAnchors,
|
|
1565
2364
|
pickClosestHandles,
|
|
2365
|
+
positionToSide,
|
|
2366
|
+
selfLoopHandleArc,
|
|
2367
|
+
selfLoopPath,
|
|
1566
2368
|
sideToPosition,
|
|
1567
2369
|
toggleGroupCollapsed,
|
|
1568
2370
|
ungroup,
|
|
@@ -1572,6 +2374,7 @@ export {
|
|
|
1572
2374
|
useFlowLayout,
|
|
1573
2375
|
useHelperLines,
|
|
1574
2376
|
useNodesState,
|
|
1575
|
-
|
|
2377
|
+
useReactFlow6 as useReactFlow,
|
|
2378
|
+
withWeightedEdgeAria
|
|
1576
2379
|
};
|
|
1577
2380
|
//# sourceMappingURL=index.js.map
|