@aranzatech/diagrams-bpmn 0.3.0 → 0.3.1

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.
@@ -1311,63 +1311,197 @@ function assignRows(nodes, forwardEdges, columns, gatewayPairs) {
1311
1311
 
1312
1312
  // src/layout/bpmn-custom-layout.ts
1313
1313
  var LANE_LABEL_W = 28;
1314
- var LANE_H_PAD = 20;
1315
- var COL_GAP = 80;
1316
- var ROW_HEIGHT = 80;
1317
- var ROW_GAP = 60;
1318
- var LANE_V_PAD = 50;
1319
- var POOL_H_PAD = 60;
1320
- var POOL_V_GAP = 50;
1321
- var LANE_MIN_H = 160;
1322
- var POOL_MIN_W = 720;
1323
- var POOL_INNER_PAD = 8;
1314
+ var LANE_H_PAD = 40;
1315
+ var COL_GAP = 100;
1316
+ var ROW_HEIGHT = 100;
1317
+ var ROW_GAP = 80;
1318
+ var LANE_V_PAD = 60;
1319
+ var POOL_H_PAD = 80;
1320
+ var POOL_V_GAP = 60;
1321
+ var LANE_MIN_H = 200;
1322
+ var POOL_MIN_W = 840;
1323
+ var POOL_INNER_PAD = 10;
1324
+ var BACK_EDGE_CLEARANCE = 70;
1325
+ var LAYOUT_ARTIFACT_TYPES = /* @__PURE__ */ new Set([
1326
+ "DataObject",
1327
+ "DataObjectReference",
1328
+ "DataInput",
1329
+ "DataOutput",
1330
+ "DataStore",
1331
+ "DataStoreReference",
1332
+ "Annotation",
1333
+ "Group"
1334
+ ]);
1335
+ var COLLAPSED_SUBPROCESS_TYPES = /* @__PURE__ */ new Set([
1336
+ "SubProcess",
1337
+ "Transaction",
1338
+ "EventSubProcess",
1339
+ "AdHocSubProcess"
1340
+ ]);
1324
1341
  function nW(node) {
1325
1342
  return node.width ?? node.measured?.width ?? 120;
1326
1343
  }
1327
1344
  function nH(node) {
1328
1345
  return node.height ?? node.measured?.height ?? 60;
1329
1346
  }
1347
+ var BOUNDARY_SPACING = 10;
1348
+ function repositionBoundaryEvents(boundaryEvents, positionedContent) {
1349
+ if (boundaryEvents.length === 0) return [];
1350
+ const hostById = new Map(positionedContent.map((n) => [n.id, n]));
1351
+ const byHost = /* @__PURE__ */ new Map();
1352
+ for (const be of boundaryEvents) {
1353
+ const hostId = be.data.attachedToRef;
1354
+ if (!hostId) continue;
1355
+ if (!byHost.has(hostId)) byHost.set(hostId, []);
1356
+ byHost.get(hostId).push(be);
1357
+ }
1358
+ const result = [];
1359
+ for (const be of boundaryEvents) {
1360
+ const hostId = be.data.attachedToRef;
1361
+ if (!hostId) {
1362
+ result.push(be);
1363
+ continue;
1364
+ }
1365
+ const host = hostById.get(hostId);
1366
+ if (!host) {
1367
+ result.push(be);
1368
+ continue;
1369
+ }
1370
+ const hostGroup = byHost.get(hostId);
1371
+ const siblingIdx = hostGroup.findIndex((n) => n.id === be.id);
1372
+ const hostW = nW(host);
1373
+ const hostH = nH(host);
1374
+ const beH = nH(be);
1375
+ const totalGroupW = hostGroup.reduce((s, b) => s + nW(b), 0) + (hostGroup.length - 1) * BOUNDARY_SPACING;
1376
+ const groupStartX = host.position.x + hostW / 2 - totalGroupW / 2;
1377
+ const offsetX = hostGroup.slice(0, siblingIdx).reduce((s, b) => s + nW(b) + BOUNDARY_SPACING, 0);
1378
+ result.push({
1379
+ ...be,
1380
+ position: {
1381
+ x: groupStartX + offsetX,
1382
+ y: host.position.y + hostH - beH / 2
1383
+ }
1384
+ });
1385
+ }
1386
+ return result;
1387
+ }
1388
+ var SP_PAD = 48;
1389
+ function layoutSubProcess(children, edges) {
1390
+ if (children.length === 0) {
1391
+ return { children: [], width: 280, height: 160 };
1392
+ }
1393
+ const boundaryEvents = children.filter((n) => n.data.elementType === "BoundaryEvent");
1394
+ const mainChildren = children.filter((n) => n.data.elementType !== "BoundaryEvent");
1395
+ if (mainChildren.length === 0) {
1396
+ return {
1397
+ children: repositionBoundaryEvents(boundaryEvents, []),
1398
+ width: 280,
1399
+ height: 160
1400
+ };
1401
+ }
1402
+ const contentIds = new Set(mainChildren.map((n) => n.id));
1403
+ const seqEdges = extractSeqEdges(edges, contentIds);
1404
+ const backIds = detectBackEdges([...contentIds], seqEdges);
1405
+ const fwdEdges = seqEdges.filter((e) => !backIds.has(e.id));
1406
+ const columns = assignColumns([...contentIds], fwdEdges);
1407
+ const pairs = detectGatewayPairs(mainChildren, fwdEdges);
1408
+ const rows = assignRows(mainChildren, fwdEdges, columns, pairs);
1409
+ const maxCol = Math.max(0, ...[...columns.values()]);
1410
+ const colW = /* @__PURE__ */ new Map();
1411
+ for (let c = 0; c <= maxCol; c++) colW.set(c, 0);
1412
+ for (const node of mainChildren) {
1413
+ const c = columns.get(node.id) ?? 0;
1414
+ colW.set(c, Math.max(colW.get(c) ?? 0, nW(node)));
1415
+ }
1416
+ const colX = /* @__PURE__ */ new Map();
1417
+ let cumX = 0;
1418
+ for (let c = 0; c <= maxCol; c++) {
1419
+ colX.set(c, cumX);
1420
+ cumX += (colW.get(c) ?? 120) + COL_GAP;
1421
+ }
1422
+ const contentW = cumX - COL_GAP;
1423
+ const rowVals = [...rows.values()];
1424
+ const minRow = Math.min(0, ...rowVals);
1425
+ const maxRow = Math.max(0, ...rowVals);
1426
+ const rowCount = maxRow - minRow + 1;
1427
+ const contentH = rowCount * ROW_HEIGHT + Math.max(0, rowCount - 1) * ROW_GAP;
1428
+ const spW = Math.max(280, SP_PAD * 2 + contentW);
1429
+ const spH = Math.max(160, SP_PAD * 2 + contentH);
1430
+ const positionedChildren = mainChildren.map((node) => {
1431
+ const c = columns.get(node.id) ?? 0;
1432
+ const r = rows.get(node.id) ?? 0;
1433
+ const colOffset = (colX.get(c) ?? 0) + (colW.get(c) ?? 120) / 2 - nW(node) / 2;
1434
+ const rowOffset = (r - minRow) * (ROW_HEIGHT + ROW_GAP) + ROW_HEIGHT / 2 - nH(node) / 2;
1435
+ return {
1436
+ ...node,
1437
+ position: { x: SP_PAD + colOffset, y: SP_PAD + rowOffset }
1438
+ };
1439
+ });
1440
+ const positionedBoundaries = repositionBoundaryEvents(boundaryEvents, positionedChildren);
1441
+ return {
1442
+ children: [...positionedChildren, ...positionedBoundaries],
1443
+ width: spW,
1444
+ height: spH
1445
+ };
1446
+ }
1330
1447
  function layoutPool(pool, lanes, content, allEdges) {
1331
- if (content.length === 0) {
1448
+ const boundaryEvents = content.filter((n) => n.data.elementType === "BoundaryEvent");
1449
+ const mainContent = content.filter((n) => n.data.elementType !== "BoundaryEvent");
1450
+ if (mainContent.length === 0) {
1451
+ const positionedBoundaries2 = repositionBoundaryEvents(boundaryEvents, []);
1332
1452
  if (lanes.length === 0) {
1333
- return { nodes: [], width: 240, height: 120 };
1453
+ return { nodes: positionedBoundaries2, width: 300, height: 140 };
1334
1454
  }
1335
1455
  const laneH = LANE_MIN_H;
1336
1456
  const h = POOL_INNER_PAD * 2 + lanes.length * laneH + Math.max(0, lanes.length - 1) * POOL_INNER_PAD;
1337
- const w = POOL_MIN_W;
1338
1457
  return {
1339
- nodes: lanes.map((lane, i) => ({
1340
- ...lane,
1341
- position: { x: POOL_INNER_PAD, y: POOL_INNER_PAD + i * (laneH + POOL_INNER_PAD) },
1342
- width: w - POOL_INNER_PAD * 2,
1343
- height: laneH
1344
- })),
1345
- width: w,
1458
+ nodes: [
1459
+ ...positionedBoundaries2,
1460
+ ...lanes.map((lane, i) => ({
1461
+ ...lane,
1462
+ position: { x: POOL_INNER_PAD, y: POOL_INNER_PAD + i * (laneH + POOL_INNER_PAD) },
1463
+ width: POOL_MIN_W - POOL_INNER_PAD * 2,
1464
+ height: laneH
1465
+ }))
1466
+ ],
1467
+ width: POOL_MIN_W,
1346
1468
  height: h
1347
1469
  };
1348
1470
  }
1349
- const contentIds = new Set(content.map((n) => n.id));
1350
- const seqEdges = extractSeqEdges(allEdges, contentIds);
1351
- const backIds = detectBackEdges([...contentIds], seqEdges);
1352
- const fwdEdges = seqEdges.filter((e) => !backIds.has(e.id));
1353
- const columns = assignColumns([...contentIds], fwdEdges);
1354
- const pairs = detectGatewayPairs(content, fwdEdges);
1355
- const rows = assignRows(content, fwdEdges, columns, pairs);
1356
1471
  const lanePositionsDistinct = lanes.some((l) => Math.abs(l.position.y) > 10);
1357
1472
  const sortedLanes = lanePositionsDistinct ? [...lanes].sort((a, b) => a.position.y - b.position.y) : [...lanes];
1358
1473
  const hasLanes = sortedLanes.length > 0;
1474
+ const laneIdSet = new Set(sortedLanes.map((l) => l.id));
1359
1475
  const nodeLaneId = /* @__PURE__ */ new Map();
1360
- for (const node of content) {
1361
- if (hasLanes && node.parentId && node.parentId !== pool.id) {
1362
- nodeLaneId.set(node.id, node.parentId);
1363
- } else {
1364
- nodeLaneId.set(node.id, "_pool_");
1365
- }
1476
+ for (const node of mainContent) {
1477
+ const lId = hasLanes && node.parentId && laneIdSet.has(node.parentId) ? node.parentId : "_pool_";
1478
+ nodeLaneId.set(node.id, lId);
1479
+ }
1480
+ const contentIds = new Set(mainContent.map((n) => n.id));
1481
+ const seqEdges = extractSeqEdges(allEdges, contentIds);
1482
+ const backIds = detectBackEdges([...contentIds], seqEdges);
1483
+ const fwdEdges = seqEdges.filter((e) => !backIds.has(e.id));
1484
+ const columns = assignColumns([...contentIds], fwdEdges);
1485
+ const rows = /* @__PURE__ */ new Map();
1486
+ const contentByLane = /* @__PURE__ */ new Map();
1487
+ for (const node of mainContent) {
1488
+ const lId = nodeLaneId.get(node.id) ?? "_pool_";
1489
+ if (!contentByLane.has(lId)) contentByLane.set(lId, []);
1490
+ contentByLane.get(lId).push(node);
1491
+ }
1492
+ for (const [, laneNodes] of contentByLane) {
1493
+ const laneNodeIds = new Set(laneNodes.map((n) => n.id));
1494
+ const intraEdges = fwdEdges.filter(
1495
+ (e) => laneNodeIds.has(e.source) && laneNodeIds.has(e.target)
1496
+ );
1497
+ const lanePairs = detectGatewayPairs(laneNodes, intraEdges);
1498
+ const laneRows = assignRows(laneNodes, intraEdges, columns, lanePairs);
1499
+ for (const [id, row] of laneRows) rows.set(id, row);
1366
1500
  }
1367
1501
  const laneIds = hasLanes ? sortedLanes.map((l) => l.id) : ["_pool_"];
1368
1502
  const laneStats = /* @__PURE__ */ new Map();
1369
1503
  for (const laneId of laneIds) {
1370
- const laneRows = content.filter((n) => nodeLaneId.get(n.id) === laneId).map((n) => rows.get(n.id) ?? 0);
1504
+ const laneRows = mainContent.filter((n) => nodeLaneId.get(n.id) === laneId).map((n) => rows.get(n.id) ?? 0);
1371
1505
  if (laneRows.length === 0) {
1372
1506
  laneStats.set(laneId, { minRow: 0, maxRow: 0, rowCount: 1, height: LANE_MIN_H });
1373
1507
  } else {
@@ -1382,7 +1516,7 @@ function layoutPool(pool, lanes, content, allEdges) {
1382
1516
  const maxCol = Math.max(0, ...[...columns.values()]);
1383
1517
  const colW = /* @__PURE__ */ new Map();
1384
1518
  for (let c = 0; c <= maxCol; c++) colW.set(c, 0);
1385
- for (const node of content) {
1519
+ for (const node of mainContent) {
1386
1520
  const c = columns.get(node.id) ?? 0;
1387
1521
  colW.set(c, Math.max(colW.get(c) ?? 0, nW(node)));
1388
1522
  }
@@ -1405,7 +1539,7 @@ function layoutPool(pool, lanes, content, allEdges) {
1405
1539
  if (i < laneIds.length - 1) cumY += POOL_INNER_PAD;
1406
1540
  }
1407
1541
  const poolH = cumY + POOL_INNER_PAD;
1408
- const positionedContent = content.map((node) => {
1542
+ const positionedContent = mainContent.map((node) => {
1409
1543
  const c = columns.get(node.id) ?? 0;
1410
1544
  const r = rows.get(node.id) ?? 0;
1411
1545
  const laneId = nodeLaneId.get(node.id) ?? "_pool_";
@@ -1413,14 +1547,14 @@ function layoutPool(pool, lanes, content, allEdges) {
1413
1547
  const lYOff = laneY.get(laneId) ?? 0;
1414
1548
  const colOffset = (colX.get(c) ?? 0) + (colW.get(c) ?? 120) / 2 - nW(node) / 2;
1415
1549
  const rowOffset = (r - stat.minRow) * (ROW_HEIGHT + ROW_GAP) + ROW_HEIGHT / 2 - nH(node) / 2;
1416
- const isLaneChild = hasLanes && !!node.parentId && node.parentId !== pool.id;
1417
1550
  const contentH_stat = stat.rowCount * ROW_HEIGHT + Math.max(0, stat.rowCount - 1) * ROW_GAP;
1418
1551
  const vertTopOffset = Math.max(LANE_V_PAD, (stat.height - contentH_stat) / 2);
1552
+ const isLaneChild = hasLanes && !!node.parentId && node.parentId !== pool.id;
1419
1553
  const x = isLaneChild ? LANE_LABEL_W + LANE_H_PAD + colOffset : POOL_H_PAD + colOffset;
1420
1554
  const y = isLaneChild ? vertTopOffset + rowOffset : lYOff + vertTopOffset + rowOffset;
1421
1555
  return { ...node, position: { x, y } };
1422
1556
  });
1423
- const NODE_MIN_GAP = 20;
1557
+ const NODE_MIN_GAP = 24;
1424
1558
  const resolvedContent = [...positionedContent];
1425
1559
  for (const laneId of laneIds) {
1426
1560
  const laneNodeIndices = resolvedContent.map((n, i) => ({ n, i })).filter(({ n }) => (nodeLaneId.get(n.id) ?? "_pool_") === laneId).sort((a, b) => a.n.position.x - b.n.position.x);
@@ -1437,30 +1571,24 @@ function layoutPool(pool, lanes, content, allEdges) {
1437
1571
  if (prevRight + NODE_MIN_GAP > curr.position.x) {
1438
1572
  resolvedContent[laneNodeIndices[k].i] = {
1439
1573
  ...curr,
1440
- position: {
1441
- x: prevRight + NODE_MIN_GAP,
1442
- y: curr.position.y
1443
- }
1574
+ position: { x: prevRight + NODE_MIN_GAP, y: curr.position.y }
1444
1575
  };
1445
1576
  }
1446
1577
  }
1447
1578
  }
1579
+ const positionedBoundaries = repositionBoundaryEvents(boundaryEvents, resolvedContent);
1448
1580
  const positionedLanes = hasLanes ? sortedLanes.map((lane) => ({
1449
1581
  ...lane,
1450
- // x: after pool label strip + left inner padding
1451
- // y: laneY already includes top POOL_INNER_PAD offset
1452
1582
  position: { x: POOL_INNER_PAD, y: laneY.get(lane.id) ?? POOL_INNER_PAD },
1453
1583
  width: laneW,
1454
1584
  height: laneStats.get(lane.id)?.height ?? LANE_MIN_H
1455
1585
  })) : [];
1456
1586
  return {
1457
- nodes: [...resolvedContent, ...positionedLanes],
1587
+ nodes: [...resolvedContent, ...positionedBoundaries, ...positionedLanes],
1458
1588
  width: poolW,
1459
1589
  height: poolH
1460
1590
  };
1461
1591
  }
1462
- var BACK_EDGE_CLEARANCE = 50;
1463
- var SAME_ROW_THRESHOLD = 15;
1464
1592
  function absolutePos(nodeId, byId, cache) {
1465
1593
  const cached = cache.get(nodeId);
1466
1594
  if (cached) return cached;
@@ -1479,10 +1607,17 @@ function absolutePos(nodeId, byId, cache) {
1479
1607
  function laneOf(node, laneIds) {
1480
1608
  return node.parentId && laneIds.has(node.parentId) ? node.parentId : void 0;
1481
1609
  }
1610
+ function gapMidX(sAbs, sW, tAbs, tW) {
1611
+ const leftRightEdge = Math.min(sAbs.x + sW, tAbs.x + tW);
1612
+ const rightLeftEdge = Math.max(sAbs.x, tAbs.x);
1613
+ return leftRightEdge < rightLeftEdge ? (leftRightEdge + rightLeftEdge) / 2 : sAbs.x + sW / 2;
1614
+ }
1615
+ var SAME_ROW_THRESHOLD = 15;
1482
1616
  function routeEdges(edges, layoutNodes, backEdgeIds, laneIds, poolIds) {
1483
1617
  const byId = new Map(layoutNodes.map((n) => [n.id, n]));
1484
1618
  const cache = /* @__PURE__ */ new Map();
1485
1619
  const abs = (id) => absolutePos(id, byId, cache);
1620
+ const sortedLanes = [...laneIds].map((id) => byId.get(id)).filter((n) => !!n).sort((a, b) => abs(a.id).y - abs(b.id).y);
1486
1621
  return edges.map((edge) => {
1487
1622
  const edgeType = edge.data?.edgeType ?? edge.type;
1488
1623
  if (edgeType !== "sequenceFlow") {
@@ -1497,11 +1632,22 @@ function routeEdges(edges, layoutNodes, backEdgeIds, laneIds, poolIds) {
1497
1632
  const tAbs = abs(tgt.id);
1498
1633
  const sW = nW(src), sH = nH(src);
1499
1634
  const tW = nW(tgt), tH = nH(tgt);
1500
- const sCX = sAbs.x + sW / 2, sCY = sAbs.y + sH / 2;
1501
- const tCX = tAbs.x + tW / 2, tCY = tAbs.y + tH / 2;
1502
- const srcPool = poolIds.has(src.parentId ?? "") ? src.parentId : poolIds.has(byId.get(src.parentId ?? "")?.parentId ?? "") ? byId.get(src.parentId ?? "")?.parentId : src.parentId;
1503
- const tgtPool = poolIds.has(tgt.parentId ?? "") ? tgt.parentId : poolIds.has(byId.get(tgt.parentId ?? "")?.parentId ?? "") ? byId.get(tgt.parentId ?? "")?.parentId : tgt.parentId;
1504
- if (srcPool !== tgtPool) {
1635
+ const sCX = sAbs.x + sW / 2;
1636
+ const tCX = tAbs.x + tW / 2;
1637
+ const sCY = sAbs.y + sH / 2;
1638
+ const tCY = tAbs.y + tH / 2;
1639
+ const getPool = (nodeId) => {
1640
+ const node = byId.get(nodeId);
1641
+ if (!node) return null;
1642
+ if (poolIds.has(nodeId)) return nodeId;
1643
+ if (node.parentId && poolIds.has(node.parentId)) return node.parentId;
1644
+ if (node.parentId) {
1645
+ const gp = byId.get(node.parentId)?.parentId ?? null;
1646
+ if (gp && poolIds.has(gp)) return gp;
1647
+ }
1648
+ return node.parentId ?? null;
1649
+ };
1650
+ if (getPool(edge.source) !== getPool(edge.target)) {
1505
1651
  const d = { ...edge.data };
1506
1652
  delete d.routingPoints;
1507
1653
  return { ...edge, data: d };
@@ -1511,33 +1657,49 @@ function routeEdges(edges, layoutNodes, backEdgeIds, laneIds, poolIds) {
1511
1657
  const topY = Math.min(sAbs.y, tAbs.y) - BACK_EDGE_CLEARANCE;
1512
1658
  routingPoints = [
1513
1659
  { x: sCX, y: sAbs.y },
1514
- // [0] discarded
1515
1660
  { x: sCX, y: topY },
1516
- // [1] go up
1517
1661
  { x: tCX, y: topY },
1518
- // [2] go left/right
1519
- { x: tCX, y: tAbs.y }
1520
- // [3] discarded
1662
+ { x: tCX, y: tAbs.y + tH }
1521
1663
  ];
1522
1664
  } else if (laneOf(src, laneIds) !== laneOf(tgt, laneIds)) {
1523
- const goingDown = tAbs.y > sAbs.y;
1524
- const sharedX = Math.abs(sCX - tCX) < 10 ? sCX : (sCX + tCX) / 2;
1525
1665
  const srcLane = byId.get(src.parentId ?? "");
1526
1666
  const tgtLane = byId.get(tgt.parentId ?? "");
1527
1667
  if (srcLane && tgtLane && laneIds.has(srcLane.id) && laneIds.has(tgtLane.id)) {
1528
- const srcLaneAbs = abs(srcLane.id);
1529
- const borderY = goingDown ? srcLaneAbs.y + (srcLane.height ?? 160) : srcLaneAbs.y;
1530
- routingPoints = [
1531
- { x: sCX, y: goingDown ? sAbs.y + sH : sAbs.y },
1532
- // [0] discarded
1533
- { x: sharedX, y: goingDown ? sAbs.y + sH : sAbs.y },
1534
- // [1] bend: horizontal exit
1535
- { x: sharedX, y: borderY },
1536
- // [2] bend: lane border
1537
- { x: sharedX, y: goingDown ? tAbs.y : tAbs.y + tH }
1538
- // [3] discarded
1539
- ];
1668
+ const goingDown = tAbs.y > sAbs.y;
1669
+ const sharedX = gapMidX(sAbs, sW, tAbs, tW);
1670
+ const srcLaneIdx = sortedLanes.findIndex((l) => l.id === srcLane.id);
1671
+ const tgtLaneIdx = sortedLanes.findIndex((l) => l.id === tgtLane.id);
1672
+ const pts = [];
1673
+ if (goingDown) {
1674
+ pts.push({ x: sCX, y: sAbs.y + sH });
1675
+ pts.push({ x: sharedX, y: sAbs.y + sH });
1676
+ const fromIdx = Math.min(srcLaneIdx, tgtLaneIdx);
1677
+ const toIdx = Math.max(srcLaneIdx, tgtLaneIdx);
1678
+ for (let i = fromIdx; i < toIdx; i++) {
1679
+ const lane = sortedLanes[i];
1680
+ const laneBot = abs(lane.id).y + (lane.height ?? LANE_MIN_H);
1681
+ pts.push({ x: sharedX, y: laneBot });
1682
+ }
1683
+ const lastY = pts[pts.length - 1].y;
1684
+ pts.push({ x: tCX, y: lastY });
1685
+ pts.push({ x: tCX, y: tAbs.y + tH });
1686
+ } else {
1687
+ pts.push({ x: sCX, y: sAbs.y });
1688
+ pts.push({ x: sharedX, y: sAbs.y });
1689
+ const fromIdx = Math.min(srcLaneIdx, tgtLaneIdx);
1690
+ const toIdx = Math.max(srcLaneIdx, tgtLaneIdx);
1691
+ for (let i = toIdx; i > fromIdx; i--) {
1692
+ const lane = sortedLanes[i];
1693
+ const laneTop = abs(lane.id).y;
1694
+ pts.push({ x: sharedX, y: laneTop });
1695
+ }
1696
+ const lastY = pts[pts.length - 1].y;
1697
+ pts.push({ x: tCX, y: lastY });
1698
+ pts.push({ x: tCX, y: tAbs.y });
1699
+ }
1700
+ routingPoints = pts;
1540
1701
  } else {
1702
+ const sharedX = gapMidX(sAbs, sW, tAbs, tW);
1541
1703
  routingPoints = [
1542
1704
  { x: sCX, y: sCY },
1543
1705
  { x: sharedX, y: sCY },
@@ -1556,39 +1718,108 @@ function routeEdges(edges, layoutNodes, backEdgeIds, laneIds, poolIds) {
1556
1718
  if (exitsTop || exitsBottom) {
1557
1719
  routingPoints = [
1558
1720
  { x: sCX, y: exitsTop ? sAbs.y : sAbs.y + sH },
1559
- // [0] discarded
1560
1721
  { x: sCX, y: tCY },
1561
- // [1] vertical to target row
1562
1722
  { x: tCX, y: tCY },
1563
- // [2] horizontal to target
1564
1723
  { x: tCX, y: exitsTop ? tAbs.y + tH : tAbs.y }
1565
- // [3] discarded
1566
1724
  ];
1567
1725
  } else {
1568
- const goingRight = tAbs.x >= sAbs.x;
1569
- const midX = goingRight ? sAbs.x + sW + COL_GAP / 2 : sAbs.x - COL_GAP / 2;
1726
+ const midX = gapMidX(sAbs, sW, tAbs, tW);
1570
1727
  routingPoints = [
1571
1728
  { x: sAbs.x + sW, y: sCY },
1572
- // [0] discarded
1573
1729
  { x: midX, y: sCY },
1574
- // [1] horizontal exit
1575
1730
  { x: midX, y: tCY },
1576
- // [2] vertical to target row
1577
1731
  { x: tAbs.x, y: tCY }
1578
- // [3] discarded
1579
1732
  ];
1580
1733
  }
1581
1734
  }
1582
1735
  return { ...edge, data: { ...edge.data, routingPoints } };
1583
1736
  });
1584
1737
  }
1738
+ var ARTIFACT_ABOVE_GAP = 16;
1739
+ var ARTIFACT_H_SPACING = 12;
1740
+ function positionArtifacts(artifacts, resultNodes, edges) {
1741
+ if (artifacts.length === 0) return [];
1742
+ const byId = new Map(resultNodes.map((n) => [n.id, n]));
1743
+ const cache = /* @__PURE__ */ new Map();
1744
+ const absPos = (id) => absolutePos(id, byId, cache);
1745
+ const artifactsByNode = /* @__PURE__ */ new Map();
1746
+ const ungrouped = [];
1747
+ for (const artifact of artifacts) {
1748
+ if (artifact.data.elementType === "Group") {
1749
+ ungrouped.push(artifact);
1750
+ continue;
1751
+ }
1752
+ const connEdge = edges.find((e) => {
1753
+ const t = e.data?.edgeType ?? e.type;
1754
+ return (t === "association" || t === "dataAssociation") && (e.source === artifact.id || e.target === artifact.id);
1755
+ });
1756
+ if (!connEdge) {
1757
+ ungrouped.push(artifact);
1758
+ continue;
1759
+ }
1760
+ const connId = connEdge.source === artifact.id ? connEdge.target : connEdge.source;
1761
+ if (!byId.has(connId)) {
1762
+ ungrouped.push(artifact);
1763
+ continue;
1764
+ }
1765
+ if (!artifactsByNode.has(connId)) artifactsByNode.set(connId, []);
1766
+ artifactsByNode.get(connId).push(artifact);
1767
+ }
1768
+ const positioned = [...ungrouped];
1769
+ for (const [connId, arts] of artifactsByNode) {
1770
+ const connNode = byId.get(connId);
1771
+ const connAbsP = absPos(connId);
1772
+ const totalW = arts.reduce((s, a) => s + nW(a), 0) + (arts.length - 1) * ARTIFACT_H_SPACING;
1773
+ const desiredAbsY = connAbsP.y - ARTIFACT_ABOVE_GAP - Math.max(...arts.map(nH));
1774
+ let desiredAbsX = connAbsP.x + nW(connNode) / 2 - totalW / 2;
1775
+ for (const artifact of arts) {
1776
+ const parentAbsP = artifact.parentId ? absPos(artifact.parentId) : { x: 0, y: 0 };
1777
+ positioned.push({
1778
+ ...artifact,
1779
+ position: {
1780
+ x: desiredAbsX - parentAbsP.x,
1781
+ y: desiredAbsY - parentAbsP.y
1782
+ }
1783
+ });
1784
+ desiredAbsX += nW(artifact) + ARTIFACT_H_SPACING;
1785
+ }
1786
+ }
1787
+ return positioned;
1788
+ }
1585
1789
  async function bpmnCustomLayout(nodes, edges) {
1586
- const pools = nodes.filter((n) => n.data.elementType === "Pool");
1587
- const lanes = nodes.filter((n) => n.data.elementType === "Lane");
1588
- const content = nodes.filter((n) => !LAYOUT_CONTAINER_TYPES.has(n.data.elementType));
1790
+ const artifacts = nodes.filter((n) => LAYOUT_ARTIFACT_TYPES.has(n.data.elementType));
1791
+ const mainNodes = nodes.filter((n) => !LAYOUT_ARTIFACT_TYPES.has(n.data.elementType));
1792
+ const pools = mainNodes.filter((n) => n.data.elementType === "Pool");
1793
+ const lanes = mainNodes.filter((n) => n.data.elementType === "Lane");
1794
+ const expandedSubProcesses = mainNodes.filter(
1795
+ (n) => COLLAPSED_SUBPROCESS_TYPES.has(n.data.elementType) && n.data.isExpanded
1796
+ );
1797
+ const subProcessLayouts = /* @__PURE__ */ new Map();
1798
+ const workingMainNodes = [...mainNodes];
1799
+ for (const sp of expandedSubProcesses) {
1800
+ const spChildren = workingMainNodes.filter((n) => n.parentId === sp.id);
1801
+ const result = layoutSubProcess(spChildren, edges);
1802
+ subProcessLayouts.set(sp.id, result);
1803
+ const spIdx = workingMainNodes.findIndex((n) => n.id === sp.id);
1804
+ if (spIdx >= 0) {
1805
+ workingMainNodes[spIdx] = {
1806
+ ...workingMainNodes[spIdx],
1807
+ width: result.width,
1808
+ height: result.height,
1809
+ measured: { width: result.width, height: result.height }
1810
+ };
1811
+ }
1812
+ }
1813
+ const content = workingMainNodes.filter((n) => {
1814
+ if (!LAYOUT_CONTAINER_TYPES.has(n.data.elementType)) return true;
1815
+ if (COLLAPSED_SUBPROCESS_TYPES.has(n.data.elementType)) return true;
1816
+ return false;
1817
+ });
1589
1818
  if (pools.length === 0) {
1590
1819
  const { bpmnElkLayout: bpmnElkLayout2 } = await Promise.resolve().then(() => (init_elk(), elk_exports));
1591
- return bpmnElkLayout2(nodes, edges);
1820
+ const elkResult = await bpmnElkLayout2(nodes.filter((n) => !LAYOUT_ARTIFACT_TYPES.has(n.data.elementType)), edges);
1821
+ const posArtifacts = positionArtifacts(artifacts, elkResult.nodes, edges);
1822
+ return { nodes: [...elkResult.nodes, ...posArtifacts], edges: elkResult.edges };
1592
1823
  }
1593
1824
  const poolIds = new Set(pools.map((p) => p.id));
1594
1825
  const allLaneIds = new Set(lanes.map((l) => l.id));
@@ -1608,7 +1839,10 @@ async function bpmnCustomLayout(nodes, edges) {
1608
1839
  const poolContent = content.filter(
1609
1840
  (n) => n.parentId === pool.id || n.parentId != null && laneIds.has(n.parentId)
1610
1841
  );
1611
- const result = layoutPool(pool, poolLanes, poolContent, edges);
1842
+ const poolBoundaries = workingMainNodes.filter(
1843
+ (n) => n.data.elementType === "BoundaryEvent" && (n.parentId === pool.id || n.parentId != null && laneIds.has(n.parentId))
1844
+ );
1845
+ const result = layoutPool(pool, poolLanes, [...poolContent, ...poolBoundaries], edges);
1612
1846
  resultNodes.push({
1613
1847
  ...pool,
1614
1848
  position: { x: 0, y: stackY },
@@ -1620,12 +1854,44 @@ async function bpmnCustomLayout(nodes, edges) {
1620
1854
  }
1621
1855
  stackY += result.height + POOL_V_GAP;
1622
1856
  }
1857
+ for (const [, spLayout] of subProcessLayouts) {
1858
+ for (const child of spLayout.children) {
1859
+ resultNodes.push(child);
1860
+ }
1861
+ }
1623
1862
  const layoutted = new Set(resultNodes.map((n) => n.id));
1624
- for (const node of nodes) {
1625
- if (!layoutted.has(node.id)) resultNodes.push(node);
1863
+ const freeNodes = workingMainNodes.filter((n) => !layoutted.has(n.id));
1864
+ if (freeNodes.length > 0) {
1865
+ const freeEdges = edges.filter(
1866
+ (e) => freeNodes.some((n) => n.id === e.source || n.id === e.target)
1867
+ );
1868
+ try {
1869
+ const { bpmnElkLayout: bpmnElkLayout2 } = await Promise.resolve().then(() => (init_elk(), elk_exports));
1870
+ const elkFree = await bpmnElkLayout2(freeNodes, freeEdges);
1871
+ const offsetY = stackY;
1872
+ for (const node of elkFree.nodes) {
1873
+ resultNodes.push({
1874
+ ...node,
1875
+ position: { x: node.position.x, y: node.position.y + offsetY }
1876
+ });
1877
+ }
1878
+ const freeEdgeIds = new Set(freeEdges.map((e) => e.id));
1879
+ const elkEdgeMap = new Map(elkFree.edges.map((e) => [e.id, e]));
1880
+ for (let i = 0; i < edges.length; i++) {
1881
+ if (freeEdgeIds.has(edges[i].id) && elkEdgeMap.has(edges[i].id)) {
1882
+ edges[i] = elkEdgeMap.get(edges[i].id);
1883
+ }
1884
+ }
1885
+ } catch {
1886
+ for (const node of freeNodes) resultNodes.push(node);
1887
+ }
1626
1888
  }
1627
1889
  const routedEdges = routeEdges(edges, resultNodes, allBackEdgeIds, allLaneIds, poolIds);
1628
- return { nodes: resultNodes, edges: routedEdges };
1890
+ const positionedArtifacts = positionArtifacts(artifacts, resultNodes, edges);
1891
+ return {
1892
+ nodes: [...resultNodes, ...positionedArtifacts],
1893
+ edges: routedEdges
1894
+ };
1629
1895
  }
1630
1896
 
1631
1897
  // src/layout/index.ts