@aranzatech/diagrams-bpmn 0.3.2 → 0.3.4

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,54 @@ 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;
631
+ }
632
+ function sortNodesParentFirst(nodes) {
633
+ const byId = new Map(nodes.map((node) => [node.id, node]));
634
+ const memo = /* @__PURE__ */ new Map();
635
+ const depthOf = (node) => {
636
+ const cached = memo.get(node.id);
637
+ if (cached != null) return cached;
638
+ if (!node.parentId) {
639
+ memo.set(node.id, 0);
640
+ return 0;
641
+ }
642
+ const parent = byId.get(node.parentId);
643
+ const depth = parent ? depthOf(parent) + 1 : 0;
644
+ memo.set(node.id, depth);
645
+ return depth;
646
+ };
647
+ return [...nodes].sort((a, b) => {
648
+ const depthDiff = depthOf(a) - depthOf(b);
649
+ if (depthDiff !== 0) return depthDiff;
650
+ const ay = a.position?.y ?? 0;
651
+ const by = b.position?.y ?? 0;
652
+ if (ay !== by) return ay - by;
653
+ const ax = a.position?.x ?? 0;
654
+ const bx = b.position?.x ?? 0;
655
+ if (ax !== bx) return ax - bx;
656
+ return a.id.localeCompare(b.id);
657
+ });
573
658
  }
574
659
  function gapMidX(sAbs, sW, tAbs, tW) {
575
660
  const leftRightEdge = Math.min(sAbs.x + sW, tAbs.x + tW);
@@ -605,18 +690,9 @@ function routeEdges(edges, layoutNodes, backEdgeIds, laneIds, poolIds) {
605
690
  const tCX = tAbs.x + tW / 2;
606
691
  const sCY = sAbs.y + sH / 2;
607
692
  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)) {
693
+ const srcPool = poolOf(src, poolIds, byId) ?? null;
694
+ const tgtPool = poolOf(tgt, poolIds, byId) ?? null;
695
+ if (srcPool !== tgtPool) {
620
696
  const d = { ...edge.data };
621
697
  delete d.routingPoints;
622
698
  return { ...edge, data: d };
@@ -625,14 +701,16 @@ function routeEdges(edges, layoutNodes, backEdgeIds, laneIds, poolIds) {
625
701
  if (backEdgeIds.has(edge.id)) {
626
702
  const topY = Math.min(sAbs.y, tAbs.y) - BACK_EDGE_CLEARANCE;
627
703
  routingPoints = [
628
- { x: sCX, y: sAbs.y },
629
- { x: sCX, y: topY },
630
- { x: tCX, y: topY },
631
- { x: tCX, y: tAbs.y + tH }
704
+ { x: sAbs.x + sW, y: sCY },
705
+ { x: sAbs.x + sW + EDGE_ROUTE_PAD, y: sCY },
706
+ { x: sAbs.x + sW + EDGE_ROUTE_PAD, y: topY },
707
+ { x: tAbs.x - EDGE_ROUTE_PAD, y: topY },
708
+ { x: tAbs.x - EDGE_ROUTE_PAD, y: tCY },
709
+ { x: tAbs.x, y: tCY }
632
710
  ];
633
- } else if (laneOf(src, laneIds) !== laneOf(tgt, laneIds)) {
634
- const srcLane = byId.get(src.parentId ?? "");
635
- const tgtLane = byId.get(tgt.parentId ?? "");
711
+ } else if (laneOf(src, laneIds, byId) !== laneOf(tgt, laneIds, byId)) {
712
+ const srcLane = byId.get(laneOf(src, laneIds, byId) ?? "");
713
+ const tgtLane = byId.get(laneOf(tgt, laneIds, byId) ?? "");
636
714
  if (srcLane && tgtLane && laneIds.has(srcLane.id) && laneIds.has(tgtLane.id)) {
637
715
  const goingDown = tAbs.y > sAbs.y;
638
716
  const sharedX = gapMidX(sAbs, sW, tAbs, tW);
@@ -640,8 +718,8 @@ function routeEdges(edges, layoutNodes, backEdgeIds, laneIds, poolIds) {
640
718
  const tgtLaneIdx = sortedLanes.findIndex((l) => l.id === tgtLane.id);
641
719
  const pts = [];
642
720
  if (goingDown) {
643
- pts.push({ x: sCX, y: sAbs.y + sH });
644
- pts.push({ x: sharedX, y: sAbs.y + sH });
721
+ pts.push({ x: sAbs.x + sW, y: sCY });
722
+ pts.push({ x: sharedX, y: sCY });
645
723
  const fromIdx = Math.min(srcLaneIdx, tgtLaneIdx);
646
724
  const toIdx = Math.max(srcLaneIdx, tgtLaneIdx);
647
725
  for (let i = fromIdx; i < toIdx; i++) {
@@ -650,11 +728,12 @@ function routeEdges(edges, layoutNodes, backEdgeIds, laneIds, poolIds) {
650
728
  pts.push({ x: sharedX, y: laneBot });
651
729
  }
652
730
  const lastY = pts[pts.length - 1].y;
653
- pts.push({ x: tCX, y: lastY });
654
- pts.push({ x: tCX, y: tAbs.y + tH });
731
+ pts.push({ x: tAbs.x - EDGE_ROUTE_PAD, y: lastY });
732
+ pts.push({ x: tAbs.x - EDGE_ROUTE_PAD, y: tCY });
733
+ pts.push({ x: tAbs.x, y: tCY });
655
734
  } else {
656
- pts.push({ x: sCX, y: sAbs.y });
657
- pts.push({ x: sharedX, y: sAbs.y });
735
+ pts.push({ x: sAbs.x + sW, y: sCY });
736
+ pts.push({ x: sharedX, y: sCY });
658
737
  const fromIdx = Math.min(srcLaneIdx, tgtLaneIdx);
659
738
  const toIdx = Math.max(srcLaneIdx, tgtLaneIdx);
660
739
  for (let i = toIdx; i > fromIdx; i--) {
@@ -663,17 +742,18 @@ function routeEdges(edges, layoutNodes, backEdgeIds, laneIds, poolIds) {
663
742
  pts.push({ x: sharedX, y: laneTop });
664
743
  }
665
744
  const lastY = pts[pts.length - 1].y;
666
- pts.push({ x: tCX, y: lastY });
667
- pts.push({ x: tCX, y: tAbs.y });
745
+ pts.push({ x: tAbs.x - EDGE_ROUTE_PAD, y: lastY });
746
+ pts.push({ x: tAbs.x - EDGE_ROUTE_PAD, y: tCY });
747
+ pts.push({ x: tAbs.x, y: tCY });
668
748
  }
669
749
  routingPoints = pts;
670
750
  } else {
671
751
  const sharedX = gapMidX(sAbs, sW, tAbs, tW);
672
752
  routingPoints = [
673
- { x: sCX, y: sCY },
753
+ { x: sAbs.x + sW, y: sCY },
674
754
  { x: sharedX, y: sCY },
675
755
  { x: sharedX, y: tCY },
676
- { x: tCX, y: tCY }
756
+ { x: tAbs.x, y: tCY }
677
757
  ];
678
758
  }
679
759
  } else if (Math.abs(sCY - tCY) <= SAME_ROW_THRESHOLD) {
@@ -685,19 +765,25 @@ function routeEdges(edges, layoutNodes, backEdgeIds, laneIds, poolIds) {
685
765
  const exitsTop = handle.includes("top");
686
766
  const exitsBottom = handle.includes("bottom");
687
767
  if (exitsTop || exitsBottom) {
768
+ const exitY = exitsTop ? sAbs.y : sAbs.y + sH;
769
+ const entryY = exitsTop ? tAbs.y + tH : tAbs.y;
770
+ const corridorY = exitsTop ? Math.min(exitY, entryY) - EDGE_ROUTE_PAD : Math.max(exitY, entryY) + EDGE_ROUTE_PAD;
688
771
  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 }
772
+ { x: sCX, y: exitY },
773
+ { x: sCX, y: corridorY },
774
+ { x: tCX, y: corridorY },
775
+ { x: tCX, y: entryY }
693
776
  ];
694
777
  } else {
695
778
  const midX = routeMidX(sAbs, sW, tAbs, tW);
779
+ const exitX = sAbs.x + sW;
780
+ const entryX = tAbs.x;
781
+ const corridorX = Math.max(midX, exitX + EDGE_ROUTE_PAD);
696
782
  routingPoints = [
697
- { x: sAbs.x + sW, y: sCY },
698
- { x: midX, y: sCY },
699
- { x: midX, y: tCY },
700
- { x: tAbs.x, y: tCY }
783
+ { x: exitX, y: sCY },
784
+ { x: corridorX, y: sCY },
785
+ { x: corridorX, y: tCY },
786
+ { x: entryX, y: tCY }
701
787
  ];
702
788
  }
703
789
  }
@@ -706,16 +792,19 @@ function routeEdges(edges, layoutNodes, backEdgeIds, laneIds, poolIds) {
706
792
  }
707
793
  var ARTIFACT_ABOVE_GAP = 16;
708
794
  var ARTIFACT_H_SPACING = 12;
709
- function positionArtifacts(artifacts, resultNodes, edges) {
795
+ function positionArtifacts(artifacts, resultNodes, edges, originalNodes) {
710
796
  if (artifacts.length === 0) return [];
711
797
  const byId = new Map(resultNodes.map((n) => [n.id, n]));
712
798
  const cache = /* @__PURE__ */ new Map();
713
799
  const absPos = (id) => absolutePos(id, byId, cache);
800
+ const originalById = new Map(originalNodes.map((n) => [n.id, n]));
801
+ const originalCache = /* @__PURE__ */ new Map();
714
802
  const artifactsByNode = /* @__PURE__ */ new Map();
715
803
  const ungrouped = [];
804
+ const groups = [];
716
805
  for (const artifact of artifacts) {
717
806
  if (artifact.data.elementType === "Group") {
718
- ungrouped.push(artifact);
807
+ groups.push(artifact);
719
808
  continue;
720
809
  }
721
810
  const connEdge = edges.find((e) => {
@@ -753,6 +842,58 @@ function positionArtifacts(artifacts, resultNodes, edges) {
753
842
  desiredAbsX += nW(artifact) + ARTIFACT_H_SPACING;
754
843
  }
755
844
  }
845
+ const nonArtifactOriginalNodes = originalNodes.filter(
846
+ (node) => !LAYOUT_ARTIFACT_TYPES.has(node.data.elementType)
847
+ );
848
+ for (const group of groups) {
849
+ const originalGroup = originalById.get(group.id);
850
+ if (!originalGroup) {
851
+ positioned.push(group);
852
+ continue;
853
+ }
854
+ const groupRect = nodeRect(originalGroup, originalById, originalCache);
855
+ const containedOriginalIds = nonArtifactOriginalNodes.filter((node) => node.id !== group.id).filter((node) => {
856
+ if (node.parentId !== group.parentId) return false;
857
+ return rectContainsRect(groupRect, nodeRect(node, originalById, originalCache));
858
+ }).map((node) => node.id);
859
+ if (containedOriginalIds.length === 0) {
860
+ positioned.push(group);
861
+ continue;
862
+ }
863
+ const containedLaidOut = containedOriginalIds.map((id) => byId.get(id)).filter((node) => !!node);
864
+ if (containedLaidOut.length === 0) {
865
+ positioned.push(group);
866
+ continue;
867
+ }
868
+ const GROUP_PAD_X = 24;
869
+ const GROUP_PAD_Y = 20;
870
+ const bounds = containedLaidOut.reduce(
871
+ (acc, node) => {
872
+ const rect = nodeRect(node, byId, cache);
873
+ return {
874
+ left: Math.min(acc.left, rect.x),
875
+ top: Math.min(acc.top, rect.y),
876
+ right: Math.max(acc.right, rect.x + rect.width),
877
+ bottom: Math.max(acc.bottom, rect.y + rect.height)
878
+ };
879
+ },
880
+ { left: Number.POSITIVE_INFINITY, top: Number.POSITIVE_INFINITY, right: Number.NEGATIVE_INFINITY, bottom: Number.NEGATIVE_INFINITY }
881
+ );
882
+ const parentAbsP = group.parentId ? absPos(group.parentId) : { x: 0, y: 0 };
883
+ positioned.push({
884
+ ...group,
885
+ position: {
886
+ x: bounds.left - GROUP_PAD_X - parentAbsP.x,
887
+ y: bounds.top - GROUP_PAD_Y - parentAbsP.y
888
+ },
889
+ width: bounds.right - bounds.left + GROUP_PAD_X * 2,
890
+ height: bounds.bottom - bounds.top + GROUP_PAD_Y * 2,
891
+ measured: {
892
+ width: bounds.right - bounds.left + GROUP_PAD_X * 2,
893
+ height: bounds.bottom - bounds.top + GROUP_PAD_Y * 2
894
+ }
895
+ });
896
+ }
756
897
  return positioned;
757
898
  }
758
899
  async function bpmnCustomLayout(nodes, edges) {
@@ -787,8 +928,8 @@ async function bpmnCustomLayout(nodes, edges) {
787
928
  if (pools.length === 0) {
788
929
  const { bpmnElkLayout: bpmnElkLayout2 } = await import('../elk-QT7H4252.js');
789
930
  const elkResult = await bpmnElkLayout2(nodes.filter((n) => !LAYOUT_ARTIFACT_TYPES.has(n.data.elementType)), edges);
790
- const posArtifacts = positionArtifacts(artifacts, elkResult.nodes, edges);
791
- return { nodes: [...elkResult.nodes, ...posArtifacts], edges: elkResult.edges };
931
+ const posArtifacts = positionArtifacts(artifacts, elkResult.nodes, edges, nodes);
932
+ return { nodes: sortNodesParentFirst([...elkResult.nodes, ...posArtifacts]), edges: elkResult.edges };
792
933
  }
793
934
  const poolIds = new Set(pools.map((p) => p.id));
794
935
  const allLaneIds = new Set(lanes.map((l) => l.id));
@@ -831,8 +972,9 @@ async function bpmnCustomLayout(nodes, edges) {
831
972
  const layoutted = new Set(resultNodes.map((n) => n.id));
832
973
  const freeNodes = workingMainNodes.filter((n) => !layoutted.has(n.id));
833
974
  if (freeNodes.length > 0) {
975
+ const freeNodeIds = new Set(freeNodes.map((n) => n.id));
834
976
  const freeEdges = edges.filter(
835
- (e) => freeNodes.some((n) => n.id === e.source || n.id === e.target)
977
+ (e) => freeNodeIds.has(e.source) && freeNodeIds.has(e.target)
836
978
  );
837
979
  try {
838
980
  const { bpmnElkLayout: bpmnElkLayout2 } = await import('../elk-QT7H4252.js');
@@ -856,9 +998,9 @@ async function bpmnCustomLayout(nodes, edges) {
856
998
  }
857
999
  }
858
1000
  const routedEdges = routeEdges(edges, resultNodes, allBackEdgeIds, allLaneIds, poolIds);
859
- const positionedArtifacts = positionArtifacts(artifacts, resultNodes, edges);
1001
+ const positionedArtifacts = positionArtifacts(artifacts, resultNodes, edges, nodes);
860
1002
  return {
861
- nodes: [...resultNodes, ...positionedArtifacts],
1003
+ nodes: sortNodesParentFirst([...resultNodes, ...positionedArtifacts]),
862
1004
  edges: routedEdges
863
1005
  };
864
1006
  }