@miragon/modeler-shell 0.0.0-stage → 0.1.0
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/API.md +1902 -0
- package/LICENSE +21 -0
- package/README.md +435 -2
- package/THIRD_PARTY_NOTICES.md +52 -0
- package/dist/appearance-D3FUcbu6.d.cts +53 -0
- package/dist/appearance-D3FUcbu6.d.ts +53 -0
- package/dist/chunk-5JSZ4VGA.js +551 -0
- package/dist/chunk-5JSZ4VGA.js.map +1 -0
- package/dist/chunk-6LQOFACK.js +1576 -0
- package/dist/chunk-6LQOFACK.js.map +1 -0
- package/dist/chunk-N4QXFF3A.js +23 -0
- package/dist/chunk-N4QXFF3A.js.map +1 -0
- package/dist/chunk-RJRFR6N3.js +136 -0
- package/dist/chunk-RJRFR6N3.js.map +1 -0
- package/dist/core.cjs +619 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +171 -0
- package/dist/core.d.ts +171 -0
- package/dist/core.js +91 -0
- package/dist/core.js.map +1 -0
- package/dist/diagram-js.cjs +1115 -0
- package/dist/diagram-js.cjs.map +1 -0
- package/dist/diagram-js.d.cts +163 -0
- package/dist/diagram-js.d.ts +163 -0
- package/dist/diagram-js.js +867 -0
- package/dist/diagram-js.js.map +1 -0
- package/dist/index.cjs +2175 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +103 -0
- package/dist/index.js.map +1 -0
- package/dist/keymap-CObQE6q6.d.cts +151 -0
- package/dist/keymap-DORzHnK-.d.ts +151 -0
- package/dist/protocol-DhYBakWL.d.cts +15 -0
- package/dist/protocol-IeW8bf4f.d.ts +15 -0
- package/dist/shell.css +914 -0
- package/dist/testing-vscode-stub.cjs +332 -0
- package/dist/testing-vscode-stub.cjs.map +1 -0
- package/dist/testing-vscode-stub.d.cts +65 -0
- package/dist/testing-vscode-stub.d.ts +65 -0
- package/dist/testing-vscode-stub.js +154 -0
- package/dist/testing-vscode-stub.js.map +1 -0
- package/dist/testing.cjs +2364 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +165 -0
- package/dist/testing.d.ts +165 -0
- package/dist/testing.js +2297 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-B6NfkiUG.d.cts +66 -0
- package/dist/types-B6NfkiUG.d.ts +66 -0
- package/dist/types-DnZn9CPw.d.ts +284 -0
- package/dist/types-OuFlqZp0.d.cts +284 -0
- package/dist/vscode-extension.cjs +215 -0
- package/dist/vscode-extension.cjs.map +1 -0
- package/dist/vscode-extension.d.cts +103 -0
- package/dist/vscode-extension.d.ts +103 -0
- package/dist/vscode-extension.js +24 -0
- package/dist/vscode-extension.js.map +1 -0
- package/dist/vscode-webview.cjs +2198 -0
- package/dist/vscode-webview.cjs.map +1 -0
- package/dist/vscode-webview.d.cts +82 -0
- package/dist/vscode-webview.d.ts +82 -0
- package/dist/vscode-webview.js +250 -0
- package/dist/vscode-webview.js.map +1 -0
- package/package.json +145 -4
- package/src/core/appearance.ts +104 -0
- package/src/core/fit.ts +205 -0
- package/src/core/index.ts +84 -0
- package/src/core/keymap.ts +275 -0
- package/src/core/layout.ts +69 -0
- package/src/core/messages.ts +88 -0
- package/src/core/sizes.ts +85 -0
- package/src/core/types.ts +84 -0
- package/src/core/zoom.ts +74 -0
- package/src/core.ts +2 -0
- package/src/diagram-js/connect.ts +75 -0
- package/src/diagram-js/index.ts +16 -0
- package/src/diagram-js/module.ts +20 -0
- package/src/diagram-js/palette-skin.ts +267 -0
- package/src/diagram-js/palette.css +269 -0
- package/src/diagram-js/service.ts +240 -0
- package/src/diagram-js/types.ts +67 -0
- package/src/diagram-js/viewport.ts +564 -0
- package/src/diagram-js.ts +2 -0
- package/src/dom/element.ts +51 -0
- package/src/dom/hint.ts +51 -0
- package/src/dom/icons.ts +309 -0
- package/src/dom/index.ts +29 -0
- package/src/dom/inspector.ts +35 -0
- package/src/dom/keys.ts +59 -0
- package/src/dom/ledger.ts +87 -0
- package/src/dom/legend.ts +85 -0
- package/src/dom/menu.ts +393 -0
- package/src/dom/mount.ts +461 -0
- package/src/dom/problems.ts +72 -0
- package/src/dom/shell-dom.css +966 -0
- package/src/dom/status.ts +72 -0
- package/src/dom/tooltip.ts +237 -0
- package/src/dom/types.ts +277 -0
- package/src/dom/zoom-panel.ts +154 -0
- package/src/index.ts +3 -0
- package/src/shell.css +6 -0
- package/src/testing/cases.ts +1012 -0
- package/src/testing/checks.ts +832 -0
- package/src/testing/facts.ts +587 -0
- package/src/testing/geometry.ts +472 -0
- package/src/testing/harness.ts +138 -0
- package/src/testing/index.ts +15 -0
- package/src/testing/page-tools.ts +434 -0
- package/src/testing/page.ts +17 -0
- package/src/testing/vscode-stub.ts +237 -0
- package/src/testing-vscode-stub.ts +2 -0
- package/src/testing.ts +2 -0
- package/src/vscode/extension/html.ts +107 -0
- package/src/vscode/extension/index.ts +8 -0
- package/src/vscode/extension/reveal.ts +9 -0
- package/src/vscode/extension/theme-bridge.ts +85 -0
- package/src/vscode/extension/types.ts +36 -0
- package/src/vscode/protocol.ts +31 -0
- package/src/vscode/webview/appearance.ts +141 -0
- package/src/vscode/webview/index.ts +8 -0
- package/src/vscode/webview/mount.ts +76 -0
- package/src/vscode/webview/state.ts +116 -0
- package/src/vscode-extension.ts +2 -0
- package/src/vscode-webview.ts +2 -0
|
@@ -0,0 +1,1115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/diagram-js.ts
|
|
21
|
+
var diagram_js_exports = {};
|
|
22
|
+
__export(diagram_js_exports, {
|
|
23
|
+
connectDiagramJs: () => connectDiagramJs,
|
|
24
|
+
createViewport: () => createViewport,
|
|
25
|
+
shellModule: () => shellModule
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(diagram_js_exports);
|
|
28
|
+
|
|
29
|
+
// src/core/fit.ts
|
|
30
|
+
var MAX_FIT_ZOOM = 1;
|
|
31
|
+
var FIT_ANIMATION_MS = 250;
|
|
32
|
+
function fitViewbox(bounds, container, insets, maxZoom = MAX_FIT_ZOOM) {
|
|
33
|
+
if (!(container.width > 0 && container.height > 0)) return void 0;
|
|
34
|
+
const hasWidth = bounds.width > 0;
|
|
35
|
+
const hasHeight = bounds.height > 0;
|
|
36
|
+
if (!hasWidth && !hasHeight) return void 0;
|
|
37
|
+
const horizontal = freeExtent(container.width, insets.left, insets.right);
|
|
38
|
+
const vertical = freeExtent(container.height, insets.top, insets.bottom);
|
|
39
|
+
const contentWidth = hasWidth ? bounds.width : 0;
|
|
40
|
+
const contentHeight = hasHeight ? bounds.height : 0;
|
|
41
|
+
const scale = Math.min(
|
|
42
|
+
maxZoom,
|
|
43
|
+
hasWidth ? horizontal.extent / contentWidth : Infinity,
|
|
44
|
+
hasHeight ? vertical.extent / contentHeight : Infinity
|
|
45
|
+
);
|
|
46
|
+
const box = {
|
|
47
|
+
x: bounds.x + contentWidth / 2 - (horizontal.start + horizontal.extent / 2) / scale,
|
|
48
|
+
y: bounds.y + contentHeight / 2 - (vertical.start + vertical.extent / 2) / scale,
|
|
49
|
+
width: container.width / scale,
|
|
50
|
+
height: container.height / scale,
|
|
51
|
+
scale
|
|
52
|
+
};
|
|
53
|
+
return Object.values(box).every(Number.isFinite) && scale > 0 ? box : void 0;
|
|
54
|
+
}
|
|
55
|
+
function freeExtent(total, before, after) {
|
|
56
|
+
const extent = total - before - after;
|
|
57
|
+
return extent > 0 ? { start: before, extent } : { start: 0, extent: total };
|
|
58
|
+
}
|
|
59
|
+
function freeRectCenter(container, insets) {
|
|
60
|
+
if (!(container.width > 0 && container.height > 0)) return void 0;
|
|
61
|
+
const horizontal = freeExtent(container.width, insets.left, insets.right);
|
|
62
|
+
const vertical = freeExtent(container.height, insets.top, insets.bottom);
|
|
63
|
+
return {
|
|
64
|
+
x: horizontal.start + horizontal.extent / 2,
|
|
65
|
+
y: vertical.start + vertical.extent / 2
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
var VIEW_TOLERANCE = 1e-3;
|
|
69
|
+
function sameView(a, b, tolerance = VIEW_TOLERANCE) {
|
|
70
|
+
const close = (first, second) => Math.abs(first - second) <= tolerance * Math.max(1, Math.abs(first), Math.abs(second));
|
|
71
|
+
return close(a.scale, b.scale) && close(a.x * a.scale, b.x * b.scale) && close(a.y * a.scale, b.y * b.scale);
|
|
72
|
+
}
|
|
73
|
+
function interpolateViewbox(from, to, progress) {
|
|
74
|
+
if (!(progress > 0)) return { ...from };
|
|
75
|
+
if (progress >= 1) return { ...to };
|
|
76
|
+
const linear = (start, end) => start + (end - start) * progress;
|
|
77
|
+
const scale = from.scale > 0 && to.scale > 0 ? Math.exp(linear(Math.log(from.scale), Math.log(to.scale))) : linear(from.scale, to.scale);
|
|
78
|
+
const centreX = linear(from.x + from.width / 2, to.x + to.width / 2);
|
|
79
|
+
const centreY = linear(from.y + from.height / 2, to.y + to.height / 2);
|
|
80
|
+
const width = linear(from.width * from.scale, to.width * to.scale) / scale;
|
|
81
|
+
const height = linear(from.height * from.scale, to.height * to.scale) / scale;
|
|
82
|
+
return { x: centreX - width / 2, y: centreY - height / 2, width, height, scale };
|
|
83
|
+
}
|
|
84
|
+
function easeStandard(progress) {
|
|
85
|
+
return STANDARD_EASING(progress);
|
|
86
|
+
}
|
|
87
|
+
var STANDARD_EASING = cubicBezier(0.4, 0, 0.2, 1);
|
|
88
|
+
function cubicBezier(x1, y1, x2, y2) {
|
|
89
|
+
const polynomial = (first, second) => {
|
|
90
|
+
const c = 3 * first;
|
|
91
|
+
const b = 3 * (second - first) - c;
|
|
92
|
+
const a = 1 - c - b;
|
|
93
|
+
return {
|
|
94
|
+
at: (t) => ((a * t + b) * t + c) * t,
|
|
95
|
+
slope: (t) => (3 * a * t + 2 * b) * t + c
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
const curveX = polynomial(x1, x2);
|
|
99
|
+
const curveY = polynomial(y1, y2);
|
|
100
|
+
const precision = 1e-7;
|
|
101
|
+
const parameterFor = (x) => {
|
|
102
|
+
let t = x;
|
|
103
|
+
for (let iteration = 0; iteration < 8; iteration++) {
|
|
104
|
+
const error = curveX.at(t) - x;
|
|
105
|
+
if (Math.abs(error) < precision) return t;
|
|
106
|
+
const slope = curveX.slope(t);
|
|
107
|
+
if (Math.abs(slope) < 1e-6) break;
|
|
108
|
+
t -= error / slope;
|
|
109
|
+
if (t < 0 || t > 1) break;
|
|
110
|
+
}
|
|
111
|
+
let low = 0;
|
|
112
|
+
let high = 1;
|
|
113
|
+
t = x;
|
|
114
|
+
for (let iteration = 0; iteration < 60; iteration++) {
|
|
115
|
+
const value = curveX.at(t);
|
|
116
|
+
if (Math.abs(value - x) < precision) break;
|
|
117
|
+
if (value < x) low = t;
|
|
118
|
+
else high = t;
|
|
119
|
+
t = (low + high) / 2;
|
|
120
|
+
}
|
|
121
|
+
return t;
|
|
122
|
+
};
|
|
123
|
+
return (progress) => {
|
|
124
|
+
if (!(progress > 0)) return 0;
|
|
125
|
+
if (progress >= 1) return 1;
|
|
126
|
+
return curveY.at(parameterFor(progress));
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/core/zoom.ts
|
|
131
|
+
var ZOOM_MIN = 0.2;
|
|
132
|
+
var ZOOM_MAX = 4;
|
|
133
|
+
function canZoomOut(zoom) {
|
|
134
|
+
return zoom > ZOOM_MIN * 1.001;
|
|
135
|
+
}
|
|
136
|
+
function canZoomIn(zoom) {
|
|
137
|
+
return zoom < ZOOM_MAX * 0.999;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/core/keymap.ts
|
|
141
|
+
function matchChromeKey(event) {
|
|
142
|
+
if (event.altKey) return void 0;
|
|
143
|
+
if (isCommand(event)) {
|
|
144
|
+
if (event.key === "+" || event.key === "=" || event.code === "NumpadAdd") return "zoomIn";
|
|
145
|
+
if (event.key === "-" || event.key === "_" || event.code === "NumpadSubtract") return "zoomOut";
|
|
146
|
+
if (event.key === "0" || event.code === "Numpad0") return "zoomToActual";
|
|
147
|
+
return void 0;
|
|
148
|
+
}
|
|
149
|
+
return event.shiftKey && event.code === "Digit1" ? "fit" : void 0;
|
|
150
|
+
}
|
|
151
|
+
function isCommand(event) {
|
|
152
|
+
return event.metaKey || event.ctrlKey;
|
|
153
|
+
}
|
|
154
|
+
function isUndoRedoKey(event) {
|
|
155
|
+
if (event.altKey || !isCommand(event)) return false;
|
|
156
|
+
const letter = undoRedoLetter(event);
|
|
157
|
+
return letter === "z" || letter === "y" && !event.shiftKey;
|
|
158
|
+
}
|
|
159
|
+
function undoRedoLetter(event) {
|
|
160
|
+
if (event.key.length === 1 && event.key.charCodeAt(0) < 128) return event.key.toLowerCase();
|
|
161
|
+
if (event.code === "KeyZ") return "z";
|
|
162
|
+
if (event.code === "KeyY") return "y";
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
165
|
+
var TEXT_FIELD_SELECTOR = "input, textarea, select, [contenteditable]:not([contenteditable='false'])";
|
|
166
|
+
var MAC_MODIFIER_SYMBOLS = {
|
|
167
|
+
"\u2318": "Meta",
|
|
168
|
+
"\u21E7": "Shift",
|
|
169
|
+
"\u2325": "Alt",
|
|
170
|
+
"\u2303": "Control"
|
|
171
|
+
};
|
|
172
|
+
var MODIFIER_NAMES = {
|
|
173
|
+
cmd: "Meta",
|
|
174
|
+
command: "Meta",
|
|
175
|
+
meta: "Meta",
|
|
176
|
+
ctrl: "Control",
|
|
177
|
+
control: "Control",
|
|
178
|
+
strg: "Control",
|
|
179
|
+
shift: "Shift",
|
|
180
|
+
umschalt: "Shift",
|
|
181
|
+
alt: "Alt",
|
|
182
|
+
option: "Alt",
|
|
183
|
+
opt: "Alt"
|
|
184
|
+
};
|
|
185
|
+
var NAMED_KEYS = {
|
|
186
|
+
esc: "Escape",
|
|
187
|
+
escape: "Escape",
|
|
188
|
+
del: "Delete",
|
|
189
|
+
delete: "Delete",
|
|
190
|
+
entf: "Delete",
|
|
191
|
+
backspace: "Backspace",
|
|
192
|
+
enter: "Enter",
|
|
193
|
+
return: "Enter",
|
|
194
|
+
tab: "Tab",
|
|
195
|
+
space: "Space",
|
|
196
|
+
home: "Home",
|
|
197
|
+
end: "End",
|
|
198
|
+
pageup: "PageUp",
|
|
199
|
+
pagedown: "PageDown",
|
|
200
|
+
arrowup: "ArrowUp",
|
|
201
|
+
arrowdown: "ArrowDown",
|
|
202
|
+
arrowleft: "ArrowLeft",
|
|
203
|
+
arrowright: "ArrowRight",
|
|
204
|
+
"\u2191": "ArrowUp",
|
|
205
|
+
"\u2193": "ArrowDown",
|
|
206
|
+
"\u2190": "ArrowLeft",
|
|
207
|
+
"\u2192": "ArrowRight",
|
|
208
|
+
plus: "Plus"
|
|
209
|
+
};
|
|
210
|
+
function ariaKeyName(key) {
|
|
211
|
+
const named = NAMED_KEYS[key.toLowerCase()];
|
|
212
|
+
if (named) return named;
|
|
213
|
+
if (/^f\d{1,2}$/i.test(key)) return key.toUpperCase();
|
|
214
|
+
if (key.length !== 1 || MAC_MODIFIER_SYMBOLS[key]) return void 0;
|
|
215
|
+
if (key === "+") return "Plus";
|
|
216
|
+
if (key === " ") return "Space";
|
|
217
|
+
return key.toUpperCase();
|
|
218
|
+
}
|
|
219
|
+
function ariaKeyShortcutsFromLabel(label) {
|
|
220
|
+
let rest = label.trim();
|
|
221
|
+
const modifiers = [];
|
|
222
|
+
for (let symbol = MAC_MODIFIER_SYMBOLS[rest.charAt(0)]; symbol && rest.length > 1; ) {
|
|
223
|
+
modifiers.push(symbol);
|
|
224
|
+
rest = rest.slice(1).trim();
|
|
225
|
+
symbol = MAC_MODIFIER_SYMBOLS[rest.charAt(0)];
|
|
226
|
+
}
|
|
227
|
+
if (!rest || /\s/.test(rest.replace(/\s*\+\s*/g, "+"))) return void 0;
|
|
228
|
+
const parts = rest.split("+").map((part) => part.trim());
|
|
229
|
+
let key = parts.pop() ?? "";
|
|
230
|
+
if (key === "" && parts.at(-1) === "") {
|
|
231
|
+
parts.pop();
|
|
232
|
+
key = "+";
|
|
233
|
+
}
|
|
234
|
+
for (const part of parts) {
|
|
235
|
+
const modifier = MODIFIER_NAMES[part.toLowerCase()];
|
|
236
|
+
if (!modifier) return void 0;
|
|
237
|
+
modifiers.push(modifier);
|
|
238
|
+
}
|
|
239
|
+
const name = ariaKeyName(key);
|
|
240
|
+
return name ? [...new Set(modifiers), name].join("+") : void 0;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/core/appearance.ts
|
|
244
|
+
var REDUCED_MOTION_CLASS = "cd-shell-reduce-motion";
|
|
245
|
+
|
|
246
|
+
// src/core/messages.ts
|
|
247
|
+
var defaultMessages = {
|
|
248
|
+
menu: "Menu",
|
|
249
|
+
tools: "Tools",
|
|
250
|
+
canvas: "Diagram",
|
|
251
|
+
zoom: "Zoom",
|
|
252
|
+
zoomOut: "Zoom out",
|
|
253
|
+
zoomIn: "Zoom in",
|
|
254
|
+
zoomToActual: (percent) => `Zoom to ${percent}`,
|
|
255
|
+
zoomToActualNow: (percent, now) => `Zoom to ${percent} (now ${now})`,
|
|
256
|
+
fit: "Fit to view",
|
|
257
|
+
legend: "Legend",
|
|
258
|
+
closeLegend: "Close legend",
|
|
259
|
+
problems: (count, formattedCount) => `${formattedCount} ${count === 1 ? "problem" : "problems"}`,
|
|
260
|
+
problemsLabel: (count, errors, formattedCount, formattedErrors) => `${formattedCount} ${count === 1 ? "problem" : "problems"}, ${errors === 0 ? "no errors" : `${formattedErrors} ${errors === 1 ? "error" : "errors"}`}`,
|
|
261
|
+
showProblems: "Show in Problems view",
|
|
262
|
+
emptyHint: "Pick a tool from the palette to start.",
|
|
263
|
+
insertExample: "Insert example",
|
|
264
|
+
invalidFile: (diagram) => `This file isn't a valid ${diagram} right now. The diagram shows the last valid version and is read-only until you fix the text.`,
|
|
265
|
+
keyNames: { ctrl: "Ctrl", shift: "Shift", plus: "Plus", minus: "Minus" }
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// src/diagram-js/palette-skin.ts
|
|
269
|
+
var TOOLS_GROUP = "tools";
|
|
270
|
+
var READ_ONLY_ACTIONS = /* @__PURE__ */ new Set(["lasso-tool", "hand-tool"]);
|
|
271
|
+
var TOOL_ACTION_SUFFIX = "-tool";
|
|
272
|
+
var ENTRIES_SELECTOR = ".djs-palette-entries";
|
|
273
|
+
var ENTRY_SELECTOR = ".entry";
|
|
274
|
+
var PaletteSkin = class {
|
|
275
|
+
constructor(element, readEntries) {
|
|
276
|
+
this.element = element;
|
|
277
|
+
this.readEntries = readEntries;
|
|
278
|
+
element.setAttribute("data-chrome", "palette");
|
|
279
|
+
element.addEventListener("keydown", this.onKeydown);
|
|
280
|
+
element.addEventListener("focusin", this.onFocusIn);
|
|
281
|
+
element.addEventListener("focusout", this.onFocusOut);
|
|
282
|
+
this.refresh();
|
|
283
|
+
}
|
|
284
|
+
element;
|
|
285
|
+
readEntries;
|
|
286
|
+
label = defaultMessages.tools;
|
|
287
|
+
activeTool = null;
|
|
288
|
+
/** The veto of a read-only canvas, at once with the invalid import. */
|
|
289
|
+
locked = false;
|
|
290
|
+
/** The `aria-disabled` marks of a read-only canvas, once the shell's delay has passed. */
|
|
291
|
+
readOnlyShown = false;
|
|
292
|
+
entries = {};
|
|
293
|
+
rovingAction;
|
|
294
|
+
focusedEntry;
|
|
295
|
+
/** The `aria-disabled` an entry had before read-only marked it (`null`: none). */
|
|
296
|
+
disabledEntries = /* @__PURE__ */ new WeakMap();
|
|
297
|
+
setLabel(label) {
|
|
298
|
+
this.label = label;
|
|
299
|
+
this.apply();
|
|
300
|
+
}
|
|
301
|
+
setActiveTool(tool) {
|
|
302
|
+
this.activeTool = tool;
|
|
303
|
+
this.apply();
|
|
304
|
+
}
|
|
305
|
+
setReadOnly(state) {
|
|
306
|
+
this.locked = state.locked;
|
|
307
|
+
this.readOnlyShown = state.locked && state.shown;
|
|
308
|
+
this.apply();
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Whether `palette.trigger` must refuse `entry`: on a locked canvas every entry but Lasso and
|
|
312
|
+
* Hand, also before their marks show. `palette.trigger` carries the entry definition without its
|
|
313
|
+
* id, so the entry element is found through the DOM event; without one, only the tools group
|
|
314
|
+
* passes.
|
|
315
|
+
*/
|
|
316
|
+
vetoes(entry, event) {
|
|
317
|
+
if (!this.locked) return false;
|
|
318
|
+
const element = this.entryOf(
|
|
319
|
+
event?.delegateTarget ?? event?.target ?? null
|
|
320
|
+
);
|
|
321
|
+
if (element) return !staysActive(element);
|
|
322
|
+
return entry.group !== TOOLS_GROUP;
|
|
323
|
+
}
|
|
324
|
+
/** After `palette.changed`: re-read the entries and re-apply every attribute. */
|
|
325
|
+
refresh() {
|
|
326
|
+
this.entries = this.readEntries();
|
|
327
|
+
this.apply();
|
|
328
|
+
}
|
|
329
|
+
entryTooltip(entry) {
|
|
330
|
+
const action = entry.getAttribute("data-action");
|
|
331
|
+
const definition = action === null ? void 0 : this.entries[action];
|
|
332
|
+
if (!definition?.title) return null;
|
|
333
|
+
return definition.shortcut ? { label: definition.title, shortcut: definition.shortcut } : { label: definition.title };
|
|
334
|
+
}
|
|
335
|
+
destroy() {
|
|
336
|
+
this.element.removeEventListener("keydown", this.onKeydown);
|
|
337
|
+
this.element.removeEventListener("focusin", this.onFocusIn);
|
|
338
|
+
this.element.removeEventListener("focusout", this.onFocusOut);
|
|
339
|
+
this.focusedEntry = void 0;
|
|
340
|
+
}
|
|
341
|
+
apply() {
|
|
342
|
+
const container = this.element.querySelector(ENTRIES_SELECTOR);
|
|
343
|
+
if (container) {
|
|
344
|
+
container.setAttribute("role", "toolbar");
|
|
345
|
+
container.setAttribute("aria-label", this.label);
|
|
346
|
+
}
|
|
347
|
+
const entries = this.entryElements();
|
|
348
|
+
for (const entry of entries) {
|
|
349
|
+
if (!entry.hasAttribute("role")) entry.setAttribute("role", "button");
|
|
350
|
+
this.applyKeyShortcuts(entry);
|
|
351
|
+
this.applyPressed(entry);
|
|
352
|
+
this.applyDisabled(entry);
|
|
353
|
+
}
|
|
354
|
+
this.applyTabStops(entries);
|
|
355
|
+
this.restoreFocus(entries);
|
|
356
|
+
}
|
|
357
|
+
// Guide §5.6: a button with a shortcut names it for assistive technology, not only in the chip.
|
|
358
|
+
applyKeyShortcuts(entry) {
|
|
359
|
+
const action = actionOf(entry);
|
|
360
|
+
const definition = action === void 0 ? void 0 : this.entries[action];
|
|
361
|
+
const keyShortcuts = definition?.ariaKeyShortcuts ?? (definition?.shortcut ? ariaKeyShortcutsFromLabel(definition.shortcut) : void 0);
|
|
362
|
+
if (keyShortcuts) entry.setAttribute("aria-keyshortcuts", keyShortcuts);
|
|
363
|
+
}
|
|
364
|
+
applyPressed(entry) {
|
|
365
|
+
const action = actionOf(entry);
|
|
366
|
+
if (groupOf(entry) !== TOOLS_GROUP || !action?.endsWith(TOOL_ACTION_SUFFIX)) return;
|
|
367
|
+
const tool = action.slice(0, -TOOL_ACTION_SUFFIX.length);
|
|
368
|
+
entry.setAttribute("aria-pressed", String(tool === this.activeTool));
|
|
369
|
+
}
|
|
370
|
+
applyDisabled(entry) {
|
|
371
|
+
if (this.readOnlyShown && !staysActive(entry)) {
|
|
372
|
+
if (!this.disabledEntries.has(entry)) {
|
|
373
|
+
this.disabledEntries.set(entry, entry.getAttribute("aria-disabled"));
|
|
374
|
+
}
|
|
375
|
+
entry.setAttribute("aria-disabled", "true");
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (!this.disabledEntries.has(entry)) return;
|
|
379
|
+
const previous = this.disabledEntries.get(entry);
|
|
380
|
+
this.disabledEntries.delete(entry);
|
|
381
|
+
if (previous === null || previous === void 0) entry.removeAttribute("aria-disabled");
|
|
382
|
+
else entry.setAttribute("aria-disabled", previous);
|
|
383
|
+
}
|
|
384
|
+
applyTabStops(entries) {
|
|
385
|
+
const stop = entries.find((entry) => actionOf(entry) === this.rovingAction) ?? entries[0];
|
|
386
|
+
for (const entry of entries) entry.tabIndex = entry === stop ? 0 : -1;
|
|
387
|
+
}
|
|
388
|
+
// A rebuild removes the focused entry; without this the focus would fall back to <body>.
|
|
389
|
+
restoreFocus(entries) {
|
|
390
|
+
const lost = this.focusedEntry;
|
|
391
|
+
if (!lost || lost.isConnected) return;
|
|
392
|
+
this.focusedEntry = void 0;
|
|
393
|
+
const replacement = entries.find((entry) => actionOf(entry) === actionOf(lost));
|
|
394
|
+
const active = this.element.ownerDocument.activeElement;
|
|
395
|
+
const focusIsNowhere = !active || active === this.element.ownerDocument.body;
|
|
396
|
+
if (replacement && focusIsNowhere) replacement.focus();
|
|
397
|
+
}
|
|
398
|
+
entryElements() {
|
|
399
|
+
const container = this.element.querySelector(ENTRIES_SELECTOR);
|
|
400
|
+
return container ? Array.from(container.querySelectorAll(ENTRY_SELECTOR)) : [];
|
|
401
|
+
}
|
|
402
|
+
entryOf(target) {
|
|
403
|
+
if (!(target instanceof Element)) return void 0;
|
|
404
|
+
const entry = target.closest(ENTRY_SELECTOR);
|
|
405
|
+
return entry && this.element.contains(entry) ? entry : void 0;
|
|
406
|
+
}
|
|
407
|
+
onFocusIn = (event) => {
|
|
408
|
+
const entry = this.entryOf(event.target);
|
|
409
|
+
if (!entry) return;
|
|
410
|
+
this.focusedEntry = entry;
|
|
411
|
+
this.rovingAction = actionOf(entry);
|
|
412
|
+
this.applyTabStops(this.entryElements());
|
|
413
|
+
};
|
|
414
|
+
onFocusOut = (event) => {
|
|
415
|
+
const entry = this.entryOf(event.target);
|
|
416
|
+
if (!entry || entry !== this.focusedEntry || !entry.isConnected) return;
|
|
417
|
+
const next = event.relatedTarget;
|
|
418
|
+
if (!(next instanceof Node) || !this.element.contains(next)) this.focusedEntry = void 0;
|
|
419
|
+
};
|
|
420
|
+
onKeydown = (event) => {
|
|
421
|
+
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
|
|
422
|
+
const entry = this.entryOf(event.target);
|
|
423
|
+
if (!entry) return;
|
|
424
|
+
const entries = this.entryElements();
|
|
425
|
+
const index = entries.indexOf(entry);
|
|
426
|
+
const last = entries.length - 1;
|
|
427
|
+
let next;
|
|
428
|
+
switch (event.key) {
|
|
429
|
+
case "ArrowLeft":
|
|
430
|
+
next = entries[index <= 0 ? last : index - 1];
|
|
431
|
+
break;
|
|
432
|
+
case "ArrowRight":
|
|
433
|
+
next = entries[index >= last ? 0 : index + 1];
|
|
434
|
+
break;
|
|
435
|
+
case "Home":
|
|
436
|
+
next = entries[0];
|
|
437
|
+
break;
|
|
438
|
+
case "End":
|
|
439
|
+
next = entries[last];
|
|
440
|
+
break;
|
|
441
|
+
case "Enter":
|
|
442
|
+
case " ":
|
|
443
|
+
event.preventDefault();
|
|
444
|
+
event.stopPropagation();
|
|
445
|
+
if (!event.repeat) activate(entry);
|
|
446
|
+
return;
|
|
447
|
+
default:
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
event.preventDefault();
|
|
451
|
+
event.stopPropagation();
|
|
452
|
+
if (!next) return;
|
|
453
|
+
this.rovingAction = actionOf(next);
|
|
454
|
+
this.applyTabStops(entries);
|
|
455
|
+
next.focus();
|
|
456
|
+
};
|
|
457
|
+
};
|
|
458
|
+
function actionOf(entry) {
|
|
459
|
+
return entry.getAttribute("data-action") ?? void 0;
|
|
460
|
+
}
|
|
461
|
+
function staysActive(entry) {
|
|
462
|
+
const action = actionOf(entry);
|
|
463
|
+
return action !== void 0 && READ_ONLY_ACTIONS.has(action);
|
|
464
|
+
}
|
|
465
|
+
function groupOf(entry) {
|
|
466
|
+
return entry.closest(".group")?.getAttribute("data-group") ?? void 0;
|
|
467
|
+
}
|
|
468
|
+
function activate(entry) {
|
|
469
|
+
const bounds = entry.getBoundingClientRect();
|
|
470
|
+
entry.dispatchEvent(
|
|
471
|
+
new MouseEvent("click", {
|
|
472
|
+
bubbles: true,
|
|
473
|
+
cancelable: true,
|
|
474
|
+
clientX: bounds.left + bounds.width / 2,
|
|
475
|
+
clientY: bounds.top + bounds.height / 2
|
|
476
|
+
})
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// src/diagram-js/service.ts
|
|
481
|
+
var SHELL_SERVICE = "modelerShell";
|
|
482
|
+
var KEYBOARD_PRIORITY = 2e3;
|
|
483
|
+
var TRIGGER_PRIORITY = 2e3;
|
|
484
|
+
var EDITABLE = { locked: false, shown: false };
|
|
485
|
+
var CANVAS_ATTRIBUTES = ["role", "aria-label"];
|
|
486
|
+
function isTextField(target) {
|
|
487
|
+
if (!target || typeof target.closest !== "function") return false;
|
|
488
|
+
return target.isContentEditable === true || target.closest(TEXT_FIELD_SELECTOR) !== null;
|
|
489
|
+
}
|
|
490
|
+
var ShellDiagramService = class {
|
|
491
|
+
constructor(eventBus, injector) {
|
|
492
|
+
this.eventBus = eventBus;
|
|
493
|
+
this.injector = injector;
|
|
494
|
+
this.subscribe("canvas.init", (event) => {
|
|
495
|
+
this.svg = event.svg;
|
|
496
|
+
this.nameCanvas();
|
|
497
|
+
});
|
|
498
|
+
this.subscribe(
|
|
499
|
+
"palette.create",
|
|
500
|
+
(event) => this.onPaletteCreate(event.container)
|
|
501
|
+
);
|
|
502
|
+
this.subscribe("palette.changed", () => this.skin?.refresh());
|
|
503
|
+
this.subscribe("tool-manager.update", (event) => {
|
|
504
|
+
this.activeTool = event.tool ?? null;
|
|
505
|
+
this.skin?.setActiveTool(this.activeTool);
|
|
506
|
+
});
|
|
507
|
+
this.subscribe("palette.trigger", this.onPaletteTrigger, TRIGGER_PRIORITY);
|
|
508
|
+
this.subscribe("diagram.destroy", () => this.destroy());
|
|
509
|
+
const keyboard = injector.get("keyboard", false);
|
|
510
|
+
if (keyboard) {
|
|
511
|
+
keyboard.addListener(KEYBOARD_PRIORITY, this.onKeyboardKeydown);
|
|
512
|
+
this.subscriptions.push(["keyboard.keydown", this.onKeyboardKeydown]);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
eventBus;
|
|
516
|
+
injector;
|
|
517
|
+
static $inject = ["eventBus", "injector"];
|
|
518
|
+
svg;
|
|
519
|
+
/** The canvas `<svg>`'s own `role` and `aria-label` before the shell named it. */
|
|
520
|
+
canvasOriginals;
|
|
521
|
+
skin;
|
|
522
|
+
activeTool = null;
|
|
523
|
+
shell;
|
|
524
|
+
unregisterPalette;
|
|
525
|
+
subscriptions = [];
|
|
526
|
+
destroyed = false;
|
|
527
|
+
/**
|
|
528
|
+
* Hands the palette (now or once diagram-js creates it) to `shell`; returns the detach
|
|
529
|
+
* function. A later call replaces the earlier shell.
|
|
530
|
+
*/
|
|
531
|
+
attachShell(shell) {
|
|
532
|
+
this.detachShell();
|
|
533
|
+
if (this.destroyed) return () => void 0;
|
|
534
|
+
this.shell = shell;
|
|
535
|
+
this.skin?.setLabel(shell.messages.tools);
|
|
536
|
+
this.nameCanvas();
|
|
537
|
+
this.registerPalette();
|
|
538
|
+
return () => {
|
|
539
|
+
if (this.shell === shell) this.detachShell();
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
registerPalette() {
|
|
543
|
+
const { shell, skin } = this;
|
|
544
|
+
if (!shell || !skin) return;
|
|
545
|
+
this.unregisterPalette?.();
|
|
546
|
+
this.unregisterPalette = shell.registerPalette({
|
|
547
|
+
element: skin.element,
|
|
548
|
+
entryTooltip: (entry) => skin.entryTooltip(entry),
|
|
549
|
+
setReadOnly: (state) => skin.setReadOnly(state)
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
detachShell() {
|
|
553
|
+
const unregister = this.unregisterPalette;
|
|
554
|
+
this.unregisterPalette = void 0;
|
|
555
|
+
this.shell = void 0;
|
|
556
|
+
unregister?.();
|
|
557
|
+
this.unnameCanvas();
|
|
558
|
+
this.skin?.setReadOnly(EDITABLE);
|
|
559
|
+
this.skin?.setLabel(defaultMessages.tools);
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* The focusable canvas is an application with a name (WCAG 4.1.2): screen readers switch to focus
|
|
563
|
+
* mode, so the canvas's single-key commands reach it. A role or name the modeler set stays.
|
|
564
|
+
*/
|
|
565
|
+
nameCanvas() {
|
|
566
|
+
const { svg, shell } = this;
|
|
567
|
+
if (!svg || !shell || this.canvasOriginals) return;
|
|
568
|
+
this.canvasOriginals = new Map(CANVAS_ATTRIBUTES.map((name) => [name, svg.getAttribute(name)]));
|
|
569
|
+
if (!svg.hasAttribute("role")) svg.setAttribute("role", "application");
|
|
570
|
+
if (!svg.hasAttribute("aria-label") && !svg.hasAttribute("aria-labelledby")) {
|
|
571
|
+
svg.setAttribute("aria-label", shell.messages.canvas);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
unnameCanvas() {
|
|
575
|
+
const { svg, canvasOriginals } = this;
|
|
576
|
+
this.canvasOriginals = void 0;
|
|
577
|
+
if (!svg || !canvasOriginals) return;
|
|
578
|
+
for (const [name, value] of canvasOriginals) {
|
|
579
|
+
if (value === null) svg.removeAttribute(name);
|
|
580
|
+
else svg.setAttribute(name, value);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
onPaletteCreate(container) {
|
|
584
|
+
this.skin?.destroy();
|
|
585
|
+
const palette = this.injector.get("palette", false);
|
|
586
|
+
const skin = new PaletteSkin(container, () => palette?.getEntries() ?? {});
|
|
587
|
+
this.skin = skin;
|
|
588
|
+
skin.setActiveTool(this.activeTool);
|
|
589
|
+
if (this.shell) skin.setLabel(this.shell.messages.tools);
|
|
590
|
+
this.moveBeforeCanvas(container);
|
|
591
|
+
this.registerPalette();
|
|
592
|
+
}
|
|
593
|
+
// Palette#_init appends the palette after the <svg>; in front of it the palette comes first in
|
|
594
|
+
// the tab order (guide §5.14).
|
|
595
|
+
moveBeforeCanvas(container) {
|
|
596
|
+
const parent = container.parentElement ?? this.canvasContainer();
|
|
597
|
+
if (!parent) return;
|
|
598
|
+
const svg = this.svg?.parentNode === parent ? this.svg : Array.from(parent.children).find((child) => child.localName === "svg");
|
|
599
|
+
if (svg) parent.insertBefore(container, svg);
|
|
600
|
+
}
|
|
601
|
+
canvasContainer() {
|
|
602
|
+
const canvas = this.injector.get("canvas", false);
|
|
603
|
+
return canvas?.getContainer();
|
|
604
|
+
}
|
|
605
|
+
onPaletteTrigger = (event) => {
|
|
606
|
+
if (!event.entry || !this.skin?.vetoes(event.entry, event.event)) return void 0;
|
|
607
|
+
event.event?.preventDefault();
|
|
608
|
+
return false;
|
|
609
|
+
};
|
|
610
|
+
/**
|
|
611
|
+
* `true` stops the diagram-js keyboard (and makes it call `preventDefault`), `false` stops it
|
|
612
|
+
* without: a chrome key in a text field inside the `<svg>` stays the field's (Shift+1 types "!"),
|
|
613
|
+
* and in the IDE host undo and redo stay the document's (guide §13.5), so they reach VS Code
|
|
614
|
+
* instead of the canvas `commandStack`.
|
|
615
|
+
*/
|
|
616
|
+
onKeyboardKeydown = (context) => {
|
|
617
|
+
const event = context.keyEvent;
|
|
618
|
+
if (!event) return void 0;
|
|
619
|
+
if (matchChromeKey(event)) return !isTextField(event.target);
|
|
620
|
+
if (this.shell?.host === "ide" && isUndoRedoKey(event)) return false;
|
|
621
|
+
return void 0;
|
|
622
|
+
};
|
|
623
|
+
subscribe(name, listener, priority) {
|
|
624
|
+
const handler = listener;
|
|
625
|
+
if (priority === void 0) this.eventBus.on(name, handler);
|
|
626
|
+
else this.eventBus.on(name, priority, handler);
|
|
627
|
+
this.subscriptions.push([name, handler]);
|
|
628
|
+
}
|
|
629
|
+
destroy() {
|
|
630
|
+
if (this.destroyed) return;
|
|
631
|
+
this.destroyed = true;
|
|
632
|
+
this.detachShell();
|
|
633
|
+
this.skin?.destroy();
|
|
634
|
+
this.skin = void 0;
|
|
635
|
+
this.svg = void 0;
|
|
636
|
+
for (const [name, listener] of this.subscriptions.splice(0)) this.eventBus.off(name, listener);
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
function shellServiceOf(diagram) {
|
|
640
|
+
let service;
|
|
641
|
+
try {
|
|
642
|
+
service = diagram.get(SHELL_SERVICE);
|
|
643
|
+
} catch {
|
|
644
|
+
service = void 0;
|
|
645
|
+
}
|
|
646
|
+
if (!isShellService(service)) {
|
|
647
|
+
throw new Error(
|
|
648
|
+
"@miragon/modeler-shell: connectDiagramJs needs shellModule in the modeler's modules (additionalModules: [shellModule])"
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
return service;
|
|
652
|
+
}
|
|
653
|
+
function isShellService(value) {
|
|
654
|
+
return typeof value?.attachShell === "function";
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// src/diagram-js/module.ts
|
|
658
|
+
var shellModule = {
|
|
659
|
+
__init__: [SHELL_SERVICE],
|
|
660
|
+
[SHELL_SERVICE]: ["type", ShellDiagramService],
|
|
661
|
+
paletteTooltip: ["value", null]
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
// src/diagram-js/viewport.ts
|
|
665
|
+
function createViewport(diagram, options) {
|
|
666
|
+
return new DiagramViewport(diagram, options);
|
|
667
|
+
}
|
|
668
|
+
var IMPORT_START_EVENTS = ["import.parse.start", "import.render.start"];
|
|
669
|
+
var IMPORT_DONE_EVENT = "import.done";
|
|
670
|
+
var CONTENT_EVENTS = [
|
|
671
|
+
"shape.added",
|
|
672
|
+
"shape.removed",
|
|
673
|
+
"connection.added",
|
|
674
|
+
"connection.removed",
|
|
675
|
+
"diagram.clear"
|
|
676
|
+
];
|
|
677
|
+
var ZOOM_STEP = Math.log10(ZOOM_MAX / ZOOM_MIN) / 10;
|
|
678
|
+
var CAPTURE = { capture: true };
|
|
679
|
+
var DiagramViewport = class {
|
|
680
|
+
constructor(diagram, options) {
|
|
681
|
+
this.diagram = diagram;
|
|
682
|
+
this.options = options;
|
|
683
|
+
this.canvas = diagram.get("canvas");
|
|
684
|
+
this.eventBus = diagram.get("eventBus");
|
|
685
|
+
this.container = this.canvas.getContainer();
|
|
686
|
+
this.subscribe("canvas.viewbox.changing", this.notify);
|
|
687
|
+
this.subscribe("canvas.viewbox.changed", this.onViewboxChanged);
|
|
688
|
+
this.subscribe(IMPORT_START_EVENTS, this.onImportStart);
|
|
689
|
+
this.subscribe(IMPORT_DONE_EVENT, this.onImportDone);
|
|
690
|
+
this.subscribe("elements.changed", this.onElementsChanged);
|
|
691
|
+
this.subscribe(CONTENT_EVENTS, this.onContentChanged);
|
|
692
|
+
this.subscribe("diagram.destroy", () => this.destroy());
|
|
693
|
+
this.container.addEventListener("wheel", this.onPointerInput, { capture: true, passive: true });
|
|
694
|
+
this.container.addEventListener("pointerdown", this.onPointerInput, CAPTURE);
|
|
695
|
+
this.container.addEventListener("keydown", this.onKeyInput, CAPTURE);
|
|
696
|
+
if (typeof ResizeObserver === "function") {
|
|
697
|
+
this.observer = new ResizeObserver(this.onResizeObserved);
|
|
698
|
+
this.observer.observe(this.container);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
diagram;
|
|
702
|
+
options;
|
|
703
|
+
canvas;
|
|
704
|
+
eventBus;
|
|
705
|
+
container;
|
|
706
|
+
listeners = /* @__PURE__ */ new Set();
|
|
707
|
+
subscriptions = [];
|
|
708
|
+
observer;
|
|
709
|
+
zoomScrollService;
|
|
710
|
+
/** The last view the viewport set as its own (fit target, restore); user moves differ from it. */
|
|
711
|
+
reference;
|
|
712
|
+
/** The last view written, animation frames included. */
|
|
713
|
+
lastWritten;
|
|
714
|
+
/** The container size the current view was set or last resized for. */
|
|
715
|
+
lastSize;
|
|
716
|
+
writing = 0;
|
|
717
|
+
userMoved = false;
|
|
718
|
+
importing = false;
|
|
719
|
+
imported = false;
|
|
720
|
+
firstFitPending = true;
|
|
721
|
+
/** Nothing has decided the view yet, so an empty first import shows 100 %. */
|
|
722
|
+
emptyViewPending = true;
|
|
723
|
+
/** The 100 % of an empty first import waits for the container to get a size (hidden tab). */
|
|
724
|
+
emptyViewDue = false;
|
|
725
|
+
/** `canFit()` of the current content; the zoom panel asks every frame, `contentBounds` may be costly. */
|
|
726
|
+
fittable;
|
|
727
|
+
lastZoom = 1;
|
|
728
|
+
/** An automatic fit is due but the container had no size yet (hidden tab). */
|
|
729
|
+
autoFitDue = false;
|
|
730
|
+
/** A restored view waits for the container to get a size. */
|
|
731
|
+
pendingView;
|
|
732
|
+
animation;
|
|
733
|
+
resizeFrame;
|
|
734
|
+
destroyed = false;
|
|
735
|
+
zoom() {
|
|
736
|
+
if (!this.destroyed) this.lastZoom = this.canvas.zoom();
|
|
737
|
+
return this.lastZoom;
|
|
738
|
+
}
|
|
739
|
+
stepZoom(direction) {
|
|
740
|
+
if (this.destroyed) return;
|
|
741
|
+
this.beforeUserChange();
|
|
742
|
+
const zoom = this.canvas.zoom();
|
|
743
|
+
if (direction < 0 ? !canZoomOut(zoom) : !canZoomIn(zoom)) return;
|
|
744
|
+
const zoomScroll = this.zoomScroll();
|
|
745
|
+
if (zoomScroll) zoomScroll.stepZoom(direction);
|
|
746
|
+
else this.canvas.zoom(stepOnRaster(zoom, direction));
|
|
747
|
+
}
|
|
748
|
+
zoomToActual() {
|
|
749
|
+
if (this.destroyed) return;
|
|
750
|
+
this.beforeUserChange();
|
|
751
|
+
this.canvas.zoom(1);
|
|
752
|
+
}
|
|
753
|
+
fit(options) {
|
|
754
|
+
if (this.destroyed) return;
|
|
755
|
+
this.importing = false;
|
|
756
|
+
this.fitTo(options?.animate !== false && !this.prefersReducedMotion());
|
|
757
|
+
}
|
|
758
|
+
canFit() {
|
|
759
|
+
if (this.destroyed) return false;
|
|
760
|
+
this.fittable ??= hasExtent(this.options.contentBounds());
|
|
761
|
+
return this.fittable;
|
|
762
|
+
}
|
|
763
|
+
onChange(listener) {
|
|
764
|
+
this.listeners.add(listener);
|
|
765
|
+
return () => {
|
|
766
|
+
this.listeners.delete(listener);
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
afterImport() {
|
|
770
|
+
if (this.destroyed) return;
|
|
771
|
+
this.importing = false;
|
|
772
|
+
this.imported = true;
|
|
773
|
+
this.fittable = void 0;
|
|
774
|
+
if (this.firstFitPending && this.canFit()) {
|
|
775
|
+
this.firstFitPending = false;
|
|
776
|
+
this.emptyViewPending = false;
|
|
777
|
+
this.emptyViewDue = false;
|
|
778
|
+
if (!this.userMoved && !this.fitTo(false)) this.autoFitDue = true;
|
|
779
|
+
} else if (this.firstFitPending && this.emptyViewPending) {
|
|
780
|
+
this.emptyViewPending = false;
|
|
781
|
+
if (!this.userMoved && !this.showActualSize()) this.emptyViewDue = true;
|
|
782
|
+
}
|
|
783
|
+
this.notify();
|
|
784
|
+
}
|
|
785
|
+
restore(viewbox) {
|
|
786
|
+
if (this.destroyed) return;
|
|
787
|
+
this.importing = false;
|
|
788
|
+
this.imported = true;
|
|
789
|
+
this.fittable = void 0;
|
|
790
|
+
this.emptyViewPending = false;
|
|
791
|
+
this.emptyViewDue = false;
|
|
792
|
+
this.cancelAnimation();
|
|
793
|
+
this.show(viewbox);
|
|
794
|
+
this.notify();
|
|
795
|
+
}
|
|
796
|
+
freeRectCenter() {
|
|
797
|
+
if (this.destroyed) return void 0;
|
|
798
|
+
const centre = freeRectCenter(this.canvas.getSize(), this.options.insets());
|
|
799
|
+
const view = this.currentView();
|
|
800
|
+
if (!centre || !view) return void 0;
|
|
801
|
+
return { x: view.x + centre.x / view.scale, y: view.y + centre.y / view.scale };
|
|
802
|
+
}
|
|
803
|
+
getViewbox() {
|
|
804
|
+
if (this.destroyed) return void 0;
|
|
805
|
+
if (this.pendingView) return { ...this.pendingView };
|
|
806
|
+
const size = this.canvas.getSize();
|
|
807
|
+
if (!hasSize(size)) return void 0;
|
|
808
|
+
const { x, y, scale } = this.canvas.viewbox();
|
|
809
|
+
return { x, y, width: size.width / scale, height: size.height / scale };
|
|
810
|
+
}
|
|
811
|
+
isAutomatic() {
|
|
812
|
+
return !this.userMoved;
|
|
813
|
+
}
|
|
814
|
+
getState() {
|
|
815
|
+
if (!this.imported) return void 0;
|
|
816
|
+
const viewbox = this.getViewbox();
|
|
817
|
+
return viewbox ? { viewbox, userMoved: this.userMoved } : void 0;
|
|
818
|
+
}
|
|
819
|
+
setState(state) {
|
|
820
|
+
if (this.destroyed) return;
|
|
821
|
+
this.importing = false;
|
|
822
|
+
this.imported = true;
|
|
823
|
+
this.fittable = void 0;
|
|
824
|
+
this.cancelAnimation();
|
|
825
|
+
this.userMoved = state.userMoved;
|
|
826
|
+
this.autoFitDue = false;
|
|
827
|
+
this.emptyViewPending = false;
|
|
828
|
+
this.emptyViewDue = false;
|
|
829
|
+
if (this.canFit()) this.firstFitPending = false;
|
|
830
|
+
this.show(state.viewbox);
|
|
831
|
+
this.notify();
|
|
832
|
+
}
|
|
833
|
+
/** `canvas.resized()`; viewbox changes it causes (keep-selection-visible) are not user moves. */
|
|
834
|
+
resized() {
|
|
835
|
+
if (this.destroyed) return;
|
|
836
|
+
this.writing++;
|
|
837
|
+
try {
|
|
838
|
+
this.canvas.resized();
|
|
839
|
+
} finally {
|
|
840
|
+
this.writing--;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
destroy() {
|
|
844
|
+
if (this.destroyed) return;
|
|
845
|
+
this.destroyed = true;
|
|
846
|
+
this.cancelAnimation();
|
|
847
|
+
if (this.resizeFrame !== void 0) cancelAnimationFrame(this.resizeFrame);
|
|
848
|
+
this.resizeFrame = void 0;
|
|
849
|
+
this.observer?.disconnect();
|
|
850
|
+
this.container.removeEventListener("wheel", this.onPointerInput, CAPTURE);
|
|
851
|
+
this.container.removeEventListener("pointerdown", this.onPointerInput, CAPTURE);
|
|
852
|
+
this.container.removeEventListener("keydown", this.onKeyInput, CAPTURE);
|
|
853
|
+
for (const [events, listener] of this.subscriptions.splice(0)) this.eventBus.off(events, listener);
|
|
854
|
+
this.listeners.clear();
|
|
855
|
+
}
|
|
856
|
+
/** Fits now (jump) or animated; false when there is nothing to fit or no container size. */
|
|
857
|
+
fitTo(animate) {
|
|
858
|
+
this.cancelAnimation();
|
|
859
|
+
const bounds = this.options.contentBounds();
|
|
860
|
+
if (!bounds) return false;
|
|
861
|
+
const target = fitViewbox(bounds, this.canvas.getSize(), this.options.insets());
|
|
862
|
+
if (!target) return false;
|
|
863
|
+
this.userMoved = false;
|
|
864
|
+
this.firstFitPending = false;
|
|
865
|
+
this.autoFitDue = false;
|
|
866
|
+
this.emptyViewPending = false;
|
|
867
|
+
this.emptyViewDue = false;
|
|
868
|
+
this.pendingView = void 0;
|
|
869
|
+
const from = this.currentView();
|
|
870
|
+
if (animate && from && !sameView(from, target) && typeof requestAnimationFrame === "function") {
|
|
871
|
+
this.animate(from, target);
|
|
872
|
+
} else {
|
|
873
|
+
this.write(target);
|
|
874
|
+
}
|
|
875
|
+
return true;
|
|
876
|
+
}
|
|
877
|
+
animate(from, target) {
|
|
878
|
+
this.reference = toView(target);
|
|
879
|
+
const animation = { frame: 0 };
|
|
880
|
+
const startedAt = performance.now();
|
|
881
|
+
const step = (timestamp) => {
|
|
882
|
+
if (this.animation !== animation) return;
|
|
883
|
+
if (!hasSize(this.canvas.getSize())) {
|
|
884
|
+
this.cancelAnimation();
|
|
885
|
+
this.autoFitDue = true;
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
const progress = Math.min(1, Math.max(0, (timestamp - startedAt) / FIT_ANIMATION_MS));
|
|
889
|
+
if (progress >= 1) {
|
|
890
|
+
this.animation = void 0;
|
|
891
|
+
this.write(target);
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
this.write(interpolateViewbox(from, target, easeStandard(progress)), false);
|
|
895
|
+
animation.frame = requestAnimationFrame(step);
|
|
896
|
+
};
|
|
897
|
+
this.animation = animation;
|
|
898
|
+
animation.frame = requestAnimationFrame(step);
|
|
899
|
+
}
|
|
900
|
+
cancelAnimation() {
|
|
901
|
+
const animation = this.animation;
|
|
902
|
+
if (!animation) return;
|
|
903
|
+
this.animation = void 0;
|
|
904
|
+
cancelAnimationFrame(animation.frame);
|
|
905
|
+
if (this.lastWritten) this.reference = this.lastWritten;
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* 100 % around the centre of the free rectangle, written as the viewport's own view; true when
|
|
909
|
+
* the view is at 100 % afterwards, false while the container has no size.
|
|
910
|
+
*/
|
|
911
|
+
showActualSize() {
|
|
912
|
+
const size = this.canvas.getSize();
|
|
913
|
+
const centre = freeRectCenter(size, this.options.insets());
|
|
914
|
+
const view = this.currentView();
|
|
915
|
+
if (!centre || !view) return false;
|
|
916
|
+
if (Math.abs(view.scale - 1) <= VIEW_TOLERANCE) return true;
|
|
917
|
+
this.cancelAnimation();
|
|
918
|
+
this.write({
|
|
919
|
+
x: view.x + centre.x / view.scale - centre.x,
|
|
920
|
+
y: view.y + centre.y / view.scale - centre.y,
|
|
921
|
+
width: size.width,
|
|
922
|
+
height: size.height
|
|
923
|
+
});
|
|
924
|
+
return true;
|
|
925
|
+
}
|
|
926
|
+
/** Shows a restored view now, or once the container has a size. */
|
|
927
|
+
show(viewbox) {
|
|
928
|
+
if (hasSize(this.canvas.getSize())) {
|
|
929
|
+
this.pendingView = void 0;
|
|
930
|
+
this.write(viewbox);
|
|
931
|
+
} else {
|
|
932
|
+
this.pendingView = { x: viewbox.x, y: viewbox.y, width: viewbox.width, height: viewbox.height };
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
/** Callers make sure the container has a size: diagram-js would set a scale of 0 otherwise. */
|
|
936
|
+
write(box, asReference = true) {
|
|
937
|
+
const size = this.canvas.getSize();
|
|
938
|
+
const view = {
|
|
939
|
+
x: box.x,
|
|
940
|
+
y: box.y,
|
|
941
|
+
scale: Math.min(size.width / box.width, size.height / box.height)
|
|
942
|
+
};
|
|
943
|
+
this.writing++;
|
|
944
|
+
try {
|
|
945
|
+
this.canvas.viewbox({ x: box.x, y: box.y, width: box.width, height: box.height });
|
|
946
|
+
} finally {
|
|
947
|
+
this.writing--;
|
|
948
|
+
}
|
|
949
|
+
this.lastWritten = view;
|
|
950
|
+
if (asReference) this.reference = view;
|
|
951
|
+
this.lastSize = { width: size.width, height: size.height };
|
|
952
|
+
}
|
|
953
|
+
currentView() {
|
|
954
|
+
const size = this.canvas.getSize();
|
|
955
|
+
const { x, y, scale } = this.canvas.viewbox();
|
|
956
|
+
if (!(scale > 0 && Number.isFinite(scale))) return void 0;
|
|
957
|
+
return { x, y, width: size.width / scale, height: size.height / scale, scale };
|
|
958
|
+
}
|
|
959
|
+
beforeUserChange() {
|
|
960
|
+
this.importing = false;
|
|
961
|
+
this.cancelAnimation();
|
|
962
|
+
}
|
|
963
|
+
zoomScroll() {
|
|
964
|
+
if (this.zoomScrollService === void 0) {
|
|
965
|
+
try {
|
|
966
|
+
this.zoomScrollService = this.diagram.get("zoomScroll") ?? null;
|
|
967
|
+
} catch {
|
|
968
|
+
this.zoomScrollService = null;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return this.zoomScrollService;
|
|
972
|
+
}
|
|
973
|
+
prefersReducedMotion() {
|
|
974
|
+
if (this.options.prefersReducedMotion) return this.options.prefersReducedMotion();
|
|
975
|
+
if (this.container.ownerDocument.documentElement.classList.contains(REDUCED_MOTION_CLASS)) return true;
|
|
976
|
+
return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
977
|
+
}
|
|
978
|
+
isOwnView(current) {
|
|
979
|
+
if (this.reference && sameView(current, this.reference)) return true;
|
|
980
|
+
return this.animation !== void 0 && this.lastWritten !== void 0 && sameView(current, this.lastWritten);
|
|
981
|
+
}
|
|
982
|
+
// Not a flag around the own writes alone: with `canvas.deferUpdate` the event of an own change
|
|
983
|
+
// arrives up to 300ms later, so it is recognised by its view.
|
|
984
|
+
onViewboxChanged = (event) => {
|
|
985
|
+
if (this.writing === 0 && !this.importing) {
|
|
986
|
+
const current = event?.viewbox ?? this.canvas.viewbox();
|
|
987
|
+
if (!this.isOwnView(current)) {
|
|
988
|
+
this.cancelAnimation();
|
|
989
|
+
this.userMoved = true;
|
|
990
|
+
this.autoFitDue = false;
|
|
991
|
+
this.emptyViewDue = false;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
this.notify();
|
|
995
|
+
};
|
|
996
|
+
onImportStart = () => {
|
|
997
|
+
this.importing = true;
|
|
998
|
+
};
|
|
999
|
+
onImportDone = () => {
|
|
1000
|
+
this.importing = false;
|
|
1001
|
+
this.fittable = void 0;
|
|
1002
|
+
this.notify();
|
|
1003
|
+
};
|
|
1004
|
+
onContentChanged = () => {
|
|
1005
|
+
this.fittable = void 0;
|
|
1006
|
+
};
|
|
1007
|
+
// Guide §5.9: an element the user creates on the canvas never fits. Once such content exists,
|
|
1008
|
+
// only imports that keep the view follow, so the first automatic fit is no longer due.
|
|
1009
|
+
onElementsChanged = () => {
|
|
1010
|
+
this.fittable = void 0;
|
|
1011
|
+
if (this.imported && !this.importing && this.firstFitPending && this.canFit()) {
|
|
1012
|
+
this.firstFitPending = false;
|
|
1013
|
+
}
|
|
1014
|
+
this.notify();
|
|
1015
|
+
};
|
|
1016
|
+
// User input also ends an import window a failed import never closed with `import.done`.
|
|
1017
|
+
onPointerInput = () => {
|
|
1018
|
+
this.importing = false;
|
|
1019
|
+
this.cancelAnimation();
|
|
1020
|
+
};
|
|
1021
|
+
onKeyInput = (event) => {
|
|
1022
|
+
this.importing = false;
|
|
1023
|
+
if (matchChromeKey(event)) this.cancelAnimation();
|
|
1024
|
+
};
|
|
1025
|
+
onResizeObserved = () => {
|
|
1026
|
+
if (this.destroyed || this.resizeFrame !== void 0) return;
|
|
1027
|
+
if (typeof requestAnimationFrame !== "function") {
|
|
1028
|
+
this.onResize();
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
this.resizeFrame = requestAnimationFrame(() => {
|
|
1032
|
+
this.resizeFrame = void 0;
|
|
1033
|
+
this.onResize();
|
|
1034
|
+
});
|
|
1035
|
+
};
|
|
1036
|
+
onResize() {
|
|
1037
|
+
if (this.destroyed) return;
|
|
1038
|
+
const size = this.canvas.getSize();
|
|
1039
|
+
if (!hasSize(size)) return;
|
|
1040
|
+
const changed = this.lastSize?.width !== size.width || this.lastSize.height !== size.height;
|
|
1041
|
+
if (!changed && !this.pendingView && !this.autoFitDue && !this.emptyViewDue) return;
|
|
1042
|
+
this.lastSize = { width: size.width, height: size.height };
|
|
1043
|
+
this.resized();
|
|
1044
|
+
const restored = this.pendingView;
|
|
1045
|
+
if (restored) {
|
|
1046
|
+
this.pendingView = void 0;
|
|
1047
|
+
this.write(restored);
|
|
1048
|
+
}
|
|
1049
|
+
if (this.userMoved) return;
|
|
1050
|
+
if (this.emptyViewDue) {
|
|
1051
|
+
this.emptyViewDue = false;
|
|
1052
|
+
this.showActualSize();
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
if (this.autoFitDue || !restored && !this.firstFitPending) this.fitTo(false);
|
|
1056
|
+
}
|
|
1057
|
+
subscribe(events, listener) {
|
|
1058
|
+
this.eventBus.on(events, listener);
|
|
1059
|
+
this.subscriptions.push([events, listener]);
|
|
1060
|
+
}
|
|
1061
|
+
notify = () => {
|
|
1062
|
+
for (const listener of [...this.listeners]) listener();
|
|
1063
|
+
};
|
|
1064
|
+
};
|
|
1065
|
+
function toView(box) {
|
|
1066
|
+
return { x: box.x, y: box.y, scale: box.scale };
|
|
1067
|
+
}
|
|
1068
|
+
function hasSize(size) {
|
|
1069
|
+
return size.width > 0 && size.height > 0;
|
|
1070
|
+
}
|
|
1071
|
+
function hasExtent(bounds) {
|
|
1072
|
+
return bounds !== void 0 && (bounds.width > 0 || bounds.height > 0);
|
|
1073
|
+
}
|
|
1074
|
+
function stepOnRaster(zoom, direction) {
|
|
1075
|
+
const level = Math.round(Math.log10(zoom) / ZOOM_STEP) * ZOOM_STEP + ZOOM_STEP * direction;
|
|
1076
|
+
return Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, 10 ** level));
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// src/diagram-js/connect.ts
|
|
1080
|
+
var HINT_SYNC_PRIORITY = 500;
|
|
1081
|
+
function connectDiagramJs(shell, diagram, options) {
|
|
1082
|
+
const service = shellServiceOf(diagram);
|
|
1083
|
+
const eventBus = diagram.get("eventBus");
|
|
1084
|
+
const prefersReducedMotion = options.prefersReducedMotion?.bind(options);
|
|
1085
|
+
const viewport = new DiagramViewport(diagram, {
|
|
1086
|
+
contentBounds: () => options.contentBounds(),
|
|
1087
|
+
insets: () => shell.fitInsets(),
|
|
1088
|
+
...prefersReducedMotion ? { prefersReducedMotion } : {}
|
|
1089
|
+
});
|
|
1090
|
+
shell.setZoomTarget(viewport);
|
|
1091
|
+
const detachPalette = service.attachShell(shell);
|
|
1092
|
+
viewport.resized();
|
|
1093
|
+
const isEmpty = options.isEmpty ? () => options.isEmpty?.() === true : () => !viewport.canFit();
|
|
1094
|
+
const syncHint = () => shell.setEmptyHint(isEmpty());
|
|
1095
|
+
eventBus.on(["import.done", "elements.changed"], HINT_SYNC_PRIORITY, syncHint);
|
|
1096
|
+
let destroyed = false;
|
|
1097
|
+
const destroy = () => {
|
|
1098
|
+
if (destroyed) return;
|
|
1099
|
+
destroyed = true;
|
|
1100
|
+
eventBus.off(["import.done", "elements.changed"], syncHint);
|
|
1101
|
+
eventBus.off("diagram.destroy", destroy);
|
|
1102
|
+
detachPalette();
|
|
1103
|
+
shell.setZoomTarget(null);
|
|
1104
|
+
viewport.destroy();
|
|
1105
|
+
};
|
|
1106
|
+
eventBus.on("diagram.destroy", destroy);
|
|
1107
|
+
return { viewport, destroy };
|
|
1108
|
+
}
|
|
1109
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1110
|
+
0 && (module.exports = {
|
|
1111
|
+
connectDiagramJs,
|
|
1112
|
+
createViewport,
|
|
1113
|
+
shellModule
|
|
1114
|
+
});
|
|
1115
|
+
//# sourceMappingURL=diagram-js.cjs.map
|