@bpmnkit/editor 0.1.0 → 0.2.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/README.md +1 -0
- package/dist/editor.d.ts +30 -2
- package/dist/editor.js +119 -82
- package/dist/headless.d.ts +21 -0
- package/dist/headless.js +17 -0
- package/dist/id.d.ts +25 -0
- package/dist/id.js +39 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/modeling.d.ts +7 -6
- package/dist/modeling.js +28 -28
- package/dist/ops.d.ts +158 -0
- package/dist/ops.js +82 -0
- package/dist/types.d.ts +16 -0
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -126,6 +126,7 @@ interface SideDock {
|
|
|
126
126
|
| [`@bpmnkit/patterns`](https://www.npmjs.com/package/@bpmnkit/patterns) | Domain process patterns for BPMNKit AIKit |
|
|
127
127
|
| [`@bpmnkit/reebe-wasm`](https://www.npmjs.com/package/@bpmnkit/reebe-wasm) | WebAssembly BPMN engine for browser simulation |
|
|
128
128
|
| [`@bpmnkit/worker-client`](https://www.npmjs.com/package/@bpmnkit/worker-client) | Thin Zeebe REST client for standalone workers |
|
|
129
|
+
| [`@bpmnkit/user-tasks`](https://www.npmjs.com/package/@bpmnkit/user-tasks) | Embeddable user task widget for Camunda 8 |
|
|
129
130
|
| [`@bpmnkit/cli-sdk`](https://www.npmjs.com/package/@bpmnkit/cli-sdk) | Plugin authoring SDK for the casen CLI |
|
|
130
131
|
| [`@bpmnkit/create-casen-plugin`](https://www.npmjs.com/package/@bpmnkit/create-casen-plugin) | Scaffold a new casen CLI plugin in seconds |
|
|
131
132
|
| [`@bpmnkit/casen-report`](https://www.npmjs.com/package/@bpmnkit/casen-report) | HTML reports from Camunda 8 incident and SLA data |
|
package/dist/editor.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OverlayManager } from "@bpmnkit/canvas";
|
|
2
|
-
import type { Theme } from "@bpmnkit/canvas";
|
|
2
|
+
import type { Theme, ViewportState } from "@bpmnkit/canvas";
|
|
3
3
|
import type { BpmnDefinitions, DiColor } from "@bpmnkit/core";
|
|
4
4
|
import type { Translate } from "./i18n.js";
|
|
5
5
|
import type { CreateShapeType, EditorEvents, EditorOptions, LabelPosition, Tool } from "./types.js";
|
|
@@ -94,10 +94,30 @@ export declare class BpmnEditor {
|
|
|
94
94
|
}>;
|
|
95
95
|
undo(): void;
|
|
96
96
|
redo(): void;
|
|
97
|
+
/**
|
|
98
|
+
* Moves to a document already on the command stack — an undo or a redo.
|
|
99
|
+
*
|
|
100
|
+
* It describes itself as a `snapshot` op, which is the only honest option: a
|
|
101
|
+
* step back through the stack is a state change like any other, and a
|
|
102
|
+
* listener relaying edits elsewhere that never heard about it would be
|
|
103
|
+
* silently wrong from then on. There is no smaller description available —
|
|
104
|
+
* the inverse of an op is not something the stack records.
|
|
105
|
+
*/
|
|
106
|
+
private _stepTo;
|
|
97
107
|
canUndo(): boolean;
|
|
98
108
|
canRedo(): boolean;
|
|
99
109
|
getDefinitions(): BpmnDefinitions | null;
|
|
100
110
|
applyChange(fn: (defs: BpmnDefinitions) => BpmnDefinitions): void;
|
|
111
|
+
/**
|
|
112
|
+
* The raw pan and zoom, for restoring it later.
|
|
113
|
+
*
|
|
114
|
+
* Paired with {@link setViewport} so a viewer can hand its view to an editor
|
|
115
|
+
* taking its place — without it the swap re-fits, and the diagram jumps under
|
|
116
|
+
* the cursor at the moment someone starts editing.
|
|
117
|
+
*/
|
|
118
|
+
getViewport(): ViewportState;
|
|
119
|
+
/** Restores a viewport captured by {@link getViewport}. */
|
|
120
|
+
setViewport(state: Partial<ViewportState>): void;
|
|
101
121
|
fitView(padding?: number): void;
|
|
102
122
|
/** The host element that receives the `data-theme` attribute. */
|
|
103
123
|
get container(): HTMLElement;
|
|
@@ -124,7 +144,15 @@ export declare class BpmnEditor {
|
|
|
124
144
|
private _getDuplicateIds;
|
|
125
145
|
private _setDuplicateIdWarning;
|
|
126
146
|
private _renderDefs;
|
|
127
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Performs an edit: the editor's single mutation path.
|
|
149
|
+
*
|
|
150
|
+
* Every change goes through {@link applyOp}, including the ones typed right
|
|
151
|
+
* here — see `ops.ts` for why local edits are not allowed their own shortcut.
|
|
152
|
+
* Returns the ids the op brought into being, which a caller may need for a
|
|
153
|
+
* follow-up (starting a label edit on a fresh annotation, say).
|
|
154
|
+
*/
|
|
155
|
+
private _executeOp;
|
|
128
156
|
/** Label of the change `undo()` would revert (for HUD tooltips), or null. */
|
|
129
157
|
getUndoLabel(): string | null;
|
|
130
158
|
/** Label of the change `redo()` would re-apply (for HUD tooltips), or null. */
|
package/dist/editor.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { KeyboardHandler, OverlayManager, ViewportController, computeDiagramBounds, createDefs, createGrid, injectStyles, render, } from "@bpmnkit/canvas";
|
|
2
|
-
import { Bpmn
|
|
2
|
+
import { Bpmn } from "@bpmnkit/core";
|
|
3
3
|
import { CommandStack } from "./command-stack.js";
|
|
4
4
|
import { injectEditorStyles } from "./css.js";
|
|
5
5
|
import { closestPort, computeWaypoints, computeWaypointsAvoiding, computeWaypointsWithPorts, diagramToScreen, labelBoundsForPosition, portFromWaypoint, screenToDiagram, } from "./geometry.js";
|
|
6
6
|
import { defaultTranslate } from "./i18n.js";
|
|
7
|
+
import { newIdSeed } from "./id.js";
|
|
7
8
|
import { LabelEditor } from "./label-editor.js";
|
|
8
|
-
import {
|
|
9
|
+
import { copyElements, createEmptyDefinitions, insertEdgeWaypoint, moveEdgeSegment, moveEdgeWaypoint, } from "./modeling.js";
|
|
10
|
+
import { applyOp } from "./ops.js";
|
|
9
11
|
import { OverlayRenderer } from "./overlay.js";
|
|
10
12
|
import { canAttach, canConnect, canMorph, canResize } from "./rules.js";
|
|
11
13
|
import { EditorStateMachine } from "./state-machine.js";
|
|
@@ -376,7 +378,7 @@ export class BpmnEditor {
|
|
|
376
378
|
previewResize: (bounds) => this._overlay.setResizePreview(bounds),
|
|
377
379
|
commitResize: (id, bounds) => {
|
|
378
380
|
this._overlay.setResizePreview(null);
|
|
379
|
-
this.
|
|
381
|
+
this._executeOp({ kind: "resize", id, bounds }, { label: "Resize" });
|
|
380
382
|
},
|
|
381
383
|
previewConnect: (ghostEnd, targetId) => {
|
|
382
384
|
const src = this._connectSourceBounds();
|
|
@@ -401,7 +403,7 @@ export class BpmnEditor {
|
|
|
401
403
|
startLabelEdit: (id) => this._startLabelEdit(id),
|
|
402
404
|
setHovered: (id) => this._overlay.setHovered(id, this._shapes),
|
|
403
405
|
executeDelete: (ids) => {
|
|
404
|
-
this.
|
|
406
|
+
this._executeOp({ kind: "delete", ids }, { label: "Delete" });
|
|
405
407
|
this._setSelection([]);
|
|
406
408
|
},
|
|
407
409
|
executeCopy: () => this._doCopy(),
|
|
@@ -431,7 +433,7 @@ export class BpmnEditor {
|
|
|
431
433
|
this._overlay.setEndpointDragGhost(null);
|
|
432
434
|
this._overlay.setAlignmentGuides([]);
|
|
433
435
|
const snap = this._snapWaypoint(pt);
|
|
434
|
-
this.
|
|
436
|
+
this._executeOp({ kind: "insertWaypoint", edgeId, segIdx, point: snap.pt }, { label: "Add waypoint" });
|
|
435
437
|
},
|
|
436
438
|
cancelWaypointInsert: () => {
|
|
437
439
|
this._overlay.setEndpointDragGhost(null);
|
|
@@ -450,7 +452,7 @@ export class BpmnEditor {
|
|
|
450
452
|
this._overlay.setEndpointDragGhost(null);
|
|
451
453
|
this._overlay.setAlignmentGuides([]);
|
|
452
454
|
const snap = this._snapWaypoint(pt);
|
|
453
|
-
this.
|
|
455
|
+
this._executeOp({ kind: "moveWaypoint", edgeId, wpIdx, point: snap.pt }, { label: "Move waypoint" });
|
|
454
456
|
},
|
|
455
457
|
cancelWaypointMove: () => {
|
|
456
458
|
this._overlay.setEndpointDragGhost(null);
|
|
@@ -465,7 +467,7 @@ export class BpmnEditor {
|
|
|
465
467
|
},
|
|
466
468
|
commitSegmentMove: (edgeId, segIdx, isHoriz, delta) => {
|
|
467
469
|
this._overlay.setEndpointDragGhost(null);
|
|
468
|
-
this.
|
|
470
|
+
this._executeOp({ kind: "moveSegment", edgeId, segIdx, isHoriz, delta }, { label: "Move segment" });
|
|
469
471
|
},
|
|
470
472
|
cancelSegmentMove: () => {
|
|
471
473
|
this._overlay.setEndpointDragGhost(null);
|
|
@@ -491,7 +493,7 @@ export class BpmnEditor {
|
|
|
491
493
|
this._stateMachine = new EditorStateMachine(callbacks);
|
|
492
494
|
// ── Label editor ─────────────────────────────────────────────
|
|
493
495
|
this._labelEditor = new LabelEditor(this._host, (id, text) => {
|
|
494
|
-
this.
|
|
496
|
+
this._executeOp({ kind: "rename", id, name: text }, { label: "Rename", coalesceKey: `label:${id}` });
|
|
495
497
|
this._stateMachine.setMode({ mode: "select", sub: { name: "idle", hoveredId: null } });
|
|
496
498
|
}, () => {
|
|
497
499
|
this._stateMachine.setMode({ mode: "select", sub: { name: "idle", hoveredId: null } });
|
|
@@ -552,7 +554,7 @@ export class BpmnEditor {
|
|
|
552
554
|
* The operation is undoable.
|
|
553
555
|
*/
|
|
554
556
|
autoLayout() {
|
|
555
|
-
this.
|
|
557
|
+
this._executeOp({ kind: "autoLayout" }, { label: "Auto-layout" });
|
|
556
558
|
this.fitView();
|
|
557
559
|
}
|
|
558
560
|
loadDefinitions(defs) {
|
|
@@ -625,7 +627,7 @@ export class BpmnEditor {
|
|
|
625
627
|
if (this._selectedIds.length === 0)
|
|
626
628
|
return;
|
|
627
629
|
const ids = [...this._selectedIds];
|
|
628
|
-
this.
|
|
630
|
+
this._executeOp({ kind: "delete", ids }, { label: "Delete" });
|
|
629
631
|
this._setSelection([]);
|
|
630
632
|
}
|
|
631
633
|
/** Bounds of the currently-selected shapes (edges have none, and are skipped). */
|
|
@@ -678,7 +680,7 @@ export class BpmnEditor {
|
|
|
678
680
|
moves.push({ id: b.id, dx, dy });
|
|
679
681
|
}
|
|
680
682
|
if (moves.length > 0)
|
|
681
|
-
this.
|
|
683
|
+
this._executeOp({ kind: "move", moves }, { label: "Align" });
|
|
682
684
|
}
|
|
683
685
|
/**
|
|
684
686
|
* Distributes the selected shapes so the gaps between them are equal along
|
|
@@ -713,7 +715,7 @@ export class BpmnEditor {
|
|
|
713
715
|
cursor += size(b) + gap;
|
|
714
716
|
}
|
|
715
717
|
if (moves.length > 0)
|
|
716
|
-
this.
|
|
718
|
+
this._executeOp({ kind: "move", moves }, { label: "Distribute" });
|
|
717
719
|
}
|
|
718
720
|
/**
|
|
719
721
|
* Searches the model (recursively, including sub-process children) for
|
|
@@ -755,17 +757,27 @@ export class BpmnEditor {
|
|
|
755
757
|
}
|
|
756
758
|
undo() {
|
|
757
759
|
const prev = this._commandStack.undo();
|
|
758
|
-
if (prev)
|
|
759
|
-
this.
|
|
760
|
-
this._emit("diagram:change", prev);
|
|
761
|
-
}
|
|
760
|
+
if (prev)
|
|
761
|
+
this._stepTo(prev);
|
|
762
762
|
}
|
|
763
763
|
redo() {
|
|
764
764
|
const next = this._commandStack.redo();
|
|
765
|
-
if (next)
|
|
766
|
-
this.
|
|
767
|
-
|
|
768
|
-
|
|
765
|
+
if (next)
|
|
766
|
+
this._stepTo(next);
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Moves to a document already on the command stack — an undo or a redo.
|
|
770
|
+
*
|
|
771
|
+
* It describes itself as a `snapshot` op, which is the only honest option: a
|
|
772
|
+
* step back through the stack is a state change like any other, and a
|
|
773
|
+
* listener relaying edits elsewhere that never heard about it would be
|
|
774
|
+
* silently wrong from then on. There is no smaller description available —
|
|
775
|
+
* the inverse of an op is not something the stack records.
|
|
776
|
+
*/
|
|
777
|
+
_stepTo(defs) {
|
|
778
|
+
this._renderDefs(defs);
|
|
779
|
+
this._emit("diagram:change", defs);
|
|
780
|
+
this._emit("diagram:op", { kind: "snapshot", defs }, defs);
|
|
769
781
|
}
|
|
770
782
|
canUndo() {
|
|
771
783
|
return this._commandStack.canUndo();
|
|
@@ -777,7 +789,23 @@ export class BpmnEditor {
|
|
|
777
789
|
return this._defs;
|
|
778
790
|
}
|
|
779
791
|
applyChange(fn) {
|
|
780
|
-
this.
|
|
792
|
+
if (this._readOnly || !this._defs)
|
|
793
|
+
return;
|
|
794
|
+
this._executeOp({ kind: "snapshot", defs: fn(this._defs) });
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* The raw pan and zoom, for restoring it later.
|
|
798
|
+
*
|
|
799
|
+
* Paired with {@link setViewport} so a viewer can hand its view to an editor
|
|
800
|
+
* taking its place — without it the swap re-fits, and the diagram jumps under
|
|
801
|
+
* the cursor at the moment someone starts editing.
|
|
802
|
+
*/
|
|
803
|
+
getViewport() {
|
|
804
|
+
return this._viewport.state;
|
|
805
|
+
}
|
|
806
|
+
/** Restores a viewport captured by {@link getViewport}. */
|
|
807
|
+
setViewport(state) {
|
|
808
|
+
this._viewport.set(state);
|
|
781
809
|
}
|
|
782
810
|
fitView(padding = 40) {
|
|
783
811
|
if (!this._defs)
|
|
@@ -988,15 +1016,31 @@ export class BpmnEditor {
|
|
|
988
1016
|
}
|
|
989
1017
|
this._emit("diagram:load", defs, { missingShapes: [], missingEdges: [] });
|
|
990
1018
|
}
|
|
991
|
-
|
|
1019
|
+
/**
|
|
1020
|
+
* Performs an edit: the editor's single mutation path.
|
|
1021
|
+
*
|
|
1022
|
+
* Every change goes through {@link applyOp}, including the ones typed right
|
|
1023
|
+
* here — see `ops.ts` for why local edits are not allowed their own shortcut.
|
|
1024
|
+
* Returns the ids the op brought into being, which a caller may need for a
|
|
1025
|
+
* follow-up (starting a label edit on a fresh annotation, say).
|
|
1026
|
+
*/
|
|
1027
|
+
_executeOp(op, options = {}) {
|
|
992
1028
|
if (this._readOnly || !this._defs)
|
|
993
|
-
return;
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
if (
|
|
998
|
-
this.
|
|
999
|
-
this.
|
|
1029
|
+
return [];
|
|
1030
|
+
const { defs, created } = applyOp(this._defs, op);
|
|
1031
|
+
// Selection is set before the render so the new element comes up selected.
|
|
1032
|
+
const select = options.select === "all" ? created : options.select === "first" ? created.slice(0, 1) : null;
|
|
1033
|
+
if (select)
|
|
1034
|
+
this._selectedIds = select;
|
|
1035
|
+
this._commandStack.push(defs, options.label ?? "", options.coalesceKey);
|
|
1036
|
+
this._renderDefs(defs);
|
|
1037
|
+
if (options.label)
|
|
1038
|
+
this._announce(this._t(options.label));
|
|
1039
|
+
this._emit("diagram:change", defs);
|
|
1040
|
+
this._emit("diagram:op", op, defs);
|
|
1041
|
+
if (select)
|
|
1042
|
+
this._emit("editor:select", select);
|
|
1043
|
+
return created;
|
|
1000
1044
|
}
|
|
1001
1045
|
/** Label of the change `undo()` would revert (for HUD tooltips), or null. */
|
|
1002
1046
|
getUndoLabel() {
|
|
@@ -1103,10 +1147,10 @@ export class BpmnEditor {
|
|
|
1103
1147
|
const moves = this._selectedIds.map((id) => ({ id, dx: snap.dx, dy: snap.dy }));
|
|
1104
1148
|
const shapeId = this._selectedIds.length === 1 ? this._selectedIds[0] : undefined;
|
|
1105
1149
|
if (edgeDropId && shapeId) {
|
|
1106
|
-
this.
|
|
1150
|
+
this._executeOp({ kind: "move", moves, onEdge: { edgeId: edgeDropId, shapeId } }, { label: "Insert on flow" });
|
|
1107
1151
|
}
|
|
1108
1152
|
else {
|
|
1109
|
-
this.
|
|
1153
|
+
this._executeOp({ kind: "move", moves }, { label: "Move" });
|
|
1110
1154
|
}
|
|
1111
1155
|
if (this._isDragging) {
|
|
1112
1156
|
this._isDragging = false;
|
|
@@ -1181,7 +1225,7 @@ export class BpmnEditor {
|
|
|
1181
1225
|
}
|
|
1182
1226
|
}
|
|
1183
1227
|
if (moves.length > 0) {
|
|
1184
|
-
this.
|
|
1228
|
+
this._executeOp({ kind: "move", moves }, { label: "Move" });
|
|
1185
1229
|
}
|
|
1186
1230
|
}
|
|
1187
1231
|
_cancelSpace() {
|
|
@@ -1203,13 +1247,9 @@ export class BpmnEditor {
|
|
|
1203
1247
|
return;
|
|
1204
1248
|
const bounds = defaultBounds(type, actualCenter.x, actualCenter.y);
|
|
1205
1249
|
if (type === "textAnnotation") {
|
|
1206
|
-
const
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
this._renderDefs(result.defs);
|
|
1210
|
-
this._emit("diagram:change", result.defs);
|
|
1211
|
-
this._emit("editor:select", [result.id]);
|
|
1212
|
-
this._startLabelEdit(result.id);
|
|
1250
|
+
const [id] = this._executeOp({ kind: "createAnnotation", bounds, seed: newIdSeed() }, { select: "first" });
|
|
1251
|
+
if (id)
|
|
1252
|
+
this._startLabelEdit(id);
|
|
1213
1253
|
return;
|
|
1214
1254
|
}
|
|
1215
1255
|
// If hovering over an activity, create a boundary event
|
|
@@ -1223,24 +1263,23 @@ export class BpmnEditor {
|
|
|
1223
1263
|
const eventBounds = snapToBoundary(actualCenter, hostBounds, 18);
|
|
1224
1264
|
// Map palette type to event definition type
|
|
1225
1265
|
const eventDefType = intermediateEventDefType(type);
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1266
|
+
this._executeOp({
|
|
1267
|
+
kind: "createBoundaryEvent",
|
|
1268
|
+
hostId: boundaryHostId,
|
|
1269
|
+
eventDefType,
|
|
1270
|
+
bounds: eventBounds,
|
|
1271
|
+
seed: newIdSeed(),
|
|
1272
|
+
}, { select: "first" });
|
|
1232
1273
|
return;
|
|
1233
1274
|
}
|
|
1234
1275
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
:
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
this._emit("diagram:change", finalDefs);
|
|
1243
|
-
this._emit("editor:select", [result.id]);
|
|
1276
|
+
this._executeOp({
|
|
1277
|
+
kind: "createShape",
|
|
1278
|
+
type,
|
|
1279
|
+
bounds,
|
|
1280
|
+
onEdge: pendingEdgeDrop ?? undefined,
|
|
1281
|
+
seed: newIdSeed(),
|
|
1282
|
+
}, { select: "first" });
|
|
1244
1283
|
}
|
|
1245
1284
|
_doConnect(srcId, tgtId) {
|
|
1246
1285
|
const srcShape = this._shapeById.get(srcId);
|
|
@@ -1255,7 +1294,7 @@ export class BpmnEditor {
|
|
|
1255
1294
|
.filter((s) => s.id !== srcId && s.id !== tgtId)
|
|
1256
1295
|
.map((s) => s.shape.bounds);
|
|
1257
1296
|
const waypoints = computeWaypointsAvoiding(srcShape.shape.bounds, tgtShape.shape.bounds, obstacles);
|
|
1258
|
-
this.
|
|
1297
|
+
this._executeOp({ kind: "createConnection", sourceId: srcId, targetId: tgtId, waypoints, seed: newIdSeed() }, { label: "Connect" });
|
|
1259
1298
|
}
|
|
1260
1299
|
_doCopy() {
|
|
1261
1300
|
if (!this._defs || this._selectedIds.length === 0)
|
|
@@ -1265,20 +1304,14 @@ export class BpmnEditor {
|
|
|
1265
1304
|
_doPaste() {
|
|
1266
1305
|
if (!this._clipboard)
|
|
1267
1306
|
return;
|
|
1268
|
-
|
|
1269
|
-
const result = pasteElements(base, this._clipboard, 20, 20);
|
|
1270
|
-
this._selectedIds = result.topLevelIds;
|
|
1271
|
-
this._commandStack.push(result.defs, "Paste");
|
|
1272
|
-
this._renderDefs(result.defs);
|
|
1273
|
-
this._emit("diagram:change", result.defs);
|
|
1274
|
-
this._emit("editor:select", result.topLevelIds);
|
|
1307
|
+
this._executeOp({ kind: "paste", clipboard: this._clipboard, offsetX: 20, offsetY: 20, seed: newIdSeed() }, { label: "Paste", select: "all" });
|
|
1275
1308
|
}
|
|
1276
1309
|
_doCut() {
|
|
1277
1310
|
if (!this._defs || this._selectedIds.length === 0)
|
|
1278
1311
|
return;
|
|
1279
1312
|
this._doCopy();
|
|
1280
1313
|
const ids = [...this._selectedIds];
|
|
1281
|
-
this.
|
|
1314
|
+
this._executeOp({ kind: "delete", ids }, { label: "Cut" });
|
|
1282
1315
|
this._setSelection([]);
|
|
1283
1316
|
}
|
|
1284
1317
|
_startLabelEdit(id) {
|
|
@@ -1390,15 +1423,17 @@ export class BpmnEditor {
|
|
|
1390
1423
|
}
|
|
1391
1424
|
const newBounds = this._smartPlaceBounds(srcBounds, sourceId, w, h);
|
|
1392
1425
|
const obstacles = this._shapes.filter((s) => s.id !== sourceId).map((s) => s.shape.bounds);
|
|
1393
|
-
const r1 = createShape(this._defs, type, newBounds, name);
|
|
1394
1426
|
const waypoints = computeWaypointsAvoiding(srcBounds, newBounds, obstacles);
|
|
1395
|
-
const
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1427
|
+
const [id] = this._executeOp({
|
|
1428
|
+
kind: "createConnected",
|
|
1429
|
+
sourceId,
|
|
1430
|
+
type,
|
|
1431
|
+
name,
|
|
1432
|
+
bounds: newBounds,
|
|
1433
|
+
waypoints,
|
|
1434
|
+
seed: newIdSeed(),
|
|
1435
|
+
}, { select: "first" });
|
|
1436
|
+
return id ?? null;
|
|
1402
1437
|
}
|
|
1403
1438
|
_smartPlaceBounds(srcBounds, sourceId, w, h) {
|
|
1404
1439
|
const GAP = 60;
|
|
@@ -1495,7 +1530,7 @@ export class BpmnEditor {
|
|
|
1495
1530
|
if (!shape)
|
|
1496
1531
|
return;
|
|
1497
1532
|
const labelBounds = labelBoundsForPosition(shape.shape.bounds, position);
|
|
1498
|
-
this.
|
|
1533
|
+
this._executeOp({ kind: "labelPosition", id: shapeId, bounds: labelBounds }, { label: "Move label" });
|
|
1499
1534
|
}
|
|
1500
1535
|
/** Starts inline label editing for the element with the given id. */
|
|
1501
1536
|
editLabel(id) {
|
|
@@ -1537,17 +1572,19 @@ export class BpmnEditor {
|
|
|
1537
1572
|
width: annW,
|
|
1538
1573
|
height: annH,
|
|
1539
1574
|
};
|
|
1540
|
-
const
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1575
|
+
const [id] = this._executeOp({
|
|
1576
|
+
kind: "createAnnotationFor",
|
|
1577
|
+
sourceId,
|
|
1578
|
+
bounds: annBounds,
|
|
1579
|
+
sourceBounds: srcBounds,
|
|
1580
|
+
seed: newIdSeed(),
|
|
1581
|
+
}, { select: "first" });
|
|
1582
|
+
if (id)
|
|
1583
|
+
this._startLabelEdit(id);
|
|
1547
1584
|
}
|
|
1548
1585
|
/** Updates the color of a shape in the diagram. Pass `{}` to clear colors. */
|
|
1549
1586
|
updateColor(id, color) {
|
|
1550
|
-
this.
|
|
1587
|
+
this._executeOp({ kind: "color", id, color }, { label: "Change colour", coalesceKey: `color:${id}` });
|
|
1551
1588
|
}
|
|
1552
1589
|
// ── Private helpers ────────────────────────────────────────────────
|
|
1553
1590
|
_setEdgeSelected(edgeId) {
|
|
@@ -1572,7 +1609,7 @@ export class BpmnEditor {
|
|
|
1572
1609
|
const current = this._shapeById.get(id)?.flowElement?.type;
|
|
1573
1610
|
if (current && !canMorph(current, newType))
|
|
1574
1611
|
return;
|
|
1575
|
-
this.
|
|
1612
|
+
this._executeOp({ kind: "changeType", id, type: newType }, { label: "Change type" });
|
|
1576
1613
|
}
|
|
1577
1614
|
_findEdgeDropTarget(dx, dy) {
|
|
1578
1615
|
if (this._selectedIds.length !== 1)
|
|
@@ -1677,7 +1714,7 @@ export class BpmnEditor {
|
|
|
1677
1714
|
const newPort = isStart
|
|
1678
1715
|
? closestPort(diagPoint, srcDi.bounds)
|
|
1679
1716
|
: closestPort(diagPoint, tgtDi.bounds);
|
|
1680
|
-
this.
|
|
1717
|
+
this._executeOp({ kind: "reconnect", edgeId, isStart, port: newPort }, { label: "Reconnect" });
|
|
1681
1718
|
}
|
|
1682
1719
|
_isResizable(id) {
|
|
1683
1720
|
const shape = this._shapeById.get(id);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The editor's DOM-free surface: operations, and the ids they mint.
|
|
3
|
+
*
|
|
4
|
+
* `@bpmnkit/editor` itself reaches for `document` the moment it is imported —
|
|
5
|
+
* it is an editor. But the part that decides *what an edit does* never touches
|
|
6
|
+
* the DOM, and that part has to run in two more places: a Durable Object
|
|
7
|
+
* replaying a writer's op to verify it, and a watcher applying the same op
|
|
8
|
+
* without an editor loaded at all.
|
|
9
|
+
*
|
|
10
|
+
* So this entry exists to be importable from a Worker. Everything reachable
|
|
11
|
+
* from here is pure, and the subpath keeps it that way — importing the package
|
|
12
|
+
* root instead would pull the canvas in behind it.
|
|
13
|
+
*/
|
|
14
|
+
export { applyOp } from "./ops.js";
|
|
15
|
+
export type { EditorOp, OpResult, ShapeMove } from "./ops.js";
|
|
16
|
+
export { createIdFactory, genId, newIdSeed } from "./id.js";
|
|
17
|
+
export type { IdFactory } from "./id.js";
|
|
18
|
+
export { createEmptyDefinitions } from "./modeling.js";
|
|
19
|
+
export type { Clipboard } from "./modeling.js";
|
|
20
|
+
export type { CreateShapeType, PortDir } from "./types.js";
|
|
21
|
+
//# sourceMappingURL=headless.d.ts.map
|
package/dist/headless.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The editor's DOM-free surface: operations, and the ids they mint.
|
|
3
|
+
*
|
|
4
|
+
* `@bpmnkit/editor` itself reaches for `document` the moment it is imported —
|
|
5
|
+
* it is an editor. But the part that decides *what an edit does* never touches
|
|
6
|
+
* the DOM, and that part has to run in two more places: a Durable Object
|
|
7
|
+
* replaying a writer's op to verify it, and a watcher applying the same op
|
|
8
|
+
* without an editor loaded at all.
|
|
9
|
+
*
|
|
10
|
+
* So this entry exists to be importable from a Worker. Everything reachable
|
|
11
|
+
* from here is pure, and the subpath keeps it that way — importing the package
|
|
12
|
+
* root instead would pull the canvas in behind it.
|
|
13
|
+
*/
|
|
14
|
+
export { applyOp } from "./ops.js";
|
|
15
|
+
export { createIdFactory, genId, newIdSeed } from "./id.js";
|
|
16
|
+
export { createEmptyDefinitions } from "./modeling.js";
|
|
17
|
+
//# sourceMappingURL=headless.js.map
|
package/dist/id.d.ts
CHANGED
|
@@ -1,2 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How new element ids are minted.
|
|
3
|
+
*
|
|
4
|
+
* Normally this is `Math.random()`, which is exactly right for one person
|
|
5
|
+
* editing one diagram. It is wrong the moment the same edit has to happen twice
|
|
6
|
+
* — replayed on a server, or on someone else's screen — because two runs would
|
|
7
|
+
* invent two different ids for the same new task.
|
|
8
|
+
*
|
|
9
|
+
* So the minting functions in `modeling.ts` take an optional {@link IdFactory}.
|
|
10
|
+
* An operation carries a seed, both sides build the same factory from it, and
|
|
11
|
+
* the same edit produces byte-identical XML wherever it runs.
|
|
12
|
+
*/
|
|
13
|
+
/** Mints an id for a new element, given the prefix its type wants. */
|
|
14
|
+
export type IdFactory = (prefix: string) => string;
|
|
15
|
+
/** The default: unique enough for one editor, different on every call. */
|
|
1
16
|
export declare function genId(prefix: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* A factory that yields the same sequence of ids for the same seed.
|
|
19
|
+
*
|
|
20
|
+
* Deterministic *in call order*, which is the contract an operation replay
|
|
21
|
+
* relies on: the same function, given the same arguments, asks for the same
|
|
22
|
+
* prefixes in the same order, so it gets the same ids back.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createIdFactory(seed: string): IdFactory;
|
|
25
|
+
/** A seed with enough entropy that two operations cannot mint the same ids. */
|
|
26
|
+
export declare function newIdSeed(): string;
|
|
2
27
|
//# sourceMappingURL=id.d.ts.map
|
package/dist/id.js
CHANGED
|
@@ -1,4 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How new element ids are minted.
|
|
3
|
+
*
|
|
4
|
+
* Normally this is `Math.random()`, which is exactly right for one person
|
|
5
|
+
* editing one diagram. It is wrong the moment the same edit has to happen twice
|
|
6
|
+
* — replayed on a server, or on someone else's screen — because two runs would
|
|
7
|
+
* invent two different ids for the same new task.
|
|
8
|
+
*
|
|
9
|
+
* So the minting functions in `modeling.ts` take an optional {@link IdFactory}.
|
|
10
|
+
* An operation carries a seed, both sides build the same factory from it, and
|
|
11
|
+
* the same edit produces byte-identical XML wherever it runs.
|
|
12
|
+
*/
|
|
13
|
+
/** The default: unique enough for one editor, different on every call. */
|
|
1
14
|
export function genId(prefix) {
|
|
2
15
|
return `${prefix}_${Math.random().toString(36).slice(2, 9)}`;
|
|
3
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* A factory that yields the same sequence of ids for the same seed.
|
|
19
|
+
*
|
|
20
|
+
* Deterministic *in call order*, which is the contract an operation replay
|
|
21
|
+
* relies on: the same function, given the same arguments, asks for the same
|
|
22
|
+
* prefixes in the same order, so it gets the same ids back.
|
|
23
|
+
*/
|
|
24
|
+
export function createIdFactory(seed) {
|
|
25
|
+
// FNV-1a over the seed, then xorshift32 per id. Small, dependency-free, and
|
|
26
|
+
// the output is shaped like `genId`'s so nothing downstream can tell them apart.
|
|
27
|
+
let state = 2166136261;
|
|
28
|
+
for (let i = 0; i < seed.length; i++) {
|
|
29
|
+
state ^= seed.charCodeAt(i);
|
|
30
|
+
state = Math.imul(state, 16777619);
|
|
31
|
+
}
|
|
32
|
+
return (prefix) => {
|
|
33
|
+
state ^= state << 13;
|
|
34
|
+
state ^= state >>> 17;
|
|
35
|
+
state ^= state << 5;
|
|
36
|
+
return `${prefix}_${(state >>> 0).toString(36).padStart(7, "0").slice(0, 7)}`;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** A seed with enough entropy that two operations cannot mint the same ids. */
|
|
40
|
+
export function newIdSeed() {
|
|
41
|
+
return Math.random().toString(36).slice(2, 12);
|
|
42
|
+
}
|
|
4
43
|
//# sourceMappingURL=id.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ export { BpmnEditor } from "./editor.js";
|
|
|
2
2
|
export { CHROME_CSS, CHROME_STYLE_ID, injectChromeStyles } from "./chrome.js";
|
|
3
3
|
export { injectStyle } from "./inject.js";
|
|
4
4
|
export { createEmptyDefinitions } from "./modeling.js";
|
|
5
|
+
export { applyOp } from "./ops.js";
|
|
6
|
+
export type { EditorOp, OpResult, ShapeMove } from "./ops.js";
|
|
7
|
+
export { createIdFactory, genId, newIdSeed } from "./id.js";
|
|
8
|
+
export type { IdFactory } from "./id.js";
|
|
5
9
|
export type { EditorEvents, EditorOptions, LabelPosition, Tool, CreateShapeType, HandleDir, PortDir, } from "./types.js";
|
|
6
10
|
export { ELEMENT_GROUPS, ELEMENT_TYPE_LABELS, EXTERNAL_LABEL_TYPES, CONTEXTUAL_ADD_TYPES, getElementGroup, getValidLabelPositions, } from "./element-groups.js";
|
|
7
11
|
export type { ElementGroup } from "./element-groups.js";
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,8 @@ export { BpmnEditor } from "./editor.js";
|
|
|
2
2
|
export { CHROME_CSS, CHROME_STYLE_ID, injectChromeStyles } from "./chrome.js";
|
|
3
3
|
export { injectStyle } from "./inject.js";
|
|
4
4
|
export { createEmptyDefinitions } from "./modeling.js";
|
|
5
|
+
export { applyOp } from "./ops.js";
|
|
6
|
+
export { createIdFactory, genId, newIdSeed } from "./id.js";
|
|
5
7
|
export { ELEMENT_GROUPS, ELEMENT_TYPE_LABELS, EXTERNAL_LABEL_TYPES, CONTEXTUAL_ADD_TYPES, getElementGroup, getValidLabelPositions, } from "./element-groups.js";
|
|
6
8
|
export { initEditorHud } from "./hud.js";
|
|
7
9
|
export { createTranslationRecorder, defaultTranslate, interpolate } from "./i18n.js";
|
package/dist/modeling.d.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import type { BpmnBounds, BpmnDefinitions, BpmnDiEdge, BpmnDiShape, BpmnFlowElement, BpmnSequenceFlow, BpmnWaypoint, DiColor } from "@bpmnkit/core";
|
|
2
|
+
import { type IdFactory } from "./id.js";
|
|
2
3
|
import type { CreateShapeType, PortDir } from "./types.js";
|
|
3
4
|
/** Creates a minimal valid BpmnDefinitions with one process and one diagram. */
|
|
4
5
|
export declare function createEmptyDefinitions(): BpmnDefinitions;
|
|
5
|
-
export declare function createShape(defs: BpmnDefinitions, type: CreateShapeType, bounds: BpmnBounds, name?: string): {
|
|
6
|
+
export declare function createShape(defs: BpmnDefinitions, type: CreateShapeType, bounds: BpmnBounds, name?: string, ids?: IdFactory): {
|
|
6
7
|
defs: BpmnDefinitions;
|
|
7
8
|
id: string;
|
|
8
9
|
};
|
|
9
|
-
export declare function createBoundaryEvent(defs: BpmnDefinitions, hostId: string, eventDefType: string | null, bounds: BpmnBounds, cancelActivity?: boolean): {
|
|
10
|
+
export declare function createBoundaryEvent(defs: BpmnDefinitions, hostId: string, eventDefType: string | null, bounds: BpmnBounds, cancelActivity?: boolean, ids?: IdFactory): {
|
|
10
11
|
defs: BpmnDefinitions;
|
|
11
12
|
id: string;
|
|
12
13
|
};
|
|
13
|
-
export declare function createConnection(defs: BpmnDefinitions, sourceId: string, targetId: string, waypoints: BpmnWaypoint[]): {
|
|
14
|
+
export declare function createConnection(defs: BpmnDefinitions, sourceId: string, targetId: string, waypoints: BpmnWaypoint[], ids?: IdFactory): {
|
|
14
15
|
defs: BpmnDefinitions;
|
|
15
16
|
id: string;
|
|
16
17
|
};
|
|
@@ -74,16 +75,16 @@ export interface Clipboard {
|
|
|
74
75
|
edges: BpmnDiEdge[];
|
|
75
76
|
}
|
|
76
77
|
export declare function copyElements(defs: BpmnDefinitions, ids: string[]): Clipboard;
|
|
77
|
-
export declare function pasteElements(defs: BpmnDefinitions, clipboard: Clipboard, offsetX: number, offsetY: number): {
|
|
78
|
+
export declare function pasteElements(defs: BpmnDefinitions, clipboard: Clipboard, offsetX: number, offsetY: number, ids?: IdFactory): {
|
|
78
79
|
defs: BpmnDefinitions;
|
|
79
80
|
newIds: Map<string, string>;
|
|
80
81
|
topLevelIds: string[];
|
|
81
82
|
};
|
|
82
|
-
export declare function createAnnotation(defs: BpmnDefinitions, bounds: BpmnBounds, text?: string): {
|
|
83
|
+
export declare function createAnnotation(defs: BpmnDefinitions, bounds: BpmnBounds, text?: string, ids?: IdFactory): {
|
|
83
84
|
defs: BpmnDefinitions;
|
|
84
85
|
id: string;
|
|
85
86
|
};
|
|
86
|
-
export declare function createAnnotationWithLink(defs: BpmnDefinitions, bounds: BpmnBounds, sourceId: string, sourceBounds: BpmnBounds, text?: string): {
|
|
87
|
+
export declare function createAnnotationWithLink(defs: BpmnDefinitions, bounds: BpmnBounds, sourceId: string, sourceBounds: BpmnBounds, text?: string, ids?: IdFactory): {
|
|
87
88
|
defs: BpmnDefinitions;
|
|
88
89
|
annotationId: string;
|
|
89
90
|
associationId: string;
|
package/dist/modeling.js
CHANGED
|
@@ -338,9 +338,9 @@ function updateRefInElements(flowElements, id, updateFn) {
|
|
|
338
338
|
});
|
|
339
339
|
}
|
|
340
340
|
// ── Create shape ─────────────────────────────────────────────────────────────
|
|
341
|
-
export function createShape(defs, type, bounds, name) {
|
|
342
|
-
const id =
|
|
343
|
-
const shapeId =
|
|
341
|
+
export function createShape(defs, type, bounds, name, ids = genId) {
|
|
342
|
+
const id = ids(type);
|
|
343
|
+
const shapeId = ids(`${type}_di`);
|
|
344
344
|
const flowElement = makeFlowElement(type, id, name);
|
|
345
345
|
const process = defs.processes[0];
|
|
346
346
|
if (!process)
|
|
@@ -382,9 +382,9 @@ export function createShape(defs, type, bounds, name) {
|
|
|
382
382
|
return { defs: newDefs, id };
|
|
383
383
|
}
|
|
384
384
|
// ── Create boundary event ─────────────────────────────────────────────────────
|
|
385
|
-
export function createBoundaryEvent(defs, hostId, eventDefType, bounds, cancelActivity = true) {
|
|
386
|
-
const id =
|
|
387
|
-
const shapeId =
|
|
385
|
+
export function createBoundaryEvent(defs, hostId, eventDefType, bounds, cancelActivity = true, ids = genId) {
|
|
386
|
+
const id = ids("BoundaryEvent");
|
|
387
|
+
const shapeId = ids("BoundaryEvent_di");
|
|
388
388
|
const process = defs.processes[0];
|
|
389
389
|
if (!process)
|
|
390
390
|
return { defs, id };
|
|
@@ -434,9 +434,9 @@ export function createBoundaryEvent(defs, hostId, eventDefType, bounds, cancelAc
|
|
|
434
434
|
};
|
|
435
435
|
}
|
|
436
436
|
// ── Create connection ─────────────────────────────────────────────────────────
|
|
437
|
-
export function createConnection(defs, sourceId, targetId, waypoints) {
|
|
438
|
-
const id =
|
|
439
|
-
const edgeId =
|
|
437
|
+
export function createConnection(defs, sourceId, targetId, waypoints, ids = genId) {
|
|
438
|
+
const id = ids("Flow");
|
|
439
|
+
const edgeId = ids("Flow_di");
|
|
440
440
|
const process = defs.processes[0];
|
|
441
441
|
if (!process)
|
|
442
442
|
return { defs, id };
|
|
@@ -1373,19 +1373,19 @@ export function copyElements(defs, ids) {
|
|
|
1373
1373
|
return { elements, flows, shapes, edges };
|
|
1374
1374
|
}
|
|
1375
1375
|
/** Assigns a fresh id to `el` and every descendant / nested edge, recording the mapping. */
|
|
1376
|
-
function buildIdMap(el, map) {
|
|
1377
|
-
map.set(el.id,
|
|
1376
|
+
function buildIdMap(el, map, ids) {
|
|
1377
|
+
map.set(el.id, ids(el.type));
|
|
1378
1378
|
if (hasChildren(el)) {
|
|
1379
1379
|
for (const child of el.flowElements)
|
|
1380
|
-
buildIdMap(child, map);
|
|
1380
|
+
buildIdMap(child, map, ids);
|
|
1381
1381
|
for (const sf of el.sequenceFlows)
|
|
1382
|
-
map.set(sf.id,
|
|
1382
|
+
map.set(sf.id, ids("Flow"));
|
|
1383
1383
|
for (const ta of el.textAnnotations)
|
|
1384
|
-
map.set(ta.id,
|
|
1384
|
+
map.set(ta.id, ids("TextAnnotation"));
|
|
1385
1385
|
for (const a of el.associations)
|
|
1386
|
-
map.set(a.id,
|
|
1386
|
+
map.set(a.id, ids("Association"));
|
|
1387
1387
|
for (const g of el.groups)
|
|
1388
|
-
map.set(g.id,
|
|
1388
|
+
map.set(g.id, ids("Group"));
|
|
1389
1389
|
}
|
|
1390
1390
|
}
|
|
1391
1391
|
/** Deep-clones `el`, remapping its own id, refs, and all nested children through `map`. */
|
|
@@ -1424,13 +1424,13 @@ function remapFlow(sf, map) {
|
|
|
1424
1424
|
targetRef: map.get(sf.targetRef) ?? sf.targetRef,
|
|
1425
1425
|
};
|
|
1426
1426
|
}
|
|
1427
|
-
export function pasteElements(defs, clipboard, offsetX, offsetY) {
|
|
1427
|
+
export function pasteElements(defs, clipboard, offsetX, offsetY, ids = genId) {
|
|
1428
1428
|
const newIds = new Map();
|
|
1429
1429
|
// Generate new IDs for every element (recursively) and top-level flow.
|
|
1430
1430
|
for (const el of clipboard.elements)
|
|
1431
|
-
buildIdMap(el, newIds);
|
|
1431
|
+
buildIdMap(el, newIds, ids);
|
|
1432
1432
|
for (const sf of clipboard.flows)
|
|
1433
|
-
newIds.set(sf.id,
|
|
1433
|
+
newIds.set(sf.id, ids("Flow"));
|
|
1434
1434
|
const process = defs.processes[0];
|
|
1435
1435
|
const diagram = defs.diagrams[0];
|
|
1436
1436
|
if (!process || !diagram)
|
|
@@ -1442,7 +1442,7 @@ export function pasteElements(defs, clipboard, offsetX, offsetY) {
|
|
|
1442
1442
|
const newElId = newIds.get(s.bpmnElement) ?? s.bpmnElement;
|
|
1443
1443
|
return {
|
|
1444
1444
|
...s,
|
|
1445
|
-
id:
|
|
1445
|
+
id: ids(`${newElId}_di`),
|
|
1446
1446
|
bpmnElement: newElId,
|
|
1447
1447
|
bounds: {
|
|
1448
1448
|
...s.bounds,
|
|
@@ -1456,7 +1456,7 @@ export function pasteElements(defs, clipboard, offsetX, offsetY) {
|
|
|
1456
1456
|
const newFlowId = newIds.get(e.bpmnElement) ?? e.bpmnElement;
|
|
1457
1457
|
return {
|
|
1458
1458
|
...e,
|
|
1459
|
-
id:
|
|
1459
|
+
id: ids(`${newFlowId}_di`),
|
|
1460
1460
|
bpmnElement: newFlowId,
|
|
1461
1461
|
waypoints: e.waypoints.map((wp) => ({
|
|
1462
1462
|
x: wp.x + offsetX,
|
|
@@ -1492,9 +1492,9 @@ export function pasteElements(defs, clipboard, offsetX, offsetY) {
|
|
|
1492
1492
|
return { defs: newDefs, newIds, topLevelIds };
|
|
1493
1493
|
}
|
|
1494
1494
|
// ── Create text annotation ────────────────────────────────────────────────────
|
|
1495
|
-
export function createAnnotation(defs, bounds, text) {
|
|
1496
|
-
const id =
|
|
1497
|
-
const shapeId =
|
|
1495
|
+
export function createAnnotation(defs, bounds, text, ids = genId) {
|
|
1496
|
+
const id = ids("TextAnnotation");
|
|
1497
|
+
const shapeId = ids("TextAnnotation_di");
|
|
1498
1498
|
const annotation = { id, text, unknownAttributes: {} };
|
|
1499
1499
|
const diShape = { id: shapeId, bpmnElement: id, bounds, unknownAttributes: {} };
|
|
1500
1500
|
const process = defs.processes[0];
|
|
@@ -1521,11 +1521,11 @@ export function createAnnotation(defs, bounds, text) {
|
|
|
1521
1521
|
id,
|
|
1522
1522
|
};
|
|
1523
1523
|
}
|
|
1524
|
-
export function createAnnotationWithLink(defs, bounds, sourceId, sourceBounds, text) {
|
|
1525
|
-
const annotResult = createAnnotation(defs, bounds, text);
|
|
1524
|
+
export function createAnnotationWithLink(defs, bounds, sourceId, sourceBounds, text, ids = genId) {
|
|
1525
|
+
const annotResult = createAnnotation(defs, bounds, text, ids);
|
|
1526
1526
|
const annotationId = annotResult.id;
|
|
1527
|
-
const assocId =
|
|
1528
|
-
const edgeId =
|
|
1527
|
+
const assocId = ids("Association");
|
|
1528
|
+
const edgeId = ids("Association_di");
|
|
1529
1529
|
const assoc = {
|
|
1530
1530
|
id: assocId,
|
|
1531
1531
|
sourceRef: sourceId,
|
package/dist/ops.d.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The editor's operations: what a change *was*, rather than what it produced.
|
|
3
|
+
*
|
|
4
|
+
* `diagram:change` hands out the whole new document, which is all a single
|
|
5
|
+
* editor needs. Sending that over a wire on every keystroke is not: a
|
|
6
|
+
* `moveShapes` op is a few hundred bytes where a serialised `BpmnDefinitions`
|
|
7
|
+
* is hundreds of kilobytes. So every edit also describes itself, and
|
|
8
|
+
* {@link applyOp} turns the description back into the edit.
|
|
9
|
+
*
|
|
10
|
+
* **`applyOp` is the only way the editor performs these edits**, locally as well
|
|
11
|
+
* as on replay. That is deliberate: if the editor composed the modeling calls
|
|
12
|
+
* itself and `applyOp` composed them again, the two could drift, and the whole
|
|
13
|
+
* point is that the same op yields byte-identical XML wherever it runs. Ids are
|
|
14
|
+
* the other half of that promise — creating ops carry a seed rather than
|
|
15
|
+
* inventing ids at random, so a replay mints the same ones (see `id.ts`).
|
|
16
|
+
*/
|
|
17
|
+
import type { BpmnBounds, BpmnDefinitions, BpmnWaypoint, DiColor } from "@bpmnkit/core";
|
|
18
|
+
import { type Clipboard } from "./modeling.js";
|
|
19
|
+
import type { CreateShapeType, PortDir } from "./types.js";
|
|
20
|
+
/** One shape's displacement. */
|
|
21
|
+
export interface ShapeMove {
|
|
22
|
+
id: string;
|
|
23
|
+
dx: number;
|
|
24
|
+
dy: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A single editor edit.
|
|
28
|
+
*
|
|
29
|
+
* `seed` appears on every op that creates something: it is what makes the new
|
|
30
|
+
* element's id a property of the op rather than of the machine that ran it.
|
|
31
|
+
*/
|
|
32
|
+
export type EditorOp = {
|
|
33
|
+
kind: "createShape";
|
|
34
|
+
type: CreateShapeType;
|
|
35
|
+
bounds: BpmnBounds;
|
|
36
|
+
name?: string;
|
|
37
|
+
onEdge?: string;
|
|
38
|
+
seed: string;
|
|
39
|
+
} | {
|
|
40
|
+
kind: "createBoundaryEvent";
|
|
41
|
+
hostId: string;
|
|
42
|
+
eventDefType: string | null;
|
|
43
|
+
bounds: BpmnBounds;
|
|
44
|
+
seed: string;
|
|
45
|
+
} | {
|
|
46
|
+
kind: "createAnnotation";
|
|
47
|
+
bounds: BpmnBounds;
|
|
48
|
+
text?: string;
|
|
49
|
+
seed: string;
|
|
50
|
+
} | {
|
|
51
|
+
kind: "createAnnotationFor";
|
|
52
|
+
sourceId: string;
|
|
53
|
+
bounds: BpmnBounds;
|
|
54
|
+
sourceBounds: BpmnBounds;
|
|
55
|
+
seed: string;
|
|
56
|
+
} | {
|
|
57
|
+
kind: "createConnection";
|
|
58
|
+
sourceId: string;
|
|
59
|
+
targetId: string;
|
|
60
|
+
waypoints: BpmnWaypoint[];
|
|
61
|
+
seed: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A shape and the flow reaching it, as one edit — the "add element" affordance.
|
|
65
|
+
*
|
|
66
|
+
* Placement and routing are decided by the editor, from shapes only it can see,
|
|
67
|
+
* and travel in the op: a replay that recomputed them would need the same
|
|
68
|
+
* obstacles on screen to land in the same place.
|
|
69
|
+
*/
|
|
70
|
+
| {
|
|
71
|
+
kind: "createConnected";
|
|
72
|
+
sourceId: string;
|
|
73
|
+
type: CreateShapeType;
|
|
74
|
+
name?: string;
|
|
75
|
+
bounds: BpmnBounds;
|
|
76
|
+
waypoints: BpmnWaypoint[];
|
|
77
|
+
seed: string;
|
|
78
|
+
} | {
|
|
79
|
+
kind: "paste";
|
|
80
|
+
clipboard: Clipboard;
|
|
81
|
+
offsetX: number;
|
|
82
|
+
offsetY: number;
|
|
83
|
+
seed: string;
|
|
84
|
+
} | {
|
|
85
|
+
kind: "move";
|
|
86
|
+
moves: ShapeMove[];
|
|
87
|
+
onEdge?: {
|
|
88
|
+
edgeId: string;
|
|
89
|
+
shapeId: string;
|
|
90
|
+
};
|
|
91
|
+
} | {
|
|
92
|
+
kind: "resize";
|
|
93
|
+
id: string;
|
|
94
|
+
bounds: BpmnBounds;
|
|
95
|
+
} | {
|
|
96
|
+
kind: "delete";
|
|
97
|
+
ids: string[];
|
|
98
|
+
} | {
|
|
99
|
+
kind: "rename";
|
|
100
|
+
id: string;
|
|
101
|
+
name: string;
|
|
102
|
+
} | {
|
|
103
|
+
kind: "labelPosition";
|
|
104
|
+
id: string;
|
|
105
|
+
bounds: BpmnBounds;
|
|
106
|
+
} | {
|
|
107
|
+
kind: "color";
|
|
108
|
+
id: string;
|
|
109
|
+
color: DiColor;
|
|
110
|
+
} | {
|
|
111
|
+
kind: "changeType";
|
|
112
|
+
id: string;
|
|
113
|
+
type: CreateShapeType;
|
|
114
|
+
} | {
|
|
115
|
+
kind: "reconnect";
|
|
116
|
+
edgeId: string;
|
|
117
|
+
isStart: boolean;
|
|
118
|
+
port: PortDir;
|
|
119
|
+
} | {
|
|
120
|
+
kind: "insertWaypoint";
|
|
121
|
+
edgeId: string;
|
|
122
|
+
segIdx: number;
|
|
123
|
+
point: BpmnWaypoint;
|
|
124
|
+
} | {
|
|
125
|
+
kind: "moveWaypoint";
|
|
126
|
+
edgeId: string;
|
|
127
|
+
wpIdx: number;
|
|
128
|
+
point: BpmnWaypoint;
|
|
129
|
+
} | {
|
|
130
|
+
kind: "moveSegment";
|
|
131
|
+
edgeId: string;
|
|
132
|
+
segIdx: number;
|
|
133
|
+
isHoriz: boolean;
|
|
134
|
+
delta: number;
|
|
135
|
+
} | {
|
|
136
|
+
kind: "autoLayout";
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The escape hatch: a whole document, for edits that have no description.
|
|
140
|
+
*
|
|
141
|
+
* `applyChange` takes an arbitrary function — the properties panel uses it —
|
|
142
|
+
* and a function cannot be replayed. Under a single writer a whole-document
|
|
143
|
+
* op is still correct, just larger on the wire, so nothing is unreplayable;
|
|
144
|
+
* it simply costs more.
|
|
145
|
+
*/
|
|
146
|
+
| {
|
|
147
|
+
kind: "snapshot";
|
|
148
|
+
defs: BpmnDefinitions;
|
|
149
|
+
};
|
|
150
|
+
/** The result of an op: the new document, and anything it brought into being. */
|
|
151
|
+
export interface OpResult {
|
|
152
|
+
defs: BpmnDefinitions;
|
|
153
|
+
/** Ids created by this op, in the order they were made. Empty for edits. */
|
|
154
|
+
created: string[];
|
|
155
|
+
}
|
|
156
|
+
/** Performs an op. The same op, on the same document, anywhere. */
|
|
157
|
+
export declare function applyOp(defs: BpmnDefinitions, op: EditorOp): OpResult;
|
|
158
|
+
//# sourceMappingURL=ops.d.ts.map
|
package/dist/ops.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { applyAutoLayout } from "@bpmnkit/core";
|
|
2
|
+
import { createIdFactory } from "./id.js";
|
|
3
|
+
import { changeElementType, createAnnotation, createAnnotationWithLink, createBoundaryEvent, createConnection, createShape, deleteElements, insertEdgeWaypoint, insertShapeOnEdge, moveEdgeSegment, moveEdgeWaypoint, moveShapes, pasteElements, removeCollinearWaypoints, resizeShape, updateEdgeEndpoint, updateLabel, updateLabelPosition, updateShapeColor, } from "./modeling.js";
|
|
4
|
+
/** Performs an op. The same op, on the same document, anywhere. */
|
|
5
|
+
export function applyOp(defs, op) {
|
|
6
|
+
switch (op.kind) {
|
|
7
|
+
case "createShape": {
|
|
8
|
+
const r = createShape(defs, op.type, op.bounds, op.name, createIdFactory(op.seed));
|
|
9
|
+
// Dropping a shape onto a flow splices it into that flow.
|
|
10
|
+
const next = op.onEdge ? insertShapeOnEdge(r.defs, op.onEdge, r.id) : r.defs;
|
|
11
|
+
return { defs: next, created: [r.id] };
|
|
12
|
+
}
|
|
13
|
+
case "createBoundaryEvent": {
|
|
14
|
+
const r = createBoundaryEvent(defs, op.hostId, op.eventDefType, op.bounds, true, createIdFactory(op.seed));
|
|
15
|
+
return { defs: r.defs, created: [r.id] };
|
|
16
|
+
}
|
|
17
|
+
case "createAnnotation": {
|
|
18
|
+
const r = createAnnotation(defs, op.bounds, op.text, createIdFactory(op.seed));
|
|
19
|
+
return { defs: r.defs, created: [r.id] };
|
|
20
|
+
}
|
|
21
|
+
case "createAnnotationFor": {
|
|
22
|
+
const r = createAnnotationWithLink(defs, op.bounds, op.sourceId, op.sourceBounds, undefined, createIdFactory(op.seed));
|
|
23
|
+
return { defs: r.defs, created: [r.annotationId, r.associationId] };
|
|
24
|
+
}
|
|
25
|
+
case "createConnection": {
|
|
26
|
+
const r = createConnection(defs, op.sourceId, op.targetId, op.waypoints, createIdFactory(op.seed));
|
|
27
|
+
return { defs: r.defs, created: [r.id] };
|
|
28
|
+
}
|
|
29
|
+
case "createConnected": {
|
|
30
|
+
// One factory for both halves, so the pair of ids is one sequence.
|
|
31
|
+
const ids = createIdFactory(op.seed);
|
|
32
|
+
const shape = createShape(defs, op.type, op.bounds, op.name, ids);
|
|
33
|
+
const flow = createConnection(shape.defs, op.sourceId, shape.id, op.waypoints, ids);
|
|
34
|
+
return { defs: flow.defs, created: [shape.id, flow.id] };
|
|
35
|
+
}
|
|
36
|
+
case "paste": {
|
|
37
|
+
const r = pasteElements(defs, op.clipboard, op.offsetX, op.offsetY, createIdFactory(op.seed));
|
|
38
|
+
return { defs: r.defs, created: r.topLevelIds };
|
|
39
|
+
}
|
|
40
|
+
case "move": {
|
|
41
|
+
const moved = moveShapes(defs, op.moves);
|
|
42
|
+
const next = op.onEdge ? insertShapeOnEdge(moved, op.onEdge.edgeId, op.onEdge.shapeId) : moved;
|
|
43
|
+
return { defs: next, created: [] };
|
|
44
|
+
}
|
|
45
|
+
case "resize":
|
|
46
|
+
return { defs: resizeShape(defs, op.id, op.bounds), created: [] };
|
|
47
|
+
case "delete":
|
|
48
|
+
return { defs: deleteElements(defs, op.ids), created: [] };
|
|
49
|
+
case "rename":
|
|
50
|
+
return { defs: updateLabel(defs, op.id, op.name), created: [] };
|
|
51
|
+
case "labelPosition":
|
|
52
|
+
return { defs: updateLabelPosition(defs, op.id, op.bounds), created: [] };
|
|
53
|
+
case "color":
|
|
54
|
+
return { defs: updateShapeColor(defs, op.id, op.color), created: [] };
|
|
55
|
+
case "changeType":
|
|
56
|
+
return { defs: changeElementType(defs, op.id, op.type), created: [] };
|
|
57
|
+
case "reconnect":
|
|
58
|
+
return { defs: updateEdgeEndpoint(defs, op.edgeId, op.isStart, op.port), created: [] };
|
|
59
|
+
// Every waypoint edit tidies the route afterwards, so a drag that leaves
|
|
60
|
+
// three points in a line does not leave the middle one behind.
|
|
61
|
+
case "insertWaypoint":
|
|
62
|
+
return {
|
|
63
|
+
defs: removeCollinearWaypoints(insertEdgeWaypoint(defs, op.edgeId, op.segIdx, op.point), op.edgeId),
|
|
64
|
+
created: [],
|
|
65
|
+
};
|
|
66
|
+
case "moveWaypoint":
|
|
67
|
+
return {
|
|
68
|
+
defs: removeCollinearWaypoints(moveEdgeWaypoint(defs, op.edgeId, op.wpIdx, op.point), op.edgeId),
|
|
69
|
+
created: [],
|
|
70
|
+
};
|
|
71
|
+
case "moveSegment":
|
|
72
|
+
return {
|
|
73
|
+
defs: removeCollinearWaypoints(moveEdgeSegment(defs, op.edgeId, op.segIdx, op.isHoriz, op.delta), op.edgeId),
|
|
74
|
+
created: [],
|
|
75
|
+
};
|
|
76
|
+
case "autoLayout":
|
|
77
|
+
return { defs: applyAutoLayout(defs), created: [] };
|
|
78
|
+
case "snapshot":
|
|
79
|
+
return { defs: op.defs, created: [] };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=ops.js.map
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CanvasEvents, CanvasOptions } from "@bpmnkit/canvas";
|
|
2
2
|
import type { BpmnDefinitions } from "@bpmnkit/core";
|
|
3
3
|
import type { Translate } from "./i18n.js";
|
|
4
|
+
import type { EditorOp } from "./ops.js";
|
|
4
5
|
export type CreateShapeType = "startEvent" | "messageStartEvent" | "timerStartEvent" | "conditionalStartEvent" | "signalStartEvent" | "endEvent" | "messageEndEvent" | "escalationEndEvent" | "errorEndEvent" | "compensationEndEvent" | "signalEndEvent" | "terminateEndEvent" | "intermediateThrowEvent" | "intermediateCatchEvent" | "messageCatchEvent" | "messageThrowEvent" | "timerCatchEvent" | "escalationThrowEvent" | "conditionalCatchEvent" | "linkCatchEvent" | "linkThrowEvent" | "compensationThrowEvent" | "signalCatchEvent" | "signalThrowEvent" | "task" | "serviceTask" | "userTask" | "scriptTask" | "sendTask" | "receiveTask" | "businessRuleTask" | "manualTask" | "callActivity" | "subProcess" | "adHocSubProcess" | "transaction" | "exclusiveGateway" | "parallelGateway" | "inclusiveGateway" | "eventBasedGateway" | "complexGateway" | "textAnnotation";
|
|
5
6
|
/** Element types that support resize handles. */
|
|
6
7
|
export declare const RESIZABLE_TYPES: ReadonlySet<string>;
|
|
@@ -25,6 +26,21 @@ export type EditorOptions = CanvasOptions & {
|
|
|
25
26
|
};
|
|
26
27
|
export interface EditorEvents extends CanvasEvents {
|
|
27
28
|
"diagram:change": (defs: BpmnDefinitions) => void;
|
|
29
|
+
/**
|
|
30
|
+
* The same change, described rather than materialised.
|
|
31
|
+
*
|
|
32
|
+
* Fires immediately after `diagram:change` for every change to the document,
|
|
33
|
+
* undo and redo included — those arrive as a whole-document `snapshot` op,
|
|
34
|
+
* because the stack records states rather than inverses and a relay that
|
|
35
|
+
* never heard about an undo would be silently wrong from then on.
|
|
36
|
+
*
|
|
37
|
+
* The exception is `loadDefinitions`, which is the host replacing the
|
|
38
|
+
* document rather than the user changing it, and says nothing.
|
|
39
|
+
*
|
|
40
|
+
* A listener relaying edits to other people wants this event; a listener that
|
|
41
|
+
* just needs the current document wants `diagram:change`.
|
|
42
|
+
*/
|
|
43
|
+
"diagram:op": (op: EditorOp, defs: BpmnDefinitions) => void;
|
|
28
44
|
"editor:select": (ids: string[]) => void;
|
|
29
45
|
"editor:tool": (tool: Tool) => void;
|
|
30
46
|
"editor:drag": (dragging: boolean) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bpmnkit/editor",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
".": {
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
10
|
"import": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./headless": {
|
|
13
|
+
"types": "./dist/headless.d.ts",
|
|
14
|
+
"import": "./dist/headless.js"
|
|
11
15
|
}
|
|
12
16
|
},
|
|
13
17
|
"files": [
|
|
@@ -17,8 +21,8 @@
|
|
|
17
21
|
"dist/**/*.d.ts"
|
|
18
22
|
],
|
|
19
23
|
"dependencies": {
|
|
20
|
-
"@bpmnkit/canvas": "0.1
|
|
21
|
-
"@bpmnkit/core": "0.
|
|
24
|
+
"@bpmnkit/canvas": "0.2.1",
|
|
25
|
+
"@bpmnkit/core": "0.5.0"
|
|
22
26
|
},
|
|
23
27
|
"description": "Full-featured interactive BPMN editor with undo/redo, HUD, and side-dock UI",
|
|
24
28
|
"keywords": [
|