bpmn-elk-layout 1.1.2 → 1.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.
@@ -26,10 +26,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  mod
27
27
  ));
28
28
 
29
- // ../../node_modules/.bun/tsup@8.5.1+83f24e8a18629abd/node_modules/tsup/assets/cjs_shims.js
29
+ // ../../node_modules/.bun/tsup@8.5.1+e293d8ed4487f686/node_modules/tsup/assets/cjs_shims.js
30
30
  var getImportMetaUrl, importMetaUrl;
31
31
  var init_cjs_shims = __esm({
32
- "../../node_modules/.bun/tsup@8.5.1+83f24e8a18629abd/node_modules/tsup/assets/cjs_shims.js"() {
32
+ "../../node_modules/.bun/tsup@8.5.1+e293d8ed4487f686/node_modules/tsup/assets/cjs_shims.js"() {
33
33
  "use strict";
34
34
  getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
35
35
  importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
@@ -42,7 +42,11 @@ function enableCliMode() {
42
42
  }
43
43
  function isDebugEnabled() {
44
44
  if (cliMode) return false;
45
- return typeof process !== "undefined" && process.env?.["DEBUG"] === "true";
45
+ try {
46
+ return typeof process !== "undefined" && typeof process.env !== "undefined" && process.env?.["DEBUG"] === "true";
47
+ } catch (e) {
48
+ return false;
49
+ }
46
50
  }
47
51
  var cliMode;
48
52
  var init_debug = __esm({
@@ -174,763 +178,103 @@ var init_size_calculator = __esm({
174
178
  }
175
179
  });
176
180
 
177
- // src/layout/edge-routing/geometry-utils.ts
178
- function distance(p1, p2) {
179
- return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
180
- }
181
- function calculatePathLength(waypoints) {
182
- let length = 0;
183
- for (let i = 0; i < waypoints.length - 1; i++) {
184
- const p1 = waypoints[i];
185
- const p2 = waypoints[i + 1];
186
- if (p1 && p2) {
187
- length += distance(p1, p2);
188
- }
189
- }
190
- return length;
191
- }
192
- function segmentIntersectsRect(p1, p2, rect) {
193
- const margin = 5;
194
- const left = rect.x - margin;
195
- const right = rect.x + rect.width + margin;
196
- const top = rect.y - margin;
197
- const bottom = rect.y + rect.height + margin;
198
- if (p1.x < left && p2.x < left || p1.x > right && p2.x > right) return false;
199
- if (p1.y < top && p2.y < top || p1.y > bottom && p2.y > bottom) return false;
200
- if (Math.abs(p1.x - p2.x) < 1) {
201
- const x = p1.x;
202
- const minY = Math.min(p1.y, p2.y);
203
- const maxY = Math.max(p1.y, p2.y);
204
- return x >= left && x <= right && maxY >= top && minY <= bottom;
205
- }
206
- if (Math.abs(p1.y - p2.y) < 1) {
207
- const y = p1.y;
208
- const minX = Math.min(p1.x, p2.x);
209
- const maxX = Math.max(p1.x, p2.x);
210
- return y >= top && y <= bottom && maxX >= left && minX <= right;
211
- }
212
- return true;
213
- }
214
- function segmentCrossesNode(p1, p2, node) {
215
- const margin = 5;
216
- const nodeLeft = node.x - margin;
217
- const nodeRight = node.x + node.width + margin;
218
- const nodeTop = node.y - margin;
219
- const nodeBottom = node.y + node.height + margin;
220
- if (Math.abs(p1.y - p2.y) < 1) {
221
- const segY = p1.y;
222
- const segMinX = Math.min(p1.x, p2.x);
223
- const segMaxX = Math.max(p1.x, p2.x);
224
- if (segY > nodeTop && segY < nodeBottom) {
225
- if (segMinX < nodeRight && segMaxX > nodeLeft) {
226
- const interiorLeft = node.x + margin;
227
- const interiorRight = node.x + node.width - margin;
228
- if (segMinX < interiorRight && segMaxX > interiorLeft) {
229
- return true;
230
- }
231
- }
232
- }
233
- }
234
- if (Math.abs(p1.x - p2.x) < 1) {
235
- const segX = p1.x;
236
- const segMinY = Math.min(p1.y, p2.y);
237
- const segMaxY = Math.max(p1.y, p2.y);
238
- if (segX > nodeLeft && segX < nodeRight) {
239
- if (segMinY < nodeBottom && segMaxY > nodeTop) {
240
- const interiorTop = node.y + margin;
241
- const interiorBottom = node.y + node.height - margin;
242
- if (segMinY < interiorBottom && segMaxY > interiorTop) {
243
- return true;
181
+ // src/layout/post-processing/boundary-event/collector.ts
182
+ function collectBoundaryEventInfo(graph) {
183
+ const info = /* @__PURE__ */ new Map();
184
+ const edgeMap = /* @__PURE__ */ new Map();
185
+ const collectEdges = (node) => {
186
+ if (node.edges) {
187
+ for (const edge of node.edges) {
188
+ const source = edge.sources?.[0];
189
+ const target = edge.targets?.[0];
190
+ if (!source || !target) continue;
191
+ if (!edgeMap.has(source)) {
192
+ edgeMap.set(source, []);
244
193
  }
194
+ edgeMap.get(source).push(target);
245
195
  }
246
196
  }
247
- }
248
- return false;
249
- }
250
- function scoreRoute(start, bendPoints, end, obstacles) {
251
- let score = 0;
252
- const crossingPenalty = 1e3;
253
- const lengthWeight = 0.1;
254
- const path = [start, ...bendPoints, end];
255
- for (const obs of obstacles) {
256
- for (let i = 0; i < path.length - 1; i++) {
257
- const p1 = path[i];
258
- const p2 = path[i + 1];
259
- if (p1 && p2 && segmentIntersectsRect(p1, p2, obs)) {
260
- score += crossingPenalty;
197
+ if (node.children) {
198
+ for (const child of node.children) {
199
+ collectEdges(child);
261
200
  }
262
201
  }
263
- }
264
- for (let i = 0; i < path.length - 1; i++) {
265
- const p1 = path[i];
266
- const p2 = path[i + 1];
267
- if (p1 && p2) {
268
- score += distance(p1, p2) * lengthWeight;
202
+ };
203
+ const collectBoundaryEvents = (node) => {
204
+ if (node.boundaryEvents) {
205
+ const totalBoundaries = node.boundaryEvents.length;
206
+ node.boundaryEvents.forEach((be, index) => {
207
+ const targets = edgeMap.get(be.id) || [];
208
+ info.set(be.id, {
209
+ attachedToRef: be.attachedToRef,
210
+ targets,
211
+ boundaryIndex: index,
212
+ totalBoundaries
213
+ });
214
+ });
269
215
  }
270
- }
271
- return score;
272
- }
273
- function findClearVerticalPath(x, startY, endY, obstacles) {
274
- const minY = Math.min(startY, endY);
275
- const maxY = Math.max(startY, endY);
276
- const margin = 10;
277
- for (const obs of obstacles) {
278
- const obsLeft = obs.x - margin;
279
- const obsRight = obs.x + obs.width + margin;
280
- const obsTop = obs.y;
281
- const obsBottom = obs.y + obs.height;
282
- if (x >= obsLeft && x <= obsRight) {
283
- if (obsBottom > minY && obsTop < maxY) {
284
- const spaceAbove = obsTop - minY;
285
- const spaceBelow = maxY - obsBottom;
286
- if (spaceBelow > spaceAbove && obsBottom + margin < maxY) {
287
- return obsBottom + margin;
288
- } else if (obsTop - margin > minY) {
289
- return obsTop - margin;
290
- }
216
+ if (node.children) {
217
+ for (const child of node.children) {
218
+ collectBoundaryEvents(child);
291
219
  }
292
220
  }
221
+ };
222
+ for (const child of graph.children ?? []) {
223
+ collectEdges(child);
224
+ collectBoundaryEvents(child);
293
225
  }
294
- return null;
295
- }
296
- function lineIntersection(p1, p2, p3, p4) {
297
- const denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);
298
- if (Math.abs(denom) < 1e-4) return null;
299
- const ua = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;
300
- const ub = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;
301
- if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
302
- return {
303
- x: p1.x + ua * (p2.x - p1.x),
304
- y: p1.y + ua * (p2.y - p1.y)
305
- };
306
- }
307
- return null;
226
+ return info;
308
227
  }
309
- var init_geometry_utils = __esm({
310
- "src/layout/edge-routing/geometry-utils.ts"() {
228
+ var init_collector = __esm({
229
+ "src/layout/post-processing/boundary-event/collector.ts"() {
311
230
  "use strict";
312
231
  init_cjs_shims();
313
232
  }
314
233
  });
315
234
 
316
- // src/layout/edge-routing/edge-fixer.ts
317
- var EdgeFixer;
318
- var init_edge_fixer = __esm({
319
- "src/layout/edge-routing/edge-fixer.ts"() {
320
- "use strict";
321
- init_cjs_shims();
322
- init_geometry_utils();
323
- init_debug();
324
- EdgeFixer = class {
325
- margin = 15;
326
- /**
327
- * Fix edges that cross through nodes
328
- */
329
- fix(graph) {
330
- const nodesByContainer = /* @__PURE__ */ new Map();
331
- const flowNodePatterns = [
332
- /^task_/,
333
- /^gateway_/,
334
- /^start_/,
335
- /^end_/,
336
- /^subprocess_/,
337
- /^call_/,
338
- /^intermediate_/,
339
- /^event_/,
340
- /^catch_/
341
- ];
342
- const boundaryEventPattern = /^boundary_/;
343
- const poolPatterns = [
344
- /^pool_/,
345
- /^participant_/,
346
- /^process_/
347
- ];
348
- const collectNodePositions = (node, offsetX = 0, offsetY = 0, containerId = "root") => {
349
- const id = node.id || "";
350
- const isBoundaryEvent = boundaryEventPattern.test(id);
351
- const isFlowNode = flowNodePatterns.some((pattern) => pattern.test(id));
352
- const isPool = poolPatterns.some((pattern) => pattern.test(id));
353
- const bpmn = node.bpmn;
354
- if (isDebugEnabled() && (id.includes("lane") || id.includes("pool") || id.includes("end_fast") || id.includes("gateway_fast"))) {
355
- console.log(`[BPMN] EdgeFixer.collectNodePositions: id=${id}, offsetX=${offsetX}, offsetY=${offsetY}, bpmn=${JSON.stringify(bpmn)}`);
356
- console.log(`[BPMN] node.x=${node.x}, node.y=${node.y}, isFlowNode=${isFlowNode}, isPool=${isPool}`);
357
- }
358
- const currentContainerId = isPool ? id : containerId;
359
- if (isFlowNode && !isBoundaryEvent && node.x !== void 0 && node.y !== void 0) {
360
- const absX = offsetX + node.x;
361
- const absY = offsetY + node.y;
362
- if (!nodesByContainer.has(currentContainerId)) {
363
- nodesByContainer.set(currentContainerId, /* @__PURE__ */ new Map());
364
- }
365
- nodesByContainer.get(currentContainerId).set(node.id, {
366
- x: absX,
367
- y: absY,
368
- width: node.width ?? 100,
369
- height: node.height ?? 80
370
- });
371
- }
372
- const isExpandedSubprocess = bpmn?.isExpanded === true && (bpmn?.type === "subProcess" || bpmn?.type === "transaction" || bpmn?.type === "adHocSubProcess" || bpmn?.type === "eventSubProcess");
373
- const isPoolOrLane = bpmn?.type === "participant" || bpmn?.type === "lane";
374
- const isContainer = isExpandedSubprocess || isPoolOrLane;
375
- if (node.children) {
376
- const newOffsetX = isContainer ? offsetX + (node.x ?? 0) : offsetX;
377
- const newOffsetY = isContainer ? offsetY + (node.y ?? 0) : offsetY;
378
- for (const child of node.children) {
379
- collectNodePositions(child, newOffsetX, newOffsetY, currentContainerId);
380
- }
381
- }
382
- };
383
- collectNodePositions(graph);
384
- const processEdges = (node, containerOffsetX = 0, containerOffsetY = 0, containerId = "root") => {
385
- const bpmn = node.bpmn;
386
- const isPool = poolPatterns.some((pattern) => pattern.test(node.id || ""));
387
- const currentContainerId = isPool ? node.id : containerId;
388
- if (node.edges) {
389
- const containerNodes = nodesByContainer.get(currentContainerId) ?? /* @__PURE__ */ new Map();
390
- for (const edge of node.edges) {
391
- if (edge.sections && edge.sections.length > 0) {
392
- const hasPoolRelativeCoords = edge._poolRelativeCoords === true;
393
- if (hasPoolRelativeCoords) {
394
- continue;
395
- }
396
- this.fixEdgeIfCrossing(edge, containerNodes, containerOffsetX, containerOffsetY);
397
- }
398
- }
399
- }
400
- const isExpandedSubprocess = bpmn?.isExpanded === true && (bpmn?.type === "subProcess" || bpmn?.type === "transaction" || bpmn?.type === "adHocSubProcess" || bpmn?.type === "eventSubProcess");
401
- const isPoolOrLane = bpmn?.type === "participant" || bpmn?.type === "lane";
402
- const isContainer = isExpandedSubprocess || isPoolOrLane;
403
- if (node.children) {
404
- const newOffsetX = isContainer ? containerOffsetX + (node.x ?? 0) : containerOffsetX;
405
- const newOffsetY = isContainer ? containerOffsetY + (node.y ?? 0) : containerOffsetY;
406
- for (const child of node.children) {
407
- processEdges(child, newOffsetX, newOffsetY, currentContainerId);
408
- }
409
- }
410
- };
411
- processEdges(graph);
235
+ // src/layout/post-processing/boundary-event/mover.ts
236
+ function buildNodeAndEdgeMaps(graph, sizedGraph) {
237
+ const nodeMap = /* @__PURE__ */ new Map();
238
+ const nodeTypeMap = /* @__PURE__ */ new Map();
239
+ const edgeMap = /* @__PURE__ */ new Map();
240
+ const reverseEdgeMap = /* @__PURE__ */ new Map();
241
+ const collectNodeTypes = (node) => {
242
+ if (node.bpmn?.type) {
243
+ nodeTypeMap.set(node.id, node.bpmn.type);
244
+ }
245
+ if (node.children) {
246
+ for (const child of node.children) {
247
+ collectNodeTypes(child);
412
248
  }
413
- /**
414
- * Check if an edge crosses any node and fix it if so
415
- */
416
- fixEdgeIfCrossing(edge, nodePositions, containerOffsetX, containerOffsetY) {
417
- const section = edge.sections?.[0];
418
- if (!section) return;
419
- const sourceId = edge.sources?.[0];
420
- const targetId = edge.targets?.[0];
421
- if (sourceId?.startsWith("boundary_")) {
422
- return;
423
- }
424
- const waypoints = [
425
- { x: containerOffsetX + section.startPoint.x, y: containerOffsetY + section.startPoint.y }
426
- ];
427
- if (section.bendPoints) {
428
- for (const bp of section.bendPoints) {
429
- waypoints.push({ x: containerOffsetX + bp.x, y: containerOffsetY + bp.y });
430
- }
431
- }
432
- waypoints.push({ x: containerOffsetX + section.endPoint.x, y: containerOffsetY + section.endPoint.y });
433
- const crossedNodes = [];
434
- for (let i = 0; i < waypoints.length - 1; i++) {
435
- const p1 = waypoints[i];
436
- const p2 = waypoints[i + 1];
437
- if (!p1 || !p2) continue;
438
- for (const [nodeId, pos] of nodePositions) {
439
- if (nodeId === sourceId || nodeId === targetId) continue;
440
- if (segmentCrossesNode(p1, p2, pos)) {
441
- crossedNodes.push(nodeId);
442
- }
443
- }
444
- }
445
- const targetPos = targetId ? nodePositions.get(targetId) : void 0;
446
- const sourcePos = sourceId ? nodePositions.get(sourceId) : void 0;
447
- if (isDebugEnabled() && edge.id?.includes("back")) {
448
- console.log(`[BPMN] Edge ${edge.id}: sourceId=${sourceId}, targetId=${targetId}`);
449
- console.log(`[BPMN] Edge ${edge.id}: sourcePos=${JSON.stringify(sourcePos)}, targetPos=${JSON.stringify(targetPos)}`);
450
- console.log(`[BPMN] Edge ${edge.id}: waypoints.length=${waypoints.length}`);
451
- }
452
- if (targetPos && sourcePos && waypoints.length >= 2) {
453
- const lastWaypoint = waypoints[waypoints.length - 1];
454
- const secondLastWaypoint = waypoints[waypoints.length - 2];
455
- const isReturnEdge2 = targetPos.y + targetPos.height < sourcePos.y;
456
- if (isDebugEnabled() && edge.id?.includes("back")) {
457
- console.log(`[BPMN] Edge ${edge.id}: isReturnEdge=${isReturnEdge2}`);
458
- if (lastWaypoint) {
459
- console.log(`[BPMN] Edge ${edge.id}: lastWaypoint=(${lastWaypoint.x},${lastWaypoint.y})`);
460
- }
461
- }
462
- if (isReturnEdge2 && lastWaypoint && secondLastWaypoint) {
463
- if (Math.abs(secondLastWaypoint.y - lastWaypoint.y) < 5) {
464
- const segY = secondLastWaypoint.y;
465
- const segMinX = Math.min(secondLastWaypoint.x, lastWaypoint.x);
466
- const segMaxX = Math.max(secondLastWaypoint.x, lastWaypoint.x);
467
- if (segY > targetPos.y && segY < targetPos.y + targetPos.height) {
468
- if (segMinX < targetPos.x + targetPos.width && segMaxX > targetPos.x) {
469
- crossedNodes.push(targetId + " (target)");
470
- }
471
- }
472
- }
473
- }
474
- }
475
- if (crossedNodes.length === 0) return;
476
- if (isDebugEnabled()) {
477
- console.log(`[BPMN] Edge ${edge.id} crosses nodes: ${crossedNodes.join(", ")}`);
478
- }
479
- if (!sourcePos || !targetPos) return;
480
- const obstacles = [];
481
- for (const [nodeId, pos] of nodePositions) {
482
- if (nodeId === sourceId || nodeId === targetId) continue;
483
- obstacles.push({ ...pos, id: nodeId });
484
- }
485
- const isReturnEdge = targetPos.y + targetPos.height < sourcePos.y;
486
- const crossesThroughTarget = crossedNodes.some((n) => n.includes("(target)"));
487
- if (isReturnEdge && crossesThroughTarget) {
488
- const targetWidth = targetPos.width;
489
- section.endPoint = {
490
- x: section.endPoint.x + targetWidth,
491
- y: section.endPoint.y
492
- };
493
- }
494
- const originalStart = {
495
- x: containerOffsetX + section.startPoint.x,
496
- y: containerOffsetY + section.startPoint.y
497
- };
498
- const originalEnd = {
499
- x: containerOffsetX + section.endPoint.x,
500
- y: containerOffsetY + section.endPoint.y
501
- };
502
- const result = this.calculatePerpendicularAvoidingPath(
503
- originalStart,
504
- originalEnd,
505
- sourcePos,
506
- targetPos,
507
- obstacles,
508
- isReturnEdge
509
- );
510
- if (result.startPoint) {
511
- section.startPoint = {
512
- x: result.startPoint.x - containerOffsetX,
513
- y: result.startPoint.y - containerOffsetY
514
- };
515
- }
516
- if (result.endPoint) {
517
- section.endPoint = {
518
- x: result.endPoint.x - containerOffsetX,
519
- y: result.endPoint.y - containerOffsetY
520
- };
521
- }
522
- const relativeBendPoints = result.bendPoints.map((bp) => ({
523
- x: bp.x - containerOffsetX,
524
- y: bp.y - containerOffsetY
525
- }));
526
- section.bendPoints = relativeBendPoints.length > 0 ? relativeBendPoints : void 0;
527
- if (isDebugEnabled()) {
528
- console.log(`[BPMN] Fixed edge ${edge.id} with ${relativeBendPoints.length} bend points`);
249
+ }
250
+ if (node.boundaryEvents) {
251
+ for (const be of node.boundaryEvents) {
252
+ if (be.bpmn?.type) {
253
+ nodeTypeMap.set(be.id, be.bpmn.type);
529
254
  }
530
255
  }
531
- /**
532
- * Determine connection side based on the edge direction
533
- * For BPMN diagrams with left-to-right flow, we prefer horizontal connections
534
- * even when vertical distance is larger, as long as target is to the right.
535
- */
536
- determineConnectionSide(from, to, nodeBounds, isSource) {
537
- const dx = to.x - from.x;
538
- const dy = to.y - from.y;
539
- const absDx = Math.abs(dx);
540
- const absDy = Math.abs(dy);
541
- if (dx > 0) {
542
- if (isSource) {
543
- return "right";
544
- } else {
545
- if (absDy > absDx * 1.5) {
546
- return dy > 0 ? "top" : "bottom";
547
- }
548
- return "left";
549
- }
550
- } else if (dx < 0) {
551
- if (isSource) {
552
- return "left";
553
- } else {
554
- return "right";
555
- }
556
- } else {
557
- if (isSource) {
558
- return dy > 0 ? "bottom" : "top";
559
- } else {
560
- return dy > 0 ? "top" : "bottom";
561
- }
562
- }
563
- }
564
- /**
565
- * Get the connection point on a node boundary
566
- */
567
- getConnectionPoint(bounds, side) {
568
- const centerX = bounds.x + bounds.width / 2;
569
- const centerY = bounds.y + bounds.height / 2;
570
- switch (side) {
571
- case "top":
572
- return { x: centerX, y: bounds.y };
573
- case "bottom":
574
- return { x: centerX, y: bounds.y + bounds.height };
575
- case "left":
576
- return { x: bounds.x, y: centerY };
577
- case "right":
578
- return { x: bounds.x + bounds.width, y: centerY };
579
- }
580
- }
581
- /**
582
- * Calculate bend points that avoid obstacles while ensuring perpendicular connections
583
- * Uses LOCAL routing - only considers obstacles actually blocking the direct path
584
- */
585
- calculatePerpendicularAvoidingPath(originalStart, originalEnd, source, target, obstacles, isReturnEdge) {
586
- const dx = originalEnd.x - originalStart.x;
587
- const dy = originalEnd.y - originalStart.y;
588
- const sourceSide = this.determineConnectionSide(originalStart, originalEnd, source, true);
589
- const targetSide = this.determineConnectionSide(originalStart, originalEnd, target, false);
590
- const startPoint = this.getConnectionPoint(source, sourceSide);
591
- const endPoint = this.getConnectionPoint(target, targetSide);
592
- const pathMinX = Math.min(startPoint.x, endPoint.x) - this.margin;
593
- const pathMaxX = Math.max(startPoint.x, endPoint.x) + this.margin;
594
- const pathMinY = Math.min(startPoint.y, endPoint.y) - this.margin;
595
- const pathMaxY = Math.max(startPoint.y, endPoint.y) + this.margin;
596
- const blockingObstacles = obstacles.filter((obs) => {
597
- const obsRight = obs.x + obs.width;
598
- const obsBottom = obs.y + obs.height;
599
- const overlapX = obs.x < pathMaxX && obsRight > pathMinX;
600
- const overlapY = obs.y < pathMaxY && obsBottom > pathMinY;
601
- return overlapX && overlapY;
602
- });
603
- if (blockingObstacles.length === 0) {
604
- const bendPoints2 = this.createPerpendicularPath(startPoint, endPoint, sourceSide, targetSide);
605
- return { bendPoints: bendPoints2, startPoint, endPoint };
606
- }
607
- const targetIsRight = dx > 0;
608
- const targetIsAbove = dy < 0;
609
- const targetIsBelow = dy > 0;
610
- let bendPoints;
611
- if (targetIsRight) {
612
- if (targetIsAbove) {
613
- bendPoints = this.routeRightThenUp(startPoint, endPoint, blockingObstacles, source, target);
614
- } else if (targetIsBelow) {
615
- bendPoints = this.routeRightThenDown(startPoint, endPoint, blockingObstacles, source, target);
616
- } else {
617
- bendPoints = this.routeRightWithObstacleAvoidance(startPoint, endPoint, sourceSide, targetSide, blockingObstacles, source, target);
256
+ }
257
+ };
258
+ if (sizedGraph) {
259
+ for (const child of sizedGraph.children ?? []) {
260
+ collectNodeTypes(child);
261
+ }
262
+ }
263
+ const buildMaps = (node) => {
264
+ nodeMap.set(node.id, node);
265
+ if (node.edges) {
266
+ for (const edge of node.edges) {
267
+ const source = edge.sources?.[0];
268
+ const target = edge.targets?.[0];
269
+ if (source && target) {
270
+ if (!edgeMap.has(source)) {
271
+ edgeMap.set(source, []);
618
272
  }
619
- } else {
620
- if (targetIsAbove) {
621
- bendPoints = this.routeUpWithObstacleAvoidance(startPoint, endPoint, sourceSide, targetSide, blockingObstacles, source, target);
622
- } else if (targetIsBelow) {
623
- bendPoints = this.routeDownWithObstacleAvoidance(startPoint, endPoint, sourceSide, targetSide, blockingObstacles, source, target);
624
- } else {
625
- bendPoints = this.routeLeftWithObstacleAvoidance(startPoint, endPoint, sourceSide, targetSide, blockingObstacles, source, target);
273
+ edgeMap.get(source).push(target);
274
+ if (!reverseEdgeMap.has(target)) {
275
+ reverseEdgeMap.set(target, []);
626
276
  }
627
- }
628
- return { bendPoints, startPoint, endPoint };
629
- }
630
- /**
631
- * Route right then up - for edges going both right and up
632
- * Source exits from right side, target enters from left side
633
- * Strategy: go horizontally right past obstacles, then vertically to target
634
- */
635
- routeRightThenUp(start, end, obstacles, source, target) {
636
- const bendPoints = [];
637
- const blockingObstacles = obstacles.filter((obs) => {
638
- const obsRight = obs.x + obs.width;
639
- const obsBottom = obs.y + obs.height;
640
- const pathMinY = Math.min(start.y, end.y);
641
- const pathMaxY = Math.max(start.y, end.y);
642
- return obs.x < end.x && obsRight > start.x - this.margin && obs.y < pathMaxY && obsBottom > pathMinY;
643
- });
644
- if (blockingObstacles.length === 0) {
645
- bendPoints.push({ x: end.x, y: start.y });
646
- return bendPoints;
647
- }
648
- let clearX = start.x;
649
- for (const obs of blockingObstacles) {
650
- clearX = Math.max(clearX, obs.x + obs.width + this.margin);
651
- }
652
- clearX = Math.max(clearX, end.x);
653
- if (Math.abs(clearX - end.x) < 5) {
654
- bendPoints.push({ x: end.x, y: start.y });
655
- } else {
656
- bendPoints.push({ x: clearX, y: start.y });
657
- bendPoints.push({ x: clearX, y: end.y });
658
- }
659
- return bendPoints;
660
- }
661
- /**
662
- * Route right then down - for edges going both right and down
663
- * Source exits from right side, target enters from left side
664
- * Strategy: go horizontally right past obstacles, then vertically to target
665
- */
666
- routeRightThenDown(start, end, obstacles, source, target) {
667
- const bendPoints = [];
668
- const blockingObstacles = obstacles.filter((obs) => {
669
- const obsRight = obs.x + obs.width;
670
- const obsBottom = obs.y + obs.height;
671
- const pathMinY = Math.min(start.y, end.y);
672
- const pathMaxY = Math.max(start.y, end.y);
673
- return obs.x < end.x && obsRight > start.x - this.margin && obs.y < pathMaxY && obsBottom > pathMinY;
674
- });
675
- if (blockingObstacles.length === 0) {
676
- bendPoints.push({ x: end.x, y: start.y });
677
- return bendPoints;
678
- }
679
- let clearX = start.x;
680
- for (const obs of blockingObstacles) {
681
- clearX = Math.max(clearX, obs.x + obs.width + this.margin);
682
- }
683
- clearX = Math.max(clearX, end.x);
684
- if (Math.abs(clearX - end.x) < 5) {
685
- bendPoints.push({ x: end.x, y: start.y });
686
- } else {
687
- bendPoints.push({ x: clearX, y: start.y });
688
- bendPoints.push({ x: clearX, y: end.y });
689
- }
690
- return bendPoints;
691
- }
692
- /**
693
- * Create a simple perpendicular path without obstacle avoidance
694
- */
695
- createPerpendicularPath(start, end, sourceSide, targetSide) {
696
- const bendPoints = [];
697
- if (Math.abs(start.x - end.x) < 5 || Math.abs(start.y - end.y) < 5) {
698
- return bendPoints;
699
- }
700
- const isVerticalExit = sourceSide === "top" || sourceSide === "bottom";
701
- const isVerticalEntry = targetSide === "top" || targetSide === "bottom";
702
- if (isVerticalExit && isVerticalEntry) {
703
- const midY = (start.y + end.y) / 2;
704
- bendPoints.push({ x: start.x, y: midY });
705
- bendPoints.push({ x: end.x, y: midY });
706
- } else if (!isVerticalExit && !isVerticalEntry) {
707
- const midX = (start.x + end.x) / 2;
708
- bendPoints.push({ x: midX, y: start.y });
709
- bendPoints.push({ x: midX, y: end.y });
710
- } else if (isVerticalExit && !isVerticalEntry) {
711
- bendPoints.push({ x: start.x, y: end.y });
712
- } else {
713
- bendPoints.push({ x: end.x, y: start.y });
714
- }
715
- return bendPoints;
716
- }
717
- /**
718
- * Route downward with obstacle avoidance, maintaining perpendicular connections
719
- */
720
- routeDownWithObstacleAvoidance(start, end, sourceSide, targetSide, obstacles, source, target) {
721
- const bendPoints = [];
722
- let avoidX = Math.min(start.x, end.x);
723
- for (const obs of obstacles) {
724
- if (obs.y <= end.y && obs.y + obs.height >= start.y) {
725
- if (obs.x <= start.x && obs.x + obs.width >= start.x) {
726
- avoidX = Math.min(avoidX, obs.x - this.margin);
727
- }
728
- if (obs.x <= end.x && obs.x + obs.width >= end.x) {
729
- avoidX = Math.min(avoidX, obs.x - this.margin);
730
- }
731
- }
732
- }
733
- avoidX = Math.min(avoidX, source.x - this.margin);
734
- avoidX = Math.min(avoidX, target.x - this.margin);
735
- if (sourceSide === "bottom") {
736
- const exitY = start.y + this.margin;
737
- bendPoints.push({ x: start.x, y: exitY });
738
- if (avoidX < start.x - 5) {
739
- bendPoints.push({ x: avoidX, y: exitY });
740
- bendPoints.push({ x: avoidX, y: end.y - this.margin });
741
- bendPoints.push({ x: end.x, y: end.y - this.margin });
742
- } else if (Math.abs(start.x - end.x) > 5) {
743
- bendPoints.push({ x: end.x, y: exitY });
744
- }
745
- } else {
746
- const midY = (start.y + end.y) / 2;
747
- bendPoints.push({ x: avoidX, y: start.y });
748
- bendPoints.push({ x: avoidX, y: midY });
749
- bendPoints.push({ x: end.x, y: midY });
750
- }
751
- return bendPoints;
752
- }
753
- /**
754
- * Route upward with obstacle avoidance, maintaining perpendicular connections
755
- */
756
- routeUpWithObstacleAvoidance(start, end, sourceSide, targetSide, obstacles, source, target) {
757
- const bendPoints = [];
758
- let clearX = Math.max(source.x + source.width, target.x + target.width) + this.margin;
759
- for (const obs of obstacles) {
760
- clearX = Math.max(clearX, obs.x + obs.width + this.margin);
761
- }
762
- if (sourceSide === "right") {
763
- bendPoints.push({ x: clearX, y: start.y });
764
- bendPoints.push({ x: clearX, y: end.y });
765
- } else if (sourceSide === "top") {
766
- const exitY = start.y - this.margin;
767
- bendPoints.push({ x: start.x, y: exitY });
768
- bendPoints.push({ x: clearX, y: exitY });
769
- bendPoints.push({ x: clearX, y: end.y });
770
- } else {
771
- bendPoints.push({ x: clearX, y: start.y });
772
- bendPoints.push({ x: clearX, y: end.y });
773
- }
774
- return bendPoints;
775
- }
776
- /**
777
- * Route rightward with obstacle avoidance, maintaining perpendicular connections
778
- */
779
- routeRightWithObstacleAvoidance(start, end, sourceSide, targetSide, obstacles, source, target) {
780
- const bendPoints = [];
781
- const sourceRight = source.x + source.width;
782
- const targetLeft = target.x;
783
- let routeX = sourceRight + this.margin;
784
- for (const obs of obstacles) {
785
- const obsLeft = obs.x;
786
- const obsRight = obs.x + obs.width;
787
- if (obsRight > sourceRight && obsLeft < targetLeft) {
788
- routeX = Math.max(routeX, obsRight + this.margin);
789
- }
790
- }
791
- routeX = Math.min(routeX, targetLeft - this.margin);
792
- if (routeX <= sourceRight) {
793
- let clearY = Math.max(source.y + source.height, target.y + target.height) + this.margin;
794
- for (const obs of obstacles) {
795
- clearY = Math.max(clearY, obs.y + obs.height + this.margin);
796
- }
797
- if (sourceSide === "right") {
798
- bendPoints.push({ x: start.x + this.margin, y: start.y });
799
- bendPoints.push({ x: start.x + this.margin, y: clearY });
800
- bendPoints.push({ x: end.x - this.margin, y: clearY });
801
- bendPoints.push({ x: end.x - this.margin, y: end.y });
802
- } else {
803
- bendPoints.push({ x: start.x, y: clearY });
804
- bendPoints.push({ x: end.x, y: clearY });
805
- }
806
- } else {
807
- bendPoints.push({ x: routeX, y: start.y });
808
- bendPoints.push({ x: routeX, y: end.y });
809
- }
810
- return bendPoints;
811
- }
812
- /**
813
- * Route leftward with obstacle avoidance, maintaining perpendicular connections
814
- */
815
- routeLeftWithObstacleAvoidance(start, end, sourceSide, targetSide, obstacles, source, target) {
816
- const bendPoints = [];
817
- let clearX = Math.min(source.x, target.x) - this.margin;
818
- for (const obs of obstacles) {
819
- if (obs.x < source.x && obs.x + obs.width > target.x + target.width) {
820
- clearX = Math.min(clearX, obs.x - this.margin);
821
- }
822
- }
823
- if (sourceSide === "left") {
824
- bendPoints.push({ x: clearX, y: start.y });
825
- bendPoints.push({ x: clearX, y: end.y });
826
- } else {
827
- const exitX = start.x - this.margin;
828
- bendPoints.push({ x: exitX, y: start.y });
829
- bendPoints.push({ x: exitX, y: end.y });
830
- }
831
- return bendPoints;
832
- }
833
- };
834
- }
835
- });
836
-
837
- // src/layout/post-processing/boundary-event/collector.ts
838
- function collectBoundaryEventInfo(graph) {
839
- const info = /* @__PURE__ */ new Map();
840
- const edgeMap = /* @__PURE__ */ new Map();
841
- const collectEdges = (node) => {
842
- if (node.edges) {
843
- for (const edge of node.edges) {
844
- const source = edge.sources?.[0];
845
- const target = edge.targets?.[0];
846
- if (!source || !target) continue;
847
- if (!edgeMap.has(source)) {
848
- edgeMap.set(source, []);
849
- }
850
- edgeMap.get(source).push(target);
851
- }
852
- }
853
- if (node.children) {
854
- for (const child of node.children) {
855
- collectEdges(child);
856
- }
857
- }
858
- };
859
- const collectBoundaryEvents = (node) => {
860
- if (node.boundaryEvents) {
861
- const totalBoundaries = node.boundaryEvents.length;
862
- node.boundaryEvents.forEach((be, index) => {
863
- const targets = edgeMap.get(be.id) || [];
864
- info.set(be.id, {
865
- attachedToRef: be.attachedToRef,
866
- targets,
867
- boundaryIndex: index,
868
- totalBoundaries
869
- });
870
- });
871
- }
872
- if (node.children) {
873
- for (const child of node.children) {
874
- collectBoundaryEvents(child);
875
- }
876
- }
877
- };
878
- for (const child of graph.children ?? []) {
879
- collectEdges(child);
880
- collectBoundaryEvents(child);
881
- }
882
- return info;
883
- }
884
- var init_collector = __esm({
885
- "src/layout/post-processing/boundary-event/collector.ts"() {
886
- "use strict";
887
- init_cjs_shims();
888
- }
889
- });
890
-
891
- // src/layout/post-processing/boundary-event/mover.ts
892
- function buildNodeAndEdgeMaps(graph, sizedGraph) {
893
- const nodeMap = /* @__PURE__ */ new Map();
894
- const nodeTypeMap = /* @__PURE__ */ new Map();
895
- const edgeMap = /* @__PURE__ */ new Map();
896
- const reverseEdgeMap = /* @__PURE__ */ new Map();
897
- const collectNodeTypes = (node) => {
898
- if (node.bpmn?.type) {
899
- nodeTypeMap.set(node.id, node.bpmn.type);
900
- }
901
- if (node.children) {
902
- for (const child of node.children) {
903
- collectNodeTypes(child);
904
- }
905
- }
906
- if (node.boundaryEvents) {
907
- for (const be of node.boundaryEvents) {
908
- if (be.bpmn?.type) {
909
- nodeTypeMap.set(be.id, be.bpmn.type);
910
- }
911
- }
912
- }
913
- };
914
- if (sizedGraph) {
915
- for (const child of sizedGraph.children ?? []) {
916
- collectNodeTypes(child);
917
- }
918
- }
919
- const buildMaps = (node) => {
920
- nodeMap.set(node.id, node);
921
- if (node.edges) {
922
- for (const edge of node.edges) {
923
- const source = edge.sources?.[0];
924
- const target = edge.targets?.[0];
925
- if (source && target) {
926
- if (!edgeMap.has(source)) {
927
- edgeMap.set(source, []);
928
- }
929
- edgeMap.get(source).push(target);
930
- if (!reverseEdgeMap.has(target)) {
931
- reverseEdgeMap.set(target, []);
932
- }
933
- reverseEdgeMap.get(target).push(source);
277
+ reverseEdgeMap.get(target).push(source);
934
278
  }
935
279
  }
936
280
  }
@@ -1354,6 +698,22 @@ function recalculateEdgesForMovedNodes(graph, movedNodes, boundaryEventInfo) {
1354
698
  for (const [nodeId] of movedNodes) {
1355
699
  obstacleIds.add(nodeId);
1356
700
  }
701
+ const flowNodePatterns = [
702
+ /^task_/,
703
+ /^gateway_/,
704
+ /^start_/,
705
+ /^end_/,
706
+ /^subprocess_/,
707
+ /^call_/,
708
+ /^intermediate_/,
709
+ /^event_/,
710
+ /^catch_/
711
+ ];
712
+ for (const [nodeId] of nodeMap) {
713
+ if (flowNodePatterns.some((pattern) => pattern.test(nodeId))) {
714
+ obstacleIds.add(nodeId);
715
+ }
716
+ }
1357
717
  const boundaryEventPositions = /* @__PURE__ */ new Map();
1358
718
  for (const [beId, info] of boundaryEventInfo) {
1359
719
  const attachedNode = nodeMap.get(info.attachedToRef);
@@ -1392,8 +752,7 @@ function recalculateEdgesForMovedNodes(graph, movedNodes, boundaryEventInfo) {
1392
752
  sourceNode,
1393
753
  targetNode,
1394
754
  obstacleIds,
1395
- nodeMap,
1396
- debug
755
+ nodeMap
1397
756
  );
1398
757
  }
1399
758
  }
@@ -1505,9 +864,29 @@ function recalculateEdgeWithObstacleAvoidance(edge, source, target, obstacleIds,
1505
864
  clearX = Math.max(clearX, obs.x + obs.width + 30);
1506
865
  }
1507
866
  }
1508
- waypoints.push({ x: clearX, y: startY });
1509
- waypoints.push({ x: clearX, y: endY });
1510
- waypoints.push({ x: endX, y: endY });
867
+ let horizontalClearY = endY;
868
+ const horizSegMinX = Math.min(clearX, endX);
869
+ const horizSegMaxX = Math.max(clearX, endX);
870
+ const margin = 20;
871
+ for (const obs of obstacles) {
872
+ const obsRight = obs.x + obs.width;
873
+ const obsBottom = obs.y + obs.height;
874
+ if (obs.x < horizSegMaxX && obsRight > horizSegMinX) {
875
+ if (horizontalClearY >= obs.y - margin && horizontalClearY <= obsBottom + margin) {
876
+ horizontalClearY = Math.max(horizontalClearY, obsBottom + margin);
877
+ }
878
+ }
879
+ }
880
+ if (horizontalClearY > endY) {
881
+ waypoints.push({ x: clearX, y: startY });
882
+ waypoints.push({ x: clearX, y: horizontalClearY });
883
+ waypoints.push({ x: endX, y: horizontalClearY });
884
+ waypoints.push({ x: endX, y: endY });
885
+ } else {
886
+ waypoints.push({ x: clearX, y: startY });
887
+ waypoints.push({ x: clearX, y: endY });
888
+ waypoints.push({ x: endX, y: endY });
889
+ }
1511
890
  } else if (!isPrimarilyVertical && dx > 0) {
1512
891
  const startX = sx + sw;
1513
892
  const startY = sy + sh / 2;
@@ -1654,6 +1033,85 @@ var init_boundary_event = __esm({
1654
1033
  }
1655
1034
  });
1656
1035
 
1036
+ // src/layout/edge-routing/geometry-utils.ts
1037
+ function distance(p1, p2) {
1038
+ return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
1039
+ }
1040
+ function segmentIntersectsRect(p1, p2, rect) {
1041
+ const margin = 5;
1042
+ const left = rect.x - margin;
1043
+ const right = rect.x + rect.width + margin;
1044
+ const top = rect.y - margin;
1045
+ const bottom = rect.y + rect.height + margin;
1046
+ if (p1.x < left && p2.x < left || p1.x > right && p2.x > right) return false;
1047
+ if (p1.y < top && p2.y < top || p1.y > bottom && p2.y > bottom) return false;
1048
+ if (Math.abs(p1.x - p2.x) < 1) {
1049
+ const x = p1.x;
1050
+ const minY = Math.min(p1.y, p2.y);
1051
+ const maxY = Math.max(p1.y, p2.y);
1052
+ return x >= left && x <= right && maxY >= top && minY <= bottom;
1053
+ }
1054
+ if (Math.abs(p1.y - p2.y) < 1) {
1055
+ const y = p1.y;
1056
+ const minX = Math.min(p1.x, p2.x);
1057
+ const maxX = Math.max(p1.x, p2.x);
1058
+ return y >= top && y <= bottom && maxX >= left && minX <= right;
1059
+ }
1060
+ return true;
1061
+ }
1062
+ function scoreRoute(start, bendPoints, end, obstacles) {
1063
+ let score = 0;
1064
+ const crossingPenalty = 1e3;
1065
+ const lengthWeight = 0.1;
1066
+ const path = [start, ...bendPoints, end];
1067
+ for (const obs of obstacles) {
1068
+ for (let i = 0; i < path.length - 1; i++) {
1069
+ const p1 = path[i];
1070
+ const p2 = path[i + 1];
1071
+ if (p1 && p2 && segmentIntersectsRect(p1, p2, obs)) {
1072
+ score += crossingPenalty;
1073
+ }
1074
+ }
1075
+ }
1076
+ for (let i = 0; i < path.length - 1; i++) {
1077
+ const p1 = path[i];
1078
+ const p2 = path[i + 1];
1079
+ if (p1 && p2) {
1080
+ score += distance(p1, p2) * lengthWeight;
1081
+ }
1082
+ }
1083
+ return score;
1084
+ }
1085
+ function findClearVerticalPath(x, startY, endY, obstacles) {
1086
+ const minY = Math.min(startY, endY);
1087
+ const maxY = Math.max(startY, endY);
1088
+ const margin = 10;
1089
+ for (const obs of obstacles) {
1090
+ const obsLeft = obs.x - margin;
1091
+ const obsRight = obs.x + obs.width + margin;
1092
+ const obsTop = obs.y;
1093
+ const obsBottom = obs.y + obs.height;
1094
+ if (x >= obsLeft && x <= obsRight) {
1095
+ if (obsBottom > minY && obsTop < maxY) {
1096
+ const spaceAbove = obsTop - minY;
1097
+ const spaceBelow = maxY - obsBottom;
1098
+ if (spaceBelow > spaceAbove && obsBottom + margin < maxY) {
1099
+ return obsBottom + margin;
1100
+ } else if (obsTop - margin > minY) {
1101
+ return obsTop - margin;
1102
+ }
1103
+ }
1104
+ }
1105
+ }
1106
+ return null;
1107
+ }
1108
+ var init_geometry_utils = __esm({
1109
+ "src/layout/edge-routing/geometry-utils.ts"() {
1110
+ "use strict";
1111
+ init_cjs_shims();
1112
+ }
1113
+ });
1114
+
1657
1115
  // src/types/bpmn-constants.ts
1658
1116
  var DEFAULT_SIZES, BPMN_ELEMENT_MAP, EVENT_DEFINITION_MAP, DEFAULT_ELK_OPTIONS, EVENT_TYPES, TASK_TYPES, GATEWAY_TYPES, SUBPROCESS_TYPES, GROUP_TYPE, ARTIFACT_TYPES_SET;
1659
1117
  var init_bpmn_constants = __esm({
@@ -2309,9 +1767,9 @@ var init_lane_arranger = __esm({
2309
1767
  laneHeaderWidth = 30;
2310
1768
  lanePadding = 0;
2311
1769
  // No extra padding - tight fit
2312
- laneExtraWidth = 50;
1770
+ laneExtraWidth = 130;
2313
1771
  // Extra width for each lane
2314
- laneExtraHeight = 80;
1772
+ laneExtraHeight = 120;
2315
1773
  // Extra height for each lane
2316
1774
  /**
2317
1775
  * Rearrange lanes within pools to stack vertically
@@ -2387,16 +1845,21 @@ var init_lane_arranger = __esm({
2387
1845
  }
2388
1846
  /**
2389
1847
  * Recursively build lane structure with positioned nodes
1848
+ * Uses ConstraintSolver for vertical stacking
2390
1849
  */
2391
1850
  buildLaneStructure(origChildren, layoutedNodes, nodeToLane, startY, maxRight) {
2392
1851
  const lanes = [];
2393
- let currentY = startY;
2394
1852
  const origLanes = origChildren.filter((c) => c.bpmn?.type === "lane");
2395
1853
  origLanes.sort((a, b) => {
2396
1854
  const partA = a.layoutOptions?.["elk.partitioning.partition"];
2397
1855
  const partB = b.layoutOptions?.["elk.partitioning.partition"];
2398
1856
  return (partA !== void 0 ? Number(partA) : 0) - (partB !== void 0 ? Number(partB) : 0);
2399
1857
  });
1858
+ if (origLanes.length === 0) {
1859
+ return { lanes: [], totalHeight: 0 };
1860
+ }
1861
+ const laneHeights = /* @__PURE__ */ new Map();
1862
+ const laneNodes = /* @__PURE__ */ new Map();
2400
1863
  for (const origLane of origLanes) {
2401
1864
  const hasNestedLanes = origLane.children?.some((c) => c.bpmn?.type === "lane");
2402
1865
  if (hasNestedLanes) {
@@ -2408,22 +1871,8 @@ var init_lane_arranger = __esm({
2408
1871
  0,
2409
1872
  nestedWidth
2410
1873
  );
2411
- for (const nestedLane of nested.lanes) {
2412
- nestedLane.width = nestedWidth;
2413
- }
2414
- const laneNode = {
2415
- id: origLane.id,
2416
- x: this.laneHeaderWidth,
2417
- y: currentY,
2418
- width: maxRight,
2419
- // Fill full width
2420
- height: nested.totalHeight,
2421
- // Tight fit
2422
- children: nested.lanes,
2423
- bpmn: origLane.bpmn
2424
- };
2425
- lanes.push(laneNode);
2426
- currentY += laneNode.height;
1874
+ laneHeights.set(origLane.id, nested.totalHeight);
1875
+ laneNodes.set(origLane.id, nested.lanes);
2427
1876
  } else {
2428
1877
  const nodesInLane = [];
2429
1878
  if (origLane.children) {
@@ -2440,23 +1889,39 @@ var init_lane_arranger = __esm({
2440
1889
  const contentHeight = nodesInLane.length > 0 ? maxY - minY : 50;
2441
1890
  const laneHeight = contentHeight + this.laneExtraHeight;
2442
1891
  const yOffset = nodesInLane.length > 0 ? this.laneExtraHeight / 2 - minY : 0;
1892
+ const xOffset = this.laneExtraWidth / 2;
2443
1893
  for (const node of nodesInLane) {
1894
+ node.x = (node.x ?? 0) + xOffset;
2444
1895
  node.y = (node.y ?? 0) + yOffset;
2445
1896
  }
2446
- const laneNode = {
2447
- id: origLane.id,
2448
- x: this.laneHeaderWidth,
2449
- y: currentY,
2450
- width: maxRight,
2451
- height: laneHeight,
2452
- children: nodesInLane,
2453
- bpmn: origLane.bpmn
2454
- };
2455
- lanes.push(laneNode);
2456
- currentY += laneHeight;
1897
+ laneHeights.set(origLane.id, laneHeight);
1898
+ laneNodes.set(origLane.id, nodesInLane);
2457
1899
  }
2458
1900
  }
2459
- return { lanes, totalHeight: currentY - startY };
1901
+ let currentY = startY;
1902
+ for (const origLane of origLanes) {
1903
+ const height = laneHeights.get(origLane.id) ?? 100;
1904
+ const children = laneNodes.get(origLane.id) ?? [];
1905
+ const hasNestedLanes = origLane.children?.some((c) => c.bpmn?.type === "lane");
1906
+ if (hasNestedLanes) {
1907
+ for (const nestedLane of children) {
1908
+ nestedLane.width = maxRight - this.laneHeaderWidth;
1909
+ }
1910
+ }
1911
+ const laneNode = {
1912
+ id: origLane.id,
1913
+ x: this.laneHeaderWidth,
1914
+ y: currentY,
1915
+ width: maxRight,
1916
+ height,
1917
+ children,
1918
+ bpmn: origLane.bpmn
1919
+ };
1920
+ lanes.push(laneNode);
1921
+ currentY += height;
1922
+ }
1923
+ const totalHeight = currentY - startY;
1924
+ return { lanes, totalHeight };
2460
1925
  }
2461
1926
  /**
2462
1927
  * Rearrange nested lanes within a parent lane
@@ -2566,18 +2031,55 @@ var init_lane_arranger = __esm({
2566
2031
  }
2567
2032
  const waypoints = [];
2568
2033
  waypoints.push({ x: startX, y: startY });
2569
- if (Math.abs(startY - endY) > 10) {
2570
- const midX = this.findClearMidX(
2571
- startX,
2572
- endX,
2573
- startY,
2574
- endY,
2575
- sourceId,
2576
- targetId,
2577
- nodePositions
2578
- );
2579
- waypoints.push({ x: midX, y: startY });
2580
- waypoints.push({ x: midX, y: endY });
2034
+ const obstaclesInPath = this.getObstaclesInPath(
2035
+ startX,
2036
+ startY,
2037
+ endX,
2038
+ endY,
2039
+ sourceId,
2040
+ targetId,
2041
+ nodePositions
2042
+ );
2043
+ if (Math.abs(startY - endY) > 10 || obstaclesInPath.length > 0) {
2044
+ if (obstaclesInPath.length > 0 && Math.abs(startY - endY) <= 10) {
2045
+ const routePoints = this.routeAroundObstacles(
2046
+ startX,
2047
+ startY,
2048
+ endX,
2049
+ endY,
2050
+ obstaclesInPath,
2051
+ nodePositions
2052
+ );
2053
+ for (const pt of routePoints) {
2054
+ waypoints.push(pt);
2055
+ }
2056
+ } else {
2057
+ const midX = this.findClearMidX(
2058
+ startX,
2059
+ endX,
2060
+ startY,
2061
+ endY,
2062
+ sourceId,
2063
+ targetId,
2064
+ nodePositions
2065
+ );
2066
+ if (midX !== null) {
2067
+ waypoints.push({ x: midX, y: startY });
2068
+ waypoints.push({ x: midX, y: endY });
2069
+ } else {
2070
+ const routePoints = this.routeAroundObstacles(
2071
+ startX,
2072
+ startY,
2073
+ endX,
2074
+ endY,
2075
+ obstaclesInPath,
2076
+ nodePositions
2077
+ );
2078
+ for (const pt of routePoints) {
2079
+ waypoints.push(pt);
2080
+ }
2081
+ }
2082
+ }
2581
2083
  }
2582
2084
  waypoints.push({ x: endX, y: endY });
2583
2085
  if (isDebugEnabled()) {
@@ -2593,12 +2095,88 @@ var init_lane_arranger = __esm({
2593
2095
  }
2594
2096
  }
2595
2097
  }
2098
+ /**
2099
+ * Get obstacles in the direct path from source to target.
2100
+ * This handles the case where source and target are at similar Y positions
2101
+ * but there are nodes in between.
2102
+ */
2103
+ getObstaclesInPath(startX, startY, endX, endY, sourceId, targetId, nodePositions) {
2104
+ const minX = Math.min(startX, endX);
2105
+ const maxX = Math.max(startX, endX);
2106
+ const minY = Math.min(startY, endY);
2107
+ const maxY = Math.max(startY, endY);
2108
+ const obstacles = [];
2109
+ const flowNodePatterns = [
2110
+ /^task_/,
2111
+ /^gateway_/,
2112
+ /^start_/,
2113
+ /^end_/,
2114
+ /^subprocess_/,
2115
+ /^call_/,
2116
+ /^intermediate_/,
2117
+ /^event_/,
2118
+ /^catch_/
2119
+ ];
2120
+ for (const [nodeId, pos] of nodePositions) {
2121
+ if (nodeId === sourceId || nodeId === targetId) continue;
2122
+ if (nodeId.startsWith("lane_")) continue;
2123
+ const isFlowNode = flowNodePatterns.some((pattern) => pattern.test(nodeId));
2124
+ if (!isFlowNode) continue;
2125
+ const nodeLeft = pos.x;
2126
+ const nodeRight = pos.x + pos.width;
2127
+ const nodeTop = pos.y;
2128
+ const nodeBottom = pos.y + pos.height;
2129
+ const xOverlap = nodeRight > minX && nodeLeft < maxX;
2130
+ if (xOverlap) {
2131
+ if (nodeTop <= maxY && nodeBottom >= minY) {
2132
+ if (isDebugEnabled()) {
2133
+ console.log(`[BPMN] getObstaclesInPath: found obstacle ${nodeId} at x=[${nodeLeft}, ${nodeRight}], y=[${nodeTop}, ${nodeBottom}]`);
2134
+ }
2135
+ obstacles.push({ id: nodeId, ...pos });
2136
+ }
2137
+ }
2138
+ }
2139
+ obstacles.sort((a, b) => a.x - b.x);
2140
+ return obstacles;
2141
+ }
2142
+ /**
2143
+ * Route around obstacles when source and target are at similar Y levels.
2144
+ * Decides whether to go above or below based on available space.
2145
+ */
2146
+ routeAroundObstacles(startX, startY, endX, endY, obstacles, nodePositions) {
2147
+ const margin = 20;
2148
+ const points = [];
2149
+ let minObsY = Infinity;
2150
+ let maxObsY = -Infinity;
2151
+ let minObsX = Infinity;
2152
+ let maxObsX = -Infinity;
2153
+ for (const obs of obstacles) {
2154
+ minObsY = Math.min(minObsY, obs.y);
2155
+ maxObsY = Math.max(maxObsY, obs.y + obs.height);
2156
+ minObsX = Math.min(minObsX, obs.x);
2157
+ maxObsX = Math.max(maxObsX, obs.x + obs.width);
2158
+ }
2159
+ const spaceAbove = minObsY - margin;
2160
+ const spaceBelow = maxObsY + margin;
2161
+ const obsCenterY = (minObsY + maxObsY) / 2;
2162
+ const goAbove = startY < obsCenterY || spaceAbove > 0;
2163
+ const routeY = goAbove ? minObsY - margin : maxObsY + margin;
2164
+ if (isDebugEnabled()) {
2165
+ console.log(`[BPMN] routeAroundObstacles: goAbove=${goAbove}, routeY=${routeY}, minObsX=${minObsX}, maxObsX=${maxObsX}`);
2166
+ }
2167
+ points.push({ x: startX, y: routeY });
2168
+ points.push({ x: maxObsX + margin, y: routeY });
2169
+ points.push({ x: maxObsX + margin, y: endY });
2170
+ return points;
2171
+ }
2596
2172
  /**
2597
2173
  * Find a clear X position for vertical edge segment that avoids obstacles.
2598
2174
  * Checks all three segments of the L-shaped path:
2599
2175
  * 1. Horizontal from (startX, startY) to (midX, startY)
2600
2176
  * 2. Vertical from (midX, startY) to (midX, endY)
2601
2177
  * 3. Horizontal from (midX, endY) to (endX, endY)
2178
+ *
2179
+ * Returns null if no valid route can be found (caller should use routeAroundObstacles instead)
2602
2180
  */
2603
2181
  findClearMidX(startX, endX, startY, endY, sourceId, targetId, nodePositions) {
2604
2182
  const margin = 15;
@@ -2609,9 +2187,23 @@ var init_lane_arranger = __esm({
2609
2187
  if (isDebugEnabled()) {
2610
2188
  console.log(`[BPMN] findClearMidX: startX=${startX}, endX=${endX}, startY=${startY}, endY=${endY}`);
2611
2189
  }
2190
+ const flowNodePatterns = [
2191
+ /^task_/,
2192
+ /^gateway_/,
2193
+ /^start_/,
2194
+ /^end_/,
2195
+ /^subprocess_/,
2196
+ /^call_/,
2197
+ /^intermediate_/,
2198
+ /^event_/,
2199
+ /^catch_/
2200
+ ];
2612
2201
  const allObstacles = [];
2613
2202
  for (const [nodeId, pos] of nodePositions) {
2614
2203
  if (nodeId === sourceId || nodeId === targetId) continue;
2204
+ if (nodeId.startsWith("lane_")) continue;
2205
+ const isFlowNode = flowNodePatterns.some((pattern) => pattern.test(nodeId));
2206
+ if (!isFlowNode) continue;
2615
2207
  const nodeLeft = pos.x;
2616
2208
  const nodeRight = pos.x + pos.width;
2617
2209
  const nodeTop = pos.y;
@@ -2692,14 +2284,469 @@ var init_lane_arranger = __esm({
2692
2284
  return rightMost;
2693
2285
  }
2694
2286
  if (isDebugEnabled()) {
2695
- console.log(`[BPMN] findClearMidX: no valid route found, using midpoint ${simpleMidX}`);
2287
+ console.log(`[BPMN] findClearMidX: no valid route found, returning null`);
2288
+ }
2289
+ return null;
2290
+ }
2291
+ };
2292
+ }
2293
+ });
2294
+
2295
+ // src/layout/constraint/constraint-solver.ts
2296
+ var kiwi, DEFAULT_OPTIONS, ConstraintSolver;
2297
+ var init_constraint_solver = __esm({
2298
+ "src/layout/constraint/constraint-solver.ts"() {
2299
+ "use strict";
2300
+ init_cjs_shims();
2301
+ kiwi = __toESM(require("kiwi.js"));
2302
+ DEFAULT_OPTIONS = {
2303
+ defaultStrength: "strong",
2304
+ debug: false
2305
+ };
2306
+ ConstraintSolver = class {
2307
+ solver = null;
2308
+ variables;
2309
+ pendingConstraints;
2310
+ kiwiConstraints;
2311
+ options;
2312
+ solved = false;
2313
+ constructor(options) {
2314
+ this.variables = /* @__PURE__ */ new Map();
2315
+ this.pendingConstraints = [];
2316
+ this.kiwiConstraints = [];
2317
+ this.options = { ...DEFAULT_OPTIONS, ...options };
2318
+ }
2319
+ /**
2320
+ * Add a node to the solver
2321
+ * Note: Nodes are stored but not added to solver until solve() is called
2322
+ */
2323
+ addNode(id, initialX, initialY, width, height) {
2324
+ if (this.variables.has(id)) {
2325
+ if (this.options.debug) {
2326
+ console.log(`[Constraint] Node ${id} already exists, skipping`);
2327
+ }
2328
+ return;
2329
+ }
2330
+ const x = new kiwi.Variable(`${id}_x`);
2331
+ const y = new kiwi.Variable(`${id}_y`);
2332
+ this.variables.set(id, { x, y, width, height, initialX, initialY });
2333
+ if (this.options.debug) {
2334
+ console.log(`[Constraint] Added node ${id} at (${initialX}, ${initialY})`);
2335
+ }
2336
+ }
2337
+ /**
2338
+ * Add a node from bounds
2339
+ */
2340
+ addNodeFromBounds(id, bounds) {
2341
+ this.addNode(id, bounds.x, bounds.y, bounds.width, bounds.height);
2342
+ }
2343
+ /**
2344
+ * Add multiple nodes from a map
2345
+ */
2346
+ addNodesFromMap(nodes) {
2347
+ for (const [id, bounds] of nodes) {
2348
+ this.addNodeFromBounds(id, bounds);
2349
+ }
2350
+ }
2351
+ /**
2352
+ * Add a constraint
2353
+ * Note: Constraints are stored and applied when solve() is called
2354
+ */
2355
+ addConstraint(constraint) {
2356
+ const nodeIds = this.getConstraintNodeIds(constraint);
2357
+ for (const id of nodeIds) {
2358
+ if (!this.variables.has(id)) {
2359
+ if (this.options.debug) {
2360
+ console.log(`[Constraint] Node ${id} not found for constraint`);
2361
+ }
2362
+ return false;
2363
+ }
2364
+ }
2365
+ if (constraint.type === "noOverlap") {
2366
+ if (this.options.debug) {
2367
+ console.log(`[Constraint] noOverlap constraint requires special handling`);
2368
+ }
2369
+ return false;
2370
+ }
2371
+ const strength = this.getStrength(constraint.strength);
2372
+ this.pendingConstraints.push({ constraint, strength });
2373
+ return true;
2374
+ }
2375
+ /**
2376
+ * Get node IDs referenced by a constraint
2377
+ */
2378
+ getConstraintNodeIds(constraint) {
2379
+ switch (constraint.type) {
2380
+ case "alignX":
2381
+ case "alignY":
2382
+ return constraint.nodes;
2383
+ case "leftOf":
2384
+ case "rightOf":
2385
+ case "above":
2386
+ case "below":
2387
+ return [constraint.node, constraint.reference];
2388
+ case "fixedPosition":
2389
+ return [constraint.node];
2390
+ case "inContainer":
2391
+ return [constraint.node, constraint.container];
2392
+ case "minDistance":
2393
+ return [constraint.node1, constraint.node2];
2394
+ case "noOverlap":
2395
+ return constraint.nodes;
2396
+ default:
2397
+ return [];
2398
+ }
2399
+ }
2400
+ /**
2401
+ * Add multiple constraints
2402
+ */
2403
+ addConstraints(constraints) {
2404
+ let successCount = 0;
2405
+ for (const constraint of constraints) {
2406
+ if (this.addConstraint(constraint)) {
2407
+ successCount++;
2408
+ }
2409
+ }
2410
+ return successCount;
2411
+ }
2412
+ /**
2413
+ * Build and solve the constraint system
2414
+ */
2415
+ solve() {
2416
+ if (this.solved) {
2417
+ const result2 = /* @__PURE__ */ new Map();
2418
+ for (const [id, vars] of this.variables) {
2419
+ result2.set(id, {
2420
+ x: vars.x.value(),
2421
+ y: vars.y.value()
2422
+ });
2423
+ }
2424
+ return result2;
2425
+ }
2426
+ this.solver = new kiwi.Solver();
2427
+ this.kiwiConstraints = [];
2428
+ for (const pending of this.pendingConstraints) {
2429
+ this.applyConstraint(pending.constraint, pending.strength);
2430
+ }
2431
+ for (const [_id, vars] of this.variables) {
2432
+ this.solver.addEditVariable(vars.x, kiwi.Strength.weak);
2433
+ this.solver.addEditVariable(vars.y, kiwi.Strength.weak);
2434
+ this.solver.suggestValue(vars.x, vars.initialX);
2435
+ this.solver.suggestValue(vars.y, vars.initialY);
2436
+ }
2437
+ try {
2438
+ this.solver.updateVariables();
2439
+ this.solved = true;
2440
+ } catch (error) {
2441
+ if (this.options.debug) {
2442
+ console.error(`[Constraint] Solver failed:`, error);
2443
+ }
2444
+ }
2445
+ const result = /* @__PURE__ */ new Map();
2446
+ for (const [id, vars] of this.variables) {
2447
+ result.set(id, {
2448
+ x: vars.x.value(),
2449
+ y: vars.y.value()
2450
+ });
2451
+ }
2452
+ return result;
2453
+ }
2454
+ /**
2455
+ * Apply a constraint to the solver
2456
+ */
2457
+ applyConstraint(constraint, strength) {
2458
+ try {
2459
+ switch (constraint.type) {
2460
+ case "alignX":
2461
+ return this.addAlignXConstraint(constraint, strength);
2462
+ case "alignY":
2463
+ return this.addAlignYConstraint(constraint, strength);
2464
+ case "leftOf":
2465
+ return this.addLeftOfConstraint(constraint, strength);
2466
+ case "rightOf":
2467
+ return this.addRightOfConstraint(constraint, strength);
2468
+ case "above":
2469
+ return this.addAboveConstraint(constraint, strength);
2470
+ case "below":
2471
+ return this.addBelowConstraint(constraint, strength);
2472
+ case "fixedPosition":
2473
+ return this.addFixedPositionConstraint(constraint, strength);
2474
+ case "inContainer":
2475
+ return this.addInContainerConstraint(constraint, strength);
2476
+ case "minDistance":
2477
+ return this.addMinDistanceConstraint(constraint, strength);
2478
+ default:
2479
+ return false;
2480
+ }
2481
+ } catch (error) {
2482
+ if (this.options.debug) {
2483
+ console.error(`[Constraint] Failed to apply constraint:`, error);
2484
+ }
2485
+ return false;
2486
+ }
2487
+ }
2488
+ /**
2489
+ * Get full bounds including width and height
2490
+ */
2491
+ solveWithBounds() {
2492
+ const positions = this.solve();
2493
+ const result = /* @__PURE__ */ new Map();
2494
+ for (const [id, pos] of positions) {
2495
+ const vars = this.variables.get(id);
2496
+ if (vars) {
2497
+ result.set(id, {
2498
+ x: pos.x,
2499
+ y: pos.y,
2500
+ width: vars.width,
2501
+ height: vars.height
2502
+ });
2503
+ }
2504
+ }
2505
+ return result;
2506
+ }
2507
+ /**
2508
+ * Clear all nodes and constraints
2509
+ */
2510
+ clear() {
2511
+ this.solver = null;
2512
+ this.variables.clear();
2513
+ this.pendingConstraints = [];
2514
+ this.kiwiConstraints = [];
2515
+ this.solved = false;
2516
+ }
2517
+ // ============================================================================
2518
+ // Private: Constraint Implementations
2519
+ // ============================================================================
2520
+ addAlignXConstraint(constraint, strength) {
2521
+ const nodes = constraint.nodes.map((id) => this.variables.get(id)).filter((v) => v !== void 0);
2522
+ if (nodes.length < 2) return false;
2523
+ const referenceX = nodes[0].x;
2524
+ for (let i = 1; i < nodes.length; i++) {
2525
+ const c = new kiwi.Constraint(
2526
+ new kiwi.Expression(nodes[i].x, [-1, referenceX]),
2527
+ kiwi.Operator.Eq,
2528
+ 0,
2529
+ strength
2530
+ );
2531
+ this.solver.addConstraint(c);
2532
+ this.kiwiConstraints.push(c);
2533
+ }
2534
+ return true;
2535
+ }
2536
+ addAlignYConstraint(constraint, strength) {
2537
+ const nodes = constraint.nodes.map((id) => this.variables.get(id)).filter((v) => v !== void 0);
2538
+ if (nodes.length < 2) return false;
2539
+ const referenceY = nodes[0].y;
2540
+ for (let i = 1; i < nodes.length; i++) {
2541
+ const c = new kiwi.Constraint(
2542
+ new kiwi.Expression(nodes[i].y, [-1, referenceY]),
2543
+ kiwi.Operator.Eq,
2544
+ 0,
2545
+ strength
2546
+ );
2547
+ this.solver.addConstraint(c);
2548
+ this.kiwiConstraints.push(c);
2549
+ }
2550
+ return true;
2551
+ }
2552
+ addLeftOfConstraint(constraint, strength) {
2553
+ const nodeVars = this.variables.get(constraint.node);
2554
+ const refVars = this.variables.get(constraint.reference);
2555
+ if (!nodeVars || !refVars) return false;
2556
+ const minSeparation = nodeVars.width + constraint.minGap;
2557
+ const c = new kiwi.Constraint(
2558
+ new kiwi.Expression(refVars.x, [-1, nodeVars.x], -minSeparation),
2559
+ kiwi.Operator.Ge,
2560
+ 0,
2561
+ strength
2562
+ );
2563
+ this.solver.addConstraint(c);
2564
+ this.kiwiConstraints.push(c);
2565
+ return true;
2566
+ }
2567
+ addRightOfConstraint(constraint, strength) {
2568
+ const nodeVars = this.variables.get(constraint.node);
2569
+ const refVars = this.variables.get(constraint.reference);
2570
+ if (!nodeVars || !refVars) return false;
2571
+ const minSeparation = refVars.width + constraint.minGap;
2572
+ const c = new kiwi.Constraint(
2573
+ new kiwi.Expression(nodeVars.x, [-1, refVars.x], -minSeparation),
2574
+ kiwi.Operator.Ge,
2575
+ 0,
2576
+ strength
2577
+ );
2578
+ this.solver.addConstraint(c);
2579
+ this.kiwiConstraints.push(c);
2580
+ return true;
2581
+ }
2582
+ addAboveConstraint(constraint, strength) {
2583
+ const nodeVars = this.variables.get(constraint.node);
2584
+ const refVars = this.variables.get(constraint.reference);
2585
+ if (!nodeVars || !refVars) return false;
2586
+ const minSeparation = nodeVars.height + constraint.minGap;
2587
+ const c = new kiwi.Constraint(
2588
+ new kiwi.Expression(refVars.y, [-1, nodeVars.y], -minSeparation),
2589
+ kiwi.Operator.Ge,
2590
+ 0,
2591
+ strength
2592
+ );
2593
+ this.solver.addConstraint(c);
2594
+ this.kiwiConstraints.push(c);
2595
+ return true;
2596
+ }
2597
+ addBelowConstraint(constraint, strength) {
2598
+ const nodeVars = this.variables.get(constraint.node);
2599
+ const refVars = this.variables.get(constraint.reference);
2600
+ if (!nodeVars || !refVars) return false;
2601
+ const minSeparation = refVars.height + constraint.minGap;
2602
+ const c = new kiwi.Constraint(
2603
+ new kiwi.Expression(nodeVars.y, [-1, refVars.y], -minSeparation),
2604
+ kiwi.Operator.Ge,
2605
+ 0,
2606
+ strength
2607
+ );
2608
+ this.solver.addConstraint(c);
2609
+ this.kiwiConstraints.push(c);
2610
+ return true;
2611
+ }
2612
+ addFixedPositionConstraint(constraint, strength) {
2613
+ const nodeVars = this.variables.get(constraint.node);
2614
+ if (!nodeVars) return false;
2615
+ if (constraint.x !== void 0) {
2616
+ const c = new kiwi.Constraint(
2617
+ new kiwi.Expression(nodeVars.x, -constraint.x),
2618
+ kiwi.Operator.Eq,
2619
+ 0,
2620
+ strength
2621
+ );
2622
+ this.solver.addConstraint(c);
2623
+ this.kiwiConstraints.push(c);
2624
+ }
2625
+ if (constraint.y !== void 0) {
2626
+ const c = new kiwi.Constraint(
2627
+ new kiwi.Expression(nodeVars.y, -constraint.y),
2628
+ kiwi.Operator.Eq,
2629
+ 0,
2630
+ strength
2631
+ );
2632
+ this.solver.addConstraint(c);
2633
+ this.kiwiConstraints.push(c);
2634
+ }
2635
+ return true;
2636
+ }
2637
+ addInContainerConstraint(constraint, strength) {
2638
+ const nodeVars = this.variables.get(constraint.node);
2639
+ const containerVars = this.variables.get(constraint.container);
2640
+ if (!nodeVars || !containerVars) return false;
2641
+ const padding = constraint.padding;
2642
+ const leftC = new kiwi.Constraint(
2643
+ new kiwi.Expression(nodeVars.x, [-1, containerVars.x], -padding),
2644
+ kiwi.Operator.Ge,
2645
+ 0,
2646
+ strength
2647
+ );
2648
+ this.solver.addConstraint(leftC);
2649
+ this.kiwiConstraints.push(leftC);
2650
+ const topC = new kiwi.Constraint(
2651
+ new kiwi.Expression(nodeVars.y, [-1, containerVars.y], -padding),
2652
+ kiwi.Operator.Ge,
2653
+ 0,
2654
+ strength
2655
+ );
2656
+ this.solver.addConstraint(topC);
2657
+ this.kiwiConstraints.push(topC);
2658
+ const rightC = new kiwi.Constraint(
2659
+ new kiwi.Expression(
2660
+ containerVars.x,
2661
+ [-1, nodeVars.x],
2662
+ containerVars.width - padding - nodeVars.width
2663
+ ),
2664
+ kiwi.Operator.Ge,
2665
+ 0,
2666
+ strength
2667
+ );
2668
+ this.solver.addConstraint(rightC);
2669
+ this.kiwiConstraints.push(rightC);
2670
+ const bottomC = new kiwi.Constraint(
2671
+ new kiwi.Expression(
2672
+ containerVars.y,
2673
+ [-1, nodeVars.y],
2674
+ containerVars.height - padding - nodeVars.height
2675
+ ),
2676
+ kiwi.Operator.Ge,
2677
+ 0,
2678
+ strength
2679
+ );
2680
+ this.solver.addConstraint(bottomC);
2681
+ this.kiwiConstraints.push(bottomC);
2682
+ return true;
2683
+ }
2684
+ addMinDistanceConstraint(constraint, strength) {
2685
+ const node1Vars = this.variables.get(constraint.node1);
2686
+ const node2Vars = this.variables.get(constraint.node2);
2687
+ if (!node1Vars || !node2Vars) return false;
2688
+ if (constraint.axis === "x") {
2689
+ const size = node1Vars.width;
2690
+ const c = new kiwi.Constraint(
2691
+ new kiwi.Expression(
2692
+ node2Vars.x,
2693
+ [-1, node1Vars.x],
2694
+ -(size + constraint.minDistance)
2695
+ ),
2696
+ kiwi.Operator.Ge,
2697
+ 0,
2698
+ strength
2699
+ );
2700
+ this.solver.addConstraint(c);
2701
+ this.kiwiConstraints.push(c);
2702
+ } else {
2703
+ const size = node1Vars.height;
2704
+ const c = new kiwi.Constraint(
2705
+ new kiwi.Expression(
2706
+ node2Vars.y,
2707
+ [-1, node1Vars.y],
2708
+ -(size + constraint.minDistance)
2709
+ ),
2710
+ kiwi.Operator.Ge,
2711
+ 0,
2712
+ strength
2713
+ );
2714
+ this.solver.addConstraint(c);
2715
+ this.kiwiConstraints.push(c);
2716
+ }
2717
+ return true;
2718
+ }
2719
+ // ============================================================================
2720
+ // Private: Helpers
2721
+ // ============================================================================
2722
+ getStrength(strength) {
2723
+ const s = strength ?? this.options.defaultStrength ?? "strong";
2724
+ switch (s) {
2725
+ case "required":
2726
+ return kiwi.Strength.required;
2727
+ case "strong":
2728
+ return kiwi.Strength.strong;
2729
+ case "medium":
2730
+ return kiwi.Strength.medium;
2731
+ case "weak":
2732
+ return kiwi.Strength.weak;
2733
+ default:
2734
+ return kiwi.Strength.strong;
2696
2735
  }
2697
- return simpleMidX;
2698
2736
  }
2699
2737
  };
2700
2738
  }
2701
2739
  });
2702
2740
 
2741
+ // src/layout/constraint/index.ts
2742
+ var init_constraint = __esm({
2743
+ "src/layout/constraint/index.ts"() {
2744
+ "use strict";
2745
+ init_cjs_shims();
2746
+ init_constraint_solver();
2747
+ }
2748
+ });
2749
+
2703
2750
  // src/layout/post-processing/pool-arranger.ts
2704
2751
  var PoolArranger;
2705
2752
  var init_pool_arranger = __esm({
@@ -2707,12 +2754,13 @@ var init_pool_arranger = __esm({
2707
2754
  "use strict";
2708
2755
  init_cjs_shims();
2709
2756
  init_artifact_positioner();
2757
+ init_constraint();
2710
2758
  PoolArranger = class {
2711
2759
  poolHeaderWidth = 55;
2712
2760
  poolPaddingX = 25;
2713
- poolPaddingY = 20;
2761
+ poolPaddingY = 40;
2714
2762
  minPoolHeight = 100;
2715
- poolExtraWidth = 50;
2763
+ poolExtraWidth = 140;
2716
2764
  poolExtraHeight = 80;
2717
2765
  /**
2718
2766
  * Rearrange pools within collaborations
@@ -2774,24 +2822,41 @@ var init_pool_arranger = __esm({
2774
2822
  maxPoolWidth = Math.max(maxPoolWidth, (pool.width ?? 680) + this.poolExtraWidth);
2775
2823
  }
2776
2824
  }
2777
- let currentY = 0;
2778
- const nodePositions = /* @__PURE__ */ new Map();
2825
+ const poolHeights = /* @__PURE__ */ new Map();
2779
2826
  for (const pool of pools) {
2780
2827
  const origPool = origPoolMap.get(pool.id);
2781
2828
  const isBlackBox = origPool?.bpmn?.isBlackBox === true;
2782
2829
  const hasLanes = origPool?.children?.some((c) => c.bpmn?.type === "lane");
2783
- pool.x = 0;
2784
- pool.y = currentY;
2830
+ pool.width = maxPoolWidth;
2785
2831
  if (isBlackBox) {
2786
- pool.width = maxPoolWidth;
2787
2832
  pool.height = 60;
2788
- } else if (hasLanes) {
2789
- pool.width = maxPoolWidth;
2790
- } else {
2791
- pool.width = maxPoolWidth;
2833
+ } else if (!hasLanes) {
2792
2834
  pool.height = (pool.height ?? 200) + this.poolExtraHeight;
2793
- this.offsetPoolChildren(pool, this.poolExtraHeight / 2);
2835
+ this.offsetPoolChildren(pool, this.poolExtraWidth / 2, this.poolExtraHeight / 2);
2794
2836
  }
2837
+ poolHeights.set(pool.id, pool.height ?? 200);
2838
+ }
2839
+ const solver = new ConstraintSolver();
2840
+ for (const pool of pools) {
2841
+ const height = poolHeights.get(pool.id) ?? 200;
2842
+ solver.addNode(pool.id, 0, 0, maxPoolWidth, height);
2843
+ }
2844
+ for (let i = 1; i < pools.length; i++) {
2845
+ solver.addConstraint({
2846
+ type: "below",
2847
+ node: pools[i].id,
2848
+ reference: pools[i - 1].id,
2849
+ minGap: 0,
2850
+ strength: "required"
2851
+ });
2852
+ }
2853
+ const positions = solver.solve();
2854
+ const nodePositions = /* @__PURE__ */ new Map();
2855
+ let totalHeight = 0;
2856
+ for (const pool of pools) {
2857
+ const pos = positions.get(pool.id);
2858
+ pool.x = 0;
2859
+ pool.y = pos?.y ?? totalHeight;
2795
2860
  nodePositions.set(pool.id, {
2796
2861
  x: pool.x,
2797
2862
  y: pool.y,
@@ -2799,10 +2864,10 @@ var init_pool_arranger = __esm({
2799
2864
  height: pool.height ?? 200
2800
2865
  });
2801
2866
  this.collectNodePositionsInPool(pool, pool.x, pool.y, nodePositions);
2802
- currentY += pool.height ?? 200;
2867
+ totalHeight = pool.y + (pool.height ?? 200);
2803
2868
  }
2804
2869
  collab.width = maxPoolWidth;
2805
- collab.height = currentY;
2870
+ collab.height = totalHeight;
2806
2871
  if (collab.edges && origCollab.edges) {
2807
2872
  this.recalculateMessageFlows(collab.edges, nodePositions, pools, origCollab.edges);
2808
2873
  }
@@ -2988,22 +3053,34 @@ var init_pool_arranger = __esm({
2988
3053
  /**
2989
3054
  * Offset all children within a pool
2990
3055
  */
2991
- offsetPoolChildren(pool, offsetY) {
3056
+ offsetPoolChildren(pool, offsetX, offsetY) {
2992
3057
  if (!pool.children) return;
2993
3058
  for (const child of pool.children) {
3059
+ if (child.x !== void 0) {
3060
+ child.x += offsetX;
3061
+ }
2994
3062
  if (child.y !== void 0) {
2995
3063
  child.y += offsetY;
2996
3064
  }
2997
- this.offsetPoolChildren(child, 0);
3065
+ this.offsetPoolChildren(child, 0, 0);
2998
3066
  }
2999
3067
  if (pool.edges) {
3000
3068
  for (const edge of pool.edges) {
3001
3069
  if (edge.sections) {
3002
3070
  for (const section of edge.sections) {
3003
- if (section.startPoint) section.startPoint.y += offsetY;
3004
- if (section.endPoint) section.endPoint.y += offsetY;
3071
+ if (section.startPoint) {
3072
+ section.startPoint.x += offsetX;
3073
+ section.startPoint.y += offsetY;
3074
+ }
3075
+ if (section.endPoint) {
3076
+ section.endPoint.x += offsetX;
3077
+ section.endPoint.y += offsetY;
3078
+ }
3005
3079
  if (section.bendPoints) {
3006
- for (const bp of section.bendPoints) bp.y += offsetY;
3080
+ for (const bp of section.bendPoints) {
3081
+ bp.x += offsetX;
3082
+ bp.y += offsetY;
3083
+ }
3007
3084
  }
3008
3085
  }
3009
3086
  }
@@ -3039,6 +3116,13 @@ var init_pool_arranger = __esm({
3039
3116
  }
3040
3117
  }
3041
3118
  }
3119
+ const blackboxPoolIds = /* @__PURE__ */ new Set();
3120
+ for (const pool of pools) {
3121
+ const isBlackbox = !pool.children || pool.children.length === 0 || (pool.height ?? 0) <= 80;
3122
+ if (isBlackbox) {
3123
+ blackboxPoolIds.add(pool.id);
3124
+ }
3125
+ }
3042
3126
  for (const edge of edges) {
3043
3127
  const sourceId = edge.sources?.[0];
3044
3128
  const targetId = edge.targets?.[0];
@@ -3053,7 +3137,7 @@ var init_pool_arranger = __esm({
3053
3137
  if (isSequenceFlow) {
3054
3138
  this.createSequenceFlowWaypoints(sourcePos, targetPos, waypoints);
3055
3139
  } else if (isMessageFlow) {
3056
- this.createMessageFlowWaypoints(sourcePos, targetPos, waypoints);
3140
+ this.createMessageFlowWaypoints(sourcePos, targetPos, waypoints, nodePositions, targetId, sourceId, blackboxPoolIds);
3057
3141
  } else if (isDataAssociation) {
3058
3142
  edge._absoluteCoords = true;
3059
3143
  edge.sections = [];
@@ -3136,28 +3220,203 @@ var init_pool_arranger = __esm({
3136
3220
  }
3137
3221
  /**
3138
3222
  * Create waypoints for message flows
3223
+ * Routes message flows to avoid being too close to other nodes in the target pool
3139
3224
  */
3140
- createMessageFlowWaypoints(sourcePos, targetPos, waypoints) {
3225
+ createMessageFlowWaypoints(sourcePos, targetPos, waypoints, nodePositions, targetId, sourceId, blackboxPoolIds) {
3141
3226
  let startX, startY, endX, endY;
3142
- if (sourcePos.y + sourcePos.height < targetPos.y) {
3227
+ const goingDown = sourcePos.y + sourcePos.height < targetPos.y;
3228
+ const isTargetBlackbox = targetId ? blackboxPoolIds?.has(targetId) ?? false : false;
3229
+ const isSourceBlackbox = sourceId ? blackboxPoolIds?.has(sourceId) ?? false : false;
3230
+ if (goingDown) {
3143
3231
  startX = sourcePos.x + sourcePos.width / 2;
3144
3232
  startY = sourcePos.y + sourcePos.height;
3145
- endX = targetPos.x + targetPos.width / 2;
3233
+ if (isTargetBlackbox) {
3234
+ const targetLeft = targetPos.x;
3235
+ const targetRight = targetPos.x + targetPos.width;
3236
+ if (startX >= targetLeft + 20 && startX <= targetRight - 20) {
3237
+ endX = startX;
3238
+ } else {
3239
+ endX = targetPos.x + targetPos.width / 2;
3240
+ }
3241
+ } else {
3242
+ endX = targetPos.x + targetPos.width / 2;
3243
+ }
3146
3244
  endY = targetPos.y;
3147
3245
  } else {
3148
3246
  startX = sourcePos.x + sourcePos.width / 2;
3149
3247
  startY = sourcePos.y;
3150
- endX = targetPos.x + targetPos.width / 2;
3248
+ if (isTargetBlackbox) {
3249
+ const targetLeft = targetPos.x;
3250
+ const targetRight = targetPos.x + targetPos.width;
3251
+ if (startX >= targetLeft + 20 && startX <= targetRight - 20) {
3252
+ endX = startX;
3253
+ } else {
3254
+ endX = targetPos.x + targetPos.width / 2;
3255
+ }
3256
+ } else {
3257
+ endX = targetPos.x + targetPos.width / 2;
3258
+ }
3151
3259
  endY = targetPos.y + targetPos.height;
3152
3260
  }
3153
- waypoints.push({ x: startX, y: startY });
3261
+ if (isSourceBlackbox) {
3262
+ const sourceLeft = sourcePos.x;
3263
+ const sourceRight = sourcePos.x + sourcePos.width;
3264
+ if (endX >= sourceLeft + 20 && endX <= sourceRight - 20) {
3265
+ startX = endX;
3266
+ }
3267
+ }
3268
+ const containerPatterns = [/^pool_/, /^participant_/, /^lane_/, /^process_/, /^collaboration_/];
3269
+ const minClearance = 25;
3270
+ const sourcePoolYMin = sourcePos.y - 50;
3271
+ const sourcePoolYMax = sourcePos.y + sourcePos.height + 50;
3272
+ const targetPoolYMin = targetPos.y - 50;
3273
+ const targetPoolYMax = targetPos.y + targetPos.height + 50;
3274
+ const fullVerticalMinY = Math.min(startY, endY);
3275
+ const fullVerticalMaxY = Math.max(startY, endY);
3276
+ const nodesBlockingDirectPath = [];
3277
+ for (const [nodeId, bounds] of nodePositions) {
3278
+ if (nodeId === targetId || nodeId === sourceId) continue;
3279
+ const isContainer = containerPatterns.some((pattern) => pattern.test(nodeId));
3280
+ if (isContainer) continue;
3281
+ const nodeLeft = bounds.x;
3282
+ const nodeRight = bounds.x + bounds.width;
3283
+ const nodeTop = bounds.y;
3284
+ const nodeBottom = bounds.y + bounds.height;
3285
+ const inSourcePool = nodeTop >= sourcePoolYMin && nodeBottom <= sourcePoolYMax;
3286
+ const inTargetPool = nodeTop >= targetPoolYMin && nodeBottom <= targetPoolYMax;
3287
+ if (inSourcePool || inTargetPool) continue;
3288
+ const overlapsVerticalLine = startX >= nodeLeft - minClearance && startX <= nodeRight + minClearance;
3289
+ const nodeInVerticalRange = nodeBottom > fullVerticalMinY && nodeTop < fullVerticalMaxY;
3290
+ if (overlapsVerticalLine && nodeInVerticalRange) {
3291
+ nodesBlockingDirectPath.push({ id: nodeId, bounds });
3292
+ }
3293
+ }
3294
+ let finalRouteX = startX;
3295
+ if (nodesBlockingDirectPath.length > 0) {
3296
+ const allObstacles = [];
3297
+ for (const { bounds } of nodesBlockingDirectPath) {
3298
+ allObstacles.push({
3299
+ left: bounds.x - minClearance,
3300
+ right: bounds.x + bounds.width + minClearance
3301
+ });
3302
+ }
3303
+ for (const [nodeId, bounds] of nodePositions) {
3304
+ if (nodeId === targetId || nodeId === sourceId) continue;
3305
+ const isContainer = containerPatterns.some((pattern) => pattern.test(nodeId));
3306
+ if (isContainer) continue;
3307
+ const nodeTop = bounds.y;
3308
+ const nodeBottom = bounds.y + bounds.height;
3309
+ const inSourcePool = nodeTop >= sourcePoolYMin && nodeBottom <= sourcePoolYMax;
3310
+ const inTargetPool = nodeTop >= targetPoolYMin && nodeBottom <= targetPoolYMax;
3311
+ if (inSourcePool || inTargetPool) continue;
3312
+ const nodeInVerticalRange = nodeBottom > fullVerticalMinY && nodeTop < fullVerticalMaxY;
3313
+ if (nodeInVerticalRange) {
3314
+ allObstacles.push({
3315
+ left: bounds.x - minClearance,
3316
+ right: bounds.x + bounds.width + minClearance
3317
+ });
3318
+ }
3319
+ }
3320
+ allObstacles.sort((a, b) => a.left - b.left);
3321
+ const mergedObstacles = [];
3322
+ for (const obs of allObstacles) {
3323
+ if (mergedObstacles.length === 0) {
3324
+ mergedObstacles.push({ ...obs });
3325
+ } else {
3326
+ const last = mergedObstacles[mergedObstacles.length - 1];
3327
+ if (obs.left <= last.right) {
3328
+ last.right = Math.max(last.right, obs.right);
3329
+ } else {
3330
+ mergedObstacles.push({ ...obs });
3331
+ }
3332
+ }
3333
+ }
3334
+ let bestRouteX = startX;
3335
+ let bestShift = Infinity;
3336
+ if (mergedObstacles.length > 0) {
3337
+ const leftMost = mergedObstacles[0].left;
3338
+ if (leftMost > 20) {
3339
+ const shiftNeeded = Math.abs(startX - leftMost);
3340
+ if (shiftNeeded < bestShift) {
3341
+ bestShift = shiftNeeded;
3342
+ bestRouteX = leftMost;
3343
+ }
3344
+ }
3345
+ }
3346
+ if (mergedObstacles.length > 0) {
3347
+ const rightMost = mergedObstacles[mergedObstacles.length - 1].right;
3348
+ const shiftNeeded = Math.abs(startX - rightMost);
3349
+ if (shiftNeeded < bestShift) {
3350
+ bestShift = shiftNeeded;
3351
+ bestRouteX = rightMost;
3352
+ }
3353
+ }
3354
+ for (let i = 0; i < mergedObstacles.length - 1; i++) {
3355
+ const gapLeft = mergedObstacles[i].right;
3356
+ const gapRight = mergedObstacles[i + 1].left;
3357
+ const gapWidth = gapRight - gapLeft;
3358
+ if (gapWidth >= 10) {
3359
+ const gapCenter = (gapLeft + gapRight) / 2;
3360
+ const shiftNeeded = Math.abs(startX - gapCenter);
3361
+ if (shiftNeeded < bestShift) {
3362
+ bestShift = shiftNeeded;
3363
+ bestRouteX = gapCenter;
3364
+ }
3365
+ }
3366
+ }
3367
+ finalRouteX = bestRouteX;
3368
+ }
3154
3369
  const horizontalDist = Math.abs(startX - endX);
3155
- if (horizontalDist > 5) {
3156
- const routeY = horizontalDist > 200 ? endY - 20 : (startY + endY) / 2;
3370
+ waypoints.push({ x: startX, y: startY });
3371
+ const needsObstacleAvoidance = Math.abs(finalRouteX - startX) > 5 && horizontalDist <= 5;
3372
+ if (needsObstacleAvoidance) {
3373
+ waypoints.push({ x: finalRouteX, y: startY });
3374
+ waypoints.push({ x: finalRouteX, y: endY });
3375
+ if (Math.abs(finalRouteX - endX) > 5) {
3376
+ waypoints.push({ x: endX, y: endY });
3377
+ }
3378
+ } else if (horizontalDist > 5) {
3379
+ let routeY = horizontalDist > 200 ? endY - 20 : (startY + endY) / 2;
3380
+ if (targetId) {
3381
+ const minX = Math.min(startX, endX);
3382
+ const maxX = Math.max(startX, endX);
3383
+ for (const [nodeId, bounds] of nodePositions) {
3384
+ if (nodeId === targetId || nodeId === sourceId) continue;
3385
+ const nodeLeft = bounds.x;
3386
+ const nodeRight = bounds.x + bounds.width;
3387
+ const nodeTop = bounds.y;
3388
+ const nodeBottom = bounds.y + bounds.height;
3389
+ const overlapsHorizontally = nodeRight > minX && nodeLeft < maxX;
3390
+ if (overlapsHorizontally) {
3391
+ if (goingDown) {
3392
+ if (routeY >= nodeTop && routeY <= nodeBottom) {
3393
+ routeY = Math.min(routeY, nodeTop - minClearance);
3394
+ } else if (nodeTop > startY && nodeTop < endY) {
3395
+ routeY = Math.min(routeY, nodeTop - minClearance);
3396
+ }
3397
+ } else {
3398
+ if (routeY >= nodeTop && routeY <= nodeBottom) {
3399
+ routeY = Math.min(routeY, nodeTop - minClearance);
3400
+ } else if (nodeTop < startY && nodeBottom > endY) {
3401
+ routeY = Math.min(routeY, nodeTop - minClearance);
3402
+ }
3403
+ }
3404
+ }
3405
+ }
3406
+ if (goingDown) {
3407
+ routeY = Math.max(routeY, startY + 10);
3408
+ routeY = Math.min(routeY, endY - 10);
3409
+ } else {
3410
+ routeY = Math.max(routeY, endY + 10);
3411
+ routeY = Math.min(routeY, startY - 10);
3412
+ }
3413
+ }
3157
3414
  waypoints.push({ x: startX, y: routeY });
3158
3415
  waypoints.push({ x: endX, y: routeY });
3416
+ waypoints.push({ x: endX, y: endY });
3417
+ } else {
3418
+ waypoints.push({ x: endX, y: endY });
3159
3419
  }
3160
- waypoints.push({ x: endX, y: endY });
3161
3420
  }
3162
3421
  /**
3163
3422
  * Find the task associated with an artifact
@@ -3190,286 +3449,263 @@ var init_pool_arranger = __esm({
3190
3449
  }
3191
3450
  });
3192
3451
 
3193
- // src/layout/post-processing/gateway-edge-adjuster.ts
3194
- var GatewayEdgeAdjuster;
3195
- var init_gateway_edge_adjuster = __esm({
3196
- "src/layout/post-processing/gateway-edge-adjuster.ts"() {
3452
+ // src/layout/post-processing/compactor.ts
3453
+ var DEFAULT_OPTIONS2, Compactor;
3454
+ var init_compactor = __esm({
3455
+ "src/layout/post-processing/compactor.ts"() {
3197
3456
  "use strict";
3198
3457
  init_cjs_shims();
3199
- init_debug();
3200
- init_geometry_utils();
3201
- GatewayEdgeAdjuster = class {
3458
+ DEFAULT_OPTIONS2 = {
3459
+ minHorizontalGap: 60,
3460
+ minVerticalGap: 40,
3461
+ compactHorizontal: true,
3462
+ compactVertical: true,
3463
+ considerDependencies: true
3464
+ };
3465
+ Compactor = class {
3466
+ options;
3467
+ constructor(options) {
3468
+ this.options = { ...DEFAULT_OPTIONS2, ...options };
3469
+ }
3202
3470
  /**
3203
- * Adjust edges that connect to gateways
3204
- * @param layoutedGraph - The ELK layouted graph (contains edge coordinates)
3205
- * @param originalGraph - The original BPMN graph (contains bpmn metadata)
3471
+ * Compact the graph layout
3206
3472
  */
3207
- adjust(layoutedGraph, originalGraph) {
3208
- const gateways = /* @__PURE__ */ new Map();
3209
- this.collectGateways(layoutedGraph, originalGraph, gateways, 0, 0);
3210
- if (gateways.size === 0) return;
3211
- if (isDebugEnabled()) {
3212
- console.log(`[BPMN] GatewayEdgeAdjuster: Found ${gateways.size} gateways`);
3213
- for (const [id, info] of gateways) {
3214
- console.log(`[BPMN] - ${id}: bounds=(${info.bounds.x},${info.bounds.y},${info.bounds.width},${info.bounds.height})`);
3215
- }
3216
- }
3217
- this.processEdges(layoutedGraph, originalGraph, gateways, 0, 0);
3218
- }
3219
- /**
3220
- * Collect all gateway nodes and calculate their diamond corners
3221
- * Uses originalGraph for BPMN type info and layoutedGraph for positions
3222
- */
3223
- collectGateways(layoutedNode, originalNode, gateways, offsetX, offsetY) {
3224
- const bpmn = originalNode.bpmn;
3225
- if (this.isGateway(bpmn?.type)) {
3226
- const x = offsetX + (layoutedNode.x ?? 0);
3227
- const y = offsetY + (layoutedNode.y ?? 0);
3228
- const width = layoutedNode.width ?? 50;
3229
- const height = layoutedNode.height ?? 50;
3230
- gateways.set(layoutedNode.id, {
3231
- id: layoutedNode.id,
3232
- bounds: { x, y, width, height },
3233
- corners: {
3234
- left: { x, y: y + height / 2 },
3235
- top: { x: x + width / 2, y },
3236
- right: { x: x + width, y: y + height / 2 },
3237
- bottom: { x: x + width / 2, y: y + height }
3238
- }
3239
- });
3240
- }
3241
- if (layoutedNode.children && originalNode.children) {
3242
- const isContainer = this.isContainer(bpmn?.type);
3243
- const newOffsetX = isContainer ? offsetX + (layoutedNode.x ?? 0) : offsetX;
3244
- const newOffsetY = isContainer ? offsetY + (layoutedNode.y ?? 0) : offsetY;
3245
- const originalChildMap = /* @__PURE__ */ new Map();
3246
- for (const child of originalNode.children) {
3247
- const childNode = child;
3248
- if (childNode.id) {
3249
- originalChildMap.set(childNode.id, childNode);
3250
- }
3473
+ compact(graph) {
3474
+ if (!graph.children || graph.children.length === 0) return;
3475
+ const nodeBounds = this.collectNodeBounds(graph);
3476
+ if (nodeBounds.length === 0) return;
3477
+ const edges = this.collectEdges(graph);
3478
+ if (this.options.considerDependencies && edges.length > 0) {
3479
+ this.compactWithDependencies(graph, nodeBounds, edges);
3480
+ } else {
3481
+ if (this.options.compactHorizontal) {
3482
+ this.compactHorizontalSimple(nodeBounds);
3251
3483
  }
3252
- for (const layoutedChild of layoutedNode.children) {
3253
- const originalChild = originalChildMap.get(layoutedChild.id);
3254
- if (originalChild) {
3255
- this.collectGateways(layoutedChild, originalChild, gateways, newOffsetX, newOffsetY);
3256
- }
3484
+ if (this.options.compactVertical) {
3485
+ this.compactVerticalSimple(nodeBounds);
3486
+ }
3487
+ this.applyBounds(graph, nodeBounds);
3488
+ }
3489
+ for (const child of graph.children) {
3490
+ if (child.children && child.children.length > 0) {
3491
+ this.compact(child);
3257
3492
  }
3258
3493
  }
3259
3494
  }
3260
3495
  /**
3261
- * Process all edges and adjust endpoints connecting to gateways
3496
+ * Collect node bounds from graph
3262
3497
  */
3263
- processEdges(layoutedNode, originalNode, gateways, offsetX, offsetY) {
3264
- const bpmn = originalNode.bpmn;
3265
- if (layoutedNode.edges) {
3266
- for (const edge of layoutedNode.edges) {
3267
- if (edge.sections && edge.sections.length > 0) {
3268
- this.adjustEdgeEndpoints(edge, gateways, offsetX, offsetY);
3269
- }
3498
+ collectNodeBounds(graph) {
3499
+ const bounds = [];
3500
+ if (!graph.children) return bounds;
3501
+ for (const child of graph.children) {
3502
+ const node = child;
3503
+ if (node.x !== void 0 && node.y !== void 0) {
3504
+ bounds.push({
3505
+ id: node.id,
3506
+ x: node.x,
3507
+ y: node.y,
3508
+ width: node.width ?? 0,
3509
+ height: node.height ?? 0
3510
+ });
3270
3511
  }
3271
3512
  }
3272
- if (layoutedNode.children && originalNode.children) {
3273
- const isContainer = this.isContainer(bpmn?.type);
3274
- const newOffsetX = isContainer ? offsetX + (layoutedNode.x ?? 0) : offsetX;
3275
- const newOffsetY = isContainer ? offsetY + (layoutedNode.y ?? 0) : offsetY;
3276
- const originalChildMap = /* @__PURE__ */ new Map();
3277
- for (const child of originalNode.children) {
3278
- const childNode = child;
3279
- if (childNode.id) {
3280
- originalChildMap.set(childNode.id, childNode);
3513
+ return bounds;
3514
+ }
3515
+ /**
3516
+ * Collect edges from graph
3517
+ */
3518
+ collectEdges(graph) {
3519
+ const edges = [];
3520
+ const collectFromNode = (node) => {
3521
+ if (node.edges) {
3522
+ for (const edge of node.edges) {
3523
+ const elkEdge = edge;
3524
+ if (elkEdge.sources && elkEdge.targets) {
3525
+ for (const source of elkEdge.sources) {
3526
+ for (const target of elkEdge.targets) {
3527
+ edges.push({ source, target });
3528
+ }
3529
+ }
3530
+ }
3281
3531
  }
3282
3532
  }
3283
- for (const layoutedChild of layoutedNode.children) {
3284
- const originalChild = originalChildMap.get(layoutedChild.id);
3285
- if (originalChild) {
3286
- this.processEdges(layoutedChild, originalChild, gateways, newOffsetX, newOffsetY);
3533
+ if (node.children) {
3534
+ for (const child of node.children) {
3535
+ collectFromNode(child);
3287
3536
  }
3288
3537
  }
3289
- }
3538
+ };
3539
+ collectFromNode(graph);
3540
+ return edges;
3290
3541
  }
3291
3542
  /**
3292
- * Adjust edge endpoints if they connect to a gateway
3543
+ * Simple horizontal compaction - move nodes left while maintaining minimum gap
3293
3544
  */
3294
- adjustEdgeEndpoints(edge, gateways, offsetX, offsetY) {
3295
- const section = edge.sections[0];
3296
- const sourceId = edge.sources?.[0];
3297
- const targetId = edge.targets?.[0];
3298
- if (isDebugEnabled()) {
3299
- console.log(`[BPMN] GatewayEdgeAdjuster: Processing edge ${edge.id}, offset=(${offsetX},${offsetY})`);
3300
- console.log(`[BPMN] sourceId=${sourceId}, targetId=${targetId}`);
3301
- console.log(`[BPMN] startPoint=(${section.startPoint.x},${section.startPoint.y}), endPoint=(${section.endPoint.x},${section.endPoint.y})`);
3302
- console.log(`[BPMN] available gateways: ${Array.from(gateways.keys()).join(", ")}`);
3303
- }
3304
- const sourceGateway = sourceId ? gateways.get(sourceId) : void 0;
3305
- if (sourceGateway) {
3306
- this.adjustStartPoint(section, sourceGateway, offsetX, offsetY);
3545
+ compactHorizontalSimple(nodes) {
3546
+ if (nodes.length === 0) return;
3547
+ nodes.sort((a, b) => a.x - b.x);
3548
+ const minX = nodes[0].x;
3549
+ for (let i = 1; i < nodes.length; i++) {
3550
+ const prev = nodes[i - 1];
3551
+ const curr = nodes[i];
3552
+ if (this.hasVerticalOverlap(prev, curr)) {
3553
+ const minAllowedX = prev.x + prev.width + this.options.minHorizontalGap;
3554
+ if (curr.x > minAllowedX) {
3555
+ curr.x = minAllowedX;
3556
+ }
3557
+ }
3307
3558
  }
3308
- const targetGateway = targetId ? gateways.get(targetId) : void 0;
3309
- if (targetGateway) {
3310
- if (isDebugEnabled()) {
3311
- console.log(`[BPMN] target gateway: ${targetId}, bounds=(${targetGateway.bounds.x},${targetGateway.bounds.y})`);
3312
- console.log(`[BPMN] left corner: (${targetGateway.corners.left.x},${targetGateway.corners.left.y})`);
3559
+ const newMinX = Math.min(...nodes.map((n) => n.x));
3560
+ const offsetX = minX - newMinX;
3561
+ if (offsetX > 0) {
3562
+ for (const node of nodes) {
3563
+ node.x += offsetX;
3313
3564
  }
3314
- this.adjustEndPoint(edge, section, targetGateway, offsetX, offsetY);
3315
3565
  }
3316
3566
  }
3317
3567
  /**
3318
- * Adjust the start point of an edge leaving a gateway
3568
+ * Simple vertical compaction - move nodes up while maintaining minimum gap
3319
3569
  */
3320
- adjustStartPoint(section, gateway, offsetX, offsetY) {
3321
- const startX = offsetX + section.startPoint.x;
3322
- const startY = offsetY + section.startPoint.y;
3323
- const corner = this.findClosestCorner(
3324
- { x: startX, y: startY },
3325
- gateway
3326
- );
3327
- section.startPoint = {
3328
- x: corner.x - offsetX,
3329
- y: corner.y - offsetY
3330
- };
3331
- if (section.bendPoints && section.bendPoints.length > 0) {
3332
- const firstBend = section.bendPoints[0];
3333
- const adjustedStart = section.startPoint;
3334
- if (Math.abs(offsetX + firstBend.x - corner.x) < 20) {
3335
- firstBend.x = adjustedStart.x;
3336
- } else if (Math.abs(offsetY + firstBend.y - corner.y) < 20) {
3337
- firstBend.y = adjustedStart.y;
3570
+ compactVerticalSimple(nodes) {
3571
+ if (nodes.length === 0) return;
3572
+ nodes.sort((a, b) => a.y - b.y);
3573
+ const minY = nodes[0].y;
3574
+ for (let i = 1; i < nodes.length; i++) {
3575
+ const prev = nodes[i - 1];
3576
+ const curr = nodes[i];
3577
+ if (this.hasHorizontalOverlap(prev, curr)) {
3578
+ const minAllowedY = prev.y + prev.height + this.options.minVerticalGap;
3579
+ if (curr.y > minAllowedY) {
3580
+ curr.y = minAllowedY;
3581
+ }
3582
+ }
3583
+ }
3584
+ const newMinY = Math.min(...nodes.map((n) => n.y));
3585
+ const offsetY = minY - newMinY;
3586
+ if (offsetY > 0) {
3587
+ for (const node of nodes) {
3588
+ node.y += offsetY;
3338
3589
  }
3339
3590
  }
3340
3591
  }
3341
3592
  /**
3342
- * Adjust the end point of an edge entering a gateway
3593
+ * Compact with dependency consideration using topological sort
3343
3594
  */
3344
- adjustEndPoint(edge, section, gateway, offsetX, offsetY) {
3345
- const endX = offsetX + section.endPoint.x;
3346
- const endY = offsetY + section.endPoint.y;
3347
- let prevPoint;
3348
- if (section.bendPoints && section.bendPoints.length > 0) {
3349
- const lastBend = section.bendPoints[section.bendPoints.length - 1];
3350
- prevPoint = { x: offsetX + lastBend.x, y: offsetY + lastBend.y };
3351
- } else {
3352
- prevPoint = { x: offsetX + section.startPoint.x, y: offsetY + section.startPoint.y };
3353
- }
3354
- const dx = endX - prevPoint.x;
3355
- const dy = endY - prevPoint.y;
3356
- let targetCorner;
3357
- if (Math.abs(dx) > Math.abs(dy)) {
3358
- targetCorner = dx > 0 ? gateway.corners.left : gateway.corners.right;
3359
- } else {
3360
- targetCorner = dy > 0 ? gateway.corners.top : gateway.corners.bottom;
3595
+ compactWithDependencies(graph, nodeBounds, edges) {
3596
+ const boundsMap = /* @__PURE__ */ new Map();
3597
+ for (const node of nodeBounds) {
3598
+ boundsMap.set(node.id, node);
3361
3599
  }
3362
- const distToCorner = Math.sqrt(
3363
- Math.pow(endX - targetCorner.x, 2) + Math.pow(endY - targetCorner.y, 2)
3364
- );
3365
- if (distToCorner < 30) {
3366
- const oldEndX = section.endPoint.x;
3367
- const oldEndY = section.endPoint.y;
3368
- section.endPoint = {
3369
- x: targetCorner.x - offsetX,
3370
- y: targetCorner.y - offsetY
3371
- };
3372
- if (section.bendPoints && section.bendPoints.length > 0) {
3373
- const lastBend = section.bendPoints[section.bendPoints.length - 1];
3374
- const newEnd = section.endPoint;
3375
- if (Math.abs(offsetY + lastBend.y - (offsetY + oldEndY)) < 5) {
3376
- lastBend.y = newEnd.y;
3377
- } else if (Math.abs(offsetX + lastBend.x - (offsetX + oldEndX)) < 5) {
3378
- lastBend.x = newEnd.x;
3600
+ const dependencies = /* @__PURE__ */ new Map();
3601
+ for (const edge of edges) {
3602
+ if (!dependencies.has(edge.target)) {
3603
+ dependencies.set(edge.target, /* @__PURE__ */ new Set());
3604
+ }
3605
+ dependencies.get(edge.target).add(edge.source);
3606
+ }
3607
+ const sorted = this.topologicalSort([...boundsMap.keys()], dependencies);
3608
+ if (this.options.compactHorizontal) {
3609
+ for (const nodeId of sorted) {
3610
+ const node = boundsMap.get(nodeId);
3611
+ if (!node) continue;
3612
+ const deps = dependencies.get(nodeId);
3613
+ if (!deps || deps.size === 0) continue;
3614
+ let maxRight = 0;
3615
+ for (const depId of deps) {
3616
+ const dep = boundsMap.get(depId);
3617
+ if (dep) {
3618
+ maxRight = Math.max(maxRight, dep.x + dep.width);
3619
+ }
3379
3620
  }
3380
- }
3381
- if (isDebugEnabled()) {
3382
- console.log(`[BPMN] GatewayEdgeAdjuster: Adjusted edge ${edge.id} endpoint from (${endX},${endY}) to (${targetCorner.x},${targetCorner.y})`);
3383
- }
3384
- } else {
3385
- const intersectionPoint = this.findDiamondIntersection(
3386
- prevPoint,
3387
- { x: endX, y: endY },
3388
- gateway
3389
- );
3390
- if (intersectionPoint) {
3391
- section.endPoint = {
3392
- x: intersectionPoint.x - offsetX,
3393
- y: intersectionPoint.y - offsetY
3394
- };
3395
- if (isDebugEnabled()) {
3396
- console.log(`[BPMN] GatewayEdgeAdjuster: Adjusted edge ${edge.id} endpoint to diamond intersection (${intersectionPoint.x},${intersectionPoint.y})`);
3621
+ const targetX = maxRight + this.options.minHorizontalGap;
3622
+ if (node.x > targetX) {
3623
+ node.x = targetX;
3397
3624
  }
3398
3625
  }
3399
3626
  }
3627
+ this.applyBounds(graph, nodeBounds);
3400
3628
  }
3401
3629
  /**
3402
- * Find the closest corner of a gateway to a given point
3630
+ * Apply bounds back to graph nodes
3403
3631
  */
3404
- findClosestCorner(point, gateway) {
3405
- const corners = [
3406
- gateway.corners.left,
3407
- gateway.corners.top,
3408
- gateway.corners.right,
3409
- gateway.corners.bottom
3410
- ];
3411
- let closest = corners[0];
3412
- let minDist = distance(point, closest);
3413
- for (let i = 1; i < corners.length; i++) {
3414
- const dist = distance(point, corners[i]);
3415
- if (dist < minDist) {
3416
- minDist = dist;
3417
- closest = corners[i];
3418
- }
3419
- }
3420
- return closest;
3421
- }
3422
- /**
3423
- * Find intersection point between a line segment and the diamond edges
3424
- */
3425
- findDiamondIntersection(from, to, gateway) {
3426
- const { corners } = gateway;
3427
- const edges = [
3428
- [corners.left, corners.top],
3429
- // top-left edge
3430
- [corners.top, corners.right],
3431
- // top-right edge
3432
- [corners.right, corners.bottom],
3433
- // bottom-right edge
3434
- [corners.bottom, corners.left]
3435
- // bottom-left edge
3436
- ];
3437
- for (const [p1, p2] of edges) {
3438
- const intersection = lineIntersection(from, to, p1, p2);
3439
- if (intersection) {
3440
- return intersection;
3632
+ applyBounds(graph, bounds) {
3633
+ const boundsMap = /* @__PURE__ */ new Map();
3634
+ for (const b of bounds) {
3635
+ boundsMap.set(b.id, b);
3636
+ }
3637
+ if (!graph.children) return;
3638
+ for (const child of graph.children) {
3639
+ const node = child;
3640
+ const b = boundsMap.get(node.id);
3641
+ if (b) {
3642
+ node.x = b.x;
3643
+ node.y = b.y;
3441
3644
  }
3442
3645
  }
3443
- return null;
3444
3646
  }
3445
3647
  /**
3446
- * Check if a BPMN type is a gateway
3648
+ * Check if two nodes have vertical overlap (same horizontal band)
3447
3649
  */
3448
- isGateway(type) {
3449
- if (!type) return false;
3450
- return [
3451
- "exclusiveGateway",
3452
- "parallelGateway",
3453
- "inclusiveGateway",
3454
- "eventBasedGateway",
3455
- "complexGateway"
3456
- ].includes(type);
3650
+ hasVerticalOverlap(a, b) {
3651
+ const aTop = a.y;
3652
+ const aBottom = a.y + a.height;
3653
+ const bTop = b.y;
3654
+ const bBottom = b.y + b.height;
3655
+ return aTop < bBottom && bTop < aBottom;
3457
3656
  }
3458
3657
  /**
3459
- * Check if a BPMN type is a container
3658
+ * Check if two nodes have horizontal overlap (same vertical band)
3460
3659
  */
3461
- isContainer(type) {
3462
- if (!type) return false;
3463
- return [
3464
- "lane",
3465
- "participant",
3466
- "collaboration",
3467
- "process",
3468
- "subProcess",
3469
- "transaction",
3470
- "adHocSubProcess",
3471
- "eventSubProcess"
3472
- ].includes(type);
3660
+ hasHorizontalOverlap(a, b) {
3661
+ const aLeft = a.x;
3662
+ const aRight = a.x + a.width;
3663
+ const bLeft = b.x;
3664
+ const bRight = b.x + b.width;
3665
+ return aLeft < bRight && bLeft < aRight;
3666
+ }
3667
+ /**
3668
+ * Topological sort using Kahn's algorithm
3669
+ */
3670
+ topologicalSort(nodes, dependencies) {
3671
+ const inDegree = /* @__PURE__ */ new Map();
3672
+ const adjacencyList = /* @__PURE__ */ new Map();
3673
+ for (const node of nodes) {
3674
+ inDegree.set(node, 0);
3675
+ adjacencyList.set(node, []);
3676
+ }
3677
+ for (const [target, sources] of dependencies) {
3678
+ for (const source of sources) {
3679
+ if (adjacencyList.has(source)) {
3680
+ adjacencyList.get(source).push(target);
3681
+ inDegree.set(target, (inDegree.get(target) ?? 0) + 1);
3682
+ }
3683
+ }
3684
+ }
3685
+ const queue = [];
3686
+ for (const node of nodes) {
3687
+ if ((inDegree.get(node) ?? 0) === 0) {
3688
+ queue.push(node);
3689
+ }
3690
+ }
3691
+ const sorted = [];
3692
+ while (queue.length > 0) {
3693
+ const node = queue.shift();
3694
+ sorted.push(node);
3695
+ for (const dependent of adjacencyList.get(node) ?? []) {
3696
+ const newDegree = (inDegree.get(dependent) ?? 0) - 1;
3697
+ inDegree.set(dependent, newDegree);
3698
+ if (newDegree === 0) {
3699
+ queue.push(dependent);
3700
+ }
3701
+ }
3702
+ }
3703
+ for (const node of nodes) {
3704
+ if (!sorted.includes(node)) {
3705
+ sorted.push(node);
3706
+ }
3707
+ }
3708
+ return sorted;
3473
3709
  }
3474
3710
  };
3475
3711
  }
@@ -3489,18 +3725,50 @@ var init_default_options = __esm({
3489
3725
  "use strict";
3490
3726
  init_cjs_shims();
3491
3727
  DEFAULT_ELK_OPTIONS2 = {
3728
+ // === Basic Configuration ===
3492
3729
  "elk.algorithm": "layered",
3493
3730
  "elk.direction": "RIGHT",
3494
- "elk.spacing.nodeNode": 50,
3495
- "elk.spacing.edgeNode": 30,
3496
- "elk.spacing.edgeEdge": 20,
3497
- "elk.layered.spacing.nodeNodeBetweenLayers": 80,
3498
- "elk.layered.spacing.edgeNodeBetweenLayers": 30,
3499
- "elk.layered.spacing.edgeEdgeBetweenLayers": 20,
3500
- "elk.hierarchyHandling": "INCLUDE_CHILDREN",
3731
+ // === Layer Assignment ===
3732
+ // NETWORK_SIMPLEX provides better layer distribution than LONGEST_PATH
3733
+ "elk.layered.layering.strategy": "NETWORK_SIMPLEX",
3734
+ // === Crossing Minimization ===
3501
3735
  "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
3736
+ // TWO_SIDED greedy switch improves crossing reduction
3737
+ "elk.layered.crossingMinimization.greedySwitch.type": "TWO_SIDED",
3738
+ // === Node Placement ===
3739
+ // BRANDES_KOEPF provides better vertical alignment than NETWORK_SIMPLEX
3502
3740
  "elk.layered.nodePlacement.strategy": "BRANDES_KOEPF",
3503
- "elk.edgeRouting": "ORTHOGONAL"
3741
+ // Use BALANCED alignment for symmetric layouts
3742
+ "elk.layered.nodePlacement.bk.fixedAlignment": "BALANCED",
3743
+ // === Edge Routing ===
3744
+ "elk.edgeRouting": "ORTHOGONAL",
3745
+ // Distribute self-loops evenly
3746
+ "elk.layered.edgeRouting.selfLoopDistribution": "EQUALLY",
3747
+ // === Edge Labels ===
3748
+ // Place edge labels at the center of the edge
3749
+ "elk.edgeLabels.placement": "CENTER",
3750
+ // Ensure labels don't overlap with nodes or other labels
3751
+ "elk.spacing.labelLabel": 5,
3752
+ "elk.spacing.labelNode": 10,
3753
+ "elk.spacing.edgeLabel": 10,
3754
+ // === Spacing (increased for better readability) ===
3755
+ "elk.spacing.nodeNode": 60,
3756
+ "elk.spacing.edgeNode": 40,
3757
+ "elk.spacing.edgeEdge": 25,
3758
+ "elk.layered.spacing.nodeNodeBetweenLayers": 100,
3759
+ "elk.layered.spacing.edgeNodeBetweenLayers": 40,
3760
+ "elk.layered.spacing.edgeEdgeBetweenLayers": 25,
3761
+ // === Hierarchy Handling ===
3762
+ "elk.hierarchyHandling": "INCLUDE_CHILDREN",
3763
+ // === Model Order ===
3764
+ // Consider both node and edge order from the input model
3765
+ "elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES",
3766
+ // === Compaction ===
3767
+ // Compact connected components together
3768
+ "elk.layered.compaction.connectedComponents": true,
3769
+ // === Alignment ===
3770
+ // Center alignment for cleaner appearance
3771
+ "elk.alignment": "CENTER"
3504
3772
  };
3505
3773
  }
3506
3774
  });
@@ -3677,10 +3945,10 @@ var init_elk_graph_preparer = __esm({
3677
3945
  /**
3678
3946
  * Prepare children for ELK layout
3679
3947
  */
3680
- prepareChildrenForElk(children, boundaryEventTargetIds = /* @__PURE__ */ new Set(), mainFlowNodes = /* @__PURE__ */ new Set()) {
3681
- if (!children) return [];
3948
+ prepareChildrenForElk(nodes, boundaryEventTargetIds = /* @__PURE__ */ new Set(), mainFlowNodes = /* @__PURE__ */ new Set()) {
3949
+ if (!nodes) return [];
3682
3950
  const result = [];
3683
- for (const child of children) {
3951
+ for (const child of nodes) {
3684
3952
  const node = child;
3685
3953
  result.push(this.prepareNodeForElk(node, boundaryEventTargetIds, mainFlowNodes));
3686
3954
  if (node.boundaryEvents && node.boundaryEvents.length > 0) {
@@ -3712,7 +3980,7 @@ var init_elk_graph_preparer = __esm({
3712
3980
  "elk.layered.layering.layerConstraint": "LAST"
3713
3981
  };
3714
3982
  }
3715
- if (mainFlowNodes.has(node.id)) {
3983
+ if (mainFlowNodes.has(node.id) && !boundaryEventTargetIds.has(node.id)) {
3716
3984
  layoutOptions = {
3717
3985
  ...layoutOptions,
3718
3986
  "elk.priority": "10"
@@ -3721,7 +3989,7 @@ var init_elk_graph_preparer = __esm({
3721
3989
  if (boundaryEventTargetIds.has(node.id)) {
3722
3990
  layoutOptions = {
3723
3991
  ...layoutOptions,
3724
- "elk.priority": "0"
3992
+ "elk.priority": "1"
3725
3993
  };
3726
3994
  }
3727
3995
  const isExpandedSubprocess = node.bpmn?.isExpanded === true && (node.bpmn?.type === "subProcess" || node.bpmn?.type === "transaction" || node.bpmn?.type === "adHocSubProcess" || node.bpmn?.type === "eventSubProcess");
@@ -4047,553 +4315,6 @@ var init_result_merger = __esm({
4047
4315
  }
4048
4316
  });
4049
4317
 
4050
- // src/layout/normalization/main-flow-normalizer.ts
4051
- var TARGET_MAIN_FLOW_Y, GATEWAY_OFFSET_Y, MainFlowNormalizer;
4052
- var init_main_flow_normalizer = __esm({
4053
- "src/layout/normalization/main-flow-normalizer.ts"() {
4054
- "use strict";
4055
- init_cjs_shims();
4056
- init_debug();
4057
- TARGET_MAIN_FLOW_Y = 12;
4058
- GATEWAY_OFFSET_Y = 150;
4059
- MainFlowNormalizer = class {
4060
- /**
4061
- * Normalize main flow position to keep it at the top of the diagram.
4062
- *
4063
- * Strategy:
4064
- * 1. Identify "upstream main flow" nodes (before converging gateway)
4065
- * 2. Shift these nodes up to target Y
4066
- * 3. Position converging gateway below the main flow
4067
- * 4. Position downstream nodes (after gateway) relative to gateway
4068
- */
4069
- normalize(graph, mainFlowNodes, boundaryEventTargetIds, originalGraph) {
4070
- const nodeMap = /* @__PURE__ */ new Map();
4071
- const nodesInsideContainers = /* @__PURE__ */ new Set();
4072
- const buildNodeMap2 = (node, parentIsContainer = false) => {
4073
- nodeMap.set(node.id, node);
4074
- if (parentIsContainer) {
4075
- nodesInsideContainers.add(node.id);
4076
- }
4077
- if (node.children) {
4078
- const nodeType = this.getNodeTypeFromOriginal(node.id, originalGraph);
4079
- const isContainer = nodeType === "subProcess" || nodeType === "transaction" || nodeType === "adHocSubProcess" || nodeType === "eventSubProcess";
4080
- for (const child of node.children) {
4081
- buildNodeMap2(child, isContainer || parentIsContainer);
4082
- }
4083
- }
4084
- };
4085
- buildNodeMap2(graph);
4086
- const convergingGatewayIds = /* @__PURE__ */ new Set();
4087
- const upstreamMainFlow = [];
4088
- const downstreamMainFlow = [];
4089
- for (const nodeId of mainFlowNodes) {
4090
- if (this.isConvergingGatewayWithBoundaryInputs(nodeId, graph, boundaryEventTargetIds)) {
4091
- const nodeType = this.getNodeTypeFromOriginal(nodeId, originalGraph);
4092
- const isEndEvent = nodeType === "endEvent";
4093
- if (!isEndEvent) {
4094
- convergingGatewayIds.add(nodeId);
4095
- }
4096
- }
4097
- }
4098
- const edgeMap = /* @__PURE__ */ new Map();
4099
- const collectEdges = (node) => {
4100
- if (node.edges) {
4101
- for (const edge of node.edges) {
4102
- const source = edge.sources?.[0];
4103
- const target = edge.targets?.[0];
4104
- if (source && target) {
4105
- if (!edgeMap.has(source)) edgeMap.set(source, []);
4106
- edgeMap.get(source).push(target);
4107
- }
4108
- }
4109
- }
4110
- if (node.children) {
4111
- for (const child of node.children) {
4112
- collectEdges(child);
4113
- }
4114
- }
4115
- };
4116
- collectEdges(graph);
4117
- const downstreamNodeIds = /* @__PURE__ */ new Set();
4118
- const markDownstream = (nodeId) => {
4119
- if (downstreamNodeIds.has(nodeId)) return;
4120
- downstreamNodeIds.add(nodeId);
4121
- const targets = edgeMap.get(nodeId) || [];
4122
- for (const targetId of targets) {
4123
- if (mainFlowNodes.has(targetId)) {
4124
- markDownstream(targetId);
4125
- }
4126
- }
4127
- };
4128
- for (const gatewayId of convergingGatewayIds) {
4129
- markDownstream(gatewayId);
4130
- }
4131
- for (const nodeId of mainFlowNodes) {
4132
- if (nodesInsideContainers.has(nodeId)) continue;
4133
- const node = nodeMap.get(nodeId);
4134
- if (!node || node.y === void 0) continue;
4135
- if (downstreamNodeIds.has(nodeId)) {
4136
- downstreamMainFlow.push(node);
4137
- } else {
4138
- upstreamMainFlow.push(node);
4139
- }
4140
- }
4141
- const endEventNodes = [];
4142
- const otherUpstreamNodes = [];
4143
- for (const node of upstreamMainFlow) {
4144
- const nodeType = this.getNodeTypeFromOriginal(node.id, originalGraph);
4145
- if (nodeType === "endEvent") {
4146
- endEventNodes.push(node);
4147
- } else {
4148
- otherUpstreamNodes.push(node);
4149
- }
4150
- }
4151
- let currentMinY = Infinity;
4152
- for (const node of otherUpstreamNodes) {
4153
- if (node.y !== void 0) {
4154
- currentMinY = Math.min(currentMinY, node.y);
4155
- }
4156
- }
4157
- if (currentMinY === Infinity || currentMinY <= TARGET_MAIN_FLOW_Y) {
4158
- return;
4159
- }
4160
- const offsetY = currentMinY - TARGET_MAIN_FLOW_Y;
4161
- if (isDebugEnabled()) {
4162
- console.log(`[BPMN] Normalizing main flow: currentMinY=${currentMinY}, offsetY=${offsetY}`);
4163
- console.log(`[BPMN] Upstream nodes: ${upstreamMainFlow.map((n) => n.id).join(", ")}`);
4164
- console.log(`[BPMN] Downstream nodes: ${downstreamMainFlow.map((n) => n.id).join(", ")}`);
4165
- }
4166
- for (const node of otherUpstreamNodes) {
4167
- if (node.y !== void 0) {
4168
- node.y -= offsetY;
4169
- if (isDebugEnabled()) {
4170
- console.log(`[BPMN] Shifted upstream ${node.id} to y=${node.y}`);
4171
- }
4172
- }
4173
- }
4174
- for (const endNode of endEventNodes) {
4175
- const predecessorId = this.findPredecessorOnMainFlow(endNode.id, graph, mainFlowNodes);
4176
- if (predecessorId) {
4177
- const predecessor = nodeMap.get(predecessorId);
4178
- if (predecessor && predecessor.y !== void 0) {
4179
- const predecessorCenterY = predecessor.y + (predecessor.height ?? 80) / 2;
4180
- const endNodeCenterY = (endNode.height ?? 36) / 2;
4181
- endNode.y = predecessorCenterY - endNodeCenterY;
4182
- if (isDebugEnabled()) {
4183
- console.log(`[BPMN] Aligned endEvent ${endNode.id} with predecessor ${predecessorId}: y=${endNode.y}`);
4184
- }
4185
- }
4186
- } else {
4187
- if (endNode.y !== void 0) {
4188
- endNode.y -= offsetY;
4189
- if (isDebugEnabled()) {
4190
- console.log(`[BPMN] Shifted upstream ${endNode.id} to y=${endNode.y} (no predecessor found)`);
4191
- }
4192
- }
4193
- }
4194
- }
4195
- const endEventsByX = /* @__PURE__ */ new Map();
4196
- for (const endNode of endEventNodes) {
4197
- const x = endNode.x ?? 0;
4198
- if (!endEventsByX.has(x)) {
4199
- endEventsByX.set(x, []);
4200
- }
4201
- endEventsByX.get(x).push(endNode);
4202
- }
4203
- const adjustedEndEvents = /* @__PURE__ */ new Map();
4204
- const MIN_SPACING = 50;
4205
- for (const [x, nodesAtX] of endEventsByX) {
4206
- if (nodesAtX.length <= 1) continue;
4207
- nodesAtX.sort((a, b) => (a.y ?? 0) - (b.y ?? 0));
4208
- for (let i = 1; i < nodesAtX.length; i++) {
4209
- const prevNode = nodesAtX[i - 1];
4210
- const currNode = nodesAtX[i];
4211
- const prevBottom = (prevNode.y ?? 0) + (prevNode.height ?? 36);
4212
- const currTop = currNode.y ?? 0;
4213
- if (currTop < prevBottom + MIN_SPACING) {
4214
- const newY = prevBottom + MIN_SPACING;
4215
- currNode.y = newY;
4216
- adjustedEndEvents.set(currNode.id, newY);
4217
- if (isDebugEnabled()) {
4218
- console.log(`[BPMN] Adjusted overlapping endEvent ${currNode.id} at x=${x}: y=${currNode.y}`);
4219
- }
4220
- }
4221
- }
4222
- }
4223
- const edgesWithAdjustedEndpoint = /* @__PURE__ */ new Set();
4224
- if (adjustedEndEvents.size > 0) {
4225
- this.updateEdgesForAdjustedEndEvents(graph, adjustedEndEvents, nodeMap, edgesWithAdjustedEndpoint);
4226
- }
4227
- const mainFlowBottom = Math.max(...upstreamMainFlow.map((n) => (n.y ?? 0) + (n.height ?? 80)));
4228
- const targetGatewayY = mainFlowBottom + GATEWAY_OFFSET_Y;
4229
- let currentGatewayY = Infinity;
4230
- for (const gatewayId of convergingGatewayIds) {
4231
- const gateway = nodeMap.get(gatewayId);
4232
- if (gateway && gateway.y !== void 0) {
4233
- currentGatewayY = Math.min(currentGatewayY, gateway.y);
4234
- }
4235
- }
4236
- if (currentGatewayY !== Infinity) {
4237
- const downstreamOffsetY = currentGatewayY - targetGatewayY;
4238
- for (const node of downstreamMainFlow) {
4239
- if (node.y !== void 0) {
4240
- node.y -= downstreamOffsetY;
4241
- if (isDebugEnabled()) {
4242
- console.log(`[BPMN] Shifted downstream ${node.id} to y=${node.y}`);
4243
- }
4244
- }
4245
- }
4246
- }
4247
- for (const [nodeId, node] of nodeMap) {
4248
- if (node.y !== void 0 && nodeId.startsWith("boundary_")) {
4249
- const attachedNodeId = this.findBoundaryAttachedNode(nodeId, graph);
4250
- if (attachedNodeId && mainFlowNodes.has(attachedNodeId)) {
4251
- const attachedNode = nodeMap.get(attachedNodeId);
4252
- if (attachedNode && attachedNode.y !== void 0) {
4253
- const attachedBottom = attachedNode.y + (attachedNode.height ?? 80);
4254
- node.y = attachedBottom - (node.height ?? 36) / 2;
4255
- }
4256
- }
4257
- }
4258
- }
4259
- this.updateEdgesAfterNormalization(graph, [...upstreamMainFlow, ...downstreamMainFlow], offsetY, edgesWithAdjustedEndpoint);
4260
- }
4261
- /**
4262
- * Find the predecessor node of a given node on the main flow
4263
- */
4264
- findPredecessorOnMainFlow(nodeId, graph, mainFlowNodes) {
4265
- const findIncoming = (node) => {
4266
- if (node.edges) {
4267
- for (const edge of node.edges) {
4268
- const target = edge.targets?.[0];
4269
- const source = edge.sources?.[0];
4270
- if (target === nodeId && source && mainFlowNodes.has(source)) {
4271
- if (!source.includes("boundary")) {
4272
- return source;
4273
- }
4274
- }
4275
- }
4276
- }
4277
- if (node.children) {
4278
- for (const child of node.children) {
4279
- const result = findIncoming(child);
4280
- if (result) return result;
4281
- }
4282
- }
4283
- return void 0;
4284
- };
4285
- return findIncoming(graph);
4286
- }
4287
- /**
4288
- * Get the BPMN type of a node by searching the original graph (with BPMN info preserved)
4289
- */
4290
- getNodeTypeFromOriginal(nodeId, graph) {
4291
- const findType = (children) => {
4292
- if (!children) return void 0;
4293
- for (const child of children) {
4294
- const node = child;
4295
- if (node.id === nodeId) {
4296
- return node.bpmn?.type;
4297
- }
4298
- if (node.children) {
4299
- const result = findType(node.children);
4300
- if (result) return result;
4301
- }
4302
- }
4303
- return void 0;
4304
- };
4305
- return findType(graph.children);
4306
- }
4307
- /**
4308
- * Check if a node is a converging gateway that receives edges from both
4309
- * main flow and boundary event branches (should be positioned below main flow)
4310
- */
4311
- isConvergingGatewayWithBoundaryInputs(nodeId, graph, boundaryEventTargetIds) {
4312
- const incomingSources = [];
4313
- const collectIncoming = (node) => {
4314
- if (node.edges) {
4315
- for (const edge of node.edges) {
4316
- const target = edge.targets?.[0];
4317
- const source = edge.sources?.[0];
4318
- if (target === nodeId && source) {
4319
- incomingSources.push(source);
4320
- }
4321
- }
4322
- }
4323
- if (node.children) {
4324
- for (const child of node.children) {
4325
- collectIncoming(child);
4326
- }
4327
- }
4328
- };
4329
- collectIncoming(graph);
4330
- if (incomingSources.length <= 1) return false;
4331
- let hasMainFlowInput = false;
4332
- let hasBoundaryInput = false;
4333
- for (const sourceId of incomingSources) {
4334
- if (boundaryEventTargetIds.has(sourceId) || this.isDownstreamOfBoundaryTarget(sourceId, graph, boundaryEventTargetIds)) {
4335
- hasBoundaryInput = true;
4336
- } else if (!sourceId.startsWith("boundary_")) {
4337
- hasMainFlowInput = true;
4338
- }
4339
- }
4340
- return hasMainFlowInput && hasBoundaryInput;
4341
- }
4342
- /**
4343
- * Check if a node is downstream of a boundary event target
4344
- */
4345
- isDownstreamOfBoundaryTarget(nodeId, graph, boundaryEventTargetIds, visited = /* @__PURE__ */ new Set()) {
4346
- if (visited.has(nodeId)) return false;
4347
- visited.add(nodeId);
4348
- if (boundaryEventTargetIds.has(nodeId)) return true;
4349
- const findIncoming = (node) => {
4350
- const sources2 = [];
4351
- if (node.edges) {
4352
- for (const edge of node.edges) {
4353
- if (edge.targets?.[0] === nodeId) {
4354
- sources2.push(edge.sources?.[0] ?? "");
4355
- }
4356
- }
4357
- }
4358
- if (node.children) {
4359
- for (const child of node.children) {
4360
- sources2.push(...findIncoming(child));
4361
- }
4362
- }
4363
- return sources2.filter((s) => s);
4364
- };
4365
- const sources = findIncoming(graph);
4366
- for (const sourceId of sources) {
4367
- if (this.isDownstreamOfBoundaryTarget(sourceId, graph, boundaryEventTargetIds, visited)) {
4368
- return true;
4369
- }
4370
- }
4371
- return false;
4372
- }
4373
- /**
4374
- * Find the node that a boundary event is attached to
4375
- */
4376
- findBoundaryAttachedNode(boundaryEventId, graph) {
4377
- const parts = boundaryEventId.replace("boundary_", "").split("_");
4378
- if (parts.length >= 2) {
4379
- const suffix = parts.slice(1).join("_");
4380
- const possibleParents = ["call_activity_" + suffix, "task_" + suffix, suffix];
4381
- const nodeMap = /* @__PURE__ */ new Map();
4382
- const buildMap = (node) => {
4383
- nodeMap.set(node.id, node);
4384
- if (node.children) {
4385
- for (const child of node.children) {
4386
- buildMap(child);
4387
- }
4388
- }
4389
- };
4390
- buildMap(graph);
4391
- for (const parentId of possibleParents) {
4392
- if (nodeMap.has(parentId)) {
4393
- return parentId;
4394
- }
4395
- }
4396
- }
4397
- return null;
4398
- }
4399
- /**
4400
- * Update edges after normalization to adjust waypoints
4401
- */
4402
- /**
4403
- * Update edges that connect to endEvents that were adjusted for overlap
4404
- */
4405
- updateEdgesForAdjustedEndEvents(graph, adjustedEndEvents, nodeMap, edgesWithAdjustedEndpoint) {
4406
- const updateEdges = (node) => {
4407
- if (node.edges) {
4408
- for (const edge of node.edges) {
4409
- const targetId = edge.targets?.[0];
4410
- if (targetId && adjustedEndEvents.has(targetId)) {
4411
- const targetNode = nodeMap.get(targetId);
4412
- if (!targetNode) continue;
4413
- const newTargetY = (targetNode.y ?? 0) + (targetNode.height ?? 36) / 2;
4414
- if (edge.sections) {
4415
- for (const section of edge.sections) {
4416
- if (section.bendPoints && section.bendPoints.length > 0 && section.endPoint) {
4417
- const lastBend = section.bendPoints[section.bendPoints.length - 1];
4418
- const oldEndY = section.endPoint.y;
4419
- if (Math.abs(lastBend.y - oldEndY) < 1) {
4420
- lastBend.y = newTargetY;
4421
- section.endPoint.y = newTargetY;
4422
- edgesWithAdjustedEndpoint.add(edge.id);
4423
- if (isDebugEnabled()) {
4424
- console.log(`[BPMN] Updated edge ${edge.id} last bendPoint and endpoint to y=${newTargetY}`);
4425
- }
4426
- } else {
4427
- section.endPoint.y = newTargetY;
4428
- edgesWithAdjustedEndpoint.add(edge.id);
4429
- if (isDebugEnabled()) {
4430
- console.log(`[BPMN] Updated edge ${edge.id} endpoint to y=${newTargetY}`);
4431
- }
4432
- }
4433
- } else if (section.endPoint) {
4434
- section.endPoint.y = newTargetY;
4435
- edgesWithAdjustedEndpoint.add(edge.id);
4436
- if (isDebugEnabled()) {
4437
- console.log(`[BPMN] Updated edge ${edge.id} endpoint to y=${newTargetY}`);
4438
- }
4439
- }
4440
- }
4441
- }
4442
- }
4443
- }
4444
- }
4445
- if (node.children) {
4446
- for (const child of node.children) {
4447
- updateEdges(child);
4448
- }
4449
- }
4450
- };
4451
- updateEdges(graph);
4452
- }
4453
- updateEdgesAfterNormalization(graph, movedNodes, offsetY, edgesWithAdjustedEndpoint = /* @__PURE__ */ new Set()) {
4454
- const movedNodeIds = new Set(movedNodes.map((n) => n.id));
4455
- const updateEdges = (node) => {
4456
- if (node.edges) {
4457
- for (const edge of node.edges) {
4458
- const sourceId = edge.sources?.[0];
4459
- const targetId = edge.targets?.[0];
4460
- const sourceMoved = sourceId && movedNodeIds.has(sourceId);
4461
- const targetMoved = targetId && movedNodeIds.has(targetId);
4462
- const endpointAlreadyAdjusted = edgesWithAdjustedEndpoint.has(edge.id);
4463
- if (sourceMoved && targetMoved && edge.sections) {
4464
- for (const section of edge.sections) {
4465
- if (section.startPoint) {
4466
- section.startPoint.y -= offsetY;
4467
- }
4468
- if (section.endPoint && !endpointAlreadyAdjusted) {
4469
- section.endPoint.y -= offsetY;
4470
- }
4471
- if (section.bendPoints) {
4472
- const bendCount = section.bendPoints.length;
4473
- for (let i = 0; i < bendCount; i++) {
4474
- if (endpointAlreadyAdjusted && i === bendCount - 1) continue;
4475
- section.bendPoints[i].y -= offsetY;
4476
- }
4477
- }
4478
- }
4479
- } else if (sourceMoved && !targetMoved && edge.sections) {
4480
- for (const section of edge.sections) {
4481
- if (section.startPoint) {
4482
- section.startPoint.y -= offsetY;
4483
- }
4484
- }
4485
- } else if (!sourceMoved && targetMoved && edge.sections && !endpointAlreadyAdjusted) {
4486
- for (const section of edge.sections) {
4487
- if (section.endPoint) {
4488
- section.endPoint.y -= offsetY;
4489
- }
4490
- }
4491
- }
4492
- }
4493
- }
4494
- if (node.children) {
4495
- for (const child of node.children) {
4496
- updateEdges(child);
4497
- }
4498
- }
4499
- };
4500
- updateEdges(graph);
4501
- }
4502
- };
4503
- }
4504
- });
4505
-
4506
- // src/layout/normalization/gateway-propagator.ts
4507
- var GatewayPropagator;
4508
- var init_gateway_propagator = __esm({
4509
- "src/layout/normalization/gateway-propagator.ts"() {
4510
- "use strict";
4511
- init_cjs_shims();
4512
- init_debug();
4513
- GatewayPropagator = class {
4514
- /**
4515
- * Propagate gateway movement to downstream nodes (nodes after the gateway in the flow)
4516
- */
4517
- propagate(graph, gatewayMoves, mainFlowNodes) {
4518
- const nodeMap = /* @__PURE__ */ new Map();
4519
- const edgeMap = /* @__PURE__ */ new Map();
4520
- const buildMaps = (node) => {
4521
- nodeMap.set(node.id, node);
4522
- if (node.edges) {
4523
- for (const edge of node.edges) {
4524
- const source = edge.sources?.[0];
4525
- const target = edge.targets?.[0];
4526
- if (source && target) {
4527
- if (!edgeMap.has(source)) edgeMap.set(source, []);
4528
- edgeMap.get(source).push(target);
4529
- }
4530
- }
4531
- }
4532
- if (node.children) {
4533
- for (const child of node.children) {
4534
- buildMaps(child);
4535
- }
4536
- }
4537
- };
4538
- buildMaps(graph);
4539
- for (const [gatewayId, gatewayMove] of gatewayMoves) {
4540
- if (gatewayMove.newX === void 0) continue;
4541
- const gateway = nodeMap.get(gatewayId);
4542
- if (!gateway) continue;
4543
- const gatewayRight = gatewayMove.newX + (gateway.width ?? 50);
4544
- const downstreamTargets = edgeMap.get(gatewayId) || [];
4545
- for (const targetId of downstreamTargets) {
4546
- if (!mainFlowNodes.has(targetId)) continue;
4547
- const targetNode = nodeMap.get(targetId);
4548
- if (!targetNode) continue;
4549
- const newX = gatewayRight + 50;
4550
- const currentX = targetNode.x ?? 0;
4551
- if (newX > currentX) {
4552
- targetNode.x = newX;
4553
- gatewayMoves.set(targetId, {
4554
- newY: targetNode.y ?? 0,
4555
- offset: 0,
4556
- newX
4557
- });
4558
- if (isDebugEnabled()) {
4559
- console.log(`[BPMN] Propagating gateway movement to ${targetId}: x ${currentX} -> ${newX}`);
4560
- }
4561
- this.propagateDownstreamX(targetId, newX, targetNode.width ?? 100, nodeMap, edgeMap, mainFlowNodes, gatewayMoves);
4562
- }
4563
- }
4564
- }
4565
- }
4566
- /**
4567
- * Recursively propagate X movement to downstream main flow nodes
4568
- */
4569
- propagateDownstreamX(sourceId, sourceX, sourceWidth, nodeMap, edgeMap, mainFlowNodes, moves) {
4570
- const targets = edgeMap.get(sourceId) || [];
4571
- const sourceRight = sourceX + sourceWidth;
4572
- for (const targetId of targets) {
4573
- if (!mainFlowNodes.has(targetId)) continue;
4574
- if (moves.has(targetId)) continue;
4575
- const targetNode = nodeMap.get(targetId);
4576
- if (!targetNode) continue;
4577
- const newX = sourceRight + 50;
4578
- const currentX = targetNode.x ?? 0;
4579
- if (newX > currentX) {
4580
- targetNode.x = newX;
4581
- moves.set(targetId, {
4582
- newY: targetNode.y ?? 0,
4583
- offset: 0,
4584
- newX
4585
- });
4586
- if (isDebugEnabled()) {
4587
- console.log(`[BPMN] Propagating X to ${targetId}: x ${currentX} -> ${newX}`);
4588
- }
4589
- this.propagateDownstreamX(targetId, newX, targetNode.width ?? 100, nodeMap, edgeMap, mainFlowNodes, moves);
4590
- }
4591
- }
4592
- }
4593
- };
4594
- }
4595
- });
4596
-
4597
4318
  // src/layout/elk-layouter.ts
4598
4319
  var import_elkjs, ElkLayouter;
4599
4320
  var init_elk_layouter = __esm({
@@ -4602,48 +4323,41 @@ var init_elk_layouter = __esm({
4602
4323
  init_cjs_shims();
4603
4324
  import_elkjs = __toESM(require("elkjs"));
4604
4325
  init_size_calculator();
4605
- init_edge_fixer();
4606
4326
  init_boundary_event();
4607
4327
  init_artifact_positioner();
4608
4328
  init_group_positioner();
4609
4329
  init_lane_arranger();
4610
4330
  init_pool_arranger();
4611
- init_gateway_edge_adjuster();
4331
+ init_compactor();
4612
4332
  init_elk_graph_preparer();
4613
4333
  init_result_merger();
4614
- init_main_flow_normalizer();
4615
- init_gateway_propagator();
4616
4334
  init_debug();
4617
4335
  ElkLayouter = class {
4618
4336
  elk;
4619
4337
  userOptions;
4338
+ enableCompaction;
4620
4339
  sizeCalculator;
4621
- edgeFixer;
4622
4340
  boundaryEventHandler;
4623
4341
  artifactPositioner;
4624
4342
  groupPositioner;
4625
4343
  laneArranger;
4626
4344
  poolArranger;
4627
- gatewayEdgeAdjuster;
4345
+ compactor;
4628
4346
  graphPreparer;
4629
4347
  resultMerger;
4630
- mainFlowNormalizer;
4631
- gatewayPropagator;
4632
4348
  constructor(options) {
4633
4349
  this.elk = new import_elkjs.default();
4634
4350
  this.userOptions = options?.elkOptions ?? {};
4351
+ this.enableCompaction = options?.enableCompaction ?? false;
4635
4352
  this.sizeCalculator = new SizeCalculator();
4636
- this.edgeFixer = new EdgeFixer();
4637
4353
  this.boundaryEventHandler = new BoundaryEventHandler();
4638
4354
  this.artifactPositioner = new ArtifactPositioner();
4639
4355
  this.groupPositioner = new GroupPositioner();
4640
4356
  this.laneArranger = new LaneArranger();
4641
4357
  this.poolArranger = new PoolArranger();
4642
- this.gatewayEdgeAdjuster = new GatewayEdgeAdjuster();
4358
+ this.compactor = new Compactor();
4643
4359
  this.graphPreparer = new ElkGraphPreparer();
4644
4360
  this.resultMerger = new ResultMerger();
4645
- this.mainFlowNormalizer = new MainFlowNormalizer();
4646
- this.gatewayPropagator = new GatewayPropagator();
4647
4361
  }
4648
4362
  /**
4649
4363
  * Run ELK layout on the graph
@@ -4657,24 +4371,14 @@ var init_elk_layouter = __esm({
4657
4371
  const groupInfo = this.groupPositioner.collectInfo(sizedGraph);
4658
4372
  const elkGraph = this.graphPreparer.prepare(sizedGraph, this.userOptions, boundaryEventTargetIds);
4659
4373
  const layoutedElkGraph = await this.elk.layout(elkGraph);
4660
- const mainFlowNodes = this.graphPreparer.identifyMainFlowNodes(sizedGraph, boundaryEventTargetIds);
4661
- this.mainFlowNormalizer.normalize(layoutedElkGraph, mainFlowNodes, boundaryEventTargetIds, sizedGraph);
4662
- const movedNodes = this.boundaryEventHandler.identifyNodesToMove(layoutedElkGraph, boundaryEventInfo, sizedGraph, isDebugEnabled());
4374
+ const movedNodes = this.boundaryEventHandler.identifyNodesToMove(
4375
+ layoutedElkGraph,
4376
+ boundaryEventInfo,
4377
+ sizedGraph,
4378
+ isDebugEnabled()
4379
+ );
4663
4380
  if (movedNodes.size > 0) {
4664
4381
  this.boundaryEventHandler.applyNodeMoves(layoutedElkGraph, movedNodes);
4665
- const gatewayMoves = this.boundaryEventHandler.repositionConvergingGateways(
4666
- layoutedElkGraph,
4667
- movedNodes,
4668
- boundaryEventInfo,
4669
- isDebugEnabled()
4670
- );
4671
- if (gatewayMoves.size > 0) {
4672
- this.boundaryEventHandler.applyNodeMoves(layoutedElkGraph, gatewayMoves);
4673
- this.gatewayPropagator.propagate(layoutedElkGraph, gatewayMoves, mainFlowNodes);
4674
- }
4675
- for (const [id, move] of gatewayMoves) {
4676
- movedNodes.set(id, move);
4677
- }
4678
4382
  this.boundaryEventHandler.recalculateEdgesForMovedNodes(layoutedElkGraph, movedNodes, boundaryEventInfo);
4679
4383
  }
4680
4384
  this.artifactPositioner.reposition(layoutedElkGraph, artifactInfo);
@@ -4682,7 +4386,9 @@ var init_elk_layouter = __esm({
4682
4386
  this.poolArranger.rearrange(layoutedElkGraph, sizedGraph);
4683
4387
  this.groupPositioner.reposition(layoutedElkGraph, groupInfo, sizedGraph);
4684
4388
  this.artifactPositioner.recalculateWithObstacleAvoidance(layoutedElkGraph, artifactInfo);
4685
- this.edgeFixer.fix(layoutedElkGraph);
4389
+ if (this.enableCompaction) {
4390
+ this.compactor.compact(layoutedElkGraph);
4391
+ }
4686
4392
  this.updateContainerBounds(layoutedElkGraph);
4687
4393
  return this.resultMerger.merge(sizedGraph, layoutedElkGraph);
4688
4394
  }
@@ -4731,6 +4437,158 @@ var init_elk_layouter = __esm({
4731
4437
  }
4732
4438
  });
4733
4439
 
4440
+ // src/layout/tree/tree-layouter.ts
4441
+ var init_tree_layouter = __esm({
4442
+ "src/layout/tree/tree-layouter.ts"() {
4443
+ "use strict";
4444
+ init_cjs_shims();
4445
+ }
4446
+ });
4447
+
4448
+ // src/layout/tree/index.ts
4449
+ var init_tree = __esm({
4450
+ "src/layout/tree/index.ts"() {
4451
+ "use strict";
4452
+ init_cjs_shims();
4453
+ init_tree_layouter();
4454
+ }
4455
+ });
4456
+
4457
+ // src/layout/edge-routing/pathfinding-router.ts
4458
+ var import_pathfinding;
4459
+ var init_pathfinding_router = __esm({
4460
+ "src/layout/edge-routing/pathfinding-router.ts"() {
4461
+ "use strict";
4462
+ init_cjs_shims();
4463
+ import_pathfinding = __toESM(require("pathfinding"));
4464
+ }
4465
+ });
4466
+
4467
+ // src/layout/edge-routing/edge-fixer.ts
4468
+ var init_edge_fixer = __esm({
4469
+ "src/layout/edge-routing/edge-fixer.ts"() {
4470
+ "use strict";
4471
+ init_cjs_shims();
4472
+ init_geometry_utils();
4473
+ init_pathfinding_router();
4474
+ init_debug();
4475
+ }
4476
+ });
4477
+
4478
+ // src/layout/edge-routing/gateway-endpoint-adjuster.ts
4479
+ function adjustGatewayEndpoint(endpoint, adjacentPoint, gatewayBounds, isSource) {
4480
+ const gatewayCenterX = gatewayBounds.x + gatewayBounds.width / 2;
4481
+ const gatewayCenterY = gatewayBounds.y + gatewayBounds.height / 2;
4482
+ const tolerance = 1;
4483
+ if (isDebugEnabled()) {
4484
+ console.log(`[BPMN] adjustGatewayEndpoint: isSource=${isSource}`);
4485
+ console.log(` endpoint: (${endpoint.x}, ${endpoint.y})`);
4486
+ console.log(` gatewayBounds: x=${gatewayBounds.x}, y=${gatewayBounds.y}, w=${gatewayBounds.width}, h=${gatewayBounds.height}`);
4487
+ console.log(` gatewayCenter: (${gatewayCenterX}, ${gatewayCenterY})`);
4488
+ console.log(` right edge x: ${gatewayBounds.x + gatewayBounds.width}`);
4489
+ }
4490
+ const leftCorner = { x: gatewayBounds.x, y: gatewayCenterY };
4491
+ const rightCorner = { x: gatewayBounds.x + gatewayBounds.width, y: gatewayCenterY };
4492
+ const topCorner = { x: gatewayCenterX, y: gatewayBounds.y };
4493
+ const bottomCorner = { x: gatewayCenterX, y: gatewayBounds.y + gatewayBounds.height };
4494
+ if (Math.abs(endpoint.x - gatewayBounds.x) < tolerance && Math.abs(endpoint.y - gatewayCenterY) < tolerance) {
4495
+ if (isDebugEnabled()) console.log(` -> Already at LEFT corner, no adjustment`);
4496
+ return endpoint;
4497
+ }
4498
+ if (Math.abs(endpoint.x - (gatewayBounds.x + gatewayBounds.width)) < tolerance && Math.abs(endpoint.y - gatewayCenterY) < tolerance) {
4499
+ if (isDebugEnabled()) console.log(` -> Already at RIGHT corner, no adjustment`);
4500
+ return endpoint;
4501
+ }
4502
+ if (Math.abs(endpoint.y - gatewayBounds.y) < tolerance && Math.abs(endpoint.x - gatewayCenterX) < tolerance) {
4503
+ if (isDebugEnabled()) console.log(` -> Already at TOP corner, no adjustment`);
4504
+ return endpoint;
4505
+ }
4506
+ if (Math.abs(endpoint.y - (gatewayBounds.y + gatewayBounds.height)) < tolerance && Math.abs(endpoint.x - gatewayCenterX) < tolerance) {
4507
+ if (isDebugEnabled()) console.log(` -> Already at BOTTOM corner, no adjustment`);
4508
+ return endpoint;
4509
+ }
4510
+ if (isDebugEnabled()) {
4511
+ console.log(` -> NOT at corner, will adjust`);
4512
+ }
4513
+ const result = calculateDiamondIntersection(
4514
+ endpoint,
4515
+ gatewayBounds,
4516
+ gatewayCenterX,
4517
+ gatewayCenterY,
4518
+ isSource,
4519
+ adjacentPoint
4520
+ );
4521
+ if (isDebugEnabled()) {
4522
+ console.log(` -> Adjusted to: (${result.x}, ${result.y})`);
4523
+ }
4524
+ return result;
4525
+ }
4526
+ function calculateDiamondIntersection(endpoint, gatewayBounds, gatewayCenterX, gatewayCenterY, isSource, adjacentPoint) {
4527
+ const tolerance = 1;
4528
+ const leftCorner = { x: gatewayBounds.x, y: gatewayCenterY };
4529
+ const rightCorner = { x: gatewayBounds.x + gatewayBounds.width, y: gatewayCenterY };
4530
+ const topCorner = { x: gatewayCenterX, y: gatewayBounds.y };
4531
+ const bottomCorner = { x: gatewayCenterX, y: gatewayBounds.y + gatewayBounds.height };
4532
+ const isOnLeftEdge = Math.abs(endpoint.x - gatewayBounds.x) < tolerance;
4533
+ const isOnRightEdge = Math.abs(endpoint.x - (gatewayBounds.x + gatewayBounds.width)) < tolerance;
4534
+ const isOnTopEdge = Math.abs(endpoint.y - gatewayBounds.y) < tolerance;
4535
+ const isOnBottomEdge = Math.abs(endpoint.y - (gatewayBounds.y + gatewayBounds.height)) < tolerance;
4536
+ const halfWidth = gatewayBounds.width / 2;
4537
+ const halfHeight = gatewayBounds.height / 2;
4538
+ if (isOnLeftEdge || isOnRightEdge) {
4539
+ const yDistFromCenter = Math.abs(endpoint.y - gatewayCenterY);
4540
+ if (yDistFromCenter >= halfHeight) {
4541
+ return isOnLeftEdge ? leftCorner : rightCorner;
4542
+ }
4543
+ const xOffsetFromCenter = halfWidth * (1 - yDistFromCenter / halfHeight);
4544
+ const intersectX = isOnLeftEdge ? gatewayCenterX - xOffsetFromCenter : gatewayCenterX + xOffsetFromCenter;
4545
+ return { x: intersectX, y: endpoint.y };
4546
+ }
4547
+ if (isOnTopEdge || isOnBottomEdge) {
4548
+ const xDistFromCenter = Math.abs(endpoint.x - gatewayCenterX);
4549
+ if (xDistFromCenter >= halfWidth) {
4550
+ return isOnTopEdge ? topCorner : bottomCorner;
4551
+ }
4552
+ const yOffsetFromCenter = halfHeight * (1 - xDistFromCenter / halfWidth);
4553
+ const intersectY = isOnTopEdge ? gatewayCenterY - yOffsetFromCenter : gatewayCenterY + yOffsetFromCenter;
4554
+ return { x: endpoint.x, y: intersectY };
4555
+ }
4556
+ const dx = isSource ? adjacentPoint.x - endpoint.x : endpoint.x - adjacentPoint.x;
4557
+ const dy = isSource ? adjacentPoint.y - endpoint.y : endpoint.y - adjacentPoint.y;
4558
+ if (Math.abs(dx) > Math.abs(dy)) {
4559
+ if (isSource) {
4560
+ return dx > 0 ? rightCorner : leftCorner;
4561
+ } else {
4562
+ return dx > 0 ? leftCorner : rightCorner;
4563
+ }
4564
+ } else {
4565
+ if (isSource) {
4566
+ return dy > 0 ? bottomCorner : topCorner;
4567
+ } else {
4568
+ return dy > 0 ? topCorner : bottomCorner;
4569
+ }
4570
+ }
4571
+ }
4572
+ var init_gateway_endpoint_adjuster = __esm({
4573
+ "src/layout/edge-routing/gateway-endpoint-adjuster.ts"() {
4574
+ "use strict";
4575
+ init_cjs_shims();
4576
+ init_debug();
4577
+ }
4578
+ });
4579
+
4580
+ // src/layout/edge-routing/index.ts
4581
+ var init_edge_routing = __esm({
4582
+ "src/layout/edge-routing/index.ts"() {
4583
+ "use strict";
4584
+ init_cjs_shims();
4585
+ init_edge_fixer();
4586
+ init_geometry_utils();
4587
+ init_gateway_endpoint_adjuster();
4588
+ init_pathfinding_router();
4589
+ }
4590
+ });
4591
+
4734
4592
  // src/layout/index.ts
4735
4593
  var init_layout = __esm({
4736
4594
  "src/layout/index.ts"() {
@@ -4739,6 +4597,9 @@ var init_layout = __esm({
4739
4597
  init_elk_layouter();
4740
4598
  init_default_options();
4741
4599
  init_size_calculator();
4600
+ init_tree();
4601
+ init_constraint();
4602
+ init_edge_routing();
4742
4603
  }
4743
4604
  });
4744
4605
 
@@ -4971,119 +4832,6 @@ var init_lane_resolver = __esm({
4971
4832
  }
4972
4833
  });
4973
4834
 
4974
- // src/layout/edge-routing/gateway-endpoint-adjuster.ts
4975
- function adjustGatewayEndpoint(endpoint, adjacentPoint, gatewayBounds, isSource) {
4976
- const gatewayCenterX = gatewayBounds.x + gatewayBounds.width / 2;
4977
- const gatewayCenterY = gatewayBounds.y + gatewayBounds.height / 2;
4978
- const tolerance = 1;
4979
- if (isDebugEnabled()) {
4980
- console.log(`[BPMN] adjustGatewayEndpoint: isSource=${isSource}`);
4981
- console.log(` endpoint: (${endpoint.x}, ${endpoint.y})`);
4982
- console.log(` gatewayBounds: x=${gatewayBounds.x}, y=${gatewayBounds.y}, w=${gatewayBounds.width}, h=${gatewayBounds.height}`);
4983
- console.log(` gatewayCenter: (${gatewayCenterX}, ${gatewayCenterY})`);
4984
- console.log(` right edge x: ${gatewayBounds.x + gatewayBounds.width}`);
4985
- }
4986
- const leftCorner = { x: gatewayBounds.x, y: gatewayCenterY };
4987
- const rightCorner = { x: gatewayBounds.x + gatewayBounds.width, y: gatewayCenterY };
4988
- const topCorner = { x: gatewayCenterX, y: gatewayBounds.y };
4989
- const bottomCorner = { x: gatewayCenterX, y: gatewayBounds.y + gatewayBounds.height };
4990
- if (Math.abs(endpoint.x - gatewayBounds.x) < tolerance && Math.abs(endpoint.y - gatewayCenterY) < tolerance) {
4991
- if (isDebugEnabled()) console.log(` -> Already at LEFT corner, no adjustment`);
4992
- return endpoint;
4993
- }
4994
- if (Math.abs(endpoint.x - (gatewayBounds.x + gatewayBounds.width)) < tolerance && Math.abs(endpoint.y - gatewayCenterY) < tolerance) {
4995
- if (isDebugEnabled()) console.log(` -> Already at RIGHT corner, no adjustment`);
4996
- return endpoint;
4997
- }
4998
- if (Math.abs(endpoint.y - gatewayBounds.y) < tolerance && Math.abs(endpoint.x - gatewayCenterX) < tolerance) {
4999
- if (isDebugEnabled()) console.log(` -> Already at TOP corner, no adjustment`);
5000
- return endpoint;
5001
- }
5002
- if (Math.abs(endpoint.y - (gatewayBounds.y + gatewayBounds.height)) < tolerance && Math.abs(endpoint.x - gatewayCenterX) < tolerance) {
5003
- if (isDebugEnabled()) console.log(` -> Already at BOTTOM corner, no adjustment`);
5004
- return endpoint;
5005
- }
5006
- if (isDebugEnabled()) {
5007
- console.log(` -> NOT at corner, will adjust`);
5008
- }
5009
- const result = calculateDiamondIntersection(
5010
- endpoint,
5011
- gatewayBounds,
5012
- gatewayCenterX,
5013
- gatewayCenterY,
5014
- isSource,
5015
- adjacentPoint
5016
- );
5017
- if (isDebugEnabled()) {
5018
- console.log(` -> Adjusted to: (${result.x}, ${result.y})`);
5019
- }
5020
- return result;
5021
- }
5022
- function calculateDiamondIntersection(endpoint, gatewayBounds, gatewayCenterX, gatewayCenterY, isSource, adjacentPoint) {
5023
- const tolerance = 1;
5024
- const leftCorner = { x: gatewayBounds.x, y: gatewayCenterY };
5025
- const rightCorner = { x: gatewayBounds.x + gatewayBounds.width, y: gatewayCenterY };
5026
- const topCorner = { x: gatewayCenterX, y: gatewayBounds.y };
5027
- const bottomCorner = { x: gatewayCenterX, y: gatewayBounds.y + gatewayBounds.height };
5028
- const isOnLeftEdge = Math.abs(endpoint.x - gatewayBounds.x) < tolerance;
5029
- const isOnRightEdge = Math.abs(endpoint.x - (gatewayBounds.x + gatewayBounds.width)) < tolerance;
5030
- const isOnTopEdge = Math.abs(endpoint.y - gatewayBounds.y) < tolerance;
5031
- const isOnBottomEdge = Math.abs(endpoint.y - (gatewayBounds.y + gatewayBounds.height)) < tolerance;
5032
- const halfWidth = gatewayBounds.width / 2;
5033
- const halfHeight = gatewayBounds.height / 2;
5034
- if (isOnLeftEdge || isOnRightEdge) {
5035
- const yDistFromCenter = Math.abs(endpoint.y - gatewayCenterY);
5036
- if (yDistFromCenter >= halfHeight) {
5037
- return isOnLeftEdge ? leftCorner : rightCorner;
5038
- }
5039
- const xOffsetFromCenter = halfWidth * (1 - yDistFromCenter / halfHeight);
5040
- const intersectX = isOnLeftEdge ? gatewayCenterX - xOffsetFromCenter : gatewayCenterX + xOffsetFromCenter;
5041
- return { x: intersectX, y: endpoint.y };
5042
- }
5043
- if (isOnTopEdge || isOnBottomEdge) {
5044
- const xDistFromCenter = Math.abs(endpoint.x - gatewayCenterX);
5045
- if (xDistFromCenter >= halfWidth) {
5046
- return isOnTopEdge ? topCorner : bottomCorner;
5047
- }
5048
- const yOffsetFromCenter = halfHeight * (1 - xDistFromCenter / halfWidth);
5049
- const intersectY = isOnTopEdge ? gatewayCenterY - yOffsetFromCenter : gatewayCenterY + yOffsetFromCenter;
5050
- return { x: endpoint.x, y: intersectY };
5051
- }
5052
- const dx = isSource ? adjacentPoint.x - endpoint.x : endpoint.x - adjacentPoint.x;
5053
- const dy = isSource ? adjacentPoint.y - endpoint.y : endpoint.y - adjacentPoint.y;
5054
- if (Math.abs(dx) > Math.abs(dy)) {
5055
- if (isSource) {
5056
- return dx > 0 ? rightCorner : leftCorner;
5057
- } else {
5058
- return dx > 0 ? leftCorner : rightCorner;
5059
- }
5060
- } else {
5061
- if (isSource) {
5062
- return dy > 0 ? bottomCorner : topCorner;
5063
- } else {
5064
- return dy > 0 ? topCorner : bottomCorner;
5065
- }
5066
- }
5067
- }
5068
- var init_gateway_endpoint_adjuster = __esm({
5069
- "src/layout/edge-routing/gateway-endpoint-adjuster.ts"() {
5070
- "use strict";
5071
- init_cjs_shims();
5072
- init_debug();
5073
- }
5074
- });
5075
-
5076
- // src/layout/edge-routing/index.ts
5077
- var init_edge_routing = __esm({
5078
- "src/layout/edge-routing/index.ts"() {
5079
- "use strict";
5080
- init_cjs_shims();
5081
- init_edge_fixer();
5082
- init_geometry_utils();
5083
- init_gateway_endpoint_adjuster();
5084
- }
5085
- });
5086
-
5087
4835
  // src/transform/diagram-builder.ts
5088
4836
  var DiagramBuilder;
5089
4837
  var init_diagram_builder = __esm({
@@ -5101,10 +4849,13 @@ var init_diagram_builder = __esm({
5101
4849
  nodeOffsets = /* @__PURE__ */ new Map();
5102
4850
  // Map to track node BPMN metadata for gateway detection
5103
4851
  nodeBpmn = /* @__PURE__ */ new Map();
4852
+ // List of already placed edge labels for collision detection
4853
+ placedEdgeLabels = [];
5104
4854
  /**
5105
4855
  * Build the diagram model from a layouted graph
5106
4856
  */
5107
4857
  build(graph, definitions) {
4858
+ this.placedEdgeLabels = [];
5108
4859
  this.boundaryEventPositions.clear();
5109
4860
  this.nodePositions.clear();
5110
4861
  this.nodeOffsets.clear();
@@ -5112,7 +4863,10 @@ var init_diagram_builder = __esm({
5112
4863
  const shapes = [];
5113
4864
  const edges = [];
5114
4865
  const mainElement = definitions.rootElements[0];
5115
- const planeElement = mainElement?.type === "collaboration" ? mainElement.id : mainElement?.id ?? graph.id;
4866
+ if (!mainElement) {
4867
+ throw new Error("Cannot create BPMN diagram: definitions.rootElements is empty. The graph must contain at least one process or collaboration.");
4868
+ }
4869
+ const planeElement = mainElement.type === "collaboration" ? mainElement.id : mainElement.id;
5116
4870
  for (const child of graph.children) {
5117
4871
  this.collectShapesAndEdges(child, shapes, edges);
5118
4872
  }
@@ -5145,11 +4899,16 @@ var init_diagram_builder = __esm({
5145
4899
  const absoluteY = offsetY + node.y;
5146
4900
  const nodeWidth = node.width ?? 100;
5147
4901
  const nodeHeight = node.height ?? 80;
4902
+ let effectiveHeight = nodeHeight;
4903
+ if (this.isEventType(node.bpmn?.type) && node.labels && node.labels.length > 0) {
4904
+ const labelHeight = node.labels[0]?.height ?? 14;
4905
+ effectiveHeight = nodeHeight + 4 + labelHeight;
4906
+ }
5148
4907
  this.nodePositions.set(node.id, {
5149
4908
  x: absoluteX,
5150
4909
  y: absoluteY,
5151
4910
  width: nodeWidth,
5152
- height: nodeHeight
4911
+ height: effectiveHeight
5153
4912
  });
5154
4913
  if (node.bpmn) {
5155
4914
  this.storeNodeBpmn(node.id, { type: node.bpmn.type });
@@ -5286,47 +5045,44 @@ var init_diagram_builder = __esm({
5286
5045
  if (node.bpmn?.type === "participant" || node.bpmn?.type === "lane") {
5287
5046
  shape.isHorizontal = true;
5288
5047
  }
5289
- if (node.labels && node.labels.length > 0) {
5290
- const label = node.labels[0];
5291
- if (!label) return shape;
5292
- const nodeWidth = node.width ?? 36;
5293
- const nodeHeight = node.height ?? 36;
5294
- if (this.isEventType(node.bpmn?.type)) {
5295
- const labelWidth = label.width ?? 100;
5296
- const labelHeight = label.height ?? 14;
5297
- shape.label = {
5298
- bounds: {
5299
- x: absoluteX + (nodeWidth - labelWidth) / 2,
5300
- y: absoluteY + nodeHeight + 4,
5301
- // 4px gap below the circle
5302
- width: labelWidth,
5303
- height: labelHeight
5304
- }
5305
- };
5306
- } else if (this.isGatewayType(node.bpmn?.type)) {
5307
- const labelWidth = label?.width ?? 100;
5308
- const labelText = node.bpmn?.name ?? label?.text ?? "";
5309
- const estimatedLines = this.estimateLabelLines(labelText, labelWidth);
5310
- const labelHeight = estimatedLines * 14;
5311
- shape.label = {
5312
- bounds: {
5313
- x: absoluteX + (nodeWidth - labelWidth) / 2,
5314
- y: absoluteY - labelHeight - 4,
5315
- // 4px gap above the diamond
5316
- width: labelWidth,
5317
- height: labelHeight
5318
- }
5319
- };
5320
- } else if (label?.x !== void 0 && label?.y !== void 0) {
5321
- shape.label = {
5322
- bounds: {
5323
- x: absoluteX + label.x,
5324
- y: absoluteY + label.y,
5325
- width: label?.width ?? 100,
5326
- height: label?.height ?? 20
5327
- }
5328
- };
5329
- }
5048
+ const nodeWidth = node.width ?? 36;
5049
+ const nodeHeight = node.height ?? 36;
5050
+ const label = node.labels?.[0];
5051
+ const labelText = node.bpmn?.name ?? label?.text ?? "";
5052
+ if (this.isEventType(node.bpmn?.type) && labelText) {
5053
+ const labelWidth = label?.width ?? 100;
5054
+ const labelHeight = label?.height ?? 14;
5055
+ shape.label = {
5056
+ bounds: {
5057
+ x: absoluteX + (nodeWidth - labelWidth) / 2,
5058
+ y: absoluteY + nodeHeight + 4,
5059
+ // 4px gap below the circle
5060
+ width: labelWidth,
5061
+ height: labelHeight
5062
+ }
5063
+ };
5064
+ } else if (this.isGatewayType(node.bpmn?.type) && labelText) {
5065
+ const labelWidth = label?.width ?? 100;
5066
+ const estimatedLines = this.estimateLabelLines(labelText, labelWidth);
5067
+ const labelHeight = estimatedLines * 14;
5068
+ shape.label = {
5069
+ bounds: {
5070
+ x: absoluteX + (nodeWidth - labelWidth) / 2,
5071
+ y: absoluteY - labelHeight - 4,
5072
+ // 4px gap above the diamond
5073
+ width: labelWidth,
5074
+ height: labelHeight
5075
+ }
5076
+ };
5077
+ } else if (label?.x !== void 0 && label?.y !== void 0) {
5078
+ shape.label = {
5079
+ bounds: {
5080
+ x: absoluteX + label.x,
5081
+ y: absoluteY + label.y,
5082
+ width: label?.width ?? 100,
5083
+ height: label?.height ?? 20
5084
+ }
5085
+ };
5330
5086
  }
5331
5087
  return shape;
5332
5088
  }
@@ -5420,6 +5176,13 @@ var init_diagram_builder = __esm({
5420
5176
  }
5421
5177
  }
5422
5178
  this.ensureOrthogonalWaypoints(waypoints);
5179
+ this.ensurePerpendicularEndpoints(
5180
+ waypoints,
5181
+ sourceId,
5182
+ targetId,
5183
+ sourceIsGateway,
5184
+ targetIsGateway
5185
+ );
5423
5186
  const edgeModel = {
5424
5187
  id: `${edge.id}_di`,
5425
5188
  bpmnElement: edge.id,
@@ -5429,7 +5192,19 @@ var init_diagram_builder = __esm({
5429
5192
  const label = edge.labels[0];
5430
5193
  const labelWidth = label?.width ?? 50;
5431
5194
  const labelHeight = label?.height ?? 14;
5432
- const labelPos = this.calculateEdgeLabelPosition(waypoints, labelWidth, labelHeight);
5195
+ const labelPos = this.calculateSmartLabelPosition(
5196
+ waypoints,
5197
+ labelWidth,
5198
+ labelHeight,
5199
+ sourceId,
5200
+ targetId
5201
+ );
5202
+ this.placedEdgeLabels.push({
5203
+ x: labelPos.x,
5204
+ y: labelPos.y,
5205
+ width: labelWidth,
5206
+ height: labelHeight
5207
+ });
5433
5208
  edgeModel.label = {
5434
5209
  bounds: {
5435
5210
  x: labelPos.x,
@@ -5442,49 +5217,137 @@ var init_diagram_builder = __esm({
5442
5217
  return edgeModel;
5443
5218
  }
5444
5219
  /**
5445
- * Calculate label position centered on the edge
5446
- * Places label at the midpoint of the edge, offset above the line
5220
+ * Calculate smart label position on the edge
5221
+ * Strategy:
5222
+ * 1. Find the longest segment of the edge (best visibility)
5223
+ * 2. Place label at the midpoint of that segment
5224
+ * 3. Offset based on segment direction, avoiding overlap with source/target nodes
5447
5225
  */
5448
- calculateEdgeLabelPosition(waypoints, labelWidth, labelHeight) {
5226
+ calculateSmartLabelPosition(waypoints, labelWidth, labelHeight, sourceId, targetId) {
5449
5227
  if (waypoints.length < 2) {
5450
5228
  return { x: 0, y: 0 };
5451
5229
  }
5452
- const totalLength = calculatePathLength(waypoints);
5453
- const halfLength = totalLength / 2;
5454
- let accumulatedLength = 0;
5230
+ const sourcePos = sourceId ? this.nodePositions.get(sourceId) : void 0;
5231
+ const targetPos = targetId ? this.nodePositions.get(targetId) : void 0;
5232
+ let bestSegmentIndex = -1;
5233
+ let bestSegmentLength = 0;
5455
5234
  for (let i = 0; i < waypoints.length - 1; i++) {
5456
5235
  const wpCurrent = waypoints[i];
5457
5236
  const wpNext = waypoints[i + 1];
5458
5237
  if (!wpCurrent || !wpNext) continue;
5459
- const segmentLength = distance(wpCurrent, wpNext);
5460
- const nextAccumulated = accumulatedLength + segmentLength;
5461
- if (nextAccumulated >= halfLength) {
5462
- const ratio = (halfLength - accumulatedLength) / segmentLength;
5463
- const midX = wpCurrent.x + (wpNext.x - wpCurrent.x) * ratio;
5464
- const midY = wpCurrent.y + (wpNext.y - wpCurrent.y) * ratio;
5465
- const dx = wpNext.x - wpCurrent.x;
5466
- const dy = wpNext.y - wpCurrent.y;
5467
- if (Math.abs(dy) < Math.abs(dx)) {
5468
- return {
5469
- x: midX - labelWidth / 2,
5470
- y: midY - labelHeight - 4
5471
- // 4px above the line
5472
- };
5473
- } else {
5474
- return {
5475
- x: midX - labelWidth - 4,
5476
- // 4px to the left
5477
- y: midY - labelHeight / 2
5478
- };
5238
+ const segmentLength2 = distance(wpCurrent, wpNext);
5239
+ if (segmentLength2 < 30) continue;
5240
+ const midX2 = (wpCurrent.x + wpNext.x) / 2;
5241
+ const midY2 = (wpCurrent.y + wpNext.y) / 2;
5242
+ const tooCloseToSource = sourcePos && this.isPointNearNode(midX2, midY2, sourcePos, 20);
5243
+ const tooCloseToTarget = targetPos && this.isPointNearNode(midX2, midY2, targetPos, 20);
5244
+ if (!tooCloseToSource && !tooCloseToTarget && segmentLength2 > bestSegmentLength) {
5245
+ bestSegmentLength = segmentLength2;
5246
+ bestSegmentIndex = i;
5247
+ }
5248
+ }
5249
+ if (bestSegmentIndex < 0) {
5250
+ for (let i = 0; i < waypoints.length - 1; i++) {
5251
+ const wpCurrent = waypoints[i];
5252
+ const wpNext = waypoints[i + 1];
5253
+ if (!wpCurrent || !wpNext) continue;
5254
+ const segmentLength2 = distance(wpCurrent, wpNext);
5255
+ if (segmentLength2 > bestSegmentLength) {
5256
+ bestSegmentLength = segmentLength2;
5257
+ bestSegmentIndex = i;
5258
+ }
5259
+ }
5260
+ }
5261
+ if (bestSegmentIndex < 0) {
5262
+ bestSegmentIndex = 0;
5263
+ }
5264
+ const wpStart = waypoints[bestSegmentIndex];
5265
+ const wpEnd = waypoints[bestSegmentIndex + 1];
5266
+ if (!wpStart || !wpEnd) {
5267
+ return { x: 0, y: 0 };
5268
+ }
5269
+ const midX = (wpStart.x + wpEnd.x) / 2;
5270
+ const midY = (wpStart.y + wpEnd.y) / 2;
5271
+ const dx = wpEnd.x - wpStart.x;
5272
+ const dy = wpEnd.y - wpStart.y;
5273
+ const isHorizontal = Math.abs(dx) > Math.abs(dy);
5274
+ const segmentLength = Math.sqrt(dx * dx + dy * dy);
5275
+ const offset = 5;
5276
+ if (isHorizontal) {
5277
+ let labelX = midX - labelWidth / 2;
5278
+ let labelY = midY - labelHeight - offset;
5279
+ const labelBounds = { x: labelX, y: labelY, width: labelWidth, height: labelHeight };
5280
+ if (this.labelOverlapsAnyNode(labelBounds)) {
5281
+ labelY = midY + offset;
5282
+ }
5283
+ return { x: labelX, y: labelY };
5284
+ } else {
5285
+ const positions = segmentLength > 80 ? [0.35, 0.5, 0.65, 0.2, 0.8, 0.15, 0.85] : [0.5];
5286
+ for (const ratio of positions) {
5287
+ const testY = wpStart.y + (wpEnd.y - wpStart.y) * ratio - labelHeight / 2;
5288
+ const boundsRight = { x: midX + offset, y: testY, width: labelWidth, height: labelHeight };
5289
+ if (!this.labelOverlapsAnyNode(boundsRight) && !this.labelOverlapsAnyEdgeLabel(boundsRight)) {
5290
+ return { x: midX + offset, y: testY };
5291
+ }
5292
+ const boundsLeft = { x: midX - labelWidth - offset, y: testY, width: labelWidth, height: labelHeight };
5293
+ if (!this.labelOverlapsAnyNode(boundsLeft) && !this.labelOverlapsAnyEdgeLabel(boundsLeft)) {
5294
+ return { x: midX - labelWidth - offset, y: testY };
5295
+ }
5296
+ }
5297
+ let labelX = midX + offset;
5298
+ let labelY = midY - labelHeight / 2;
5299
+ for (let yOffset = 0; yOffset <= 100; yOffset += 20) {
5300
+ for (const side of [1, -1]) {
5301
+ for (const yDir of [0, 1, -1]) {
5302
+ const testX = side === 1 ? midX + offset : midX - labelWidth - offset;
5303
+ const testY = labelY + yDir * yOffset;
5304
+ const testBounds = { x: testX, y: testY, width: labelWidth, height: labelHeight };
5305
+ if (!this.labelOverlapsAnyEdgeLabel(testBounds)) {
5306
+ if (!this.labelOverlapsAnyNode(testBounds)) {
5307
+ return { x: testX, y: testY };
5308
+ }
5309
+ labelX = testX;
5310
+ labelY = testY;
5311
+ }
5312
+ }
5479
5313
  }
5480
5314
  }
5481
- accumulatedLength = nextAccumulated;
5315
+ return { x: labelX, y: labelY };
5482
5316
  }
5483
- const lastPoint = waypoints[waypoints.length - 1];
5484
- return {
5485
- x: (lastPoint?.x ?? 0) - labelWidth / 2,
5486
- y: (lastPoint?.y ?? 0) - labelHeight - 4
5487
- };
5317
+ }
5318
+ /**
5319
+ * Check if a point is near a node (within padding distance)
5320
+ */
5321
+ isPointNearNode(x, y, nodePos, padding) {
5322
+ return x >= nodePos.x - padding && x <= nodePos.x + nodePos.width + padding && y >= nodePos.y - padding && y <= nodePos.y + nodePos.height + padding;
5323
+ }
5324
+ /**
5325
+ * Check if a label bounds overlaps with any node
5326
+ */
5327
+ labelOverlapsAnyNode(labelBounds) {
5328
+ for (const nodePos of this.nodePositions.values()) {
5329
+ if (this.boundsOverlap(labelBounds, nodePos)) {
5330
+ return true;
5331
+ }
5332
+ }
5333
+ return false;
5334
+ }
5335
+ /**
5336
+ * Check if a label bounds overlaps with any already placed edge label
5337
+ */
5338
+ labelOverlapsAnyEdgeLabel(labelBounds) {
5339
+ for (const placedLabel of this.placedEdgeLabels) {
5340
+ if (this.boundsOverlap(labelBounds, placedLabel)) {
5341
+ return true;
5342
+ }
5343
+ }
5344
+ return false;
5345
+ }
5346
+ /**
5347
+ * Check if two rectangles overlap
5348
+ */
5349
+ boundsOverlap(a, b) {
5350
+ return !(a.x + a.width < b.x || b.x + b.width < a.x || a.y + a.height < b.y || b.y + b.height < a.y);
5488
5351
  }
5489
5352
  /**
5490
5353
  * Ensure all waypoint segments are orthogonal (horizontal or vertical)
@@ -5516,6 +5379,135 @@ var init_diagram_builder = __esm({
5516
5379
  }
5517
5380
  }
5518
5381
  }
5382
+ /**
5383
+ * Detect which side of a node a point is connected to.
5384
+ * Returns 'top', 'bottom', 'left', 'right', or 'unknown'.
5385
+ *
5386
+ * First tries exact match with tolerance, then falls back to closest edge detection.
5387
+ */
5388
+ detectConnectionSide(point, nodeBounds, _isGateway = false) {
5389
+ const exactTolerance = 3;
5390
+ const maxTolerance = 15;
5391
+ const nodeTop = nodeBounds.y;
5392
+ const nodeBottom = nodeBounds.y + nodeBounds.height;
5393
+ const nodeLeft = nodeBounds.x;
5394
+ const nodeRight = nodeBounds.x + nodeBounds.width;
5395
+ if (Math.abs(point.y - nodeTop) <= exactTolerance) return "top";
5396
+ if (Math.abs(point.y - nodeBottom) <= exactTolerance) return "bottom";
5397
+ if (Math.abs(point.x - nodeLeft) <= exactTolerance) return "left";
5398
+ if (Math.abs(point.x - nodeRight) <= exactTolerance) return "right";
5399
+ const distToTop = Math.abs(point.y - nodeTop);
5400
+ const distToBottom = Math.abs(point.y - nodeBottom);
5401
+ const distToLeft = Math.abs(point.x - nodeLeft);
5402
+ const distToRight = Math.abs(point.x - nodeRight);
5403
+ const minDist = Math.min(distToTop, distToBottom, distToLeft, distToRight);
5404
+ if (minDist > maxTolerance) return "unknown";
5405
+ if (minDist === distToTop) return "top";
5406
+ if (minDist === distToBottom) return "bottom";
5407
+ if (minDist === distToLeft) return "left";
5408
+ return "right";
5409
+ }
5410
+ /**
5411
+ * Detect connection side for gateway based on edge direction.
5412
+ * When point is on a diagonal edge of the diamond, use the adjacent point to determine direction.
5413
+ */
5414
+ detectGatewayConnectionSide(point, adjacentPoint, nodeBounds, isSource) {
5415
+ const centerX = nodeBounds.x + nodeBounds.width / 2;
5416
+ const centerY = nodeBounds.y + nodeBounds.height / 2;
5417
+ const nodeTop = nodeBounds.y;
5418
+ const nodeBottom = nodeBounds.y + nodeBounds.height;
5419
+ const nodeLeft = nodeBounds.x;
5420
+ const nodeRight = nodeBounds.x + nodeBounds.width;
5421
+ const distToTopCorner = Math.abs(point.x - centerX) + Math.abs(point.y - nodeTop);
5422
+ const distToBottomCorner = Math.abs(point.x - centerX) + Math.abs(point.y - nodeBottom);
5423
+ const distToLeftCorner = Math.abs(point.x - nodeLeft) + Math.abs(point.y - centerY);
5424
+ const distToRightCorner = Math.abs(point.x - nodeRight) + Math.abs(point.y - centerY);
5425
+ const minDist = Math.min(distToTopCorner, distToBottomCorner, distToLeftCorner, distToRightCorner);
5426
+ const tolerance = 5;
5427
+ if (distToTopCorner <= minDist + tolerance && distToTopCorner < distToBottomCorner - tolerance && distToTopCorner < distToLeftCorner - tolerance && distToTopCorner < distToRightCorner - tolerance) {
5428
+ return "top";
5429
+ }
5430
+ if (distToBottomCorner <= minDist + tolerance && distToBottomCorner < distToTopCorner - tolerance && distToBottomCorner < distToLeftCorner - tolerance && distToBottomCorner < distToRightCorner - tolerance) {
5431
+ return "bottom";
5432
+ }
5433
+ if (distToLeftCorner <= minDist + tolerance && distToLeftCorner < distToTopCorner - tolerance && distToLeftCorner < distToBottomCorner - tolerance && distToLeftCorner < distToRightCorner - tolerance) {
5434
+ return "left";
5435
+ }
5436
+ if (distToRightCorner <= minDist + tolerance && distToRightCorner < distToTopCorner - tolerance && distToRightCorner < distToBottomCorner - tolerance && distToRightCorner < distToLeftCorner - tolerance) {
5437
+ return "right";
5438
+ }
5439
+ const dx = isSource ? adjacentPoint.x - point.x : point.x - adjacentPoint.x;
5440
+ const dy = isSource ? adjacentPoint.y - point.y : point.y - adjacentPoint.y;
5441
+ if (Math.abs(dx) > Math.abs(dy)) {
5442
+ return dx > 0 ? "right" : "left";
5443
+ } else {
5444
+ return dy > 0 ? "bottom" : "top";
5445
+ }
5446
+ }
5447
+ /**
5448
+ * Ensure edge endpoints connect perpendicular to node borders.
5449
+ * - Connection to top/bottom: last segment must be vertical (same x)
5450
+ * - Connection to left/right: last segment must be horizontal (same y)
5451
+ *
5452
+ * When inserting bend points, we also update the previous waypoint to maintain
5453
+ * orthogonality (no diagonal lines).
5454
+ */
5455
+ ensurePerpendicularEndpoints(waypoints, sourceId, targetId, sourceIsGateway = false, targetIsGateway = false) {
5456
+ if (waypoints.length < 2) return;
5457
+ const tolerance = 2;
5458
+ const minBendOffset = 15;
5459
+ if (targetId) {
5460
+ const targetPos = this.nodePositions.get(targetId);
5461
+ if (targetPos) {
5462
+ const lastIdx = waypoints.length - 1;
5463
+ const endPoint = waypoints[lastIdx];
5464
+ const prevPoint = waypoints[lastIdx - 1];
5465
+ if (endPoint && prevPoint) {
5466
+ const side = targetIsGateway ? this.detectGatewayConnectionSide(endPoint, prevPoint, targetPos, false) : this.detectConnectionSide(endPoint, targetPos, false);
5467
+ if (side === "top" || side === "bottom") {
5468
+ if (Math.abs(endPoint.x - prevPoint.x) > tolerance) {
5469
+ const bendY = side === "top" ? endPoint.y - minBendOffset : endPoint.y + minBendOffset;
5470
+ const bendPoint = { x: endPoint.x, y: bendY };
5471
+ waypoints.splice(lastIdx, 0, bendPoint);
5472
+ prevPoint.y = bendY;
5473
+ }
5474
+ } else if (side === "left" || side === "right") {
5475
+ if (Math.abs(endPoint.y - prevPoint.y) > tolerance) {
5476
+ const bendX = side === "left" ? endPoint.x - minBendOffset : endPoint.x + minBendOffset;
5477
+ const bendPoint = { x: bendX, y: endPoint.y };
5478
+ waypoints.splice(lastIdx, 0, bendPoint);
5479
+ prevPoint.x = bendX;
5480
+ }
5481
+ }
5482
+ }
5483
+ }
5484
+ }
5485
+ if (sourceId) {
5486
+ const sourcePos = this.nodePositions.get(sourceId) ?? this.boundaryEventPositions.get(sourceId);
5487
+ if (sourcePos) {
5488
+ const startPoint = waypoints[0];
5489
+ const nextPoint = waypoints[1];
5490
+ if (startPoint && nextPoint) {
5491
+ const side = sourceIsGateway ? this.detectGatewayConnectionSide(startPoint, nextPoint, sourcePos, true) : this.detectConnectionSide(startPoint, sourcePos, false);
5492
+ if (side === "top" || side === "bottom") {
5493
+ if (Math.abs(startPoint.x - nextPoint.x) > tolerance) {
5494
+ const bendY = side === "top" ? startPoint.y - minBendOffset : startPoint.y + minBendOffset;
5495
+ const bendPoint = { x: startPoint.x, y: bendY };
5496
+ waypoints.splice(1, 0, bendPoint);
5497
+ nextPoint.y = bendY;
5498
+ }
5499
+ } else if (side === "left" || side === "right") {
5500
+ if (Math.abs(startPoint.y - nextPoint.y) > tolerance) {
5501
+ const bendX = side === "left" ? startPoint.x - minBendOffset : startPoint.x + minBendOffset;
5502
+ const bendPoint = { x: bendX, y: startPoint.y };
5503
+ waypoints.splice(1, 0, bendPoint);
5504
+ nextPoint.x = bendX;
5505
+ }
5506
+ }
5507
+ }
5508
+ }
5509
+ }
5510
+ }
5519
5511
  };
5520
5512
  }
5521
5513
  });
@@ -5570,7 +5562,10 @@ var init_model_builder = __esm({
5570
5562
  rootElements: []
5571
5563
  };
5572
5564
  for (const child of graph.children) {
5573
- const bpmnType = child.bpmn.type;
5565
+ const bpmnType = child.bpmn?.type;
5566
+ if (!bpmnType) {
5567
+ throw new Error(`Invalid graph child: missing bpmn.type property for node ${child.id}`);
5568
+ }
5574
5569
  if (bpmnType === "collaboration") {
5575
5570
  definitions.rootElements.push(this.buildCollaboration(child));
5576
5571
  const collab = child;
@@ -5584,8 +5579,13 @@ var init_model_builder = __esm({
5584
5579
  }
5585
5580
  } else if (bpmnType === "process") {
5586
5581
  definitions.rootElements.push(this.buildProcess(child));
5582
+ } else {
5583
+ throw new Error(`Invalid top-level element type: "${bpmnType}". Only "process" or "collaboration" are allowed at the top level.`);
5587
5584
  }
5588
5585
  }
5586
+ if (definitions.rootElements.length === 0) {
5587
+ throw new Error("Cannot create BPMN definitions: no valid process or collaboration found in the graph.");
5588
+ }
5589
5589
  return definitions;
5590
5590
  }
5591
5591
  /**
@@ -6453,7 +6453,10 @@ var init_converter = __esm({
6453
6453
  modelBuilder;
6454
6454
  xmlGenerator;
6455
6455
  constructor(options) {
6456
- this.layouter = new ElkLayouter({ elkOptions: options?.elkOptions });
6456
+ this.layouter = new ElkLayouter({
6457
+ elkOptions: options?.elkOptions,
6458
+ enableCompaction: options?.enableCompaction
6459
+ });
6457
6460
  this.modelBuilder = new ModelBuilder();
6458
6461
  this.xmlGenerator = new BpmnXmlGenerator();
6459
6462
  }