@bpmnkit/canvas 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,128 @@
1
+ <div align="center">
2
+ <img src="https://raw.githubusercontent.com/bpmn-sdk/monorepo/main/doc/logos/logo-2-gateway.svg" width="72" height="72" alt="BPMN Kit logo">
3
+ <h1>@bpmnkit/canvas</h1>
4
+ <p>Zero-dependency SVG BPMN viewer with pan/zoom, theming, and a plugin API</p>
5
+
6
+ [![npm](https://img.shields.io/npm/v/@bpmnkit/canvas?style=flat-square&color=6244d7)](https://www.npmjs.com/package/@bpmnkit/canvas)
7
+ [![license](https://img.shields.io/npm/l/@bpmnkit/canvas?style=flat-square)](https://github.com/bpmnkit/monorepo/blob/main/LICENSE)
8
+ [![typescript](https://img.shields.io/badge/TypeScript-strict-6244d7?style=flat-square&logo=typescript&logoColor=white)](https://github.com/bpmnkit/monorepo)
9
+
10
+ [Documentation](https://bpmn-sdk-docs.pages.dev) · [GitHub](https://github.com/bpmnkit/monorepo) · [Changelog](https://github.com/bpmnkit/monorepo/blob/main/packages/canvas/CHANGELOG.md)
11
+ </div>
12
+
13
+ ---
14
+
15
+ ## Overview
16
+
17
+ `@bpmnkit/canvas` renders BPMN 2.0 diagrams as interactive SVG. It has zero runtime dependencies, works in any framework (or none), and exposes a plugin API for extending its behaviour.
18
+
19
+ ## Features
20
+
21
+ - **SVG rendering** — crisp diagrams at any zoom level, all element types
22
+ - **Pan & zoom** — mouse drag, scroll wheel, touch/pinch, keyboard shortcuts
23
+ - **Theming** — light / dark / system-auto, fully overridable via CSS custom properties
24
+ - **Plugin API** — install composable `CanvasPlugin` add-ons without touching core code
25
+ - **Event system** — subscribe to element clicks, selection changes, diagram load, viewport updates
26
+ - **Keyboard navigation** — arrow keys, fit-to-screen, zoom shortcuts
27
+ - **Zero dependencies** — runs in browsers, bundlers, and SSR
28
+
29
+ ## Installation
30
+
31
+ ```sh
32
+ npm install @bpmnkit/canvas
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ```typescript
38
+ import { BpmnCanvas } from "@bpmnkit/canvas"
39
+
40
+ const canvas = new BpmnCanvas({
41
+ container: document.getElementById("canvas")!,
42
+ theme: "dark", // "light" | "dark" | "auto"
43
+ grid: true, // dot-grid background
44
+ plugins: [], // CanvasPlugin[]
45
+ })
46
+
47
+ // Load a diagram
48
+ canvas.loadXML(bpmnXml)
49
+
50
+ // Respond to element clicks
51
+ canvas.on("element:click", (id, event) => {
52
+ console.log("Clicked:", id)
53
+ })
54
+
55
+ // Fit diagram to container
56
+ canvas.fit()
57
+ ```
58
+
59
+ ## API Reference
60
+
61
+ ### Constructor Options
62
+
63
+ ```typescript
64
+ interface CanvasOptions {
65
+ container: HTMLElement
66
+ theme?: "light" | "dark" | "auto" // default: "auto"
67
+ grid?: boolean // default: false
68
+ fit?: boolean // auto-fit on load, default: true
69
+ plugins?: CanvasPlugin[]
70
+ }
71
+ ```
72
+
73
+ ### CanvasApi Methods
74
+
75
+ | Method | Description |
76
+ |--------|-------------|
77
+ | `loadXML(xml)` | Parse and render a BPMN XML string |
78
+ | `fit()` | Fit the diagram to the container |
79
+ | `zoom(factor)` | Set zoom level (1 = 100%) |
80
+ | `getShapes()` | All rendered `RenderedShape` objects |
81
+ | `getEdges()` | All rendered `RenderedEdge` objects |
82
+ | `setTheme(theme)` | Switch theme at runtime |
83
+ | `destroy()` | Remove canvas and clean up |
84
+ | `on(event, handler)` | Subscribe to a canvas event |
85
+ | `off(event, handler)` | Unsubscribe |
86
+
87
+ ### Events
88
+
89
+ | Event | Payload | Description |
90
+ |-------|---------|-------------|
91
+ | `diagram:load` | `BpmnDefinitions` | Fired when a new diagram loads |
92
+ | `element:click` | `(id, PointerEvent)` | An element was clicked |
93
+ | `editor:select` | `string[]` | Selection changed (element IDs) |
94
+ | `viewport:change` | `ViewportState` | Pan or zoom occurred |
95
+
96
+ ### Plugin API
97
+
98
+ ```typescript
99
+ import type { CanvasPlugin, CanvasApi } from "@bpmnkit/canvas"
100
+
101
+ const myPlugin: CanvasPlugin = {
102
+ name: "my-plugin",
103
+ install(api: CanvasApi) {
104
+ api.on("element:click", (id) => console.log("clicked", id))
105
+ },
106
+ uninstall() {},
107
+ }
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Related Packages
113
+
114
+ | Package | Description |
115
+ |---------|-------------|
116
+ | [`@bpmnkit/core`](https://www.npmjs.com/package/@bpmnkit/core) | BPMN/DMN/Form parser, builder, layout engine |
117
+ | [`@bpmnkit/editor`](https://www.npmjs.com/package/@bpmnkit/editor) | Full-featured interactive BPMN editor |
118
+ | [`@bpmnkit/engine`](https://www.npmjs.com/package/@bpmnkit/engine) | Lightweight BPMN process execution engine |
119
+ | [`@bpmnkit/feel`](https://www.npmjs.com/package/@bpmnkit/feel) | FEEL expression language parser & evaluator |
120
+ | [`@bpmnkit/plugins`](https://www.npmjs.com/package/@bpmnkit/plugins) | 22 composable canvas plugins |
121
+ | [`@bpmnkit/api`](https://www.npmjs.com/package/@bpmnkit/api) | Camunda 8 REST API TypeScript client |
122
+ | [`@bpmnkit/ascii`](https://www.npmjs.com/package/@bpmnkit/ascii) | Render BPMN diagrams as Unicode ASCII art |
123
+ | [`@bpmnkit/profiles`](https://www.npmjs.com/package/@bpmnkit/profiles) | Shared auth, profile storage, and client factories for CLI & proxy |
124
+ | [`@bpmnkit/operate`](https://www.npmjs.com/package/@bpmnkit/operate) | Monitoring & operations frontend for Camunda clusters |
125
+
126
+ ## License
127
+
128
+ [MIT](https://github.com/bpmnkit/monorepo/blob/main/LICENSE) © bpmn-sdk
@@ -0,0 +1,114 @@
1
+ import type { BpmnDefinitions } from "@bpmnkit/core";
2
+ import type { CanvasEvents, CanvasOptions, Theme } from "./types.js";
3
+ /**
4
+ * BpmnCanvas — a high-performance, accessible BPMN 2.0 diagram viewer.
5
+ *
6
+ * ## Quick start
7
+ * ```typescript
8
+ * import { BpmnCanvas } from "@bpmnkit/canvas";
9
+ *
10
+ * const canvas = new BpmnCanvas({
11
+ * container: document.getElementById("app")!,
12
+ * xml: myBpmnXml,
13
+ * theme: "auto",
14
+ * });
15
+ * ```
16
+ *
17
+ * ## Framework integration
18
+ * The canvas is framework-agnostic and mounts into any `HTMLElement`.
19
+ *
20
+ * ### React
21
+ * ```tsx
22
+ * const ref = useRef<HTMLDivElement>(null);
23
+ * useEffect(() => {
24
+ * const canvas = new BpmnCanvas({ container: ref.current!, xml });
25
+ * return () => canvas.destroy();
26
+ * }, [xml]);
27
+ * return <div ref={ref} style={{ width: "100%", height: "500px" }} />;
28
+ * ```
29
+ *
30
+ * ### Vue
31
+ * ```vue
32
+ * <template><div ref="el" style="width:100%;height:500px" /></template>
33
+ * <script setup>
34
+ * const el = ref(null);
35
+ * onMounted(() => { canvas = new BpmnCanvas({ container: el.value, xml }); });
36
+ * onUnmounted(() => canvas?.destroy());
37
+ * </script>
38
+ * ```
39
+ *
40
+ * ## Plugin system
41
+ * Extend the canvas with custom behaviour by passing plugins to the constructor:
42
+ * ```typescript
43
+ * const canvas = new BpmnCanvas({
44
+ * container,
45
+ * plugins: [tooltipPlugin, editModePlugin],
46
+ * });
47
+ * ```
48
+ * See {@link CanvasPlugin} for the plugin contract.
49
+ */
50
+ export declare class BpmnCanvas {
51
+ private readonly _id;
52
+ private readonly _host;
53
+ private readonly _svg;
54
+ private readonly _viewportG;
55
+ private readonly _containersG;
56
+ private readonly _edgesG;
57
+ private readonly _shapesG;
58
+ private readonly _labelsG;
59
+ private _gridPattern;
60
+ private _markerId;
61
+ private readonly _viewport;
62
+ private readonly _keyboard;
63
+ private readonly _plugins;
64
+ private _shapes;
65
+ private _edges;
66
+ private _currentDefs;
67
+ private _theme;
68
+ private _fit;
69
+ private _listeners;
70
+ constructor(options: CanvasOptions);
71
+ private _ro;
72
+ /**
73
+ * Parses and renders a BPMN 2.0 XML string.
74
+ *
75
+ * @throws {Error} If the XML cannot be parsed.
76
+ */
77
+ load(xml: string): void;
78
+ /**
79
+ * Renders an already-parsed `BpmnDefinitions` model.
80
+ * Use this when you already have the parsed model from `@bpmnkit/core`.
81
+ */
82
+ loadDefinitions(defs: BpmnDefinitions): void;
83
+ /** Clears the canvas and fires `diagram:clear`. */
84
+ clear(): void;
85
+ /**
86
+ * Scales and pans the viewport to make the entire diagram visible.
87
+ * @param padding — pixels of whitespace around the diagram. Default: 40.
88
+ */
89
+ fitView(padding?: number): void;
90
+ /** Sets the color theme. Pass `"auto"` to follow the OS preference. */
91
+ setTheme(theme: Theme): void;
92
+ /** Zooms in by 25% centred on the canvas. */
93
+ zoomIn(): void;
94
+ /** Zooms out by 25% centred on the canvas. */
95
+ zoomOut(): void;
96
+ /** Resets to 100% zoom, centred on the canvas. */
97
+ resetZoom(): void;
98
+ /**
99
+ * Subscribes to a canvas event. Returns an unsubscribe function.
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * const off = canvas.on("element:click", (id) => console.log(id));
104
+ * off(); // unsubscribe
105
+ * ```
106
+ */
107
+ on<K extends keyof CanvasEvents>(event: K, handler: CanvasEvents[K]): () => void;
108
+ /** Destroys the canvas, removing all DOM nodes and event listeners. */
109
+ destroy(): void;
110
+ private _applyTheme;
111
+ private _installPlugin;
112
+ private _emit;
113
+ }
114
+ //# sourceMappingURL=canvas.d.ts.map
package/dist/canvas.js ADDED
@@ -0,0 +1,326 @@
1
+ import { Bpmn } from "@bpmnkit/core";
2
+ import { injectStyles } from "./css.js";
3
+ import { KeyboardHandler } from "./keyboard.js";
4
+ import { computeDiagramBounds, createDefs, createGrid, render } from "./renderer.js";
5
+ import { ViewportController } from "./viewport.js";
6
+ const NS = "http://www.w3.org/2000/svg";
7
+ let _instanceCounter = 0;
8
+ /**
9
+ * BpmnCanvas — a high-performance, accessible BPMN 2.0 diagram viewer.
10
+ *
11
+ * ## Quick start
12
+ * ```typescript
13
+ * import { BpmnCanvas } from "@bpmnkit/canvas";
14
+ *
15
+ * const canvas = new BpmnCanvas({
16
+ * container: document.getElementById("app")!,
17
+ * xml: myBpmnXml,
18
+ * theme: "auto",
19
+ * });
20
+ * ```
21
+ *
22
+ * ## Framework integration
23
+ * The canvas is framework-agnostic and mounts into any `HTMLElement`.
24
+ *
25
+ * ### React
26
+ * ```tsx
27
+ * const ref = useRef<HTMLDivElement>(null);
28
+ * useEffect(() => {
29
+ * const canvas = new BpmnCanvas({ container: ref.current!, xml });
30
+ * return () => canvas.destroy();
31
+ * }, [xml]);
32
+ * return <div ref={ref} style={{ width: "100%", height: "500px" }} />;
33
+ * ```
34
+ *
35
+ * ### Vue
36
+ * ```vue
37
+ * <template><div ref="el" style="width:100%;height:500px" /></template>
38
+ * <script setup>
39
+ * const el = ref(null);
40
+ * onMounted(() => { canvas = new BpmnCanvas({ container: el.value, xml }); });
41
+ * onUnmounted(() => canvas?.destroy());
42
+ * </script>
43
+ * ```
44
+ *
45
+ * ## Plugin system
46
+ * Extend the canvas with custom behaviour by passing plugins to the constructor:
47
+ * ```typescript
48
+ * const canvas = new BpmnCanvas({
49
+ * container,
50
+ * plugins: [tooltipPlugin, editModePlugin],
51
+ * });
52
+ * ```
53
+ * See {@link CanvasPlugin} for the plugin contract.
54
+ */
55
+ export class BpmnCanvas {
56
+ // ── DOM structure ─────────────────────────────────────────────────
57
+ _id;
58
+ _host;
59
+ _svg;
60
+ _viewportG;
61
+ _containersG;
62
+ _edgesG;
63
+ _shapesG;
64
+ _labelsG;
65
+ _gridPattern = null;
66
+ _markerId = "";
67
+ // ── Sub-systems ───────────────────────────────────────────────────
68
+ _viewport;
69
+ _keyboard;
70
+ _plugins = [];
71
+ // ── State ─────────────────────────────────────────────────────────
72
+ _shapes = [];
73
+ _edges = [];
74
+ _currentDefs = null;
75
+ _theme;
76
+ _fit;
77
+ // ── Event emitter ─────────────────────────────────────────────────
78
+ _listeners = new Map();
79
+ constructor(options) {
80
+ injectStyles();
81
+ this._id = String(_instanceCounter++);
82
+ this._theme = options.theme ?? "auto";
83
+ this._fit = options.fit ?? "contain";
84
+ // ── Build DOM ────────────────────────────────────────────────
85
+ const container = options.container;
86
+ container.innerHTML = "";
87
+ this._host = document.createElement("div");
88
+ this._host.className = "bpmnkit-canvas-host";
89
+ this._host.setAttribute("role", "application");
90
+ this._host.setAttribute("aria-label", "BPMN Diagram");
91
+ this._host.setAttribute("tabindex", "0");
92
+ this._applyTheme(this._theme);
93
+ container.appendChild(this._host);
94
+ // SVG root
95
+ this._svg = document.createElementNS(NS, "svg");
96
+ this._svg.setAttribute("aria-hidden", "true");
97
+ this._host.appendChild(this._svg);
98
+ // Arrow marker defs
99
+ this._markerId = createDefs(this._svg, this._id);
100
+ // Dot grid
101
+ if (options.grid !== false) {
102
+ this._gridPattern = createGrid(this._svg, this._id);
103
+ }
104
+ // Viewport group
105
+ this._viewportG = document.createElementNS(NS, "g");
106
+ this._svg.appendChild(this._viewportG);
107
+ // Layer order: containers (pools/lanes) → edges → shapes → labels
108
+ this._containersG = document.createElementNS(NS, "g");
109
+ this._edgesG = document.createElementNS(NS, "g");
110
+ this._shapesG = document.createElementNS(NS, "g");
111
+ this._labelsG = document.createElementNS(NS, "g");
112
+ this._viewportG.appendChild(this._containersG);
113
+ this._viewportG.appendChild(this._edgesG);
114
+ this._viewportG.appendChild(this._shapesG);
115
+ this._viewportG.appendChild(this._labelsG);
116
+ // ── Viewport controller ───────────────────────────────────────
117
+ this._viewport = new ViewportController(this._host, this._svg, this._viewportG, this._gridPattern, (state) => {
118
+ this._emit("viewport:change", state);
119
+ });
120
+ // ── Keyboard ──────────────────────────────────────────────────
121
+ this._keyboard = new KeyboardHandler(this._host, this._viewport, () => this.fitView(), (id) => {
122
+ const shape = this._shapes.find((s) => s.id === id);
123
+ if (shape) {
124
+ const e = new PointerEvent("click");
125
+ this._emit("element:click", id, e);
126
+ }
127
+ }, (id) => this._emit("element:focus", id), () => this._emit("element:blur"));
128
+ // ── Click detection ───────────────────────────────────────────
129
+ // Use elementFromPoint rather than e.target because native SVG hit-testing
130
+ // can return the root <svg> element even when a shape is visually under the
131
+ // cursor (reproducible when the canvas is inside a flex/scroll container).
132
+ // elementFromPoint correctly resolves the topmost painted element.
133
+ this._svg.addEventListener("click", (e) => {
134
+ if (this._viewport.didPan)
135
+ return;
136
+ // elementFromPoint resolves the correct element when native SVG hit-testing
137
+ // returns the root <svg> (reproducible in flex/scroll containers). Fall back
138
+ // to e.target for test environments where elementFromPoint isn't reliable.
139
+ const fromPoint = document.elementFromPoint(e.clientX, e.clientY);
140
+ const target = fromPoint?.closest("[data-bpmnkit-id]") ??
141
+ e.target.closest("[data-bpmnkit-id]");
142
+ const id = target?.getAttribute("data-bpmnkit-id");
143
+ if (id)
144
+ this._emit("element:click", id, e);
145
+ });
146
+ // ── Install plugins ───────────────────────────────────────────
147
+ if (options.plugins) {
148
+ for (const plugin of options.plugins) {
149
+ this._installPlugin(plugin);
150
+ }
151
+ }
152
+ // ── Initial diagram ───────────────────────────────────────────
153
+ if (options.xml) {
154
+ this.load(options.xml);
155
+ }
156
+ // Re-fit on container resize
157
+ const ro = new ResizeObserver(() => {
158
+ if (this._currentDefs)
159
+ this.fitView();
160
+ });
161
+ ro.observe(this._host);
162
+ this._ro = ro;
163
+ }
164
+ _ro;
165
+ // ── Public API ────────────────────────────────────────────────────
166
+ /**
167
+ * Parses and renders a BPMN 2.0 XML string.
168
+ *
169
+ * @throws {Error} If the XML cannot be parsed.
170
+ */
171
+ load(xml) {
172
+ const defs = Bpmn.parse(xml);
173
+ this.loadDefinitions(defs);
174
+ }
175
+ /**
176
+ * Renders an already-parsed `BpmnDefinitions` model.
177
+ * Use this when you already have the parsed model from `@bpmnkit/core`.
178
+ */
179
+ loadDefinitions(defs) {
180
+ // Clear previous content
181
+ this._containersG.innerHTML = "";
182
+ this._edgesG.innerHTML = "";
183
+ this._shapesG.innerHTML = "";
184
+ this._labelsG.innerHTML = "";
185
+ this._shapes = [];
186
+ this._edges = [];
187
+ this._currentDefs = defs;
188
+ const result = render(defs, this._containersG, this._edgesG, this._shapesG, this._labelsG, this._markerId, this._id);
189
+ this._shapes = result.shapes;
190
+ this._edges = result.edges;
191
+ this._keyboard.setShapes(this._shapes);
192
+ if (this._fit !== "none") {
193
+ // Defer fit to next frame so the SVG has been laid out
194
+ requestAnimationFrame(() => this.fitView());
195
+ }
196
+ this._emit("diagram:load", defs);
197
+ }
198
+ /** Clears the canvas and fires `diagram:clear`. */
199
+ clear() {
200
+ this._containersG.innerHTML = "";
201
+ this._edgesG.innerHTML = "";
202
+ this._shapesG.innerHTML = "";
203
+ this._labelsG.innerHTML = "";
204
+ this._shapes = [];
205
+ this._edges = [];
206
+ this._currentDefs = null;
207
+ this._emit("diagram:clear");
208
+ }
209
+ /**
210
+ * Scales and pans the viewport to make the entire diagram visible.
211
+ * @param padding — pixels of whitespace around the diagram. Default: 40.
212
+ */
213
+ fitView(padding = 40) {
214
+ if (!this._currentDefs)
215
+ return;
216
+ const bounds = computeDiagramBounds(this._currentDefs);
217
+ if (!bounds)
218
+ return;
219
+ const svgW = this._svg.clientWidth;
220
+ const svgH = this._svg.clientHeight;
221
+ if (svgW === 0 || svgH === 0)
222
+ return;
223
+ const dW = bounds.maxX - bounds.minX;
224
+ const dH = bounds.maxY - bounds.minY;
225
+ if (dW === 0 || dH === 0)
226
+ return;
227
+ const scaleX = (svgW - padding * 2) / dW;
228
+ const scaleY = (svgH - padding * 2) / dH;
229
+ let scale = Math.min(scaleX, scaleY);
230
+ if (this._fit === "center")
231
+ scale = 1;
232
+ const tx = (svgW - dW * scale) / 2 - bounds.minX * scale;
233
+ const ty = (svgH - dH * scale) / 2 - bounds.minY * scale;
234
+ this._viewport.set({ tx, ty, scale });
235
+ }
236
+ /** Sets the color theme. Pass `"auto"` to follow the OS preference. */
237
+ setTheme(theme) {
238
+ this._theme = theme;
239
+ this._applyTheme(theme);
240
+ }
241
+ /** Zooms in by 25% centred on the canvas. */
242
+ zoomIn() {
243
+ const { width, height } = this._svg.getBoundingClientRect();
244
+ this._viewport.zoomAt(width / 2, height / 2, 1.25);
245
+ }
246
+ /** Zooms out by 25% centred on the canvas. */
247
+ zoomOut() {
248
+ const { width, height } = this._svg.getBoundingClientRect();
249
+ this._viewport.zoomAt(width / 2, height / 2, 0.8);
250
+ }
251
+ /** Resets to 100% zoom, centred on the canvas. */
252
+ resetZoom() {
253
+ const { width, height } = this._svg.getBoundingClientRect();
254
+ this._viewport.set({ scale: 1, tx: width / 2, ty: height / 2 });
255
+ }
256
+ /**
257
+ * Subscribes to a canvas event. Returns an unsubscribe function.
258
+ *
259
+ * @example
260
+ * ```typescript
261
+ * const off = canvas.on("element:click", (id) => console.log(id));
262
+ * off(); // unsubscribe
263
+ * ```
264
+ */
265
+ on(event, handler) {
266
+ let set = this._listeners.get(event);
267
+ if (!set) {
268
+ set = new Set();
269
+ this._listeners.set(event, set);
270
+ }
271
+ set.add(handler);
272
+ return () => set.delete(handler);
273
+ }
274
+ /** Destroys the canvas, removing all DOM nodes and event listeners. */
275
+ destroy() {
276
+ this._ro.disconnect();
277
+ this._viewport.destroy();
278
+ this._keyboard.destroy();
279
+ for (const plugin of this._plugins)
280
+ plugin.uninstall?.();
281
+ this._plugins.length = 0;
282
+ this._listeners.clear();
283
+ this._host.remove();
284
+ }
285
+ // ── Private ───────────────────────────────────────────────────────
286
+ _applyTheme(theme) {
287
+ const resolved = theme === "auto"
288
+ ? window.matchMedia("(prefers-color-scheme: dark)").matches
289
+ ? "dark"
290
+ : "light"
291
+ : theme;
292
+ if (resolved === "dark") {
293
+ this._host.setAttribute("data-theme", "dark");
294
+ }
295
+ else {
296
+ this._host.removeAttribute("data-theme");
297
+ }
298
+ }
299
+ _installPlugin(plugin) {
300
+ this._plugins.push(plugin);
301
+ const api = {
302
+ container: this._host,
303
+ svg: this._svg,
304
+ viewportEl: this._viewportG,
305
+ getViewport: () => this._viewport.state,
306
+ setViewport: (s) => this._viewport.set(s),
307
+ getShapes: () => [...this._shapes],
308
+ getEdges: () => [...this._edges],
309
+ getTheme: () => this._theme,
310
+ setTheme: (theme) => this.setTheme(theme),
311
+ on: (event, handler) => this.on(event, handler),
312
+ emit: (event, ...args) => this._emit(event, ...args),
313
+ };
314
+ plugin.install(api);
315
+ }
316
+ _emit(event, ...args) {
317
+ const handlers = this._listeners.get(event);
318
+ if (!handlers)
319
+ return;
320
+ for (const h of handlers) {
321
+ ;
322
+ h(...args);
323
+ }
324
+ }
325
+ }
326
+ //# sourceMappingURL=canvas.js.map
package/dist/css.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /** ID used to prevent duplicate style injection. */
2
+ export declare const STYLE_ID = "bpmnkit-canvas-styles-v1";
3
+ /** Complete CSS for the BpmnCanvas component, injected once into `<head>`. */
4
+ export declare const CANVAS_CSS = "\n.bpmnkit-canvas-host {\n position: relative;\n overflow: hidden;\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n -webkit-user-select: none;\n user-select: none;\n touch-action: none;\n background: var(--bpmnkit-bg, #f8f9fa);\n}\n.bpmnkit-canvas-host *,\n.bpmnkit-canvas-host *::before,\n.bpmnkit-canvas-host *::after {\n box-sizing: border-box;\n}\n.bpmnkit-canvas-host > svg {\n display: block;\n width: 100%;\n height: 100%;\n cursor: default;\n}\n.bpmnkit-canvas-host.is-panning > svg {\n cursor: grabbing;\n}\n.bpmnkit-canvas-host > svg:focus {\n outline: none;\n}\n\n/* \u2500\u2500 Shapes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.bpmnkit-shape {\n cursor: pointer;\n outline: none;\n}\n.bpmnkit-shape:focus .bpmnkit-shape-body,\n.bpmnkit-shape:focus .bpmnkit-event-body,\n.bpmnkit-shape:focus .bpmnkit-gw-body,\n.bpmnkit-shape:focus .bpmnkit-pool-body,\n.bpmnkit-shape:focus .bpmnkit-lane-body {\n stroke: var(--bpmnkit-focus, #0066cc);\n stroke-width: 2.5;\n}\n.bpmnkit-shape.bpmnkit-selected .bpmnkit-shape-body,\n.bpmnkit-shape.bpmnkit-selected .bpmnkit-event-body,\n.bpmnkit-shape.bpmnkit-selected .bpmnkit-gw-body,\n.bpmnkit-shape.bpmnkit-selected .bpmnkit-pool-body,\n.bpmnkit-shape.bpmnkit-selected .bpmnkit-lane-body {\n stroke: var(--bpmnkit-highlight, #0066cc);\n stroke-width: 2.5;\n}\n\n.bpmnkit-shape-body,\n.bpmnkit-event-body,\n.bpmnkit-gw-body {\n fill: var(--bpmnkit-shape-fill, #ffffff);\n stroke: var(--bpmnkit-shape-stroke, #404040);\n stroke-width: 1.5;\n}\n.bpmnkit-end-body {\n fill: var(--bpmnkit-shape-fill, #ffffff);\n stroke: var(--bpmnkit-shape-stroke, #404040);\n stroke-width: 3;\n}\n.bpmnkit-event-inner {\n fill: none;\n stroke: var(--bpmnkit-shape-stroke, #404040);\n stroke-width: 1.5;\n}\n.bpmnkit-event-inner-dashed {\n fill: none;\n stroke: var(--bpmnkit-shape-stroke, #404040);\n stroke-width: 1.5;\n stroke-dasharray: 4 2;\n}\n.bpmnkit-eventsubprocess-body {\n fill: var(--bpmnkit-shape-fill, #ffffff);\n stroke: var(--bpmnkit-shape-stroke, #404040);\n stroke-width: 1.5;\n stroke-dasharray: 5 3;\n}\n.bpmnkit-callactivity-body {\n fill: var(--bpmnkit-shape-fill, #ffffff);\n stroke: var(--bpmnkit-shape-stroke, #404040);\n stroke-width: 3;\n}\n\n/* \u2500\u2500 Icons \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.bpmnkit-icon {\n fill: none;\n stroke: var(--bpmnkit-shape-stroke, #404040);\n stroke-width: 1.5;\n stroke-linecap: round;\n stroke-linejoin: round;\n pointer-events: none;\n}\n.bpmnkit-icon-solid {\n fill: var(--bpmnkit-shape-stroke, #404040);\n stroke: none;\n pointer-events: none;\n}\n.bpmnkit-gw-marker {\n fill: var(--bpmnkit-shape-stroke, #404040);\n stroke: none;\n pointer-events: none;\n}\n.bpmnkit-gw-marker-stroke {\n fill: none;\n stroke: var(--bpmnkit-shape-stroke, #404040);\n stroke-width: 2.5;\n stroke-linecap: round;\n pointer-events: none;\n}\n\n/* \u2500\u2500 Pool / Lane \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.bpmnkit-pool-body,\n.bpmnkit-lane-body {\n fill: var(--bpmnkit-shape-fill, #ffffff);\n stroke: var(--bpmnkit-shape-stroke, #404040);\n stroke-width: 1.5;\n}\n.bpmnkit-pool-header,\n.bpmnkit-lane-header {\n fill: var(--bpmnkit-pool-header, rgba(0,0,0,0.04));\n stroke: var(--bpmnkit-shape-stroke, #404040);\n stroke-width: 1.5;\n}\n[data-theme=\"dark\"] .bpmnkit-pool-header,\n[data-theme=\"dark\"] .bpmnkit-lane-header {\n fill: rgba(255,255,255,0.06);\n}\n\n/* \u2500\u2500 Message flow \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.bpmnkit-msgflow-path {\n fill: none;\n stroke: var(--bpmnkit-flow-stroke, #404040);\n stroke-width: 1.5;\n stroke-dasharray: 6 3;\n}\n\n/* \u2500\u2500 Edges \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.bpmnkit-edge {\n cursor: pointer;\n}\n.bpmnkit-edge-path {\n fill: none;\n stroke: var(--bpmnkit-flow-stroke, #404040);\n stroke-width: 1.5;\n}\n.bpmnkit-edge-assoc {\n fill: none;\n stroke: var(--bpmnkit-flow-stroke, #404040);\n stroke-width: 1.5;\n stroke-dasharray: 5 3;\n}\n.bpmnkit-edge-default-slash {\n stroke: var(--bpmnkit-flow-stroke, #404040);\n stroke-width: 1.5;\n}\n.bpmnkit-arrow-fill {\n fill: var(--bpmnkit-flow-stroke, #404040);\n}\n\n/* \u2500\u2500 Labels \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.bpmnkit-label {\n fill: var(--bpmnkit-text, #333333);\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 11px;\n text-anchor: middle;\n dominant-baseline: central;\n pointer-events: none;\n /* White halo behind text so labels never visually merge with edge lines */\n paint-order: stroke;\n stroke: var(--bpmnkit-bg, #f8f9fa);\n stroke-width: 4px;\n stroke-linejoin: round;\n}\n\n/* \u2500\u2500 Light theme (default) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.bpmnkit-canvas-host {\n --bpmnkit-bg: #f8f9fa;\n --bpmnkit-grid: rgba(0, 0, 0, 0.14);\n --bpmnkit-shape-fill: #ffffff;\n --bpmnkit-shape-stroke: #404040;\n --bpmnkit-flow-stroke: #404040;\n --bpmnkit-text: var(--bpmnkit-fg, #333333);\n --bpmnkit-highlight: var(--bpmnkit-accent, #1a56db);\n --bpmnkit-focus: #0066cc;\n --bpmnkit-overlay-bg: rgba(248, 249, 250, 0.92);\n --bpmnkit-overlay-border: rgba(0, 0, 0, 0.12);\n}\n\n/* \u2500\u2500 Dark theme \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.bpmnkit-canvas-host[data-theme=\"dark\"] {\n --bpmnkit-bg: #0d0d16;\n --bpmnkit-grid: rgba(255, 255, 255, 0.07);\n --bpmnkit-shape-fill: #1e1e2e;\n --bpmnkit-shape-stroke: #8888bb;\n --bpmnkit-flow-stroke: #7777aa;\n --bpmnkit-text: var(--bpmnkit-fg, #cdd6f4);\n --bpmnkit-highlight: var(--bpmnkit-accent-bright, #89b4fa);\n --bpmnkit-focus: #89b4fa;\n --bpmnkit-overlay-bg: rgba(30, 30, 46, 0.92);\n --bpmnkit-overlay-border: rgba(255, 255, 255, 0.1);\n}\n.bpmnkit-canvas-host[data-theme=\"dark\"] .bpmnkit-edge-hover-dot { fill: var(--bpmnkit-accent, #6b9df7); }\n.bpmnkit-canvas-host[data-theme=\"dark\"] .bpmnkit-edge-waypoint-ball { fill: var(--bpmnkit-accent, #6b9df7); }\n";
5
+ /**
6
+ * Injects the canvas stylesheet into `<head>` if not already present.
7
+ * Safe to call multiple times — only one `<style>` tag is ever inserted.
8
+ */
9
+ export declare function injectStyles(): void;
10
+ //# sourceMappingURL=css.d.ts.map