@bpmnkit/core 0.0.27 → 0.1.1
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 +350 -31
- package/dist/bpmn/bpmn-model.d.ts +35 -2
- package/dist/bpmn/bpmn-parser.js +39 -1
- package/dist/bpmn/bpmn-serializer.js +27 -0
- package/dist/bpmn/compact.js +11 -1
- package/dist/bpmn/di-check.d.ts +14 -0
- package/dist/bpmn/di-check.js +51 -0
- package/dist/bpmn/di-planes.d.ts +13 -0
- package/dist/bpmn/di-planes.js +20 -0
- package/dist/bpmn/optimize/tasks.js +1 -0
- package/dist/bpmn/svg.js +36 -4
- package/dist/bpmn/type-guards.d.ts +7 -1
- package/dist/bpmn/type-guards.js +13 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +3 -1
- 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
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { ELEMENT_SIZES, GRID_CELL_HEIGHT, GRID_CELL_WIDTH } from "../types.js";
|
|
2
|
+
import { placeEdgeLabels } from "./edge-labels.js";
|
|
3
|
+
import { buildFlowGraph } from "./flow-graph.js";
|
|
4
|
+
import { collapseCollinear, connectElements, ensureExitBottom } from "./grid-router.js";
|
|
5
|
+
import { createGridLayout } from "./walker.js";
|
|
6
|
+
const DEFAULT_SIZE = { width: 100, height: 80 };
|
|
7
|
+
const SUB_TYPES = new Set(["subProcess", "adHocSubProcess", "eventSubProcess", "transaction"]);
|
|
8
|
+
const CHILD_SHIFT_X = 50; // upstream: CELL_W/2 − baseW/4
|
|
9
|
+
const CHILD_SHIFT_Y = 40; // upstream: CELL_H − baseH − baseH/4
|
|
10
|
+
export function gridLayoutFlowNodes(flowNodes, sequenceFlows) {
|
|
11
|
+
if (flowNodes.length === 0)
|
|
12
|
+
return { nodes: [], edges: [] };
|
|
13
|
+
const root = buildLevel(flowNodes, sequenceFlows, false);
|
|
14
|
+
const out = { nodes: [], edges: [] };
|
|
15
|
+
emitLevel(root, { x: 0, y: 0 }, out);
|
|
16
|
+
const nodeMap = new Map(out.nodes.map((n) => [n.id, n]));
|
|
17
|
+
placeEdgeLabels(out.edges, nodeMap);
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
function buildLevel(flowNodes, sequenceFlows, compact) {
|
|
21
|
+
const graph = buildFlowGraph(flowNodes, sequenceFlows);
|
|
22
|
+
const children = new Map();
|
|
23
|
+
for (const el of graph.elements) {
|
|
24
|
+
if (!SUB_TYPES.has(el.type))
|
|
25
|
+
continue;
|
|
26
|
+
const sub = el;
|
|
27
|
+
if (!sub.flowElements || sub.flowElements.length === 0)
|
|
28
|
+
continue;
|
|
29
|
+
const childFlows = sub.sequenceFlows ?? [];
|
|
30
|
+
const childCompact = el.type === "adHocSubProcess" && childFlows.length === 0;
|
|
31
|
+
children.set(el.id, buildLevel(sub.flowElements, childFlows, childCompact));
|
|
32
|
+
}
|
|
33
|
+
const grid = createGridLayout(graph, { compact });
|
|
34
|
+
expandForChildren(grid, children);
|
|
35
|
+
return { graph, grid, children };
|
|
36
|
+
}
|
|
37
|
+
/** Insert blank cols/rows after every cell holding an expanded subprocess (§A.2.3). */
|
|
38
|
+
function expandForChildren(grid, children) {
|
|
39
|
+
if (children.size === 0)
|
|
40
|
+
return;
|
|
41
|
+
// columns, right-to-left
|
|
42
|
+
for (let c = grid.colCount() - 1; c >= 0; c--) {
|
|
43
|
+
let maxCols = 0;
|
|
44
|
+
for (const { element, col } of grid.elementsByPosition()) {
|
|
45
|
+
if (col !== c)
|
|
46
|
+
continue;
|
|
47
|
+
const child = children.get(element.id);
|
|
48
|
+
if (child)
|
|
49
|
+
maxCols = Math.max(maxCols, child.grid.colCount());
|
|
50
|
+
}
|
|
51
|
+
if (maxCols > 0)
|
|
52
|
+
grid.createCol(c, Math.max(maxCols, 2));
|
|
53
|
+
}
|
|
54
|
+
// rows, bottom-to-top
|
|
55
|
+
for (let r = grid.rowCount() - 1; r >= 0; r--) {
|
|
56
|
+
let maxRows = 0;
|
|
57
|
+
for (const { element, row } of grid.elementsByPosition()) {
|
|
58
|
+
if (row !== r)
|
|
59
|
+
continue;
|
|
60
|
+
const child = children.get(element.id);
|
|
61
|
+
if (child)
|
|
62
|
+
maxRows = Math.max(maxRows, child.grid.rowCount());
|
|
63
|
+
}
|
|
64
|
+
for (let i = 0; i < maxRows; i++)
|
|
65
|
+
grid.createRow(r);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function sizeOf(el) {
|
|
69
|
+
return ELEMENT_SIZES[el.type] ?? DEFAULT_SIZE;
|
|
70
|
+
}
|
|
71
|
+
function emitLevel(level, shift, out) {
|
|
72
|
+
const routables = new Map();
|
|
73
|
+
const expandedRowsByRow = new Map();
|
|
74
|
+
const boundaryBounds = new Map();
|
|
75
|
+
// Pass 1: shapes (+ boundary events + recursion into children)
|
|
76
|
+
for (const { element, row, col } of level.grid.elementsByPosition()) {
|
|
77
|
+
const base = sizeOf(element);
|
|
78
|
+
const child = level.children.get(element.id);
|
|
79
|
+
const bounds = {
|
|
80
|
+
x: col * GRID_CELL_WIDTH + (GRID_CELL_WIDTH - base.width) / 2 + shift.x,
|
|
81
|
+
y: row * GRID_CELL_HEIGHT + (GRID_CELL_HEIGHT - base.height) / 2 + shift.y,
|
|
82
|
+
width: child ? child.grid.colCount() * GRID_CELL_WIDTH + base.width : base.width,
|
|
83
|
+
height: child ? child.grid.rowCount() * GRID_CELL_HEIGHT + base.height : base.height,
|
|
84
|
+
};
|
|
85
|
+
const layoutNode = {
|
|
86
|
+
id: element.id,
|
|
87
|
+
type: element.type,
|
|
88
|
+
bounds,
|
|
89
|
+
layer: col,
|
|
90
|
+
position: row,
|
|
91
|
+
gridRow: row,
|
|
92
|
+
};
|
|
93
|
+
if (element.name)
|
|
94
|
+
layoutNode.label = element.name;
|
|
95
|
+
const lb = computeLabelBounds(element, bounds);
|
|
96
|
+
if (lb)
|
|
97
|
+
layoutNode.labelBounds = lb;
|
|
98
|
+
if (child)
|
|
99
|
+
layoutNode.isExpanded = true;
|
|
100
|
+
out.nodes.push(layoutNode);
|
|
101
|
+
routables.set(element.id, {
|
|
102
|
+
id: element.id,
|
|
103
|
+
bounds,
|
|
104
|
+
row,
|
|
105
|
+
col,
|
|
106
|
+
childGrid: child ? { rows: child.grid.rowCount(), cols: child.grid.colCount() } : undefined,
|
|
107
|
+
});
|
|
108
|
+
if (child) {
|
|
109
|
+
expandedRowsByRow.set(row, Math.max(expandedRowsByRow.get(row) ?? 0, child.grid.rowCount()));
|
|
110
|
+
}
|
|
111
|
+
// boundary events ride on the host's bottom edge
|
|
112
|
+
const attachers = level.graph.attachers.get(element.id) ?? [];
|
|
113
|
+
const n = attachers.length;
|
|
114
|
+
for (let i = 0; i < n; i++) {
|
|
115
|
+
const be = attachers[i];
|
|
116
|
+
const beBounds = {
|
|
117
|
+
x: bounds.x + ((i + 1) * bounds.width) / (n + 1) - 18,
|
|
118
|
+
y: bounds.y + bounds.height - 18,
|
|
119
|
+
width: 36,
|
|
120
|
+
height: 36,
|
|
121
|
+
};
|
|
122
|
+
const beNode = {
|
|
123
|
+
id: be.id,
|
|
124
|
+
type: be.type,
|
|
125
|
+
bounds: beBounds,
|
|
126
|
+
layer: col,
|
|
127
|
+
position: row,
|
|
128
|
+
gridRow: row,
|
|
129
|
+
};
|
|
130
|
+
if (be.name) {
|
|
131
|
+
beNode.label = be.name;
|
|
132
|
+
const labelWidth = Math.min(Math.max(be.name.length * 7, 40), GRID_CELL_WIDTH);
|
|
133
|
+
beNode.labelBounds = {
|
|
134
|
+
x: beBounds.x + beBounds.width / 2 - labelWidth / 2,
|
|
135
|
+
y: beBounds.y + beBounds.height + 4,
|
|
136
|
+
width: labelWidth,
|
|
137
|
+
height: 14,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
out.nodes.push(beNode);
|
|
141
|
+
boundaryBounds.set(be.id, beBounds);
|
|
142
|
+
routables.set(be.id, {
|
|
143
|
+
id: be.id,
|
|
144
|
+
bounds: beBounds,
|
|
145
|
+
row,
|
|
146
|
+
col,
|
|
147
|
+
hostChildGrid: child
|
|
148
|
+
? { rows: child.grid.rowCount(), cols: child.grid.colCount() }
|
|
149
|
+
: undefined,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if (child) {
|
|
153
|
+
emitLevel(child, { x: bounds.x + CHILD_SHIFT_X, y: bounds.y + CHILD_SHIFT_Y }, out);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Pass 2: edges of THIS level
|
|
157
|
+
for (const flows of level.graph.outgoing.values()) {
|
|
158
|
+
for (const flow of flows) {
|
|
159
|
+
const source = routables.get(flow.sourceRef);
|
|
160
|
+
const target = routables.get(flow.targetRef);
|
|
161
|
+
if (!source || !target)
|
|
162
|
+
continue;
|
|
163
|
+
let waypoints = connectElements(source, target, level.grid, shift, expandedRowsByRow);
|
|
164
|
+
const beB = boundaryBounds.get(flow.sourceRef);
|
|
165
|
+
if (beB)
|
|
166
|
+
waypoints = ensureExitBottom(beB, waypoints);
|
|
167
|
+
const edge = {
|
|
168
|
+
id: flow.id,
|
|
169
|
+
sourceRef: flow.sourceRef,
|
|
170
|
+
targetRef: flow.targetRef,
|
|
171
|
+
waypoints: collapseCollinear(waypoints),
|
|
172
|
+
};
|
|
173
|
+
if (flow.name)
|
|
174
|
+
edge.label = flow.name;
|
|
175
|
+
out.edges.push(edge);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/** Copied from coordinates.ts:141 — events and gateways get a label below the shape. */
|
|
180
|
+
function computeLabelBounds(node, bounds) {
|
|
181
|
+
if (!node.name)
|
|
182
|
+
return undefined;
|
|
183
|
+
// Cap label width to one grid cell so labels don't overlap adjacent elements
|
|
184
|
+
const labelWidth = Math.min(Math.max(node.name.length * 7, 40), GRID_CELL_WIDTH);
|
|
185
|
+
const labelHeight = 14;
|
|
186
|
+
switch (node.type) {
|
|
187
|
+
case "startEvent":
|
|
188
|
+
case "endEvent":
|
|
189
|
+
case "intermediateThrowEvent":
|
|
190
|
+
case "intermediateCatchEvent":
|
|
191
|
+
// Labels centered below events
|
|
192
|
+
return {
|
|
193
|
+
x: bounds.x + bounds.width / 2 - labelWidth / 2,
|
|
194
|
+
y: bounds.y + bounds.height + 4,
|
|
195
|
+
width: labelWidth,
|
|
196
|
+
height: labelHeight,
|
|
197
|
+
};
|
|
198
|
+
case "exclusiveGateway":
|
|
199
|
+
case "parallelGateway":
|
|
200
|
+
case "inclusiveGateway":
|
|
201
|
+
case "eventBasedGateway":
|
|
202
|
+
// Labels centered below gateway diamond (standard BPMN convention)
|
|
203
|
+
return {
|
|
204
|
+
x: bounds.x + bounds.width / 2 - labelWidth / 2,
|
|
205
|
+
y: bounds.y + bounds.height + 4,
|
|
206
|
+
width: labelWidth,
|
|
207
|
+
height: labelHeight,
|
|
208
|
+
};
|
|
209
|
+
default:
|
|
210
|
+
// Tasks/activities: labels centered inside — no separate label bounds needed
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=grid-engine.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Bounds, Waypoint } from "../types.js";
|
|
2
|
+
import type { Grid } from "./grid.js";
|
|
3
|
+
/** Routing endpoint: absolute bounds plus grid position. */
|
|
4
|
+
export interface RoutableNode {
|
|
5
|
+
id: string;
|
|
6
|
+
bounds: Bounds;
|
|
7
|
+
row: number;
|
|
8
|
+
col: number;
|
|
9
|
+
/** Child grid dims when this node is an expanded subprocess. */
|
|
10
|
+
childGrid?: {
|
|
11
|
+
rows: number;
|
|
12
|
+
cols: number;
|
|
13
|
+
};
|
|
14
|
+
/** For boundary events: the host's childGrid (if the host is expanded). */
|
|
15
|
+
hostChildGrid?: {
|
|
16
|
+
rows: number;
|
|
17
|
+
cols: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Compute orthogonal waypoints between two grid-placed nodes.
|
|
22
|
+
* Port of bpmn-auto-layout's connectElements (lib/utils/layoutUtil.js:52),
|
|
23
|
+
* with the level shift applied consistently (upstream drops it for
|
|
24
|
+
* subprocess children).
|
|
25
|
+
*/
|
|
26
|
+
export declare function connectElements(source: RoutableNode, target: RoutableNode, grid: Grid<{
|
|
27
|
+
id: string;
|
|
28
|
+
}>, shift: {
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
}, expandedRowsByRow: Map<number, number>): Waypoint[];
|
|
32
|
+
/** Force an edge to leave through a boundary event's bottom docking point. */
|
|
33
|
+
export declare function ensureExitBottom(be: Bounds, waypoints: Waypoint[]): Waypoint[];
|
|
34
|
+
/** Remove intermediate waypoints that lie on a straight segment; round coordinates. */
|
|
35
|
+
export declare function collapseCollinear(waypoints: Waypoint[]): Waypoint[];
|
|
36
|
+
//# sourceMappingURL=grid-router.d.ts.map
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { GRID_CELL_HEIGHT, GRID_CELL_WIDTH } from "../types.js";
|
|
2
|
+
const H = GRID_CELL_HEIGHT; // 140
|
|
3
|
+
const W = GRID_CELL_WIDTH; // 150
|
|
4
|
+
const HALF_H = H / 2; // 70
|
|
5
|
+
const HALF_W = W / 2; // 75
|
|
6
|
+
const TASK_HALF_HEIGHT = 40;
|
|
7
|
+
const BOUNDARY_STEM = 20;
|
|
8
|
+
function mid(b) {
|
|
9
|
+
return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
|
|
10
|
+
}
|
|
11
|
+
function dock(point, rect, dir) {
|
|
12
|
+
switch (dir) {
|
|
13
|
+
case "t":
|
|
14
|
+
return { x: point.x, y: rect.y };
|
|
15
|
+
case "b":
|
|
16
|
+
return { x: point.x, y: rect.y + rect.height };
|
|
17
|
+
case "l":
|
|
18
|
+
return { x: rect.x, y: point.y };
|
|
19
|
+
case "r":
|
|
20
|
+
return { x: rect.x + rect.width, y: point.y };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function sourceGridOf(node) {
|
|
24
|
+
return node.childGrid ?? node.hostChildGrid;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Compute orthogonal waypoints between two grid-placed nodes.
|
|
28
|
+
* Port of bpmn-auto-layout's connectElements (lib/utils/layoutUtil.js:52),
|
|
29
|
+
* with the level shift applied consistently (upstream drops it for
|
|
30
|
+
* subprocess children).
|
|
31
|
+
*/
|
|
32
|
+
export function connectElements(source, target, grid, shift, expandedRowsByRow) {
|
|
33
|
+
const sMid = mid(source.bounds);
|
|
34
|
+
const tMid = mid(target.bounds);
|
|
35
|
+
const dX = target.col - source.col;
|
|
36
|
+
const dY = target.row - source.row;
|
|
37
|
+
const cellTop = (row) => row * H + shift.y;
|
|
38
|
+
const cellLeft = (col) => col * W + shift.x;
|
|
39
|
+
const srcGrid = sourceGridOf(source);
|
|
40
|
+
// Self-loop
|
|
41
|
+
if (dX === 0 && dY === 0 && source.id === target.id) {
|
|
42
|
+
const loopX = cellLeft(source.col) + (srcGrid ? (srcGrid.cols + 1) * W : W);
|
|
43
|
+
const topY = cellTop(source.row);
|
|
44
|
+
return [
|
|
45
|
+
dock(sMid, source.bounds, "r"),
|
|
46
|
+
{ x: loopX, y: sMid.y },
|
|
47
|
+
{ x: loopX, y: topY },
|
|
48
|
+
{ x: tMid.x, y: topY },
|
|
49
|
+
dock(tMid, target.bounds, "t"),
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
// Back-edge (loop closing leftwards)
|
|
53
|
+
if (dX < 0) {
|
|
54
|
+
if (sMid.y >= tMid.y) {
|
|
55
|
+
const extraRows = srcGrid ? srcGrid.rows + 1 : 1 + (expandedRowsByRow.get(source.row) ?? 0);
|
|
56
|
+
const downY = cellTop(source.row) + extraRows * H;
|
|
57
|
+
return [
|
|
58
|
+
dock(sMid, source.bounds, "b"),
|
|
59
|
+
{ x: sMid.x, y: downY },
|
|
60
|
+
{ x: tMid.x, y: downY },
|
|
61
|
+
dock(tMid, target.bounds, "b"),
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
const upY = sMid.y - HALF_H;
|
|
65
|
+
return [
|
|
66
|
+
dock(sMid, source.bounds, "t"),
|
|
67
|
+
{ x: sMid.x, y: upY },
|
|
68
|
+
{ x: tMid.x, y: upY },
|
|
69
|
+
dock(tMid, target.bounds, "t"),
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
// Same row, forward
|
|
73
|
+
if (dY === 0) {
|
|
74
|
+
if (isDirectPathBlocked(source, target, grid)) {
|
|
75
|
+
const extraRows = srcGrid ? srcGrid.rows + 1 : 1;
|
|
76
|
+
const underY = cellTop(source.row) + extraRows * H;
|
|
77
|
+
return [
|
|
78
|
+
dock(sMid, source.bounds, "b"),
|
|
79
|
+
{ x: sMid.x, y: underY },
|
|
80
|
+
{ x: tMid.x, y: underY },
|
|
81
|
+
dock(tMid, target.bounds, "b"),
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
const first = dock(sMid, source.bounds, "r");
|
|
85
|
+
const last = dock(tMid, target.bounds, "l");
|
|
86
|
+
// Expanded boxes dock at header height, not box middle
|
|
87
|
+
if (source.childGrid)
|
|
88
|
+
first.y = source.bounds.y + TASK_HALF_HEIGHT;
|
|
89
|
+
if (target.childGrid)
|
|
90
|
+
last.y = target.bounds.y + TASK_HALF_HEIGHT;
|
|
91
|
+
if (first.y !== last.y) {
|
|
92
|
+
// header-height correction created a step — resolve with an L
|
|
93
|
+
return collapseCollinear([first, { x: last.x, y: first.y }, last]);
|
|
94
|
+
}
|
|
95
|
+
return [first, last];
|
|
96
|
+
}
|
|
97
|
+
// Same column, vertical
|
|
98
|
+
if (dX === 0) {
|
|
99
|
+
if (isDirectPathBlocked(source, target, grid)) {
|
|
100
|
+
const yOff = -Math.sign(dY) * HALF_H;
|
|
101
|
+
return [
|
|
102
|
+
dock(sMid, source.bounds, "r"),
|
|
103
|
+
{ x: sMid.x + HALF_W, y: sMid.y },
|
|
104
|
+
{ x: sMid.x + HALF_W, y: tMid.y + yOff },
|
|
105
|
+
{ x: tMid.x, y: tMid.y + yOff },
|
|
106
|
+
dock(tMid, target.bounds, yOff > 0 ? "b" : "t"),
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
return [
|
|
110
|
+
dock(sMid, source.bounds, dY > 0 ? "b" : "t"),
|
|
111
|
+
dock(tMid, target.bounds, dY > 0 ? "t" : "b"),
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
// Diagonal forward: try the single-bend route
|
|
115
|
+
const direct = directManhattan(source, target, grid, dY);
|
|
116
|
+
if (direct)
|
|
117
|
+
return direct;
|
|
118
|
+
// Fallback: 6-point S-route
|
|
119
|
+
const yOff = -Math.sign(dY) * HALF_H;
|
|
120
|
+
return [
|
|
121
|
+
dock(sMid, source.bounds, "r"),
|
|
122
|
+
{ x: sMid.x + HALF_W, y: sMid.y },
|
|
123
|
+
{ x: sMid.x + HALF_W, y: tMid.y + yOff },
|
|
124
|
+
{ x: tMid.x - HALF_W, y: tMid.y + yOff },
|
|
125
|
+
{ x: tMid.x - HALF_W, y: tMid.y },
|
|
126
|
+
dock(tMid, target.bounds, "l"),
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
function directManhattan(source, target, grid, dY) {
|
|
130
|
+
const sMid = mid(source.bounds);
|
|
131
|
+
const tMid = mid(target.bounds);
|
|
132
|
+
if (dY > 0) {
|
|
133
|
+
// bend at (targetRow, sourceCol): down, then right
|
|
134
|
+
const count = grid.getElementsInRange({ row: source.row, col: source.col }, { row: target.row, col: source.col }).length +
|
|
135
|
+
grid.getElementsInRange({ row: target.row, col: source.col }, { row: target.row, col: target.col }).length;
|
|
136
|
+
if (count > 2)
|
|
137
|
+
return undefined;
|
|
138
|
+
return [
|
|
139
|
+
dock(sMid, source.bounds, "b"),
|
|
140
|
+
{ x: sMid.x, y: tMid.y },
|
|
141
|
+
dock(tMid, target.bounds, "l"),
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
// bend at (sourceRow, targetCol): right, then up
|
|
145
|
+
const count = grid.getElementsInRange({ row: source.row, col: source.col }, { row: source.row, col: target.col }).length +
|
|
146
|
+
grid.getElementsInRange({ row: source.row, col: target.col }, { row: target.row, col: target.col }).length;
|
|
147
|
+
if (count > 2)
|
|
148
|
+
return undefined;
|
|
149
|
+
return [dock(sMid, source.bounds, "r"), { x: tMid.x, y: sMid.y }, dock(tMid, target.bounds, "b")];
|
|
150
|
+
}
|
|
151
|
+
function isDirectPathBlocked(source, target, grid) {
|
|
152
|
+
// Each range is counted only when there is movement along that axis —
|
|
153
|
+
// otherwise a same-row edge would double-count its target and always block.
|
|
154
|
+
let total = 0;
|
|
155
|
+
if (target.col !== source.col) {
|
|
156
|
+
total += grid.getElementsInRange({ row: source.row, col: source.col }, { row: source.row, col: target.col }).length;
|
|
157
|
+
}
|
|
158
|
+
if (target.row !== source.row) {
|
|
159
|
+
total += grid.getElementsInRange({ row: source.row, col: target.col }, { row: target.row, col: target.col }).length;
|
|
160
|
+
}
|
|
161
|
+
return total > 2;
|
|
162
|
+
}
|
|
163
|
+
/** Force an edge to leave through a boundary event's bottom docking point. */
|
|
164
|
+
export function ensureExitBottom(be, waypoints) {
|
|
165
|
+
if (waypoints.length === 0)
|
|
166
|
+
return waypoints;
|
|
167
|
+
const exit = { x: be.x + be.width / 2, y: be.y + be.height };
|
|
168
|
+
const stemY = exit.y + BOUNDARY_STEM;
|
|
169
|
+
const rest = waypoints.slice(1);
|
|
170
|
+
const rejoin = rest[0] ?? exit;
|
|
171
|
+
return collapseCollinear([exit, { x: exit.x, y: stemY }, { x: rejoin.x, y: stemY }, ...rest]);
|
|
172
|
+
}
|
|
173
|
+
/** Remove intermediate waypoints that lie on a straight segment; round coordinates. */
|
|
174
|
+
export function collapseCollinear(waypoints) {
|
|
175
|
+
const rounded = waypoints.map((w) => ({ x: Math.round(w.x), y: Math.round(w.y) }));
|
|
176
|
+
const out = [];
|
|
177
|
+
for (const p of rounded) {
|
|
178
|
+
const a = out[out.length - 2];
|
|
179
|
+
const b = out[out.length - 1];
|
|
180
|
+
if (b && b.x === p.x && b.y === p.y)
|
|
181
|
+
continue;
|
|
182
|
+
if (a && b && ((a.x === b.x && b.x === p.x) || (a.y === b.y && b.y === p.y))) {
|
|
183
|
+
out[out.length - 1] = p;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
out.push(p);
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=grid-router.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sparse row/column grid used by the grid layout engine.
|
|
3
|
+
* Port of bpmn-io bpmn-auto-layout's Grid (lib/Grid.js) with two fixes:
|
|
4
|
+
* an explicit optional position (upstream conflated [0,0] with "no
|
|
5
|
+
* position") and a working row-splice guard.
|
|
6
|
+
*/
|
|
7
|
+
export declare class Grid<T> {
|
|
8
|
+
private grid;
|
|
9
|
+
/** Without a position, start a new bottom row; with one, place exactly there. */
|
|
10
|
+
add(element: T, position?: [number, number]): void;
|
|
11
|
+
createRow(afterIndex?: number): void;
|
|
12
|
+
createCol(afterIndex: number, count: number): void;
|
|
13
|
+
addAfter(element: T, newElement: T): void;
|
|
14
|
+
addBelow(element: T, newElement: T): void;
|
|
15
|
+
find(element: T): [number, number];
|
|
16
|
+
get(row: number, col: number): T | undefined;
|
|
17
|
+
getElementsInRange(from: {
|
|
18
|
+
row: number;
|
|
19
|
+
col: number;
|
|
20
|
+
}, to: {
|
|
21
|
+
row: number;
|
|
22
|
+
col: number;
|
|
23
|
+
}): T[];
|
|
24
|
+
/**
|
|
25
|
+
* Move an element to the current last column of the grid (right-align
|
|
26
|
+
* before a fan-out). No-op when that cell is occupied — upstream would
|
|
27
|
+
* overwrite; we keep the no-overwrite invariant.
|
|
28
|
+
*/
|
|
29
|
+
adjustGridPosition(element: T): void;
|
|
30
|
+
adjustRowForMultipleIncoming(sources: T[], element: T): void;
|
|
31
|
+
adjustColumnForMultipleIncoming(sources: T[], element: T): void;
|
|
32
|
+
getAllElements(): T[];
|
|
33
|
+
getGridDimensions(): [number, number];
|
|
34
|
+
elementsByPosition(): Array<{
|
|
35
|
+
element: T;
|
|
36
|
+
row: number;
|
|
37
|
+
col: number;
|
|
38
|
+
}>;
|
|
39
|
+
getElementsTotal(): number;
|
|
40
|
+
rowCount(): number;
|
|
41
|
+
colCount(): number;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=grid.d.ts.map
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sparse row/column grid used by the grid layout engine.
|
|
3
|
+
* Port of bpmn-io bpmn-auto-layout's Grid (lib/Grid.js) with two fixes:
|
|
4
|
+
* an explicit optional position (upstream conflated [0,0] with "no
|
|
5
|
+
* position") and a working row-splice guard.
|
|
6
|
+
*/
|
|
7
|
+
export class Grid {
|
|
8
|
+
grid = [];
|
|
9
|
+
/** Without a position, start a new bottom row; with one, place exactly there. */
|
|
10
|
+
add(element, position) {
|
|
11
|
+
if (!position) {
|
|
12
|
+
this.grid.push([element]);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const [row, col] = position;
|
|
16
|
+
while (this.grid.length <= row)
|
|
17
|
+
this.grid.push([]);
|
|
18
|
+
const gridRow = this.grid[row];
|
|
19
|
+
if (gridRow[col] !== undefined) {
|
|
20
|
+
throw new Error(`Grid cell (${row},${col}) is already occupied`);
|
|
21
|
+
}
|
|
22
|
+
gridRow[col] = element;
|
|
23
|
+
}
|
|
24
|
+
createRow(afterIndex) {
|
|
25
|
+
if (afterIndex === undefined) {
|
|
26
|
+
this.grid.push([]);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
this.grid.splice(afterIndex + 1, 0, []);
|
|
30
|
+
}
|
|
31
|
+
createCol(afterIndex, count) {
|
|
32
|
+
for (const row of this.grid) {
|
|
33
|
+
if (row.length > afterIndex) {
|
|
34
|
+
row.splice(afterIndex + 1, 0, ...new Array(count).fill(undefined));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
addAfter(element, newElement) {
|
|
39
|
+
const [row, col] = this.find(element);
|
|
40
|
+
if (row < 0) {
|
|
41
|
+
this.add(newElement);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this.grid[row]?.splice(col + 1, 0, newElement);
|
|
45
|
+
}
|
|
46
|
+
addBelow(element, newElement) {
|
|
47
|
+
const [row, col] = this.find(element);
|
|
48
|
+
if (row < 0) {
|
|
49
|
+
this.add(newElement);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
while (this.grid.length <= row + 1)
|
|
53
|
+
this.grid.push([]);
|
|
54
|
+
const below = this.grid[row + 1];
|
|
55
|
+
if (below[col] !== undefined) {
|
|
56
|
+
this.grid.splice(row + 1, 0, []);
|
|
57
|
+
}
|
|
58
|
+
this.add(newElement, [row + 1, col]);
|
|
59
|
+
}
|
|
60
|
+
find(element) {
|
|
61
|
+
for (let r = 0; r < this.grid.length; r++) {
|
|
62
|
+
const row = this.grid[r];
|
|
63
|
+
if (!row)
|
|
64
|
+
continue;
|
|
65
|
+
for (let c = 0; c < row.length; c++) {
|
|
66
|
+
if (row[c] === element)
|
|
67
|
+
return [r, c];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return [-1, -1];
|
|
71
|
+
}
|
|
72
|
+
get(row, col) {
|
|
73
|
+
return this.grid[row]?.[col];
|
|
74
|
+
}
|
|
75
|
+
getElementsInRange(from, to) {
|
|
76
|
+
const r1 = Math.min(from.row, to.row);
|
|
77
|
+
const r2 = Math.max(from.row, to.row);
|
|
78
|
+
const c1 = Math.min(from.col, to.col);
|
|
79
|
+
const c2 = Math.max(from.col, to.col);
|
|
80
|
+
const out = [];
|
|
81
|
+
for (let r = r1; r <= r2; r++) {
|
|
82
|
+
for (let c = c1; c <= c2; c++) {
|
|
83
|
+
const el = this.get(r, c);
|
|
84
|
+
if (el !== undefined)
|
|
85
|
+
out.push(el);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Move an element to the current last column of the grid (right-align
|
|
92
|
+
* before a fan-out). No-op when that cell is occupied — upstream would
|
|
93
|
+
* overwrite; we keep the no-overwrite invariant.
|
|
94
|
+
*/
|
|
95
|
+
adjustGridPosition(element) {
|
|
96
|
+
const [row, col] = this.find(element);
|
|
97
|
+
if (row < 0)
|
|
98
|
+
return;
|
|
99
|
+
const maxCol = this.colCount() - 1;
|
|
100
|
+
if (col < maxCol - 1 && this.get(row, maxCol) === undefined) {
|
|
101
|
+
const gridRow = this.grid[row];
|
|
102
|
+
gridRow[col] = undefined;
|
|
103
|
+
while (gridRow.length <= maxCol)
|
|
104
|
+
gridRow.push(undefined);
|
|
105
|
+
gridRow[maxCol] = element;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
adjustRowForMultipleIncoming(sources, element) {
|
|
109
|
+
const [row, col] = this.find(element);
|
|
110
|
+
if (row < 0)
|
|
111
|
+
return;
|
|
112
|
+
const rows = sources.map((s) => this.find(s)[0]).filter((r) => r >= 0);
|
|
113
|
+
if (rows.length === 0)
|
|
114
|
+
return;
|
|
115
|
+
const lowestRow = Math.min(...rows);
|
|
116
|
+
if (lowestRow < row && this.get(lowestRow, col) === undefined) {
|
|
117
|
+
const gridRow = this.grid[row];
|
|
118
|
+
gridRow[col] = undefined;
|
|
119
|
+
this.add(element, [lowestRow, col]);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
adjustColumnForMultipleIncoming(sources, element) {
|
|
123
|
+
const [row, col] = this.find(element);
|
|
124
|
+
if (row < 0)
|
|
125
|
+
return;
|
|
126
|
+
const cols = sources.map((s) => this.find(s)[1]).filter((c) => c >= 0);
|
|
127
|
+
if (cols.length === 0)
|
|
128
|
+
return;
|
|
129
|
+
const maxCol = Math.max(...cols);
|
|
130
|
+
if (maxCol + 1 > col) {
|
|
131
|
+
const gridRow = this.grid[row];
|
|
132
|
+
gridRow[col] = undefined;
|
|
133
|
+
// splice-free targeted set; grow the row as needed
|
|
134
|
+
while (gridRow.length <= maxCol + 1)
|
|
135
|
+
gridRow.push(undefined);
|
|
136
|
+
if (gridRow[maxCol + 1] === undefined) {
|
|
137
|
+
gridRow[maxCol + 1] = element;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
this.addBelow(gridRow[maxCol + 1], element);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
getAllElements() {
|
|
145
|
+
return this.elementsByPosition().map((e) => e.element);
|
|
146
|
+
}
|
|
147
|
+
getGridDimensions() {
|
|
148
|
+
return [this.rowCount(), this.colCount()];
|
|
149
|
+
}
|
|
150
|
+
elementsByPosition() {
|
|
151
|
+
const out = [];
|
|
152
|
+
for (let r = 0; r < this.grid.length; r++) {
|
|
153
|
+
const row = this.grid[r];
|
|
154
|
+
if (!row)
|
|
155
|
+
continue;
|
|
156
|
+
for (let c = 0; c < row.length; c++) {
|
|
157
|
+
const el = row[c];
|
|
158
|
+
if (el !== undefined)
|
|
159
|
+
out.push({ element: el, row: r, col: c });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
getElementsTotal() {
|
|
165
|
+
return new Set(this.getAllElements()).size;
|
|
166
|
+
}
|
|
167
|
+
rowCount() {
|
|
168
|
+
return this.grid.length;
|
|
169
|
+
}
|
|
170
|
+
colCount() {
|
|
171
|
+
return this.grid.reduce((max, row) => Math.max(max, row.length), 0);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=grid.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BpmnFlowElement } from "../../bpmn/bpmn-model.js";
|
|
2
|
+
import type { FlowGraph } from "./flow-graph.js";
|
|
3
|
+
import { Grid } from "./grid.js";
|
|
4
|
+
/**
|
|
5
|
+
* Place every element of the graph into a Grid via the bpmn-io DFS walk.
|
|
6
|
+
* `compact` bypasses the walk and packs row-major (adHoc tool palettes).
|
|
7
|
+
*/
|
|
8
|
+
export declare function createGridLayout(graph: FlowGraph, opts?: {
|
|
9
|
+
compact?: boolean;
|
|
10
|
+
}): Grid<BpmnFlowElement>;
|
|
11
|
+
//# sourceMappingURL=walker.d.ts.map
|