@bpmnkit/editor 0.0.31 → 0.0.33

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/overlay.js CHANGED
@@ -205,14 +205,14 @@ export class OverlayRenderer {
205
205
  this._resizePreviewG.appendChild(rect);
206
206
  }
207
207
  // ── Ghost connection ───────────────────────────────────────────────
208
- setGhostConnection(waypoints) {
208
+ setGhostConnection(waypoints, invalid = false) {
209
209
  this._ghostConnG.innerHTML = "";
210
210
  if (!waypoints || waypoints.length < 2)
211
211
  return;
212
212
  const points = waypoints.map((wp) => `${wp.x},${wp.y}`).join(" ");
213
213
  const poly = svgEl("polyline");
214
214
  attr(poly, {
215
- class: "bpmnkit-ghost-conn",
215
+ class: invalid ? "bpmnkit-ghost-conn bpmnkit-ghost-conn-invalid" : "bpmnkit-ghost-conn",
216
216
  points,
217
217
  "marker-end": `url(#${this._markerId})`,
218
218
  });
package/dist/rules.d.ts CHANGED
@@ -1,10 +1,24 @@
1
- import type { BpmnElementType } from "@bpmnkit/core";
1
+ /** True when `type` is an activity (task family, sub-process, call activity, …). */
2
+ export declare function isActivity(type: string): boolean;
2
3
  /**
3
4
  * Returns true if a sequence flow from `sourceType` to `targetType` is valid.
4
5
  *
5
- * - endEvent cannot be a source
6
- * - startEvent cannot be a target
7
- * - boundaryEvent can only be a source
6
+ * BPMN defaults: an end event has no outgoing flow, a start event has no
7
+ * incoming flow, boundary events attach (they are never a flow *target*), data
8
+ * elements and annotations use associations rather than sequence flows, and an
9
+ * event-based gateway may only target intermediate catch events or receive tasks.
8
10
  */
9
- export declare function canConnect(sourceType: BpmnElementType, targetType: BpmnElementType): boolean;
11
+ export declare function canConnect(sourceType: string, targetType: string): boolean;
12
+ /** Returns true if a boundary event may attach to a host of `hostType` (activities only). */
13
+ export declare function canAttach(hostType: string): boolean;
14
+ /**
15
+ * Returns true if a `parentType` container may hold a `childType` element.
16
+ * Containers hold flow nodes; lanes and pools hold flow nodes but not other
17
+ * pools; data/annotation elements and flows are never containers.
18
+ */
19
+ export declare function canContain(parentType: string, childType: string): boolean;
20
+ /** Returns true if an element of `type` supports interactive resizing. */
21
+ export declare function canResize(type: string): boolean;
22
+ /** Returns true if an element may be morphed from `fromType` into `toType` (same category). */
23
+ export declare function canMorph(fromType: string, toType: string): boolean;
10
24
  //# sourceMappingURL=rules.d.ts.map
package/dist/rules.js CHANGED
@@ -1,17 +1,113 @@
1
+ import { RESIZABLE_TYPES } from "./types.js";
2
+ /**
3
+ * BPMN modeling rules — a small, dependency-free rule set matching the intent of
4
+ * bpmn-js's `BpmnRules`. Each predicate answers a single yes/no question and is
5
+ * consulted by the editor (state machine + HUD) before enabling or committing an
6
+ * action, so illegal edits are refused both visually and at commit time.
7
+ */
8
+ const ACTIVITY_TYPES = new Set([
9
+ "task",
10
+ "serviceTask",
11
+ "userTask",
12
+ "scriptTask",
13
+ "sendTask",
14
+ "receiveTask",
15
+ "businessRuleTask",
16
+ "manualTask",
17
+ "callActivity",
18
+ "subProcess",
19
+ "adHocSubProcess",
20
+ "eventSubProcess",
21
+ "transaction",
22
+ ]);
23
+ const CONTAINER_TYPES = new Set([
24
+ "process",
25
+ "subProcess",
26
+ "adHocSubProcess",
27
+ "eventSubProcess",
28
+ "transaction",
29
+ "participant",
30
+ "lane",
31
+ ]);
32
+ const DATA_TYPES = new Set([
33
+ "dataObject",
34
+ "dataObjectReference",
35
+ "dataStoreReference",
36
+ ]);
37
+ /** True when `type` is an activity (task family, sub-process, call activity, …). */
38
+ export function isActivity(type) {
39
+ return ACTIVITY_TYPES.has(type);
40
+ }
41
+ function morphCategory(type) {
42
+ if (type.endsWith("Gateway"))
43
+ return "gateway";
44
+ if (type === "startEvent" || type.endsWith("StartEvent"))
45
+ return "startEvent";
46
+ if (type === "endEvent" || type.endsWith("EndEvent"))
47
+ return "endEvent";
48
+ if (type === "intermediateCatchEvent" ||
49
+ type === "intermediateThrowEvent" ||
50
+ type.endsWith("CatchEvent") ||
51
+ type.endsWith("ThrowEvent")) {
52
+ return "intermediateEvent";
53
+ }
54
+ if (ACTIVITY_TYPES.has(type))
55
+ return "activity";
56
+ return null;
57
+ }
1
58
  /**
2
59
  * Returns true if a sequence flow from `sourceType` to `targetType` is valid.
3
60
  *
4
- * - endEvent cannot be a source
5
- * - startEvent cannot be a target
6
- * - boundaryEvent can only be a source
61
+ * BPMN defaults: an end event has no outgoing flow, a start event has no
62
+ * incoming flow, boundary events attach (they are never a flow *target*), data
63
+ * elements and annotations use associations rather than sequence flows, and an
64
+ * event-based gateway may only target intermediate catch events or receive tasks.
7
65
  */
8
66
  export function canConnect(sourceType, targetType) {
9
- if (sourceType === "endEvent")
67
+ // End events never emit, start events never receive.
68
+ if (sourceType === "endEvent" || sourceType.endsWith("EndEvent"))
10
69
  return false;
11
- if (targetType === "startEvent")
70
+ if (targetType === "startEvent" || targetType.endsWith("StartEvent"))
12
71
  return false;
13
72
  if (targetType === "boundaryEvent")
14
73
  return false;
74
+ // Data elements and text annotations are not sequence-flow endpoints.
75
+ if (DATA_TYPES.has(sourceType) || DATA_TYPES.has(targetType))
76
+ return false;
77
+ if (sourceType === "textAnnotation" || targetType === "textAnnotation")
78
+ return false;
79
+ // Event-based gateway → intermediate catch event or receive task only.
80
+ if (sourceType === "eventBasedGateway") {
81
+ return targetType === "intermediateCatchEvent" || targetType === "receiveTask";
82
+ }
83
+ return true;
84
+ }
85
+ /** Returns true if a boundary event may attach to a host of `hostType` (activities only). */
86
+ export function canAttach(hostType) {
87
+ return isActivity(hostType);
88
+ }
89
+ /**
90
+ * Returns true if a `parentType` container may hold a `childType` element.
91
+ * Containers hold flow nodes; lanes and pools hold flow nodes but not other
92
+ * pools; data/annotation elements and flows are never containers.
93
+ */
94
+ export function canContain(parentType, childType) {
95
+ if (!CONTAINER_TYPES.has(parentType))
96
+ return false;
97
+ // Pools/lanes and sub-processes never contain a participant (pool).
98
+ if (childType === "participant")
99
+ return false;
15
100
  return true;
16
101
  }
102
+ /** Returns true if an element of `type` supports interactive resizing. */
103
+ export function canResize(type) {
104
+ return RESIZABLE_TYPES.has(type);
105
+ }
106
+ /** Returns true if an element may be morphed from `fromType` into `toType` (same category). */
107
+ export function canMorph(fromType, toType) {
108
+ if (fromType === toType)
109
+ return false;
110
+ const from = morphCategory(fromType);
111
+ return from !== null && from === morphCategory(toType);
112
+ }
17
113
  //# sourceMappingURL=rules.js.map
@@ -69,6 +69,7 @@ type SelectSub = {
69
69
  isHoriz: boolean;
70
70
  projPt: DiagPoint;
71
71
  origin: DiagPoint;
72
+ nearMidpoint: boolean;
72
73
  screenX: number;
73
74
  screenY: number;
74
75
  } | {
@@ -76,6 +77,12 @@ type SelectSub = {
76
77
  edgeId: string;
77
78
  segIdx: number;
78
79
  origin: DiagPoint;
80
+ } | {
81
+ name: "dragging-edge-segment";
82
+ edgeId: string;
83
+ segIdx: number;
84
+ isHoriz: boolean;
85
+ origin: DiagPoint;
79
86
  } | {
80
87
  name: "pointing-edge-waypoint";
81
88
  edgeId: string;
@@ -127,7 +134,7 @@ export interface Callbacks {
127
134
  cancelTranslate(): void;
128
135
  previewResize(bounds: BpmnBounds): void;
129
136
  commitResize(id: string, bounds: BpmnBounds): void;
130
- previewConnect(ghostEnd: DiagPoint): void;
137
+ previewConnect(ghostEnd: DiagPoint, targetId: string | null): void;
131
138
  cancelConnect(): void;
132
139
  commitConnect(sourceId: string, targetId: string): void;
133
140
  previewRubberBand(origin: DiagPoint, current: DiagPoint): void;
@@ -148,6 +155,9 @@ export interface Callbacks {
148
155
  previewWaypointMove(edgeId: string, wpIdx: number, pt: DiagPoint): void;
149
156
  commitWaypointMove(edgeId: string, wpIdx: number, pt: DiagPoint): void;
150
157
  cancelWaypointMove(): void;
158
+ previewSegmentMove(edgeId: string, segIdx: number, isHoriz: boolean, delta: number): void;
159
+ commitSegmentMove(edgeId: string, segIdx: number, isHoriz: boolean, delta: number): void;
160
+ cancelSegmentMove(): void;
151
161
  showEdgeHoverDot(pt: DiagPoint): void;
152
162
  hideEdgeHoverDot(): void;
153
163
  showEdgeWaypointBalls(edgeId: string): void;
@@ -148,6 +148,7 @@ export class EditorStateMachine {
148
148
  isHoriz: hit.isHoriz,
149
149
  projPt: hit.projPt,
150
150
  origin: diag,
151
+ nearMidpoint: hit.nearMidpoint,
151
152
  screenX: e.clientX,
152
153
  screenY: e.clientY,
153
154
  });
@@ -290,13 +291,13 @@ export class EditorStateMachine {
290
291
  sourceId: sub.sourceId,
291
292
  ghostEnd: diag,
292
293
  });
293
- this._cb.previewConnect(diag);
294
+ this._cb.previewConnect(diag, hit.type === "shape" ? hit.id : null);
294
295
  }
295
296
  break;
296
297
  }
297
298
  case "connecting": {
298
299
  this._mode = this._withSub({ ...sub, ghostEnd: diag });
299
- this._cb.previewConnect(diag);
300
+ this._cb.previewConnect(diag, hit.type === "shape" ? hit.id : null);
300
301
  break;
301
302
  }
302
303
  case "pointing-edge-endpoint": {
@@ -319,13 +320,28 @@ export class EditorStateMachine {
319
320
  case "pointing-edge-segment": {
320
321
  const dist = screenDist(e.clientX, e.clientY, sub.screenX, sub.screenY);
321
322
  if (dist > DRAG_THRESHOLD) {
322
- this._mode = this._withSub({
323
- name: "dragging-edge-waypoint-new",
324
- edgeId: sub.edgeId,
325
- segIdx: sub.segIdx,
326
- origin: sub.origin,
327
- });
328
- this._cb.previewWaypointInsert(sub.edgeId, sub.segIdx, diag);
323
+ if (sub.nearMidpoint) {
324
+ // Near the segment midpoint → insert a new waypoint.
325
+ this._mode = this._withSub({
326
+ name: "dragging-edge-waypoint-new",
327
+ edgeId: sub.edgeId,
328
+ segIdx: sub.segIdx,
329
+ origin: sub.origin,
330
+ });
331
+ this._cb.previewWaypointInsert(sub.edgeId, sub.segIdx, diag);
332
+ }
333
+ else {
334
+ // Elsewhere on the segment → move the whole segment orthogonally.
335
+ this._mode = this._withSub({
336
+ name: "dragging-edge-segment",
337
+ edgeId: sub.edgeId,
338
+ segIdx: sub.segIdx,
339
+ isHoriz: sub.isHoriz,
340
+ origin: sub.origin,
341
+ });
342
+ const delta = sub.isHoriz ? diag.y - sub.origin.y : diag.x - sub.origin.x;
343
+ this._cb.previewSegmentMove(sub.edgeId, sub.segIdx, sub.isHoriz, delta);
344
+ }
329
345
  }
330
346
  break;
331
347
  }
@@ -333,6 +349,11 @@ export class EditorStateMachine {
333
349
  this._cb.previewWaypointInsert(sub.edgeId, sub.segIdx, diag);
334
350
  break;
335
351
  }
352
+ case "dragging-edge-segment": {
353
+ const delta = sub.isHoriz ? diag.y - sub.origin.y : diag.x - sub.origin.x;
354
+ this._cb.previewSegmentMove(sub.edgeId, sub.segIdx, sub.isHoriz, delta);
355
+ break;
356
+ }
336
357
  case "pointing-edge-waypoint": {
337
358
  const dist = screenDist(e.clientX, e.clientY, sub.screenX, sub.screenY);
338
359
  if (dist > DRAG_THRESHOLD) {
@@ -482,6 +503,13 @@ export class EditorStateMachine {
482
503
  this._mode = this._idle();
483
504
  break;
484
505
  }
506
+ case "dragging-edge-segment": {
507
+ this._cb.lockViewport(false);
508
+ const delta = sub.isHoriz ? diag.y - sub.origin.y : diag.x - sub.origin.x;
509
+ this._cb.commitSegmentMove(sub.edgeId, sub.segIdx, sub.isHoriz, delta);
510
+ this._mode = this._idle();
511
+ break;
512
+ }
485
513
  case "pointing-edge-waypoint": {
486
514
  this._cb.lockViewport(false);
487
515
  this._cb.setEdgeSelected(sub.edgeId);
@@ -557,6 +585,10 @@ export class EditorStateMachine {
557
585
  this._cb.cancelWaypointMove();
558
586
  this._cb.lockViewport(false);
559
587
  }
588
+ else if (sub.name === "dragging-edge-segment") {
589
+ this._cb.cancelSegmentMove();
590
+ this._cb.lockViewport(false);
591
+ }
560
592
  }
561
593
  this._mode = { mode: "default", sub: { name: "idle", hoveredId: null } };
562
594
  }
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { CanvasEvents, CanvasOptions } from "@bpmnkit/canvas";
2
2
  import type { BpmnDefinitions } from "@bpmnkit/core";
3
+ import type { Translate } from "./i18n.js";
3
4
  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";
4
5
  /** Element types that support resize handles. */
5
6
  export declare const RESIZABLE_TYPES: ReadonlySet<string>;
@@ -13,6 +14,14 @@ export type EditorOptions = CanvasOptions & {
13
14
  * localStorage automatically. The stored key is `"bpmnkit-theme"`.
14
15
  */
15
16
  persistTheme?: boolean;
17
+ /**
18
+ * Optional translation hook for the editor's HUD strings (palette labels,
19
+ * banners, keyboard-shortcut names, context-menu actions). Receives the
20
+ * English template as the key plus optional `{name}` interpolation vars and
21
+ * returns the localized string. Defaults to identity (English); return
22
+ * unlocalized keys unchanged.
23
+ */
24
+ translate?: Translate;
16
25
  };
17
26
  export interface EditorEvents extends CanvasEvents {
18
27
  "diagram:change": (defs: BpmnDefinitions) => void;
@@ -48,6 +57,8 @@ export type HitResult = {
48
57
  segIdx: number;
49
58
  isHoriz: boolean;
50
59
  projPt: DiagPoint;
60
+ /** True when the press is near the segment midpoint (→ insert a waypoint). */
61
+ nearMidpoint: boolean;
51
62
  } | {
52
63
  type: "edge-waypoint";
53
64
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bpmnkit/editor",
3
- "version": "0.0.31",
3
+ "version": "0.0.33",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -17,8 +17,8 @@
17
17
  "dist/**/*.d.ts"
18
18
  ],
19
19
  "dependencies": {
20
- "@bpmnkit/canvas": "0.0.28",
21
- "@bpmnkit/core": "0.1.0"
20
+ "@bpmnkit/canvas": "0.0.30",
21
+ "@bpmnkit/core": "0.1.2"
22
22
  },
23
23
  "description": "Full-featured interactive BPMN editor with undo/redo, HUD, and side-dock UI",
24
24
  "keywords": [