@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,551 @@
|
|
|
1
|
+
// src/core/types.ts
|
|
2
|
+
var CHROME_ISLANDS = [
|
|
3
|
+
"menu",
|
|
4
|
+
"palette",
|
|
5
|
+
"primary",
|
|
6
|
+
"zoom",
|
|
7
|
+
"problems",
|
|
8
|
+
"legend",
|
|
9
|
+
"inspector",
|
|
10
|
+
"status"
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
// src/core/sizes.ts
|
|
14
|
+
var SHELL_TOKENS = {
|
|
15
|
+
edge: "--cd-shell-rand",
|
|
16
|
+
gap: "--cd-shell-luecke",
|
|
17
|
+
row: "--cd-shell-zeile",
|
|
18
|
+
target: "--cd-shell-ziel",
|
|
19
|
+
entry: "--cd-shell-eintrag",
|
|
20
|
+
free: "--cd-shell-frei",
|
|
21
|
+
menuMinWidth: "--cd-shell-menue-breite",
|
|
22
|
+
inspectorWidth: "--cd-shell-inspector-breite",
|
|
23
|
+
fitInsetTop: "--cd-shell-einpassen-oben",
|
|
24
|
+
fitInsetTopSecondRow: "--cd-shell-einpassen-oben-schmal",
|
|
25
|
+
fitInsetSide: "--cd-shell-einpassen-seite",
|
|
26
|
+
fitInsetBottom: "--cd-shell-einpassen-unten",
|
|
27
|
+
fitInsetBottomWeb: "--cd-shell-einpassen-unten-web",
|
|
28
|
+
narrow: "--cd-shell-schmal",
|
|
29
|
+
veryNarrow: "--cd-shell-sehr-schmal",
|
|
30
|
+
low: "--cd-shell-niedrig",
|
|
31
|
+
layerIslands: "--cd-shell-ebene-inseln",
|
|
32
|
+
layerInspector: "--cd-shell-ebene-inspector",
|
|
33
|
+
layerTopRow: "--cd-shell-ebene-reihe",
|
|
34
|
+
layerPopups: "--cd-shell-ebene-aufklapper",
|
|
35
|
+
layerDialog: "--cd-shell-ebene-dialog",
|
|
36
|
+
layerToast: "--cd-shell-ebene-toast"
|
|
37
|
+
};
|
|
38
|
+
var SHELL = {
|
|
39
|
+
edge: 12,
|
|
40
|
+
gap: 8,
|
|
41
|
+
row: 40,
|
|
42
|
+
target: 32,
|
|
43
|
+
entry: 36,
|
|
44
|
+
free: 64,
|
|
45
|
+
menuMinWidth: 248,
|
|
46
|
+
inspectorWidth: 288,
|
|
47
|
+
fitInsetTop: 88,
|
|
48
|
+
fitInsetTopSecondRow: 140,
|
|
49
|
+
fitInsetSide: 24,
|
|
50
|
+
fitInsetBottom: 64,
|
|
51
|
+
fitInsetBottomWeb: 92,
|
|
52
|
+
narrow: 760,
|
|
53
|
+
veryNarrow: 440,
|
|
54
|
+
low: 820,
|
|
55
|
+
layerIslands: 20,
|
|
56
|
+
layerInspector: 25,
|
|
57
|
+
layerTopRow: 30,
|
|
58
|
+
layerPopups: 40,
|
|
59
|
+
layerDialog: 50,
|
|
60
|
+
layerToast: 60
|
|
61
|
+
};
|
|
62
|
+
var PIXEL_OR_PLAIN_NUMBER = /^\s*-?(?:\d+\.?\d*|\.\d+)(?:px)?\s*$/;
|
|
63
|
+
function shellSizesFromTokens(shellGroup) {
|
|
64
|
+
const sizes = {};
|
|
65
|
+
for (const name of Object.keys(SHELL_TOKENS)) {
|
|
66
|
+
const token = SHELL_TOKENS[name];
|
|
67
|
+
const raw = Object.hasOwn(shellGroup, token) ? shellGroup[token] : void 0;
|
|
68
|
+
if (raw === void 0) {
|
|
69
|
+
throw new Error(`@miragon/modeler-shell: the shell token ${token} is missing`);
|
|
70
|
+
}
|
|
71
|
+
if (typeof raw !== "string" || !PIXEL_OR_PLAIN_NUMBER.test(raw)) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`@miragon/modeler-shell: the shell token ${token} is not a number: ${JSON.stringify(raw)}`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
sizes[name] = Number.parseFloat(raw);
|
|
77
|
+
}
|
|
78
|
+
return sizes;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/core/fit.ts
|
|
82
|
+
var MAX_FIT_ZOOM = 1;
|
|
83
|
+
var FIT_ANIMATION_MS = 250;
|
|
84
|
+
function fitViewbox(bounds, container, insets, maxZoom = MAX_FIT_ZOOM) {
|
|
85
|
+
if (!(container.width > 0 && container.height > 0)) return void 0;
|
|
86
|
+
const hasWidth = bounds.width > 0;
|
|
87
|
+
const hasHeight = bounds.height > 0;
|
|
88
|
+
if (!hasWidth && !hasHeight) return void 0;
|
|
89
|
+
const horizontal = freeExtent(container.width, insets.left, insets.right);
|
|
90
|
+
const vertical = freeExtent(container.height, insets.top, insets.bottom);
|
|
91
|
+
const contentWidth = hasWidth ? bounds.width : 0;
|
|
92
|
+
const contentHeight = hasHeight ? bounds.height : 0;
|
|
93
|
+
const scale = Math.min(
|
|
94
|
+
maxZoom,
|
|
95
|
+
hasWidth ? horizontal.extent / contentWidth : Infinity,
|
|
96
|
+
hasHeight ? vertical.extent / contentHeight : Infinity
|
|
97
|
+
);
|
|
98
|
+
const box = {
|
|
99
|
+
x: bounds.x + contentWidth / 2 - (horizontal.start + horizontal.extent / 2) / scale,
|
|
100
|
+
y: bounds.y + contentHeight / 2 - (vertical.start + vertical.extent / 2) / scale,
|
|
101
|
+
width: container.width / scale,
|
|
102
|
+
height: container.height / scale,
|
|
103
|
+
scale
|
|
104
|
+
};
|
|
105
|
+
return Object.values(box).every(Number.isFinite) && scale > 0 ? box : void 0;
|
|
106
|
+
}
|
|
107
|
+
function freeExtent(total, before, after) {
|
|
108
|
+
const extent = total - before - after;
|
|
109
|
+
return extent > 0 ? { start: before, extent } : { start: 0, extent: total };
|
|
110
|
+
}
|
|
111
|
+
function freeRectCenter(container, insets) {
|
|
112
|
+
if (!(container.width > 0 && container.height > 0)) return void 0;
|
|
113
|
+
const horizontal = freeExtent(container.width, insets.left, insets.right);
|
|
114
|
+
const vertical = freeExtent(container.height, insets.top, insets.bottom);
|
|
115
|
+
return {
|
|
116
|
+
x: horizontal.start + horizontal.extent / 2,
|
|
117
|
+
y: vertical.start + vertical.extent / 2
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function fitInsets(layout) {
|
|
121
|
+
const { container, host, paletteRow, problemsVisible, legendWidth, inspector } = layout;
|
|
122
|
+
const insets = {
|
|
123
|
+
top: paletteRow === "second-row" ? SHELL.fitInsetTopSecondRow : SHELL.fitInsetTop,
|
|
124
|
+
right: SHELL.fitInsetSide,
|
|
125
|
+
bottom: host === "web" && problemsVisible ? SHELL.fitInsetBottomWeb : SHELL.fitInsetBottom,
|
|
126
|
+
left: SHELL.fitInsetSide
|
|
127
|
+
};
|
|
128
|
+
const bottomSheetOpen = inspector !== void 0 && container.width <= SHELL.narrow;
|
|
129
|
+
if (inspector !== void 0 && !bottomSheetOpen) {
|
|
130
|
+
insets.right = SHELL.edge + SHELL.inspectorWidth + SHELL.fitInsetSide;
|
|
131
|
+
}
|
|
132
|
+
if (bottomSheetOpen) {
|
|
133
|
+
insets.bottom = inspector.sheetTop === void 0 ? SHELL.fitInsetBottom : container.height - inspector.sheetTop + SHELL.edge;
|
|
134
|
+
} else if (legendWidth !== void 0 && legendWidth > 0 && container.width > SHELL.veryNarrow) {
|
|
135
|
+
insets.left = SHELL.edge + legendWidth + SHELL.fitInsetSide;
|
|
136
|
+
}
|
|
137
|
+
return insets;
|
|
138
|
+
}
|
|
139
|
+
var VIEW_TOLERANCE = 1e-3;
|
|
140
|
+
function sameView(a, b, tolerance = VIEW_TOLERANCE) {
|
|
141
|
+
const close = (first, second) => Math.abs(first - second) <= tolerance * Math.max(1, Math.abs(first), Math.abs(second));
|
|
142
|
+
return close(a.scale, b.scale) && close(a.x * a.scale, b.x * b.scale) && close(a.y * a.scale, b.y * b.scale);
|
|
143
|
+
}
|
|
144
|
+
function interpolateViewbox(from, to, progress) {
|
|
145
|
+
if (!(progress > 0)) return { ...from };
|
|
146
|
+
if (progress >= 1) return { ...to };
|
|
147
|
+
const linear = (start, end) => start + (end - start) * progress;
|
|
148
|
+
const scale = from.scale > 0 && to.scale > 0 ? Math.exp(linear(Math.log(from.scale), Math.log(to.scale))) : linear(from.scale, to.scale);
|
|
149
|
+
const centreX = linear(from.x + from.width / 2, to.x + to.width / 2);
|
|
150
|
+
const centreY = linear(from.y + from.height / 2, to.y + to.height / 2);
|
|
151
|
+
const width = linear(from.width * from.scale, to.width * to.scale) / scale;
|
|
152
|
+
const height = linear(from.height * from.scale, to.height * to.scale) / scale;
|
|
153
|
+
return { x: centreX - width / 2, y: centreY - height / 2, width, height, scale };
|
|
154
|
+
}
|
|
155
|
+
function easeStandard(progress) {
|
|
156
|
+
return STANDARD_EASING(progress);
|
|
157
|
+
}
|
|
158
|
+
var STANDARD_EASING = cubicBezier(0.4, 0, 0.2, 1);
|
|
159
|
+
function cubicBezier(x1, y1, x2, y2) {
|
|
160
|
+
const polynomial = (first, second) => {
|
|
161
|
+
const c = 3 * first;
|
|
162
|
+
const b = 3 * (second - first) - c;
|
|
163
|
+
const a = 1 - c - b;
|
|
164
|
+
return {
|
|
165
|
+
at: (t) => ((a * t + b) * t + c) * t,
|
|
166
|
+
slope: (t) => (3 * a * t + 2 * b) * t + c
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
const curveX = polynomial(x1, x2);
|
|
170
|
+
const curveY = polynomial(y1, y2);
|
|
171
|
+
const precision = 1e-7;
|
|
172
|
+
const parameterFor = (x) => {
|
|
173
|
+
let t = x;
|
|
174
|
+
for (let iteration = 0; iteration < 8; iteration++) {
|
|
175
|
+
const error = curveX.at(t) - x;
|
|
176
|
+
if (Math.abs(error) < precision) return t;
|
|
177
|
+
const slope = curveX.slope(t);
|
|
178
|
+
if (Math.abs(slope) < 1e-6) break;
|
|
179
|
+
t -= error / slope;
|
|
180
|
+
if (t < 0 || t > 1) break;
|
|
181
|
+
}
|
|
182
|
+
let low = 0;
|
|
183
|
+
let high = 1;
|
|
184
|
+
t = x;
|
|
185
|
+
for (let iteration = 0; iteration < 60; iteration++) {
|
|
186
|
+
const value = curveX.at(t);
|
|
187
|
+
if (Math.abs(value - x) < precision) break;
|
|
188
|
+
if (value < x) low = t;
|
|
189
|
+
else high = t;
|
|
190
|
+
t = (low + high) / 2;
|
|
191
|
+
}
|
|
192
|
+
return t;
|
|
193
|
+
};
|
|
194
|
+
return (progress) => {
|
|
195
|
+
if (!(progress > 0)) return 0;
|
|
196
|
+
if (progress >= 1) return 1;
|
|
197
|
+
return curveY.at(parameterFor(progress));
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/core/zoom.ts
|
|
202
|
+
var ZOOM_MIN = 0.2;
|
|
203
|
+
var ZOOM_MAX = 4;
|
|
204
|
+
function canZoomOut(zoom) {
|
|
205
|
+
return zoom > ZOOM_MIN * 1.001;
|
|
206
|
+
}
|
|
207
|
+
function canZoomIn(zoom) {
|
|
208
|
+
return zoom < ZOOM_MAX * 0.999;
|
|
209
|
+
}
|
|
210
|
+
function formatPercent(zoom, lang) {
|
|
211
|
+
return numberFormat(lang, "percent").format(zoom);
|
|
212
|
+
}
|
|
213
|
+
function formatNumber(value, lang) {
|
|
214
|
+
return numberFormat(lang, "decimal").format(value);
|
|
215
|
+
}
|
|
216
|
+
var FALLBACK_LANG = "en";
|
|
217
|
+
var formats = /* @__PURE__ */ new Map();
|
|
218
|
+
function numberFormat(lang, style) {
|
|
219
|
+
const key = `${style}|${lang}`;
|
|
220
|
+
let format = formats.get(key);
|
|
221
|
+
if (!format) {
|
|
222
|
+
const options = style === "percent" ? { style, maximumFractionDigits: 0 } : { style };
|
|
223
|
+
try {
|
|
224
|
+
format = new Intl.NumberFormat(lang, options);
|
|
225
|
+
} catch {
|
|
226
|
+
format = new Intl.NumberFormat(FALLBACK_LANG, options);
|
|
227
|
+
}
|
|
228
|
+
formats.set(key, format);
|
|
229
|
+
}
|
|
230
|
+
return format;
|
|
231
|
+
}
|
|
232
|
+
function zoomReadout(zoom, lang, messages) {
|
|
233
|
+
const text = formatPercent(zoom, lang);
|
|
234
|
+
return {
|
|
235
|
+
text,
|
|
236
|
+
ariaLabel: messages.zoomToActualNow(formatPercent(1, lang), text),
|
|
237
|
+
canZoomOut: canZoomOut(zoom),
|
|
238
|
+
canZoomIn: canZoomIn(zoom)
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/core/keymap.ts
|
|
243
|
+
function matchChromeKey(event) {
|
|
244
|
+
if (event.altKey) return void 0;
|
|
245
|
+
if (isCommand(event)) {
|
|
246
|
+
if (event.key === "+" || event.key === "=" || event.code === "NumpadAdd") return "zoomIn";
|
|
247
|
+
if (event.key === "-" || event.key === "_" || event.code === "NumpadSubtract") return "zoomOut";
|
|
248
|
+
if (event.key === "0" || event.code === "Numpad0") return "zoomToActual";
|
|
249
|
+
return void 0;
|
|
250
|
+
}
|
|
251
|
+
return event.shiftKey && event.code === "Digit1" ? "fit" : void 0;
|
|
252
|
+
}
|
|
253
|
+
function isCommand(event) {
|
|
254
|
+
return event.metaKey || event.ctrlKey;
|
|
255
|
+
}
|
|
256
|
+
function isUndoRedoKey(event) {
|
|
257
|
+
if (event.altKey || !isCommand(event)) return false;
|
|
258
|
+
const letter = undoRedoLetter(event);
|
|
259
|
+
return letter === "z" || letter === "y" && !event.shiftKey;
|
|
260
|
+
}
|
|
261
|
+
function undoRedoLetter(event) {
|
|
262
|
+
if (event.key.length === 1 && event.key.charCodeAt(0) < 128) return event.key.toLowerCase();
|
|
263
|
+
if (event.code === "KeyZ") return "z";
|
|
264
|
+
if (event.code === "KeyY") return "y";
|
|
265
|
+
return void 0;
|
|
266
|
+
}
|
|
267
|
+
var TEXT_FIELD_SELECTOR = "input, textarea, select, [contenteditable]:not([contenteditable='false'])";
|
|
268
|
+
function decideKey(event, context) {
|
|
269
|
+
const stop = context.host === "ide";
|
|
270
|
+
const composing = event.isComposing === true;
|
|
271
|
+
if (event.key === "Escape" && !event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey) {
|
|
272
|
+
if (context.menuOpen && !composing) {
|
|
273
|
+
return { action: "closeMenu", preventDefault: true, stopPropagation: stop };
|
|
274
|
+
}
|
|
275
|
+
if (context.menuOpen || context.dialogOpen) return { preventDefault: false, stopPropagation: stop };
|
|
276
|
+
}
|
|
277
|
+
const chromeKey = matchChromeKey(event);
|
|
278
|
+
if (chromeKey !== void 0) {
|
|
279
|
+
const blocked = composing || context.inTextField || context.modalOpen;
|
|
280
|
+
if (!blocked) return { action: chromeKey, preventDefault: true, stopPropagation: stop };
|
|
281
|
+
return { preventDefault: false, stopPropagation: chromeKey === "fit" ? false : stop };
|
|
282
|
+
}
|
|
283
|
+
if (context.inTextField && isUndoRedoKey(event)) return { preventDefault: false, stopPropagation: stop };
|
|
284
|
+
if (context.defaultPrevented) return { preventDefault: false, stopPropagation: stop };
|
|
285
|
+
return { preventDefault: false, stopPropagation: false };
|
|
286
|
+
}
|
|
287
|
+
function detectPlatform(navigator) {
|
|
288
|
+
const platform = navigator?.userAgentData?.platform || navigator?.platform || "";
|
|
289
|
+
return /mac/i.test(platform) ? "mac" : "other";
|
|
290
|
+
}
|
|
291
|
+
function shortcutLabel(key, platform, keyNames) {
|
|
292
|
+
if (platform === "mac") return MAC_SHORTCUT_LABELS[key];
|
|
293
|
+
switch (key) {
|
|
294
|
+
case "zoomIn":
|
|
295
|
+
return `${keyNames.ctrl}+${keyNames.plus}`;
|
|
296
|
+
case "zoomOut":
|
|
297
|
+
return `${keyNames.ctrl}+${keyNames.minus}`;
|
|
298
|
+
case "zoomToActual":
|
|
299
|
+
return `${keyNames.ctrl}+0`;
|
|
300
|
+
case "fit":
|
|
301
|
+
return `${keyNames.shift}+1`;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
var MAC_SHORTCUT_LABELS = {
|
|
305
|
+
zoomIn: "\u2318+",
|
|
306
|
+
zoomOut: "\u2318\u2212",
|
|
307
|
+
zoomToActual: "\u23180",
|
|
308
|
+
fit: "\u21E71"
|
|
309
|
+
};
|
|
310
|
+
function ariaKeyShortcuts(key, platform) {
|
|
311
|
+
const command = platform === "mac" ? "Meta" : "Control";
|
|
312
|
+
switch (key) {
|
|
313
|
+
case "zoomIn":
|
|
314
|
+
return `${command}+= ${command}+Plus`;
|
|
315
|
+
case "zoomOut":
|
|
316
|
+
return `${command}+-`;
|
|
317
|
+
case "zoomToActual":
|
|
318
|
+
return `${command}+0`;
|
|
319
|
+
case "fit":
|
|
320
|
+
return "Shift+1";
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
var MAC_MODIFIER_SYMBOLS = {
|
|
324
|
+
"\u2318": "Meta",
|
|
325
|
+
"\u21E7": "Shift",
|
|
326
|
+
"\u2325": "Alt",
|
|
327
|
+
"\u2303": "Control"
|
|
328
|
+
};
|
|
329
|
+
var MODIFIER_NAMES = {
|
|
330
|
+
cmd: "Meta",
|
|
331
|
+
command: "Meta",
|
|
332
|
+
meta: "Meta",
|
|
333
|
+
ctrl: "Control",
|
|
334
|
+
control: "Control",
|
|
335
|
+
strg: "Control",
|
|
336
|
+
shift: "Shift",
|
|
337
|
+
umschalt: "Shift",
|
|
338
|
+
alt: "Alt",
|
|
339
|
+
option: "Alt",
|
|
340
|
+
opt: "Alt"
|
|
341
|
+
};
|
|
342
|
+
var NAMED_KEYS = {
|
|
343
|
+
esc: "Escape",
|
|
344
|
+
escape: "Escape",
|
|
345
|
+
del: "Delete",
|
|
346
|
+
delete: "Delete",
|
|
347
|
+
entf: "Delete",
|
|
348
|
+
backspace: "Backspace",
|
|
349
|
+
enter: "Enter",
|
|
350
|
+
return: "Enter",
|
|
351
|
+
tab: "Tab",
|
|
352
|
+
space: "Space",
|
|
353
|
+
home: "Home",
|
|
354
|
+
end: "End",
|
|
355
|
+
pageup: "PageUp",
|
|
356
|
+
pagedown: "PageDown",
|
|
357
|
+
arrowup: "ArrowUp",
|
|
358
|
+
arrowdown: "ArrowDown",
|
|
359
|
+
arrowleft: "ArrowLeft",
|
|
360
|
+
arrowright: "ArrowRight",
|
|
361
|
+
"\u2191": "ArrowUp",
|
|
362
|
+
"\u2193": "ArrowDown",
|
|
363
|
+
"\u2190": "ArrowLeft",
|
|
364
|
+
"\u2192": "ArrowRight",
|
|
365
|
+
plus: "Plus"
|
|
366
|
+
};
|
|
367
|
+
function ariaKeyName(key) {
|
|
368
|
+
const named = NAMED_KEYS[key.toLowerCase()];
|
|
369
|
+
if (named) return named;
|
|
370
|
+
if (/^f\d{1,2}$/i.test(key)) return key.toUpperCase();
|
|
371
|
+
if (key.length !== 1 || MAC_MODIFIER_SYMBOLS[key]) return void 0;
|
|
372
|
+
if (key === "+") return "Plus";
|
|
373
|
+
if (key === " ") return "Space";
|
|
374
|
+
return key.toUpperCase();
|
|
375
|
+
}
|
|
376
|
+
function ariaKeyShortcutsFromLabel(label) {
|
|
377
|
+
let rest = label.trim();
|
|
378
|
+
const modifiers = [];
|
|
379
|
+
for (let symbol = MAC_MODIFIER_SYMBOLS[rest.charAt(0)]; symbol && rest.length > 1; ) {
|
|
380
|
+
modifiers.push(symbol);
|
|
381
|
+
rest = rest.slice(1).trim();
|
|
382
|
+
symbol = MAC_MODIFIER_SYMBOLS[rest.charAt(0)];
|
|
383
|
+
}
|
|
384
|
+
if (!rest || /\s/.test(rest.replace(/\s*\+\s*/g, "+"))) return void 0;
|
|
385
|
+
const parts = rest.split("+").map((part) => part.trim());
|
|
386
|
+
let key = parts.pop() ?? "";
|
|
387
|
+
if (key === "" && parts.at(-1) === "") {
|
|
388
|
+
parts.pop();
|
|
389
|
+
key = "+";
|
|
390
|
+
}
|
|
391
|
+
for (const part of parts) {
|
|
392
|
+
const modifier = MODIFIER_NAMES[part.toLowerCase()];
|
|
393
|
+
if (!modifier) return void 0;
|
|
394
|
+
modifiers.push(modifier);
|
|
395
|
+
}
|
|
396
|
+
const name = ariaKeyName(key);
|
|
397
|
+
return name ? [...new Set(modifiers), name].join("+") : void 0;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/core/appearance.ts
|
|
401
|
+
function resolveAppearance(requested, themes) {
|
|
402
|
+
const [fallback] = themes;
|
|
403
|
+
if (fallback === void 0) {
|
|
404
|
+
throw new Error(
|
|
405
|
+
"@miragon/modeler-shell: resolveAppearance needs at least one theme the modeler can show"
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
const theme = themes.includes(requested.theme) ? requested.theme : fallback;
|
|
409
|
+
return withKontrast(theme, requested.kontrast);
|
|
410
|
+
}
|
|
411
|
+
function withKontrast(theme, kontrast) {
|
|
412
|
+
return kontrast === "hoch" ? { theme, kontrast } : { theme };
|
|
413
|
+
}
|
|
414
|
+
function appearanceFromColorThemeKind(kind) {
|
|
415
|
+
switch (kind) {
|
|
416
|
+
case 2:
|
|
417
|
+
return { theme: "dark" };
|
|
418
|
+
case 3:
|
|
419
|
+
return { theme: "dark", kontrast: "hoch" };
|
|
420
|
+
case 4:
|
|
421
|
+
return { theme: "light", kontrast: "hoch" };
|
|
422
|
+
default:
|
|
423
|
+
return { theme: "light" };
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function requestedAppearance(kind, setting) {
|
|
427
|
+
const fromKind = appearanceFromColorThemeKind(kind);
|
|
428
|
+
const theme = setting === "light" || setting === "dark" ? setting : fromKind.theme;
|
|
429
|
+
return withKontrast(theme, fromKind.kontrast);
|
|
430
|
+
}
|
|
431
|
+
function appearanceFromBodyClasses(classes) {
|
|
432
|
+
const present = new Set(classes);
|
|
433
|
+
if (present.has("vscode-high-contrast-light")) return { theme: "light", kontrast: "hoch" };
|
|
434
|
+
if (present.has("vscode-high-contrast")) return { theme: "dark", kontrast: "hoch" };
|
|
435
|
+
if (present.has("vscode-dark")) return { theme: "dark" };
|
|
436
|
+
if (present.has("vscode-light")) return { theme: "light" };
|
|
437
|
+
return void 0;
|
|
438
|
+
}
|
|
439
|
+
function reducedMotionFromBodyClasses(classes) {
|
|
440
|
+
return new Set(classes).has("vscode-reduce-motion");
|
|
441
|
+
}
|
|
442
|
+
var REDUCED_MOTION_CLASS = "cd-shell-reduce-motion";
|
|
443
|
+
function appearanceAttributes(appearance) {
|
|
444
|
+
return appearance.kontrast === "hoch" ? { "data-cd-theme": appearance.theme, "data-cd-kontrast": "hoch" } : { "data-cd-theme": appearance.theme };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/core/messages.ts
|
|
448
|
+
var defaultMessages = {
|
|
449
|
+
menu: "Menu",
|
|
450
|
+
tools: "Tools",
|
|
451
|
+
canvas: "Diagram",
|
|
452
|
+
zoom: "Zoom",
|
|
453
|
+
zoomOut: "Zoom out",
|
|
454
|
+
zoomIn: "Zoom in",
|
|
455
|
+
zoomToActual: (percent) => `Zoom to ${percent}`,
|
|
456
|
+
zoomToActualNow: (percent, now) => `Zoom to ${percent} (now ${now})`,
|
|
457
|
+
fit: "Fit to view",
|
|
458
|
+
legend: "Legend",
|
|
459
|
+
closeLegend: "Close legend",
|
|
460
|
+
problems: (count, formattedCount) => `${formattedCount} ${count === 1 ? "problem" : "problems"}`,
|
|
461
|
+
problemsLabel: (count, errors, formattedCount, formattedErrors) => `${formattedCount} ${count === 1 ? "problem" : "problems"}, ${errors === 0 ? "no errors" : `${formattedErrors} ${errors === 1 ? "error" : "errors"}`}`,
|
|
462
|
+
showProblems: "Show in Problems view",
|
|
463
|
+
emptyHint: "Pick a tool from the palette to start.",
|
|
464
|
+
insertExample: "Insert example",
|
|
465
|
+
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.`,
|
|
466
|
+
keyNames: { ctrl: "Ctrl", shift: "Shift", plus: "Plus", minus: "Minus" }
|
|
467
|
+
};
|
|
468
|
+
function resolveMessages(overrides) {
|
|
469
|
+
const { keyNames, ...texts } = overrides ?? {};
|
|
470
|
+
return {
|
|
471
|
+
...defaultMessages,
|
|
472
|
+
...definedEntries(texts),
|
|
473
|
+
keyNames: { ...defaultMessages.keyNames, ...definedEntries(keyNames ?? {}) }
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
function definedEntries(partial) {
|
|
477
|
+
return Object.fromEntries(Object.entries(partial).filter(([, value]) => value !== void 0));
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// src/core/layout.ts
|
|
481
|
+
var PALETTE_CLEARANCE = 16;
|
|
482
|
+
var TOOLTIP_DELAY_MS = 300;
|
|
483
|
+
var INVALID_STATUS_DELAY_MS = 800;
|
|
484
|
+
function decidePaletteRow(input) {
|
|
485
|
+
const { containerWidth, paletteWidth, menuRight, primaryLeft } = input;
|
|
486
|
+
if (containerWidth <= SHELL.narrow) return "second-row";
|
|
487
|
+
const paletteLeft = (containerWidth - paletteWidth) / 2;
|
|
488
|
+
const paletteRight = (containerWidth + paletteWidth) / 2;
|
|
489
|
+
if (paletteLeft < menuRight + PALETTE_CLEARANCE) return "second-row";
|
|
490
|
+
if (primaryLeft !== void 0 && paletteRight > primaryLeft - PALETTE_CLEARANCE) return "second-row";
|
|
491
|
+
return "top";
|
|
492
|
+
}
|
|
493
|
+
function legendStartsOpen(containerHeight) {
|
|
494
|
+
return containerHeight > SHELL.low;
|
|
495
|
+
}
|
|
496
|
+
function placeTooltip(input) {
|
|
497
|
+
const { anchor, island, tooltip, container } = input;
|
|
498
|
+
const inUpperHalf = island.y + island.height / 2 < container.height / 2;
|
|
499
|
+
const y = inUpperHalf ? island.y + island.height + SHELL.gap : island.y - SHELL.gap - tooltip.height;
|
|
500
|
+
const centred = anchor.x + anchor.width / 2 - tooltip.width / 2;
|
|
501
|
+
const rightmost = container.width - SHELL.edge - tooltip.width;
|
|
502
|
+
const x = Math.max(SHELL.edge, Math.min(centred, rightmost));
|
|
503
|
+
return { x, y };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export {
|
|
507
|
+
CHROME_ISLANDS,
|
|
508
|
+
SHELL_TOKENS,
|
|
509
|
+
SHELL,
|
|
510
|
+
shellSizesFromTokens,
|
|
511
|
+
MAX_FIT_ZOOM,
|
|
512
|
+
FIT_ANIMATION_MS,
|
|
513
|
+
fitViewbox,
|
|
514
|
+
freeRectCenter,
|
|
515
|
+
fitInsets,
|
|
516
|
+
VIEW_TOLERANCE,
|
|
517
|
+
sameView,
|
|
518
|
+
interpolateViewbox,
|
|
519
|
+
easeStandard,
|
|
520
|
+
ZOOM_MIN,
|
|
521
|
+
ZOOM_MAX,
|
|
522
|
+
canZoomOut,
|
|
523
|
+
canZoomIn,
|
|
524
|
+
formatPercent,
|
|
525
|
+
formatNumber,
|
|
526
|
+
zoomReadout,
|
|
527
|
+
matchChromeKey,
|
|
528
|
+
isUndoRedoKey,
|
|
529
|
+
TEXT_FIELD_SELECTOR,
|
|
530
|
+
decideKey,
|
|
531
|
+
detectPlatform,
|
|
532
|
+
shortcutLabel,
|
|
533
|
+
ariaKeyShortcuts,
|
|
534
|
+
ariaKeyShortcutsFromLabel,
|
|
535
|
+
resolveAppearance,
|
|
536
|
+
appearanceFromColorThemeKind,
|
|
537
|
+
requestedAppearance,
|
|
538
|
+
appearanceFromBodyClasses,
|
|
539
|
+
reducedMotionFromBodyClasses,
|
|
540
|
+
REDUCED_MOTION_CLASS,
|
|
541
|
+
appearanceAttributes,
|
|
542
|
+
defaultMessages,
|
|
543
|
+
resolveMessages,
|
|
544
|
+
PALETTE_CLEARANCE,
|
|
545
|
+
TOOLTIP_DELAY_MS,
|
|
546
|
+
INVALID_STATUS_DELAY_MS,
|
|
547
|
+
decidePaletteRow,
|
|
548
|
+
legendStartsOpen,
|
|
549
|
+
placeTooltip
|
|
550
|
+
};
|
|
551
|
+
//# sourceMappingURL=chunk-5JSZ4VGA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/types.ts","../src/core/sizes.ts","../src/core/fit.ts","../src/core/zoom.ts","../src/core/keymap.ts","../src/core/appearance.ts","../src/core/messages.ts","../src/core/layout.ts"],"sourcesContent":["/** A point in pixels or diagram coordinates, as the context says. */\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport interface Size {\n width: number;\n height: number;\n}\n\n/** A rectangle; content bounds and viewboxes are in diagram coordinates. */\nexport interface Bounds {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/** A viewbox together with the scale diagram-js shows it at (`canvas.viewbox().scale`). */\nexport interface ScaledViewbox extends Bounds {\n scale: number;\n}\n\n/** Distances in pixels from the edges of the modeler's container. */\nexport interface Insets {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n\n/**\n * The host column of guide §5.2: `ide` is a VS Code webview (later IntelliJ), `web` a Miragon web\n * app that owns its page, `embedded` a modeler inside a foreign page (designIQ, modeler-api hosts).\n */\nexport type ShellHost = \"ide\" | \"web\" | \"embedded\";\n\n/** The `data-chrome` names of the islands (guide §5.2, §13.4). `primary` is reserved for the web app. */\nexport type ChromeIsland =\n \"menu\" | \"palette\" | \"primary\" | \"zoom\" | \"problems\" | \"legend\" | \"inspector\" | \"status\";\n\nexport const CHROME_ISLANDS: readonly ChromeIsland[] = [\n \"menu\",\n \"palette\",\n \"primary\",\n \"zoom\",\n \"problems\",\n \"legend\",\n \"inspector\",\n \"status\",\n];\n\n/** Where the palette sits: in the top row, or in the second row below it (guide §5.15). */\nexport type PaletteRow = \"top\" | \"second-row\";\n\n/**\n * What the zoom panel and the chrome keys act on. The diagram-js viewport implements it; the DOM\n * shell only talks to this interface.\n */\nexport interface ZoomTarget {\n /** The current zoom, 1 = 100 % (diagram-js `canvas.zoom()`, rounded to three decimals). */\n zoom(): number;\n /** One step on the zoom raster around the container centre (diagram-js `zoomScroll.stepZoom`). */\n stepZoom(direction: 1 | -1): void;\n /** 100 % around the container centre. Counts as a user move. */\n zoomToActual(): void;\n /** Explicit fit (button, Shift+1, command): animated unless reduced motion, makes the view automatic. */\n fit(): void;\n /** False while there is nothing to fit (the content bounds are empty). */\n canFit(): boolean;\n /**\n * Calls `listener` after every viewbox change and whenever `canFit()` may have changed, possibly\n * several times per frame. Returns the unsubscribe function.\n */\n onChange(listener: () => void): () => void;\n}\n\n/** The persisted view (VS Code `setState`): JSON-serializable. */\nexport interface ViewportState {\n viewbox: Bounds;\n /** The user zoomed or panned since the last fit; a resize no longer refits. */\n userMoved: boolean;\n}\n","/**\n * The shell dimensions of the design system (guide §5.1, `shell` group of\n * `cd-tokens.generated.json`), keyed by a readable name. Scripts use `SHELL`; CSS uses the\n * `var(--cd-shell-*)` variables the consumer vendors. A drift test pins `SHELL` to the token JSON.\n */\nexport const SHELL_TOKENS = {\n edge: \"--cd-shell-rand\",\n gap: \"--cd-shell-luecke\",\n row: \"--cd-shell-zeile\",\n target: \"--cd-shell-ziel\",\n entry: \"--cd-shell-eintrag\",\n free: \"--cd-shell-frei\",\n menuMinWidth: \"--cd-shell-menue-breite\",\n inspectorWidth: \"--cd-shell-inspector-breite\",\n fitInsetTop: \"--cd-shell-einpassen-oben\",\n fitInsetTopSecondRow: \"--cd-shell-einpassen-oben-schmal\",\n fitInsetSide: \"--cd-shell-einpassen-seite\",\n fitInsetBottom: \"--cd-shell-einpassen-unten\",\n fitInsetBottomWeb: \"--cd-shell-einpassen-unten-web\",\n narrow: \"--cd-shell-schmal\",\n veryNarrow: \"--cd-shell-sehr-schmal\",\n low: \"--cd-shell-niedrig\",\n layerIslands: \"--cd-shell-ebene-inseln\",\n layerInspector: \"--cd-shell-ebene-inspector\",\n layerTopRow: \"--cd-shell-ebene-reihe\",\n layerPopups: \"--cd-shell-ebene-aufklapper\",\n layerDialog: \"--cd-shell-ebene-dialog\",\n layerToast: \"--cd-shell-ebene-toast\",\n} as const;\n\nexport type ShellSizeName = keyof typeof SHELL_TOKENS;\n\nexport type ShellSizes = Readonly<Record<ShellSizeName, number>>;\n\n/** Pixels (layers: plain z-index numbers), equal to `parseFloat` of the token JSON values. */\nexport const SHELL: ShellSizes = {\n edge: 12,\n gap: 8,\n row: 40,\n target: 32,\n entry: 36,\n free: 64,\n menuMinWidth: 248,\n inspectorWidth: 288,\n fitInsetTop: 88,\n fitInsetTopSecondRow: 140,\n fitInsetSide: 24,\n fitInsetBottom: 64,\n fitInsetBottomWeb: 92,\n narrow: 760,\n veryNarrow: 440,\n low: 820,\n layerIslands: 20,\n layerInspector: 25,\n layerTopRow: 30,\n layerPopups: 40,\n layerDialog: 50,\n layerToast: 60,\n};\n\n// Shell sizes are pixels (\"12px\") or plain z-index numbers (\"20\"); any other unit would make\n// parseFloat silently return a number with the wrong meaning.\nconst PIXEL_OR_PLAIN_NUMBER = /^\\s*-?(?:\\d+\\.?\\d*|\\.\\d+)(?:px)?\\s*$/;\n\n/**\n * Reads the `shell` group of `cd-tokens.generated.json` (`{ \"--cd-shell-rand\": \"12px\", … }`) into\n * numbers. Throws when a size of `SHELL_TOKENS` is missing or not a number.\n */\nexport function shellSizesFromTokens(shellGroup: Readonly<Record<string, string>>): ShellSizes {\n const sizes = {} as Record<ShellSizeName, number>;\n for (const name of Object.keys(SHELL_TOKENS) as ShellSizeName[]) {\n const token = SHELL_TOKENS[name];\n const raw: unknown = Object.hasOwn(shellGroup, token) ? shellGroup[token] : undefined;\n if (raw === undefined) {\n throw new Error(`@miragon/modeler-shell: the shell token ${token} is missing`);\n }\n if (typeof raw !== \"string\" || !PIXEL_OR_PLAIN_NUMBER.test(raw)) {\n throw new Error(\n `@miragon/modeler-shell: the shell token ${token} is not a number: ${JSON.stringify(raw)}`,\n );\n }\n sizes[name] = Number.parseFloat(raw);\n }\n return sizes;\n}\n","import { SHELL } from \"./sizes.js\";\nimport type { Bounds, Insets, PaletteRow, Point, ScaledViewbox, ShellHost, Size } from \"./types.js\";\n\n/** Fit never zooms above 100 % (guide §5.9). */\nexport const MAX_FIT_ZOOM = 1;\n\n/** Duration of the explicit, animated fit (`--cd-motion-base`). */\nexport const FIT_ANIMATION_MS = 250;\n\n/**\n * The viewbox that shows `bounds` centred in the free rectangle (`container` minus `insets`) at the\n * largest scale that fits, capped at `maxZoom`. The box has the container's aspect ratio, so\n * diagram-js's `canvas.viewbox(box)` sets exactly this view. Returns `undefined` when the container\n * is 0 × 0 or `bounds` has neither width nor height; a zero width or height scales by the other\n * dimension. Where the insets leave no free room on an axis (a container lower or narrower than\n * its chrome), that axis centres in the whole container instead.\n */\nexport function fitViewbox(\n bounds: Bounds,\n container: Size,\n insets: Insets,\n maxZoom: number = MAX_FIT_ZOOM,\n): ScaledViewbox | undefined {\n if (!(container.width > 0 && container.height > 0)) return undefined;\n const hasWidth = bounds.width > 0;\n const hasHeight = bounds.height > 0;\n if (!hasWidth && !hasHeight) return undefined;\n\n const horizontal = freeExtent(container.width, insets.left, insets.right);\n const vertical = freeExtent(container.height, insets.top, insets.bottom);\n const contentWidth = hasWidth ? bounds.width : 0;\n const contentHeight = hasHeight ? bounds.height : 0;\n const scale = Math.min(\n maxZoom,\n hasWidth ? horizontal.extent / contentWidth : Infinity,\n hasHeight ? vertical.extent / contentHeight : Infinity,\n );\n const box: ScaledViewbox = {\n x: bounds.x + contentWidth / 2 - (horizontal.start + horizontal.extent / 2) / scale,\n y: bounds.y + contentHeight / 2 - (vertical.start + vertical.extent / 2) / scale,\n width: container.width / scale,\n height: container.height / scale,\n scale,\n };\n return Object.values(box).every(Number.isFinite) && scale > 0 ? box : undefined;\n}\n\nfunction freeExtent(total: number, before: number, after: number): { start: number; extent: number } {\n const extent = total - before - after;\n return extent > 0 ? { start: before, extent } : { start: 0, extent: total };\n}\n\n/**\n * The centre of the free rectangle (`container` minus `insets`) in pixels from the container's top\n * left, where `fitViewbox` centres the content and keyboard creation places an element (guide\n * §5.5). An axis the insets leave no room on centres in the whole container, as in `fitViewbox`.\n * Returns `undefined` when the container is 0 × 0.\n */\nexport function freeRectCenter(container: Size, insets: Insets): Point | undefined {\n if (!(container.width > 0 && container.height > 0)) return undefined;\n const horizontal = freeExtent(container.width, insets.left, insets.right);\n const vertical = freeExtent(container.height, insets.top, insets.bottom);\n return {\n x: horizontal.start + horizontal.extent / 2,\n y: vertical.start + vertical.extent / 2,\n };\n}\n\n/** What is open or shown when a fit runs (guide §5.9: every fit counts what is open right now). */\nexport interface FitLayout {\n container: Size;\n host: ShellHost;\n paletteRow: PaletteRow;\n /** The problems pill is shown (only the web app reserves room for it). */\n problemsVisible: boolean;\n /** Rendered width of the open legend; `undefined` when it is closed or the modeler has none. */\n legendWidth?: number | undefined;\n /**\n * The open inspector; `undefined` when closed. Above `SHELL.narrow` it is the side panel; up to it\n * a bottom sheet whose top edge (`sheetTop`, px from the container top) must be measured.\n */\n inspector?: { sheetTop?: number | undefined } | undefined;\n}\n\n/**\n * The fit insets of guide §5.9: top 88 (second row 140), sides 24, bottom 64 (web app with pill 92),\n * right 324 with an open side inspector, left 12 + legend width + 24 with an open legend above\n * `SHELL.veryNarrow`, bottom = distance to the sheet's top edge + 12 with an open bottom sheet.\n */\nexport function fitInsets(layout: FitLayout): Insets {\n const { container, host, paletteRow, problemsVisible, legendWidth, inspector } = layout;\n const insets: Insets = {\n top: paletteRow === \"second-row\" ? SHELL.fitInsetTopSecondRow : SHELL.fitInsetTop,\n right: SHELL.fitInsetSide,\n bottom: host === \"web\" && problemsVisible ? SHELL.fitInsetBottomWeb : SHELL.fitInsetBottom,\n left: SHELL.fitInsetSide,\n };\n\n const bottomSheetOpen = inspector !== undefined && container.width <= SHELL.narrow;\n if (inspector !== undefined && !bottomSheetOpen) {\n insets.right = SHELL.edge + SHELL.inspectorWidth + SHELL.fitInsetSide;\n }\n if (bottomSheetOpen) {\n // Guide §5.11: the sheet hides zoom panel, pill and legend, so only the sheet itself counts.\n insets.bottom =\n inspector.sheetTop === undefined\n ? SHELL.fitInsetBottom\n : container.height - inspector.sheetTop + SHELL.edge;\n } else if (legendWidth !== undefined && legendWidth > 0 && container.width > SHELL.veryNarrow) {\n insets.left = SHELL.edge + legendWidth + SHELL.fitInsetSide;\n }\n return insets;\n}\n\n/** Relative tolerance of `sameView` (diagram-js rounds the transform to three decimals). */\nexport const VIEW_TOLERANCE = 1e-3;\n\n/**\n * True when two views are the same within `tolerance`: the scale and the translation in screen\n * pixels (`x · scale`, `y · scale`) agree relative to `max(1, |value|)`. Width and height are\n * ignored, so a container resize is never a difference.\n */\nexport function sameView(\n a: Pick<ScaledViewbox, \"x\" | \"y\" | \"scale\">,\n b: Pick<ScaledViewbox, \"x\" | \"y\" | \"scale\">,\n tolerance: number = VIEW_TOLERANCE,\n): boolean {\n const close = (first: number, second: number): boolean =>\n Math.abs(first - second) <= tolerance * Math.max(1, Math.abs(first), Math.abs(second));\n return (\n close(a.scale, b.scale) && close(a.x * a.scale, b.x * b.scale) && close(a.y * a.scale, b.y * b.scale)\n );\n}\n\n/**\n * The view at `progress` (0…1) between two viewboxes of the same container: the scale is\n * interpolated in log space, the centre linearly. Used per animation frame by the animated fit.\n */\nexport function interpolateViewbox(from: ScaledViewbox, to: ScaledViewbox, progress: number): ScaledViewbox {\n if (!(progress > 0)) return { ...from };\n if (progress >= 1) return { ...to };\n const linear = (start: number, end: number): number => start + (end - start) * progress;\n const scale =\n from.scale > 0 && to.scale > 0\n ? Math.exp(linear(Math.log(from.scale), Math.log(to.scale)))\n : linear(from.scale, to.scale);\n const centreX = linear(from.x + from.width / 2, to.x + to.width / 2);\n const centreY = linear(from.y + from.height / 2, to.y + to.height / 2);\n const width = linear(from.width * from.scale, to.width * to.scale) / scale;\n const height = linear(from.height * from.scale, to.height * to.scale) / scale;\n return { x: centreX - width / 2, y: centreY - height / 2, width, height, scale };\n}\n\n/** `--cd-ease`, `cubic-bezier(0.4, 0, 0.2, 1)`, as a function of time 0…1. */\nexport function easeStandard(progress: number): number {\n return STANDARD_EASING(progress);\n}\n\nconst STANDARD_EASING = cubicBezier(0.4, 0, 0.2, 1);\n\n/** A CSS `cubic-bezier(x1, y1, x2, y2)` timing function (`x1`, `x2` within 0…1, so x(t) is monotone). */\nfunction cubicBezier(x1: number, y1: number, x2: number, y2: number): (progress: number) => number {\n const polynomial = (first: number, second: number) => {\n const c = 3 * first;\n const b = 3 * (second - first) - c;\n const a = 1 - c - b;\n return {\n at: (t: number): number => ((a * t + b) * t + c) * t,\n slope: (t: number): number => (3 * a * t + 2 * b) * t + c,\n };\n };\n const curveX = polynomial(x1, x2);\n const curveY = polynomial(y1, y2);\n const precision = 1e-7;\n\n const parameterFor = (x: number): number => {\n let t = x;\n for (let iteration = 0; iteration < 8; iteration++) {\n const error = curveX.at(t) - x;\n if (Math.abs(error) < precision) return t;\n const slope = curveX.slope(t);\n if (Math.abs(slope) < 1e-6) break;\n t -= error / slope;\n if (t < 0 || t > 1) break;\n }\n // Newton left the curve or stalled on a flat stretch: bisection always converges.\n let low = 0;\n let high = 1;\n t = x;\n for (let iteration = 0; iteration < 60; iteration++) {\n const value = curveX.at(t);\n if (Math.abs(value - x) < precision) break;\n if (value < x) low = t;\n else high = t;\n t = (low + high) / 2;\n }\n return t;\n };\n\n return (progress) => {\n if (!(progress > 0)) return 0;\n if (progress >= 1) return 1;\n return curveY.at(parameterFor(progress));\n };\n}\n","import type { ShellMessages } from \"./messages.js\";\n\n/** diagram-js `ZoomScroll` RANGE (not configurable): 20 % … 400 %. */\nexport const ZOOM_MIN = 0.2;\nexport const ZOOM_MAX = 4;\n\n/** Zoom out is disabled at 20 % and below (`zoom <= 0.2 · 1.001`), so it never zooms in. */\nexport function canZoomOut(zoom: number): boolean {\n return zoom > ZOOM_MIN * 1.001;\n}\n\n/** Zoom in is disabled at 400 % (`zoom >= 4 · 0.999`). */\nexport function canZoomIn(zoom: number): boolean {\n return zoom < ZOOM_MAX * 0.999;\n}\n\n/**\n * A zoom as a whole percentage in the UI language: `Intl.NumberFormat(lang, { style: \"percent\",\n * maximumFractionDigits: 0 })`, so English \"86%\", German \"86 %\" (with U+00A0).\n */\nexport function formatPercent(zoom: number, lang: string): string {\n return numberFormat(lang, \"percent\").format(zoom);\n}\n\n/** Any other number in the chrome (\"2 problems\"), via `Intl.NumberFormat(lang)`. */\nexport function formatNumber(value: number, lang: string): string {\n return numberFormat(lang, \"decimal\").format(value);\n}\n\nconst FALLBACK_LANG = \"en\";\nconst formats = new Map<string, Intl.NumberFormat>();\n\n// The readout formats on every animation frame of a zoom, so each formatter is built once. A\n// language tag Intl rejects (an empty `lang=\"\"`, a typo) falls back to English, the shell's default\n// language, instead of breaking the chrome.\nfunction numberFormat(lang: string, style: \"percent\" | \"decimal\"): Intl.NumberFormat {\n const key = `${style}|${lang}`;\n let format = formats.get(key);\n if (!format) {\n const options: Intl.NumberFormatOptions =\n style === \"percent\" ? { style, maximumFractionDigits: 0 } : { style };\n try {\n format = new Intl.NumberFormat(lang, options);\n } catch {\n format = new Intl.NumberFormat(FALLBACK_LANG, options);\n }\n formats.set(key, format);\n }\n return format;\n}\n\nexport interface ZoomReadout {\n /** \"86%\" */\n text: string;\n /** \"Zoom to 100% (now 86%)\" */\n ariaLabel: string;\n canZoomOut: boolean;\n canZoomIn: boolean;\n}\n\n/** Everything the zoom panel shows for a zoom: readout text, its accessible name and the limits. */\nexport function zoomReadout(\n zoom: number,\n lang: string,\n messages: Pick<ShellMessages, \"zoomToActualNow\">,\n): ZoomReadout {\n const text = formatPercent(zoom, lang);\n return {\n text,\n ariaLabel: messages.zoomToActualNow(formatPercent(1, lang), text),\n canZoomOut: canZoomOut(zoom),\n canZoomIn: canZoomIn(zoom),\n };\n}\n","import type { ShellMessages } from \"./messages.js\";\nimport type { ShellHost } from \"./types.js\";\n\n/** The only keys the chrome handles (guide §5.8). */\nexport type ChromeKey = \"zoomIn\" | \"zoomOut\" | \"zoomToActual\" | \"fit\";\n\n/** The `KeyboardEvent` fields the keymap reads. */\nexport interface KeyLike {\n key: string;\n code: string;\n metaKey: boolean;\n ctrlKey: boolean;\n shiftKey: boolean;\n altKey: boolean;\n isComposing?: boolean | undefined;\n}\n\n/**\n * Which chrome key `event` is, ignoring where it was pressed:\n * - `zoomIn`: Cmd/Ctrl + `+` or `=` (by `key`, with or without Shift), or `NumpadAdd` (by `code`);\n * - `zoomOut`: Cmd/Ctrl + `-` or `_`, or `NumpadSubtract`;\n * - `zoomToActual`: Cmd/Ctrl + `0`, or `Numpad0` (also with NumLock off);\n * - `fit`: Shift + `Digit1` (by `code`, every layout) without Cmd, Ctrl or Alt.\n * Cmd/Ctrl is `metaKey || ctrlKey` on every platform; nothing matches with `altKey` (AltGr).\n */\nexport function matchChromeKey(event: KeyLike): ChromeKey | undefined {\n if (event.altKey) return undefined;\n if (isCommand(event)) {\n if (event.key === \"+\" || event.key === \"=\" || event.code === \"NumpadAdd\") return \"zoomIn\";\n if (event.key === \"-\" || event.key === \"_\" || event.code === \"NumpadSubtract\") return \"zoomOut\";\n if (event.key === \"0\" || event.code === \"Numpad0\") return \"zoomToActual\";\n return undefined;\n }\n return event.shiftKey && event.code === \"Digit1\" ? \"fit\" : undefined;\n}\n\nfunction isCommand(event: KeyLike): boolean {\n return event.metaKey || event.ctrlKey;\n}\n\n/**\n * Cmd/Ctrl + Z, Cmd/Ctrl + Shift + Z or Cmd/Ctrl + Y, without Alt (by `key`, `code` as fallback).\n * Cmd/Ctrl + Shift + Y is not redo (VS Code toggles its Debug Console with it).\n */\nexport function isUndoRedoKey(event: KeyLike): boolean {\n if (event.altKey || !isCommand(event)) return false;\n const letter = undoRedoLetter(event);\n return letter === \"z\" || (letter === \"y\" && !event.shiftKey);\n}\n\n// The produced character decides on Latin layouts (QWERTZ swaps the Y and Z keys); only where the\n// layout produces no ASCII character (Cyrillic, Greek, …) the physical key stands in.\nfunction undoRedoLetter(event: KeyLike): string | undefined {\n if (event.key.length === 1 && event.key.charCodeAt(0) < 0x80) return event.key.toLowerCase();\n if (event.code === \"KeyZ\") return \"z\";\n if (event.code === \"KeyY\") return \"y\";\n return undefined;\n}\n\n/** A text field: every input, select list and editable text (label editor, inspector, dialogs). */\nexport const TEXT_FIELD_SELECTOR =\n \"input, textarea, select, [contenteditable]:not([contenteditable='false'])\";\n\n/** Where a key was pressed, as the shell's one `keydown` listener sees it. */\nexport interface KeyContext {\n host: ShellHost;\n /** The event target matches `TEXT_FIELD_SELECTOR` or is `isContentEditable`. */\n inTextField: boolean;\n /** The shell's menu dropdown is open. */\n menuOpen: boolean;\n /** A popover or dialog (`[role=\"dialog\"]`, modal or not) is open. */\n dialogOpen: boolean;\n /** A modal dialog (`[role=\"dialog\"][aria-modal=\"true\"]`) is open: chrome keys do not act. */\n modalOpen: boolean;\n /** `event.defaultPrevented` when the listener ran: the canvas (or the modeler) handled the key. */\n defaultPrevented: boolean;\n}\n\nexport type KeyAction = ChromeKey | \"closeMenu\";\n\nexport interface KeyDecision {\n /** What the shell executes; `undefined` = nothing. */\n action?: KeyAction;\n preventDefault: boolean;\n stopPropagation: boolean;\n}\n\n/**\n * The key rules of guide §5.8 and §13.5 in one place:\n * 1. Escape with the menu open: `closeMenu`, prevented; in the IDE host stopped whenever the menu\n * or a dialog/popover is open. During IME composition Escape belongs to the IME: no `closeMenu`.\n * 2. Zoom keys: executed and prevented unless composing, in a text field or with a modal dialog;\n * in the IDE host always stopped, also when not executed (VS Code must never zoom the window).\n * 3. Shift+1: `fit` under the same conditions; otherwise untouched (it types \"!\").\n * 4. IDE host, text field, undo/redo key: stopped only (the field keeps its native undo).\n * 5. IDE host, `defaultPrevented`: stopped (the canvas handled it; VS Code must not run it again).\n * Everything else passes untouched. `stopPropagation` is only ever true in the IDE host.\n */\nexport function decideKey(event: KeyLike, context: KeyContext): KeyDecision {\n const stop = context.host === \"ide\";\n const composing = event.isComposing === true;\n\n if (event.key === \"Escape\" && !event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey) {\n if (context.menuOpen && !composing) {\n return { action: \"closeMenu\", preventDefault: true, stopPropagation: stop };\n }\n if (context.menuOpen || context.dialogOpen) return { preventDefault: false, stopPropagation: stop };\n }\n\n const chromeKey = matchChromeKey(event);\n if (chromeKey !== undefined) {\n const blocked = composing || context.inTextField || context.modalOpen;\n if (!blocked) return { action: chromeKey, preventDefault: true, stopPropagation: stop };\n // Blocked zoom keys are still kept from VS Code (it would zoom the window); a blocked Shift+1\n // is a character and travels on like any other.\n return { preventDefault: false, stopPropagation: chromeKey === \"fit\" ? false : stop };\n }\n\n if (context.inTextField && isUndoRedoKey(event)) return { preventDefault: false, stopPropagation: stop };\n if (context.defaultPrevented) return { preventDefault: false, stopPropagation: stop };\n return { preventDefault: false, stopPropagation: false };\n}\n\n/** The keyboard the user sits at; decides the shortcut labels (guide §5.8). */\nexport type Platform = \"mac\" | \"other\";\n\n/** The `navigator` fields `detectPlatform` reads. */\nexport interface NavigatorLike {\n platform?: string | undefined;\n userAgentData?: { platform?: string | undefined } | undefined;\n}\n\n/**\n * `mac` when `userAgentData.platform ?? platform` matches `/mac/i`, else `other`; `other` without a\n * navigator. Read it in the webview or page itself, never from the extension host (VS Code Remote).\n */\nexport function detectPlatform(navigator?: NavigatorLike | undefined): Platform {\n // An empty userAgentData.platform means \"unknown\", not \"not a Mac\": fall back to navigator.platform.\n const platform = navigator?.userAgentData?.platform || navigator?.platform || \"\";\n return /mac/i.test(platform) ? \"mac\" : \"other\";\n}\n\n/**\n * The tooltip chip text: macOS `⌘+`, `⌘−` (U+2212), `⌘0`, `⇧1`; elsewhere `Ctrl+Plus`,\n * `Ctrl+Minus`, `Ctrl+0`, `Shift+1` with the key names of the messages table.\n */\nexport function shortcutLabel(\n key: ChromeKey,\n platform: Platform,\n keyNames: ShellMessages[\"keyNames\"],\n): string {\n if (platform === \"mac\") return MAC_SHORTCUT_LABELS[key];\n switch (key) {\n case \"zoomIn\":\n return `${keyNames.ctrl}+${keyNames.plus}`;\n case \"zoomOut\":\n return `${keyNames.ctrl}+${keyNames.minus}`;\n case \"zoomToActual\":\n return `${keyNames.ctrl}+0`;\n case \"fit\":\n return `${keyNames.shift}+1`;\n }\n}\n\nconst MAC_SHORTCUT_LABELS: Readonly<Record<ChromeKey, string>> = {\n zoomIn: \"⌘+\",\n zoomOut: \"⌘\\u2212\",\n zoomToActual: \"⌘0\",\n fit: \"⇧1\",\n};\n\n/**\n * The `aria-keyshortcuts` value (WAI-ARIA names): zoom in `Meta+= Meta+Plus`, zoom out `Meta+-`,\n * 100 % `Meta+0`, fit `Shift+1`; outside macOS `Control` instead of `Meta`.\n */\nexport function ariaKeyShortcuts(key: ChromeKey, platform: Platform): string {\n const command = platform === \"mac\" ? \"Meta\" : \"Control\";\n switch (key) {\n case \"zoomIn\":\n return `${command}+= ${command}+Plus`;\n case \"zoomOut\":\n return `${command}+-`;\n case \"zoomToActual\":\n return `${command}+0`;\n case \"fit\":\n return \"Shift+1\";\n }\n}\n\nconst MAC_MODIFIER_SYMBOLS: Readonly<Record<string, string>> = {\n \"⌘\": \"Meta\",\n \"⇧\": \"Shift\",\n \"⌥\": \"Alt\",\n \"⌃\": \"Control\",\n};\n\nconst MODIFIER_NAMES: Readonly<Record<string, string>> = {\n cmd: \"Meta\",\n command: \"Meta\",\n meta: \"Meta\",\n ctrl: \"Control\",\n control: \"Control\",\n strg: \"Control\",\n shift: \"Shift\",\n umschalt: \"Shift\",\n alt: \"Alt\",\n option: \"Alt\",\n opt: \"Alt\",\n};\n\nconst NAMED_KEYS: Readonly<Record<string, string>> = {\n esc: \"Escape\",\n escape: \"Escape\",\n del: \"Delete\",\n delete: \"Delete\",\n entf: \"Delete\",\n backspace: \"Backspace\",\n enter: \"Enter\",\n return: \"Enter\",\n tab: \"Tab\",\n space: \"Space\",\n home: \"Home\",\n end: \"End\",\n pageup: \"PageUp\",\n pagedown: \"PageDown\",\n arrowup: \"ArrowUp\",\n arrowdown: \"ArrowDown\",\n arrowleft: \"ArrowLeft\",\n arrowright: \"ArrowRight\",\n \"↑\": \"ArrowUp\",\n \"↓\": \"ArrowDown\",\n \"←\": \"ArrowLeft\",\n \"→\": \"ArrowRight\",\n plus: \"Plus\",\n};\n\nfunction ariaKeyName(key: string): string | undefined {\n const named = NAMED_KEYS[key.toLowerCase()];\n if (named) return named;\n if (/^f\\d{1,2}$/i.test(key)) return key.toUpperCase();\n if (key.length !== 1 || MAC_MODIFIER_SYMBOLS[key]) return undefined;\n if (key === \"+\") return \"Plus\";\n if (key === \" \") return \"Space\";\n return key.toUpperCase();\n}\n\n/**\n * The `aria-keyshortcuts` value (WAI-ARIA names, guide §5.6) for a shortcut chip label: `\"L\"` →\n * `\"L\"`, `\"⇧L\"` → `\"Shift+L\"`, `\"⌘⇧Z\"` → `\"Meta+Shift+Z\"`, `\"Ctrl+Shift+E\"` → `\"Control+Shift+E\"`,\n * `\"Del\"` → `\"Delete\"`, `\"Ctrl++\"` → `\"Control+Plus\"`. `undefined` for a label that is not one key\n * combination (`\"G then D\"`, `\"1–9\"`, empty).\n */\nexport function ariaKeyShortcutsFromLabel(label: string): string | undefined {\n let rest = label.trim();\n const modifiers: string[] = [];\n for (let symbol = MAC_MODIFIER_SYMBOLS[rest.charAt(0)]; symbol && rest.length > 1;) {\n modifiers.push(symbol);\n rest = rest.slice(1).trim();\n symbol = MAC_MODIFIER_SYMBOLS[rest.charAt(0)];\n }\n if (!rest || /\\s/.test(rest.replace(/\\s*\\+\\s*/g, \"+\"))) return undefined;\n const parts = rest.split(\"+\").map((part) => part.trim());\n let key = parts.pop() ?? \"\";\n if (key === \"\" && parts.at(-1) === \"\") {\n parts.pop();\n key = \"+\";\n }\n for (const part of parts) {\n const modifier = MODIFIER_NAMES[part.toLowerCase()];\n if (!modifier) return undefined;\n modifiers.push(modifier);\n }\n const name = ariaKeyName(key);\n return name ? [...new Set(modifiers), name].join(\"+\") : undefined;\n}\n","/** The modes a Miragon modeler can show (guide §13.3). */\nexport type Theme = \"light\" | \"dark\";\n\n/** High contrast; written as `data-cd-kontrast=\"hoch\"` on the same element as `data-cd-theme`. */\nexport type Kontrast = \"hoch\";\n\nexport interface Appearance {\n theme: Theme;\n kontrast?: Kontrast | undefined;\n}\n\n/**\n * The one appearance decision of guide §13.5: a theme the modeler cannot show is never shown\n * (`themes[0]` wins, so `[\"light\"]` stays light whatever the host requests); `kontrast` always\n * passes through. Throws when `themes` is empty.\n */\nexport function resolveAppearance(requested: Appearance, themes: readonly Theme[]): Appearance {\n const [fallback] = themes;\n if (fallback === undefined) {\n throw new Error(\n \"@miragon/modeler-shell: resolveAppearance needs at least one theme the modeler can show\",\n );\n }\n const theme = themes.includes(requested.theme) ? requested.theme : fallback;\n return withKontrast(theme, requested.kontrast);\n}\n\n// Appearances arrive through postMessage and settings, so only the one known value passes.\nfunction withKontrast(theme: Theme, kontrast: unknown): Appearance {\n return kontrast === \"hoch\" ? { theme, kontrast } : { theme };\n}\n\n/**\n * The appearance VS Code requests for `vscode.window.activeColorTheme.kind`:\n * 1 Light → light, 2 Dark → dark, 3 HighContrast → dark + hoch, 4 HighContrastLight → light + hoch;\n * any other value → light.\n */\nexport function appearanceFromColorThemeKind(kind: number): Appearance {\n switch (kind) {\n case 2:\n return { theme: \"dark\" };\n case 3:\n return { theme: \"dark\", kontrast: \"hoch\" };\n case 4:\n return { theme: \"light\", kontrast: \"hoch\" };\n default:\n return { theme: \"light\" };\n }\n}\n\n/** The values of the setting `miragon.<modeler>.colorTheme` (it ships once a modeler can show dark). */\nexport type ColorThemeSetting = \"automatic\" | \"light\" | \"dark\";\n\n/**\n * The requested appearance in an IDE host: the setting's theme unless it is `automatic` (or\n * missing), the contrast always from the color theme kind.\n */\nexport function requestedAppearance(kind: number, setting?: ColorThemeSetting | undefined): Appearance {\n const fromKind = appearanceFromColorThemeKind(kind);\n const theme = setting === \"light\" || setting === \"dark\" ? setting : fromKind.theme;\n return withKontrast(theme, fromKind.kontrast);\n}\n\n/**\n * The fallback signal inside a VS Code webview: the body classes `vscode-high-contrast-light`\n * (checked first, VS Code adds `vscode-high-contrast` to it as well), `vscode-high-contrast`,\n * `vscode-dark`, `vscode-light`. `undefined` when none is set. Never use them for styling.\n */\nexport function appearanceFromBodyClasses(classes: Iterable<string>): Appearance | undefined {\n const present = new Set(classes);\n if (present.has(\"vscode-high-contrast-light\")) return { theme: \"light\", kontrast: \"hoch\" };\n if (present.has(\"vscode-high-contrast\")) return { theme: \"dark\", kontrast: \"hoch\" };\n if (present.has(\"vscode-dark\")) return { theme: \"dark\" };\n if (present.has(\"vscode-light\")) return { theme: \"light\" };\n return undefined;\n}\n\n/**\n * VS Code's `workbench.reduceMotion` as the webview sees it: the body class `vscode-reduce-motion`\n * (the webview host page sets it). Never use it for styling; the shell mirrors it as\n * `REDUCED_MOTION_CLASS` on `<html>`.\n */\nexport function reducedMotionFromBodyClasses(classes: Iterable<string>): boolean {\n return new Set(classes).has(\"vscode-reduce-motion\");\n}\n\n/**\n * On `<html>` while the host asks for reduced motion although the OS does not (VS Code's\n * `workbench.reduceMotion`): the shell's animations stop as under `prefers-reduced-motion`, and an\n * explicit fit jumps.\n */\nexport const REDUCED_MOTION_CLASS = \"cd-shell-reduce-motion\";\n\nexport interface AppearanceAttributes {\n \"data-cd-theme\": Theme;\n \"data-cd-kontrast\"?: Kontrast;\n}\n\n/** The attributes for `<html>` (own document) or the modeler container (foreign page). */\nexport function appearanceAttributes(appearance: Appearance): AppearanceAttributes {\n return appearance.kontrast === \"hoch\"\n ? { \"data-cd-theme\": appearance.theme, \"data-cd-kontrast\": \"hoch\" }\n : { \"data-cd-theme\": appearance.theme };\n}\n","/**\n * Every text the shell shows or announces, in one table (guide §2, shared terms). English by\n * default; a localized modeler overrides the whole table in its language, numbers arrive already\n * formatted through `Intl` in that language.\n */\nexport interface ShellMessages {\n /** Menu trigger `aria-label` and tooltip, dropdown `aria-label`. */\n menu: string;\n /** Palette `aria-label`. */\n tools: string;\n /** `aria-label` of the focusable canvas `<svg>` (`role=\"application\"`); a modeler names its notation. */\n canvas: string;\n /** Zoom panel `aria-label`. */\n zoom: string;\n zoomOut: string;\n zoomIn: string;\n /** Tooltip of the readout, e.g. `(percent) => \"Zoom to \" + percent` with percent \"100%\". */\n zoomToActual(percent: string): string;\n /** `aria-label` of the readout: \"Zoom to 100% (now 86%)\". */\n zoomToActualNow(percent: string, now: string): string;\n fit: string;\n /** Legend title and the Model-group checkbox. */\n legend: string;\n closeLegend: string;\n /** Pill text: \"1 problem\", \"2 problems\". */\n problems(count: number, formattedCount: string): string;\n /** Pill `aria-label`: \"2 problems, 1 error\", \"2 problems, no errors\". */\n problemsLabel(count: number, errors: number, formattedCount: string, formattedErrors: string): string;\n /** Pill tooltip in the IDE host. */\n showProblems: string;\n /** Empty-file hint (guide §13.5). */\n emptyHint: string;\n insertExample: string;\n /** Status box for an invalid file; `diagram` names the notation (\"Event Storming board\"). */\n invalidFile(diagram: string): string;\n /** Key names in shortcut chips outside macOS (German: Strg, Umschalt, Plus, Minus). */\n keyNames: { ctrl: string; shift: string; plus: string; minus: string };\n}\n\n/** Every key optional; an `undefined` value keeps the default (a conditional override). */\nexport type ShellMessageOverrides = {\n [Key in Exclude<keyof ShellMessages, \"keyNames\">]?: ShellMessages[Key] | undefined;\n} & {\n keyNames?: { [Key in keyof ShellMessages[\"keyNames\"]]?: string | undefined } | undefined;\n};\n\nexport const defaultMessages: ShellMessages = {\n menu: \"Menu\",\n tools: \"Tools\",\n canvas: \"Diagram\",\n zoom: \"Zoom\",\n zoomOut: \"Zoom out\",\n zoomIn: \"Zoom in\",\n zoomToActual: (percent) => `Zoom to ${percent}`,\n zoomToActualNow: (percent, now) => `Zoom to ${percent} (now ${now})`,\n fit: \"Fit to view\",\n legend: \"Legend\",\n closeLegend: \"Close legend\",\n problems: (count, formattedCount) => `${formattedCount} ${count === 1 ? \"problem\" : \"problems\"}`,\n problemsLabel: (count, errors, formattedCount, formattedErrors) =>\n `${formattedCount} ${count === 1 ? \"problem\" : \"problems\"}, ${\n errors === 0 ? \"no errors\" : `${formattedErrors} ${errors === 1 ? \"error\" : \"errors\"}`\n }`,\n showProblems: \"Show in Problems view\",\n emptyHint: \"Pick a tool from the palette to start.\",\n insertExample: \"Insert example\",\n invalidFile: (diagram) =>\n `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.`,\n keyNames: { ctrl: \"Ctrl\", shift: \"Shift\", plus: \"Plus\", minus: \"Minus\" },\n};\n\n/** `defaultMessages` with `overrides` applied (`keyNames` merged key by key). */\nexport function resolveMessages(overrides?: ShellMessageOverrides | undefined): ShellMessages {\n const { keyNames, ...texts } = overrides ?? {};\n return {\n ...defaultMessages,\n ...definedEntries(texts),\n keyNames: { ...defaultMessages.keyNames, ...definedEntries(keyNames ?? {}) },\n };\n}\n\n// `{ menu: undefined }` (a spread of optional fields, a conditional override) keeps the default\n// instead of blanking the text.\nfunction definedEntries<T extends object>(partial: T): { [Key in keyof T]?: Exclude<T[Key], undefined> } {\n return Object.fromEntries(Object.entries(partial).filter(([, value]) => value !== undefined)) as {\n [Key in keyof T]?: Exclude<T[Key], undefined>;\n };\n}\n","import { SHELL } from \"./sizes.js\";\nimport type { Bounds, PaletteRow, Point, Size } from \"./types.js\";\n\n/** Free space the top row needs between the palette and a neighbouring island (guide §5.15). */\nexport const PALETTE_CLEARANCE = 16;\n\n/** Hover delay of the chrome tooltip, equal to diagram-js `HoverTooltip` `SHOW_DELAY`. */\nexport const TOOLTIP_DELAY_MS = 300;\n\n/** The status box for an invalid file shows after this long without a valid import (guide §5.12). */\nexport const INVALID_STATUS_DELAY_MS = 800;\n\nexport interface PaletteRowInput {\n containerWidth: number;\n /**\n * The palette's intrinsic border-box width (`scrollWidth` plus its borders), so the decision does\n * not flip once the second row clips it.\n */\n paletteWidth: number;\n /** Right edge of the menu trigger, relative to the shell root. */\n menuRight: number;\n /** Left edge of the primary action (web app), relative to the shell root; absent in IDE hosts. */\n primaryLeft?: number | undefined;\n}\n\n/**\n * `second-row` when the container is at most `SHELL.narrow` wide or a centred palette would keep\n * less than `PALETTE_CLEARANCE` to the menu trigger (or the primary action); `top` otherwise.\n */\nexport function decidePaletteRow(input: PaletteRowInput): PaletteRow {\n const { containerWidth, paletteWidth, menuRight, primaryLeft } = input;\n if (containerWidth <= SHELL.narrow) return \"second-row\";\n const paletteLeft = (containerWidth - paletteWidth) / 2;\n const paletteRight = (containerWidth + paletteWidth) / 2;\n if (paletteLeft < menuRight + PALETTE_CLEARANCE) return \"second-row\";\n if (primaryLeft !== undefined && paletteRight > primaryLeft - PALETTE_CLEARANCE) return \"second-row\";\n return \"top\";\n}\n\n/** The legend starts open unless the container is at most `SHELL.low` high (guide §5.10). */\nexport function legendStartsOpen(containerHeight: number): boolean {\n return containerHeight > SHELL.low;\n}\n\nexport interface TooltipPlacementInput {\n /** The hovered or focused control, relative to the shell root (horizontal centre). */\n anchor: Bounds;\n /** The island the control sits in, relative to the shell root (vertical placement). */\n island: Bounds;\n tooltip: Size;\n container: Size;\n}\n\n/**\n * The tooltip's top-left corner relative to the shell root (guide §5.6): below an island in the\n * upper half (`island.bottom + 8`: top row 60, second row 112), above one in the lower half\n * (`island.top − 8 − height`: bottom row ends at `bottom: 60px`); centred on the anchor, shifted to\n * keep `SHELL.edge` to the container's sides.\n */\nexport function placeTooltip(input: TooltipPlacementInput): Point {\n const { anchor, island, tooltip, container } = input;\n const inUpperHalf = island.y + island.height / 2 < container.height / 2;\n const y = inUpperHalf ? island.y + island.height + SHELL.gap : island.y - SHELL.gap - tooltip.height;\n const centred = anchor.x + anchor.width / 2 - tooltip.width / 2;\n const rightmost = container.width - SHELL.edge - tooltip.width;\n // In a container narrower than the tooltip plus both edges, the left edge wins.\n const x = Math.max(SHELL.edge, Math.min(centred, rightmost));\n return { x, y };\n}\n"],"mappings":"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","names":[]}
|