@bpmnkit/editor 0.0.30 → 0.0.32
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/command-stack.d.ts +20 -5
- package/dist/command-stack.js +42 -15
- package/dist/css.d.ts +1 -1
- package/dist/css.js +40 -0
- package/dist/editor.d.ts +60 -0
- package/dist/editor.js +404 -55
- package/dist/hud.js +114 -41
- package/dist/i18n.d.ts +14 -0
- package/dist/i18n.js +11 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/modeling.d.ts +1 -0
- package/dist/modeling.js +143 -33
- package/dist/overlay.d.ts +1 -1
- package/dist/overlay.js +2 -2
- package/dist/rules.d.ts +19 -5
- package/dist/rules.js +101 -5
- package/dist/state-machine.d.ts +11 -1
- package/dist/state-machine.js +41 -9
- package/dist/types.d.ts +11 -0
- package/package.json +3 -3
package/dist/hud.js
CHANGED
|
@@ -57,6 +57,7 @@ const SHORTCUTS = [
|
|
|
57
57
|
["Delete / Backspace", "Delete selected"],
|
|
58
58
|
["Ctrl+A", "Select all"],
|
|
59
59
|
["Ctrl+C", "Copy"],
|
|
60
|
+
["Ctrl+X", "Cut"],
|
|
60
61
|
["Ctrl+V", "Paste"],
|
|
61
62
|
["Ctrl+F", "Find element"],
|
|
62
63
|
["Ctrl+K", "Command palette"],
|
|
@@ -69,6 +70,7 @@ const SHORTCUTS = [
|
|
|
69
70
|
];
|
|
70
71
|
export function initEditorHud(editor, options = {}) {
|
|
71
72
|
injectHudStyles();
|
|
73
|
+
const t = editor.translate;
|
|
72
74
|
// ── Create and inject HUD DOM ──────────────────────────────────────────────
|
|
73
75
|
function hudBtn(id, title) {
|
|
74
76
|
const b = document.createElement("button");
|
|
@@ -176,10 +178,10 @@ export function initEditorHud(editor, options = {}) {
|
|
|
176
178
|
simBannerEl.id = "bpmnkit-sim-banner";
|
|
177
179
|
simBannerEl.className = "hidden";
|
|
178
180
|
const simBannerText = document.createElement("span");
|
|
179
|
-
simBannerText.textContent = "Simulation active — editing is disabled";
|
|
181
|
+
simBannerText.textContent = t("Simulation active — editing is disabled");
|
|
180
182
|
const simBannerExit = document.createElement("button");
|
|
181
183
|
simBannerExit.id = "bpmnkit-sim-banner-exit";
|
|
182
|
-
simBannerExit.textContent = "Exit simulation";
|
|
184
|
+
simBannerExit.textContent = t("Exit simulation");
|
|
183
185
|
simBannerExit.addEventListener("click", () => options.onExitSimulation?.());
|
|
184
186
|
simBannerEl.append(simBannerText, simBannerExit);
|
|
185
187
|
// Right-click context menu
|
|
@@ -193,14 +195,14 @@ export function initEditorHud(editor, options = {}) {
|
|
|
193
195
|
const searchInput = document.createElement("input");
|
|
194
196
|
searchInput.id = "bpmnkit-search-input";
|
|
195
197
|
searchInput.type = "text";
|
|
196
|
-
searchInput.placeholder = "Search elements…";
|
|
197
|
-
searchInput.setAttribute("aria-label", "Search diagram elements");
|
|
198
|
+
searchInput.placeholder = t("Search elements…");
|
|
199
|
+
searchInput.setAttribute("aria-label", t("Search diagram elements"));
|
|
198
200
|
const searchCount = document.createElement("span");
|
|
199
201
|
searchCount.id = "bpmnkit-search-count";
|
|
200
202
|
const searchClose = document.createElement("button");
|
|
201
203
|
searchClose.id = "bpmnkit-search-close";
|
|
202
204
|
searchClose.textContent = "×";
|
|
203
|
-
searchClose.title = "Close search (Escape)";
|
|
205
|
+
searchClose.title = t("Close search (Escape)");
|
|
204
206
|
searchBarEl.append(searchInput, searchCount, searchClose);
|
|
205
207
|
// Keyboard shortcuts modal
|
|
206
208
|
const shortcutsModal = document.createElement("div");
|
|
@@ -209,13 +211,13 @@ export function initEditorHud(editor, options = {}) {
|
|
|
209
211
|
const shortcutsInner = document.createElement("div");
|
|
210
212
|
shortcutsInner.id = "bpmnkit-shortcuts-inner";
|
|
211
213
|
const shortcutsTitle = document.createElement("h3");
|
|
212
|
-
shortcutsTitle.textContent = "Keyboard shortcuts";
|
|
214
|
+
shortcutsTitle.textContent = t("Keyboard shortcuts");
|
|
213
215
|
shortcutsInner.appendChild(shortcutsTitle);
|
|
214
216
|
for (const [key, desc] of SHORTCUTS) {
|
|
215
217
|
const row = document.createElement("div");
|
|
216
218
|
row.className = "bpmnkit-sc-row";
|
|
217
219
|
const descEl = document.createElement("span");
|
|
218
|
-
descEl.textContent = desc;
|
|
220
|
+
descEl.textContent = t(desc);
|
|
219
221
|
const keyEl = document.createElement("kbd");
|
|
220
222
|
keyEl.className = "bpmnkit-sc-key";
|
|
221
223
|
keyEl.textContent = key;
|
|
@@ -224,7 +226,7 @@ export function initEditorHud(editor, options = {}) {
|
|
|
224
226
|
}
|
|
225
227
|
const shortcutsClose = document.createElement("button");
|
|
226
228
|
shortcutsClose.id = "bpmnkit-shortcuts-close";
|
|
227
|
-
shortcutsClose.textContent = "Close";
|
|
229
|
+
shortcutsClose.textContent = t("Close");
|
|
228
230
|
shortcutsClose.addEventListener("click", () => shortcutsModal.classList.add("hidden"));
|
|
229
231
|
shortcutsModal.addEventListener("click", (e) => {
|
|
230
232
|
if (e.target === shortcutsModal)
|
|
@@ -310,13 +312,13 @@ export function initEditorHud(editor, options = {}) {
|
|
|
310
312
|
picker.className = "group-picker";
|
|
311
313
|
const label = document.createElement("span");
|
|
312
314
|
label.className = "group-picker-label";
|
|
313
|
-
label.textContent = group.title;
|
|
315
|
+
label.textContent = t(group.title);
|
|
314
316
|
picker.appendChild(label);
|
|
315
317
|
for (const item of group.items) {
|
|
316
318
|
const btn = document.createElement("button");
|
|
317
319
|
btn.className = item.type === groupActiveType[group.id] ? "hud-btn active" : "hud-btn";
|
|
318
320
|
btn.innerHTML = item.icon;
|
|
319
|
-
btn.title = item.title;
|
|
321
|
+
btn.title = t(item.title);
|
|
320
322
|
btn.addEventListener("click", (e) => {
|
|
321
323
|
e.stopPropagation();
|
|
322
324
|
groupActiveType[group.id] = item.type;
|
|
@@ -352,13 +354,13 @@ export function initEditorHud(editor, options = {}) {
|
|
|
352
354
|
picker.className = "group-picker";
|
|
353
355
|
const label = document.createElement("span");
|
|
354
356
|
label.className = "group-picker-label";
|
|
355
|
-
label.textContent = group.title;
|
|
357
|
+
label.textContent = t(group.title);
|
|
356
358
|
picker.appendChild(label);
|
|
357
359
|
for (const item of group.items) {
|
|
358
360
|
const btn = document.createElement("button");
|
|
359
361
|
btn.className = item.type === sourceType ? "hud-btn active" : "hud-btn";
|
|
360
362
|
btn.innerHTML = item.icon;
|
|
361
|
-
btn.title = item.title;
|
|
363
|
+
btn.title = t(item.title);
|
|
362
364
|
btn.addEventListener("click", (e) => {
|
|
363
365
|
e.stopPropagation();
|
|
364
366
|
if (item.type !== sourceType)
|
|
@@ -441,7 +443,7 @@ export function initEditorHud(editor, options = {}) {
|
|
|
441
443
|
btn.className = "hud-btn";
|
|
442
444
|
btn.dataset.group = group.id;
|
|
443
445
|
btn.innerHTML = group.groupIcon;
|
|
444
|
-
btn.title =
|
|
446
|
+
btn.title = t("{name} (hold for options)", { name: t(group.title) });
|
|
445
447
|
let longPressTimer = null;
|
|
446
448
|
let isLongPress = false;
|
|
447
449
|
btn.addEventListener("pointerdown", (e) => {
|
|
@@ -648,6 +650,45 @@ export function initEditorHud(editor, options = {}) {
|
|
|
648
650
|
},
|
|
649
651
|
],
|
|
650
652
|
];
|
|
653
|
+
// Align / distribute — only meaningful for a multi-selection.
|
|
654
|
+
if (selectedIds.length >= 2) {
|
|
655
|
+
const aligns = [
|
|
656
|
+
["Align left", "left"],
|
|
657
|
+
["Align centre", "center"],
|
|
658
|
+
["Align right", "right"],
|
|
659
|
+
["Align top", "top"],
|
|
660
|
+
["Align middle", "middle"],
|
|
661
|
+
["Align bottom", "bottom"],
|
|
662
|
+
];
|
|
663
|
+
for (const [label, edge] of aligns) {
|
|
664
|
+
items.push([
|
|
665
|
+
label,
|
|
666
|
+
IC.dots,
|
|
667
|
+
() => {
|
|
668
|
+
editor.alignSelected(edge);
|
|
669
|
+
closeAllDropdowns();
|
|
670
|
+
},
|
|
671
|
+
]);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (selectedIds.length >= 3) {
|
|
675
|
+
items.push([
|
|
676
|
+
"Distribute horizontally",
|
|
677
|
+
IC.dots,
|
|
678
|
+
() => {
|
|
679
|
+
editor.distributeSelected("horizontal");
|
|
680
|
+
closeAllDropdowns();
|
|
681
|
+
},
|
|
682
|
+
]);
|
|
683
|
+
items.push([
|
|
684
|
+
"Distribute vertically",
|
|
685
|
+
IC.dots,
|
|
686
|
+
() => {
|
|
687
|
+
editor.distributeSelected("vertical");
|
|
688
|
+
closeAllDropdowns();
|
|
689
|
+
},
|
|
690
|
+
]);
|
|
691
|
+
}
|
|
651
692
|
if (options.optimizeButton) {
|
|
652
693
|
items.push([
|
|
653
694
|
"Optimize diagram",
|
|
@@ -702,6 +743,10 @@ export function initEditorHud(editor, options = {}) {
|
|
|
702
743
|
btnRedo.disabled = !editor.canRedo();
|
|
703
744
|
btnDelete.disabled = selectedIds.length === 0;
|
|
704
745
|
btnDuplicate.disabled = selectedIds.length === 0;
|
|
746
|
+
const undoLabel = editor.getUndoLabel();
|
|
747
|
+
const redoLabel = editor.getRedoLabel();
|
|
748
|
+
btnUndo.title = undoLabel ? `Undo ${undoLabel} (Ctrl+Z)` : "Undo (Ctrl+Z)";
|
|
749
|
+
btnRedo.title = redoLabel ? `Redo ${redoLabel} (Ctrl+Y)` : "Redo (Ctrl+Y)";
|
|
705
750
|
}
|
|
706
751
|
updateActionBar();
|
|
707
752
|
btnUndo.addEventListener("click", () => {
|
|
@@ -790,7 +835,7 @@ export function initEditorHud(editor, options = {}) {
|
|
|
790
835
|
const btn = document.createElement("button");
|
|
791
836
|
btn.className = "hud-btn";
|
|
792
837
|
btn.innerHTML = opt.icon;
|
|
793
|
-
btn.title = opt.title;
|
|
838
|
+
btn.title = t(opt.title);
|
|
794
839
|
btn.addEventListener("click", () => {
|
|
795
840
|
editor.addConnectedElement(sourceId, opt.type);
|
|
796
841
|
closeAllDropdowns();
|
|
@@ -856,7 +901,7 @@ export function initEditorHud(editor, options = {}) {
|
|
|
856
901
|
const typeBtn = document.createElement("button");
|
|
857
902
|
typeBtn.className = "hud-btn active";
|
|
858
903
|
typeBtn.innerHTML = currentItem?.icon ?? group.groupIcon;
|
|
859
|
-
typeBtn.title =
|
|
904
|
+
typeBtn.title = t("{name} (click to change)", { name: t(currentItem?.title ?? group.title) });
|
|
860
905
|
typeBtn.addEventListener("click", () => showTypePicker(typeBtn, group, sourceId, sourceType));
|
|
861
906
|
cfgToolbar.appendChild(typeBtn);
|
|
862
907
|
}
|
|
@@ -1461,7 +1506,7 @@ export function initEditorHud(editor, options = {}) {
|
|
|
1461
1506
|
function makeCtxItem(icon, label, onClick) {
|
|
1462
1507
|
const btn = document.createElement("button");
|
|
1463
1508
|
btn.className = "drop-item";
|
|
1464
|
-
btn.innerHTML = `<span class="di-check"></span><span class="di-icon">${icon}</span><span>${label}</span>`;
|
|
1509
|
+
btn.innerHTML = `<span class="di-check"></span><span class="di-icon">${icon}</span><span>${t(label)}</span>`;
|
|
1465
1510
|
btn.addEventListener("click", onClick);
|
|
1466
1511
|
return btn;
|
|
1467
1512
|
}
|
|
@@ -1518,12 +1563,45 @@ export function initEditorHud(editor, options = {}) {
|
|
|
1518
1563
|
editor.duplicate();
|
|
1519
1564
|
closeAllDropdowns();
|
|
1520
1565
|
}));
|
|
1566
|
+
ctxMenuEl.appendChild(makeCtxItem(IC.duplicate, "Cut", () => {
|
|
1567
|
+
editor.cut();
|
|
1568
|
+
closeAllDropdowns();
|
|
1569
|
+
}));
|
|
1521
1570
|
ctxMenuEl.appendChild(makeCtxItem(IC.trash, "Delete", () => {
|
|
1522
1571
|
editor.deleteSelected();
|
|
1523
1572
|
closeAllDropdowns();
|
|
1524
1573
|
}));
|
|
1525
1574
|
openCtxMenu(e.clientX, e.clientY);
|
|
1526
1575
|
});
|
|
1576
|
+
// Touch: a long-press opens the same context menu (dispatched as a synthetic
|
|
1577
|
+
// contextmenu event at the press point, so the handler above is reused).
|
|
1578
|
+
let lpTimer = null;
|
|
1579
|
+
let lpStart = null;
|
|
1580
|
+
const cancelLongPress = () => {
|
|
1581
|
+
if (lpTimer) {
|
|
1582
|
+
clearTimeout(lpTimer);
|
|
1583
|
+
lpTimer = null;
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
editor.container.addEventListener("pointerdown", (e) => {
|
|
1587
|
+
if (e.pointerType !== "touch")
|
|
1588
|
+
return;
|
|
1589
|
+
lpStart = { x: e.clientX, y: e.clientY };
|
|
1590
|
+
lpTimer = setTimeout(() => {
|
|
1591
|
+
lpTimer = null;
|
|
1592
|
+
if (!lpStart)
|
|
1593
|
+
return;
|
|
1594
|
+
editor.container.dispatchEvent(new MouseEvent("contextmenu", { bubbles: true, clientX: lpStart.x, clientY: lpStart.y }));
|
|
1595
|
+
}, 500);
|
|
1596
|
+
});
|
|
1597
|
+
editor.container.addEventListener("pointermove", (e) => {
|
|
1598
|
+
// A drag past the slop threshold is a pan/move, not a long-press.
|
|
1599
|
+
if (lpTimer && lpStart && Math.hypot(e.clientX - lpStart.x, e.clientY - lpStart.y) > 10) {
|
|
1600
|
+
cancelLongPress();
|
|
1601
|
+
}
|
|
1602
|
+
});
|
|
1603
|
+
editor.container.addEventListener("pointerup", cancelLongPress);
|
|
1604
|
+
editor.container.addEventListener("pointercancel", cancelLongPress);
|
|
1527
1605
|
// ── Element search (Ctrl+F) ─────────────────────────────────────────────────
|
|
1528
1606
|
let searchMatches = [];
|
|
1529
1607
|
let searchMatchIdx = 0;
|
|
@@ -1554,31 +1632,17 @@ export function initEditorHud(editor, options = {}) {
|
|
|
1554
1632
|
}
|
|
1555
1633
|
}
|
|
1556
1634
|
function runSearch() {
|
|
1557
|
-
const q = searchInput.value.trim()
|
|
1635
|
+
const q = searchInput.value.trim();
|
|
1558
1636
|
if (!q) {
|
|
1559
1637
|
searchCount.textContent = "";
|
|
1560
1638
|
searchMatches = [];
|
|
1561
1639
|
editor.setSelection([]);
|
|
1562
1640
|
return;
|
|
1563
1641
|
}
|
|
1564
|
-
|
|
1565
|
-
if (!defs)
|
|
1566
|
-
return;
|
|
1567
|
-
const ids = [];
|
|
1568
|
-
for (const proc of defs.processes) {
|
|
1569
|
-
for (const el of proc.flowElements) {
|
|
1570
|
-
if (el.name?.toLowerCase().includes(q))
|
|
1571
|
-
ids.push(el.id);
|
|
1572
|
-
}
|
|
1573
|
-
for (const el of proc.textAnnotations) {
|
|
1574
|
-
if (el.text?.toLowerCase().includes(q))
|
|
1575
|
-
ids.push(el.id);
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
searchMatches = ids;
|
|
1642
|
+
searchMatches = editor.find(q).map((m) => m.id);
|
|
1579
1643
|
searchMatchIdx = 0;
|
|
1580
1644
|
updateSearchCount();
|
|
1581
|
-
const firstId =
|
|
1645
|
+
const firstId = searchMatches[0];
|
|
1582
1646
|
if (firstId) {
|
|
1583
1647
|
selectSearchMatch(firstId);
|
|
1584
1648
|
}
|
|
@@ -1586,21 +1650,30 @@ export function initEditorHud(editor, options = {}) {
|
|
|
1586
1650
|
editor.setSelection([]);
|
|
1587
1651
|
}
|
|
1588
1652
|
}
|
|
1653
|
+
/** Moves to the next/previous match, wrapping, and selects it. */
|
|
1654
|
+
function stepSearch(dir) {
|
|
1655
|
+
if (searchMatches.length === 0)
|
|
1656
|
+
return;
|
|
1657
|
+
searchMatchIdx = (searchMatchIdx + dir + searchMatches.length) % searchMatches.length;
|
|
1658
|
+
const id = searchMatches[searchMatchIdx];
|
|
1659
|
+
updateSearchCount();
|
|
1660
|
+
if (id)
|
|
1661
|
+
selectSearchMatch(id);
|
|
1662
|
+
}
|
|
1589
1663
|
searchInput.addEventListener("input", runSearch);
|
|
1590
1664
|
searchInput.addEventListener("keydown", (e) => {
|
|
1591
1665
|
if (e.key === "Escape") {
|
|
1592
1666
|
e.preventDefault();
|
|
1593
1667
|
closeSearch();
|
|
1594
1668
|
}
|
|
1595
|
-
if (e.key === "Enter") {
|
|
1669
|
+
else if (e.key === "Enter" || e.key === "ArrowDown") {
|
|
1596
1670
|
e.preventDefault();
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
}
|
|
1671
|
+
// Enter on the first result keeps it selected; subsequent presses step.
|
|
1672
|
+
stepSearch(1);
|
|
1673
|
+
}
|
|
1674
|
+
else if (e.key === "ArrowUp") {
|
|
1675
|
+
e.preventDefault();
|
|
1676
|
+
stepSearch(-1);
|
|
1604
1677
|
}
|
|
1605
1678
|
});
|
|
1606
1679
|
searchClose.addEventListener("click", closeSearch);
|
package/dist/i18n.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Interpolation variables for a translation template. */
|
|
2
|
+
export type TranslateVars = Record<string, string | number>;
|
|
3
|
+
/**
|
|
4
|
+
* A translation hook. Receives an English template string (used as the lookup
|
|
5
|
+
* key) plus optional `{name}` interpolation variables and returns the localized
|
|
6
|
+
* string. Keys that are not localized should be returned unchanged (after
|
|
7
|
+
* interpolation) — {@link defaultTranslate} does exactly that.
|
|
8
|
+
*/
|
|
9
|
+
export type Translate = (template: string, vars?: TranslateVars) => string;
|
|
10
|
+
/** Fills `{name}` placeholders in `template` from `vars`. */
|
|
11
|
+
export declare function interpolate(template: string, vars?: TranslateVars): string;
|
|
12
|
+
/** The default (identity) translator: no localization, just placeholder interpolation. */
|
|
13
|
+
export declare function defaultTranslate(template: string, vars?: TranslateVars): string;
|
|
14
|
+
//# sourceMappingURL=i18n.d.ts.map
|
package/dist/i18n.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Fills `{name}` placeholders in `template` from `vars`. */
|
|
2
|
+
export function interpolate(template, vars) {
|
|
3
|
+
if (!vars)
|
|
4
|
+
return template;
|
|
5
|
+
return template.replace(/\{(\w+)\}/g, (match, key) => key in vars ? String(vars[key]) : match);
|
|
6
|
+
}
|
|
7
|
+
/** The default (identity) translator: no localization, just placeholder interpolation. */
|
|
8
|
+
export function defaultTranslate(template, vars) {
|
|
9
|
+
return interpolate(template, vars);
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=i18n.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export { ELEMENT_GROUPS, ELEMENT_TYPE_LABELS, EXTERNAL_LABEL_TYPES, CONTEXTUAL_A
|
|
|
5
5
|
export type { ElementGroup } from "./element-groups.js";
|
|
6
6
|
export { initEditorHud } from "./hud.js";
|
|
7
7
|
export type { HudOptions } from "./hud.js";
|
|
8
|
+
export { defaultTranslate, interpolate } from "./i18n.js";
|
|
9
|
+
export type { Translate, TranslateVars } from "./i18n.js";
|
|
8
10
|
export { createSideDock } from "./dock.js";
|
|
9
11
|
export type { SideDock } from "./dock.js";
|
|
10
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -2,5 +2,6 @@ export { BpmnEditor } from "./editor.js";
|
|
|
2
2
|
export { createEmptyDefinitions } from "./modeling.js";
|
|
3
3
|
export { ELEMENT_GROUPS, ELEMENT_TYPE_LABELS, EXTERNAL_LABEL_TYPES, CONTEXTUAL_ADD_TYPES, getElementGroup, getValidLabelPositions, } from "./element-groups.js";
|
|
4
4
|
export { initEditorHud } from "./hud.js";
|
|
5
|
+
export { defaultTranslate, interpolate } from "./i18n.js";
|
|
5
6
|
export { createSideDock } from "./dock.js";
|
|
6
7
|
//# sourceMappingURL=index.js.map
|
package/dist/modeling.d.ts
CHANGED
|
@@ -77,6 +77,7 @@ export declare function copyElements(defs: BpmnDefinitions, ids: string[]): Clip
|
|
|
77
77
|
export declare function pasteElements(defs: BpmnDefinitions, clipboard: Clipboard, offsetX: number, offsetY: number): {
|
|
78
78
|
defs: BpmnDefinitions;
|
|
79
79
|
newIds: Map<string, string>;
|
|
80
|
+
topLevelIds: string[];
|
|
80
81
|
};
|
|
81
82
|
export declare function createAnnotation(defs: BpmnDefinitions, bounds: BpmnBounds, text?: string): {
|
|
82
83
|
defs: BpmnDefinitions;
|
package/dist/modeling.js
CHANGED
|
@@ -29,6 +29,7 @@ export function createEmptyDefinitions() {
|
|
|
29
29
|
sequenceFlows: [],
|
|
30
30
|
textAnnotations: [],
|
|
31
31
|
associations: [],
|
|
32
|
+
groups: [],
|
|
32
33
|
unknownAttributes: {},
|
|
33
34
|
},
|
|
34
35
|
],
|
|
@@ -142,6 +143,7 @@ function makeFlowElement(type, id, name) {
|
|
|
142
143
|
sequenceFlows: [],
|
|
143
144
|
textAnnotations: [],
|
|
144
145
|
associations: [],
|
|
146
|
+
groups: [],
|
|
145
147
|
};
|
|
146
148
|
case "adHocSubProcess":
|
|
147
149
|
return {
|
|
@@ -151,6 +153,7 @@ function makeFlowElement(type, id, name) {
|
|
|
151
153
|
sequenceFlows: [],
|
|
152
154
|
textAnnotations: [],
|
|
153
155
|
associations: [],
|
|
156
|
+
groups: [],
|
|
154
157
|
};
|
|
155
158
|
case "transaction":
|
|
156
159
|
return {
|
|
@@ -160,6 +163,7 @@ function makeFlowElement(type, id, name) {
|
|
|
160
163
|
sequenceFlows: [],
|
|
161
164
|
textAnnotations: [],
|
|
162
165
|
associations: [],
|
|
166
|
+
groups: [],
|
|
163
167
|
};
|
|
164
168
|
case "exclusiveGateway":
|
|
165
169
|
return { ...base, type: "exclusiveGateway" };
|
|
@@ -1071,6 +1075,21 @@ export function removeCollinearWaypoints(defs, edgeId) {
|
|
|
1071
1075
|
};
|
|
1072
1076
|
}
|
|
1073
1077
|
// ── Change element type ───────────────────────────────────────────────────────
|
|
1078
|
+
/** Activity types that carry `loopCharacteristics` (multi-instance / loop markers). */
|
|
1079
|
+
const ACTIVITY_LOOP_TYPES = new Set([
|
|
1080
|
+
"task",
|
|
1081
|
+
"serviceTask",
|
|
1082
|
+
"userTask",
|
|
1083
|
+
"scriptTask",
|
|
1084
|
+
"sendTask",
|
|
1085
|
+
"receiveTask",
|
|
1086
|
+
"businessRuleTask",
|
|
1087
|
+
"manualTask",
|
|
1088
|
+
"callActivity",
|
|
1089
|
+
"subProcess",
|
|
1090
|
+
"adHocSubProcess",
|
|
1091
|
+
"transaction",
|
|
1092
|
+
]);
|
|
1074
1093
|
/**
|
|
1075
1094
|
* Replaces a flow element's type while preserving its id, name, and connections.
|
|
1076
1095
|
* Use this for gateway type-switching (exclusive ↔ parallel) and task type-switching.
|
|
@@ -1090,6 +1109,8 @@ export function changeElementType(defs, id, newType) {
|
|
|
1090
1109
|
name: el.name,
|
|
1091
1110
|
incoming: el.incoming,
|
|
1092
1111
|
outgoing: el.outgoing,
|
|
1112
|
+
documentation: el.documentation,
|
|
1113
|
+
isForCompensation: el.isForCompensation,
|
|
1093
1114
|
extensionElements: el.extensionElements,
|
|
1094
1115
|
unknownAttributes: el.unknownAttributes,
|
|
1095
1116
|
};
|
|
@@ -1214,6 +1235,7 @@ export function changeElementType(defs, id, newType) {
|
|
|
1214
1235
|
sequenceFlows: el.type === "subProcess" ? el.sequenceFlows : [],
|
|
1215
1236
|
textAnnotations: el.type === "subProcess" ? el.textAnnotations : [],
|
|
1216
1237
|
associations: el.type === "subProcess" ? el.associations : [],
|
|
1238
|
+
groups: el.type === "subProcess" ? el.groups : [],
|
|
1217
1239
|
};
|
|
1218
1240
|
break;
|
|
1219
1241
|
case "adHocSubProcess":
|
|
@@ -1224,6 +1246,7 @@ export function changeElementType(defs, id, newType) {
|
|
|
1224
1246
|
sequenceFlows: el.type === "adHocSubProcess" ? el.sequenceFlows : [],
|
|
1225
1247
|
textAnnotations: el.type === "adHocSubProcess" ? el.textAnnotations : [],
|
|
1226
1248
|
associations: el.type === "adHocSubProcess" ? el.associations : [],
|
|
1249
|
+
groups: el.type === "adHocSubProcess" ? el.groups : [],
|
|
1227
1250
|
};
|
|
1228
1251
|
break;
|
|
1229
1252
|
case "transaction":
|
|
@@ -1234,6 +1257,7 @@ export function changeElementType(defs, id, newType) {
|
|
|
1234
1257
|
sequenceFlows: el.type === "transaction" ? el.sequenceFlows : [],
|
|
1235
1258
|
textAnnotations: el.type === "transaction" ? el.textAnnotations : [],
|
|
1236
1259
|
associations: el.type === "transaction" ? el.associations : [],
|
|
1260
|
+
groups: el.type === "transaction" ? el.groups : [],
|
|
1237
1261
|
};
|
|
1238
1262
|
break;
|
|
1239
1263
|
case "exclusiveGateway":
|
|
@@ -1254,6 +1278,14 @@ export function changeElementType(defs, id, newType) {
|
|
|
1254
1278
|
case "textAnnotation":
|
|
1255
1279
|
throw new Error("textAnnotation is not a flow element — use createAnnotation()");
|
|
1256
1280
|
}
|
|
1281
|
+
// Preserve a multi-instance/loop marker when morphing between activity types
|
|
1282
|
+
// that both support one (e.g. serviceTask ⇄ userTask, task → subProcess).
|
|
1283
|
+
const loop = "loopCharacteristics" in el ? el.loopCharacteristics : undefined;
|
|
1284
|
+
if (loop && ACTIVITY_LOOP_TYPES.has(newEl.type)) {
|
|
1285
|
+
;
|
|
1286
|
+
newEl.loopCharacteristics =
|
|
1287
|
+
loop;
|
|
1288
|
+
}
|
|
1257
1289
|
const newElements = [...process.flowElements];
|
|
1258
1290
|
newElements[elIndex] = newEl;
|
|
1259
1291
|
return {
|
|
@@ -1285,6 +1317,33 @@ export function insertShapeOnEdge(defs, edgeId, shapeId) {
|
|
|
1285
1317
|
const r2 = createConnection(r1.defs, shapeId, flow.targetRef, computeWaypoints(newDi.bounds, tgtDi.bounds));
|
|
1286
1318
|
return r2.defs;
|
|
1287
1319
|
}
|
|
1320
|
+
/** True when a flow element is a container carrying its own children. */
|
|
1321
|
+
function hasChildren(el) {
|
|
1322
|
+
return "flowElements" in el;
|
|
1323
|
+
}
|
|
1324
|
+
/** Collects every id that owns a DI shape within `el` (itself + recursive children). */
|
|
1325
|
+
function collectShapeIds(el, out) {
|
|
1326
|
+
out.add(el.id);
|
|
1327
|
+
if (hasChildren(el)) {
|
|
1328
|
+
for (const child of el.flowElements)
|
|
1329
|
+
collectShapeIds(child, out);
|
|
1330
|
+
for (const ta of el.textAnnotations)
|
|
1331
|
+
out.add(ta.id);
|
|
1332
|
+
for (const g of el.groups)
|
|
1333
|
+
out.add(g.id);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
/** Collects every id that owns a DI edge within `el` (nested flows + associations). */
|
|
1337
|
+
function collectEdgeIds(el, out) {
|
|
1338
|
+
if (hasChildren(el)) {
|
|
1339
|
+
for (const sf of el.sequenceFlows)
|
|
1340
|
+
out.add(sf.id);
|
|
1341
|
+
for (const a of el.associations)
|
|
1342
|
+
out.add(a.id);
|
|
1343
|
+
for (const child of el.flowElements)
|
|
1344
|
+
collectEdgeIds(child, out);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1288
1347
|
export function copyElements(defs, ids) {
|
|
1289
1348
|
const idSet = new Set(ids);
|
|
1290
1349
|
const process = defs.processes[0];
|
|
@@ -1292,45 +1351,93 @@ export function copyElements(defs, ids) {
|
|
|
1292
1351
|
if (!process || !diagram) {
|
|
1293
1352
|
return { elements: [], flows: [], shapes: [], edges: [] };
|
|
1294
1353
|
}
|
|
1295
|
-
const
|
|
1296
|
-
|
|
1297
|
-
const
|
|
1298
|
-
const
|
|
1299
|
-
const
|
|
1354
|
+
const selected = process.flowElements.filter((el) => idSet.has(el.id));
|
|
1355
|
+
// Auto-include boundary events attached to any copied host (bpmn-js parity).
|
|
1356
|
+
const selectedIds = new Set(selected.map((el) => el.id));
|
|
1357
|
+
const boundaries = process.flowElements.filter((el) => el.type === "boundaryEvent" && selectedIds.has(el.attachedToRef) && !selectedIds.has(el.id));
|
|
1358
|
+
const elements = [...selected, ...boundaries];
|
|
1359
|
+
const topIds = new Set(elements.map((el) => el.id));
|
|
1360
|
+
// All shape/edge ids within the copied subtree (recurses into sub-processes).
|
|
1361
|
+
const shapeIds = new Set();
|
|
1362
|
+
const edgeIds = new Set();
|
|
1363
|
+
for (const el of elements) {
|
|
1364
|
+
collectShapeIds(el, shapeIds);
|
|
1365
|
+
collectEdgeIds(el, edgeIds);
|
|
1366
|
+
}
|
|
1367
|
+
// Top-level flows whose endpoints are both copied.
|
|
1368
|
+
const flows = process.sequenceFlows.filter((sf) => topIds.has(sf.sourceRef) && topIds.has(sf.targetRef));
|
|
1369
|
+
for (const sf of flows)
|
|
1370
|
+
edgeIds.add(sf.id);
|
|
1371
|
+
const shapes = diagram.plane.shapes.filter((s) => shapeIds.has(s.bpmnElement));
|
|
1372
|
+
const edges = diagram.plane.edges.filter((e) => edgeIds.has(e.bpmnElement));
|
|
1300
1373
|
return { elements, flows, shapes, edges };
|
|
1301
1374
|
}
|
|
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, genId(el.type));
|
|
1378
|
+
if (hasChildren(el)) {
|
|
1379
|
+
for (const child of el.flowElements)
|
|
1380
|
+
buildIdMap(child, map);
|
|
1381
|
+
for (const sf of el.sequenceFlows)
|
|
1382
|
+
map.set(sf.id, genId("Flow"));
|
|
1383
|
+
for (const ta of el.textAnnotations)
|
|
1384
|
+
map.set(ta.id, genId("TextAnnotation"));
|
|
1385
|
+
for (const a of el.associations)
|
|
1386
|
+
map.set(a.id, genId("Association"));
|
|
1387
|
+
for (const g of el.groups)
|
|
1388
|
+
map.set(g.id, genId("Group"));
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
/** Deep-clones `el`, remapping its own id, refs, and all nested children through `map`. */
|
|
1392
|
+
function remapElement(el, map) {
|
|
1393
|
+
const clone = {
|
|
1394
|
+
...el,
|
|
1395
|
+
id: map.get(el.id) ?? el.id,
|
|
1396
|
+
incoming: el.incoming.map((r) => map.get(r)).filter((r) => r !== undefined),
|
|
1397
|
+
outgoing: el.outgoing.map((r) => map.get(r)).filter((r) => r !== undefined),
|
|
1398
|
+
};
|
|
1399
|
+
if (clone.type === "boundaryEvent" && el.type === "boundaryEvent") {
|
|
1400
|
+
clone.attachedToRef = map.get(el.attachedToRef) ?? el.attachedToRef;
|
|
1401
|
+
}
|
|
1402
|
+
if (hasChildren(el) && hasChildren(clone)) {
|
|
1403
|
+
clone.flowElements = el.flowElements.map((c) => remapElement(c, map));
|
|
1404
|
+
clone.sequenceFlows = el.sequenceFlows.map((sf) => remapFlow(sf, map));
|
|
1405
|
+
clone.textAnnotations = el.textAnnotations.map((ta) => ({
|
|
1406
|
+
...ta,
|
|
1407
|
+
id: map.get(ta.id) ?? ta.id,
|
|
1408
|
+
}));
|
|
1409
|
+
clone.associations = el.associations.map((a) => ({
|
|
1410
|
+
...a,
|
|
1411
|
+
id: map.get(a.id) ?? a.id,
|
|
1412
|
+
sourceRef: map.get(a.sourceRef) ?? a.sourceRef,
|
|
1413
|
+
targetRef: map.get(a.targetRef) ?? a.targetRef,
|
|
1414
|
+
}));
|
|
1415
|
+
clone.groups = el.groups.map((g) => ({ ...g, id: map.get(g.id) ?? g.id }));
|
|
1416
|
+
}
|
|
1417
|
+
return clone;
|
|
1418
|
+
}
|
|
1419
|
+
function remapFlow(sf, map) {
|
|
1420
|
+
return {
|
|
1421
|
+
...sf,
|
|
1422
|
+
id: map.get(sf.id) ?? sf.id,
|
|
1423
|
+
sourceRef: map.get(sf.sourceRef) ?? sf.sourceRef,
|
|
1424
|
+
targetRef: map.get(sf.targetRef) ?? sf.targetRef,
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1302
1427
|
export function pasteElements(defs, clipboard, offsetX, offsetY) {
|
|
1303
1428
|
const newIds = new Map();
|
|
1304
|
-
// Generate new IDs for
|
|
1305
|
-
for (const el of clipboard.elements)
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
for (const sf of clipboard.flows) {
|
|
1429
|
+
// Generate new IDs for every element (recursively) and top-level flow.
|
|
1430
|
+
for (const el of clipboard.elements)
|
|
1431
|
+
buildIdMap(el, newIds);
|
|
1432
|
+
for (const sf of clipboard.flows)
|
|
1309
1433
|
newIds.set(sf.id, genId("Flow"));
|
|
1310
|
-
}
|
|
1311
1434
|
const process = defs.processes[0];
|
|
1312
1435
|
const diagram = defs.diagrams[0];
|
|
1313
1436
|
if (!process || !diagram)
|
|
1314
|
-
return { defs, newIds };
|
|
1315
|
-
|
|
1316
|
-
const
|
|
1317
|
-
|
|
1318
|
-
const newIncoming = el.incoming
|
|
1319
|
-
.map((ref) => newIds.get(ref))
|
|
1320
|
-
.filter((r) => r !== undefined);
|
|
1321
|
-
const newOutgoing = el.outgoing
|
|
1322
|
-
.map((ref) => newIds.get(ref))
|
|
1323
|
-
.filter((r) => r !== undefined);
|
|
1324
|
-
return { ...el, id: newId, incoming: newIncoming, outgoing: newOutgoing };
|
|
1325
|
-
});
|
|
1326
|
-
// Create new sequence flows
|
|
1327
|
-
const newFlows = clipboard.flows.map((sf) => {
|
|
1328
|
-
const newId = newIds.get(sf.id) ?? genId("Flow");
|
|
1329
|
-
const newSrc = newIds.get(sf.sourceRef) ?? sf.sourceRef;
|
|
1330
|
-
const newTgt = newIds.get(sf.targetRef) ?? sf.targetRef;
|
|
1331
|
-
return { ...sf, id: newId, sourceRef: newSrc, targetRef: newTgt };
|
|
1332
|
-
});
|
|
1333
|
-
// Create new DI shapes with offset
|
|
1437
|
+
return { defs, newIds, topLevelIds: [] };
|
|
1438
|
+
const newElements = clipboard.elements.map((el) => remapElement(el, newIds));
|
|
1439
|
+
const newFlows = clipboard.flows.map((sf) => remapFlow(sf, newIds));
|
|
1440
|
+
// Create new DI shapes with offset (covers nested descendants too).
|
|
1334
1441
|
const newDiShapes = clipboard.shapes.map((s) => {
|
|
1335
1442
|
const newElId = newIds.get(s.bpmnElement) ?? s.bpmnElement;
|
|
1336
1443
|
return {
|
|
@@ -1344,7 +1451,7 @@ export function pasteElements(defs, clipboard, offsetX, offsetY) {
|
|
|
1344
1451
|
},
|
|
1345
1452
|
};
|
|
1346
1453
|
});
|
|
1347
|
-
// Create new DI edges with offset waypoints
|
|
1454
|
+
// Create new DI edges with offset waypoints (covers nested flows/associations).
|
|
1348
1455
|
const newDiEdges = clipboard.edges.map((e) => {
|
|
1349
1456
|
const newFlowId = newIds.get(e.bpmnElement) ?? e.bpmnElement;
|
|
1350
1457
|
return {
|
|
@@ -1379,7 +1486,10 @@ export function pasteElements(defs, clipboard, offsetX, offsetY) {
|
|
|
1379
1486
|
...defs.diagrams.slice(1),
|
|
1380
1487
|
],
|
|
1381
1488
|
};
|
|
1382
|
-
|
|
1489
|
+
const topLevelIds = clipboard.elements
|
|
1490
|
+
.map((el) => newIds.get(el.id))
|
|
1491
|
+
.filter((id) => id !== undefined);
|
|
1492
|
+
return { defs: newDefs, newIds, topLevelIds };
|
|
1383
1493
|
}
|
|
1384
1494
|
// ── Create text annotation ────────────────────────────────────────────────────
|
|
1385
1495
|
export function createAnnotation(defs, bounds, text) {
|
package/dist/overlay.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export declare class OverlayRenderer {
|
|
|
42
42
|
}>): void;
|
|
43
43
|
setRubberBand(origin: DiagPoint | null, current?: DiagPoint): void;
|
|
44
44
|
setResizePreview(bounds: BpmnBounds | null): void;
|
|
45
|
-
setGhostConnection(waypoints: BpmnWaypoint[] | null): void;
|
|
45
|
+
setGhostConnection(waypoints: BpmnWaypoint[] | null, invalid?: boolean): void;
|
|
46
46
|
setEdgeEndpoints(waypoints: BpmnWaypoint[] | null, edgeId: string): void;
|
|
47
47
|
setEndpointDragGhost(waypoints: BpmnWaypoint[] | null): void;
|
|
48
48
|
setSpacePreview(axis: "h" | "v" | null, splitValue?: number): void;
|