Skip to content

Commit e2aa6db

Browse files
authored
Merge pull request #274 from eccenca/feature/bump-react-flow-v12-CMEM-6506
Add support for some React Flow v12 components
2 parents 577b795 + a79157b commit e2aa6db

File tree

17 files changed

+877
-132
lines changed

17 files changed

+877
-132
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- added support for `react-flow` v12 for `NodeContent` component, added v12-related components: `EdgeDefaultV12`, `NodeDefaultV12`, `EdgeDefs`
12+
913
## [24.2.0] - 2025-06-04
1014

1115
### Added

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
"@codemirror/lang-yaml": "^6.1.2",
8282
"@codemirror/legacy-modes": "^6.5.0",
8383
"@mavrin/remark-typograf": "^2.2.0",
84+
"@xyflow/react": "^12.6.0",
8485
"classnames": "^2.5.1",
8586
"codemirror": "^6.0.1",
8687
"color": "^4.2.3",
@@ -179,6 +180,8 @@
179180
"resolutions": {
180181
"**/@types/react": "^17.0.85",
181182
"node-sass-package-importer/**/postcss": "^8.4.49",
183+
"string-width": "^4.2.3",
184+
"wrap-ansi": "^7.0.0",
182185
"hast-util-from-parse5": "8.0.0"
183186
},
184187
"husky": {

src/components/AutoSuggestion/AutoSuggestion.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ const AutoSuggestion = ({
208208
const suggestionRequestData = React.useRef<RequestMetaData>({ requestId: undefined });
209209
const [pathValidationPending, setPathValidationPending] = React.useState(false);
210210
const validationRequestData = React.useRef<RequestMetaData>({ requestId: undefined });
211-
const errorMarkers = React.useRef<any[]>([])
211+
const errorMarkers = React.useRef<any[]>([]);
212212
const [validationResponse, setValidationResponse] = useState<CodeAutocompleteFieldValidationResult | undefined>(
213213
undefined
214214
);
@@ -300,26 +300,26 @@ const AutoSuggestion = ({
300300
const parseError = validationResponse?.parseError;
301301
if (cm) {
302302
const clearCurrentErrorMarker = () => {
303-
if(errorMarkers.current.length) {
303+
if (errorMarkers.current.length) {
304304
const [from, to] = errorMarkers.current;
305-
removeMarkFromText({ view: cm, from, to })
306-
errorMarkers.current = []
305+
removeMarkFromText({ view: cm, from, to });
306+
errorMarkers.current = [];
307307
}
308-
}
308+
};
309309
if (parseError) {
310310
const { message, start, end } = parseError;
311311
const { toOffset, fromOffset } = getOffsetRange(cm, start, end);
312-
clearCurrentErrorMarker()
313-
const {from, to} = markText({
312+
clearCurrentErrorMarker();
313+
const { from, to } = markText({
314314
view: cm,
315315
from: fromOffset,
316316
to: toOffset,
317317
className: `${eccgui}-autosuggestion__text--highlighted-error`,
318318
title: message,
319319
});
320-
errorMarkers.current = [from, to]
320+
errorMarkers.current = [from, to];
321321
} else {
322-
clearCurrentErrorMarker()
322+
clearCurrentErrorMarker();
323323
}
324324
}
325325

@@ -448,13 +448,13 @@ const AutoSuggestion = ({
448448
[asyncHandleEditorInputChange, autoCompletionRequestDelay]
449449
);
450450

451-
const handleChange = React.useMemo( () => {
451+
const handleChange = React.useMemo(() => {
452452
return (val: string) => {
453453
value.current = val;
454454
checkValuePathValidity.cancel();
455455
checkValuePathValidity(value.current);
456456
onChange(val);
457-
}
457+
};
458458
}, [onChange, checkValuePathValidity]);
459459

460460
const handleCursorChange = (cursor: number, coords: Rect, scrollinfo: HTMLElement, view: EditorView) => {

src/components/Tooltip/Tooltip.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,15 @@ export const Tooltip = ({
8686
}, swapDelayTime);
8787
if (placeholderRef.current !== null) {
8888
const eventType = ev.type === "focusin" ? "focusout" : "mouseleave";
89-
(placeholderRef.current as HTMLElement).addEventListener(
90-
eventType,
91-
() => {
92-
if (eventType === "focusout" && eventMemory.current === "afterfocus" ||
93-
eventType === "mouseleave" && eventMemory.current === "afterhover") {
94-
eventMemory.current = null
95-
}
96-
clearTimeout(swapDelay)
89+
(placeholderRef.current as HTMLElement).addEventListener(eventType, () => {
90+
if (
91+
(eventType === "focusout" && eventMemory.current === "afterfocus") ||
92+
(eventType === "mouseleave" && eventMemory.current === "afterhover")
93+
) {
94+
eventMemory.current = null;
9795
}
98-
);
96+
clearTimeout(swapDelay);
97+
});
9998
}
10099
};
101100
(placeholderRef.current as HTMLElement).addEventListener("mouseenter", swap);

src/extensions/react-flow/_react-flow.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
@import "edges/edges";
44
@import "handles/handles";
55
@import "minimap/minimap";
6+
@import "react-flow_v12";
67

78
.react-flow__background {
89
border: solid 1px $eccgui-color-separation-divider;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.react-flow .react-flow__edges svg {
2+
overflow: visible;
3+
position: absolute;
4+
pointer-events: none;
5+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { memo } from "react";
2+
import React from "react";
3+
import { BaseEdge, Edge, EdgeProps, EdgeText, getBezierPath, getEdgeCenter } from "@xyflow/react";
4+
5+
import { IntentTypes } from "../../../common/Intent";
6+
import { nodeContentUtils } from "../nodes/NodeContent";
7+
import { NodeHighlightColor } from "../nodes/sharedTypes";
8+
9+
import { edgeDefaultUtils } from "./EdgeDefault";
10+
11+
export type EdgeDefaultV12DataProps = Record<string, unknown> & {
12+
/**
13+
* Overwrites the default style how the edge stroke is displayed.
14+
*/
15+
strokeType?: "solid" | "dashed" | "dotted" | "double" | "doubledashed";
16+
/**
17+
* Feedback state of the node.
18+
*/
19+
intent?: IntentTypes;
20+
/**
21+
* Set the color of used highlights to mark the edge.
22+
*/
23+
highlightColor?: NodeHighlightColor | [NodeHighlightColor, NodeHighlightColor];
24+
/**
25+
* Size of the "glow" effect when the edge is hovered.
26+
*/
27+
pathGlowWidth?: number;
28+
/*
29+
* Direction of the SVG path is inversed.
30+
* This is important for the placement of the markers and the animation movement.
31+
*/
32+
inversePath?: boolean;
33+
/**
34+
* Callback handler that returns a React element used as edge title.
35+
*/
36+
renderLabel?: (edgeCenter: [number, number, number, number]) => React.ReactNode;
37+
/**
38+
* Properties are forwarded to the internally used SVG `g` element.
39+
* Data attributes for test ids coud be included here.
40+
*/
41+
edgeSvgProps?: React.SVGProps<SVGGElement>;
42+
};
43+
44+
export const EdgeDefaultV12 = memo(
45+
({
46+
id,
47+
sourceX,
48+
sourceY,
49+
targetX,
50+
targetY,
51+
sourcePosition,
52+
targetPosition,
53+
label,
54+
labelStyle,
55+
labelShowBg,
56+
labelBgStyle,
57+
labelBgPadding = [5, 5],
58+
labelBgBorderRadius = 3,
59+
data = {},
60+
...edgeOriginalProperties
61+
}: EdgeProps<Edge<EdgeDefaultV12DataProps>>) => {
62+
const { pathGlowWidth = 10, highlightColor, renderLabel, edgeSvgProps, intent, inversePath, strokeType } = data;
63+
64+
const [edgePath, labelX, labelY] = getBezierPath({
65+
sourceX,
66+
sourceY,
67+
sourcePosition,
68+
targetX,
69+
targetY,
70+
targetPosition,
71+
});
72+
73+
const edgeStyle = edgeOriginalProperties.style ?? {};
74+
const { highlightCustomPropertySettings } = nodeContentUtils.evaluateHighlightColors(
75+
"--edge-highlight",
76+
highlightColor
77+
);
78+
79+
const edgeCenter = getEdgeCenter({
80+
sourceX,
81+
sourceY,
82+
targetX,
83+
targetY,
84+
});
85+
86+
const renderedLabel =
87+
renderLabel?.([labelX, labelY, sourceX, targetX]) ??
88+
(label ? (
89+
<EdgeText
90+
x={edgeCenter[0]}
91+
y={edgeCenter[1]}
92+
label={label}
93+
labelStyle={labelStyle}
94+
labelShowBg={labelShowBg}
95+
labelBgStyle={labelBgStyle}
96+
labelBgPadding={labelBgPadding || [5, 5]}
97+
labelBgBorderRadius={labelBgBorderRadius || 3}
98+
/>
99+
) : null);
100+
101+
return (
102+
<g
103+
className={
104+
"react-flow__edge " +
105+
edgeDefaultUtils.createEdgeDefaultClassName(
106+
{ intent },
107+
`${edgeOriginalProperties.selected ? "selected" : ""}`
108+
)
109+
}
110+
tabIndex={0}
111+
role="button"
112+
data-id={id}
113+
aria-label={`Edge from ${edgeOriginalProperties.source} to ${edgeOriginalProperties.target}`}
114+
aria-describedby={`react-flow__edge-desc-${id}`}
115+
>
116+
<g className={edgeSvgProps?.className ?? ""}>
117+
{highlightColor && (
118+
<path
119+
d={edgePath}
120+
className={edgeDefaultUtils.createEdgeDefaultClassName(
121+
{ highlightColor },
122+
"react-flow__edge-path-highlight"
123+
)}
124+
strokeWidth={10}
125+
style={{
126+
...highlightCustomPropertySettings,
127+
}}
128+
/>
129+
)}
130+
131+
<BaseEdge
132+
id={id}
133+
path={edgePath}
134+
markerStart={inversePath ? "url(#arrow-closed-reverse)" : undefined}
135+
markerEnd={!inversePath ? "url(#arrow-closed)" : undefined}
136+
className={edgeDefaultUtils.createEdgeDefaultClassName({ strokeType })}
137+
interactionWidth={pathGlowWidth}
138+
style={{
139+
...edgeSvgProps?.style,
140+
...edgeStyle,
141+
color: edgeStyle.color || edgeStyle.stroke,
142+
}}
143+
/>
144+
</g>
145+
{renderedLabel}
146+
</g>
147+
);
148+
}
149+
);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React from "react";
2+
3+
export const EdgeDefs = React.memo(() => (
4+
<svg style={{ position: "absolute", top: 0, left: 0 }}>
5+
<defs>
6+
<marker
7+
id="arrow-closed"
8+
viewBox="-10 -10 20 20"
9+
markerWidth="10"
10+
markerHeight="10"
11+
refX="0"
12+
refY="0"
13+
orient="auto"
14+
>
15+
<path d="M-4,-4 L4,0 L-4,4 Z" fill="currentColor" />
16+
</marker>
17+
<marker
18+
id="arrow-closed-reverse"
19+
viewBox="-10 -10 20 20"
20+
markerWidth="10"
21+
markerHeight="10"
22+
refX="0"
23+
refY="0"
24+
orient="auto-start-reverse"
25+
>
26+
<path d="M-4,-4 L4,0 L-4,4 Z" fill="currentColor" />
27+
</marker>
28+
</defs>
29+
</svg>
30+
));

src/extensions/react-flow/edges/_edges.scss

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,24 @@ path.react-flow__edge-path {
6262
}
6363
}
6464

65+
path.react-flow__edge-interaction {
66+
fill: none;
67+
stroke: currentcolor;
68+
stroke-linecap: round;
69+
70+
.react-flow__edge & {
71+
stroke-opacity: 0;
72+
}
73+
74+
.react-flow__edge:hover & {
75+
stroke-opacity: $reactflow-edge-stroke-opacity-hover * 0.2;
76+
}
77+
78+
.react-flow__edge.selected & {
79+
stroke-opacity: $reactflow-edge-stroke-opacity-selected * 0.2;
80+
}
81+
}
82+
6583
path.react-flow__edge-path-glow {
6684
fill: none;
6785
stroke: currentcolor;

0 commit comments

Comments
 (0)