@bpmnkit/core 0.1.1 → 0.1.2
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/README.md +2 -0
- package/dist/bpmn/agentic.d.ts +121 -0
- package/dist/bpmn/agentic.js +97 -0
- package/dist/bpmn/auto-layout.d.ts +5 -5
- package/dist/bpmn/auto-layout.js +592 -36
- package/dist/bpmn/bpmn-builder.d.ts +56 -0
- package/dist/bpmn/bpmn-builder.js +148 -182
- package/dist/bpmn/bpmn-model.d.ts +4 -0
- package/dist/bpmn/bpmn-parser.js +9 -1
- package/dist/bpmn/bpmn-serializer.js +6 -0
- package/dist/bpmn/optimize/agentic.d.ts +10 -0
- package/dist/bpmn/optimize/agentic.js +88 -0
- package/dist/bpmn/optimize/deploy.d.ts +16 -0
- package/dist/bpmn/optimize/deploy.js +143 -0
- package/dist/bpmn/optimize/feel-syntax.d.ts +12 -0
- package/dist/bpmn/optimize/feel-syntax.js +87 -0
- package/dist/bpmn/optimize/feel.js +5 -2
- package/dist/bpmn/optimize/flow.js +22 -2
- package/dist/bpmn/optimize/index.js +20 -9
- package/dist/bpmn/optimize/types.d.ts +10 -1
- package/dist/bpmn/zeebe-extensions.d.ts +27 -0
- package/dist/bpmn/zeebe-extensions.js +38 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +2 -0
- package/dist/layout/annotations.js +36 -1
- package/dist/layout/collaboration/alignment.d.ts +26 -0
- package/dist/layout/collaboration/alignment.js +66 -0
- package/dist/layout/collaboration/ordering.d.ts +21 -0
- package/dist/layout/collaboration/ordering.js +102 -0
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/layout-engine.d.ts +13 -3
- package/dist/layout/layout-engine.js +9 -4
- package/dist/layout/semantic/bands.d.ts +19 -0
- package/dist/layout/semantic/bands.js +324 -0
- package/dist/layout/semantic/graph.d.ts +29 -0
- package/dist/layout/semantic/graph.js +217 -0
- package/dist/layout/semantic/index.d.ts +13 -0
- package/dist/layout/semantic/index.js +181 -0
- package/dist/layout/semantic/place.d.ts +40 -0
- package/dist/layout/semantic/place.js +271 -0
- package/dist/layout/semantic/route.d.ts +14 -0
- package/dist/layout/semantic/route.js +454 -0
- package/dist/layout/types.d.ts +17 -0
- package/dist/plan/compile.d.ts +39 -0
- package/dist/plan/compile.js +380 -0
- package/dist/plan/extract.d.ts +31 -0
- package/dist/plan/extract.js +248 -0
- package/dist/plan/index.d.ts +6 -0
- package/dist/plan/index.js +5 -0
- package/dist/plan/merge.d.ts +13 -0
- package/dist/plan/merge.js +80 -0
- package/dist/plan/slug.d.ts +5 -0
- package/dist/plan/slug.js +22 -0
- package/dist/plan/types.d.ts +225 -0
- package/dist/plan/types.js +13 -0
- package/package.json +2 -2
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { BpmnBoundaryEvent, BpmnFlowElement, BpmnSequenceFlow } from "../../bpmn/bpmn-model.js";
|
|
2
|
+
/**
|
|
3
|
+
* Adjacency for one process scope, with cycles broken and ranks assigned.
|
|
4
|
+
*
|
|
5
|
+
* Boundary events are not ranked themselves: they dock onto their host, and an
|
|
6
|
+
* implicit host → handler edge carries their successors forward instead. That
|
|
7
|
+
* keeps a handler path strictly to the right of the activity it guards.
|
|
8
|
+
*/
|
|
9
|
+
export interface SemanticGraph {
|
|
10
|
+
/** Rankable flow nodes, in declaration order. Boundary events excluded. */
|
|
11
|
+
nodes: BpmnFlowElement[];
|
|
12
|
+
byId: Map<string, BpmnFlowElement>;
|
|
13
|
+
outgoing: Map<string, BpmnSequenceFlow[]>;
|
|
14
|
+
incoming: Map<string, BpmnSequenceFlow[]>;
|
|
15
|
+
/** hostId → boundary events attached to it, in declaration order. */
|
|
16
|
+
attachers: Map<string, BpmnBoundaryEvent[]>;
|
|
17
|
+
/** Ids of sequence flows that close a cycle; excluded from ranking. */
|
|
18
|
+
backEdges: Set<string>;
|
|
19
|
+
/** Weakly connected components, each a node-id list in declaration order. */
|
|
20
|
+
components: string[][];
|
|
21
|
+
/** Semantic start node id per component, index-aligned with `components`. */
|
|
22
|
+
starts: string[];
|
|
23
|
+
/** Longest-path rank per node id. */
|
|
24
|
+
ranks: Map<string, number>;
|
|
25
|
+
}
|
|
26
|
+
export declare function buildSemanticGraph(flowElements: BpmnFlowElement[], sequenceFlows: BpmnSequenceFlow[]): SemanticGraph;
|
|
27
|
+
/** True when an end event is reachable from `id` without traversing a back edge. */
|
|
28
|
+
export declare function reachesEnd(graph: SemanticGraph, id: string): boolean;
|
|
29
|
+
//# sourceMappingURL=graph.d.ts.map
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/** Flow-node types that end a path — used to score candidate spine edges. */
|
|
2
|
+
const END_TYPES = new Set(["endEvent"]);
|
|
3
|
+
/**
|
|
4
|
+
* The node a flow leaves from for layout purposes. A flow out of a boundary
|
|
5
|
+
* event belongs to the host's position, not the event's.
|
|
6
|
+
*/
|
|
7
|
+
function effectiveSource(flow, byId) {
|
|
8
|
+
const source = byId.get(flow.sourceRef);
|
|
9
|
+
if (source?.type === "boundaryEvent")
|
|
10
|
+
return source.attachedToRef;
|
|
11
|
+
return flow.sourceRef;
|
|
12
|
+
}
|
|
13
|
+
export function buildSemanticGraph(flowElements, sequenceFlows) {
|
|
14
|
+
const byId = new Map();
|
|
15
|
+
for (const el of flowElements)
|
|
16
|
+
byId.set(el.id, el);
|
|
17
|
+
const nodes = [];
|
|
18
|
+
const attachers = new Map();
|
|
19
|
+
for (const el of flowElements) {
|
|
20
|
+
if (el.type === "boundaryEvent") {
|
|
21
|
+
const be = el;
|
|
22
|
+
const list = attachers.get(be.attachedToRef);
|
|
23
|
+
if (list)
|
|
24
|
+
list.push(be);
|
|
25
|
+
else
|
|
26
|
+
attachers.set(be.attachedToRef, [be]);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
nodes.push(el);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const outgoing = new Map();
|
|
33
|
+
const incoming = new Map();
|
|
34
|
+
for (const flow of sequenceFlows) {
|
|
35
|
+
const from = effectiveSource(flow, byId);
|
|
36
|
+
if (!byId.has(from) || !byId.has(flow.targetRef))
|
|
37
|
+
continue;
|
|
38
|
+
const out = outgoing.get(from);
|
|
39
|
+
if (out)
|
|
40
|
+
out.push(flow);
|
|
41
|
+
else
|
|
42
|
+
outgoing.set(from, [flow]);
|
|
43
|
+
const inc = incoming.get(flow.targetRef);
|
|
44
|
+
if (inc)
|
|
45
|
+
inc.push(flow);
|
|
46
|
+
else
|
|
47
|
+
incoming.set(flow.targetRef, [flow]);
|
|
48
|
+
}
|
|
49
|
+
const graph = {
|
|
50
|
+
nodes,
|
|
51
|
+
byId,
|
|
52
|
+
outgoing,
|
|
53
|
+
incoming,
|
|
54
|
+
attachers,
|
|
55
|
+
backEdges: new Set(),
|
|
56
|
+
components: [],
|
|
57
|
+
starts: [],
|
|
58
|
+
ranks: new Map(),
|
|
59
|
+
};
|
|
60
|
+
findComponents(graph);
|
|
61
|
+
markBackEdges(graph);
|
|
62
|
+
assignRanks(graph);
|
|
63
|
+
return graph;
|
|
64
|
+
}
|
|
65
|
+
/** Weakly connected components, discovered in declaration order. */
|
|
66
|
+
function findComponents(graph) {
|
|
67
|
+
const seen = new Set();
|
|
68
|
+
for (const node of graph.nodes) {
|
|
69
|
+
if (seen.has(node.id))
|
|
70
|
+
continue;
|
|
71
|
+
const members = [];
|
|
72
|
+
const stack = [node.id];
|
|
73
|
+
while (stack.length > 0) {
|
|
74
|
+
const id = stack.pop();
|
|
75
|
+
if (id === undefined || seen.has(id))
|
|
76
|
+
continue;
|
|
77
|
+
seen.add(id);
|
|
78
|
+
members.push(id);
|
|
79
|
+
for (const f of graph.outgoing.get(id) ?? [])
|
|
80
|
+
stack.push(f.targetRef);
|
|
81
|
+
for (const f of graph.incoming.get(id) ?? [])
|
|
82
|
+
stack.push(effectiveSource(f, graph.byId));
|
|
83
|
+
}
|
|
84
|
+
// Declaration order within the component keeps ties deterministic.
|
|
85
|
+
const order = new Map(graph.nodes.map((n, i) => [n.id, i]));
|
|
86
|
+
members.sort((a, b) => (order.get(a) ?? 0) - (order.get(b) ?? 0));
|
|
87
|
+
graph.components.push(members);
|
|
88
|
+
graph.starts.push(pickStart(members, graph));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A component starts at its earliest declared start event, else its earliest
|
|
93
|
+
* node with no incoming flow, else its earliest declared node.
|
|
94
|
+
*/
|
|
95
|
+
function pickStart(members, graph) {
|
|
96
|
+
const startEvent = members.find((id) => graph.byId.get(id)?.type === "startEvent");
|
|
97
|
+
if (startEvent)
|
|
98
|
+
return startEvent;
|
|
99
|
+
const source = members.find((id) => (graph.incoming.get(id) ?? []).length === 0);
|
|
100
|
+
return source ?? members[0] ?? "";
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Depth-first from each semantic start; an edge onto a node already on the
|
|
104
|
+
* traversal stack closes a cycle and is excluded from ranking.
|
|
105
|
+
*/
|
|
106
|
+
function markBackEdges(graph) {
|
|
107
|
+
const state = new Map();
|
|
108
|
+
const visit = (id) => {
|
|
109
|
+
state.set(id, "open");
|
|
110
|
+
for (const flow of graph.outgoing.get(id) ?? []) {
|
|
111
|
+
const target = flow.targetRef;
|
|
112
|
+
const targetState = state.get(target);
|
|
113
|
+
if (targetState === "open")
|
|
114
|
+
graph.backEdges.add(flow.id);
|
|
115
|
+
else if (targetState === undefined)
|
|
116
|
+
visit(target);
|
|
117
|
+
}
|
|
118
|
+
state.set(id, "done");
|
|
119
|
+
};
|
|
120
|
+
for (const start of graph.starts)
|
|
121
|
+
if (start && !state.has(start))
|
|
122
|
+
visit(start);
|
|
123
|
+
// Nodes only reachable through a cycle still need a traversal root.
|
|
124
|
+
for (const node of graph.nodes)
|
|
125
|
+
if (!state.has(node.id))
|
|
126
|
+
visit(node.id);
|
|
127
|
+
}
|
|
128
|
+
const GATEWAY_TYPES = new Set([
|
|
129
|
+
"exclusiveGateway",
|
|
130
|
+
"parallelGateway",
|
|
131
|
+
"inclusiveGateway",
|
|
132
|
+
"eventBasedGateway",
|
|
133
|
+
"complexGateway",
|
|
134
|
+
]);
|
|
135
|
+
/** True when a node merges more than one path. */
|
|
136
|
+
function isJoin(graph, id) {
|
|
137
|
+
return (graph.incoming.get(id) ?? []).length > 1;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Nested joins of one gateway type may share a rank and connect vertically —
|
|
141
|
+
* closing an inner branch and the branch around it is one moment in the flow,
|
|
142
|
+
* not two. Joins of different types keep their forward step, because the change
|
|
143
|
+
* of gateway is itself part of the story.
|
|
144
|
+
*/
|
|
145
|
+
function sharesRank(graph, flow) {
|
|
146
|
+
const source = graph.byId.get(flow.sourceRef);
|
|
147
|
+
const target = graph.byId.get(flow.targetRef);
|
|
148
|
+
if (!source || !target)
|
|
149
|
+
return false;
|
|
150
|
+
if (source.type !== target.type || !GATEWAY_TYPES.has(source.type))
|
|
151
|
+
return false;
|
|
152
|
+
return isJoin(graph, source.id) && isJoin(graph, target.id);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Longest-path ranks over the acyclic remainder, then a bounded fixed point so
|
|
156
|
+
* a boundary handler never precedes the activity it is attached to.
|
|
157
|
+
*/
|
|
158
|
+
function assignRanks(graph) {
|
|
159
|
+
const { ranks } = graph;
|
|
160
|
+
for (const node of graph.nodes)
|
|
161
|
+
ranks.set(node.id, 0);
|
|
162
|
+
const forward = (id) => (graph.outgoing.get(id) ?? []).filter((f) => !graph.backEdges.has(f.id));
|
|
163
|
+
// Longest path: relax every edge until no rank grows. |V| passes suffice
|
|
164
|
+
// because the remaining graph is acyclic.
|
|
165
|
+
for (let pass = 0; pass < graph.nodes.length + 1; pass++) {
|
|
166
|
+
let changed = false;
|
|
167
|
+
for (const node of graph.nodes) {
|
|
168
|
+
const rank = ranks.get(node.id) ?? 0;
|
|
169
|
+
for (const flow of forward(node.id)) {
|
|
170
|
+
const step = sharesRank(graph, flow) ? 0 : 1;
|
|
171
|
+
const target = ranks.get(flow.targetRef) ?? 0;
|
|
172
|
+
if (target < rank + step) {
|
|
173
|
+
ranks.set(flow.targetRef, rank + step);
|
|
174
|
+
changed = true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (!changed)
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
// Normalize each component so its start sits at rank 0.
|
|
182
|
+
for (let i = 0; i < graph.components.length; i++) {
|
|
183
|
+
const members = graph.components[i];
|
|
184
|
+
if (!members)
|
|
185
|
+
continue;
|
|
186
|
+
let min = Number.POSITIVE_INFINITY;
|
|
187
|
+
for (const id of members)
|
|
188
|
+
min = Math.min(min, ranks.get(id) ?? 0);
|
|
189
|
+
if (min === 0 || !Number.isFinite(min))
|
|
190
|
+
continue;
|
|
191
|
+
for (const id of members)
|
|
192
|
+
ranks.set(id, (ranks.get(id) ?? 0) - min);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/** True when an end event is reachable from `id` without traversing a back edge. */
|
|
196
|
+
export function reachesEnd(graph, id) {
|
|
197
|
+
const seen = new Set();
|
|
198
|
+
const stack = [id];
|
|
199
|
+
while (stack.length > 0) {
|
|
200
|
+
const current = stack.pop();
|
|
201
|
+
if (current === undefined || seen.has(current))
|
|
202
|
+
continue;
|
|
203
|
+
seen.add(current);
|
|
204
|
+
const node = graph.byId.get(current);
|
|
205
|
+
if (node && END_TYPES.has(node.type))
|
|
206
|
+
return true;
|
|
207
|
+
const out = graph.outgoing.get(current) ?? [];
|
|
208
|
+
if (out.length === 0 && seen.size > 1)
|
|
209
|
+
return true; // a terminal node ends the path
|
|
210
|
+
for (const flow of out) {
|
|
211
|
+
if (!graph.backEdges.has(flow.id))
|
|
212
|
+
stack.push(flow.targetRef);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=graph.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BpmnFlowElement, BpmnLaneSet, BpmnProcess, BpmnSequenceFlow } from "../../bpmn/bpmn-model.js";
|
|
2
|
+
import type { LayoutResult } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Lay out a process the way the BPMN reads: ranks carry the flow left to right,
|
|
5
|
+
* semantic bands carry branch meaning up and down, and lane membership wins over
|
|
6
|
+
* both.
|
|
7
|
+
*/
|
|
8
|
+
export declare function semanticLayoutProcess(process: BpmnProcess, collapsed?: ReadonlySet<string>): LayoutResult;
|
|
9
|
+
export declare function semanticLayout(flowElements: BpmnFlowElement[], sequenceFlows: BpmnSequenceFlow[], laneSet?: BpmnLaneSet,
|
|
10
|
+
/** Sub-processes drawn collapsed: their contents go on a plane of their own. */
|
|
11
|
+
collapsed?: ReadonlySet<string>): LayoutResult;
|
|
12
|
+
export type { Placement } from "./place.js";
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { placeEdgeLabels } from "../grid/edge-labels.js";
|
|
2
|
+
import { LABEL_CHAR_WIDTH, LABEL_HEIGHT, LABEL_MIN_WIDTH, SUBPROCESS_PADDING } from "../types.js";
|
|
3
|
+
import { assignBands } from "./bands.js";
|
|
4
|
+
import { buildSemanticGraph } from "./graph.js";
|
|
5
|
+
import { place, sizeOf } from "./place.js";
|
|
6
|
+
import { routeFlows } from "./route.js";
|
|
7
|
+
/** Padding between an expanded sub-process border and its children. */
|
|
8
|
+
const SUB_PADDING = SUBPROCESS_PADDING;
|
|
9
|
+
/** Extra top padding inside a named expanded sub-process, for its title. */
|
|
10
|
+
const TITLE_BAND = 28;
|
|
11
|
+
/** Gap between an event or gateway and its external label. */
|
|
12
|
+
const LABEL_OFFSET = 4;
|
|
13
|
+
const CONTAINER_TYPES = new Set(["subProcess", "adHocSubProcess", "eventSubProcess", "transaction"]);
|
|
14
|
+
const EXTERNAL_LABEL_TYPES = new Set([
|
|
15
|
+
"startEvent",
|
|
16
|
+
"endEvent",
|
|
17
|
+
"intermediateThrowEvent",
|
|
18
|
+
"intermediateCatchEvent",
|
|
19
|
+
"boundaryEvent",
|
|
20
|
+
"exclusiveGateway",
|
|
21
|
+
"parallelGateway",
|
|
22
|
+
"inclusiveGateway",
|
|
23
|
+
"eventBasedGateway",
|
|
24
|
+
"complexGateway",
|
|
25
|
+
]);
|
|
26
|
+
/**
|
|
27
|
+
* Lay out a process the way the BPMN reads: ranks carry the flow left to right,
|
|
28
|
+
* semantic bands carry branch meaning up and down, and lane membership wins over
|
|
29
|
+
* both.
|
|
30
|
+
*/
|
|
31
|
+
export function semanticLayoutProcess(process, collapsed) {
|
|
32
|
+
return semanticLayout(process.flowElements, process.sequenceFlows, process.laneSet, collapsed);
|
|
33
|
+
}
|
|
34
|
+
export function semanticLayout(flowElements, sequenceFlows, laneSet,
|
|
35
|
+
/** Sub-processes drawn collapsed: their contents go on a plane of their own. */
|
|
36
|
+
collapsed = new Set()) {
|
|
37
|
+
if (flowElements.length === 0)
|
|
38
|
+
return { nodes: [], edges: [] };
|
|
39
|
+
const graph = buildSemanticGraph(flowElements, sequenceFlows);
|
|
40
|
+
const bandLayout = assignBands(graph);
|
|
41
|
+
// Children first: an expanded sub-process is sized by what it contains, while
|
|
42
|
+
// a collapsed one stays activity-sized and its contents move to their own plane.
|
|
43
|
+
const childResults = new Map();
|
|
44
|
+
const planes = [];
|
|
45
|
+
const sizes = new Map();
|
|
46
|
+
for (const el of graph.nodes) {
|
|
47
|
+
const child = childLayoutOf(el, collapsed);
|
|
48
|
+
if (!child) {
|
|
49
|
+
sizes.set(el.id, sizeOf(el));
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (collapsed.has(el.id)) {
|
|
53
|
+
planes.push({ elementId: el.id, result: child.result });
|
|
54
|
+
planes.push(...(child.result.planes ?? []));
|
|
55
|
+
sizes.set(el.id, sizeOf(el));
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
childResults.set(el.id, child.result);
|
|
59
|
+
planes.push(...(child.result.planes ?? []));
|
|
60
|
+
sizes.set(el.id, child.size);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const { bounds, lanes, gutterX } = place(graph, bandLayout, sizes, laneSet);
|
|
64
|
+
const nodes = [];
|
|
65
|
+
for (const el of flowElements) {
|
|
66
|
+
const b = bounds.get(el.id);
|
|
67
|
+
if (!b)
|
|
68
|
+
continue;
|
|
69
|
+
const node = {
|
|
70
|
+
id: el.id,
|
|
71
|
+
type: el.type,
|
|
72
|
+
bounds: b,
|
|
73
|
+
layer: graph.ranks.get(el.id) ?? 0,
|
|
74
|
+
position: bandLayout.bands.get(el.id) ?? 0,
|
|
75
|
+
gridRow: bandLayout.bands.get(el.id) ?? 0,
|
|
76
|
+
};
|
|
77
|
+
if (el.name) {
|
|
78
|
+
node.label = el.name;
|
|
79
|
+
const labelBounds = externalLabel(el.type, el.name, b);
|
|
80
|
+
if (labelBounds)
|
|
81
|
+
node.labelBounds = labelBounds;
|
|
82
|
+
}
|
|
83
|
+
if (childResults.has(el.id))
|
|
84
|
+
node.isExpanded = true;
|
|
85
|
+
else if (collapsed.has(el.id))
|
|
86
|
+
node.isExpanded = false;
|
|
87
|
+
nodes.push(node);
|
|
88
|
+
}
|
|
89
|
+
const edges = routeFlows(graph, sequenceFlows, bounds, bandLayout, gutterX);
|
|
90
|
+
// Drop the children in, translated into their parent's interior.
|
|
91
|
+
for (const [parentId, child] of childResults) {
|
|
92
|
+
const parent = bounds.get(parentId);
|
|
93
|
+
if (!parent)
|
|
94
|
+
continue;
|
|
95
|
+
const extent = extentOf(child);
|
|
96
|
+
if (!extent)
|
|
97
|
+
continue;
|
|
98
|
+
const named = graph.byId.get(parentId)?.name !== undefined;
|
|
99
|
+
const dx = parent.x + SUB_PADDING - extent.x;
|
|
100
|
+
const dy = parent.y + SUB_PADDING + (named ? TITLE_BAND : 0) - extent.y;
|
|
101
|
+
for (const node of child.nodes) {
|
|
102
|
+
node.bounds = shift(node.bounds, dx, dy);
|
|
103
|
+
if (node.labelBounds)
|
|
104
|
+
node.labelBounds = shift(node.labelBounds, dx, dy);
|
|
105
|
+
nodes.push(node);
|
|
106
|
+
}
|
|
107
|
+
for (const edge of child.edges) {
|
|
108
|
+
edge.waypoints = edge.waypoints.map((wp) => ({ x: wp.x + dx, y: wp.y + dy }));
|
|
109
|
+
if (edge.labelBounds)
|
|
110
|
+
edge.labelBounds = shift(edge.labelBounds, dx, dy);
|
|
111
|
+
edges.push(edge);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const result = { nodes, edges };
|
|
115
|
+
if (lanes.length > 0)
|
|
116
|
+
result.lanes = lanes;
|
|
117
|
+
if (planes.length > 0)
|
|
118
|
+
result.planes = planes;
|
|
119
|
+
placeEdgeLabels(edges, new Map(nodes.map((n) => [n.id, n])));
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
/** Lay out a container's children and report the size its border needs. */
|
|
123
|
+
function childLayoutOf(el, collapsed) {
|
|
124
|
+
if (!CONTAINER_TYPES.has(el.type))
|
|
125
|
+
return null;
|
|
126
|
+
const container = el;
|
|
127
|
+
if (!container.flowElements || container.flowElements.length === 0)
|
|
128
|
+
return null;
|
|
129
|
+
const result = semanticLayout(container.flowElements, container.sequenceFlows ?? [], container.laneSet, collapsed);
|
|
130
|
+
const extent = extentOf(result);
|
|
131
|
+
if (!extent)
|
|
132
|
+
return null;
|
|
133
|
+
const titleBand = el.name !== undefined ? TITLE_BAND : 0;
|
|
134
|
+
return {
|
|
135
|
+
result,
|
|
136
|
+
size: {
|
|
137
|
+
width: extent.width + 2 * SUB_PADDING,
|
|
138
|
+
height: extent.height + 2 * SUB_PADDING + titleBand,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function extentOf(result) {
|
|
143
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
144
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
145
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
146
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
147
|
+
const consider = (b) => {
|
|
148
|
+
minX = Math.min(minX, b.x);
|
|
149
|
+
minY = Math.min(minY, b.y);
|
|
150
|
+
maxX = Math.max(maxX, b.x + b.width);
|
|
151
|
+
maxY = Math.max(maxY, b.y + b.height);
|
|
152
|
+
};
|
|
153
|
+
for (const node of result.nodes) {
|
|
154
|
+
consider(node.bounds);
|
|
155
|
+
if (node.labelBounds)
|
|
156
|
+
consider(node.labelBounds);
|
|
157
|
+
}
|
|
158
|
+
for (const edge of result.edges) {
|
|
159
|
+
for (const wp of edge.waypoints)
|
|
160
|
+
consider({ x: wp.x, y: wp.y, width: 0, height: 0 });
|
|
161
|
+
}
|
|
162
|
+
if (!Number.isFinite(minX))
|
|
163
|
+
return null;
|
|
164
|
+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
165
|
+
}
|
|
166
|
+
function shift(b, dx, dy) {
|
|
167
|
+
return { x: b.x + dx, y: b.y + dy, width: b.width, height: b.height };
|
|
168
|
+
}
|
|
169
|
+
/** Events and gateways carry their name outside the shape; activities do not. */
|
|
170
|
+
function externalLabel(type, name, bounds) {
|
|
171
|
+
if (!EXTERNAL_LABEL_TYPES.has(type))
|
|
172
|
+
return undefined;
|
|
173
|
+
const width = Math.max(name.length * LABEL_CHAR_WIDTH, LABEL_MIN_WIDTH);
|
|
174
|
+
return {
|
|
175
|
+
x: bounds.x + bounds.width / 2 - width / 2,
|
|
176
|
+
y: bounds.y + bounds.height + LABEL_OFFSET,
|
|
177
|
+
width,
|
|
178
|
+
height: LABEL_HEIGHT,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { BpmnFlowElement, BpmnLaneSet } from "../../bpmn/bpmn-model.js";
|
|
2
|
+
import type { Bounds } from "../types.js";
|
|
3
|
+
import type { BandLayout } from "./bands.js";
|
|
4
|
+
import type { SemanticGraph } from "./graph.js";
|
|
5
|
+
/** Geometry constants. Layout may add space beyond these; it never takes any back. */
|
|
6
|
+
export declare const H_GAP = 100;
|
|
7
|
+
export declare const V_GAP = 80;
|
|
8
|
+
export declare const COMPONENT_GAP = 80;
|
|
9
|
+
export declare const LANE_PADDING = 40;
|
|
10
|
+
export declare const BOUNDARY_SIZE = 36;
|
|
11
|
+
export interface Placement {
|
|
12
|
+
/** Absolute bounds per flow node, boundary events included. */
|
|
13
|
+
bounds: Map<string, Bounds>;
|
|
14
|
+
/** Lane bands in top-to-bottom order, empty when the scope has no lanes. */
|
|
15
|
+
lanes: Array<{
|
|
16
|
+
id: string;
|
|
17
|
+
bounds: Bounds;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* Mid-x of the empty gutter in front of each rank. Vertical runs belong
|
|
21
|
+
* here: placement never puts a shape in a gutter, so these lines are clear.
|
|
22
|
+
*/
|
|
23
|
+
gutterX: Map<number, number>;
|
|
24
|
+
}
|
|
25
|
+
export declare function sizeOf(el: BpmnFlowElement, childExtent?: Bounds): {
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Turn ranks and bands into coordinates.
|
|
31
|
+
*
|
|
32
|
+
* Ranks become columns sized by their widest node, bands become rows sized by
|
|
33
|
+
* their tallest. Nodes are centred in both, so any two nodes sharing a band
|
|
34
|
+
* share a centre line and the spine routes as one straight segment.
|
|
35
|
+
*/
|
|
36
|
+
export declare function place(graph: SemanticGraph, bandLayout: BandLayout, sizes: Map<string, {
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
}>, laneSet: BpmnLaneSet | undefined): Placement;
|
|
40
|
+
//# sourceMappingURL=place.d.ts.map
|