@bpmnkit/core 0.0.27 → 0.1.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.
- package/dist/bpmn/auto-layout.js +39 -126
- package/dist/bpmn/bpmn-builder.d.ts +30 -0
- package/dist/bpmn/bpmn-builder.js +343 -31
- package/dist/bpmn/di-check.d.ts +14 -0
- package/dist/bpmn/di-check.js +51 -0
- package/dist/bpmn/svg.js +14 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/layout/annotations.d.ts +27 -0
- package/dist/layout/annotations.js +251 -0
- package/dist/layout/grid/edge-labels.d.ts +8 -0
- package/dist/layout/grid/edge-labels.js +126 -0
- package/dist/layout/grid/flow-graph.d.ts +25 -0
- package/dist/layout/grid/flow-graph.js +99 -0
- package/dist/layout/grid/grid-engine.d.ts +4 -0
- package/dist/layout/grid/grid-engine.js +214 -0
- package/dist/layout/grid/grid-router.d.ts +36 -0
- package/dist/layout/grid/grid-router.js +190 -0
- package/dist/layout/grid/grid.d.ts +43 -0
- package/dist/layout/grid/grid.js +174 -0
- package/dist/layout/grid/walker.d.ts +11 -0
- package/dist/layout/grid/walker.js +126 -0
- package/dist/layout/index.d.ts +2 -5
- package/dist/layout/index.js +1 -4
- package/dist/layout/layout-engine.d.ts +5 -15
- package/dist/layout/layout-engine.js +8 -486
- package/dist/layout/types.js +4 -0
- package/dist/xml/xml-parser.js +5 -0
- package/package.json +1 -1
- package/dist/layout/astar.d.ts +0 -15
- package/dist/layout/astar.js +0 -191
- package/dist/layout/block-builder.d.ts +0 -37
- package/dist/layout/block-builder.js +0 -154
- package/dist/layout/block-layout.d.ts +0 -9
- package/dist/layout/block-layout.js +0 -163
- package/dist/layout/coordinates.d.ts +0 -85
- package/dist/layout/coordinates.js +0 -1392
- package/dist/layout/crossing.d.ts +0 -8
- package/dist/layout/crossing.js +0 -60
- package/dist/layout/graph.d.ts +0 -28
- package/dist/layout/graph.js +0 -126
- package/dist/layout/layers.d.ts +0 -13
- package/dist/layout/layers.js +0 -49
- package/dist/layout/routing.d.ts +0 -33
- package/dist/layout/routing.js +0 -622
- package/dist/layout/subprocess.d.ts +0 -14
- package/dist/layout/subprocess.js +0 -115
package/dist/bpmn/auto-layout.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
+
import { associationWaypoints, packAnnotations } from "../layout/annotations.js";
|
|
1
2
|
import { layoutProcess } from "../layout/layout-engine.js";
|
|
2
|
-
import { resolveEdgeCrossings } from "../layout/routing.js";
|
|
3
3
|
const POOL_HEADER = 30;
|
|
4
4
|
const LANE_HEADER = 30;
|
|
5
5
|
const PADDING = 20;
|
|
6
6
|
const POOL_GAP = 30;
|
|
7
|
-
const ANN_H = 50;
|
|
8
|
-
const ANN_GAP = 60;
|
|
9
|
-
const ANN_PADDING = 20;
|
|
10
7
|
function contentBbox(nodes, extra) {
|
|
11
8
|
let minX = Number.POSITIVE_INFINITY;
|
|
12
9
|
let minY = Number.POSITIVE_INFINITY;
|
|
@@ -34,97 +31,6 @@ function contentBbox(nodes, extra) {
|
|
|
34
31
|
}
|
|
35
32
|
return { minX, minY, maxX, maxY };
|
|
36
33
|
}
|
|
37
|
-
/** Pre-compute annotation positions in layout space (before dx/dy shift). */
|
|
38
|
-
function computeAnnotationLocalBounds(process, layoutNodes) {
|
|
39
|
-
const nodeById = new Map(layoutNodes.map((n) => [n.id, n]));
|
|
40
|
-
const placements = new Map();
|
|
41
|
-
// Occupied regions for overlap checks (node bounds + label bounds)
|
|
42
|
-
const occupied = [];
|
|
43
|
-
for (const n of layoutNodes) {
|
|
44
|
-
occupied.push({ ...n.bounds });
|
|
45
|
-
if (n.labelBounds)
|
|
46
|
-
occupied.push({ ...n.labelBounds });
|
|
47
|
-
}
|
|
48
|
-
// Static obstacles for crossing detection (nodes + labels only, not annotations)
|
|
49
|
-
const obstacles = [...occupied];
|
|
50
|
-
for (const ta of process.textAnnotations) {
|
|
51
|
-
const assoc = process.associations.find((a) => a.sourceRef === ta.id || a.targetRef === ta.id);
|
|
52
|
-
const connId = assoc
|
|
53
|
-
? assoc.sourceRef === ta.id
|
|
54
|
-
? assoc.targetRef
|
|
55
|
-
: assoc.sourceRef
|
|
56
|
-
: undefined;
|
|
57
|
-
const connNode = connId ? nodeById.get(connId) : undefined;
|
|
58
|
-
const annW = Math.min(200, Math.max(100, (ta.text?.length ?? 10) * 5));
|
|
59
|
-
if (!connNode) {
|
|
60
|
-
const candidate = { x: 0, y: 0, width: annW, height: ANN_H };
|
|
61
|
-
occupied.push({ ...candidate });
|
|
62
|
-
placements.set(ta.id, candidate);
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
const localX = connNode.bounds.x + connNode.bounds.width / 2 - annW / 2;
|
|
66
|
-
const anchorX = connNode.bounds.x + connNode.bounds.width / 2;
|
|
67
|
-
const pushStep = ANN_H + ANN_PADDING * 2 + 10;
|
|
68
|
-
// Try below: start below connected element, push down for overlaps
|
|
69
|
-
const belowY = connNode.bounds.y + connNode.bounds.height + ANN_GAP;
|
|
70
|
-
const below = { x: localX, y: belowY, width: annW, height: ANN_H };
|
|
71
|
-
for (let i = 0; i < 30 && hasOverlapPadded(below, occupied, ANN_PADDING); i++)
|
|
72
|
-
below.y += pushStep;
|
|
73
|
-
// Try above: gap scales with text length so longer annotations have more breathing room
|
|
74
|
-
const aboveGap = ANN_GAP + Math.round(annW * 0.2);
|
|
75
|
-
const aboveY = connNode.bounds.y - aboveGap - ANN_H;
|
|
76
|
-
const above = { x: localX, y: aboveY, width: annW, height: ANN_H };
|
|
77
|
-
for (let i = 0; i < 30 && hasOverlapPadded(above, occupied, ANN_PADDING); i++)
|
|
78
|
-
above.y -= pushStep;
|
|
79
|
-
// Count how many obstacles the association line would cross for each candidate
|
|
80
|
-
const belowCrossings = countLineCrossings(anchorX, connNode.bounds, below, obstacles);
|
|
81
|
-
const aboveCrossings = countLineCrossings(anchorX, connNode.bounds, above, obstacles);
|
|
82
|
-
const candidate = belowCrossings <= aboveCrossings ? below : above;
|
|
83
|
-
occupied.push({ ...candidate });
|
|
84
|
-
obstacles.push({ ...candidate });
|
|
85
|
-
placements.set(ta.id, candidate);
|
|
86
|
-
}
|
|
87
|
-
return placements;
|
|
88
|
-
}
|
|
89
|
-
/** Count how many obstacles the vertical association line from connNode to annotation crosses. */
|
|
90
|
-
function countLineCrossings(lineX, connBounds, annBounds, obstacles) {
|
|
91
|
-
const annCY = annBounds.y + annBounds.height / 2;
|
|
92
|
-
const connCY = connBounds.y + connBounds.height / 2;
|
|
93
|
-
const top = Math.min(annCY, connCY);
|
|
94
|
-
const bottom = Math.max(annCY, connCY);
|
|
95
|
-
const tolerance = 20;
|
|
96
|
-
let crossings = 0;
|
|
97
|
-
for (const b of obstacles) {
|
|
98
|
-
// Skip the connected element itself
|
|
99
|
-
if (b.x === connBounds.x && b.y === connBounds.y && b.width === connBounds.width)
|
|
100
|
-
continue;
|
|
101
|
-
// Obstacle must overlap with the line's X corridor
|
|
102
|
-
if (b.x + b.width < lineX - tolerance || b.x > lineX + tolerance)
|
|
103
|
-
continue;
|
|
104
|
-
// Obstacle must be between connNode and annotation vertically
|
|
105
|
-
if (b.y + b.height <= top || b.y >= bottom)
|
|
106
|
-
continue;
|
|
107
|
-
crossings++;
|
|
108
|
-
}
|
|
109
|
-
return crossings;
|
|
110
|
-
}
|
|
111
|
-
function hasOverlap(a, others) {
|
|
112
|
-
for (const b of others) {
|
|
113
|
-
if (a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y)
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
function hasOverlapPadded(a, others, padding) {
|
|
119
|
-
for (const b of others) {
|
|
120
|
-
if (a.x - padding < b.x + b.width &&
|
|
121
|
-
a.x + a.width + padding > b.x &&
|
|
122
|
-
a.y - padding < b.y + b.height &&
|
|
123
|
-
a.y + a.height + padding > b.y)
|
|
124
|
-
return true;
|
|
125
|
-
}
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
34
|
function nodeToShape(node, dx, dy) {
|
|
129
35
|
const shape = {
|
|
130
36
|
id: `${node.id}_di`,
|
|
@@ -263,33 +169,17 @@ function addAnnotationShapes(process, layoutNodes, annLocalBounds, allShapes, al
|
|
|
263
169
|
const elId = annId === assoc.sourceRef ? assoc.targetRef : assoc.sourceRef;
|
|
264
170
|
const annB = annId ? annLocalBounds.get(annId) : undefined;
|
|
265
171
|
const elNode = nodeById.get(elId);
|
|
266
|
-
if (!annB || !elNode)
|
|
172
|
+
if (!annB || !annId || !elNode)
|
|
267
173
|
continue;
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
];
|
|
278
|
-
}
|
|
279
|
-
else if (annB.y + annB.height <= elB.y) {
|
|
280
|
-
// annotation above: element top-center → annotation bottom-center
|
|
281
|
-
waypoints = [
|
|
282
|
-
{ x: elCx, y: Math.round(elB.y + dy) },
|
|
283
|
-
{ x: annCx, y: Math.round(annB.y + annB.height + dy) },
|
|
284
|
-
];
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
// side-by-side: center-to-center
|
|
288
|
-
waypoints = [
|
|
289
|
-
{ x: Math.round(elB.x + elB.width / 2 + dx), y: Math.round(elB.y + elB.height / 2 + dy) },
|
|
290
|
-
{ x: annCx, y: Math.round(annB.y + annB.height / 2 + dy) },
|
|
291
|
-
];
|
|
292
|
-
}
|
|
174
|
+
const { pElem, pAnn } = associationWaypoints(elNode.bounds, annB);
|
|
175
|
+
// Shift into diagram space and honour the original sourceRef→targetRef order.
|
|
176
|
+
const shifted = {
|
|
177
|
+
pElem: { x: Math.round(pElem.x + dx), y: Math.round(pElem.y + dy) },
|
|
178
|
+
pAnn: { x: Math.round(pAnn.x + dx), y: Math.round(pAnn.y + dy) },
|
|
179
|
+
};
|
|
180
|
+
const wp1 = assoc.sourceRef === annId ? shifted.pAnn : shifted.pElem;
|
|
181
|
+
const wp2 = assoc.sourceRef === annId ? shifted.pElem : shifted.pAnn;
|
|
182
|
+
const waypoints = [wp1, wp2];
|
|
293
183
|
allEdges.push({
|
|
294
184
|
id: `${assoc.id}_di`,
|
|
295
185
|
bpmnElement: assoc.id,
|
|
@@ -324,15 +214,11 @@ export function applyAutoLayout(defs) {
|
|
|
324
214
|
const participantId = processToParticipant.get(process.id);
|
|
325
215
|
const lanes = process.laneSet?.lanes ?? [];
|
|
326
216
|
const hasLanes = lanes.length > 0;
|
|
327
|
-
// layoutProcess now handles boundary event repositioning internally.
|
|
328
217
|
const result = layoutProcess(process);
|
|
329
|
-
// Re-resolve edge crossings after boundary events moved shapes
|
|
330
|
-
const nodeMap = new Map(result.nodes.map((n) => [n.id, n]));
|
|
331
|
-
resolveEdgeCrossings(result.edges, nodeMap);
|
|
332
218
|
if (result.nodes.length === 0)
|
|
333
219
|
continue;
|
|
334
220
|
// Pre-compute annotation positions in layout space so they're included in the bbox
|
|
335
|
-
const annBounds =
|
|
221
|
+
const annBounds = packAnnotations(process, result.nodes);
|
|
336
222
|
const { minX, minY, maxX, maxY } = contentBbox(result.nodes, annBounds.values());
|
|
337
223
|
const contentW = maxX - minX;
|
|
338
224
|
const contentH = maxY - minY;
|
|
@@ -371,6 +257,33 @@ export function applyAutoLayout(defs) {
|
|
|
371
257
|
poolY += innerH + POOL_GAP;
|
|
372
258
|
}
|
|
373
259
|
}
|
|
260
|
+
if (collab && collab.messageFlows.length > 0) {
|
|
261
|
+
const shapeByElement = new Map(allShapes.map((s) => [s.bpmnElement, s.bounds]));
|
|
262
|
+
for (const mf of collab.messageFlows) {
|
|
263
|
+
const src = shapeByElement.get(mf.sourceRef);
|
|
264
|
+
const tgt = shapeByElement.get(mf.targetRef);
|
|
265
|
+
if (!src || !tgt)
|
|
266
|
+
continue;
|
|
267
|
+
const srcBelow = src.y + src.height / 2 > tgt.y + tgt.height / 2;
|
|
268
|
+
const sx = Math.round(src.x + src.width / 2);
|
|
269
|
+
const tx = Math.round(tgt.x + tgt.width / 2);
|
|
270
|
+
const sy = srcBelow ? src.y : src.y + src.height;
|
|
271
|
+
const ty = srcBelow ? tgt.y + tgt.height : tgt.y;
|
|
272
|
+
const midY = Math.round((sy + ty) / 2);
|
|
273
|
+
const waypoints = sx === tx
|
|
274
|
+
? [
|
|
275
|
+
{ x: sx, y: sy },
|
|
276
|
+
{ x: tx, y: ty },
|
|
277
|
+
]
|
|
278
|
+
: [
|
|
279
|
+
{ x: sx, y: sy },
|
|
280
|
+
{ x: sx, y: midY },
|
|
281
|
+
{ x: tx, y: midY },
|
|
282
|
+
{ x: tx, y: ty },
|
|
283
|
+
];
|
|
284
|
+
allEdges.push({ id: `${mf.id}_di`, bpmnElement: mf.id, waypoints, unknownAttributes: {} });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
374
287
|
const planeBpmnElement = collab?.id ?? defs.processes[0]?.id ?? "plane";
|
|
375
288
|
const existingDiagram = defs.diagrams[0];
|
|
376
289
|
return {
|
|
@@ -321,6 +321,12 @@ export declare class BranchBuilder {
|
|
|
321
321
|
* @param handler - Callback that chains elements from the boundary event.
|
|
322
322
|
*/
|
|
323
323
|
withBoundary(id: string, options: Omit<BoundaryEventOptions, "attachedTo">, handler: (b: BranchBuilder) => void): this;
|
|
324
|
+
/** Add a sub-process (mirrors `ProcessBuilder.subProcess`). */
|
|
325
|
+
subProcess(id: string, content: (b: SubProcessContentBuilder) => void, options?: SubProcessOptions): this;
|
|
326
|
+
/** Add an ad-hoc sub-process (mirrors `ProcessBuilder.adHocSubProcess`). */
|
|
327
|
+
adHocSubProcess(id: string, content: (b: SubProcessContentBuilder) => void, options?: AdHocSubProcessOptions): this;
|
|
328
|
+
/** Add an event sub-process. Triggered by its start event — no incoming or outgoing sequence flows. */
|
|
329
|
+
eventSubProcess(id: string, content: (b: SubProcessContentBuilder) => void, options?: ElementOptions): this;
|
|
324
330
|
/**
|
|
325
331
|
* Create a named branch from the last gateway added inside this branch.
|
|
326
332
|
*
|
|
@@ -372,6 +378,30 @@ export declare class SubProcessContentBuilder {
|
|
|
372
378
|
branch(name: string, callback: (b: BranchBuilder) => void): this;
|
|
373
379
|
connectTo(targetId: string): this;
|
|
374
380
|
element(elementId: string): this;
|
|
381
|
+
/**
|
|
382
|
+
* Add a boundary event attached to an existing activity in this sub-process.
|
|
383
|
+
*
|
|
384
|
+
* The boundary event is NOT connected by a sequence flow — it attaches via
|
|
385
|
+
* `attachedToRef`. The builder cursor advances to the boundary event so
|
|
386
|
+
* subsequent elements chain from it. Use `withBoundary()` if you want the
|
|
387
|
+
* cursor to return to the task afterward.
|
|
388
|
+
*/
|
|
389
|
+
boundaryEvent(id: string, options: BoundaryEventOptions): this;
|
|
390
|
+
/**
|
|
391
|
+
* Attach a boundary event to the preceding task and build its outgoing path,
|
|
392
|
+
* then restore the builder cursor to the preceding task so the main flow continues.
|
|
393
|
+
*
|
|
394
|
+
* @param id - ID for the boundary event element.
|
|
395
|
+
* @param options - Boundary event options (without `attachedTo` — inferred from cursor).
|
|
396
|
+
* @param handler - Callback that chains elements from the boundary event.
|
|
397
|
+
*/
|
|
398
|
+
withBoundary(id: string, options: Omit<BoundaryEventOptions, "attachedTo">, handler: (b: SubProcessContentBuilder) => void): this;
|
|
399
|
+
/** Add a sub-process nested inside this sub-process's content. */
|
|
400
|
+
subProcess(id: string, content: (b: SubProcessContentBuilder) => void, options?: SubProcessOptions): this;
|
|
401
|
+
/** Add an ad-hoc sub-process nested inside this sub-process's content. */
|
|
402
|
+
adHocSubProcess(id: string, content: (b: SubProcessContentBuilder) => void, options?: AdHocSubProcessOptions): this;
|
|
403
|
+
/** Add an event sub-process. Triggered by its start event — no incoming or outgoing sequence flows. */
|
|
404
|
+
eventSubProcess(id: string, content: (b: SubProcessContentBuilder) => void, options?: ElementOptions): this;
|
|
375
405
|
}
|
|
376
406
|
/** Fluent builder for constructing BPMN processes. */
|
|
377
407
|
export declare class ProcessBuilder {
|