@bpmnkit/core 0.0.26 → 0.0.27

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.
@@ -201,19 +201,38 @@ function buildLaneShapes(lanes, nodes, dx, dy, poolY, poolHeaderWidth, laneConte
201
201
  const mB = accB && accB.count > 0 ? accB.sum / accB.count : Number.POSITIVE_INFINITY;
202
202
  return mA - mB;
203
203
  });
204
- const tileH = Math.round(poolHeight / sortedLanes.length);
205
- return sortedLanes.map((lane, i) => ({
206
- id: `${lane.id}_di`,
207
- bpmnElement: lane.id,
208
- isHorizontal: true,
209
- bounds: {
210
- x: Math.round(poolHeaderWidth),
211
- y: Math.round(poolY + i * tileH),
212
- width: Math.round(laneContentWidth),
213
- height: Math.round(i === sortedLanes.length - 1 ? poolHeight - i * tileH : tileH),
214
- },
215
- unknownAttributes: {},
216
- }));
204
+ // Compute proportional weight per lane: node count × row height, minimum 1 row
205
+ const MIN_LANE_H = 80;
206
+ const weights = sortedLanes.map((lane) => {
207
+ const count = nodes.filter((n) => elemToLane.get(n.id) === lane.id).length;
208
+ return Math.max(count, 1);
209
+ });
210
+ const totalWeight = weights.reduce((a, b) => a + b, 0);
211
+ // Scale proportionally to poolHeight so all lanes fill the pool exactly
212
+ const scaledHeights = weights.map((w) => Math.round((w / totalWeight) * poolHeight));
213
+ // Fix last lane for rounding drift
214
+ if (scaledHeights.length > 0) {
215
+ scaledHeights[scaledHeights.length - 1] =
216
+ poolHeight - scaledHeights.slice(0, -1).reduce((a, b) => a + b, 0);
217
+ }
218
+ let cumulativeY = 0;
219
+ return sortedLanes.map((lane, i) => {
220
+ const laneH = scaledHeights[i] ?? MIN_LANE_H;
221
+ const shape = {
222
+ id: `${lane.id}_di`,
223
+ bpmnElement: lane.id,
224
+ isHorizontal: true,
225
+ bounds: {
226
+ x: Math.round(poolHeaderWidth),
227
+ y: Math.round(poolY + cumulativeY),
228
+ width: Math.round(laneContentWidth),
229
+ height: Math.round(laneH),
230
+ },
231
+ unknownAttributes: {},
232
+ };
233
+ cumulativeY += laneH;
234
+ return shape;
235
+ });
217
236
  }
218
237
  function addAnnotationShapes(process, layoutNodes, annLocalBounds, allShapes, allEdges, dx, dy) {
219
238
  if (process.textAnnotations.length === 0 && process.associations.length === 0)
@@ -41,6 +41,16 @@ function repositionBoundaryEvents(flowElements, result) {
41
41
  const hostNode = nodeById.get(hostId);
42
42
  if (!hostNode)
43
43
  continue;
44
+ // Pre-compute distribution parameters (all boundary events share the same fixed size).
45
+ // Distribute events evenly along the bottom edge, centered on the task.
46
+ // effectiveSpacing guarantees events don't overlap (min bW + 4px gap).
47
+ const firstBeNode = nodeById.get(beIds[0] ?? "");
48
+ const bW = firstBeNode?.bounds.width ?? 36;
49
+ const bH = firstBeNode?.bounds.height ?? 36;
50
+ const n = beIds.length;
51
+ const effectiveSpacing = Math.max(Math.round(hostNode.bounds.width / (n + 1)), bW + 4);
52
+ const groupWidth = Math.max(0, n - 1) * effectiveSpacing;
53
+ const groupStartCenterX = Math.round(hostNode.bounds.x + hostNode.bounds.width / 2 - groupWidth / 2);
44
54
  for (let i = 0; i < beIds.length; i++) {
45
55
  const beId = beIds[i];
46
56
  if (!beId)
@@ -48,11 +58,9 @@ function repositionBoundaryEvents(flowElements, result) {
48
58
  const beNode = nodeById.get(beId);
49
59
  if (!beNode)
50
60
  continue;
51
- const bW = beNode.bounds.width;
52
- const bH = beNode.bounds.height;
53
- // Place boundary event on the bottom edge of the host task, stacking leftward
54
- const rightEdge = hostNode.bounds.x + hostNode.bounds.width;
55
- beNode.bounds.x = Math.round(rightEdge - bW / 2 - i * (bW + 4));
61
+ // bW / bH come from the pre-loop computation (all BEs are fixed 36×36)
62
+ // Center-bottom distribution: single event → task center; multiple → even spread
63
+ beNode.bounds.x = Math.round(groupStartCenterX + i * effectiveSpacing - bW / 2);
56
64
  beNode.bounds.y = Math.round(hostNode.bounds.y + hostNode.bounds.height - bH / 2);
57
65
  if (beNode.labelBounds) {
58
66
  beNode.labelBounds.x = beNode.bounds.x + Math.round(bW / 2 - beNode.labelBounds.width / 2);
@@ -1,5 +1,36 @@
1
1
  import { layoutFlowNodes } from "./layout-engine.js";
2
2
  import { SUBPROCESS_PADDING } from "./types.js";
3
+ const ADHOC_MAX_COLS = 4;
4
+ const ADHOC_H_GAP = 50;
5
+ const ADHOC_V_GAP = 80;
6
+ /**
7
+ * Rearrange disconnected adHocSubProcess tool nodes into a grid.
8
+ * Nodes start at (0, 0) so the subprocess padding offset applies cleanly.
9
+ */
10
+ function applyAdHocGridLayout(nodes) {
11
+ if (nodes.length === 0)
12
+ return;
13
+ const cols = Math.min(nodes.length, ADHOC_MAX_COLS);
14
+ const cellW = nodes.reduce((max, n) => Math.max(max, n.bounds.width), 0);
15
+ const cellH = nodes.reduce((max, n) => Math.max(max, n.bounds.height), 0);
16
+ for (let i = 0; i < nodes.length; i++) {
17
+ const col = i % cols;
18
+ const row = Math.floor(i / cols);
19
+ const n = nodes[i];
20
+ if (!n)
21
+ continue;
22
+ const newX = col * (cellW + ADHOC_H_GAP) + Math.round((cellW - n.bounds.width) / 2);
23
+ const newY = row * (cellH + ADHOC_V_GAP) + Math.round((cellH - n.bounds.height) / 2);
24
+ const dx = newX - n.bounds.x;
25
+ const dy = newY - n.bounds.y;
26
+ n.bounds.x = newX;
27
+ n.bounds.y = newY;
28
+ if (n.labelBounds) {
29
+ n.labelBounds.x += dx;
30
+ n.labelBounds.y += dy;
31
+ }
32
+ }
33
+ }
3
34
  /**
4
35
  * Check if a node type is a sub-process container.
5
36
  */
@@ -22,6 +53,13 @@ export function layoutSubProcesses(layoutNodes, nodeIndex) {
22
53
  if (!subProcess.flowElements || subProcess.flowElements.length === 0)
23
54
  continue;
24
55
  const childResult = layoutFlowNodes(subProcess.flowElements, subProcess.sequenceFlows ?? []);
56
+ // For adHocSubProcess with no sequence flows, rearrange into a compact grid
57
+ // instead of a single long horizontal row.
58
+ if (bpmnNode.type === "adHocSubProcess" &&
59
+ (subProcess.sequenceFlows?.length ?? 0) === 0 &&
60
+ childResult.nodes.length > 0) {
61
+ applyAdHocGridLayout(childResult.nodes);
62
+ }
25
63
  if (childResult.nodes.length === 0)
26
64
  continue;
27
65
  // Compute bounding box of child elements
@@ -5,13 +5,13 @@ export declare const ELEMENT_SIZES: Record<string, {
5
5
  height: number;
6
6
  }>;
7
7
  /** Virtual grid cell dimensions for element placement. */
8
- export declare const GRID_CELL_WIDTH = 130;
8
+ export declare const GRID_CELL_WIDTH = 150;
9
9
  export declare const GRID_CELL_HEIGHT = 140;
10
10
  /** Minimum spacing between elements (derived from grid). */
11
11
  export declare const HORIZONTAL_SPACING: number;
12
12
  export declare const VERTICAL_SPACING: number;
13
13
  /** Padding inside sub-process containers. */
14
- export declare const SUBPROCESS_PADDING = 20;
14
+ export declare const SUBPROCESS_PADDING = 50;
15
15
  /** Edge-label sizing constants (used for placement & collision detection). */
16
16
  export declare const LABEL_CHAR_WIDTH = 7;
17
17
  export declare const LABEL_MIN_WIDTH = 40;
@@ -22,13 +22,13 @@ export const ELEMENT_SIZES = {
22
22
  eventSubProcess: { width: 100, height: 80 },
23
23
  };
24
24
  /** Virtual grid cell dimensions for element placement. */
25
- export const GRID_CELL_WIDTH = 130;
25
+ export const GRID_CELL_WIDTH = 150;
26
26
  export const GRID_CELL_HEIGHT = 140;
27
27
  /** Minimum spacing between elements (derived from grid). */
28
28
  export const HORIZONTAL_SPACING = GRID_CELL_WIDTH - 100; // 100 = max element width
29
29
  export const VERTICAL_SPACING = GRID_CELL_HEIGHT - 80; // 80 = max element height
30
30
  /** Padding inside sub-process containers. */
31
- export const SUBPROCESS_PADDING = 20;
31
+ export const SUBPROCESS_PADDING = 50;
32
32
  /** Edge-label sizing constants (used for placement & collision detection). */
33
33
  export const LABEL_CHAR_WIDTH = 7;
34
34
  export const LABEL_MIN_WIDTH = 40;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bpmnkit/core",
3
- "version": "0.0.26",
3
+ "version": "0.0.27",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",