@umodeapporg/ui 0.22.3 → 0.23.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.
Files changed (63) hide show
  1. package/dist/assets/umode-ui.css +105 -0
  2. package/dist/components/UI/Flow/FlowBlockNode.d.ts +35 -0
  3. package/dist/components/UI/Flow/FlowBlockNode.js +84 -0
  4. package/dist/components/UI/Flow/FlowBlockNode.js.map +1 -0
  5. package/dist/components/UI/Flow/FlowCanvas.d.ts +22 -0
  6. package/dist/components/UI/Flow/FlowCanvas.js +263 -0
  7. package/dist/components/UI/Flow/FlowCanvas.js.map +1 -0
  8. package/dist/components/UI/Flow/FlowConnectionEdge.d.ts +10 -0
  9. package/dist/components/UI/Flow/FlowConnectionEdge.js +54 -0
  10. package/dist/components/UI/Flow/FlowConnectionEdge.js.map +1 -0
  11. package/dist/components/UI/Flow/FlowEditorProvider.d.ts +17 -0
  12. package/dist/components/UI/Flow/FlowEditorProvider.js +27 -0
  13. package/dist/components/UI/Flow/FlowEditorProvider.js.map +1 -0
  14. package/dist/components/UI/Flow/FlowInspector.d.ts +11 -0
  15. package/dist/components/UI/Flow/FlowInspector.js +124 -0
  16. package/dist/components/UI/Flow/FlowInspector.js.map +1 -0
  17. package/dist/components/UI/Flow/FlowIssueList.d.ts +8 -0
  18. package/dist/components/UI/Flow/FlowIssueList.js +55 -0
  19. package/dist/components/UI/Flow/FlowIssueList.js.map +1 -0
  20. package/dist/components/UI/Flow/FlowLaneMap.d.ts +30 -0
  21. package/dist/components/UI/Flow/FlowLaneMap.js +206 -0
  22. package/dist/components/UI/Flow/FlowLaneMap.js.map +1 -0
  23. package/dist/components/UI/Flow/FlowNodeShell.d.ts +20 -0
  24. package/dist/components/UI/Flow/FlowNodeShell.js +81 -0
  25. package/dist/components/UI/Flow/FlowNodeShell.js.map +1 -0
  26. package/dist/components/UI/Flow/FlowPalette.d.ts +7 -0
  27. package/dist/components/UI/Flow/FlowPalette.js +90 -0
  28. package/dist/components/UI/Flow/FlowPalette.js.map +1 -0
  29. package/dist/components/UI/Flow/FlowResizablePanel.d.ts +20 -0
  30. package/dist/components/UI/Flow/FlowResizablePanel.js +117 -0
  31. package/dist/components/UI/Flow/FlowResizablePanel.js.map +1 -0
  32. package/dist/components/UI/Flow/FlowZoomControls.d.ts +12 -0
  33. package/dist/components/UI/Flow/FlowZoomControls.js +53 -0
  34. package/dist/components/UI/Flow/FlowZoomControls.js.map +1 -0
  35. package/dist/components/UI/Flow/flowBlockRegistry.d.ts +12 -0
  36. package/dist/components/UI/Flow/flowBlockRegistry.js +15 -0
  37. package/dist/components/UI/Flow/flowBlockRegistry.js.map +1 -0
  38. package/dist/components/UI/Flow/flowConstants.d.ts +2 -0
  39. package/dist/components/UI/Flow/flowConstants.js +5 -0
  40. package/dist/components/UI/Flow/flowConstants.js.map +1 -0
  41. package/dist/components/UI/Flow/flowEditorStore.d.ts +44 -0
  42. package/dist/components/UI/Flow/flowEditorStore.js +258 -0
  43. package/dist/components/UI/Flow/flowEditorStore.js.map +1 -0
  44. package/dist/components/UI/Flow/flowGraph.d.ts +32 -0
  45. package/dist/components/UI/Flow/flowGraph.js +156 -0
  46. package/dist/components/UI/Flow/flowGraph.js.map +1 -0
  47. package/dist/components/UI/Flow/flowTypes.d.ts +74 -0
  48. package/dist/components/UI/Flow/flowTypes.js +2 -0
  49. package/dist/components/UI/Flow/flowTypes.js.map +1 -0
  50. package/dist/components/UI/Flow/index.d.ts +14 -0
  51. package/dist/components/UI/Flow/index.js +43 -0
  52. package/dist/components/UI/Flow/index.js.map +1 -0
  53. package/dist/components/UI/Flow/useFlowLabels.d.ts +10 -0
  54. package/dist/components/UI/Flow/useFlowLabels.js +51 -0
  55. package/dist/components/UI/Flow/useFlowLabels.js.map +1 -0
  56. package/dist/locales/en/ui.json.d.ts +67 -0
  57. package/dist/locales/en/ui.json.js +3 -0
  58. package/dist/locales/en/ui.json.js.map +1 -1
  59. package/dist/locales/index.d.ts +134 -0
  60. package/dist/locales/pt/ui.json.d.ts +67 -0
  61. package/dist/locales/pt/ui.json.js +3 -0
  62. package/dist/locales/pt/ui.json.js.map +1 -1
  63. package/package.json +10 -1
@@ -338,3 +338,108 @@
338
338
  height: 1.25rem;
339
339
  font-size: 1.25rem;
340
340
  }
341
+ /* Theme bridge for @xyflow/react internals the components cannot reach with classes. */
342
+ .umode-flow .react-flow__pane {
343
+ cursor: default;
344
+ }
345
+
346
+ .umode-flow .react-flow__background {
347
+ --xy-background-pattern-color: rgb(var(--color-border-subtle));
348
+ }
349
+
350
+ .umode-flow .umode-flow-handle {
351
+ width: 0.75rem;
352
+ height: 0.75rem;
353
+ border: 2px solid rgb(var(--color-surface));
354
+ background: rgb(var(--color-border-strong));
355
+ }
356
+
357
+ .umode-flow .umode-flow-handle.connectionindicator:hover,
358
+ .umode-flow .umode-flow-handle.connectingto {
359
+ background: rgb(var(--color-primary));
360
+ }
361
+
362
+ .umode-flow .umode-flow-edge-path {
363
+ stroke: rgb(var(--color-border-strong));
364
+ stroke-width: 1.5;
365
+ transition: stroke 140ms ease-out;
366
+ }
367
+
368
+ .umode-flow .umode-flow-edge-path--selected {
369
+ stroke: rgb(var(--color-primary));
370
+ stroke-width: 2;
371
+ }
372
+
373
+ .umode-flow .umode-flow-edge-path--traversed {
374
+ stroke: rgb(var(--color-primary));
375
+ stroke-width: 2.5;
376
+ }
377
+
378
+ .umode-flow .umode-flow-edge-path--warning {
379
+ stroke: rgb(var(--color-warning));
380
+ }
381
+
382
+ .umode-flow .umode-flow-edge-path--error {
383
+ stroke: rgb(var(--color-danger));
384
+ stroke-dasharray: 6 4;
385
+ }
386
+
387
+ .umode-flow .react-flow__minimap {
388
+ background: rgb(var(--color-surface));
389
+ border: 1px solid rgb(var(--color-border));
390
+ border-radius: 0.75rem;
391
+ overflow: hidden;
392
+ }
393
+
394
+ .umode-flow .react-flow__minimap-mask {
395
+ fill: rgb(var(--color-muted) / 0.7);
396
+ }
397
+
398
+ .umode-flow .react-flow__minimap-node {
399
+ fill: rgb(var(--color-border-emphasis));
400
+ stroke: none;
401
+ }
402
+
403
+ .umode-flow .react-flow__attribution {
404
+ background: transparent;
405
+ color: rgb(var(--color-foreground-faint));
406
+ }
407
+
408
+ .umode-flow .react-flow__connectionline path {
409
+ stroke: rgb(var(--color-primary));
410
+ stroke-width: 2;
411
+ }
412
+
413
+ .umode-flow .umode-flow-arc {
414
+ stroke: rgb(var(--color-border-strong));
415
+ stroke-width: 1.5;
416
+ fill: none;
417
+ transition: stroke 140ms ease-out;
418
+ }
419
+
420
+ .umode-flow .umode-flow-arc--warning {
421
+ stroke: rgb(var(--color-warning));
422
+ }
423
+
424
+ .umode-flow .umode-flow-arc--danger {
425
+ stroke: rgb(var(--color-danger));
426
+ }
427
+
428
+ .umode-flow .umode-flow-arc--dashed {
429
+ stroke-dasharray: 6 4;
430
+ }
431
+
432
+ .umode-flow .umode-flow-arc--selected,
433
+ .umode-flow .react-flow__edge:hover .umode-flow-arc {
434
+ stroke: rgb(var(--color-primary));
435
+ stroke-width: 2.5;
436
+ }
437
+
438
+ .umode-flow .react-flow__edge {
439
+ cursor: pointer;
440
+ }
441
+
442
+ .umode-flow .react-flow__arrowhead polyline {
443
+ stroke: rgb(var(--color-border-strong));
444
+ fill: rgb(var(--color-border-strong));
445
+ }
@@ -0,0 +1,35 @@
1
+ import { ReactNode } from 'react';
2
+ import { SymbolCodepoints } from '../MaterialSymbol';
3
+ import { Node, NodeProps } from '@xyflow/react';
4
+ import { TFlowIssueSeverity, TFlowPortId, TFlowRunStepStatus } from './flowTypes';
5
+ export declare const FLOW_INPUT_HANDLE_ID = "in";
6
+ export interface IFlowAddableBlock {
7
+ type: string;
8
+ label: string;
9
+ icon: SymbolCodepoints;
10
+ }
11
+ export interface IFlowBlockNodeData extends Record<string, unknown> {
12
+ icon: SymbolCodepoints;
13
+ title: string;
14
+ categoryLabel: string;
15
+ summary?: ReactNode;
16
+ hasInput: boolean;
17
+ outputs: Array<TFlowPortId>;
18
+ /** Ports already wired: their edge carries the label, so the port does not repeat it. */
19
+ connectedPorts: Array<TFlowPortId>;
20
+ portLabels: Record<TFlowPortId, string>;
21
+ /** What a free port can grow into; empty in a read-only canvas. */
22
+ addableBlocks: Array<IFlowAddableBlock>;
23
+ onAddFromPort?: (port: TFlowPortId, type: string) => void;
24
+ disabled?: boolean;
25
+ issueSeverity?: TFlowIssueSeverity;
26
+ issueCount: number;
27
+ runStatus?: TFlowRunStepStatus;
28
+ }
29
+ export type TFlowBlockNode = Node<IFlowBlockNodeData, 'flowBlock'>;
30
+ /**
31
+ * Input on the left edge, output ports spread along the right edge; a lone `next` port needs no
32
+ * label. A free port shows a "+" that adds the next block already wired to it.
33
+ */
34
+ declare const FlowBlockNode: React.FC<NodeProps<TFlowBlockNode>>;
35
+ export default FlowBlockNode;
@@ -0,0 +1,84 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useTranslation } from "react-i18next";
3
+ import ControlledButton from "../../Form/ControlledButton.js";
4
+ import ControlledMenu from "../ControlledMenu.js";
5
+ import { MaterialSymbol } from "../MaterialSymbol/index.js";
6
+ import { Handle, Position } from "@xyflow/react";
7
+ import FlowNodeShell from "./FlowNodeShell.js";
8
+ const FLOW_INPUT_HANDLE_ID = "in";
9
+ const portTop = (index, count) => `${(index + 1) / (count + 1) * 100}%`;
10
+ const FlowBlockNode = ({ data, selected }) => {
11
+ const { t } = useTranslation("ui");
12
+ const outputs = Array.isArray(data.outputs) ? data.outputs : [];
13
+ const connectedPorts = Array.isArray(data.connectedPorts) ? data.connectedPorts : [];
14
+ const addableBlocks = Array.isArray(data.addableBlocks) ? data.addableBlocks : [];
15
+ const showPortLabels = !(outputs.length === 1 && outputs[0] === "next");
16
+ const canAdd = Boolean(data.onAddFromPort) && addableBlocks.length > 0;
17
+ return /* @__PURE__ */ jsxs(
18
+ FlowNodeShell,
19
+ {
20
+ icon: data.icon,
21
+ title: data.title,
22
+ categoryLabel: data.categoryLabel,
23
+ summary: data.summary,
24
+ selected,
25
+ disabled: data.disabled,
26
+ issueSeverity: data.issueSeverity,
27
+ issueCount: data.issueCount,
28
+ runStatus: data.runStatus,
29
+ className: showPortLabels ? "pr-12" : void 0,
30
+ children: [
31
+ data.hasInput && /* @__PURE__ */ jsx(Handle, { id: FLOW_INPUT_HANDLE_ID, type: "target", position: Position.Left, className: "umode-flow-handle" }),
32
+ outputs.map((port, index) => /* @__PURE__ */ jsx(
33
+ Handle,
34
+ {
35
+ id: port,
36
+ type: "source",
37
+ position: Position.Right,
38
+ className: "umode-flow-handle",
39
+ "data-port": port,
40
+ style: { top: portTop(index, outputs.length) },
41
+ children: showPortLabels && !connectedPorts.includes(port) && /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 whitespace-nowrap text-xxs font-semibold text-foreground-subtle", children: data.portLabels[port] })
42
+ },
43
+ port
44
+ )),
45
+ canAdd && outputs.map(
46
+ (port, index) => connectedPorts.includes(port) ? null : /* @__PURE__ */ jsx(
47
+ "div",
48
+ {
49
+ className: "nodrag nopan absolute -right-10 -translate-y-1/2",
50
+ style: { top: portTop(index, outputs.length) },
51
+ children: /* @__PURE__ */ jsx(
52
+ ControlledMenu,
53
+ {
54
+ placement: "right-start",
55
+ triggerTooltip: t("flow.node.addBlock"),
56
+ items: addableBlocks.map((block) => ({
57
+ key: block.type,
58
+ label: block.label,
59
+ icon: block.icon,
60
+ onClick: () => data.onAddFromPort?.(port, block.type)
61
+ })),
62
+ trigger: /* @__PURE__ */ jsx(
63
+ ControlledButton,
64
+ {
65
+ defaultHtmlButton: true,
66
+ className: "interactive flex size-6 items-center justify-center rounded-full border border-border-emphasis bg-surface text-foreground-muted shadow-sm hover:border-brand-border hover:bg-brand-soft hover:text-brand-emphasis focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
67
+ children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "add", size: 16, "aria-hidden": true })
68
+ }
69
+ )
70
+ }
71
+ )
72
+ },
73
+ `add|${port}`
74
+ )
75
+ )
76
+ ]
77
+ }
78
+ );
79
+ };
80
+ export {
81
+ FLOW_INPUT_HANDLE_ID,
82
+ FlowBlockNode as default
83
+ };
84
+ //# sourceMappingURL=FlowBlockNode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FlowBlockNode.js","sources":["../../../../src/components/UI/Flow/FlowBlockNode.tsx"],"sourcesContent":["import { ReactNode } from 'react';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledMenu from '@/components/UI/ControlledMenu';\nimport { MaterialSymbol, type SymbolCodepoints } from '@/components/UI/MaterialSymbol';\nimport { Handle, type Node, type NodeProps, Position } from '@xyflow/react';\n\nimport FlowNodeShell from './FlowNodeShell';\nimport type { TFlowIssueSeverity, TFlowPortId, TFlowRunStepStatus } from './flowTypes';\n\nexport const FLOW_INPUT_HANDLE_ID = 'in';\n\nexport interface IFlowAddableBlock {\n type: string;\n label: string;\n icon: SymbolCodepoints;\n}\n\nexport interface IFlowBlockNodeData extends Record<string, unknown> {\n icon: SymbolCodepoints;\n title: string;\n categoryLabel: string;\n summary?: ReactNode;\n hasInput: boolean;\n outputs: Array<TFlowPortId>;\n /** Ports already wired: their edge carries the label, so the port does not repeat it. */\n connectedPorts: Array<TFlowPortId>;\n portLabels: Record<TFlowPortId, string>;\n /** What a free port can grow into; empty in a read-only canvas. */\n addableBlocks: Array<IFlowAddableBlock>;\n onAddFromPort?: (port: TFlowPortId, type: string) => void;\n disabled?: boolean;\n issueSeverity?: TFlowIssueSeverity;\n issueCount: number;\n runStatus?: TFlowRunStepStatus;\n}\n\nexport type TFlowBlockNode = Node<IFlowBlockNodeData, 'flowBlock'>;\n\nconst portTop = (index: number, count: number): string => `${((index + 1) / (count + 1)) * 100}%`;\n\n/**\n * Input on the left edge, output ports spread along the right edge; a lone `next` port needs no\n * label. A free port shows a \"+\" that adds the next block already wired to it.\n */\nconst FlowBlockNode: React.FC<NodeProps<TFlowBlockNode>> = ({ data, selected }) => {\n const { t } = useTranslation('ui');\n const outputs = Array.isArray(data.outputs) ? data.outputs : [];\n const connectedPorts = Array.isArray(data.connectedPorts) ? data.connectedPorts : [];\n const addableBlocks = Array.isArray(data.addableBlocks) ? data.addableBlocks : [];\n const showPortLabels = !(outputs.length === 1 && outputs[0] === 'next');\n const canAdd = Boolean(data.onAddFromPort) && addableBlocks.length > 0;\n\n return (\n <FlowNodeShell\n icon={data.icon}\n title={data.title}\n categoryLabel={data.categoryLabel}\n summary={data.summary}\n selected={selected}\n disabled={data.disabled}\n issueSeverity={data.issueSeverity}\n issueCount={data.issueCount}\n runStatus={data.runStatus}\n className={showPortLabels ? 'pr-12' : undefined}\n >\n {data.hasInput && (\n <Handle id={FLOW_INPUT_HANDLE_ID} type=\"target\" position={Position.Left} className=\"umode-flow-handle\" />\n )}\n {outputs.map((port, index) => (\n <Handle\n key={port}\n id={port}\n type=\"source\"\n position={Position.Right}\n className=\"umode-flow-handle\"\n data-port={port}\n style={{ top: portTop(index, outputs.length) }}\n >\n {showPortLabels && !connectedPorts.includes(port) && (\n <span className=\"pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 whitespace-nowrap text-xxs font-semibold text-foreground-subtle\">\n {data.portLabels[port]}\n </span>\n )}\n </Handle>\n ))}\n {canAdd &&\n outputs.map((port, index) =>\n connectedPorts.includes(port) ? null : (\n <div\n key={`add|${port}`}\n className=\"nodrag nopan absolute -right-10 -translate-y-1/2\"\n style={{ top: portTop(index, outputs.length) }}\n >\n <ControlledMenu\n placement=\"right-start\"\n triggerTooltip={t('flow.node.addBlock')}\n items={addableBlocks.map((block) => ({\n key: block.type,\n label: block.label,\n icon: block.icon,\n onClick: () => data.onAddFromPort?.(port, block.type),\n }))}\n trigger={\n <ControlledButton\n defaultHtmlButton\n className=\"interactive flex size-6 items-center justify-center rounded-full border border-border-emphasis bg-surface text-foreground-muted shadow-sm hover:border-brand-border hover:bg-brand-soft hover:text-brand-emphasis focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <MaterialSymbol icon=\"add\" size={16} aria-hidden />\n </ControlledButton>\n }\n />\n </div>\n ),\n )}\n </FlowNodeShell>\n );\n};\n\nexport default FlowBlockNode;\n"],"names":[],"mappings":";;;;;;;AAWO,MAAM,uBAAuB;AA6BpC,MAAM,UAAU,CAAC,OAAe,UAA0B,IAAK,QAAQ,MAAM,QAAQ,KAAM,GAAG;AAM9F,MAAM,gBAAqD,CAAC,EAAE,MAAM,eAAe;AACjF,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,UAAU,MAAM,QAAQ,KAAK,OAAO,IAAI,KAAK,UAAU,CAAA;AAC7D,QAAM,iBAAiB,MAAM,QAAQ,KAAK,cAAc,IAAI,KAAK,iBAAiB,CAAA;AAClF,QAAM,gBAAgB,MAAM,QAAQ,KAAK,aAAa,IAAI,KAAK,gBAAgB,CAAA;AAC/E,QAAM,iBAAiB,EAAE,QAAQ,WAAW,KAAK,QAAQ,CAAC,MAAM;AAChE,QAAM,SAAS,QAAQ,KAAK,aAAa,KAAK,cAAc,SAAS;AAErE,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAM,KAAK;AAAA,MACX,OAAO,KAAK;AAAA,MACZ,eAAe,KAAK;AAAA,MACpB,SAAS,KAAK;AAAA,MACd;AAAA,MACA,UAAU,KAAK;AAAA,MACf,eAAe,KAAK;AAAA,MACpB,YAAY,KAAK;AAAA,MACjB,WAAW,KAAK;AAAA,MAChB,WAAW,iBAAiB,UAAU;AAAA,MAErC,UAAA;AAAA,QAAA,KAAK,YACJ,oBAAC,QAAA,EAAO,IAAI,sBAAsB,MAAK,UAAS,UAAU,SAAS,MAAM,WAAU,oBAAA,CAAoB;AAAA,QAExG,QAAQ,IAAI,CAAC,MAAM,UAClB;AAAA,UAAC;AAAA,UAAA;AAAA,YAEC,IAAI;AAAA,YACJ,MAAK;AAAA,YACL,UAAU,SAAS;AAAA,YACnB,WAAU;AAAA,YACV,aAAW;AAAA,YACX,OAAO,EAAE,KAAK,QAAQ,OAAO,QAAQ,MAAM,EAAA;AAAA,YAE1C,UAAA,kBAAkB,CAAC,eAAe,SAAS,IAAI,KAC9C,oBAAC,QAAA,EAAK,WAAU,iIACb,UAAA,KAAK,WAAW,IAAI,EAAA,CACvB;AAAA,UAAA;AAAA,UAXG;AAAA,QAAA,CAcR;AAAA,QACA,UACC,QAAQ;AAAA,UAAI,CAAC,MAAM,UACjB,eAAe,SAAS,IAAI,IAAI,OAC9B;AAAA,YAAC;AAAA,YAAA;AAAA,cAEC,WAAU;AAAA,cACV,OAAO,EAAE,KAAK,QAAQ,OAAO,QAAQ,MAAM,EAAA;AAAA,cAE3C,UAAA;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,WAAU;AAAA,kBACV,gBAAgB,EAAE,oBAAoB;AAAA,kBACtC,OAAO,cAAc,IAAI,CAAC,WAAW;AAAA,oBACnC,KAAK,MAAM;AAAA,oBACX,OAAO,MAAM;AAAA,oBACb,MAAM,MAAM;AAAA,oBACZ,SAAS,MAAM,KAAK,gBAAgB,MAAM,MAAM,IAAI;AAAA,kBAAA,EACpD;AAAA,kBACF,SACE;AAAA,oBAAC;AAAA,oBAAA;AAAA,sBACC,mBAAiB;AAAA,sBACjB,WAAU;AAAA,sBAEV,8BAAC,gBAAA,EAAe,MAAK,OAAM,MAAM,IAAI,eAAW,KAAA,CAAC;AAAA,oBAAA;AAAA,kBAAA;AAAA,gBACnD;AAAA,cAAA;AAAA,YAEJ;AAAA,YArBK,OAAO,IAAI;AAAA,UAAA;AAAA,QAsBlB;AAAA,MAEJ;AAAA,IAAA;AAAA,EAAA;AAGR;"}
@@ -0,0 +1,22 @@
1
+ import { ReactNode } from 'react';
2
+ import { PanelPosition } from '@xyflow/react';
3
+ import { IFlowIssue, IFlowRunOverlay } from './flowTypes';
4
+ export interface IFlowCanvasProps {
5
+ issues?: Array<IFlowIssue>;
6
+ runOverlay?: IFlowRunOverlay | null;
7
+ readOnly?: boolean;
8
+ /** Accessible name of the canvas region, from `t()`. Defaults to the library label. */
9
+ ariaLabel?: string;
10
+ className?: string;
11
+ /** Where the zoom and history controls sit; move them when floating chrome takes that corner. */
12
+ controlsPosition?: PanelPosition;
13
+ /** Floating chrome drawn over the canvas (toolbars, panels); position it with absolute classes. */
14
+ children?: ReactNode;
15
+ }
16
+ /**
17
+ * The editing surface. Renders the graph held by `FlowEditorProvider` and turns every
18
+ * gesture (drag, connect, select, delete, drop, shortcuts) into a store action, so undo
19
+ * and the dirty flag see all of them.
20
+ */
21
+ declare const FlowCanvas: React.FC<IFlowCanvasProps>;
22
+ export default FlowCanvas;
@@ -0,0 +1,263 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useMemo, useState, useCallback } from "react";
3
+ import { useTranslation } from "react-i18next";
4
+ import { twMerge } from "tailwind-merge";
5
+ import { useReactFlow, MarkerType, ReactFlow, Background, BackgroundVariant, MiniMap } from "@xyflow/react";
6
+ import FlowBlockNode, { FLOW_INPUT_HANDLE_ID } from "./FlowBlockNode.js";
7
+ import FlowConnectionEdge from "./FlowConnectionEdge.js";
8
+ import { FLOW_BLOCK_DRAG_TYPE } from "./flowConstants.js";
9
+ import { useFlowBlockRegistry, useFlowEditorStore, useFlowEditor } from "./FlowEditorProvider.js";
10
+ import { canConnect, layoutFlowByLevels } from "./flowGraph.js";
11
+ import FlowZoomControls from "./FlowZoomControls.js";
12
+ import { useFlowLabels } from "./useFlowLabels.js";
13
+ import "@xyflow/react/dist/base.css";
14
+ /* empty css */
15
+ const ATTRIBUTION_POSITION = "bottom-left";
16
+ const NODE_TYPES = { flowBlock: FlowBlockNode };
17
+ const EDGE_TYPES = { flowConnection: FlowConnectionEdge };
18
+ const MINIMAP_SIZE = { width: 160, height: 104 };
19
+ const MINIMAP_MIN_NODES = 5;
20
+ const SNAP_GRID = [16, 16];
21
+ const PORTS = ["next", "true", "false", "error"];
22
+ const isPortId = (value) => PORTS.includes(value);
23
+ const worstSeverity = (issues) => {
24
+ if (issues.some((issue) => issue.severity === "error")) return "error";
25
+ if (issues.some((issue) => issue.severity === "warning")) return "warning";
26
+ return void 0;
27
+ };
28
+ const isTypingTarget = (target) => target instanceof HTMLElement && (target.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName));
29
+ const FlowCanvas = ({
30
+ issues = [],
31
+ runOverlay,
32
+ readOnly = false,
33
+ ariaLabel,
34
+ className,
35
+ controlsPosition,
36
+ children
37
+ }) => {
38
+ const { t } = useTranslation("ui");
39
+ const labels = useFlowLabels();
40
+ const registry = useFlowBlockRegistry();
41
+ const store = useFlowEditorStore();
42
+ const { screenToFlowPosition } = useReactFlow();
43
+ const graph = useFlowEditor((state) => state.graph);
44
+ const selectedNodeIds = useFlowEditor((state) => state.selectedNodeIds);
45
+ const selectedEdgeIds = useFlowEditor((state) => state.selectedEdgeIds);
46
+ const canUndo = useFlowEditor((state) => state.past.length > 0);
47
+ const canRedo = useFlowEditor((state) => state.future.length > 0);
48
+ const safeIssues = useMemo(() => Array.isArray(issues) ? issues : [], [issues]);
49
+ const [measuredSizes, setMeasuredSizes] = useState({});
50
+ const addableBlocks = useMemo(() => {
51
+ if (readOnly) return [];
52
+ return registry.list.filter((definition) => definition.category !== "trigger").filter(
53
+ (definition) => definition.maxInstances === void 0 || graph.nodes.filter((node) => node.type === definition.type).length < definition.maxInstances
54
+ ).map((definition) => ({ type: definition.type, label: definition.label, icon: definition.icon }));
55
+ }, [registry, graph.nodes, readOnly]);
56
+ const nodes = useMemo(
57
+ () => graph.nodes.map((node) => {
58
+ const definition = registry.get(node.type);
59
+ const nodeIssues = safeIssues.filter((issue) => issue.nodeId === node.nodeId);
60
+ return {
61
+ id: node.nodeId,
62
+ type: "flowBlock",
63
+ position: node.position,
64
+ measured: measuredSizes[node.nodeId],
65
+ selected: selectedNodeIds.includes(node.nodeId),
66
+ draggable: !readOnly,
67
+ connectable: !readOnly,
68
+ data: {
69
+ icon: definition?.resolveIcon?.(node.config) ?? definition?.icon ?? "help",
70
+ title: node.label ?? definition?.resolveLabel?.(node.config) ?? definition?.label ?? node.type,
71
+ categoryLabel: definition ? labels.categories[definition.category] : "",
72
+ summary: definition ? definition.renderSummary(node.config) : labels.issueMessage({ code: "unknownBlock", severity: "error" }),
73
+ hasInput: definition ? definition.category !== "trigger" : true,
74
+ outputs: definition?.outputs ?? [],
75
+ connectedPorts: graph.edges.filter((edge) => edge.source === node.nodeId).map((edge) => edge.sourcePort),
76
+ portLabels: labels.ports,
77
+ addableBlocks,
78
+ onAddFromPort: readOnly ? void 0 : (port, type) => store.getState().addNodeFromPort(type, node.nodeId, port),
79
+ disabled: node.disabled,
80
+ issueSeverity: worstSeverity(nodeIssues),
81
+ issueCount: nodeIssues.length,
82
+ runStatus: runOverlay?.steps?.[node.nodeId]
83
+ }
84
+ };
85
+ }),
86
+ [
87
+ graph.nodes,
88
+ graph.edges,
89
+ registry,
90
+ safeIssues,
91
+ selectedNodeIds,
92
+ readOnly,
93
+ labels,
94
+ runOverlay,
95
+ measuredSizes,
96
+ addableBlocks,
97
+ store
98
+ ]
99
+ );
100
+ const edges = useMemo(() => {
101
+ const traversed = new Set(runOverlay?.traversedEdgeIds ?? []);
102
+ return graph.edges.map((edge) => ({
103
+ id: edge.edgeId,
104
+ type: "flowConnection",
105
+ source: edge.source,
106
+ sourceHandle: edge.sourcePort,
107
+ target: edge.target,
108
+ targetHandle: FLOW_INPUT_HANDLE_ID,
109
+ selected: selectedEdgeIds.includes(edge.edgeId),
110
+ markerEnd: { type: MarkerType.ArrowClosed, width: 16, height: 16 },
111
+ data: {
112
+ label: edge.sourcePort === "next" ? void 0 : labels.ports[edge.sourcePort],
113
+ traversed: traversed.has(edge.edgeId),
114
+ issueSeverity: worstSeverity(safeIssues.filter((issue) => issue.edgeId === edge.edgeId))
115
+ }
116
+ }));
117
+ }, [graph.edges, selectedEdgeIds, runOverlay, labels, safeIssues]);
118
+ const handleNodesChange = useCallback(
119
+ (changes) => {
120
+ const positions = {};
121
+ const sizes = {};
122
+ let selection = null;
123
+ for (const change of changes) {
124
+ if (change.type === "dimensions" && change.dimensions) sizes[change.id] = change.dimensions;
125
+ if (change.type === "position" && change.position && !readOnly) positions[change.id] = change.position;
126
+ if (change.type === "select") {
127
+ const current = selection ?? store.getState().selectedNodeIds;
128
+ selection = change.selected ? [.../* @__PURE__ */ new Set([...current, change.id])] : current.filter((id) => id !== change.id);
129
+ }
130
+ }
131
+ if (Object.keys(sizes).length > 0) setMeasuredSizes((current) => ({ ...current, ...sizes }));
132
+ if (Object.keys(positions).length > 0) store.getState().moveNodes(positions);
133
+ if (selection) store.getState().setSelection({ nodeIds: selection });
134
+ },
135
+ [store, readOnly]
136
+ );
137
+ const handleEdgesChange = useCallback(
138
+ (changes) => {
139
+ let selection = null;
140
+ for (const change of changes) {
141
+ if (change.type !== "select") continue;
142
+ const current = selection ?? store.getState().selectedEdgeIds;
143
+ selection = change.selected ? [.../* @__PURE__ */ new Set([...current, change.id])] : current.filter((id) => id !== change.id);
144
+ }
145
+ if (selection) store.getState().setSelection({ edgeIds: selection });
146
+ },
147
+ [store]
148
+ );
149
+ const handleConnect = useCallback(
150
+ (connection) => {
151
+ if (readOnly || !isPortId(connection.sourceHandle)) return;
152
+ store.getState().connect(connection.source, connection.sourceHandle, connection.target);
153
+ },
154
+ [store, readOnly]
155
+ );
156
+ const isValidConnection = useCallback(
157
+ (connection) => isPortId(connection.sourceHandle) && canConnect(store.getState().graph, registry, {
158
+ source: connection.source,
159
+ sourcePort: connection.sourceHandle,
160
+ target: connection.target
161
+ }),
162
+ [store, registry]
163
+ );
164
+ const handleDragOver = useCallback((event) => {
165
+ if (!event.dataTransfer.types.includes(FLOW_BLOCK_DRAG_TYPE)) return;
166
+ event.preventDefault();
167
+ event.dataTransfer.dropEffect = "copy";
168
+ }, []);
169
+ const handleDrop = useCallback(
170
+ (event) => {
171
+ const blockType = event.dataTransfer.getData(FLOW_BLOCK_DRAG_TYPE);
172
+ if (!blockType || readOnly) return;
173
+ event.preventDefault();
174
+ store.getState().addNode(blockType, screenToFlowPosition({ x: event.clientX, y: event.clientY }));
175
+ },
176
+ [store, readOnly, screenToFlowPosition]
177
+ );
178
+ const handleKeyDown = useCallback(
179
+ (event) => {
180
+ if (readOnly || isTypingTarget(event.target)) return;
181
+ const state = store.getState();
182
+ const withModifier = event.metaKey || event.ctrlKey;
183
+ const key = event.key.toLowerCase();
184
+ if (event.key === "Delete" || event.key === "Backspace") state.removeSelected();
185
+ else if (withModifier && key === "z" && event.shiftKey) state.redo();
186
+ else if (withModifier && key === "z") state.undo();
187
+ else if (withModifier && key === "y") state.redo();
188
+ else if (withModifier && key === "c") state.copySelected();
189
+ else if (withModifier && key === "v") state.paste();
190
+ else if (withModifier && key === "d") state.duplicateSelected();
191
+ else return;
192
+ event.preventDefault();
193
+ },
194
+ [store, readOnly]
195
+ );
196
+ const handleLayout = useCallback(() => {
197
+ const state = store.getState();
198
+ state.recordHistory();
199
+ state.moveNodes(layoutFlowByLevels(state.graph, registry));
200
+ }, [store, registry]);
201
+ return /* @__PURE__ */ jsxs(
202
+ "div",
203
+ {
204
+ role: "region",
205
+ "aria-label": ariaLabel ?? t("flow.canvas"),
206
+ tabIndex: 0,
207
+ onKeyDown: handleKeyDown,
208
+ className: twMerge(
209
+ "umode-flow relative h-full min-h-[24rem] w-full rounded-xl bg-canvas focus:outline-none",
210
+ className
211
+ ),
212
+ children: [
213
+ /* @__PURE__ */ jsxs(
214
+ ReactFlow,
215
+ {
216
+ nodes,
217
+ edges,
218
+ nodeTypes: NODE_TYPES,
219
+ edgeTypes: EDGE_TYPES,
220
+ onNodesChange: handleNodesChange,
221
+ onEdgesChange: handleEdgesChange,
222
+ onConnect: handleConnect,
223
+ isValidConnection,
224
+ onNodeDragStart: () => store.getState().recordHistory(),
225
+ onPaneClick: () => store.getState().setSelection({ nodeIds: [], edgeIds: [] }),
226
+ onDragOver: handleDragOver,
227
+ onDrop: handleDrop,
228
+ deleteKeyCode: null,
229
+ nodesDraggable: !readOnly,
230
+ nodesConnectable: !readOnly,
231
+ snapToGrid: true,
232
+ snapGrid: SNAP_GRID,
233
+ minZoom: 0.25,
234
+ maxZoom: 2,
235
+ fitView: true,
236
+ fitViewOptions: { padding: 0.2, maxZoom: 1 },
237
+ attributionPosition: ATTRIBUTION_POSITION,
238
+ children: [
239
+ /* @__PURE__ */ jsx(Background, { variant: BackgroundVariant.Dots, gap: 16, size: 1 }),
240
+ nodes.length >= MINIMAP_MIN_NODES && /* @__PURE__ */ jsx(MiniMap, { pannable: true, zoomable: true, position: "bottom-right", style: MINIMAP_SIZE }),
241
+ /* @__PURE__ */ jsx(
242
+ FlowZoomControls,
243
+ {
244
+ onUndo: readOnly ? void 0 : () => store.getState().undo(),
245
+ onRedo: readOnly ? void 0 : () => store.getState().redo(),
246
+ canUndo,
247
+ canRedo,
248
+ onLayout: readOnly ? void 0 : handleLayout,
249
+ position: controlsPosition
250
+ }
251
+ )
252
+ ]
253
+ }
254
+ ),
255
+ children
256
+ ]
257
+ }
258
+ );
259
+ };
260
+ export {
261
+ FlowCanvas as default
262
+ };
263
+ //# sourceMappingURL=FlowCanvas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FlowCanvas.js","sources":["../../../../src/components/UI/Flow/FlowCanvas.tsx"],"sourcesContent":["import { DragEvent, KeyboardEvent, ReactNode, useCallback, useMemo, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport {\n Background,\n BackgroundVariant,\n type Connection,\n type EdgeChange,\n MarkerType,\n MiniMap,\n type NodeChange,\n type PanelPosition,\n ReactFlow,\n useReactFlow,\n} from '@xyflow/react';\n\nimport FlowBlockNode, { FLOW_INPUT_HANDLE_ID, type IFlowAddableBlock, type TFlowBlockNode } from './FlowBlockNode';\nimport FlowConnectionEdge, { type TFlowConnectionEdge } from './FlowConnectionEdge';\nimport { FLOW_BLOCK_DRAG_TYPE } from './flowConstants';\nimport { useFlowBlockRegistry, useFlowEditor, useFlowEditorStore } from './FlowEditorProvider';\nimport { canConnect, layoutFlowByLevels } from './flowGraph';\nimport type { IFlowIssue, IFlowPosition, IFlowRunOverlay, TFlowIssueSeverity, TFlowPortId } from './flowTypes';\nimport FlowZoomControls from './FlowZoomControls';\nimport { useFlowLabels } from './useFlowLabels';\n\nimport '@xyflow/react/dist/base.css';\nimport '../../../styles/flow.css';\n\nconst ATTRIBUTION_POSITION: PanelPosition = 'bottom-left';\nconst NODE_TYPES = { flowBlock: FlowBlockNode };\nconst EDGE_TYPES = { flowConnection: FlowConnectionEdge };\nconst MINIMAP_SIZE = { width: 160, height: 104 };\n/** A small flow fits on screen whole, where a minimap only covers blocks. */\nconst MINIMAP_MIN_NODES = 5;\nconst SNAP_GRID: [number, number] = [16, 16];\nconst PORTS: Array<TFlowPortId> = ['next', 'true', 'false', 'error'];\n\nexport interface IFlowCanvasProps {\n issues?: Array<IFlowIssue>;\n runOverlay?: IFlowRunOverlay | null;\n readOnly?: boolean;\n /** Accessible name of the canvas region, from `t()`. Defaults to the library label. */\n ariaLabel?: string;\n className?: string;\n /** Where the zoom and history controls sit; move them when floating chrome takes that corner. */\n controlsPosition?: PanelPosition;\n /** Floating chrome drawn over the canvas (toolbars, panels); position it with absolute classes. */\n children?: ReactNode;\n}\n\nconst isPortId = (value: string | null | undefined): value is TFlowPortId => PORTS.includes(value as TFlowPortId);\n\nconst worstSeverity = (issues: Array<IFlowIssue>): TFlowIssueSeverity | undefined => {\n if (issues.some((issue) => issue.severity === 'error')) return 'error';\n if (issues.some((issue) => issue.severity === 'warning')) return 'warning';\n return undefined;\n};\n\nconst isTypingTarget = (target: EventTarget | null): boolean =>\n target instanceof HTMLElement &&\n (target.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName));\n\n/**\n * The editing surface. Renders the graph held by `FlowEditorProvider` and turns every\n * gesture (drag, connect, select, delete, drop, shortcuts) into a store action, so undo\n * and the dirty flag see all of them.\n */\nconst FlowCanvas: React.FC<IFlowCanvasProps> = ({\n issues = [],\n runOverlay,\n readOnly = false,\n ariaLabel,\n className,\n controlsPosition,\n children,\n}) => {\n const { t } = useTranslation('ui');\n const labels = useFlowLabels();\n const registry = useFlowBlockRegistry();\n const store = useFlowEditorStore();\n const { screenToFlowPosition } = useReactFlow();\n\n const graph = useFlowEditor((state) => state.graph);\n const selectedNodeIds = useFlowEditor((state) => state.selectedNodeIds);\n const selectedEdgeIds = useFlowEditor((state) => state.selectedEdgeIds);\n const canUndo = useFlowEditor((state) => state.past.length > 0);\n const canRedo = useFlowEditor((state) => state.future.length > 0);\n\n const safeIssues = useMemo(() => (Array.isArray(issues) ? issues : []), [issues]);\n // Measured sizes are view state, not part of the flow: kept here so the minimap can draw the blocks.\n const [measuredSizes, setMeasuredSizes] = useState<Record<string, { width: number; height: number }>>({});\n\n const addableBlocks = useMemo<Array<IFlowAddableBlock>>(() => {\n if (readOnly) return [];\n return registry.list\n .filter((definition) => definition.category !== 'trigger')\n .filter(\n (definition) =>\n definition.maxInstances === undefined ||\n graph.nodes.filter((node) => node.type === definition.type).length < definition.maxInstances,\n )\n .map((definition) => ({ type: definition.type, label: definition.label, icon: definition.icon }));\n }, [registry, graph.nodes, readOnly]);\n\n const nodes = useMemo<Array<TFlowBlockNode>>(\n () =>\n graph.nodes.map((node) => {\n const definition = registry.get(node.type);\n const nodeIssues = safeIssues.filter((issue) => issue.nodeId === node.nodeId);\n return {\n id: node.nodeId,\n type: 'flowBlock',\n position: node.position,\n measured: measuredSizes[node.nodeId],\n selected: selectedNodeIds.includes(node.nodeId),\n draggable: !readOnly,\n connectable: !readOnly,\n data: {\n icon: definition?.resolveIcon?.(node.config) ?? definition?.icon ?? 'help',\n title: node.label ?? definition?.resolveLabel?.(node.config) ?? definition?.label ?? node.type,\n categoryLabel: definition ? labels.categories[definition.category] : '',\n summary: definition\n ? definition.renderSummary(node.config)\n : labels.issueMessage({ code: 'unknownBlock', severity: 'error' }),\n hasInput: definition ? definition.category !== 'trigger' : true,\n outputs: definition?.outputs ?? [],\n connectedPorts: graph.edges.filter((edge) => edge.source === node.nodeId).map((edge) => edge.sourcePort),\n portLabels: labels.ports,\n addableBlocks,\n onAddFromPort: readOnly\n ? undefined\n : (port, type) => store.getState().addNodeFromPort(type, node.nodeId, port),\n disabled: node.disabled,\n issueSeverity: worstSeverity(nodeIssues),\n issueCount: nodeIssues.length,\n runStatus: runOverlay?.steps?.[node.nodeId],\n },\n };\n }),\n [\n graph.nodes,\n graph.edges,\n registry,\n safeIssues,\n selectedNodeIds,\n readOnly,\n labels,\n runOverlay,\n measuredSizes,\n addableBlocks,\n store,\n ],\n );\n\n const edges = useMemo<Array<TFlowConnectionEdge>>(() => {\n const traversed = new Set(runOverlay?.traversedEdgeIds ?? []);\n return graph.edges.map((edge) => ({\n id: edge.edgeId,\n type: 'flowConnection',\n source: edge.source,\n sourceHandle: edge.sourcePort,\n target: edge.target,\n targetHandle: FLOW_INPUT_HANDLE_ID,\n selected: selectedEdgeIds.includes(edge.edgeId),\n markerEnd: { type: MarkerType.ArrowClosed, width: 16, height: 16 },\n data: {\n label: edge.sourcePort === 'next' ? undefined : labels.ports[edge.sourcePort],\n traversed: traversed.has(edge.edgeId),\n issueSeverity: worstSeverity(safeIssues.filter((issue) => issue.edgeId === edge.edgeId)),\n },\n }));\n }, [graph.edges, selectedEdgeIds, runOverlay, labels, safeIssues]);\n\n const handleNodesChange = useCallback(\n (changes: Array<NodeChange<TFlowBlockNode>>) => {\n const positions: Record<string, IFlowPosition> = {};\n const sizes: Record<string, { width: number; height: number }> = {};\n let selection: Array<string> | null = null;\n for (const change of changes) {\n if (change.type === 'dimensions' && change.dimensions) sizes[change.id] = change.dimensions;\n if (change.type === 'position' && change.position && !readOnly) positions[change.id] = change.position;\n if (change.type === 'select') {\n const current: Array<string> = selection ?? store.getState().selectedNodeIds;\n selection = change.selected\n ? [...new Set([...current, change.id])]\n : current.filter((id) => id !== change.id);\n }\n }\n if (Object.keys(sizes).length > 0) setMeasuredSizes((current) => ({ ...current, ...sizes }));\n if (Object.keys(positions).length > 0) store.getState().moveNodes(positions);\n if (selection) store.getState().setSelection({ nodeIds: selection });\n },\n [store, readOnly],\n );\n\n const handleEdgesChange = useCallback(\n (changes: Array<EdgeChange<TFlowConnectionEdge>>) => {\n let selection: Array<string> | null = null;\n for (const change of changes) {\n if (change.type !== 'select') continue;\n const current: Array<string> = selection ?? store.getState().selectedEdgeIds;\n selection = change.selected ? [...new Set([...current, change.id])] : current.filter((id) => id !== change.id);\n }\n if (selection) store.getState().setSelection({ edgeIds: selection });\n },\n [store],\n );\n\n const handleConnect = useCallback(\n (connection: Connection) => {\n if (readOnly || !isPortId(connection.sourceHandle)) return;\n store.getState().connect(connection.source, connection.sourceHandle, connection.target);\n },\n [store, readOnly],\n );\n\n const isValidConnection = useCallback(\n (connection: Connection | TFlowConnectionEdge) =>\n isPortId(connection.sourceHandle) &&\n canConnect(store.getState().graph, registry, {\n source: connection.source,\n sourcePort: connection.sourceHandle,\n target: connection.target,\n }),\n [store, registry],\n );\n\n const handleDragOver = useCallback((event: DragEvent<HTMLDivElement>) => {\n if (!event.dataTransfer.types.includes(FLOW_BLOCK_DRAG_TYPE)) return;\n event.preventDefault();\n event.dataTransfer.dropEffect = 'copy';\n }, []);\n\n const handleDrop = useCallback(\n (event: DragEvent<HTMLDivElement>) => {\n const blockType = event.dataTransfer.getData(FLOW_BLOCK_DRAG_TYPE);\n if (!blockType || readOnly) return;\n event.preventDefault();\n store.getState().addNode(blockType, screenToFlowPosition({ x: event.clientX, y: event.clientY }));\n },\n [store, readOnly, screenToFlowPosition],\n );\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n if (readOnly || isTypingTarget(event.target)) return;\n const state = store.getState();\n const withModifier = event.metaKey || event.ctrlKey;\n const key = event.key.toLowerCase();\n\n if (event.key === 'Delete' || event.key === 'Backspace') state.removeSelected();\n else if (withModifier && key === 'z' && event.shiftKey) state.redo();\n else if (withModifier && key === 'z') state.undo();\n else if (withModifier && key === 'y') state.redo();\n else if (withModifier && key === 'c') state.copySelected();\n else if (withModifier && key === 'v') state.paste();\n else if (withModifier && key === 'd') state.duplicateSelected();\n else return;\n event.preventDefault();\n },\n [store, readOnly],\n );\n\n const handleLayout = useCallback(() => {\n const state = store.getState();\n state.recordHistory();\n state.moveNodes(layoutFlowByLevels(state.graph, registry));\n }, [store, registry]);\n\n return (\n <div\n role=\"region\"\n aria-label={ariaLabel ?? t('flow.canvas')}\n tabIndex={0}\n onKeyDown={handleKeyDown}\n className={twMerge(\n 'umode-flow relative h-full min-h-[24rem] w-full rounded-xl bg-canvas focus:outline-none',\n className,\n )}\n >\n <ReactFlow<TFlowBlockNode, TFlowConnectionEdge>\n nodes={nodes}\n edges={edges}\n nodeTypes={NODE_TYPES}\n edgeTypes={EDGE_TYPES}\n onNodesChange={handleNodesChange}\n onEdgesChange={handleEdgesChange}\n onConnect={handleConnect}\n isValidConnection={isValidConnection}\n onNodeDragStart={() => store.getState().recordHistory()}\n onPaneClick={() => store.getState().setSelection({ nodeIds: [], edgeIds: [] })}\n onDragOver={handleDragOver}\n onDrop={handleDrop}\n deleteKeyCode={null}\n nodesDraggable={!readOnly}\n nodesConnectable={!readOnly}\n snapToGrid\n snapGrid={SNAP_GRID}\n minZoom={0.25}\n maxZoom={2}\n fitView\n fitViewOptions={{ padding: 0.2, maxZoom: 1 }}\n attributionPosition={ATTRIBUTION_POSITION}\n >\n <Background variant={BackgroundVariant.Dots} gap={16} size={1} />\n {nodes.length >= MINIMAP_MIN_NODES && (\n <MiniMap pannable zoomable position=\"bottom-right\" style={MINIMAP_SIZE} />\n )}\n <FlowZoomControls\n onUndo={readOnly ? undefined : () => store.getState().undo()}\n onRedo={readOnly ? undefined : () => store.getState().redo()}\n canUndo={canUndo}\n canRedo={canRedo}\n onLayout={readOnly ? undefined : handleLayout}\n position={controlsPosition}\n />\n </ReactFlow>\n {children}\n </div>\n );\n};\n\nexport default FlowCanvas;\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { Edge, EdgeProps } from '@xyflow/react';
2
+ import { TFlowIssueSeverity } from './flowTypes';
3
+ export interface IFlowConnectionEdgeData extends Record<string, unknown> {
4
+ label?: string;
5
+ traversed?: boolean;
6
+ issueSeverity?: TFlowIssueSeverity;
7
+ }
8
+ export type TFlowConnectionEdge = Edge<IFlowConnectionEdgeData, 'flowConnection'>;
9
+ declare const FlowConnectionEdge: React.FC<EdgeProps<TFlowConnectionEdge>>;
10
+ export default FlowConnectionEdge;
@@ -0,0 +1,54 @@
1
+ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
+ import classNames from "classnames";
3
+ import { getSmoothStepPath, BaseEdge, EdgeLabelRenderer } from "@xyflow/react";
4
+ import OutlinedChip from "../Chips/OutlinedChip.js";
5
+ const FlowConnectionEdge = ({
6
+ id,
7
+ sourceX,
8
+ sourceY,
9
+ targetX,
10
+ targetY,
11
+ sourcePosition,
12
+ targetPosition,
13
+ data,
14
+ markerEnd,
15
+ selected
16
+ }) => {
17
+ const [path, labelX, labelY] = getSmoothStepPath({
18
+ sourceX,
19
+ sourceY,
20
+ targetX,
21
+ targetY,
22
+ sourcePosition,
23
+ targetPosition,
24
+ borderRadius: 12
25
+ });
26
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
27
+ /* @__PURE__ */ jsx(
28
+ BaseEdge,
29
+ {
30
+ id,
31
+ path,
32
+ markerEnd,
33
+ className: classNames("umode-flow-edge-path", {
34
+ "umode-flow-edge-path--selected": selected,
35
+ "umode-flow-edge-path--traversed": data?.traversed,
36
+ "umode-flow-edge-path--error": data?.issueSeverity === "error",
37
+ "umode-flow-edge-path--warning": data?.issueSeverity === "warning"
38
+ })
39
+ }
40
+ ),
41
+ data?.label && /* @__PURE__ */ jsx(EdgeLabelRenderer, { children: /* @__PURE__ */ jsx(
42
+ "div",
43
+ {
44
+ className: "nodrag nopan pointer-events-none absolute",
45
+ style: { transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)` },
46
+ children: /* @__PURE__ */ jsx(OutlinedChip, { label: data.label, tone: "neutral", variant: "outlined", size: "xs", preserveLabelCase: true })
47
+ }
48
+ ) })
49
+ ] });
50
+ };
51
+ export {
52
+ FlowConnectionEdge as default
53
+ };
54
+ //# sourceMappingURL=FlowConnectionEdge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FlowConnectionEdge.js","sources":["../../../../src/components/UI/Flow/FlowConnectionEdge.tsx"],"sourcesContent":["import classNames from 'classnames';\n\nimport { BaseEdge, type Edge, EdgeLabelRenderer, type EdgeProps, getSmoothStepPath } from '@xyflow/react';\n\nimport OutlinedChip from '../Chips/OutlinedChip';\n\nimport type { TFlowIssueSeverity } from './flowTypes';\n\nexport interface IFlowConnectionEdgeData extends Record<string, unknown> {\n label?: string;\n traversed?: boolean;\n issueSeverity?: TFlowIssueSeverity;\n}\n\nexport type TFlowConnectionEdge = Edge<IFlowConnectionEdgeData, 'flowConnection'>;\n\nconst FlowConnectionEdge: React.FC<EdgeProps<TFlowConnectionEdge>> = ({\n id,\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition,\n targetPosition,\n data,\n markerEnd,\n selected,\n}) => {\n const [path, labelX, labelY] = getSmoothStepPath({\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition,\n targetPosition,\n borderRadius: 12,\n });\n\n return (\n <>\n <BaseEdge\n id={id}\n path={path}\n markerEnd={markerEnd}\n className={classNames('umode-flow-edge-path', {\n 'umode-flow-edge-path--selected': selected,\n 'umode-flow-edge-path--traversed': data?.traversed,\n 'umode-flow-edge-path--error': data?.issueSeverity === 'error',\n 'umode-flow-edge-path--warning': data?.issueSeverity === 'warning',\n })}\n />\n {data?.label && (\n <EdgeLabelRenderer>\n <div\n className=\"nodrag nopan pointer-events-none absolute\"\n style={{ transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)` }}\n >\n <OutlinedChip label={data.label} tone=\"neutral\" variant=\"outlined\" size=\"xs\" preserveLabelCase />\n </div>\n </EdgeLabelRenderer>\n )}\n </>\n );\n};\n\nexport default FlowConnectionEdge;\n"],"names":[],"mappings":";;;;AAgBA,MAAM,qBAA+D,CAAC;AAAA,EACpE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,CAAC,MAAM,QAAQ,MAAM,IAAI,kBAAkB;AAAA,IAC/C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,EAAA,CACf;AAED,SACE,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAW,WAAW,wBAAwB;AAAA,UAC5C,kCAAkC;AAAA,UAClC,mCAAmC,MAAM;AAAA,UACzC,+BAA+B,MAAM,kBAAkB;AAAA,UACvD,iCAAiC,MAAM,kBAAkB;AAAA,QAAA,CAC1D;AAAA,MAAA;AAAA,IAAA;AAAA,IAEF,MAAM,SACL,oBAAC,mBAAA,EACC,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAU;AAAA,QACV,OAAO,EAAE,WAAW,mCAAmC,MAAM,OAAO,MAAM,MAAA;AAAA,QAE1E,UAAA,oBAAC,cAAA,EAAa,OAAO,KAAK,OAAO,MAAK,WAAU,SAAQ,YAAW,MAAK,MAAK,mBAAiB,KAAA,CAAC;AAAA,MAAA;AAAA,IAAA,EACjG,CACF;AAAA,EAAA,GAEJ;AAEJ;"}