@bpmnkit/core 0.0.22 → 0.0.24

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.
@@ -336,6 +336,11 @@ export interface BpmnMessage {
336
336
  name?: string;
337
337
  unknownAttributes: Record<string, string>;
338
338
  }
339
+ /** A root-level BPMN signal definition referenced by signal catch/throw events. */
340
+ export interface BpmnSignal {
341
+ id: string;
342
+ name?: string;
343
+ }
339
344
  /** Optional label positioning information for a BPMNDi shape or edge. */
340
345
  export interface BpmnDiLabel {
341
346
  bounds?: BpmnBounds;
@@ -397,6 +402,7 @@ export interface BpmnDefinitions {
397
402
  errors: BpmnError[];
398
403
  escalations: BpmnEscalation[];
399
404
  messages: BpmnMessage[];
405
+ signals: BpmnSignal[];
400
406
  collaborations: BpmnCollaboration[];
401
407
  processes: BpmnProcess[];
402
408
  diagrams: BpmnDiagram[];
@@ -505,6 +505,12 @@ function parseMessage(element) {
505
505
  unknownAttributes: unknownAttrs(element),
506
506
  };
507
507
  }
508
+ function parseSignal(element) {
509
+ return {
510
+ id: requiredAttr(element, "id"),
511
+ name: attr(element, "name"),
512
+ };
513
+ }
508
514
  // ---------------------------------------------------------------------------
509
515
  // Diagram interchange
510
516
  // ---------------------------------------------------------------------------
@@ -607,6 +613,7 @@ export function parseBpmn(xml) {
607
613
  const errors = [];
608
614
  const escalations = [];
609
615
  const messages = [];
616
+ const signals = [];
610
617
  const collaborations = [];
611
618
  const processes = [];
612
619
  const diagrams = [];
@@ -618,6 +625,8 @@ export function parseBpmn(xml) {
618
625
  escalations.push(parseEscalation(child));
619
626
  else if (ln === "message")
620
627
  messages.push(parseMessage(child));
628
+ else if (ln === "signal")
629
+ signals.push(parseSignal(child));
621
630
  else if (ln === "collaboration")
622
631
  collaborations.push(parseCollaboration(child));
623
632
  else if (ln === "process")
@@ -635,6 +644,7 @@ export function parseBpmn(xml) {
635
644
  errors,
636
645
  escalations,
637
646
  messages,
647
+ signals,
638
648
  collaborations,
639
649
  processes,
640
650
  diagrams,
@@ -387,6 +387,12 @@ function serializeMessage(m, bp) {
387
387
  attrs.name = m.name;
388
388
  return el(`${bp}:message`, attrs, []);
389
389
  }
390
+ function serializeSignal(s, bp) {
391
+ const attrs = { id: s.id };
392
+ if (s.name !== undefined)
393
+ attrs.name = s.name;
394
+ return el(`${bp}:signal`, attrs, []);
395
+ }
390
396
  // ---------------------------------------------------------------------------
391
397
  // Diagram interchange
392
398
  // ---------------------------------------------------------------------------
@@ -490,7 +496,7 @@ export function serializeBpmn(definitions) {
490
496
  attrs[key] = value;
491
497
  }
492
498
  const children = [];
493
- // Root elements: errors, escalations, messages first
499
+ // Root elements: errors, escalations, messages, signals first
494
500
  for (const e of definitions.escalations) {
495
501
  children.push(serializeEscalation(e, bp));
496
502
  }
@@ -500,6 +506,9 @@ export function serializeBpmn(definitions) {
500
506
  for (const m of definitions.messages) {
501
507
  children.push(serializeMessage(m, bp));
502
508
  }
509
+ for (const s of definitions.signals ?? []) {
510
+ children.push(serializeSignal(s, bp));
511
+ }
503
512
  // Collaborations
504
513
  for (const c of definitions.collaborations) {
505
514
  children.push(serializeCollaboration(c, ns));
@@ -380,6 +380,7 @@ export function expand(compact) {
380
380
  errors: [],
381
381
  escalations: [],
382
382
  messages: [],
383
+ signals: [],
383
384
  collaborations: [],
384
385
  processes,
385
386
  diagrams,
@@ -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
  *
@@ -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
  *
@@ -204,6 +204,7 @@ function buildExtractedDefs(representative, newProcessId) {
204
204
  errors: [],
205
205
  escalations: [],
206
206
  messages: [],
207
+ signals: [],
207
208
  collaborations: [],
208
209
  processes: [extractedProcess],
209
210
  diagrams: [diagram],
@@ -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,8 +4,9 @@ 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 type { ProcessBuilder, BranchBuilder, SubProcessContentBuilder, ServiceTaskOptions, ScriptTaskOptions, UserTaskOptions, CallActivityOptions, BusinessRuleTaskOptions, ElementOptions, GatewayOptions, MultiInstanceOptions, SubProcessOptions, StartEventOptions, IntermediateCatchEventOptions, IntermediateThrowEventOptions, BoundaryEventOptions, AdHocSubProcessOptions, } from "./bpmn/bpmn-builder.js";
8
- 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, 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";
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";
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";
11
12
  export { zeebeExtensionsToXmlElements } from "./bpmn/zeebe-extensions.js";
@@ -32,7 +33,7 @@ export { layoutProcess, layoutFlowNodes } from "./layout/index.js";
32
33
  export { benchmarkLayout, compareLayouts, formatBenchmarkResult, generateAutoLayout, parseReferenceLayout, } from "./layout/index.js";
33
34
  export type { BenchmarkResult, BoundingBox, ElementComparison, ElementPosition, FlowOrderViolation, } from "./layout/index.js";
34
35
  export type { Bounds, LayoutEdge, LayoutNode, LayoutResult, Waypoint } from "./layout/index.js";
35
- export { ELEMENT_SIZES, GRID_CELL_HEIGHT } from "./layout/index.js";
36
+ export { ELEMENT_SIZES, GRID_CELL_HEIGHT, GRID_CELL_WIDTH } from "./layout/index.js";
36
37
  export { compactify, expand } from "./bpmn/compact.js";
37
38
  export { applyOperations } from "./bpmn/operations.js";
38
39
  export type { BpmnOperation } from "./bpmn/operations.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";
@@ -15,7 +16,7 @@ export { renderStoryHtml } from "./bpmn/story.js";
15
16
  export { analyzeVariableFlow, extractFeelIdentifiers } from "./bpmn/optimize/variable-flow.js";
16
17
  export { layoutProcess, layoutFlowNodes } from "./layout/index.js";
17
18
  export { benchmarkLayout, compareLayouts, formatBenchmarkResult, generateAutoLayout, parseReferenceLayout, } from "./layout/index.js";
18
- export { ELEMENT_SIZES, GRID_CELL_HEIGHT } from "./layout/index.js";
19
+ export { ELEMENT_SIZES, GRID_CELL_HEIGHT, GRID_CELL_WIDTH } from "./layout/index.js";
19
20
  export { compactify, expand } from "./bpmn/compact.js";
20
21
  export { applyOperations } from "./bpmn/operations.js";
21
22
  export { buildValidationDmn, findValidationStructure, getValidationInputNames, insertValidationStructure, removeValidationStructure, validationDecisionId, } from "./bpmn/input-validation.js";
@@ -0,0 +1,15 @@
1
+ import type { Bounds, Waypoint } from "./types.js";
2
+ /**
3
+ * Route a single edge using A* on a 10px grid.
4
+ * source/target are center points of source/target nodes.
5
+ * obstacles are node bounding boxes to avoid (inflated by 6px margin).
6
+ * Returns simplified orthogonal waypoints.
7
+ */
8
+ export declare function routeEdgeAstar(source: {
9
+ x: number;
10
+ y: number;
11
+ }, target: {
12
+ x: number;
13
+ y: number;
14
+ }, obstacles: Bounds[], canvasWidth: number, canvasHeight: number): Waypoint[];
15
+ //# sourceMappingURL=astar.d.ts.map
@@ -0,0 +1,191 @@
1
+ const GRID_RES = 10;
2
+ const OBSTACLE_MARGIN = 6;
3
+ const TURN_PENALTY = 5;
4
+ const CANVAS_EXTEND = 80;
5
+ const DX = [1, 0, -1, 0];
6
+ const DY = [0, 1, 0, -1];
7
+ /** Minimal binary min-heap keyed by f-score. */
8
+ class MinHeap {
9
+ data = [];
10
+ push(f, key) {
11
+ this.data.push({ f, key });
12
+ this.bubbleUp(this.data.length - 1);
13
+ }
14
+ pop() {
15
+ const top = this.data[0];
16
+ const last = this.data.pop();
17
+ if (this.data.length > 0 && last !== undefined) {
18
+ this.data[0] = last;
19
+ this.sinkDown(0);
20
+ }
21
+ return top;
22
+ }
23
+ get size() {
24
+ return this.data.length;
25
+ }
26
+ bubbleUp(startIdx) {
27
+ let i = startIdx;
28
+ while (i > 0) {
29
+ const parent = (i - 1) >> 1;
30
+ const d = this.data[i];
31
+ const p = this.data[parent];
32
+ if (!d || !p || p.f <= d.f)
33
+ break;
34
+ this.data[i] = p;
35
+ this.data[parent] = d;
36
+ i = parent;
37
+ }
38
+ }
39
+ sinkDown(startIdx) {
40
+ let i = startIdx;
41
+ for (;;) {
42
+ const left = 2 * i + 1;
43
+ const right = 2 * i + 2;
44
+ let smallest = i;
45
+ const d = this.data[smallest];
46
+ const l = this.data[left];
47
+ const r = this.data[right];
48
+ if (l && l.f < (d?.f ?? Number.POSITIVE_INFINITY))
49
+ smallest = left;
50
+ const s = this.data[smallest];
51
+ if (r && r.f < (s?.f ?? Number.POSITIVE_INFINITY))
52
+ smallest = right;
53
+ if (smallest === i)
54
+ break;
55
+ const tmp = this.data[i];
56
+ const sm = this.data[smallest];
57
+ if (!tmp || !sm)
58
+ break;
59
+ this.data[i] = sm;
60
+ this.data[smallest] = tmp;
61
+ i = smallest;
62
+ }
63
+ }
64
+ }
65
+ /**
66
+ * Route a single edge using A* on a 10px grid.
67
+ * source/target are center points of source/target nodes.
68
+ * obstacles are node bounding boxes to avoid (inflated by 6px margin).
69
+ * Returns simplified orthogonal waypoints.
70
+ */
71
+ export function routeEdgeAstar(source, target, obstacles, canvasWidth, canvasHeight) {
72
+ // Extend canvas to allow routing around edges
73
+ const minX = Math.max(0, Math.min(source.x, target.x) - CANVAS_EXTEND);
74
+ const minY = Math.max(0, Math.min(source.y, target.y) - CANVAS_EXTEND);
75
+ const maxX = Math.max(source.x, target.x) + CANVAS_EXTEND + canvasWidth;
76
+ const maxY = Math.max(source.y, target.y) + CANVAS_EXTEND + canvasHeight;
77
+ const cols = Math.ceil((maxX - minX) / GRID_RES) + 1;
78
+ const rows = Math.ceil((maxY - minY) / GRID_RES) + 1;
79
+ // Snap source and target to grid
80
+ const sx = Math.round((source.x - minX) / GRID_RES);
81
+ const sy = Math.round((source.y - minY) / GRID_RES);
82
+ const tx = Math.round((target.x - minX) / GRID_RES);
83
+ const ty = Math.round((target.y - minY) / GRID_RES);
84
+ // If they're at the same grid cell, return straight line
85
+ if (sx === tx && sy === ty) {
86
+ return [source, target];
87
+ }
88
+ // Build blocked grid
89
+ const blocked = new Uint8Array(cols * rows);
90
+ for (const ob of obstacles) {
91
+ const ox1 = Math.floor((ob.x - OBSTACLE_MARGIN - minX) / GRID_RES);
92
+ const oy1 = Math.floor((ob.y - OBSTACLE_MARGIN - minY) / GRID_RES);
93
+ const ox2 = Math.ceil((ob.x + ob.width + OBSTACLE_MARGIN - minX) / GRID_RES);
94
+ const oy2 = Math.ceil((ob.y + ob.height + OBSTACLE_MARGIN - minY) / GRID_RES);
95
+ for (let gy = Math.max(0, oy1); gy <= Math.min(rows - 1, oy2); gy++) {
96
+ for (let gx = Math.max(0, ox1); gx <= Math.min(cols - 1, ox2); gx++) {
97
+ blocked[gy * cols + gx] = 1;
98
+ }
99
+ }
100
+ }
101
+ // Unblock source and target cells (they're inside nodes)
102
+ blocked[sy * cols + sx] = 0;
103
+ blocked[ty * cols + tx] = 0;
104
+ // A* with direction-aware state: state = cell * 4 + dir
105
+ const INF = Number.MAX_SAFE_INTEGER;
106
+ // g-scores per (cell, dir)
107
+ const g = new Float32Array(cols * rows * 4).fill(INF);
108
+ const parent = new Int32Array(cols * rows * 4).fill(-1);
109
+ const parentDir = new Int8Array(cols * rows * 4).fill(-1);
110
+ const heap = new MinHeap();
111
+ // Initialize: try all 4 directions from source
112
+ for (let d = 0; d < 4; d++) {
113
+ const key = (sy * cols + sx) * 4 + d;
114
+ g[key] = 0;
115
+ const h = Math.abs(tx - sx) + Math.abs(ty - sy);
116
+ heap.push(h, key);
117
+ }
118
+ let found = false;
119
+ while (heap.size > 0) {
120
+ const item = heap.pop();
121
+ if (!item)
122
+ break;
123
+ const { key } = item;
124
+ const dir = key % 4;
125
+ const cell = (key - dir) / 4;
126
+ const cx = cell % cols;
127
+ const cy = (cell - cx) / cols;
128
+ if (cx === tx && cy === ty) {
129
+ found = true;
130
+ // Reconstruct path
131
+ const path = [];
132
+ let k = key;
133
+ while (k !== -1) {
134
+ const kDir = k % 4;
135
+ const kCell = (k - kDir) / 4;
136
+ const kx = kCell % cols;
137
+ const ky = (kCell - kx) / cols;
138
+ path.push({ x: kx * GRID_RES + minX, y: ky * GRID_RES + minY });
139
+ k = parent[k] ?? -1;
140
+ }
141
+ path.reverse();
142
+ // Simplify collinear points
143
+ return simplifyPath(path);
144
+ }
145
+ const gCur = g[key] ?? INF;
146
+ for (let nd = 0; nd < 4; nd++) {
147
+ const nx = cx + (DX[nd] ?? 0);
148
+ const ny = cy + (DY[nd] ?? 0);
149
+ if (nx < 0 || nx >= cols || ny < 0 || ny >= rows)
150
+ continue;
151
+ const ncell = ny * cols + nx;
152
+ if (blocked[ncell])
153
+ continue;
154
+ const nkey = ncell * 4 + nd;
155
+ const turnCost = nd !== dir ? TURN_PENALTY : 0;
156
+ const ng = gCur + 1 + turnCost;
157
+ const prevG = g[nkey] ?? INF;
158
+ if (ng < prevG) {
159
+ g[nkey] = ng;
160
+ parent[nkey] = key;
161
+ parentDir[nkey] = dir;
162
+ const h = Math.abs(tx - nx) + Math.abs(ty - ny);
163
+ heap.push(ng + h, nkey);
164
+ }
165
+ }
166
+ }
167
+ if (!found) {
168
+ // Fall back to straight line
169
+ return [source, target];
170
+ }
171
+ return [source, target];
172
+ }
173
+ /** Remove collinear intermediate waypoints. */
174
+ function simplifyPath(path) {
175
+ if (path.length <= 2)
176
+ return path;
177
+ const result = [path[0]];
178
+ for (let i = 1; i < path.length - 1; i++) {
179
+ const prev = path[i - 1];
180
+ const curr = path[i];
181
+ const next = path[i + 1];
182
+ const isCollinear = (Math.abs(prev.x - curr.x) < 0.5 && Math.abs(curr.x - next.x) < 0.5) ||
183
+ (Math.abs(prev.y - curr.y) < 0.5 && Math.abs(curr.y - next.y) < 0.5);
184
+ if (!isCollinear) {
185
+ result.push(curr);
186
+ }
187
+ }
188
+ result.push(path[path.length - 1]);
189
+ return result;
190
+ }
191
+ //# sourceMappingURL=astar.js.map
@@ -0,0 +1,37 @@
1
+ import type { BpmnElementType, BpmnFlowElement } from "../bpmn/bpmn-model.js";
2
+ import type { DirectedGraph } from "./graph.js";
3
+ export type FlowBlock = NodeBlock | GatewayBlock | SequenceBlock;
4
+ export interface NodeBlock {
5
+ kind: "node";
6
+ id: string;
7
+ type: BpmnElementType;
8
+ label?: string;
9
+ width: number;
10
+ height: number;
11
+ x: number;
12
+ y: number;
13
+ }
14
+ export interface SequenceBlock {
15
+ kind: "sequence";
16
+ items: FlowBlock[];
17
+ width: number;
18
+ height: number;
19
+ x: number;
20
+ y: number;
21
+ }
22
+ export interface GatewayBlock {
23
+ kind: "gateway";
24
+ split: NodeBlock;
25
+ join: NodeBlock;
26
+ branches: SequenceBlock[];
27
+ branchColumnWidth: number;
28
+ width: number;
29
+ height: number;
30
+ x: number;
31
+ y: number;
32
+ }
33
+ /**
34
+ * Attempt to build block tree. Returns null if process is unstructured.
35
+ */
36
+ export declare function buildBlockTree(dag: DirectedGraph, nodeIndex: Map<string, BpmnFlowElement>): SequenceBlock | null;
37
+ //# sourceMappingURL=block-builder.d.ts.map
@@ -0,0 +1,154 @@
1
+ import { topologicalSort } from "./graph.js";
2
+ import { ELEMENT_SIZES } from "./types.js";
3
+ const GATEWAY_TYPES = new Set([
4
+ "exclusiveGateway",
5
+ "parallelGateway",
6
+ "inclusiveGateway",
7
+ "eventBasedGateway",
8
+ "complexGateway",
9
+ ]);
10
+ /** Make a NodeBlock for the given element. */
11
+ function makeNodeBlock(id, nodeIndex) {
12
+ const el = nodeIndex.get(id);
13
+ const type = el?.type ?? "serviceTask";
14
+ const size = ELEMENT_SIZES[type] ?? { width: 100, height: 80 };
15
+ return {
16
+ kind: "node",
17
+ id,
18
+ type,
19
+ label: el?.name,
20
+ width: size.width,
21
+ height: size.height,
22
+ x: 0,
23
+ y: 0,
24
+ };
25
+ }
26
+ /**
27
+ * Find the matching join gateway for a split gateway.
28
+ * Uses a depth counter across the topoOrder of provided node IDs:
29
+ * +1 for each split (outDegree≥2), -1 for each join (inDegree≥2).
30
+ * First time depth returns to 0 → that's the matching join.
31
+ */
32
+ function findMatchingJoin(splitId, orderedIds, splitPos, dag, nodeIndex) {
33
+ let depth = 1;
34
+ for (let j = splitPos + 1; j < orderedIds.length; j++) {
35
+ const id = orderedIds[j];
36
+ if (!id)
37
+ continue;
38
+ const el = nodeIndex.get(id);
39
+ if (!el || !GATEWAY_TYPES.has(el.type))
40
+ continue;
41
+ const outDegree = (dag.successors.get(id) ?? []).length;
42
+ const inDegree = (dag.predecessors.get(id) ?? []).length;
43
+ if (outDegree >= 2)
44
+ depth++;
45
+ if (inDegree >= 2) {
46
+ depth--;
47
+ if (depth === 0)
48
+ return id;
49
+ }
50
+ }
51
+ return undefined;
52
+ }
53
+ /**
54
+ * Collect all node IDs reachable from startId before reaching stopId (exclusive).
55
+ * Uses BFS over DAG successors. The stop node is not included.
56
+ */
57
+ function collectBranchNodes(startId, stopId, dag) {
58
+ const result = [];
59
+ const seen = new Set();
60
+ const queue = [startId];
61
+ while (queue.length > 0) {
62
+ const id = queue.shift();
63
+ if (!id || seen.has(id) || id === stopId)
64
+ continue;
65
+ seen.add(id);
66
+ result.push(id);
67
+ for (const succ of dag.successors.get(id) ?? []) {
68
+ if (!seen.has(succ) && succ !== stopId) {
69
+ queue.push(succ);
70
+ }
71
+ }
72
+ }
73
+ return result;
74
+ }
75
+ /**
76
+ * Build a SequenceBlock for the given ordered list of node IDs.
77
+ * Recursively handles nested gateways.
78
+ * Throws if the structure is unstructured (no matching join found).
79
+ */
80
+ function buildSequenceFromIds(orderedIds, dag, nodeIndex, topoPos) {
81
+ const items = [];
82
+ let i = 0;
83
+ while (i < orderedIds.length) {
84
+ const id = orderedIds[i];
85
+ if (!id) {
86
+ i++;
87
+ continue;
88
+ }
89
+ const el = nodeIndex.get(id);
90
+ const outDegree = (dag.successors.get(id) ?? []).length;
91
+ const isSplit = outDegree >= 2 && el !== undefined && GATEWAY_TYPES.has(el.type);
92
+ if (isSplit) {
93
+ // This is a split gateway — find its matching join within orderedIds
94
+ const joinId = findMatchingJoin(id, orderedIds, i, dag, nodeIndex);
95
+ if (!joinId)
96
+ throw new Error(`No matching join for split gateway ${id}`);
97
+ const joinPos = orderedIds.indexOf(joinId);
98
+ if (joinPos < 0)
99
+ throw new Error(`Join ${joinId} not in current sequence`);
100
+ // For each successor of split, collect branch nodes (up to join)
101
+ const successors = dag.successors.get(id) ?? [];
102
+ const branches = [];
103
+ for (const succId of successors) {
104
+ // Collect nodes in this branch (BFS stopping at join)
105
+ const branchNodes = collectBranchNodes(succId, joinId, dag);
106
+ // Filter to only nodes that appear in orderedIds (safety)
107
+ const branchSet = new Set(branchNodes);
108
+ // Sort by topo position
109
+ const branchOrdered = orderedIds.filter((nid) => branchSet.has(nid));
110
+ branches.push(buildSequenceFromIds(branchOrdered, dag, nodeIndex, topoPos));
111
+ }
112
+ const splitBlock = makeNodeBlock(id, nodeIndex);
113
+ const joinBlock = makeNodeBlock(joinId, nodeIndex);
114
+ const gatewayBlock = {
115
+ kind: "gateway",
116
+ split: splitBlock,
117
+ join: joinBlock,
118
+ branches,
119
+ branchColumnWidth: 0,
120
+ width: 0,
121
+ height: 0,
122
+ x: 0,
123
+ y: 0,
124
+ };
125
+ items.push(gatewayBlock);
126
+ // Skip past the join gateway
127
+ i = joinPos + 1;
128
+ }
129
+ else {
130
+ items.push(makeNodeBlock(id, nodeIndex));
131
+ i++;
132
+ }
133
+ }
134
+ return { kind: "sequence", items, width: 0, height: 0, x: 0, y: 0 };
135
+ }
136
+ /**
137
+ * Attempt to build block tree. Returns null if process is unstructured.
138
+ */
139
+ export function buildBlockTree(dag, nodeIndex) {
140
+ try {
141
+ const topoOrder = topologicalSort(dag);
142
+ const topoPos = new Map();
143
+ for (let i = 0; i < topoOrder.length; i++) {
144
+ const id = topoOrder[i];
145
+ if (id)
146
+ topoPos.set(id, i);
147
+ }
148
+ return buildSequenceFromIds(topoOrder, dag, nodeIndex, topoPos);
149
+ }
150
+ catch {
151
+ return null;
152
+ }
153
+ }
154
+ //# sourceMappingURL=block-builder.js.map
@@ -0,0 +1,9 @@
1
+ import type { BpmnFlowElement } from "../bpmn/bpmn-model.js";
2
+ import type { SequenceBlock } from "./block-builder.js";
3
+ import type { LayoutNode } from "./types.js";
4
+ /**
5
+ * Apply block-based layout: size (bottom-up) then position (top-down).
6
+ * Returns LayoutNode[] with absolute positions.
7
+ */
8
+ export declare function applyBlockLayout(root: SequenceBlock, nodeIndex: Map<string, BpmnFlowElement>): LayoutNode[];
9
+ //# sourceMappingURL=block-layout.d.ts.map