@aranzatech/diagrams-bpmn 0.3.2 → 0.3.3

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.
@@ -135,6 +135,17 @@ function detectGatewayPairs(nodes, forwardEdges) {
135
135
  }
136
136
  return pairs;
137
137
  }
138
+ function createSecondaryOffsets(count) {
139
+ if (count <= 0) return [];
140
+ const offsets = [];
141
+ let step = 1;
142
+ while (offsets.length < count) {
143
+ offsets.push(-step);
144
+ if (offsets.length < count) offsets.push(step);
145
+ step++;
146
+ }
147
+ return offsets;
148
+ }
138
149
  function handleToRowBias(handle) {
139
150
  if (!handle) return null;
140
151
  if (handle.includes("top")) return -1;
@@ -150,6 +161,52 @@ function buildHandleMap(edges) {
150
161
  }
151
162
  return map;
152
163
  }
164
+ function pickMainBranch(branchData) {
165
+ if (branchData.length === 0) return void 0;
166
+ return [...branchData].sort((a, b) => {
167
+ const aRight = a.bias === 0 ? 1 : 0;
168
+ const bRight = b.bias === 0 ? 1 : 0;
169
+ if (aRight !== bRight) return bRight - aRight;
170
+ if (a.nodeIds.length !== b.nodeIds.length) return b.nodeIds.length - a.nodeIds.length;
171
+ return a.start.localeCompare(b.start);
172
+ })[0]?.start;
173
+ }
174
+ function resolveDirectionalRow(splitRow, preferredOffset, usedRows) {
175
+ if (preferredOffset === 0 && !usedRows.has(splitRow)) return splitRow;
176
+ const direction = preferredOffset < 0 ? -1 : 1;
177
+ let distance = Math.max(1, Math.abs(preferredOffset));
178
+ while (usedRows.has(splitRow + direction * distance)) distance++;
179
+ return splitRow + direction * distance;
180
+ }
181
+ function assignBranchRowsAroundMain(branchData, splitRow) {
182
+ const assigned = /* @__PURE__ */ new Map();
183
+ const mainBranchStart = pickMainBranch(branchData);
184
+ if (mainBranchStart) assigned.set(mainBranchStart, splitRow);
185
+ const usedRows = new Set(assigned.values());
186
+ const remaining = branchData.filter((branch) => branch.start !== mainBranchStart);
187
+ const withBias = remaining.filter((branch) => branch.bias !== null && branch.bias !== 0).sort((a, b) => {
188
+ const aMag = Math.abs(a.bias ?? 0);
189
+ const bMag = Math.abs(b.bias ?? 0);
190
+ if (aMag !== bMag) return aMag - bMag;
191
+ return a.start.localeCompare(b.start);
192
+ });
193
+ const withoutBias = remaining.filter((branch) => branch.bias === null || branch.bias === 0).sort((a, b) => b.nodeIds.length - a.nodeIds.length || a.start.localeCompare(b.start));
194
+ for (const branch of withBias) {
195
+ const row = resolveDirectionalRow(splitRow, branch.bias ?? 0, usedRows);
196
+ assigned.set(branch.start, row);
197
+ usedRows.add(row);
198
+ }
199
+ const preferredOffsets = createSecondaryOffsets(withoutBias.length);
200
+ for (let i = 0; i < withoutBias.length; i++) {
201
+ let row = splitRow + preferredOffsets[i];
202
+ if (usedRows.has(row)) {
203
+ row = resolveDirectionalRow(splitRow, preferredOffsets[i], usedRows);
204
+ }
205
+ assigned.set(withoutBias[i].start, row);
206
+ usedRows.add(row);
207
+ }
208
+ return assigned;
209
+ }
153
210
  function assignRows(nodes, forwardEdges, columns, gatewayPairs) {
154
211
  const rows = new Map(nodes.map((n) => [n.id, 0]));
155
212
  const succs = new Map(nodes.map((n) => [n.id, []]));
@@ -183,24 +240,14 @@ function assignRows(nodes, forwardEdges, columns, gatewayPairs) {
183
240
  nodeIds: getBranchNodes(start),
184
241
  bias: handleToRowBias(splitHandles?.get(start) ?? null)
185
242
  }));
186
- const withBias = branchData.filter((b) => b.bias !== null && b.bias !== 0);
187
- const withoutBias = branchData.filter((b) => b.bias === null || b.bias === 0).sort((a, b) => b.nodeIds.length - a.nodeIds.length);
188
- const assigned = /* @__PURE__ */ new Map();
189
- for (const b of withBias) {
190
- assigned.set(b.start, splitRow + b.bias);
191
- }
192
- let nextOffset = 0;
193
- for (const b of withoutBias) {
194
- while ([...assigned.values()].includes(splitRow + nextOffset)) nextOffset++;
195
- assigned.set(b.start, splitRow + nextOffset);
196
- nextOffset++;
197
- }
243
+ const assigned = assignBranchRowsAroundMain(branchData, splitRow);
198
244
  for (const b of branchData) {
199
245
  const row = assigned.get(b.start) ?? splitRow;
200
246
  for (const id of b.nodeIds) {
201
247
  rows.set(id, row);
202
248
  }
203
249
  }
250
+ rows.set(mergeId, splitRow);
204
251
  }
205
252
  const pairedSplits = new Set(gatewayPairs.keys());
206
253
  const unpairedSplits = nodes.filter(
@@ -229,16 +276,7 @@ function assignRows(nodes, forwardEdges, columns, gatewayPairs) {
229
276
  nodeIds: getAllReachable(start),
230
277
  bias: handleToRowBias(splitHandles2?.get(start) ?? null)
231
278
  }));
232
- const withBias2 = branchData.filter((b) => b.bias !== null && b.bias !== 0);
233
- const withoutBias2 = branchData.filter((b) => b.bias === null || b.bias === 0).sort((a, b) => b.nodeIds.length - a.nodeIds.length);
234
- const assigned2 = /* @__PURE__ */ new Map();
235
- for (const b of withBias2) assigned2.set(b.start, splitRow + b.bias);
236
- let nextOff = 0;
237
- for (const b of withoutBias2) {
238
- while ([...assigned2.values()].includes(splitRow + nextOff)) nextOff++;
239
- assigned2.set(b.start, splitRow + nextOff);
240
- nextOff++;
241
- }
279
+ const assigned2 = assignBranchRowsAroundMain(branchData, splitRow);
242
280
  for (const b of branchData) {
243
281
  const row = assigned2.get(b.start) ?? splitRow;
244
282
  for (const id of b.nodeIds) rows.set(id, row);
@@ -260,6 +298,7 @@ var LANE_MIN_H = 180;
260
298
  var POOL_MIN_W = 720;
261
299
  var POOL_INNER_PAD = 10;
262
300
  var BACK_EDGE_CLEARANCE = 56;
301
+ var EDGE_ROUTE_PAD = 28;
263
302
  var LAYOUT_ARTIFACT_TYPES = /* @__PURE__ */ new Set([
264
303
  "DataObject",
265
304
  "DataObjectReference",
@@ -568,8 +607,27 @@ function absolutePos(nodeId, byId, cache) {
568
607
  cache.set(nodeId, abs);
569
608
  return abs;
570
609
  }
571
- function laneOf(node, laneIds) {
572
- return node.parentId && laneIds.has(node.parentId) ? node.parentId : void 0;
610
+ function findAncestorNodeId(node, candidateIds, byId) {
611
+ let current = node;
612
+ while (current?.parentId) {
613
+ if (candidateIds.has(current.parentId)) return current.parentId;
614
+ current = byId.get(current.parentId);
615
+ }
616
+ return void 0;
617
+ }
618
+ function laneOf(node, laneIds, byId) {
619
+ return findAncestorNodeId(node, laneIds, byId);
620
+ }
621
+ function poolOf(node, poolIds, byId) {
622
+ if (poolIds.has(node.id)) return node.id;
623
+ return findAncestorNodeId(node, poolIds, byId);
624
+ }
625
+ function nodeRect(node, byId, cache) {
626
+ const pos = absolutePos(node.id, byId, cache);
627
+ return { x: pos.x, y: pos.y, width: nW(node), height: nH(node) };
628
+ }
629
+ function rectContainsRect(outer, inner) {
630
+ return inner.x >= outer.x && inner.y >= outer.y && inner.x + inner.width <= outer.x + outer.width && inner.y + inner.height <= outer.y + outer.height;
573
631
  }
574
632
  function gapMidX(sAbs, sW, tAbs, tW) {
575
633
  const leftRightEdge = Math.min(sAbs.x + sW, tAbs.x + tW);
@@ -605,18 +663,9 @@ function routeEdges(edges, layoutNodes, backEdgeIds, laneIds, poolIds) {
605
663
  const tCX = tAbs.x + tW / 2;
606
664
  const sCY = sAbs.y + sH / 2;
607
665
  const tCY = tAbs.y + tH / 2;
608
- const getPool = (nodeId) => {
609
- const node = byId.get(nodeId);
610
- if (!node) return null;
611
- if (poolIds.has(nodeId)) return nodeId;
612
- if (node.parentId && poolIds.has(node.parentId)) return node.parentId;
613
- if (node.parentId) {
614
- const gp = byId.get(node.parentId)?.parentId ?? null;
615
- if (gp && poolIds.has(gp)) return gp;
616
- }
617
- return node.parentId ?? null;
618
- };
619
- if (getPool(edge.source) !== getPool(edge.target)) {
666
+ const srcPool = poolOf(src, poolIds, byId) ?? null;
667
+ const tgtPool = poolOf(tgt, poolIds, byId) ?? null;
668
+ if (srcPool !== tgtPool) {
620
669
  const d = { ...edge.data };
621
670
  delete d.routingPoints;
622
671
  return { ...edge, data: d };
@@ -625,14 +674,16 @@ function routeEdges(edges, layoutNodes, backEdgeIds, laneIds, poolIds) {
625
674
  if (backEdgeIds.has(edge.id)) {
626
675
  const topY = Math.min(sAbs.y, tAbs.y) - BACK_EDGE_CLEARANCE;
627
676
  routingPoints = [
628
- { x: sCX, y: sAbs.y },
629
- { x: sCX, y: topY },
630
- { x: tCX, y: topY },
631
- { x: tCX, y: tAbs.y + tH }
677
+ { x: sAbs.x + sW, y: sCY },
678
+ { x: sAbs.x + sW + EDGE_ROUTE_PAD, y: sCY },
679
+ { x: sAbs.x + sW + EDGE_ROUTE_PAD, y: topY },
680
+ { x: tAbs.x - EDGE_ROUTE_PAD, y: topY },
681
+ { x: tAbs.x - EDGE_ROUTE_PAD, y: tCY },
682
+ { x: tAbs.x, y: tCY }
632
683
  ];
633
- } else if (laneOf(src, laneIds) !== laneOf(tgt, laneIds)) {
634
- const srcLane = byId.get(src.parentId ?? "");
635
- const tgtLane = byId.get(tgt.parentId ?? "");
684
+ } else if (laneOf(src, laneIds, byId) !== laneOf(tgt, laneIds, byId)) {
685
+ const srcLane = byId.get(laneOf(src, laneIds, byId) ?? "");
686
+ const tgtLane = byId.get(laneOf(tgt, laneIds, byId) ?? "");
636
687
  if (srcLane && tgtLane && laneIds.has(srcLane.id) && laneIds.has(tgtLane.id)) {
637
688
  const goingDown = tAbs.y > sAbs.y;
638
689
  const sharedX = gapMidX(sAbs, sW, tAbs, tW);
@@ -640,8 +691,8 @@ function routeEdges(edges, layoutNodes, backEdgeIds, laneIds, poolIds) {
640
691
  const tgtLaneIdx = sortedLanes.findIndex((l) => l.id === tgtLane.id);
641
692
  const pts = [];
642
693
  if (goingDown) {
643
- pts.push({ x: sCX, y: sAbs.y + sH });
644
- pts.push({ x: sharedX, y: sAbs.y + sH });
694
+ pts.push({ x: sAbs.x + sW, y: sCY });
695
+ pts.push({ x: sharedX, y: sCY });
645
696
  const fromIdx = Math.min(srcLaneIdx, tgtLaneIdx);
646
697
  const toIdx = Math.max(srcLaneIdx, tgtLaneIdx);
647
698
  for (let i = fromIdx; i < toIdx; i++) {
@@ -650,11 +701,12 @@ function routeEdges(edges, layoutNodes, backEdgeIds, laneIds, poolIds) {
650
701
  pts.push({ x: sharedX, y: laneBot });
651
702
  }
652
703
  const lastY = pts[pts.length - 1].y;
653
- pts.push({ x: tCX, y: lastY });
654
- pts.push({ x: tCX, y: tAbs.y + tH });
704
+ pts.push({ x: tAbs.x - EDGE_ROUTE_PAD, y: lastY });
705
+ pts.push({ x: tAbs.x - EDGE_ROUTE_PAD, y: tCY });
706
+ pts.push({ x: tAbs.x, y: tCY });
655
707
  } else {
656
- pts.push({ x: sCX, y: sAbs.y });
657
- pts.push({ x: sharedX, y: sAbs.y });
708
+ pts.push({ x: sAbs.x + sW, y: sCY });
709
+ pts.push({ x: sharedX, y: sCY });
658
710
  const fromIdx = Math.min(srcLaneIdx, tgtLaneIdx);
659
711
  const toIdx = Math.max(srcLaneIdx, tgtLaneIdx);
660
712
  for (let i = toIdx; i > fromIdx; i--) {
@@ -663,17 +715,18 @@ function routeEdges(edges, layoutNodes, backEdgeIds, laneIds, poolIds) {
663
715
  pts.push({ x: sharedX, y: laneTop });
664
716
  }
665
717
  const lastY = pts[pts.length - 1].y;
666
- pts.push({ x: tCX, y: lastY });
667
- pts.push({ x: tCX, y: tAbs.y });
718
+ pts.push({ x: tAbs.x - EDGE_ROUTE_PAD, y: lastY });
719
+ pts.push({ x: tAbs.x - EDGE_ROUTE_PAD, y: tCY });
720
+ pts.push({ x: tAbs.x, y: tCY });
668
721
  }
669
722
  routingPoints = pts;
670
723
  } else {
671
724
  const sharedX = gapMidX(sAbs, sW, tAbs, tW);
672
725
  routingPoints = [
673
- { x: sCX, y: sCY },
726
+ { x: sAbs.x + sW, y: sCY },
674
727
  { x: sharedX, y: sCY },
675
728
  { x: sharedX, y: tCY },
676
- { x: tCX, y: tCY }
729
+ { x: tAbs.x, y: tCY }
677
730
  ];
678
731
  }
679
732
  } else if (Math.abs(sCY - tCY) <= SAME_ROW_THRESHOLD) {
@@ -685,19 +738,25 @@ function routeEdges(edges, layoutNodes, backEdgeIds, laneIds, poolIds) {
685
738
  const exitsTop = handle.includes("top");
686
739
  const exitsBottom = handle.includes("bottom");
687
740
  if (exitsTop || exitsBottom) {
741
+ const exitY = exitsTop ? sAbs.y : sAbs.y + sH;
742
+ const entryY = exitsTop ? tAbs.y + tH : tAbs.y;
743
+ const corridorY = exitsTop ? Math.min(exitY, entryY) - EDGE_ROUTE_PAD : Math.max(exitY, entryY) + EDGE_ROUTE_PAD;
688
744
  routingPoints = [
689
- { x: sCX, y: exitsTop ? sAbs.y : sAbs.y + sH },
690
- { x: sCX, y: tCY },
691
- { x: tCX, y: tCY },
692
- { x: tCX, y: exitsTop ? tAbs.y + tH : tAbs.y }
745
+ { x: sCX, y: exitY },
746
+ { x: sCX, y: corridorY },
747
+ { x: tCX, y: corridorY },
748
+ { x: tCX, y: entryY }
693
749
  ];
694
750
  } else {
695
751
  const midX = routeMidX(sAbs, sW, tAbs, tW);
752
+ const exitX = sAbs.x + sW;
753
+ const entryX = tAbs.x;
754
+ const corridorX = Math.max(midX, exitX + EDGE_ROUTE_PAD);
696
755
  routingPoints = [
697
- { x: sAbs.x + sW, y: sCY },
698
- { x: midX, y: sCY },
699
- { x: midX, y: tCY },
700
- { x: tAbs.x, y: tCY }
756
+ { x: exitX, y: sCY },
757
+ { x: corridorX, y: sCY },
758
+ { x: corridorX, y: tCY },
759
+ { x: entryX, y: tCY }
701
760
  ];
702
761
  }
703
762
  }
@@ -706,16 +765,19 @@ function routeEdges(edges, layoutNodes, backEdgeIds, laneIds, poolIds) {
706
765
  }
707
766
  var ARTIFACT_ABOVE_GAP = 16;
708
767
  var ARTIFACT_H_SPACING = 12;
709
- function positionArtifacts(artifacts, resultNodes, edges) {
768
+ function positionArtifacts(artifacts, resultNodes, edges, originalNodes) {
710
769
  if (artifacts.length === 0) return [];
711
770
  const byId = new Map(resultNodes.map((n) => [n.id, n]));
712
771
  const cache = /* @__PURE__ */ new Map();
713
772
  const absPos = (id) => absolutePos(id, byId, cache);
773
+ const originalById = new Map(originalNodes.map((n) => [n.id, n]));
774
+ const originalCache = /* @__PURE__ */ new Map();
714
775
  const artifactsByNode = /* @__PURE__ */ new Map();
715
776
  const ungrouped = [];
777
+ const groups = [];
716
778
  for (const artifact of artifacts) {
717
779
  if (artifact.data.elementType === "Group") {
718
- ungrouped.push(artifact);
780
+ groups.push(artifact);
719
781
  continue;
720
782
  }
721
783
  const connEdge = edges.find((e) => {
@@ -753,6 +815,58 @@ function positionArtifacts(artifacts, resultNodes, edges) {
753
815
  desiredAbsX += nW(artifact) + ARTIFACT_H_SPACING;
754
816
  }
755
817
  }
818
+ const nonArtifactOriginalNodes = originalNodes.filter(
819
+ (node) => !LAYOUT_ARTIFACT_TYPES.has(node.data.elementType)
820
+ );
821
+ for (const group of groups) {
822
+ const originalGroup = originalById.get(group.id);
823
+ if (!originalGroup) {
824
+ positioned.push(group);
825
+ continue;
826
+ }
827
+ const groupRect = nodeRect(originalGroup, originalById, originalCache);
828
+ const containedOriginalIds = nonArtifactOriginalNodes.filter((node) => node.id !== group.id).filter((node) => {
829
+ if (node.parentId !== group.parentId) return false;
830
+ return rectContainsRect(groupRect, nodeRect(node, originalById, originalCache));
831
+ }).map((node) => node.id);
832
+ if (containedOriginalIds.length === 0) {
833
+ positioned.push(group);
834
+ continue;
835
+ }
836
+ const containedLaidOut = containedOriginalIds.map((id) => byId.get(id)).filter((node) => !!node);
837
+ if (containedLaidOut.length === 0) {
838
+ positioned.push(group);
839
+ continue;
840
+ }
841
+ const GROUP_PAD_X = 24;
842
+ const GROUP_PAD_Y = 20;
843
+ const bounds = containedLaidOut.reduce(
844
+ (acc, node) => {
845
+ const rect = nodeRect(node, byId, cache);
846
+ return {
847
+ left: Math.min(acc.left, rect.x),
848
+ top: Math.min(acc.top, rect.y),
849
+ right: Math.max(acc.right, rect.x + rect.width),
850
+ bottom: Math.max(acc.bottom, rect.y + rect.height)
851
+ };
852
+ },
853
+ { left: Number.POSITIVE_INFINITY, top: Number.POSITIVE_INFINITY, right: Number.NEGATIVE_INFINITY, bottom: Number.NEGATIVE_INFINITY }
854
+ );
855
+ const parentAbsP = group.parentId ? absPos(group.parentId) : { x: 0, y: 0 };
856
+ positioned.push({
857
+ ...group,
858
+ position: {
859
+ x: bounds.left - GROUP_PAD_X - parentAbsP.x,
860
+ y: bounds.top - GROUP_PAD_Y - parentAbsP.y
861
+ },
862
+ width: bounds.right - bounds.left + GROUP_PAD_X * 2,
863
+ height: bounds.bottom - bounds.top + GROUP_PAD_Y * 2,
864
+ measured: {
865
+ width: bounds.right - bounds.left + GROUP_PAD_X * 2,
866
+ height: bounds.bottom - bounds.top + GROUP_PAD_Y * 2
867
+ }
868
+ });
869
+ }
756
870
  return positioned;
757
871
  }
758
872
  async function bpmnCustomLayout(nodes, edges) {
@@ -787,7 +901,7 @@ async function bpmnCustomLayout(nodes, edges) {
787
901
  if (pools.length === 0) {
788
902
  const { bpmnElkLayout: bpmnElkLayout2 } = await import('../elk-QT7H4252.js');
789
903
  const elkResult = await bpmnElkLayout2(nodes.filter((n) => !LAYOUT_ARTIFACT_TYPES.has(n.data.elementType)), edges);
790
- const posArtifacts = positionArtifacts(artifacts, elkResult.nodes, edges);
904
+ const posArtifacts = positionArtifacts(artifacts, elkResult.nodes, edges, nodes);
791
905
  return { nodes: [...elkResult.nodes, ...posArtifacts], edges: elkResult.edges };
792
906
  }
793
907
  const poolIds = new Set(pools.map((p) => p.id));
@@ -831,8 +945,9 @@ async function bpmnCustomLayout(nodes, edges) {
831
945
  const layoutted = new Set(resultNodes.map((n) => n.id));
832
946
  const freeNodes = workingMainNodes.filter((n) => !layoutted.has(n.id));
833
947
  if (freeNodes.length > 0) {
948
+ const freeNodeIds = new Set(freeNodes.map((n) => n.id));
834
949
  const freeEdges = edges.filter(
835
- (e) => freeNodes.some((n) => n.id === e.source || n.id === e.target)
950
+ (e) => freeNodeIds.has(e.source) && freeNodeIds.has(e.target)
836
951
  );
837
952
  try {
838
953
  const { bpmnElkLayout: bpmnElkLayout2 } = await import('../elk-QT7H4252.js');
@@ -856,7 +971,7 @@ async function bpmnCustomLayout(nodes, edges) {
856
971
  }
857
972
  }
858
973
  const routedEdges = routeEdges(edges, resultNodes, allBackEdgeIds, allLaneIds, poolIds);
859
- const positionedArtifacts = positionArtifacts(artifacts, resultNodes, edges);
974
+ const positionedArtifacts = positionArtifacts(artifacts, resultNodes, edges, nodes);
860
975
  return {
861
976
  nodes: [...resultNodes, ...positionedArtifacts],
862
977
  edges: routedEdges