@bpmnkit/editor 0.0.35 → 0.2.0
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/chrome.d.ts +24 -0
- package/dist/chrome.js +75 -0
- package/dist/css.d.ts +5 -4
- package/dist/css.js +231 -357
- package/dist/dock.js +58 -80
- package/dist/editor.d.ts +30 -2
- package/dist/editor.js +123 -84
- 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 +6 -0
- package/dist/index.js +4 -0
- package/dist/inject.d.ts +3 -0
- package/dist/inject.js +12 -0
- package/dist/modal.js +33 -53
- 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/dist/dock.js
CHANGED
|
@@ -1,131 +1,109 @@
|
|
|
1
|
+
import { injectChromeStyles } from "./chrome.js";
|
|
1
2
|
const DOCK_STYLE_ID = "bpmnkit-side-dock-styles-v1";
|
|
2
3
|
const STORAGE_KEY_WIDTH = "bpmnkit-side-dock-width";
|
|
3
4
|
const STORAGE_KEY_COLLAPSED = "bpmnkit-side-dock-collapsed";
|
|
4
5
|
const MIN_WIDTH = 280;
|
|
5
6
|
const MAX_WIDTH = 700;
|
|
6
|
-
const DEFAULT_WIDTH =
|
|
7
|
+
const DEFAULT_WIDTH = 340;
|
|
7
8
|
const DOCK_CSS = `
|
|
8
|
-
/* ── Side
|
|
9
|
-
|
|
9
|
+
/* ── Side dock ───────────────────────────────────────────────────────────
|
|
10
|
+
The properties panel, in the bpmnkit.com design system: flat, square,
|
|
11
|
+
hairline-bounded, mono uppercase labels. Grounds follow the HUD theme
|
|
12
|
+
variables declared in css.ts, so the two read as one piece of chrome.
|
|
13
|
+
──────────────────────────────────────────────────────────────────────── */
|
|
10
14
|
.bpmnkit-side-dock {
|
|
11
15
|
position: fixed; right: 0; top: 36px; bottom: 0;
|
|
12
16
|
z-index: 9999; display: flex; flex-direction: column;
|
|
13
|
-
background:
|
|
14
|
-
border-left: 1px solid rgba(255,255,255,0.
|
|
15
|
-
|
|
16
|
-
font-family: system-ui, -apple-system, sans-serif;
|
|
17
|
+
background: var(--bpmnkit-chrome-ground, #ffffff);
|
|
18
|
+
border-left: 1px solid var(--bpmnkit-chrome-line, rgba(255, 255, 255, 0.14));
|
|
19
|
+
font-family: var(--bpmnkit-ds-font-sans, system-ui, -apple-system, sans-serif);
|
|
17
20
|
transition: width 0.22s ease;
|
|
18
21
|
}
|
|
19
|
-
/*
|
|
22
|
+
/* Square handle — sticks out from the left edge; always visible + clickable */
|
|
20
23
|
.bpmnkit-side-dock__collapse-handle {
|
|
21
24
|
position: absolute; left: -20px; top: 50%; transform: translateY(-50%);
|
|
22
25
|
width: 20px; height: 52px;
|
|
23
|
-
background:
|
|
24
|
-
border: 1px solid rgba(255,255,255,0.
|
|
25
|
-
border-radius: 8px 0 0 8px;
|
|
26
|
+
background: var(--bpmnkit-chrome-ground, #ffffff);
|
|
27
|
+
border: 1px solid var(--bpmnkit-chrome-line, rgba(255, 255, 255, 0.14)); border-right: none;
|
|
26
28
|
display: flex; align-items: center; justify-content: center;
|
|
27
29
|
cursor: pointer; z-index: 1;
|
|
28
|
-
color:
|
|
29
|
-
|
|
30
|
+
color: var(--bpmnkit-chrome-ink-4, #9aa1aa);
|
|
31
|
+
font-family: var(--bpmnkit-ds-font-mono, ui-monospace, monospace);
|
|
32
|
+
font-size: 13px; line-height: 1;
|
|
33
|
+
font-variant-emoji: text;
|
|
30
34
|
user-select: none;
|
|
31
35
|
}
|
|
32
|
-
.bpmnkit-side-dock__collapse-handle:hover { color: #
|
|
36
|
+
.bpmnkit-side-dock__collapse-handle:hover { color: var(--bpmnkit-chrome-ink, #f4f5f7); background: var(--bpmnkit-chrome-hover, rgba(255,255,255,0.07)); }
|
|
33
37
|
.bpmnkit-side-dock__resize-handle {
|
|
34
38
|
position: absolute; left: 0; top: 0; bottom: 0;
|
|
35
39
|
width: 5px; cursor: ew-resize; z-index: 2;
|
|
36
40
|
}
|
|
37
|
-
.bpmnkit-side-dock__resize-handle:hover { background:
|
|
41
|
+
.bpmnkit-side-dock__resize-handle:hover { background: var(--bpmnkit-chrome-accent, #c9755c); }
|
|
42
|
+
|
|
43
|
+
/* ── Tabs — equal-width row, the underline sits on the row's own rule ── */
|
|
38
44
|
.bpmnkit-side-dock__tab-strip {
|
|
39
|
-
display: flex; align-items:
|
|
40
|
-
border-bottom: 1px solid rgba(255,255,255,0.
|
|
45
|
+
display: flex; align-items: stretch; height: 34px; flex-shrink: 0;
|
|
46
|
+
border-bottom: 1px solid var(--bpmnkit-chrome-line, rgba(255, 255, 255, 0.14));
|
|
47
|
+
overflow-x: auto; scrollbar-width: none;
|
|
41
48
|
}
|
|
49
|
+
.bpmnkit-side-dock__tab-strip::-webkit-scrollbar { display: none; }
|
|
50
|
+
/* Equal-width while the labels fit; the strip scrolls rather than clipping them. */
|
|
42
51
|
.bpmnkit-side-dock__tab {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
font-family:
|
|
52
|
+
flex: 1 0 auto;
|
|
53
|
+
padding: 0 10px; height: 100%; background: none; border: none;
|
|
54
|
+
border-bottom: 2px solid transparent; color: var(--bpmnkit-chrome-ink-4, #9aa1aa);
|
|
55
|
+
cursor: pointer; white-space: nowrap;
|
|
56
|
+
font-family: var(--bpmnkit-ds-font-mono, ui-monospace, monospace);
|
|
57
|
+
font-size: var(--bpmnkit-ds-t-mono-micro, 10.5px);
|
|
58
|
+
letter-spacing: 0.08em; text-transform: uppercase;
|
|
59
|
+
}
|
|
60
|
+
.bpmnkit-side-dock__tab:hover { color: var(--bpmnkit-chrome-ink-2, #c8ccd2); }
|
|
61
|
+
.bpmnkit-side-dock__tab.active {
|
|
62
|
+
color: var(--bpmnkit-chrome-ink, #f4f5f7);
|
|
63
|
+
border-bottom-color: var(--bpmnkit-chrome-accent, #c9755c);
|
|
64
|
+
margin-bottom: -1px;
|
|
48
65
|
}
|
|
49
|
-
.bpmnkit-side-dock__tab:
|
|
50
|
-
.bpmnkit-side-dock__tab.active { color: #4c8ef7; border-bottom-color: #4c8ef7; }
|
|
66
|
+
.bpmnkit-side-dock__tab:disabled { opacity: 0.3; cursor: default; }
|
|
51
67
|
.bpmnkit-side-dock__pane {
|
|
52
68
|
flex: 1; display: flex; flex-direction: column; overflow: hidden; min-height: 0;
|
|
53
69
|
}
|
|
54
70
|
.bpmnkit-side-dock__pane--hidden { display: none; }
|
|
55
|
-
|
|
71
|
+
|
|
72
|
+
/* ── Empty state — info rows + hint, shown when nothing is selected ── */
|
|
56
73
|
.bpmnkit-side-dock__empty {
|
|
57
74
|
flex: 1; display: flex; flex-direction: column;
|
|
58
|
-
overflow-y: auto; padding:
|
|
59
|
-
gap: 0;
|
|
75
|
+
overflow-y: auto; padding: 0 20px;
|
|
60
76
|
}
|
|
61
77
|
.bpmnkit-side-dock__info-row {
|
|
62
78
|
display: flex; flex-direction: column;
|
|
63
|
-
padding:
|
|
64
|
-
border-bottom: 1px solid rgba(255,255,255,0.
|
|
65
|
-
gap:
|
|
79
|
+
padding: 14px 0;
|
|
80
|
+
border-bottom: 1px solid var(--bpmnkit-chrome-line-soft, rgba(255, 255, 255, 0.08));
|
|
81
|
+
gap: 7px;
|
|
66
82
|
}
|
|
67
83
|
.bpmnkit-side-dock__info-label {
|
|
68
|
-
font-
|
|
69
|
-
|
|
84
|
+
font-family: var(--bpmnkit-ds-font-mono, ui-monospace, monospace);
|
|
85
|
+
font-size: var(--bpmnkit-ds-t-mono-micro, 10.5px); letter-spacing: 0.12em;
|
|
86
|
+
text-transform: uppercase; color: var(--bpmnkit-chrome-ink-4, #9aa1aa);
|
|
70
87
|
}
|
|
71
88
|
.bpmnkit-side-dock__info-value {
|
|
72
|
-
font-
|
|
89
|
+
font-family: var(--bpmnkit-ds-font-mono, ui-monospace, monospace);
|
|
90
|
+
font-size: 12.5px; color: var(--bpmnkit-chrome-ink-2, #c8ccd2); word-break: break-word;
|
|
73
91
|
}
|
|
92
|
+
/* A sentence, not a label — --ink-4 does not clear 4.5:1 on a light ground. */
|
|
74
93
|
.bpmnkit-side-dock__empty-hint {
|
|
75
|
-
font-
|
|
76
|
-
|
|
94
|
+
font-family: var(--bpmnkit-ds-font-mono, ui-monospace, monospace);
|
|
95
|
+
font-size: var(--bpmnkit-ds-t-mono-label, 11.5px); letter-spacing: 0.04em;
|
|
96
|
+
color: var(--bpmnkit-chrome-ink-2, #c8ccd2);
|
|
97
|
+
padding: 24px 0;
|
|
77
98
|
}
|
|
78
|
-
/* Collapsed state — only the
|
|
99
|
+
/* Collapsed state — only the handle remains visible */
|
|
79
100
|
.bpmnkit-side-dock--collapsed .bpmnkit-side-dock__tab-strip,
|
|
80
101
|
.bpmnkit-side-dock--collapsed .bpmnkit-side-dock__pane { display: none; }
|
|
81
102
|
/* Push watermark left of the dock using a CSS variable updated by JS */
|
|
82
103
|
.bpmnkit-watermark { right: calc(var(--bpmnkit-dock-width, 0px) + 8px) !important; }
|
|
83
|
-
/* Light theme */
|
|
84
|
-
[data-bpmnkit-hud-theme="light"] .bpmnkit-side-dock {
|
|
85
|
-
background: rgba(248,248,252,0.99); border-left-color: rgba(0,0,0,0.08);
|
|
86
|
-
box-shadow: -8px 0 40px rgba(0,0,0,0.12);
|
|
87
|
-
}
|
|
88
|
-
[data-bpmnkit-hud-theme="light"] .bpmnkit-side-dock__collapse-handle {
|
|
89
|
-
background: rgba(248,248,252,0.99); border-color: rgba(0,0,0,0.08); color: rgba(0,0,0,0.4);
|
|
90
|
-
}
|
|
91
|
-
[data-bpmnkit-hud-theme="light"] .bpmnkit-side-dock__collapse-handle:hover {
|
|
92
|
-
background: rgba(235,235,242,0.99); color: rgba(0,0,0,0.9);
|
|
93
|
-
}
|
|
94
|
-
[data-bpmnkit-hud-theme="light"] .bpmnkit-side-dock__tab-strip { border-bottom-color: rgba(0,0,0,0.07); }
|
|
95
|
-
[data-bpmnkit-hud-theme="light"] .bpmnkit-side-dock__tab { color: rgba(0,0,0,0.4); }
|
|
96
|
-
[data-bpmnkit-hud-theme="light"] .bpmnkit-side-dock__tab:hover { color: rgba(0,0,0,0.7); }
|
|
97
|
-
[data-bpmnkit-hud-theme="light"] .bpmnkit-side-dock__tab.active { color: #1a56db; border-bottom-color: #1a56db; }
|
|
98
|
-
[data-bpmnkit-hud-theme="light"] .bpmnkit-side-dock__resize-handle:hover { background: rgba(26,86,219,0.2); }
|
|
99
|
-
[data-bpmnkit-hud-theme="light"] .bpmnkit-side-dock__info-label { color: rgba(0,0,0,0.3); }
|
|
100
|
-
[data-bpmnkit-hud-theme="light"] .bpmnkit-side-dock__info-value { color: rgba(0,0,0,0.75); }
|
|
101
|
-
[data-bpmnkit-hud-theme="light"] .bpmnkit-side-dock__info-row { border-bottom-color: rgba(0,0,0,0.06); }
|
|
102
|
-
[data-bpmnkit-hud-theme="light"] .bpmnkit-side-dock__empty-hint { color: rgba(0,0,0,0.25); }
|
|
103
|
-
/* Neon theme */
|
|
104
|
-
[data-bpmnkit-hud-theme="neon"] .bpmnkit-side-dock {
|
|
105
|
-
background: oklch(6% 0.04 280);
|
|
106
|
-
border-left: 1px solid oklch(65% 0.28 280 / 0.35);
|
|
107
|
-
box-shadow: -8px 0 40px oklch(0% 0 0 / 0.8), -1px 0 20px oklch(65% 0.28 280 / 0.12);
|
|
108
|
-
}
|
|
109
|
-
[data-bpmnkit-hud-theme="neon"] .bpmnkit-side-dock__collapse-handle {
|
|
110
|
-
background: oklch(6% 0.04 280);
|
|
111
|
-
border-color: oklch(65% 0.28 280 / 0.35);
|
|
112
|
-
color: oklch(65% 0.28 280 / 0.6);
|
|
113
|
-
}
|
|
114
|
-
[data-bpmnkit-hud-theme="neon"] .bpmnkit-side-dock__collapse-handle:hover {
|
|
115
|
-
background: oklch(12% 0.04 270 / 0.99);
|
|
116
|
-
color: oklch(73% 0.16 280);
|
|
117
|
-
}
|
|
118
|
-
[data-bpmnkit-hud-theme="neon"] .bpmnkit-side-dock__tab-strip { border-bottom-color: oklch(65% 0.28 280 / 0.25); }
|
|
119
|
-
[data-bpmnkit-hud-theme="neon"] .bpmnkit-side-dock__tab { color: oklch(50% 0.1 280); }
|
|
120
|
-
[data-bpmnkit-hud-theme="neon"] .bpmnkit-side-dock__tab:hover { color: oklch(73% 0.16 280); }
|
|
121
|
-
[data-bpmnkit-hud-theme="neon"] .bpmnkit-side-dock__tab.active { color: oklch(72% 0.18 185); border-bottom-color: oklch(72% 0.18 185); }
|
|
122
|
-
[data-bpmnkit-hud-theme="neon"] .bpmnkit-side-dock__resize-handle:hover { background: oklch(65% 0.28 280 / 0.25); }
|
|
123
|
-
[data-bpmnkit-hud-theme="neon"] .bpmnkit-side-dock__info-label { color: oklch(50% 0.06 280); }
|
|
124
|
-
[data-bpmnkit-hud-theme="neon"] .bpmnkit-side-dock__info-value { color: oklch(73% 0.16 280); }
|
|
125
|
-
[data-bpmnkit-hud-theme="neon"] .bpmnkit-side-dock__info-row { border-bottom-color: oklch(65% 0.28 280 / 0.1); }
|
|
126
|
-
[data-bpmnkit-hud-theme="neon"] .bpmnkit-side-dock__empty-hint { color: oklch(45% 0.08 280); }
|
|
127
104
|
`;
|
|
128
105
|
function injectDockStyles() {
|
|
106
|
+
injectChromeStyles();
|
|
129
107
|
if (document.getElementById(DOCK_STYLE_ID))
|
|
130
108
|
return;
|
|
131
109
|
const style = document.createElement("style");
|
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";
|
|
@@ -295,8 +297,10 @@ export class BpmnEditor {
|
|
|
295
297
|
injectEditorStyles();
|
|
296
298
|
this._id = String(_instanceCounter++);
|
|
297
299
|
this._t = options.translate ?? defaultTranslate;
|
|
298
|
-
// Resolve initial theme — localStorage overrides the options.theme when persistTheme is on
|
|
299
|
-
|
|
300
|
+
// Resolve initial theme — localStorage overrides the options.theme when persistTheme is on.
|
|
301
|
+
// The shell's default is the design system's light `--canvas` ground; `neon` stays a
|
|
302
|
+
// white-label theme a host opts into.
|
|
303
|
+
let initialTheme = options.theme ?? "light";
|
|
300
304
|
if (options.persistTheme) {
|
|
301
305
|
try {
|
|
302
306
|
const stored = localStorage.getItem("bpmnkit-theme");
|
|
@@ -374,7 +378,7 @@ export class BpmnEditor {
|
|
|
374
378
|
previewResize: (bounds) => this._overlay.setResizePreview(bounds),
|
|
375
379
|
commitResize: (id, bounds) => {
|
|
376
380
|
this._overlay.setResizePreview(null);
|
|
377
|
-
this.
|
|
381
|
+
this._executeOp({ kind: "resize", id, bounds }, { label: "Resize" });
|
|
378
382
|
},
|
|
379
383
|
previewConnect: (ghostEnd, targetId) => {
|
|
380
384
|
const src = this._connectSourceBounds();
|
|
@@ -399,7 +403,7 @@ export class BpmnEditor {
|
|
|
399
403
|
startLabelEdit: (id) => this._startLabelEdit(id),
|
|
400
404
|
setHovered: (id) => this._overlay.setHovered(id, this._shapes),
|
|
401
405
|
executeDelete: (ids) => {
|
|
402
|
-
this.
|
|
406
|
+
this._executeOp({ kind: "delete", ids }, { label: "Delete" });
|
|
403
407
|
this._setSelection([]);
|
|
404
408
|
},
|
|
405
409
|
executeCopy: () => this._doCopy(),
|
|
@@ -429,7 +433,7 @@ export class BpmnEditor {
|
|
|
429
433
|
this._overlay.setEndpointDragGhost(null);
|
|
430
434
|
this._overlay.setAlignmentGuides([]);
|
|
431
435
|
const snap = this._snapWaypoint(pt);
|
|
432
|
-
this.
|
|
436
|
+
this._executeOp({ kind: "insertWaypoint", edgeId, segIdx, point: snap.pt }, { label: "Add waypoint" });
|
|
433
437
|
},
|
|
434
438
|
cancelWaypointInsert: () => {
|
|
435
439
|
this._overlay.setEndpointDragGhost(null);
|
|
@@ -448,7 +452,7 @@ export class BpmnEditor {
|
|
|
448
452
|
this._overlay.setEndpointDragGhost(null);
|
|
449
453
|
this._overlay.setAlignmentGuides([]);
|
|
450
454
|
const snap = this._snapWaypoint(pt);
|
|
451
|
-
this.
|
|
455
|
+
this._executeOp({ kind: "moveWaypoint", edgeId, wpIdx, point: snap.pt }, { label: "Move waypoint" });
|
|
452
456
|
},
|
|
453
457
|
cancelWaypointMove: () => {
|
|
454
458
|
this._overlay.setEndpointDragGhost(null);
|
|
@@ -463,7 +467,7 @@ export class BpmnEditor {
|
|
|
463
467
|
},
|
|
464
468
|
commitSegmentMove: (edgeId, segIdx, isHoriz, delta) => {
|
|
465
469
|
this._overlay.setEndpointDragGhost(null);
|
|
466
|
-
this.
|
|
470
|
+
this._executeOp({ kind: "moveSegment", edgeId, segIdx, isHoriz, delta }, { label: "Move segment" });
|
|
467
471
|
},
|
|
468
472
|
cancelSegmentMove: () => {
|
|
469
473
|
this._overlay.setEndpointDragGhost(null);
|
|
@@ -489,7 +493,7 @@ export class BpmnEditor {
|
|
|
489
493
|
this._stateMachine = new EditorStateMachine(callbacks);
|
|
490
494
|
// ── Label editor ─────────────────────────────────────────────
|
|
491
495
|
this._labelEditor = new LabelEditor(this._host, (id, text) => {
|
|
492
|
-
this.
|
|
496
|
+
this._executeOp({ kind: "rename", id, name: text }, { label: "Rename", coalesceKey: `label:${id}` });
|
|
493
497
|
this._stateMachine.setMode({ mode: "select", sub: { name: "idle", hoveredId: null } });
|
|
494
498
|
}, () => {
|
|
495
499
|
this._stateMachine.setMode({ mode: "select", sub: { name: "idle", hoveredId: null } });
|
|
@@ -550,7 +554,7 @@ export class BpmnEditor {
|
|
|
550
554
|
* The operation is undoable.
|
|
551
555
|
*/
|
|
552
556
|
autoLayout() {
|
|
553
|
-
this.
|
|
557
|
+
this._executeOp({ kind: "autoLayout" }, { label: "Auto-layout" });
|
|
554
558
|
this.fitView();
|
|
555
559
|
}
|
|
556
560
|
loadDefinitions(defs) {
|
|
@@ -623,7 +627,7 @@ export class BpmnEditor {
|
|
|
623
627
|
if (this._selectedIds.length === 0)
|
|
624
628
|
return;
|
|
625
629
|
const ids = [...this._selectedIds];
|
|
626
|
-
this.
|
|
630
|
+
this._executeOp({ kind: "delete", ids }, { label: "Delete" });
|
|
627
631
|
this._setSelection([]);
|
|
628
632
|
}
|
|
629
633
|
/** Bounds of the currently-selected shapes (edges have none, and are skipped). */
|
|
@@ -676,7 +680,7 @@ export class BpmnEditor {
|
|
|
676
680
|
moves.push({ id: b.id, dx, dy });
|
|
677
681
|
}
|
|
678
682
|
if (moves.length > 0)
|
|
679
|
-
this.
|
|
683
|
+
this._executeOp({ kind: "move", moves }, { label: "Align" });
|
|
680
684
|
}
|
|
681
685
|
/**
|
|
682
686
|
* Distributes the selected shapes so the gaps between them are equal along
|
|
@@ -711,7 +715,7 @@ export class BpmnEditor {
|
|
|
711
715
|
cursor += size(b) + gap;
|
|
712
716
|
}
|
|
713
717
|
if (moves.length > 0)
|
|
714
|
-
this.
|
|
718
|
+
this._executeOp({ kind: "move", moves }, { label: "Distribute" });
|
|
715
719
|
}
|
|
716
720
|
/**
|
|
717
721
|
* Searches the model (recursively, including sub-process children) for
|
|
@@ -753,17 +757,27 @@ export class BpmnEditor {
|
|
|
753
757
|
}
|
|
754
758
|
undo() {
|
|
755
759
|
const prev = this._commandStack.undo();
|
|
756
|
-
if (prev)
|
|
757
|
-
this.
|
|
758
|
-
this._emit("diagram:change", prev);
|
|
759
|
-
}
|
|
760
|
+
if (prev)
|
|
761
|
+
this._stepTo(prev);
|
|
760
762
|
}
|
|
761
763
|
redo() {
|
|
762
764
|
const next = this._commandStack.redo();
|
|
763
|
-
if (next)
|
|
764
|
-
this.
|
|
765
|
-
|
|
766
|
-
|
|
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);
|
|
767
781
|
}
|
|
768
782
|
canUndo() {
|
|
769
783
|
return this._commandStack.canUndo();
|
|
@@ -775,7 +789,23 @@ export class BpmnEditor {
|
|
|
775
789
|
return this._defs;
|
|
776
790
|
}
|
|
777
791
|
applyChange(fn) {
|
|
778
|
-
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);
|
|
779
809
|
}
|
|
780
810
|
fitView(padding = 40) {
|
|
781
811
|
if (!this._defs)
|
|
@@ -986,15 +1016,31 @@ export class BpmnEditor {
|
|
|
986
1016
|
}
|
|
987
1017
|
this._emit("diagram:load", defs, { missingShapes: [], missingEdges: [] });
|
|
988
1018
|
}
|
|
989
|
-
|
|
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 = {}) {
|
|
990
1028
|
if (this._readOnly || !this._defs)
|
|
991
|
-
return;
|
|
992
|
-
const
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
if (
|
|
996
|
-
this.
|
|
997
|
-
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;
|
|
998
1044
|
}
|
|
999
1045
|
/** Label of the change `undo()` would revert (for HUD tooltips), or null. */
|
|
1000
1046
|
getUndoLabel() {
|
|
@@ -1101,10 +1147,10 @@ export class BpmnEditor {
|
|
|
1101
1147
|
const moves = this._selectedIds.map((id) => ({ id, dx: snap.dx, dy: snap.dy }));
|
|
1102
1148
|
const shapeId = this._selectedIds.length === 1 ? this._selectedIds[0] : undefined;
|
|
1103
1149
|
if (edgeDropId && shapeId) {
|
|
1104
|
-
this.
|
|
1150
|
+
this._executeOp({ kind: "move", moves, onEdge: { edgeId: edgeDropId, shapeId } }, { label: "Insert on flow" });
|
|
1105
1151
|
}
|
|
1106
1152
|
else {
|
|
1107
|
-
this.
|
|
1153
|
+
this._executeOp({ kind: "move", moves }, { label: "Move" });
|
|
1108
1154
|
}
|
|
1109
1155
|
if (this._isDragging) {
|
|
1110
1156
|
this._isDragging = false;
|
|
@@ -1179,7 +1225,7 @@ export class BpmnEditor {
|
|
|
1179
1225
|
}
|
|
1180
1226
|
}
|
|
1181
1227
|
if (moves.length > 0) {
|
|
1182
|
-
this.
|
|
1228
|
+
this._executeOp({ kind: "move", moves }, { label: "Move" });
|
|
1183
1229
|
}
|
|
1184
1230
|
}
|
|
1185
1231
|
_cancelSpace() {
|
|
@@ -1201,13 +1247,9 @@ export class BpmnEditor {
|
|
|
1201
1247
|
return;
|
|
1202
1248
|
const bounds = defaultBounds(type, actualCenter.x, actualCenter.y);
|
|
1203
1249
|
if (type === "textAnnotation") {
|
|
1204
|
-
const
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
this._renderDefs(result.defs);
|
|
1208
|
-
this._emit("diagram:change", result.defs);
|
|
1209
|
-
this._emit("editor:select", [result.id]);
|
|
1210
|
-
this._startLabelEdit(result.id);
|
|
1250
|
+
const [id] = this._executeOp({ kind: "createAnnotation", bounds, seed: newIdSeed() }, { select: "first" });
|
|
1251
|
+
if (id)
|
|
1252
|
+
this._startLabelEdit(id);
|
|
1211
1253
|
return;
|
|
1212
1254
|
}
|
|
1213
1255
|
// If hovering over an activity, create a boundary event
|
|
@@ -1221,24 +1263,23 @@ export class BpmnEditor {
|
|
|
1221
1263
|
const eventBounds = snapToBoundary(actualCenter, hostBounds, 18);
|
|
1222
1264
|
// Map palette type to event definition type
|
|
1223
1265
|
const eventDefType = intermediateEventDefType(type);
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1266
|
+
this._executeOp({
|
|
1267
|
+
kind: "createBoundaryEvent",
|
|
1268
|
+
hostId: boundaryHostId,
|
|
1269
|
+
eventDefType,
|
|
1270
|
+
bounds: eventBounds,
|
|
1271
|
+
seed: newIdSeed(),
|
|
1272
|
+
}, { select: "first" });
|
|
1230
1273
|
return;
|
|
1231
1274
|
}
|
|
1232
1275
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
:
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
this._emit("diagram:change", finalDefs);
|
|
1241
|
-
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" });
|
|
1242
1283
|
}
|
|
1243
1284
|
_doConnect(srcId, tgtId) {
|
|
1244
1285
|
const srcShape = this._shapeById.get(srcId);
|
|
@@ -1253,7 +1294,7 @@ export class BpmnEditor {
|
|
|
1253
1294
|
.filter((s) => s.id !== srcId && s.id !== tgtId)
|
|
1254
1295
|
.map((s) => s.shape.bounds);
|
|
1255
1296
|
const waypoints = computeWaypointsAvoiding(srcShape.shape.bounds, tgtShape.shape.bounds, obstacles);
|
|
1256
|
-
this.
|
|
1297
|
+
this._executeOp({ kind: "createConnection", sourceId: srcId, targetId: tgtId, waypoints, seed: newIdSeed() }, { label: "Connect" });
|
|
1257
1298
|
}
|
|
1258
1299
|
_doCopy() {
|
|
1259
1300
|
if (!this._defs || this._selectedIds.length === 0)
|
|
@@ -1263,20 +1304,14 @@ export class BpmnEditor {
|
|
|
1263
1304
|
_doPaste() {
|
|
1264
1305
|
if (!this._clipboard)
|
|
1265
1306
|
return;
|
|
1266
|
-
|
|
1267
|
-
const result = pasteElements(base, this._clipboard, 20, 20);
|
|
1268
|
-
this._selectedIds = result.topLevelIds;
|
|
1269
|
-
this._commandStack.push(result.defs, "Paste");
|
|
1270
|
-
this._renderDefs(result.defs);
|
|
1271
|
-
this._emit("diagram:change", result.defs);
|
|
1272
|
-
this._emit("editor:select", result.topLevelIds);
|
|
1307
|
+
this._executeOp({ kind: "paste", clipboard: this._clipboard, offsetX: 20, offsetY: 20, seed: newIdSeed() }, { label: "Paste", select: "all" });
|
|
1273
1308
|
}
|
|
1274
1309
|
_doCut() {
|
|
1275
1310
|
if (!this._defs || this._selectedIds.length === 0)
|
|
1276
1311
|
return;
|
|
1277
1312
|
this._doCopy();
|
|
1278
1313
|
const ids = [...this._selectedIds];
|
|
1279
|
-
this.
|
|
1314
|
+
this._executeOp({ kind: "delete", ids }, { label: "Cut" });
|
|
1280
1315
|
this._setSelection([]);
|
|
1281
1316
|
}
|
|
1282
1317
|
_startLabelEdit(id) {
|
|
@@ -1388,15 +1423,17 @@ export class BpmnEditor {
|
|
|
1388
1423
|
}
|
|
1389
1424
|
const newBounds = this._smartPlaceBounds(srcBounds, sourceId, w, h);
|
|
1390
1425
|
const obstacles = this._shapes.filter((s) => s.id !== sourceId).map((s) => s.shape.bounds);
|
|
1391
|
-
const r1 = createShape(this._defs, type, newBounds, name);
|
|
1392
1426
|
const waypoints = computeWaypointsAvoiding(srcBounds, newBounds, obstacles);
|
|
1393
|
-
const
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
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;
|
|
1400
1437
|
}
|
|
1401
1438
|
_smartPlaceBounds(srcBounds, sourceId, w, h) {
|
|
1402
1439
|
const GAP = 60;
|
|
@@ -1493,7 +1530,7 @@ export class BpmnEditor {
|
|
|
1493
1530
|
if (!shape)
|
|
1494
1531
|
return;
|
|
1495
1532
|
const labelBounds = labelBoundsForPosition(shape.shape.bounds, position);
|
|
1496
|
-
this.
|
|
1533
|
+
this._executeOp({ kind: "labelPosition", id: shapeId, bounds: labelBounds }, { label: "Move label" });
|
|
1497
1534
|
}
|
|
1498
1535
|
/** Starts inline label editing for the element with the given id. */
|
|
1499
1536
|
editLabel(id) {
|
|
@@ -1535,17 +1572,19 @@ export class BpmnEditor {
|
|
|
1535
1572
|
width: annW,
|
|
1536
1573
|
height: annH,
|
|
1537
1574
|
};
|
|
1538
|
-
const
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
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);
|
|
1545
1584
|
}
|
|
1546
1585
|
/** Updates the color of a shape in the diagram. Pass `{}` to clear colors. */
|
|
1547
1586
|
updateColor(id, color) {
|
|
1548
|
-
this.
|
|
1587
|
+
this._executeOp({ kind: "color", id, color }, { label: "Change colour", coalesceKey: `color:${id}` });
|
|
1549
1588
|
}
|
|
1550
1589
|
// ── Private helpers ────────────────────────────────────────────────
|
|
1551
1590
|
_setEdgeSelected(edgeId) {
|
|
@@ -1570,7 +1609,7 @@ export class BpmnEditor {
|
|
|
1570
1609
|
const current = this._shapeById.get(id)?.flowElement?.type;
|
|
1571
1610
|
if (current && !canMorph(current, newType))
|
|
1572
1611
|
return;
|
|
1573
|
-
this.
|
|
1612
|
+
this._executeOp({ kind: "changeType", id, type: newType }, { label: "Change type" });
|
|
1574
1613
|
}
|
|
1575
1614
|
_findEdgeDropTarget(dx, dy) {
|
|
1576
1615
|
if (this._selectedIds.length !== 1)
|
|
@@ -1675,7 +1714,7 @@ export class BpmnEditor {
|
|
|
1675
1714
|
const newPort = isStart
|
|
1676
1715
|
? closestPort(diagPoint, srcDi.bounds)
|
|
1677
1716
|
: closestPort(diagPoint, tgtDi.bounds);
|
|
1678
|
-
this.
|
|
1717
|
+
this._executeOp({ kind: "reconnect", edgeId, isStart, port: newPort }, { label: "Reconnect" });
|
|
1679
1718
|
}
|
|
1680
1719
|
_isResizable(id) {
|
|
1681
1720
|
const shape = this._shapeById.get(id);
|