@bpmnkit/core 0.0.23 → 0.0.25
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 +1 -131
- package/dist/bpmn/bpmn-builder.d.ts +142 -19
- package/dist/bpmn/bpmn-builder.js +736 -405
- package/dist/bpmn/bpmn-model.d.ts +2 -0
- package/dist/bpmn/bpmn-parser.js +16 -2
- package/dist/bpmn/bpmn-serializer.js +6 -0
- package/dist/bpmn/index.d.ts +15 -1
- package/dist/bpmn/index.js +17 -1
- package/dist/bpmn/zeebe-extensions.d.ts +2 -0
- package/dist/bpmn/zeebe-extensions.js +3 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/layout/layout-engine.d.ts +2 -1
- package/dist/layout/layout-engine.js +130 -1
- package/dist/layout/overlap.js +3 -0
- package/package.json +2 -2
|
@@ -105,10 +105,12 @@ interface BpmnFlowNodeBase {
|
|
|
105
105
|
documentation?: string;
|
|
106
106
|
extensionElements: XmlElement[];
|
|
107
107
|
unknownAttributes: Record<string, string>;
|
|
108
|
+
isForCompensation?: boolean;
|
|
108
109
|
}
|
|
109
110
|
export interface BpmnStartEvent extends BpmnFlowNodeBase {
|
|
110
111
|
type: "startEvent";
|
|
111
112
|
eventDefinitions: BpmnEventDefinition[];
|
|
113
|
+
isInterrupting?: boolean;
|
|
112
114
|
}
|
|
113
115
|
export interface BpmnEndEvent extends BpmnFlowNodeBase {
|
|
114
116
|
type: "endEvent";
|
package/dist/bpmn/bpmn-parser.js
CHANGED
|
@@ -42,6 +42,7 @@ const KNOWN_ATTRS = new Set([
|
|
|
42
42
|
"default",
|
|
43
43
|
"attachedToRef",
|
|
44
44
|
"cancelActivity",
|
|
45
|
+
"isForCompensation",
|
|
45
46
|
"sourceRef",
|
|
46
47
|
"targetRef",
|
|
47
48
|
"associationDirection",
|
|
@@ -275,7 +276,15 @@ function parseFlowElement(element) {
|
|
|
275
276
|
unknownAttributes: unknownAttrs(element),
|
|
276
277
|
};
|
|
277
278
|
switch (ln) {
|
|
278
|
-
case "startEvent":
|
|
279
|
+
case "startEvent": {
|
|
280
|
+
const isInterruptingAttr = attr(element, "isInterrupting");
|
|
281
|
+
return {
|
|
282
|
+
...base,
|
|
283
|
+
type: "startEvent",
|
|
284
|
+
eventDefinitions: parseEventDefinitions(element),
|
|
285
|
+
...(isInterruptingAttr === "false" ? { isInterrupting: false } : {}),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
279
288
|
case "endEvent":
|
|
280
289
|
case "intermediateCatchEvent":
|
|
281
290
|
case "intermediateThrowEvent":
|
|
@@ -299,7 +308,12 @@ function parseFlowElement(element) {
|
|
|
299
308
|
case "businessRuleTask":
|
|
300
309
|
case "manualTask":
|
|
301
310
|
case "callActivity":
|
|
302
|
-
return {
|
|
311
|
+
return {
|
|
312
|
+
...base,
|
|
313
|
+
type: ln,
|
|
314
|
+
loopCharacteristics: parseLoopCharacteristics(element),
|
|
315
|
+
isForCompensation: attr(element, "isForCompensation") === "true" ? true : undefined,
|
|
316
|
+
};
|
|
303
317
|
case "adHocSubProcess":
|
|
304
318
|
return {
|
|
305
319
|
...base,
|
|
@@ -157,6 +157,8 @@ function serializeFlowElement(fe, ns) {
|
|
|
157
157
|
const attrs = { id: fe.id, ...fe.unknownAttributes };
|
|
158
158
|
if (fe.name !== undefined)
|
|
159
159
|
attrs.name = fe.name;
|
|
160
|
+
if (fe.isForCompensation)
|
|
161
|
+
attrs.isForCompensation = "true";
|
|
160
162
|
const children = [];
|
|
161
163
|
// Documentation
|
|
162
164
|
if (fe.documentation !== undefined) {
|
|
@@ -169,6 +171,10 @@ function serializeFlowElement(fe, ns) {
|
|
|
169
171
|
children.push(...flowRefs(fe.outgoing, "outgoing", bp));
|
|
170
172
|
switch (fe.type) {
|
|
171
173
|
case "startEvent":
|
|
174
|
+
if (fe.isInterrupting === false)
|
|
175
|
+
attrs.isInterrupting = "false";
|
|
176
|
+
children.push(...serializeEventDefinitions(fe.eventDefinitions, bp));
|
|
177
|
+
break;
|
|
172
178
|
case "endEvent":
|
|
173
179
|
case "intermediateCatchEvent":
|
|
174
180
|
case "intermediateThrowEvent":
|
package/dist/bpmn/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ProcessBuilder } from "./bpmn-builder.js";
|
|
1
|
+
import { DiagramBuilder, ProcessBuilder } from "./bpmn-builder.js";
|
|
2
2
|
import type { BpmnDefinitions } from "./bpmn-model.js";
|
|
3
3
|
/** A minimal 3-element BPMN diagram useful for first-launch or "New Diagram" defaults. */
|
|
4
4
|
export declare const SAMPLE_BPMN_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<bpmn:definitions xmlns:bpmn=\"http://www.omg.org/spec/BPMN/20100524/MODEL\"\n xmlns:bpmndi=\"http://www.omg.org/spec/BPMN/20100524/DI\"\n xmlns:dc=\"http://www.omg.org/spec/DD/20100524/DC\"\n xmlns:di=\"http://www.omg.org/spec/DD/20100524/DI\"\n id=\"Definitions_1\" targetNamespace=\"http://bpmn.io/schema/bpmn\">\n <bpmn:process id=\"proc\" isExecutable=\"true\">\n <bpmn:startEvent id=\"start\" name=\"Start\">\n <bpmn:outgoing>flow1</bpmn:outgoing>\n </bpmn:startEvent>\n <bpmn:serviceTask id=\"task1\" name=\"Process Order\">\n <bpmn:incoming>flow1</bpmn:incoming>\n <bpmn:outgoing>flow2</bpmn:outgoing>\n </bpmn:serviceTask>\n <bpmn:endEvent id=\"end\" name=\"End\">\n <bpmn:incoming>flow2</bpmn:incoming>\n </bpmn:endEvent>\n <bpmn:sequenceFlow id=\"flow1\" sourceRef=\"start\" targetRef=\"task1\"/>\n <bpmn:sequenceFlow id=\"flow2\" sourceRef=\"task1\" targetRef=\"end\"/>\n </bpmn:process>\n <bpmndi:BPMNDiagram id=\"diagram1\">\n <bpmndi:BPMNPlane id=\"plane1\" bpmnElement=\"proc\">\n <bpmndi:BPMNShape id=\"start_di\" bpmnElement=\"start\">\n <dc:Bounds x=\"152\" y=\"202\" width=\"36\" height=\"36\"/>\n </bpmndi:BPMNShape>\n <bpmndi:BPMNShape id=\"task1_di\" bpmnElement=\"task1\">\n <dc:Bounds x=\"260\" y=\"180\" width=\"100\" height=\"80\"/>\n </bpmndi:BPMNShape>\n <bpmndi:BPMNShape id=\"end_di\" bpmnElement=\"end\">\n <dc:Bounds x=\"432\" y=\"202\" width=\"36\" height=\"36\"/>\n </bpmndi:BPMNShape>\n <bpmndi:BPMNEdge id=\"flow1_di\" bpmnElement=\"flow1\">\n <di:waypoint x=\"188\" y=\"220\"/>\n <di:waypoint x=\"260\" y=\"220\"/>\n </bpmndi:BPMNEdge>\n <bpmndi:BPMNEdge id=\"flow2_di\" bpmnElement=\"flow2\">\n <di:waypoint x=\"360\" y=\"220\"/>\n <di:waypoint x=\"432\" y=\"220\"/>\n </bpmndi:BPMNEdge>\n </bpmndi:BPMNPlane>\n </bpmndi:BPMNDiagram>\n</bpmn:definitions>";
|
|
@@ -41,6 +41,20 @@ export declare const Bpmn: {
|
|
|
41
41
|
* ```
|
|
42
42
|
*/
|
|
43
43
|
readonly createProcess: (processId: string) => ProcessBuilder;
|
|
44
|
+
/**
|
|
45
|
+
* Create a multi-process BPMN definitions document using the fluent builder API.
|
|
46
|
+
*
|
|
47
|
+
* @param id - Unique identifier for the definitions element (defaults to `"Definitions_1"`).
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const defs = Bpmn.createDiagram("OrderSystem")
|
|
52
|
+
* .process("caller", (p) => p.startEvent("s").callActivity("call", { processId: "callee" }).endEvent("e"))
|
|
53
|
+
* .process("callee", (p) => p.startEvent("s2").serviceTask("work", { name: "Work", taskType: "work" }).endEvent("e2"))
|
|
54
|
+
* .build()
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
readonly createDiagram: (id?: string) => DiagramBuilder;
|
|
44
58
|
/**
|
|
45
59
|
* Parse a BPMN 2.0 XML string into a typed {@link BpmnDefinitions} model.
|
|
46
60
|
*
|
package/dist/bpmn/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { applyAutoLayout } from "./auto-layout.js";
|
|
2
|
-
import { ProcessBuilder } from "./bpmn-builder.js";
|
|
2
|
+
import { DiagramBuilder, ProcessBuilder } from "./bpmn-builder.js";
|
|
3
3
|
import { parseBpmn } from "./bpmn-parser.js";
|
|
4
4
|
import { serializeBpmn } from "./bpmn-serializer.js";
|
|
5
5
|
/** A minimal 3-element BPMN diagram useful for first-launch or "New Diagram" defaults. */
|
|
@@ -86,6 +86,22 @@ export const Bpmn = {
|
|
|
86
86
|
createProcess(processId) {
|
|
87
87
|
return new ProcessBuilder(processId);
|
|
88
88
|
},
|
|
89
|
+
/**
|
|
90
|
+
* Create a multi-process BPMN definitions document using the fluent builder API.
|
|
91
|
+
*
|
|
92
|
+
* @param id - Unique identifier for the definitions element (defaults to `"Definitions_1"`).
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* const defs = Bpmn.createDiagram("OrderSystem")
|
|
97
|
+
* .process("caller", (p) => p.startEvent("s").callActivity("call", { processId: "callee" }).endEvent("e"))
|
|
98
|
+
* .process("callee", (p) => p.startEvent("s2").serviceTask("work", { name: "Work", taskType: "work" }).endEvent("e2"))
|
|
99
|
+
* .build()
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
createDiagram(id = "Definitions_1") {
|
|
103
|
+
return new DiagramBuilder(id);
|
|
104
|
+
},
|
|
89
105
|
/**
|
|
90
106
|
* Parse a BPMN 2.0 XML string into a typed {@link BpmnDefinitions} model.
|
|
91
107
|
*
|
|
@@ -64,6 +64,8 @@ export interface ZeebeExtensions {
|
|
|
64
64
|
formDefinition?: ZeebeFormDefinition;
|
|
65
65
|
/** DMN decision invoked by a business rule task (zeebe:calledDecision). */
|
|
66
66
|
calledDecision?: ZeebeCalledDecision;
|
|
67
|
+
/** Marks this as a Camunda 8 native user task (zeebe:userTask). */
|
|
68
|
+
userTask?: true;
|
|
67
69
|
/** Unrecognized extension elements preserved for roundtrip. */
|
|
68
70
|
unknownElements?: XmlElement[];
|
|
69
71
|
}
|
|
@@ -71,6 +71,9 @@ export function zeebeExtensionsToXmlElements(extensions) {
|
|
|
71
71
|
attrs.activeElementsCollection = activeElementsCollection;
|
|
72
72
|
elements.push({ name: "zeebe:adHoc", attributes: attrs, children: [] });
|
|
73
73
|
}
|
|
74
|
+
if (extensions.userTask) {
|
|
75
|
+
elements.push({ name: "zeebe:userTask", attributes: {}, children: [] });
|
|
76
|
+
}
|
|
74
77
|
if (extensions.formDefinition) {
|
|
75
78
|
elements.push({
|
|
76
79
|
name: "zeebe:formDefinition",
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ export { isBpmnActivity, isBpmnAdHocSubProcess, isBpmnBoundaryEvent, isBpmnBusin
|
|
|
4
4
|
export { findElement, findElementInProcess, findProcess, findSequenceFlow, getAllElements, getElementType, getZeebeExtensions, } from "./bpmn/utils.js";
|
|
5
5
|
export { Bpmn, SAMPLE_BPMN_XML } from "./bpmn/index.js";
|
|
6
6
|
export { applyAutoLayout } from "./bpmn/auto-layout.js";
|
|
7
|
-
export
|
|
7
|
+
export { DiagramBuilder } from "./bpmn/bpmn-builder.js";
|
|
8
|
+
export type { ProcessBuilder, BranchBuilder, SubProcessContentBuilder, ServiceTaskOptions, ScriptTaskOptions, UserTaskOptions, CallActivityOptions, BusinessRuleTaskOptions, ElementOptions, GatewayOptions, MultiInstanceOptions, SubProcessOptions, StartEventOptions, IntermediateCatchEventOptions, IntermediateThrowEventOptions, EndEventOptions, BoundaryEventOptions, AdHocSubProcessOptions, } from "./bpmn/bpmn-builder.js";
|
|
8
9
|
export type { BpmnDefinitions, BpmnProcess, BpmnFlowNode, BpmnFlowElement, BpmnSequenceFlow, BpmnBoundaryEvent, BpmnElementType, BpmnStartEvent, BpmnEndEvent, BpmnIntermediateCatchEvent, BpmnIntermediateThrowEvent, BpmnTask, BpmnServiceTask, BpmnScriptTask, BpmnUserTask, BpmnSendTask, BpmnReceiveTask, BpmnBusinessRuleTask, BpmnManualTask, BpmnCallActivity, BpmnSubProcess, BpmnAdHocSubProcess, BpmnEventSubProcess, BpmnTransaction, BpmnExclusiveGateway, BpmnParallelGateway, BpmnInclusiveGateway, BpmnEventBasedGateway, BpmnComplexGateway, BpmnCollaboration, BpmnParticipant, BpmnMessageFlow, BpmnLane, BpmnLaneSet, BpmnError, BpmnEscalation, BpmnMessage, BpmnSignal, BpmnTextAnnotation, BpmnAssociation, BpmnConditionExpression, BpmnEventDefinition, BpmnTimerEventDefinition, BpmnErrorEventDefinition, BpmnEscalationEventDefinition, BpmnMessageEventDefinition, BpmnSignalEventDefinition, BpmnConditionalEventDefinition, BpmnLinkEventDefinition, BpmnCancelEventDefinition, BpmnTerminateEventDefinition, BpmnCompensateEventDefinition, BpmnMultiInstanceLoopCharacteristics, BpmnDiagram, BpmnDiPlane, BpmnDiShape, BpmnDiEdge, BpmnDiLabel, BpmnBounds, BpmnWaypoint, } from "./bpmn/bpmn-model.js";
|
|
9
10
|
export type { RestConnectorConfig, RestAuthentication, HttpMethod, } from "./bpmn/rest-connector.js";
|
|
10
11
|
export type { ZeebeExtensions, ZeebeTaskDefinition, ZeebeIoMapping, ZeebeIoMappingEntry, ZeebeTaskHeaders, ZeebeTaskHeaderEntry, ZeebeProperties, ZeebePropertyEntry, ZeebeFormDefinition, ZeebeCalledDecision, } from "./bpmn/zeebe-extensions.js";
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { isBpmnActivity, isBpmnAdHocSubProcess, isBpmnBoundaryEvent, isBpmnBusin
|
|
|
3
3
|
export { findElement, findElementInProcess, findProcess, findSequenceFlow, getAllElements, getElementType, getZeebeExtensions, } from "./bpmn/utils.js";
|
|
4
4
|
export { Bpmn, SAMPLE_BPMN_XML } from "./bpmn/index.js";
|
|
5
5
|
export { applyAutoLayout } from "./bpmn/auto-layout.js";
|
|
6
|
+
export { DiagramBuilder } from "./bpmn/bpmn-builder.js";
|
|
6
7
|
export { zeebeExtensionsToXmlElements } from "./bpmn/zeebe-extensions.js";
|
|
7
8
|
export { Dmn, layoutDmn, benchmarkDmnLayout, compactifyDmn, expandDmn } from "./dmn/index.js";
|
|
8
9
|
export { Form, compactifyForm, expandForm } from "./form/index.js";
|
|
@@ -10,7 +10,8 @@ import type { LayoutResult } from "./types.js";
|
|
|
10
10
|
* 4. Coordinate assignment — Fixed element sizes with spacing
|
|
11
11
|
* 5. Sub-process layout — Recursive nested passes
|
|
12
12
|
* 6. Edge routing — Orthogonal waypoints
|
|
13
|
-
* 7.
|
|
13
|
+
* 7. Boundary event repositioning — place events on host border
|
|
14
|
+
* 8. Overlap assertion — Post-condition validation
|
|
14
15
|
*/
|
|
15
16
|
export declare function layoutProcess(process: BpmnProcess): LayoutResult;
|
|
16
17
|
/**
|
|
@@ -7,6 +7,133 @@ import { assignLayers, groupByLayer } from "./layers.js";
|
|
|
7
7
|
import { assertNoOverlap } from "./overlap.js";
|
|
8
8
|
import { routeEdges } from "./routing.js";
|
|
9
9
|
import { layoutSubProcesses } from "./subprocess.js";
|
|
10
|
+
const CHAIN_GAP = 30;
|
|
11
|
+
const CHAIN_V_GAP = 20;
|
|
12
|
+
/**
|
|
13
|
+
* Reposition boundary events to the bottom edge of their host task, then walk
|
|
14
|
+
* each boundary event's exclusive downstream chain and place those nodes
|
|
15
|
+
* horizontally to the right of the host task. Re-routes all affected edges.
|
|
16
|
+
*/
|
|
17
|
+
function repositionBoundaryEvents(flowElements, result) {
|
|
18
|
+
const boundaryMap = new Map();
|
|
19
|
+
for (const el of flowElements) {
|
|
20
|
+
if (el.type !== "boundaryEvent")
|
|
21
|
+
continue;
|
|
22
|
+
const list = boundaryMap.get(el.attachedToRef) ?? [];
|
|
23
|
+
list.push(el.id);
|
|
24
|
+
boundaryMap.set(el.attachedToRef, list);
|
|
25
|
+
}
|
|
26
|
+
if (boundaryMap.size === 0)
|
|
27
|
+
return;
|
|
28
|
+
const nodeById = new Map(result.nodes.map((n) => [n.id, n]));
|
|
29
|
+
const succIds = new Map();
|
|
30
|
+
const predIds = new Map();
|
|
31
|
+
for (const edge of result.edges) {
|
|
32
|
+
const se = succIds.get(edge.sourceRef) ?? [];
|
|
33
|
+
se.push(edge.targetRef);
|
|
34
|
+
succIds.set(edge.sourceRef, se);
|
|
35
|
+
const ps = predIds.get(edge.targetRef) ?? new Set();
|
|
36
|
+
ps.add(edge.sourceRef);
|
|
37
|
+
predIds.set(edge.targetRef, ps);
|
|
38
|
+
}
|
|
39
|
+
for (const [hostId, beIds] of boundaryMap) {
|
|
40
|
+
const hostNode = nodeById.get(hostId);
|
|
41
|
+
if (!hostNode)
|
|
42
|
+
continue;
|
|
43
|
+
for (let i = 0; i < beIds.length; i++) {
|
|
44
|
+
const beId = beIds[i];
|
|
45
|
+
if (!beId)
|
|
46
|
+
continue;
|
|
47
|
+
const beNode = nodeById.get(beId);
|
|
48
|
+
if (!beNode)
|
|
49
|
+
continue;
|
|
50
|
+
const bW = beNode.bounds.width;
|
|
51
|
+
const bH = beNode.bounds.height;
|
|
52
|
+
// Place boundary event on the bottom edge of the host task, stacking leftward
|
|
53
|
+
const rightEdge = hostNode.bounds.x + hostNode.bounds.width;
|
|
54
|
+
beNode.bounds.x = Math.round(rightEdge - bW / 2 - i * (bW + 4));
|
|
55
|
+
beNode.bounds.y = Math.round(hostNode.bounds.y + hostNode.bounds.height - bH / 2);
|
|
56
|
+
if (beNode.labelBounds) {
|
|
57
|
+
beNode.labelBounds.x = beNode.bounds.x + Math.round(bW / 2 - beNode.labelBounds.width / 2);
|
|
58
|
+
beNode.labelBounds.y = beNode.bounds.y + bH + 4;
|
|
59
|
+
}
|
|
60
|
+
// Collect nodes exclusively reachable from this boundary event (BFS)
|
|
61
|
+
const chainSet = new Set([beId]);
|
|
62
|
+
const chainOrder = [];
|
|
63
|
+
const queue = [...(succIds.get(beId) ?? [])];
|
|
64
|
+
while (queue.length > 0) {
|
|
65
|
+
const id = queue.shift();
|
|
66
|
+
if (!id || chainSet.has(id))
|
|
67
|
+
continue;
|
|
68
|
+
const preds = predIds.get(id) ?? new Set();
|
|
69
|
+
if ([...preds].every((p) => chainSet.has(p))) {
|
|
70
|
+
chainSet.add(id);
|
|
71
|
+
chainOrder.push(id);
|
|
72
|
+
queue.push(...(succIds.get(id) ?? []));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Each boundary event's chain gets its own vertical lane
|
|
76
|
+
let maxChainH = 0;
|
|
77
|
+
for (const id of chainOrder) {
|
|
78
|
+
const n = nodeById.get(id);
|
|
79
|
+
if (n)
|
|
80
|
+
maxChainH = Math.max(maxChainH, n.bounds.height);
|
|
81
|
+
}
|
|
82
|
+
const laneOffset = i * (maxChainH + CHAIN_V_GAP + 10);
|
|
83
|
+
const chainCenterY = Math.round(beNode.bounds.y + bH + CHAIN_V_GAP + maxChainH / 2 + laneOffset);
|
|
84
|
+
const chainStartX = Math.max(Math.round(beNode.bounds.x + bW / 2) + CHAIN_GAP, hostNode.bounds.x + hostNode.bounds.width + CHAIN_GAP);
|
|
85
|
+
let curX = chainStartX;
|
|
86
|
+
for (const id of chainOrder) {
|
|
87
|
+
const n = nodeById.get(id);
|
|
88
|
+
if (!n)
|
|
89
|
+
continue;
|
|
90
|
+
n.bounds.x = curX;
|
|
91
|
+
n.bounds.y = chainCenterY - Math.round(n.bounds.height / 2);
|
|
92
|
+
if (n.labelBounds) {
|
|
93
|
+
// Center label on node, but clamp so it never extends left of the node —
|
|
94
|
+
// wide event labels would otherwise overlap the preceding chain element.
|
|
95
|
+
const labelX = n.bounds.x + Math.round(n.bounds.width / 2 - n.labelBounds.width / 2);
|
|
96
|
+
n.labelBounds.x = Math.max(labelX, n.bounds.x);
|
|
97
|
+
n.labelBounds.y = n.bounds.y + n.bounds.height + 4;
|
|
98
|
+
}
|
|
99
|
+
// Advance past the node AND its label so the next element doesn't overlap.
|
|
100
|
+
const nodeRight = n.bounds.x + n.bounds.width;
|
|
101
|
+
const labelRight = n.labelBounds ? n.labelBounds.x + n.labelBounds.width : 0;
|
|
102
|
+
curX = Math.max(nodeRight, labelRight) + CHAIN_GAP;
|
|
103
|
+
}
|
|
104
|
+
// Re-route edges touching the boundary event or its chain
|
|
105
|
+
for (const edge of result.edges) {
|
|
106
|
+
if (!chainSet.has(edge.sourceRef))
|
|
107
|
+
continue;
|
|
108
|
+
const src = nodeById.get(edge.sourceRef);
|
|
109
|
+
const tgt = nodeById.get(edge.targetRef);
|
|
110
|
+
if (!src || !tgt)
|
|
111
|
+
continue;
|
|
112
|
+
if (edge.sourceRef === beId) {
|
|
113
|
+
const srcX = Math.round(src.bounds.x + bW / 2);
|
|
114
|
+
const srcY = Math.round(src.bounds.y + bH);
|
|
115
|
+
const tgtX = Math.round(tgt.bounds.x);
|
|
116
|
+
const tgtY = Math.round(tgt.bounds.y + tgt.bounds.height / 2);
|
|
117
|
+
edge.waypoints = [
|
|
118
|
+
{ x: srcX, y: srcY },
|
|
119
|
+
{ x: srcX, y: tgtY },
|
|
120
|
+
{ x: tgtX, y: tgtY },
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
const srcX = Math.round(src.bounds.x + src.bounds.width);
|
|
125
|
+
const srcY = Math.round(src.bounds.y + src.bounds.height / 2);
|
|
126
|
+
const tgtX = Math.round(tgt.bounds.x);
|
|
127
|
+
const tgtY = Math.round(tgt.bounds.y + tgt.bounds.height / 2);
|
|
128
|
+
edge.waypoints = [
|
|
129
|
+
{ x: srcX, y: srcY },
|
|
130
|
+
{ x: tgtX, y: tgtY },
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
10
137
|
/**
|
|
11
138
|
* Auto-layout a BPMN process using the Sugiyama/layered algorithm.
|
|
12
139
|
*
|
|
@@ -17,10 +144,12 @@ import { layoutSubProcesses } from "./subprocess.js";
|
|
|
17
144
|
* 4. Coordinate assignment — Fixed element sizes with spacing
|
|
18
145
|
* 5. Sub-process layout — Recursive nested passes
|
|
19
146
|
* 6. Edge routing — Orthogonal waypoints
|
|
20
|
-
* 7.
|
|
147
|
+
* 7. Boundary event repositioning — place events on host border
|
|
148
|
+
* 8. Overlap assertion — Post-condition validation
|
|
21
149
|
*/
|
|
22
150
|
export function layoutProcess(process) {
|
|
23
151
|
const result = layoutFlowNodes(process.flowElements, process.sequenceFlows);
|
|
152
|
+
repositionBoundaryEvents(process.flowElements, result);
|
|
24
153
|
assertNoOverlap(result);
|
|
25
154
|
return result;
|
|
26
155
|
}
|
package/dist/layout/overlap.js
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
export function assertNoOverlap(result) {
|
|
9
9
|
const allBounds = [];
|
|
10
10
|
for (const node of result.nodes) {
|
|
11
|
+
// Boundary events intentionally overlap their host activity — skip them.
|
|
12
|
+
if (node.type === "boundaryEvent")
|
|
13
|
+
continue;
|
|
11
14
|
allBounds.push({ id: node.id, kind: "element", bounds: node.bounds });
|
|
12
15
|
if (node.labelBounds) {
|
|
13
16
|
allBounds.push({ id: `${node.id}-label`, kind: "label", bounds: node.labelBounds });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bpmnkit/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"dist/**/*.d.ts"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@bpmnkit/feel": "0.0.
|
|
20
|
+
"@bpmnkit/feel": "0.0.19"
|
|
21
21
|
},
|
|
22
22
|
"description": "TypeScript-first BPMN 2.0 SDK — parse, build, layout, and optimize diagrams",
|
|
23
23
|
"keywords": [
|