Skip to content

Commit 83906e1

Browse files
refactor(edges): pass aggregate metadata as DefaultAggregatedEdge props
Let apps supply role, count, and bidirectional leaf ids instead of reading a fixed getData() shape inside the toolkit component. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 7f38f8d commit 83906e1

3 files changed

Lines changed: 63 additions & 13 deletions

File tree

‎packages/demo-app-ts/src/demos/aggregateEdges/AggregateEdge.tsx‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,39 @@
11
import { FunctionComponent } from 'react';
22
import { observer } from 'mobx-react';
3-
import { DefaultAggregatedEdge, GraphElement, WithSelectionProps } from '@patternfly/react-topology';
3+
import { AggregateEdgeRole, DefaultAggregatedEdge, GraphElement, WithSelectionProps } from '@patternfly/react-topology';
44
import { useAggregateEdgesDemo } from './DemoContext';
55

66
type AggregateEdgeProps = {
77
element: GraphElement;
88
} & WithSelectionProps;
99

1010
/**
11-
* Demo wrapper around DefaultAggregatedEdge: supplies snapGeneration after
12-
* collapse and optional label/metric tags from the demo toolbar.
11+
* Demo wrapper around DefaultAggregatedEdge: maps model data + demo toolbar
12+
* options into explicit props (apps can derive these differently).
1313
*/
1414
const AggregateEdge: FunctionComponent<AggregateEdgeProps> = observer(({ element, selected }) => {
1515
const { snapGeneration, showEdgeLabels, showMetricTags } = useAggregateEdgesDemo();
16+
const data = element.getData() || {};
17+
const role = data.role as AggregateEdgeRole | undefined;
18+
const count = data.count as number | undefined;
19+
const bidirectional = data.bidirectional as boolean | undefined;
20+
const forwardEdgeIds = data.forwardEdgeIds as string[] | undefined;
21+
const reverseEdgeIds = data.reverseEdgeIds as string[] | undefined;
22+
1623
const edgeLabel = showEdgeLabels ? element.getLabel() : undefined;
17-
const metricTag = showMetricTags ? (element.getData()?.tag as string | undefined) : undefined;
24+
const metricTag = showMetricTags ? (data.tag as string | undefined) : undefined;
1825
// Only override the default count tag when the demo toolbar opts into labels/metrics.
1926
const tag = edgeLabel || metricTag;
2027

2128
return (
2229
<DefaultAggregatedEdge
2330
element={element}
2431
selected={selected}
32+
role={role}
33+
count={count}
34+
bidirectional={bidirectional}
35+
forwardEdgeIds={forwardEdgeIds}
36+
reverseEdgeIds={reverseEdgeIds}
2537
snapGeneration={snapGeneration}
2638
{...(tag ? { tag } : {})}
2739
/>

‎packages/module/src/components/edges/DefaultAggregatedEdge.tsx‎

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
isEdge,
1111
isNode
1212
} from '../../types';
13+
import { AggregateEdgeRole } from '../../utils/createAggregateEdges';
1314
import { SELECTION_STATE } from '../../behavior';
1415
import { useEventListener } from '../../hooks';
1516
import DefaultEdge from './DefaultEdge';
@@ -32,6 +33,22 @@ export interface DefaultAggregatedEdgeProps {
3233
element: GraphElement;
3334
/** Flag indicating if the element is selected. Part of WithSelectionProps */
3435
selected?: boolean;
36+
/**
37+
* Segment role (`exit` / `bridge` / `entry`). Typically from `createAggregateEdges`
38+
* edge data, but apps may supply an equivalent.
39+
*/
40+
role?: AggregateEdgeRole | string;
41+
/**
42+
* Number of leaf edges folded into this segment. Used for the default bridge tag
43+
* when {@link DefaultAggregatedEdgeProps.tag} is omitted.
44+
*/
45+
count?: number;
46+
/** When true, the bridge shows terminals on both ends. */
47+
bidirectional?: boolean;
48+
/** Leaf edge ids flowing in the bridge's stored orientation. */
49+
forwardEdgeIds?: string[];
50+
/** Leaf edge ids flowing opposite the bridge orientation. */
51+
reverseEdgeIds?: string[];
3552
/**
3653
* Bump after structural changes (e.g. group collapse / model rebuild) that leave
3754
* fixed endpoints stale without enough bound change to re-trigger snap.
@@ -58,11 +75,26 @@ export interface DefaultAggregatedEdgeProps {
5875
* - Multi-segment path selection by shared `aggregatedEdgeIds`
5976
* - Bidirectional bridge terminals, muting the opposite arrow when selection is one-way
6077
*
78+
* Aggregate metadata (`role`, `count`, `bidirectional`, …) is passed as props so apps
79+
* can derive or override them instead of reading a fixed `element.getData()` shape.
80+
*
6181
* Note: path selection updates `SELECTION_STATE` directly. Prefer `withSelection()` for the
6282
* `selected` prop only — do not rely on its `onSelect` (it would collapse the path to one id).
6383
*/
6484
const DefaultAggregatedEdge: FunctionComponent<DefaultAggregatedEdgeProps> = observer(
65-
({ element, selected, snapGeneration = 0, tag: tagProp, className, children }) => {
85+
({
86+
element,
87+
selected,
88+
role,
89+
count,
90+
bidirectional,
91+
forwardEdgeIds,
92+
reverseEdgeIds,
93+
snapGeneration = 0,
94+
tag: tagProp,
95+
className,
96+
children
97+
}) => {
6698
const edge = isEdge(element) ? (element as Edge) : null;
6799
const [layoutSnapGeneration, setLayoutSnapGeneration] = useState(0);
68100

@@ -80,9 +112,7 @@ const DefaultAggregatedEdge: FunctionComponent<DefaultAggregatedEdgeProps> = obs
80112
// Clear this edge's fixed endpoints and force-resnap when Cola (etc.) finishes.
81113
// Mid-layout snaps often leave stubs/bridges pointing at stale hulls.
82114
useEventListener(GRAPH_LAYOUT_END_EVENT, onLayoutEnd);
83-
const data = edge?.getData() || {};
84-
const role = data.role as string | undefined;
85-
const count = data.count as number | undefined;
115+
86116
const sourceNode = edge?.getSource();
87117
const targetNode = edge?.getTarget();
88118

@@ -174,7 +204,11 @@ const DefaultAggregatedEdge: FunctionComponent<DefaultAggregatedEdgeProps> = obs
174204
const selectionState = edge.getController().getState<{ [SELECTION_STATE]?: string[] }>();
175205
const selectedIds = selectionState[SELECTION_STATE] || [];
176206
const focusLeaves = getSelectionFocusLeaves(edge, selectedIds);
177-
const terminals = getBridgeTerminalPresentation(data, selected, focusLeaves);
207+
const terminals = getBridgeTerminalPresentation(
208+
{ bidirectional, forwardEdgeIds, reverseEdgeIds },
209+
selected,
210+
focusLeaves
211+
);
178212
startTerminalType = terminals.start;
179213
endTerminalType = terminals.end;
180214
if (terminals.muteStart) {

‎packages/module/src/components/edges/aggregateEdgeUtils.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,11 @@ export const getSelectionFocusLeaves = (edge: Edge, selectedIds: string[]): Set<
223223
};
224224

225225
export const getBridgeTerminalPresentation = (
226-
data: Record<string, unknown>,
226+
options: {
227+
bidirectional?: boolean;
228+
forwardEdgeIds?: string[];
229+
reverseEdgeIds?: string[];
230+
},
227231
selected: boolean | undefined,
228232
focusLeaves: Set<string>
229233
): {
@@ -232,7 +236,7 @@ export const getBridgeTerminalPresentation = (
232236
muteStart: boolean;
233237
muteEnd: boolean;
234238
} => {
235-
const bidirectional = !!data.bidirectional;
239+
const bidirectional = !!options.bidirectional;
236240
if (!bidirectional) {
237241
return {
238242
start: EdgeTerminalType.none,
@@ -253,8 +257,8 @@ export const getBridgeTerminalPresentation = (
253257
return both;
254258
}
255259

256-
const forwardIds = (data.forwardEdgeIds as string[]) || [];
257-
const reverseIds = (data.reverseEdgeIds as string[]) || [];
260+
const forwardIds = options.forwardEdgeIds || [];
261+
const reverseIds = options.reverseEdgeIds || [];
258262
const hasForward = forwardIds.some((id) => focusLeaves.has(id));
259263
const hasReverse = reverseIds.some((id) => focusLeaves.has(id));
260264

0 commit comments

Comments
 (0)