@principal-ai/principal-view-react 0.15.5 → 0.15.7
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/dist/components/SequenceDiagramRenderer.d.ts +6 -2
- package/dist/components/SequenceDiagramRenderer.d.ts.map +1 -1
- package/dist/components/SequenceDiagramRenderer.js +253 -35
- package/dist/components/SequenceDiagramRenderer.js.map +1 -1
- package/dist/components/WorkflowSequenceDiagram.d.ts +1 -4
- package/dist/components/WorkflowSequenceDiagram.d.ts.map +1 -1
- package/dist/components/WorkflowSequenceDiagram.js +3 -6
- package/dist/components/WorkflowSequenceDiagram.js.map +1 -1
- package/dist/hooks/useSequenceLayout.d.ts +51 -27
- package/dist/hooks/useSequenceLayout.d.ts.map +1 -1
- package/dist/hooks/useSequenceLayout.js +140 -118
- package/dist/hooks/useSequenceLayout.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/SequenceDiagramRenderer.tsx +395 -48
- package/src/components/WorkflowSequenceDiagram.tsx +3 -6
- package/src/hooks/useSequenceLayout.ts +192 -163
- package/src/index.ts +0 -1
- package/src/stories/FileCitySequence.stories.tsx +22 -50
- package/src/stories/SequenceDiagram.stories.tsx +12 -29
- package/src/stories/WorkflowSequenceDiagram.stories.tsx +14 -26
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* React hook for sequence diagram layout
|
|
3
3
|
*
|
|
4
|
-
* Computes swimlane-based positioning for events
|
|
4
|
+
* Computes swimlane-based positioning for events. Lanes default to the first
|
|
5
|
+
* dotted segment of each event name. Callers can drill deeper by passing
|
|
6
|
+
* `openedNamespaces`: any prefix listed there has its events pushed one level
|
|
7
|
+
* deeper, so its children appear as their own lanes instead of being grouped
|
|
8
|
+
* under the parent.
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
11
|
import { useMemo } from 'react';
|
|
@@ -55,40 +59,36 @@ export interface SequenceEdge {
|
|
|
55
59
|
* Swimlane information computed from events
|
|
56
60
|
*/
|
|
57
61
|
export interface Swimlane {
|
|
58
|
-
/** Namespace identifier */
|
|
62
|
+
/** Namespace identifier for this lane */
|
|
59
63
|
namespace: string;
|
|
60
|
-
/** Display label for the lane header */
|
|
64
|
+
/** Display label for the lane header (last segment of the namespace) */
|
|
61
65
|
label: string;
|
|
62
66
|
/** X position of the lane center */
|
|
63
67
|
x: number;
|
|
64
|
-
/** Parent namespace (
|
|
68
|
+
/** Parent namespace (one segment shallower), if any */
|
|
65
69
|
parentNamespace?: string;
|
|
66
|
-
/** Whether this lane is
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
|
|
70
|
-
/**
|
|
70
|
+
/** Whether this lane's namespace is in `openedNamespaces` (its events have been drilled deeper) */
|
|
71
|
+
isOpened: boolean;
|
|
72
|
+
/** Whether the immediate parent namespace is opened (this lane only exists because its parent was drilled into) */
|
|
73
|
+
isParentOpened: boolean;
|
|
74
|
+
/** Whether any event extends strictly past this lane's namespace (so opening would split it further) */
|
|
75
|
+
canExpand: boolean;
|
|
76
|
+
/** Events directly assigned to this lane */
|
|
71
77
|
eventIds: string[];
|
|
72
78
|
}
|
|
73
79
|
|
|
74
|
-
/**
|
|
75
|
-
* Namespace extraction strategy
|
|
76
|
-
*/
|
|
77
|
-
export type NamespaceStrategy =
|
|
78
|
-
| 'first' // First segment only (auth.validation.started -> auth)
|
|
79
|
-
| 'all-but-last' // All but last segment (auth.validation.started -> auth.validation)
|
|
80
|
-
| number // Specific depth (2 -> auth.validation)
|
|
81
|
-
| ((name: string) => string); // Custom function
|
|
82
|
-
|
|
83
80
|
/**
|
|
84
81
|
* Options for sequence layout
|
|
85
82
|
*/
|
|
86
83
|
export interface UseSequenceLayoutOptions {
|
|
87
84
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
85
|
+
* Namespace prefixes whose events should be drilled one segment deeper.
|
|
86
|
+
* Pass an array or a Set. Each entry is a dotted prefix (e.g. `auth` or
|
|
87
|
+
* `auth.user`). When listed, events under that prefix land in lanes one
|
|
88
|
+
* level deeper than the prefix, instead of all sharing the prefix lane.
|
|
89
|
+
* Drilling is recursive: list both `auth` and `auth.user` to drill twice.
|
|
90
90
|
*/
|
|
91
|
-
|
|
91
|
+
openedNamespaces?: string[] | Set<string>;
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
94
|
* Width of each swimlane
|
|
@@ -125,11 +125,23 @@ export interface UseSequenceLayoutOptions {
|
|
|
125
125
|
* @default 14
|
|
126
126
|
*/
|
|
127
127
|
nodeHeight?: number;
|
|
128
|
+
}
|
|
128
129
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
/**
|
|
131
|
+
* A header cell for an opened ancestor namespace, sitting above the leaf
|
|
132
|
+
* lanes it groups. Rendered as a row in the header strip.
|
|
133
|
+
*/
|
|
134
|
+
export interface ParentHeader {
|
|
135
|
+
/** Full ancestor namespace (always in `openedNamespaces`) */
|
|
136
|
+
namespace: string;
|
|
137
|
+
/** Last segment, for display */
|
|
138
|
+
label: string;
|
|
139
|
+
/** Center x of the cell */
|
|
140
|
+
x: number;
|
|
141
|
+
/** Total span width across the leaf lanes underneath */
|
|
142
|
+
width: number;
|
|
143
|
+
/** 1-based depth in the header strip (1 = topmost row) */
|
|
144
|
+
depth: number;
|
|
133
145
|
}
|
|
134
146
|
|
|
135
147
|
/**
|
|
@@ -140,8 +152,15 @@ export interface UseSequenceLayoutResult {
|
|
|
140
152
|
nodes: Node[];
|
|
141
153
|
/** Edges for React Flow */
|
|
142
154
|
edges: Edge[];
|
|
143
|
-
/**
|
|
155
|
+
/** Leaf swimlanes — each gets a lifeline and a leaf header row */
|
|
144
156
|
swimlanes: Swimlane[];
|
|
157
|
+
/**
|
|
158
|
+
* Header cells for opened ancestor namespaces. Stack above the leaf
|
|
159
|
+
* headers; depth 1 sits at the top of the header strip.
|
|
160
|
+
*/
|
|
161
|
+
parentHeaders: ParentHeader[];
|
|
162
|
+
/** Number of header rows (= max leaf depth). At least 1. */
|
|
163
|
+
headerRows: number;
|
|
145
164
|
/** Total width of the diagram */
|
|
146
165
|
totalWidth: number;
|
|
147
166
|
/** Total height of the diagram */
|
|
@@ -149,52 +168,30 @@ export interface UseSequenceLayoutResult {
|
|
|
149
168
|
}
|
|
150
169
|
|
|
151
170
|
/**
|
|
152
|
-
*
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (typeof strategy === 'function') {
|
|
156
|
-
return strategy(name);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const segments = name.split('.');
|
|
160
|
-
|
|
161
|
-
if (segments.length <= 1) {
|
|
162
|
-
return name;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (strategy === 'first') {
|
|
166
|
-
return segments[0];
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (strategy === 'all-but-last') {
|
|
170
|
-
return segments.slice(0, -1).join('.');
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (typeof strategy === 'number') {
|
|
174
|
-
return segments.slice(0, strategy).join('.');
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return segments.slice(0, -1).join('.');
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Get parent namespace (one level up)
|
|
171
|
+
* Resolve the lane (namespace prefix) for a given event name, given the set
|
|
172
|
+
* of opened namespaces. Walks segments from depth 1 outward, descending while
|
|
173
|
+
* the current prefix is opened, and stopping at the first prefix that isn't.
|
|
182
174
|
*/
|
|
183
|
-
function
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
175
|
+
function resolveLane(name: string, opened: Set<string>): string {
|
|
176
|
+
const segs = name.split('.');
|
|
177
|
+
let depth = 1;
|
|
178
|
+
while (depth < segs.length) {
|
|
179
|
+
const prefix = segs.slice(0, depth).join('.');
|
|
180
|
+
if (opened.has(prefix)) {
|
|
181
|
+
depth++;
|
|
182
|
+
} else {
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
187
185
|
}
|
|
188
|
-
return
|
|
186
|
+
return segs.slice(0, depth).join('.');
|
|
189
187
|
}
|
|
190
188
|
|
|
191
189
|
/**
|
|
192
|
-
*
|
|
190
|
+
* useSequenceLayout
|
|
193
191
|
*
|
|
194
|
-
* @param events - Events to
|
|
195
|
-
* @param
|
|
196
|
-
* @param options - Layout options
|
|
197
|
-
* @returns Layout result with positioned nodes, edges, and swimlane info
|
|
192
|
+
* @param events - Events to lay out
|
|
193
|
+
* @param sequenceEdges - Edges connecting events
|
|
194
|
+
* @param options - Layout options (lane sizing, openedNamespaces)
|
|
198
195
|
*
|
|
199
196
|
* @example
|
|
200
197
|
* ```tsx
|
|
@@ -207,7 +204,8 @@ function getParentNamespace(namespace: string): string | undefined {
|
|
|
207
204
|
* [
|
|
208
205
|
* { id: 'e1', fromEvent: '1', toEvent: '2' },
|
|
209
206
|
* { id: 'e2', fromEvent: '2', toEvent: '3' },
|
|
210
|
-
* ]
|
|
207
|
+
* ],
|
|
208
|
+
* { openedNamespaces: ['auth'] }, // drill `auth` to show validation/token as separate lanes
|
|
211
209
|
* );
|
|
212
210
|
* ```
|
|
213
211
|
*/
|
|
@@ -217,129 +215,162 @@ export function useSequenceLayout(
|
|
|
217
215
|
options: UseSequenceLayoutOptions = {}
|
|
218
216
|
): UseSequenceLayoutResult {
|
|
219
217
|
const {
|
|
220
|
-
|
|
218
|
+
openedNamespaces,
|
|
221
219
|
laneWidth = 250,
|
|
222
220
|
laneGap = 0,
|
|
223
221
|
eventSpacing = 80,
|
|
224
222
|
headerHeight = 60,
|
|
225
|
-
collapsedNamespaces = [],
|
|
226
223
|
nodeWidth = 14,
|
|
227
224
|
nodeHeight = 14,
|
|
228
225
|
} = options;
|
|
229
226
|
|
|
227
|
+
// Normalize openedNamespaces into a stable string for memo dependency
|
|
228
|
+
const openedKey = useMemo(() => {
|
|
229
|
+
if (!openedNamespaces) return '';
|
|
230
|
+
const arr = Array.from(openedNamespaces);
|
|
231
|
+
arr.sort();
|
|
232
|
+
return arr.join('|');
|
|
233
|
+
}, [openedNamespaces]);
|
|
234
|
+
|
|
230
235
|
return useMemo(() => {
|
|
231
236
|
if (events.length === 0) {
|
|
232
237
|
return {
|
|
233
238
|
nodes: [],
|
|
234
239
|
edges: [],
|
|
235
240
|
swimlanes: [],
|
|
241
|
+
parentHeaders: [],
|
|
242
|
+
headerRows: 1,
|
|
236
243
|
totalWidth: 0,
|
|
237
244
|
totalHeight: 0,
|
|
238
245
|
};
|
|
239
246
|
}
|
|
240
247
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
248
|
+
const opened = new Set<string>(
|
|
249
|
+
openedKey ? openedKey.split('|') : []
|
|
250
|
+
);
|
|
244
251
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
252
|
+
// Step 1: Resolve each event to a lane prefix
|
|
253
|
+
const eventLane = new Map<string, string>();
|
|
254
|
+
const laneEvents = new Map<string, string[]>();
|
|
248
255
|
|
|
249
|
-
|
|
250
|
-
|
|
256
|
+
for (const event of events) {
|
|
257
|
+
const lane = resolveLane(event.name, opened);
|
|
258
|
+
eventLane.set(event.id, lane);
|
|
259
|
+
if (!laneEvents.has(lane)) {
|
|
260
|
+
laneEvents.set(lane, []);
|
|
251
261
|
}
|
|
252
|
-
|
|
262
|
+
laneEvents.get(lane)!.push(event.id);
|
|
253
263
|
}
|
|
254
264
|
|
|
255
|
-
// Step 2:
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
265
|
+
// Step 2: Order lanes alphabetically (parents naturally sort before children)
|
|
266
|
+
const laneNames = Array.from(laneEvents.keys()).sort();
|
|
267
|
+
|
|
268
|
+
// canExpand is meaningful only when opening would actually fork the lane
|
|
269
|
+
// into multiple child lanes. If every event in the lane would drill into
|
|
270
|
+
// the same child, drilling is a relabel — hide the chevron.
|
|
271
|
+
const eventsById = new Map<string, SequenceEvent>();
|
|
272
|
+
for (const event of events) eventsById.set(event.id, event);
|
|
273
|
+
|
|
274
|
+
const canExpandLane = (laneNs: string, eventIds: string[]): boolean => {
|
|
275
|
+
const augmented = new Set(opened);
|
|
276
|
+
augmented.add(laneNs);
|
|
277
|
+
const seen = new Set<string>();
|
|
278
|
+
for (const eid of eventIds) {
|
|
279
|
+
const event = eventsById.get(eid);
|
|
280
|
+
if (!event) continue;
|
|
281
|
+
seen.add(resolveLane(event.name, augmented));
|
|
282
|
+
if (seen.size > 1) return true;
|
|
273
283
|
}
|
|
284
|
+
return false;
|
|
285
|
+
};
|
|
274
286
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (!visibleNamespaces.includes(visibleNs)) {
|
|
278
|
-
visibleNamespaces.push(visibleNs);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Step 3: Create swimlanes with positions
|
|
283
|
-
const swimlanes: Swimlane[] = visibleNamespaces.map((namespace, index) => {
|
|
287
|
+
// Step 3: Build Swimlane records
|
|
288
|
+
const swimlanes: Swimlane[] = laneNames.map((namespace, index) => {
|
|
284
289
|
const x = index * (laneWidth + laneGap) + laneWidth / 2;
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (namespaceToVisible.get(eventNs) === namespace) {
|
|
290
|
-
eventIds.push(eventId);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Find children (namespaces that have this as parent)
|
|
295
|
-
const children = allNamespaces.filter(
|
|
296
|
-
(ns) => getParentNamespace(ns) === namespace
|
|
297
|
-
);
|
|
290
|
+
const segs = namespace.split('.');
|
|
291
|
+
const parentNamespace =
|
|
292
|
+
segs.length > 1 ? segs.slice(0, -1).join('.') : undefined;
|
|
293
|
+
const eventIds = laneEvents.get(namespace)!;
|
|
298
294
|
|
|
299
295
|
return {
|
|
300
296
|
namespace,
|
|
301
|
-
label:
|
|
297
|
+
label: segs[segs.length - 1] || namespace,
|
|
302
298
|
x,
|
|
303
|
-
parentNamespace
|
|
304
|
-
|
|
305
|
-
|
|
299
|
+
parentNamespace,
|
|
300
|
+
isOpened: opened.has(namespace),
|
|
301
|
+
isParentOpened: parentNamespace ? opened.has(parentNamespace) : false,
|
|
302
|
+
canExpand: canExpandLane(namespace, eventIds),
|
|
306
303
|
eventIds,
|
|
307
304
|
};
|
|
308
305
|
});
|
|
309
306
|
|
|
310
|
-
|
|
311
|
-
const swimlaneByNamespace = new Map<string, Swimlane>();
|
|
307
|
+
const laneByNamespace = new Map<string, Swimlane>();
|
|
312
308
|
for (const lane of swimlanes) {
|
|
313
|
-
|
|
309
|
+
laneByNamespace.set(lane.namespace, lane);
|
|
314
310
|
}
|
|
315
311
|
|
|
316
|
-
// Step
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
const
|
|
312
|
+
// Step 3b: Build parent header cells for opened ancestors. For every leaf
|
|
313
|
+
// lane at depth > 1, walk depths 1..d-1 and aggregate the leaf x-extent
|
|
314
|
+
// under each ancestor.
|
|
315
|
+
const ancestorBounds = new Map<
|
|
316
|
+
string,
|
|
317
|
+
{ xMin: number; xMax: number; depth: number }
|
|
318
|
+
>();
|
|
319
|
+
for (const lane of swimlanes) {
|
|
320
|
+
const segs = lane.namespace.split('.');
|
|
321
|
+
const left = lane.x - laneWidth / 2;
|
|
322
|
+
const right = lane.x + laneWidth / 2;
|
|
323
|
+
for (let d = 1; d < segs.length; d++) {
|
|
324
|
+
const ancestorNs = segs.slice(0, d).join('.');
|
|
325
|
+
const existing = ancestorBounds.get(ancestorNs);
|
|
326
|
+
if (existing) {
|
|
327
|
+
existing.xMin = Math.min(existing.xMin, left);
|
|
328
|
+
existing.xMax = Math.max(existing.xMax, right);
|
|
329
|
+
} else {
|
|
330
|
+
ancestorBounds.set(ancestorNs, { xMin: left, xMax: right, depth: d });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
320
334
|
|
|
335
|
+
const parentHeaders: ParentHeader[] = Array.from(ancestorBounds.entries())
|
|
336
|
+
.map(([namespace, { xMin, xMax, depth }]) => {
|
|
337
|
+
const segs = namespace.split('.');
|
|
338
|
+
return {
|
|
339
|
+
namespace,
|
|
340
|
+
label: segs[segs.length - 1] || namespace,
|
|
341
|
+
x: (xMin + xMax) / 2,
|
|
342
|
+
width: xMax - xMin,
|
|
343
|
+
depth,
|
|
344
|
+
};
|
|
345
|
+
})
|
|
346
|
+
.sort((a, b) => a.depth - b.depth || a.x - b.x);
|
|
347
|
+
|
|
348
|
+
const headerRows = swimlanes.reduce(
|
|
349
|
+
(max, lane) => Math.max(max, lane.namespace.split('.').length),
|
|
350
|
+
1
|
|
351
|
+
);
|
|
352
|
+
const totalHeaderHeight = headerHeight * headerRows;
|
|
353
|
+
|
|
354
|
+
// Step 4: Position events on global time layers, below the full header strip
|
|
355
|
+
const nodes: Node[] = [];
|
|
321
356
|
for (let i = 0; i < events.length; i++) {
|
|
322
357
|
const event = events[i];
|
|
323
|
-
const
|
|
324
|
-
const
|
|
325
|
-
const lane = swimlaneByNamespace.get(visibleNamespace)!;
|
|
358
|
+
const laneNs = eventLane.get(event.id)!;
|
|
359
|
+
const lane = laneByNamespace.get(laneNs)!;
|
|
326
360
|
|
|
327
|
-
|
|
328
|
-
// Start first event closer to header with small offset
|
|
329
|
-
const y = headerHeight + 40 + i * eventSpacing;
|
|
361
|
+
const y = totalHeaderHeight + 40 + i * eventSpacing;
|
|
330
362
|
|
|
331
363
|
nodes.push({
|
|
332
364
|
id: event.id,
|
|
333
365
|
type: 'sequenceMarker',
|
|
334
366
|
position: {
|
|
335
367
|
x: lane.x - nodeWidth / 2,
|
|
336
|
-
y: y - nodeHeight / 2,
|
|
368
|
+
y: y - nodeHeight / 2,
|
|
337
369
|
},
|
|
338
370
|
data: {
|
|
339
371
|
label: event.label || event.name.split('.').pop() || event.name,
|
|
340
372
|
fullName: event.name,
|
|
341
|
-
namespace:
|
|
342
|
-
visibleNamespace,
|
|
373
|
+
namespace: laneNs,
|
|
343
374
|
timeLayer: i,
|
|
344
375
|
isMoveEvent: event.moveEvent === true,
|
|
345
376
|
sourcePath: event.sourcePath,
|
|
@@ -352,27 +383,24 @@ export function useSequenceLayout(
|
|
|
352
383
|
});
|
|
353
384
|
}
|
|
354
385
|
|
|
355
|
-
// Step 5:
|
|
356
|
-
// Each edge looks forward to determine what to render
|
|
386
|
+
// Step 5: Edges — one per event, looking ahead to the next
|
|
357
387
|
const edges: Edge[] = [];
|
|
358
|
-
|
|
359
388
|
for (let i = 0; i < events.length; i++) {
|
|
360
389
|
const currentEvent = events[i];
|
|
361
|
-
const
|
|
362
|
-
const
|
|
363
|
-
const currentLane = swimlaneByNamespace.get(currentVisibleNs)!;
|
|
390
|
+
const currentLaneNs = eventLane.get(currentEvent.id)!;
|
|
391
|
+
const currentLane = laneByNamespace.get(currentLaneNs)!;
|
|
364
392
|
|
|
365
|
-
// Look at the next event (if any)
|
|
366
393
|
if (i < events.length - 1) {
|
|
367
394
|
const nextEvent = events[i + 1];
|
|
368
|
-
const
|
|
369
|
-
const
|
|
370
|
-
const nextLane = swimlaneByNamespace.get(nextVisibleNs)!;
|
|
395
|
+
const nextLaneNs = eventLane.get(nextEvent.id)!;
|
|
396
|
+
const nextLane = laneByNamespace.get(nextLaneNs)!;
|
|
371
397
|
const nextIsMoveEvent = nextEvent.moveEvent === true;
|
|
372
|
-
const crossesLanes =
|
|
398
|
+
const crossesLanes = currentLaneNs !== nextLaneNs;
|
|
373
399
|
|
|
374
|
-
|
|
375
|
-
|
|
400
|
+
const edgeLabel =
|
|
401
|
+
currentEvent.label ||
|
|
402
|
+
currentEvent.name.split('.').pop() ||
|
|
403
|
+
currentEvent.name;
|
|
376
404
|
|
|
377
405
|
edges.push({
|
|
378
406
|
id: `edge-${currentEvent.id}-to-${nextEvent.id}`,
|
|
@@ -384,8 +412,8 @@ export function useSequenceLayout(
|
|
|
384
412
|
labelBgStyle: { fill: 'white', fillOpacity: 0.8 },
|
|
385
413
|
data: {
|
|
386
414
|
crossesLanes,
|
|
387
|
-
sourceNamespace:
|
|
388
|
-
targetNamespace:
|
|
415
|
+
sourceNamespace: currentLaneNs,
|
|
416
|
+
targetNamespace: nextLaneNs,
|
|
389
417
|
isMoveEvent: nextIsMoveEvent,
|
|
390
418
|
sourceEvent: currentEvent,
|
|
391
419
|
targetEvent: nextEvent,
|
|
@@ -394,9 +422,11 @@ export function useSequenceLayout(
|
|
|
394
422
|
},
|
|
395
423
|
});
|
|
396
424
|
} else {
|
|
397
|
-
// Last event - render small activation bar to show it exists
|
|
398
425
|
const currentIsMoveEvent = currentEvent.moveEvent === true;
|
|
399
|
-
const edgeLabel =
|
|
426
|
+
const edgeLabel =
|
|
427
|
+
currentEvent.label ||
|
|
428
|
+
currentEvent.name.split('.').pop() ||
|
|
429
|
+
currentEvent.name;
|
|
400
430
|
|
|
401
431
|
edges.push({
|
|
402
432
|
id: `edge-${currentEvent.id}-end`,
|
|
@@ -408,8 +438,8 @@ export function useSequenceLayout(
|
|
|
408
438
|
labelBgStyle: { fill: 'white', fillOpacity: 0.8 },
|
|
409
439
|
data: {
|
|
410
440
|
crossesLanes: false,
|
|
411
|
-
sourceNamespace:
|
|
412
|
-
targetNamespace:
|
|
441
|
+
sourceNamespace: currentLaneNs,
|
|
442
|
+
targetNamespace: currentLaneNs,
|
|
413
443
|
isMoveEvent: currentIsMoveEvent,
|
|
414
444
|
sourceEvent: currentEvent,
|
|
415
445
|
targetEvent: currentEvent,
|
|
@@ -425,15 +455,13 @@ export function useSequenceLayout(
|
|
|
425
455
|
// Step 6: Compute total dimensions
|
|
426
456
|
const totalWidth =
|
|
427
457
|
swimlanes.length * laneWidth + (swimlanes.length - 1) * laneGap;
|
|
428
|
-
|
|
429
|
-
const totalHeight = headerHeight + 40 + events.length * eventSpacing;
|
|
458
|
+
const totalHeight = totalHeaderHeight + 40 + events.length * eventSpacing;
|
|
430
459
|
|
|
431
|
-
// Step 7:
|
|
460
|
+
// Step 7: Boundary nodes so React Flow's fitView covers full width
|
|
432
461
|
if (swimlanes.length > 0) {
|
|
433
462
|
const leftmostLane = swimlanes[0];
|
|
434
463
|
const rightmostLane = swimlanes[swimlanes.length - 1];
|
|
435
464
|
|
|
436
|
-
// Add boundary nodes at the corners
|
|
437
465
|
nodes.push(
|
|
438
466
|
{
|
|
439
467
|
id: '__boundary_left__',
|
|
@@ -460,19 +488,20 @@ export function useSequenceLayout(
|
|
|
460
488
|
nodes,
|
|
461
489
|
edges,
|
|
462
490
|
swimlanes,
|
|
491
|
+
parentHeaders,
|
|
492
|
+
headerRows,
|
|
463
493
|
totalWidth,
|
|
464
494
|
totalHeight,
|
|
465
495
|
};
|
|
466
496
|
}, [
|
|
467
497
|
events,
|
|
468
498
|
sequenceEdges,
|
|
469
|
-
|
|
499
|
+
openedKey,
|
|
470
500
|
laneWidth,
|
|
471
501
|
laneGap,
|
|
472
502
|
eventSpacing,
|
|
473
503
|
headerHeight,
|
|
474
504
|
nodeWidth,
|
|
475
505
|
nodeHeight,
|
|
476
|
-
collapsedNamespaces,
|
|
477
506
|
]);
|
|
478
507
|
}
|