@bpmnkit/canvas 0.0.27 → 0.0.29

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.
@@ -0,0 +1,51 @@
1
+ import type { BpmnDefinitions, BpmnDiPlane } from "@bpmnkit/core";
2
+ import type { RenderedEdge, RenderedShape } from "./types.js";
3
+ /** The four stacked SVG layers a scene renders into (bottom → top). */
4
+ export interface SceneLayers {
5
+ containers: SVGGElement;
6
+ edges: SVGGElement;
7
+ shapes: SVGGElement;
8
+ labels: SVGGElement;
9
+ }
10
+ /**
11
+ * Owns a diagram's SVG layers and an id → graphics registry, and renders a DI
12
+ * plane into them. Beyond the initial full {@link render}, individual elements
13
+ * can be updated or removed in place ({@link updateElement}/{@link removeElement})
14
+ * without tearing down the whole scene — the foundation for incremental editing.
15
+ *
16
+ * CSS classes added to an element's `<g>` after rendering (markers, selection,
17
+ * highlights) are preserved across {@link updateElement}.
18
+ */
19
+ export declare class Scene {
20
+ private readonly _layers;
21
+ private readonly _instanceId;
22
+ private readonly _registry;
23
+ private _defs;
24
+ private _plane;
25
+ private _drillableIds;
26
+ constructor(_layers: SceneLayers, _instanceId: string);
27
+ /** Renders a plane from scratch, replacing any current content. */
28
+ render(defs: BpmnDefinitions, plane: BpmnDiPlane, drillableIds?: ReadonlySet<string>): void;
29
+ /** Empties the layers and the registry. */
30
+ clear(): void;
31
+ /** Returns the registered shape or edge for an id, or `undefined`. */
32
+ getElement(id: string): RenderedShape | RenderedEdge | undefined;
33
+ /** Returns the `<g>` graphics element for an id, or `undefined`. */
34
+ getGraphics(id: string): SVGGElement | undefined;
35
+ /** Iterates every registered element in insertion order. */
36
+ forEach(fn: (el: RenderedShape | RenderedEdge) => void): void;
37
+ /** All rendered shapes, in registry order. */
38
+ getShapes(): RenderedShape[];
39
+ /** All rendered edges, in registry order. */
40
+ getEdges(): RenderedEdge[];
41
+ /**
42
+ * Re-renders a single element's `<g>` in place from the current model,
43
+ * preserving CSS classes (markers/selection) applied after the last render.
44
+ * No-op if the id is unknown or there is no current plane.
45
+ */
46
+ updateElement(id: string): void;
47
+ /** Removes an element's `<g>` (and its external label) from the scene. */
48
+ removeElement(id: string): void;
49
+ private _context;
50
+ }
51
+ //# sourceMappingURL=scene.d.ts.map
package/dist/scene.js ADDED
@@ -0,0 +1,140 @@
1
+ import { buildRenderContext, renderEdgeGroup, renderShapeGroup, } from "./renderer.js";
2
+ /**
3
+ * Owns a diagram's SVG layers and an id → graphics registry, and renders a DI
4
+ * plane into them. Beyond the initial full {@link render}, individual elements
5
+ * can be updated or removed in place ({@link updateElement}/{@link removeElement})
6
+ * without tearing down the whole scene — the foundation for incremental editing.
7
+ *
8
+ * CSS classes added to an element's `<g>` after rendering (markers, selection,
9
+ * highlights) are preserved across {@link updateElement}.
10
+ */
11
+ export class Scene {
12
+ _layers;
13
+ _instanceId;
14
+ _registry = new Map();
15
+ _defs = null;
16
+ _plane = null;
17
+ _drillableIds = new Set();
18
+ constructor(_layers, _instanceId) {
19
+ this._layers = _layers;
20
+ this._instanceId = _instanceId;
21
+ }
22
+ /** Renders a plane from scratch, replacing any current content. */
23
+ render(defs, plane, drillableIds = new Set()) {
24
+ this.clear();
25
+ this._defs = defs;
26
+ this._plane = plane;
27
+ this._drillableIds = drillableIds;
28
+ const ctx = this._context();
29
+ for (const edge of plane.edges) {
30
+ const g = renderEdgeGroup(edge, ctx);
31
+ this._layers.edges.appendChild(g);
32
+ this._registry.set(edge.bpmnElement, {
33
+ rendered: { id: edge.bpmnElement, element: g, edge },
34
+ label: null,
35
+ kind: "edge",
36
+ });
37
+ }
38
+ for (const shape of plane.shapes) {
39
+ const { group, layer, label, rendered } = renderShapeGroup(shape, ctx);
40
+ this._layers[layer].appendChild(group);
41
+ if (label)
42
+ this._layers.labels.appendChild(label);
43
+ this._registry.set(shape.bpmnElement, { rendered, label, kind: "shape" });
44
+ }
45
+ }
46
+ /** Empties the layers and the registry. */
47
+ clear() {
48
+ this._layers.containers.replaceChildren();
49
+ this._layers.edges.replaceChildren();
50
+ this._layers.shapes.replaceChildren();
51
+ this._layers.labels.replaceChildren();
52
+ this._registry.clear();
53
+ }
54
+ /** Returns the registered shape or edge for an id, or `undefined`. */
55
+ getElement(id) {
56
+ return this._registry.get(id)?.rendered;
57
+ }
58
+ /** Returns the `<g>` graphics element for an id, or `undefined`. */
59
+ getGraphics(id) {
60
+ return this._registry.get(id)?.rendered.element;
61
+ }
62
+ /** Iterates every registered element in insertion order. */
63
+ forEach(fn) {
64
+ for (const entry of this._registry.values())
65
+ fn(entry.rendered);
66
+ }
67
+ /** All rendered shapes, in registry order. */
68
+ getShapes() {
69
+ const out = [];
70
+ for (const entry of this._registry.values()) {
71
+ if (entry.kind === "shape")
72
+ out.push(entry.rendered);
73
+ }
74
+ return out;
75
+ }
76
+ /** All rendered edges, in registry order. */
77
+ getEdges() {
78
+ const out = [];
79
+ for (const entry of this._registry.values()) {
80
+ if (entry.kind === "edge")
81
+ out.push(entry.rendered);
82
+ }
83
+ return out;
84
+ }
85
+ /**
86
+ * Re-renders a single element's `<g>` in place from the current model,
87
+ * preserving CSS classes (markers/selection) applied after the last render.
88
+ * No-op if the id is unknown or there is no current plane.
89
+ */
90
+ updateElement(id) {
91
+ const entry = this._registry.get(id);
92
+ if (!entry || !this._plane)
93
+ return;
94
+ const ctx = this._context();
95
+ const oldClasses = (entry.rendered.element.getAttribute("class") ?? "")
96
+ .split(/\s+/)
97
+ .filter(Boolean);
98
+ if (entry.kind === "edge") {
99
+ const edge = this._plane.edges.find((e) => e.bpmnElement === id);
100
+ if (!edge)
101
+ return;
102
+ const g = renderEdgeGroup(edge, ctx);
103
+ for (const c of oldClasses)
104
+ g.classList.add(c);
105
+ entry.rendered.element.replaceWith(g);
106
+ this._registry.set(id, { rendered: { id, element: g, edge }, label: null, kind: "edge" });
107
+ return;
108
+ }
109
+ const shape = this._plane.shapes.find((s) => s.bpmnElement === id);
110
+ if (!shape)
111
+ return;
112
+ const { group, layer, label, rendered } = renderShapeGroup(shape, ctx);
113
+ for (const c of oldClasses)
114
+ group.classList.add(c);
115
+ entry.rendered.element.replaceWith(group);
116
+ entry.label?.remove();
117
+ if (label)
118
+ this._layers.labels.appendChild(label);
119
+ // Keep the group in the correct layer if its target changed.
120
+ if (group.parentNode !== this._layers[layer])
121
+ this._layers[layer].appendChild(group);
122
+ this._registry.set(id, { rendered, label, kind: "shape" });
123
+ }
124
+ /** Removes an element's `<g>` (and its external label) from the scene. */
125
+ removeElement(id) {
126
+ const entry = this._registry.get(id);
127
+ if (!entry)
128
+ return;
129
+ entry.rendered.element.remove();
130
+ entry.label?.remove();
131
+ this._registry.delete(id);
132
+ }
133
+ _context() {
134
+ if (!this._defs || !this._plane) {
135
+ throw new Error("Scene has no rendered plane");
136
+ }
137
+ return buildRenderContext(this._defs, this._plane, this._drillableIds, this._instanceId);
138
+ }
139
+ }
140
+ //# sourceMappingURL=scene.js.map
package/dist/types.d.ts CHANGED
@@ -1,4 +1,7 @@
1
- import type { BpmnDefinitions, BpmnDiEdge, BpmnDiShape, BpmnFlowElement, BpmnTextAnnotation } from "@bpmnkit/core";
1
+ import type { BpmnDefinitions, BpmnDiEdge, BpmnDiShape, BpmnFlowElement, BpmnTextAnnotation, DiCompleteness } from "@bpmnkit/core";
2
+ import type { OverlayManager } from "./overlays.js";
3
+ /** Elements in the model that have no diagram interchange (BPMNShape/BPMNEdge). */
4
+ export type ImportWarnings = DiCompleteness;
2
5
  /** The color theme applied to the canvas. */
3
6
  export type Theme = "light" | "dark" | "auto" | "neon";
4
7
  /**
@@ -35,6 +38,15 @@ export interface CanvasOptions {
35
38
  * @see {@link CanvasPlugin}
36
39
  */
37
40
  plugins?: CanvasPlugin[];
41
+ /**
42
+ * What to do when the model has elements without diagram interchange
43
+ * (no `BPMNShape`/`BPMNEdge`), which would otherwise be invisible.
44
+ * - `"off"` (default) — render only elements that have DI.
45
+ * - `"all"` — if any DI is missing, auto-layout a copy of the model and
46
+ * render that. The caller's model is never mutated.
47
+ * @default "off"
48
+ */
49
+ layoutMissingDi?: "off" | "all";
38
50
  }
39
51
  /** The current pan/zoom state of the canvas viewport. */
40
52
  export interface ViewportState {
@@ -45,6 +57,29 @@ export interface ViewportState {
45
57
  /** Zoom scale factor. `1.0` = 100%, `0.5` = 50%, `2.0` = 200%. */
46
58
  scale: number;
47
59
  }
60
+ /** A rectangle in screen pixels, relative to the canvas host. */
61
+ export interface ScreenBox {
62
+ x: number;
63
+ y: number;
64
+ width: number;
65
+ height: number;
66
+ }
67
+ /**
68
+ * The visible region of the diagram, in diagram coordinates, plus the current
69
+ * zoom scale. The inverse of the pan/zoom transform applied to the viewport.
70
+ */
71
+ export interface Viewbox {
72
+ /** Diagram x-coordinate at the left edge of the viewport. */
73
+ x: number;
74
+ /** Diagram y-coordinate at the top edge of the viewport. */
75
+ y: number;
76
+ /** Width of the visible region in diagram units. */
77
+ width: number;
78
+ /** Height of the visible region in diagram units. */
79
+ height: number;
80
+ /** Current zoom scale factor. */
81
+ scale: number;
82
+ }
48
83
  /** A rendered BPMN shape with its SVG element and source model data. */
49
84
  export interface RenderedShape {
50
85
  /** The BPMN element ID. */
@@ -73,14 +108,42 @@ export interface CanvasEvents {
73
108
  "viewport:change": (state: ViewportState) => void;
74
109
  /** Fired when a BPMN element is clicked. */
75
110
  "element:click": (id: string, event: PointerEvent) => void;
111
+ /** Fired when the pointer moves onto a BPMN element (once per enter). */
112
+ "element:hover": (id: string, event: PointerEvent) => void;
113
+ /** Fired when the pointer leaves the previously-hovered element. */
114
+ "element:out": (id: string) => void;
115
+ /** Fired when a BPMN element is double-clicked. */
116
+ "element:dblclick": (id: string, event: MouseEvent) => void;
117
+ /**
118
+ * Fired when a BPMN element is right-clicked. Call `event.preventDefault()`
119
+ * in the handler to suppress the browser's native context menu.
120
+ */
121
+ "element:contextmenu": (id: string, event: MouseEvent) => void;
122
+ /** Fired when the empty canvas background (no element) is clicked. */
123
+ "canvas:click": (event: MouseEvent) => void;
76
124
  /** Fired when keyboard focus moves to a BPMN element. */
77
125
  "element:focus": (id: string) => void;
78
126
  /** Fired when keyboard focus leaves all BPMN elements. */
79
127
  "element:blur": () => void;
80
- /** Fired after a BPMN diagram is loaded and rendered. */
81
- "diagram:load": (defs: BpmnDefinitions) => void;
128
+ /**
129
+ * Fired after a BPMN diagram is loaded and rendered. `warnings` lists any
130
+ * model elements that had no diagram interchange (see {@link ImportWarnings}).
131
+ */
132
+ "diagram:load": (defs: BpmnDefinitions, warnings: ImportWarnings) => void;
82
133
  /** Fired when the canvas is cleared. */
83
134
  "diagram:clear": () => void;
135
+ /**
136
+ * Fired when the visible plane changes (drilling into a collapsed
137
+ * sub-process or navigating back). Both ids are DI plane `bpmnElement`s.
138
+ */
139
+ "plane:change": (fromPlaneId: string, toPlaneId: string) => void;
140
+ }
141
+ /** A DI plane the canvas can display (a process/collaboration or sub-process). */
142
+ export interface PlaneInfo {
143
+ /** The plane's `bpmnElement` id (process, collaboration, or sub-process id). */
144
+ id: string;
145
+ /** A human-readable label (element name, or a fallback). */
146
+ name: string;
84
147
  }
85
148
  /**
86
149
  * The stable API surface exposed to plugins.
@@ -120,6 +183,34 @@ export interface CanvasApi {
120
183
  getTheme(): Theme;
121
184
  /** Sets the color theme. Pass `"auto"` to follow the OS preference. */
122
185
  setTheme(theme: Theme): void;
186
+ /** HTML overlays anchored to diagram elements. */
187
+ readonly overlays: OverlayManager;
188
+ /** Adds a CSS class to the element with the given BPMN id. No-op if not found. */
189
+ addMarker(id: string, cls: string): void;
190
+ /** Removes a CSS class from the element with the given BPMN id. */
191
+ removeMarker(id: string, cls: string): void;
192
+ /** Returns whether the element with the given id currently has the CSS class. */
193
+ hasMarker(id: string, cls: string): boolean;
194
+ /** Toggles a CSS class on the element with the given id. */
195
+ toggleMarker(id: string, cls: string): void;
196
+ /**
197
+ * Adjusts the zoom. Pass `"fit"` (or no argument) to fit the whole diagram;
198
+ * pass a number for an absolute scale, optionally keeping `center`
199
+ * (screen-space pixels relative to the host) fixed.
200
+ */
201
+ zoom(scaleOrFit?: number | "fit", center?: {
202
+ x: number;
203
+ y: number;
204
+ }): void;
205
+ /** Returns the visible region in diagram coordinates plus the zoom scale. */
206
+ viewbox(): Viewbox;
207
+ /** Pans (without changing zoom) so the element with the given id is centred. */
208
+ scrollToElement(id: string): void;
209
+ /**
210
+ * Returns the element's bounding box in screen pixels relative to the host,
211
+ * or `null` if the element is not found.
212
+ */
213
+ getAbsoluteBBox(id: string): ScreenBox | null;
123
214
  /**
124
215
  * Subscribes to a canvas event. Returns an unsubscribe function.
125
216
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bpmnkit/canvas",
3
- "version": "0.0.27",
3
+ "version": "0.0.29",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -17,7 +17,7 @@
17
17
  "dist/**/*.d.ts"
18
18
  ],
19
19
  "dependencies": {
20
- "@bpmnkit/core": "0.0.27"
20
+ "@bpmnkit/core": "0.1.1"
21
21
  },
22
22
  "description": "Zero-dependency SVG BPMN viewer with pan/zoom, theming, and a plugin API",
23
23
  "keywords": [