@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 +128 -0
- package/dist/canvas.d.ts +114 -0
- package/dist/canvas.js +326 -0
- package/dist/css.d.ts +10 -0
- package/dist/css.js +226 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +72 -0
- package/dist/keyboard.d.ts +40 -0
- package/dist/keyboard.js +166 -0
- package/dist/minimap.d.ts +37 -0
- package/dist/minimap.js +171 -0
- package/dist/renderer.d.ts +40 -0
- package/dist/renderer.js +835 -0
- package/dist/types.d.ts +177 -0
- package/dist/types.js +2 -0
- package/dist/viewport.d.ts +77 -0
- package/dist/viewport.js +203 -0
- package/package.json +30 -0
package/dist/css.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/** ID used to prevent duplicate style injection. */
|
|
2
|
+
export const STYLE_ID = "bpmnkit-canvas-styles-v1";
|
|
3
|
+
/** Complete CSS for the BpmnCanvas component, injected once into `<head>`. */
|
|
4
|
+
export const CANVAS_CSS = `
|
|
5
|
+
.bpmnkit-canvas-host {
|
|
6
|
+
position: relative;
|
|
7
|
+
overflow: hidden;
|
|
8
|
+
width: 100%;
|
|
9
|
+
height: 100%;
|
|
10
|
+
box-sizing: border-box;
|
|
11
|
+
-webkit-user-select: none;
|
|
12
|
+
user-select: none;
|
|
13
|
+
touch-action: none;
|
|
14
|
+
background: var(--bpmnkit-bg, #f8f9fa);
|
|
15
|
+
}
|
|
16
|
+
.bpmnkit-canvas-host *,
|
|
17
|
+
.bpmnkit-canvas-host *::before,
|
|
18
|
+
.bpmnkit-canvas-host *::after {
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
}
|
|
21
|
+
.bpmnkit-canvas-host > svg {
|
|
22
|
+
display: block;
|
|
23
|
+
width: 100%;
|
|
24
|
+
height: 100%;
|
|
25
|
+
cursor: default;
|
|
26
|
+
}
|
|
27
|
+
.bpmnkit-canvas-host.is-panning > svg {
|
|
28
|
+
cursor: grabbing;
|
|
29
|
+
}
|
|
30
|
+
.bpmnkit-canvas-host > svg:focus {
|
|
31
|
+
outline: none;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* ── Shapes ─────────────────────────────────────────────────────── */
|
|
35
|
+
.bpmnkit-shape {
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
outline: none;
|
|
38
|
+
}
|
|
39
|
+
.bpmnkit-shape:focus .bpmnkit-shape-body,
|
|
40
|
+
.bpmnkit-shape:focus .bpmnkit-event-body,
|
|
41
|
+
.bpmnkit-shape:focus .bpmnkit-gw-body,
|
|
42
|
+
.bpmnkit-shape:focus .bpmnkit-pool-body,
|
|
43
|
+
.bpmnkit-shape:focus .bpmnkit-lane-body {
|
|
44
|
+
stroke: var(--bpmnkit-focus, #0066cc);
|
|
45
|
+
stroke-width: 2.5;
|
|
46
|
+
}
|
|
47
|
+
.bpmnkit-shape.bpmnkit-selected .bpmnkit-shape-body,
|
|
48
|
+
.bpmnkit-shape.bpmnkit-selected .bpmnkit-event-body,
|
|
49
|
+
.bpmnkit-shape.bpmnkit-selected .bpmnkit-gw-body,
|
|
50
|
+
.bpmnkit-shape.bpmnkit-selected .bpmnkit-pool-body,
|
|
51
|
+
.bpmnkit-shape.bpmnkit-selected .bpmnkit-lane-body {
|
|
52
|
+
stroke: var(--bpmnkit-highlight, #0066cc);
|
|
53
|
+
stroke-width: 2.5;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.bpmnkit-shape-body,
|
|
57
|
+
.bpmnkit-event-body,
|
|
58
|
+
.bpmnkit-gw-body {
|
|
59
|
+
fill: var(--bpmnkit-shape-fill, #ffffff);
|
|
60
|
+
stroke: var(--bpmnkit-shape-stroke, #404040);
|
|
61
|
+
stroke-width: 1.5;
|
|
62
|
+
}
|
|
63
|
+
.bpmnkit-end-body {
|
|
64
|
+
fill: var(--bpmnkit-shape-fill, #ffffff);
|
|
65
|
+
stroke: var(--bpmnkit-shape-stroke, #404040);
|
|
66
|
+
stroke-width: 3;
|
|
67
|
+
}
|
|
68
|
+
.bpmnkit-event-inner {
|
|
69
|
+
fill: none;
|
|
70
|
+
stroke: var(--bpmnkit-shape-stroke, #404040);
|
|
71
|
+
stroke-width: 1.5;
|
|
72
|
+
}
|
|
73
|
+
.bpmnkit-event-inner-dashed {
|
|
74
|
+
fill: none;
|
|
75
|
+
stroke: var(--bpmnkit-shape-stroke, #404040);
|
|
76
|
+
stroke-width: 1.5;
|
|
77
|
+
stroke-dasharray: 4 2;
|
|
78
|
+
}
|
|
79
|
+
.bpmnkit-eventsubprocess-body {
|
|
80
|
+
fill: var(--bpmnkit-shape-fill, #ffffff);
|
|
81
|
+
stroke: var(--bpmnkit-shape-stroke, #404040);
|
|
82
|
+
stroke-width: 1.5;
|
|
83
|
+
stroke-dasharray: 5 3;
|
|
84
|
+
}
|
|
85
|
+
.bpmnkit-callactivity-body {
|
|
86
|
+
fill: var(--bpmnkit-shape-fill, #ffffff);
|
|
87
|
+
stroke: var(--bpmnkit-shape-stroke, #404040);
|
|
88
|
+
stroke-width: 3;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* ── Icons ───────────────────────────────────────────────────────── */
|
|
92
|
+
.bpmnkit-icon {
|
|
93
|
+
fill: none;
|
|
94
|
+
stroke: var(--bpmnkit-shape-stroke, #404040);
|
|
95
|
+
stroke-width: 1.5;
|
|
96
|
+
stroke-linecap: round;
|
|
97
|
+
stroke-linejoin: round;
|
|
98
|
+
pointer-events: none;
|
|
99
|
+
}
|
|
100
|
+
.bpmnkit-icon-solid {
|
|
101
|
+
fill: var(--bpmnkit-shape-stroke, #404040);
|
|
102
|
+
stroke: none;
|
|
103
|
+
pointer-events: none;
|
|
104
|
+
}
|
|
105
|
+
.bpmnkit-gw-marker {
|
|
106
|
+
fill: var(--bpmnkit-shape-stroke, #404040);
|
|
107
|
+
stroke: none;
|
|
108
|
+
pointer-events: none;
|
|
109
|
+
}
|
|
110
|
+
.bpmnkit-gw-marker-stroke {
|
|
111
|
+
fill: none;
|
|
112
|
+
stroke: var(--bpmnkit-shape-stroke, #404040);
|
|
113
|
+
stroke-width: 2.5;
|
|
114
|
+
stroke-linecap: round;
|
|
115
|
+
pointer-events: none;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* ── Pool / Lane ─────────────────────────────────────────────────── */
|
|
119
|
+
.bpmnkit-pool-body,
|
|
120
|
+
.bpmnkit-lane-body {
|
|
121
|
+
fill: var(--bpmnkit-shape-fill, #ffffff);
|
|
122
|
+
stroke: var(--bpmnkit-shape-stroke, #404040);
|
|
123
|
+
stroke-width: 1.5;
|
|
124
|
+
}
|
|
125
|
+
.bpmnkit-pool-header,
|
|
126
|
+
.bpmnkit-lane-header {
|
|
127
|
+
fill: var(--bpmnkit-pool-header, rgba(0,0,0,0.04));
|
|
128
|
+
stroke: var(--bpmnkit-shape-stroke, #404040);
|
|
129
|
+
stroke-width: 1.5;
|
|
130
|
+
}
|
|
131
|
+
[data-theme="dark"] .bpmnkit-pool-header,
|
|
132
|
+
[data-theme="dark"] .bpmnkit-lane-header {
|
|
133
|
+
fill: rgba(255,255,255,0.06);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* ── Message flow ─────────────────────────────────────────────────── */
|
|
137
|
+
.bpmnkit-msgflow-path {
|
|
138
|
+
fill: none;
|
|
139
|
+
stroke: var(--bpmnkit-flow-stroke, #404040);
|
|
140
|
+
stroke-width: 1.5;
|
|
141
|
+
stroke-dasharray: 6 3;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* ── Edges ───────────────────────────────────────────────────────── */
|
|
145
|
+
.bpmnkit-edge {
|
|
146
|
+
cursor: pointer;
|
|
147
|
+
}
|
|
148
|
+
.bpmnkit-edge-path {
|
|
149
|
+
fill: none;
|
|
150
|
+
stroke: var(--bpmnkit-flow-stroke, #404040);
|
|
151
|
+
stroke-width: 1.5;
|
|
152
|
+
}
|
|
153
|
+
.bpmnkit-edge-assoc {
|
|
154
|
+
fill: none;
|
|
155
|
+
stroke: var(--bpmnkit-flow-stroke, #404040);
|
|
156
|
+
stroke-width: 1.5;
|
|
157
|
+
stroke-dasharray: 5 3;
|
|
158
|
+
}
|
|
159
|
+
.bpmnkit-edge-default-slash {
|
|
160
|
+
stroke: var(--bpmnkit-flow-stroke, #404040);
|
|
161
|
+
stroke-width: 1.5;
|
|
162
|
+
}
|
|
163
|
+
.bpmnkit-arrow-fill {
|
|
164
|
+
fill: var(--bpmnkit-flow-stroke, #404040);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* ── Labels ─────────────────────────────────────────────────────── */
|
|
168
|
+
.bpmnkit-label {
|
|
169
|
+
fill: var(--bpmnkit-text, #333333);
|
|
170
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
171
|
+
font-size: 11px;
|
|
172
|
+
text-anchor: middle;
|
|
173
|
+
dominant-baseline: central;
|
|
174
|
+
pointer-events: none;
|
|
175
|
+
/* White halo behind text so labels never visually merge with edge lines */
|
|
176
|
+
paint-order: stroke;
|
|
177
|
+
stroke: var(--bpmnkit-bg, #f8f9fa);
|
|
178
|
+
stroke-width: 4px;
|
|
179
|
+
stroke-linejoin: round;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* ── Light theme (default) ───────────────────────────────────────── */
|
|
183
|
+
.bpmnkit-canvas-host {
|
|
184
|
+
--bpmnkit-bg: #f8f9fa;
|
|
185
|
+
--bpmnkit-grid: rgba(0, 0, 0, 0.14);
|
|
186
|
+
--bpmnkit-shape-fill: #ffffff;
|
|
187
|
+
--bpmnkit-shape-stroke: #404040;
|
|
188
|
+
--bpmnkit-flow-stroke: #404040;
|
|
189
|
+
--bpmnkit-text: var(--bpmnkit-fg, #333333);
|
|
190
|
+
--bpmnkit-highlight: var(--bpmnkit-accent, #1a56db);
|
|
191
|
+
--bpmnkit-focus: #0066cc;
|
|
192
|
+
--bpmnkit-overlay-bg: rgba(248, 249, 250, 0.92);
|
|
193
|
+
--bpmnkit-overlay-border: rgba(0, 0, 0, 0.12);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* ── Dark theme ──────────────────────────────────────────────────── */
|
|
197
|
+
.bpmnkit-canvas-host[data-theme="dark"] {
|
|
198
|
+
--bpmnkit-bg: #0d0d16;
|
|
199
|
+
--bpmnkit-grid: rgba(255, 255, 255, 0.07);
|
|
200
|
+
--bpmnkit-shape-fill: #1e1e2e;
|
|
201
|
+
--bpmnkit-shape-stroke: #8888bb;
|
|
202
|
+
--bpmnkit-flow-stroke: #7777aa;
|
|
203
|
+
--bpmnkit-text: var(--bpmnkit-fg, #cdd6f4);
|
|
204
|
+
--bpmnkit-highlight: var(--bpmnkit-accent-bright, #89b4fa);
|
|
205
|
+
--bpmnkit-focus: #89b4fa;
|
|
206
|
+
--bpmnkit-overlay-bg: rgba(30, 30, 46, 0.92);
|
|
207
|
+
--bpmnkit-overlay-border: rgba(255, 255, 255, 0.1);
|
|
208
|
+
}
|
|
209
|
+
.bpmnkit-canvas-host[data-theme="dark"] .bpmnkit-edge-hover-dot { fill: var(--bpmnkit-accent, #6b9df7); }
|
|
210
|
+
.bpmnkit-canvas-host[data-theme="dark"] .bpmnkit-edge-waypoint-ball { fill: var(--bpmnkit-accent, #6b9df7); }
|
|
211
|
+
`;
|
|
212
|
+
/**
|
|
213
|
+
* Injects the canvas stylesheet into `<head>` if not already present.
|
|
214
|
+
* Safe to call multiple times — only one `<style>` tag is ever inserted.
|
|
215
|
+
*/
|
|
216
|
+
export function injectStyles() {
|
|
217
|
+
if (typeof document === "undefined")
|
|
218
|
+
return;
|
|
219
|
+
if (document.getElementById(STYLE_ID))
|
|
220
|
+
return;
|
|
221
|
+
const style = document.createElement("style");
|
|
222
|
+
style.id = STYLE_ID;
|
|
223
|
+
style.textContent = CANVAS_CSS;
|
|
224
|
+
document.head.appendChild(style);
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=css.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @bpmnkit/canvas — a high-performance, accessible BPMN 2.0 diagram viewer.
|
|
3
|
+
*
|
|
4
|
+
* ## Features
|
|
5
|
+
* - **Zero external dependencies** (only `@bpmnkit/core` for XML parsing)
|
|
6
|
+
* - **Infinite canvas** with pan and zoom (mouse, touch, keyboard)
|
|
7
|
+
* - **Dot-grid background** for visual orientation
|
|
8
|
+
* - **Light / dark / auto theming** via CSS custom properties
|
|
9
|
+
* - **First-class accessibility** — ARIA roles, keyboard navigation, focus management
|
|
10
|
+
* - **Framework-agnostic** — plain DOM, works in React, Vue, Svelte, Angular, or vanilla JS
|
|
11
|
+
* - **Plugin system** — extend with editing, tooltips, minimap, custom shapes, and more
|
|
12
|
+
*
|
|
13
|
+
* ## Quick start
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { BpmnCanvas } from "@bpmnkit/canvas";
|
|
16
|
+
*
|
|
17
|
+
* const canvas = new BpmnCanvas({
|
|
18
|
+
* container: document.getElementById("app")!,
|
|
19
|
+
* xml: myBpmnXml, // BPMN 2.0 XML string
|
|
20
|
+
* theme: "auto", // follows OS preference
|
|
21
|
+
* grid: true, // dot-grid background
|
|
22
|
+
* fit: "contain", // scale to fit on load
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* // Listen to events
|
|
26
|
+
* canvas.on("element:click", (id) => console.log("clicked", id));
|
|
27
|
+
*
|
|
28
|
+
* // Programmatic control
|
|
29
|
+
* canvas.fitView();
|
|
30
|
+
* canvas.zoomIn();
|
|
31
|
+
* canvas.setTheme("dark");
|
|
32
|
+
*
|
|
33
|
+
* // Always clean up
|
|
34
|
+
* canvas.destroy();
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* ## Embedding in React
|
|
38
|
+
* ```tsx
|
|
39
|
+
* function BpmnViewer({ xml }: { xml: string }) {
|
|
40
|
+
* const ref = useRef<HTMLDivElement>(null);
|
|
41
|
+
* useEffect(() => {
|
|
42
|
+
* if (!ref.current) return;
|
|
43
|
+
* const canvas = new BpmnCanvas({ container: ref.current, xml });
|
|
44
|
+
* return () => canvas.destroy();
|
|
45
|
+
* }, [xml]);
|
|
46
|
+
* return <div ref={ref} style={{ width: "100%", height: "500px" }} />;
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* ## Plugin example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const tooltipPlugin: CanvasPlugin = {
|
|
53
|
+
* name: "tooltips",
|
|
54
|
+
* install(api) {
|
|
55
|
+
* api.on("element:click", (id) => {
|
|
56
|
+
* const shape = api.getShapes().find((s) => s.id === id);
|
|
57
|
+
* alert(shape?.flowElement?.name ?? id);
|
|
58
|
+
* });
|
|
59
|
+
* },
|
|
60
|
+
* };
|
|
61
|
+
* const canvas = new BpmnCanvas({ container, plugins: [tooltipPlugin] });
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* @packageDocumentation
|
|
65
|
+
*/
|
|
66
|
+
export { BpmnCanvas } from "./canvas.js";
|
|
67
|
+
export type { CanvasApi, CanvasEvents, CanvasOptions, CanvasPlugin, FitMode, RenderedEdge, RenderedShape, Theme, ViewportState, } from "./types.js";
|
|
68
|
+
export { ViewportController } from "./viewport.js";
|
|
69
|
+
export { render, computeDiagramBounds, createDefs, createGrid } from "./renderer.js";
|
|
70
|
+
export { KeyboardHandler } from "./keyboard.js";
|
|
71
|
+
export { injectStyles, CANVAS_CSS, STYLE_ID } from "./css.js";
|
|
72
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @bpmnkit/canvas — a high-performance, accessible BPMN 2.0 diagram viewer.
|
|
3
|
+
*
|
|
4
|
+
* ## Features
|
|
5
|
+
* - **Zero external dependencies** (only `@bpmnkit/core` for XML parsing)
|
|
6
|
+
* - **Infinite canvas** with pan and zoom (mouse, touch, keyboard)
|
|
7
|
+
* - **Dot-grid background** for visual orientation
|
|
8
|
+
* - **Light / dark / auto theming** via CSS custom properties
|
|
9
|
+
* - **First-class accessibility** — ARIA roles, keyboard navigation, focus management
|
|
10
|
+
* - **Framework-agnostic** — plain DOM, works in React, Vue, Svelte, Angular, or vanilla JS
|
|
11
|
+
* - **Plugin system** — extend with editing, tooltips, minimap, custom shapes, and more
|
|
12
|
+
*
|
|
13
|
+
* ## Quick start
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { BpmnCanvas } from "@bpmnkit/canvas";
|
|
16
|
+
*
|
|
17
|
+
* const canvas = new BpmnCanvas({
|
|
18
|
+
* container: document.getElementById("app")!,
|
|
19
|
+
* xml: myBpmnXml, // BPMN 2.0 XML string
|
|
20
|
+
* theme: "auto", // follows OS preference
|
|
21
|
+
* grid: true, // dot-grid background
|
|
22
|
+
* fit: "contain", // scale to fit on load
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* // Listen to events
|
|
26
|
+
* canvas.on("element:click", (id) => console.log("clicked", id));
|
|
27
|
+
*
|
|
28
|
+
* // Programmatic control
|
|
29
|
+
* canvas.fitView();
|
|
30
|
+
* canvas.zoomIn();
|
|
31
|
+
* canvas.setTheme("dark");
|
|
32
|
+
*
|
|
33
|
+
* // Always clean up
|
|
34
|
+
* canvas.destroy();
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* ## Embedding in React
|
|
38
|
+
* ```tsx
|
|
39
|
+
* function BpmnViewer({ xml }: { xml: string }) {
|
|
40
|
+
* const ref = useRef<HTMLDivElement>(null);
|
|
41
|
+
* useEffect(() => {
|
|
42
|
+
* if (!ref.current) return;
|
|
43
|
+
* const canvas = new BpmnCanvas({ container: ref.current, xml });
|
|
44
|
+
* return () => canvas.destroy();
|
|
45
|
+
* }, [xml]);
|
|
46
|
+
* return <div ref={ref} style={{ width: "100%", height: "500px" }} />;
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* ## Plugin example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const tooltipPlugin: CanvasPlugin = {
|
|
53
|
+
* name: "tooltips",
|
|
54
|
+
* install(api) {
|
|
55
|
+
* api.on("element:click", (id) => {
|
|
56
|
+
* const shape = api.getShapes().find((s) => s.id === id);
|
|
57
|
+
* alert(shape?.flowElement?.name ?? id);
|
|
58
|
+
* });
|
|
59
|
+
* },
|
|
60
|
+
* };
|
|
61
|
+
* const canvas = new BpmnCanvas({ container, plugins: [tooltipPlugin] });
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* @packageDocumentation
|
|
65
|
+
*/
|
|
66
|
+
export { BpmnCanvas } from "./canvas.js";
|
|
67
|
+
// Internal exports for use by @bpmnkit/editor
|
|
68
|
+
export { ViewportController } from "./viewport.js";
|
|
69
|
+
export { render, computeDiagramBounds, createDefs, createGrid } from "./renderer.js";
|
|
70
|
+
export { KeyboardHandler } from "./keyboard.js";
|
|
71
|
+
export { injectStyles, CANVAS_CSS, STYLE_ID } from "./css.js";
|
|
72
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { RenderedShape } from "./types.js";
|
|
2
|
+
import type { ViewportController } from "./viewport.js";
|
|
3
|
+
/**
|
|
4
|
+
* Handles keyboard navigation for the BPMN canvas.
|
|
5
|
+
*
|
|
6
|
+
* ## Key bindings
|
|
7
|
+
* | Key | Action |
|
|
8
|
+
* |-----|--------|
|
|
9
|
+
* | `←` `→` `↑` `↓` | Pan viewport |
|
|
10
|
+
* | `+` / `=` | Zoom in |
|
|
11
|
+
* | `-` | Zoom out |
|
|
12
|
+
* | `0` / `Home` | Fit diagram to view |
|
|
13
|
+
* | `Tab` | Focus next element |
|
|
14
|
+
* | `Shift+Tab` | Focus previous element |
|
|
15
|
+
* | `Enter` / `Space` | Activate focused element (fires `element:click`) |
|
|
16
|
+
* | `Escape` | Return focus to canvas container |
|
|
17
|
+
*
|
|
18
|
+
* The canvas container uses `role="application"` so screen readers pass
|
|
19
|
+
* all key presses to the application rather than handling them natively.
|
|
20
|
+
*/
|
|
21
|
+
export declare class KeyboardHandler {
|
|
22
|
+
private readonly _container;
|
|
23
|
+
private readonly _viewport;
|
|
24
|
+
private readonly _onFit;
|
|
25
|
+
private readonly _onActivate;
|
|
26
|
+
private readonly _onFocus;
|
|
27
|
+
private readonly _onBlur;
|
|
28
|
+
private _shapes;
|
|
29
|
+
private _focusIndex;
|
|
30
|
+
constructor(_container: HTMLElement, _viewport: ViewportController, _onFit: () => void, _onActivate: (id: string) => void, _onFocus: (id: string) => void, _onBlur: () => void);
|
|
31
|
+
/** Updates the navigable shape list after a diagram (re)load. */
|
|
32
|
+
setShapes(shapes: RenderedShape[]): void;
|
|
33
|
+
/** Removes all keyboard event listeners. */
|
|
34
|
+
destroy(): void;
|
|
35
|
+
private _focusShape;
|
|
36
|
+
private _clearFocus;
|
|
37
|
+
private readonly _onKeyDown;
|
|
38
|
+
private readonly _onFocusOut;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=keyboard.d.ts.map
|
package/dist/keyboard.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
const PAN_STEP = 60; // pixels per arrow key press
|
|
2
|
+
const ZOOM_STEP = 1.25; // factor per +/- key press
|
|
3
|
+
/**
|
|
4
|
+
* Handles keyboard navigation for the BPMN canvas.
|
|
5
|
+
*
|
|
6
|
+
* ## Key bindings
|
|
7
|
+
* | Key | Action |
|
|
8
|
+
* |-----|--------|
|
|
9
|
+
* | `←` `→` `↑` `↓` | Pan viewport |
|
|
10
|
+
* | `+` / `=` | Zoom in |
|
|
11
|
+
* | `-` | Zoom out |
|
|
12
|
+
* | `0` / `Home` | Fit diagram to view |
|
|
13
|
+
* | `Tab` | Focus next element |
|
|
14
|
+
* | `Shift+Tab` | Focus previous element |
|
|
15
|
+
* | `Enter` / `Space` | Activate focused element (fires `element:click`) |
|
|
16
|
+
* | `Escape` | Return focus to canvas container |
|
|
17
|
+
*
|
|
18
|
+
* The canvas container uses `role="application"` so screen readers pass
|
|
19
|
+
* all key presses to the application rather than handling them natively.
|
|
20
|
+
*/
|
|
21
|
+
export class KeyboardHandler {
|
|
22
|
+
_container;
|
|
23
|
+
_viewport;
|
|
24
|
+
_onFit;
|
|
25
|
+
_onActivate;
|
|
26
|
+
_onFocus;
|
|
27
|
+
_onBlur;
|
|
28
|
+
_shapes = [];
|
|
29
|
+
_focusIndex = -1; // -1 = container has focus
|
|
30
|
+
constructor(_container, _viewport, _onFit, _onActivate, _onFocus, _onBlur) {
|
|
31
|
+
this._container = _container;
|
|
32
|
+
this._viewport = _viewport;
|
|
33
|
+
this._onFit = _onFit;
|
|
34
|
+
this._onActivate = _onActivate;
|
|
35
|
+
this._onFocus = _onFocus;
|
|
36
|
+
this._onBlur = _onBlur;
|
|
37
|
+
this._container.addEventListener("keydown", this._onKeyDown);
|
|
38
|
+
this._container.addEventListener("focusout", this._onFocusOut);
|
|
39
|
+
}
|
|
40
|
+
/** Updates the navigable shape list after a diagram (re)load. */
|
|
41
|
+
setShapes(shapes) {
|
|
42
|
+
this._shapes = shapes.filter((s) => s.element.getAttribute("tabindex") === "-1");
|
|
43
|
+
this._focusIndex = -1;
|
|
44
|
+
}
|
|
45
|
+
/** Removes all keyboard event listeners. */
|
|
46
|
+
destroy() {
|
|
47
|
+
this._container.removeEventListener("keydown", this._onKeyDown);
|
|
48
|
+
this._container.removeEventListener("focusout", this._onFocusOut);
|
|
49
|
+
}
|
|
50
|
+
// ── Internals ─────────────────────────────────────────────────────
|
|
51
|
+
_focusShape(index) {
|
|
52
|
+
const shape = this._shapes[index];
|
|
53
|
+
if (!shape)
|
|
54
|
+
return;
|
|
55
|
+
this._focusIndex = index;
|
|
56
|
+
shape.element.focus();
|
|
57
|
+
shape.element.classList.add("bpmnkit-selected");
|
|
58
|
+
this._onFocus(shape.id);
|
|
59
|
+
}
|
|
60
|
+
_clearFocus() {
|
|
61
|
+
if (this._focusIndex >= 0) {
|
|
62
|
+
this._shapes[this._focusIndex]?.element.classList.remove("bpmnkit-selected");
|
|
63
|
+
}
|
|
64
|
+
this._focusIndex = -1;
|
|
65
|
+
this._onBlur();
|
|
66
|
+
}
|
|
67
|
+
_onKeyDown = (e) => {
|
|
68
|
+
// Don't intercept keys typed into form controls or editable elements
|
|
69
|
+
const target = e.target;
|
|
70
|
+
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable)
|
|
71
|
+
return;
|
|
72
|
+
const svgRect = this._container.getBoundingClientRect();
|
|
73
|
+
const cx = svgRect.width / 2;
|
|
74
|
+
const cy = svgRect.height / 2;
|
|
75
|
+
switch (e.key) {
|
|
76
|
+
// ── Pan ────────────────────────────────────────────────────
|
|
77
|
+
case "ArrowLeft":
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
this._viewport.set({
|
|
80
|
+
tx: this._viewport.state.tx + PAN_STEP,
|
|
81
|
+
ty: this._viewport.state.ty,
|
|
82
|
+
});
|
|
83
|
+
break;
|
|
84
|
+
case "ArrowRight":
|
|
85
|
+
e.preventDefault();
|
|
86
|
+
this._viewport.set({
|
|
87
|
+
tx: this._viewport.state.tx - PAN_STEP,
|
|
88
|
+
ty: this._viewport.state.ty,
|
|
89
|
+
});
|
|
90
|
+
break;
|
|
91
|
+
case "ArrowUp":
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
this._viewport.set({
|
|
94
|
+
tx: this._viewport.state.tx,
|
|
95
|
+
ty: this._viewport.state.ty + PAN_STEP,
|
|
96
|
+
});
|
|
97
|
+
break;
|
|
98
|
+
case "ArrowDown":
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
this._viewport.set({
|
|
101
|
+
tx: this._viewport.state.tx,
|
|
102
|
+
ty: this._viewport.state.ty - PAN_STEP,
|
|
103
|
+
});
|
|
104
|
+
break;
|
|
105
|
+
// ── Zoom ───────────────────────────────────────────────────
|
|
106
|
+
case "+":
|
|
107
|
+
case "=":
|
|
108
|
+
e.preventDefault();
|
|
109
|
+
this._viewport.zoomAt(cx, cy, ZOOM_STEP);
|
|
110
|
+
break;
|
|
111
|
+
case "-":
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
this._viewport.zoomAt(cx, cy, 1 / ZOOM_STEP);
|
|
114
|
+
break;
|
|
115
|
+
// ── Fit ────────────────────────────────────────────────────
|
|
116
|
+
case "0":
|
|
117
|
+
case "Home":
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
this._onFit();
|
|
120
|
+
break;
|
|
121
|
+
// ── Element navigation ─────────────────────────────────────
|
|
122
|
+
case "Tab": {
|
|
123
|
+
if (this._shapes.length === 0)
|
|
124
|
+
break;
|
|
125
|
+
e.preventDefault();
|
|
126
|
+
const next = e.shiftKey
|
|
127
|
+
? this._focusIndex <= 0
|
|
128
|
+
? this._shapes.length - 1
|
|
129
|
+
: this._focusIndex - 1
|
|
130
|
+
: this._focusIndex >= this._shapes.length - 1
|
|
131
|
+
? 0
|
|
132
|
+
: this._focusIndex + 1;
|
|
133
|
+
this._clearFocus();
|
|
134
|
+
this._focusShape(next);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
// ── Activate ───────────────────────────────────────────────
|
|
138
|
+
case "Enter":
|
|
139
|
+
case " ": {
|
|
140
|
+
if (this._focusIndex < 0)
|
|
141
|
+
break;
|
|
142
|
+
const shape = this._shapes[this._focusIndex];
|
|
143
|
+
if (shape) {
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
this._onActivate(shape.id);
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
// ── Escape ─────────────────────────────────────────────────
|
|
150
|
+
case "Escape":
|
|
151
|
+
if (this._focusIndex >= 0) {
|
|
152
|
+
e.preventDefault();
|
|
153
|
+
this._clearFocus();
|
|
154
|
+
this._container.focus();
|
|
155
|
+
}
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
_onFocusOut = (e) => {
|
|
160
|
+
// Only clear focus when leaving the container entirely
|
|
161
|
+
if (!this._container.contains(e.relatedTarget)) {
|
|
162
|
+
this._clearFocus();
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=keyboard.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { BpmnDefinitions } from "@bpmn-sdk/core";
|
|
2
|
+
import type { ViewportState } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* A scaled-down overview of the BPMN diagram that shows the current viewport
|
|
5
|
+
* position as a highlighted rectangle.
|
|
6
|
+
*
|
|
7
|
+
* Clicking the minimap pans the canvas to that position.
|
|
8
|
+
*/
|
|
9
|
+
export declare class Minimap {
|
|
10
|
+
/** Invoked when the user clicks the minimap to request a viewport pan. */
|
|
11
|
+
private readonly _onNavigate;
|
|
12
|
+
private readonly _host;
|
|
13
|
+
private readonly _svg;
|
|
14
|
+
private readonly _shapesG;
|
|
15
|
+
private readonly _edgesG;
|
|
16
|
+
private readonly _viewportRect;
|
|
17
|
+
private _scale;
|
|
18
|
+
private _offsetX;
|
|
19
|
+
private _offsetY;
|
|
20
|
+
private _diagramMinX;
|
|
21
|
+
private _diagramMinY;
|
|
22
|
+
private _diagramW;
|
|
23
|
+
private _diagramH;
|
|
24
|
+
private readonly _mmW;
|
|
25
|
+
private readonly _mmH;
|
|
26
|
+
constructor(container: HTMLElement,
|
|
27
|
+
/** Invoked when the user clicks the minimap to request a viewport pan. */
|
|
28
|
+
_onNavigate: (tx: number, ty: number) => void);
|
|
29
|
+
/** Updates the minimap with shapes and edges from a newly loaded diagram. */
|
|
30
|
+
update(defs: BpmnDefinitions): void;
|
|
31
|
+
/** Syncs the viewport indicator rectangle with the current pan/zoom state. */
|
|
32
|
+
syncViewport(state: ViewportState, svgWidth: number, svgHeight: number): void;
|
|
33
|
+
/** Removes the minimap from the DOM. */
|
|
34
|
+
destroy(): void;
|
|
35
|
+
private readonly _onClick;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=minimap.d.ts.map
|