@miragon/modeler-shell 0.1.0 → 0.2.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 +1051 -84
- package/README.md +162 -31
- package/dist/appearance-BBaM_4bD.d.ts +323 -0
- package/dist/appearance-XX3JC3pI.d.cts +323 -0
- package/dist/{chunk-6LQOFACK.js → chunk-66HCSJNY.js} +1247 -31
- package/dist/chunk-66HCSJNY.js.map +1 -0
- package/dist/{chunk-RJRFR6N3.js → chunk-KYDG5DMX.js} +2 -2
- package/dist/{chunk-5JSZ4VGA.js → chunk-OADW3GPA.js} +240 -6
- package/dist/chunk-OADW3GPA.js.map +1 -0
- package/dist/core.cjs +260 -6
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +81 -6
- package/dist/core.d.ts +81 -6
- package/dist/core.js +43 -3
- package/dist/diagram-js.cjs +34 -2
- package/dist/diagram-js.cjs.map +1 -1
- package/dist/diagram-js.d.cts +10 -3
- package/dist/diagram-js.d.ts +10 -3
- package/dist/diagram-js.js +14 -2
- package/dist/diagram-js.js.map +1 -1
- package/dist/index.cjs +1654 -197
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -9
- package/dist/index.d.ts +18 -9
- package/dist/index.js +44 -4
- package/dist/shell.css +692 -3
- package/dist/testing-vscode-stub.js +2 -2
- package/dist/testing.cjs +2849 -550
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +75 -9
- package/dist/testing.d.ts +75 -9
- package/dist/testing.js +2744 -558
- package/dist/testing.js.map +1 -1
- package/dist/toasts-Bp0REqdq.d.cts +142 -0
- package/dist/toasts-Bp0REqdq.d.ts +142 -0
- package/dist/{types-OuFlqZp0.d.cts → types-CnRrINRs.d.cts} +170 -9
- package/dist/{types-DnZn9CPw.d.ts → types-PuDZczzy.d.ts} +170 -9
- package/dist/vscode-extension.js +2 -2
- package/dist/vscode-webview.cjs +1541 -178
- package/dist/vscode-webview.cjs.map +1 -1
- package/dist/vscode-webview.d.cts +6 -4
- package/dist/vscode-webview.d.ts +6 -4
- package/dist/vscode-webview.js +11 -2
- package/dist/vscode-webview.js.map +1 -1
- package/package.json +2 -2
- package/src/core/feedback/appearance.ts +141 -0
- package/src/core/feedback/problems.ts +23 -0
- package/src/core/feedback/toasts.ts +118 -0
- package/src/core/fit.ts +18 -3
- package/src/core/index.ts +43 -0
- package/src/core/keymap.ts +20 -0
- package/src/core/messages.ts +71 -0
- package/src/core/types.ts +24 -2
- package/src/core/web-chrome/bottom-row.ts +37 -0
- package/src/core/web-chrome/drop.ts +78 -0
- package/src/core/web-chrome/top-row.ts +66 -0
- package/src/diagram-js/service.ts +4 -1
- package/src/diagram-js/viewport.ts +19 -0
- package/src/dom/buttons.css +124 -0
- package/src/dom/feedback/appearance.ts +184 -0
- package/src/dom/feedback/feedback.css +435 -0
- package/src/dom/feedback/problems-popover.ts +287 -0
- package/src/dom/feedback/toasts.ts +431 -0
- package/src/dom/index.ts +13 -0
- package/src/dom/keys.ts +8 -0
- package/src/dom/menu.ts +28 -17
- package/src/dom/mount.ts +290 -20
- package/src/dom/problems.ts +5 -4
- package/src/dom/shell-dom.css +7 -4
- package/src/dom/types.ts +188 -6
- package/src/dom/web/drop-zone.ts +181 -0
- package/src/dom/web/empty-state.ts +144 -0
- package/src/dom/web/legal.ts +75 -0
- package/src/dom/web/menu-groups.ts +69 -0
- package/src/dom/web/primary.ts +124 -0
- package/src/dom/web/web.css +386 -0
- package/src/shell.css +3 -0
- package/src/testing/case-tools.ts +164 -0
- package/src/testing/cases.ts +209 -144
- package/src/testing/checks.ts +51 -15
- package/src/testing/facts.ts +56 -2
- package/src/testing/geometry.ts +206 -26
- package/src/testing/harness.ts +56 -7
- package/src/testing/index.ts +4 -0
- package/src/testing/page-tools.ts +24 -1
- package/src/testing/page.ts +10 -0
- package/src/testing/web-cases.ts +913 -0
- package/src/testing/web-checks.ts +1049 -0
- package/src/testing/web-facts.ts +713 -0
- package/src/vscode/webview/mount.ts +13 -1
- package/dist/chunk-5JSZ4VGA.js.map +0 -1
- package/dist/chunk-6LQOFACK.js.map +0 -1
- package/dist/keymap-CObQE6q6.d.cts +0 -151
- package/dist/keymap-DORzHnK-.d.ts +0 -151
- package/dist/types-B6NfkiUG.d.cts +0 -66
- package/dist/types-B6NfkiUG.d.ts +0 -66
- /package/dist/{chunk-RJRFR6N3.js.map → chunk-KYDG5DMX.js.map} +0 -0
|
@@ -7,7 +7,12 @@ var CHROME_ISLANDS = [
|
|
|
7
7
|
"problems",
|
|
8
8
|
"legend",
|
|
9
9
|
"inspector",
|
|
10
|
-
"status"
|
|
10
|
+
"status",
|
|
11
|
+
"legal",
|
|
12
|
+
"toasts",
|
|
13
|
+
"empty-state",
|
|
14
|
+
"drop-zone",
|
|
15
|
+
"problems-popover"
|
|
11
16
|
];
|
|
12
17
|
|
|
13
18
|
// src/core/sizes.ts
|
|
@@ -78,6 +83,19 @@ function shellSizesFromTokens(shellGroup) {
|
|
|
78
83
|
return sizes;
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
// src/core/web-chrome/bottom-row.ts
|
|
87
|
+
var LEGAL_LINE_HEIGHT = 20;
|
|
88
|
+
function legalLineHeight(measured) {
|
|
89
|
+
if (measured === void 0 || !Number.isFinite(measured) || measured <= 0) return LEGAL_LINE_HEIGHT;
|
|
90
|
+
return Math.max(1, Math.round(measured / LEGAL_LINE_HEIGHT)) * LEGAL_LINE_HEIGHT;
|
|
91
|
+
}
|
|
92
|
+
function zoomRowRaised(input) {
|
|
93
|
+
const { containerWidth, legalRight, zoomWidth } = input;
|
|
94
|
+
if (legalRight === void 0 || zoomWidth === void 0) return false;
|
|
95
|
+
const zoomLeft = containerWidth - SHELL.edge - zoomWidth;
|
|
96
|
+
return legalRight + SHELL.gap > zoomLeft;
|
|
97
|
+
}
|
|
98
|
+
|
|
81
99
|
// src/core/fit.ts
|
|
82
100
|
var MAX_FIT_ZOOM = 1;
|
|
83
101
|
var FIT_ANIMATION_MS = 250;
|
|
@@ -118,11 +136,13 @@ function freeRectCenter(container, insets) {
|
|
|
118
136
|
};
|
|
119
137
|
}
|
|
120
138
|
function fitInsets(layout) {
|
|
121
|
-
const { container, host, paletteRow, problemsVisible, legendWidth, inspector } = layout;
|
|
139
|
+
const { container, host, paletteRow, problemsVisible, zoomRaised, legendWidth, inspector } = layout;
|
|
140
|
+
const raisedBottomRow = host === "web" && (problemsVisible || zoomRaised === true);
|
|
141
|
+
const wrappedLegalLines = legalLineHeight(layout.legalHeight) - LEGAL_LINE_HEIGHT;
|
|
122
142
|
const insets = {
|
|
123
143
|
top: paletteRow === "second-row" ? SHELL.fitInsetTopSecondRow : SHELL.fitInsetTop,
|
|
124
144
|
right: SHELL.fitInsetSide,
|
|
125
|
-
bottom:
|
|
145
|
+
bottom: raisedBottomRow ? SHELL.fitInsetBottomWeb + wrappedLegalLines : SHELL.fitInsetBottom,
|
|
126
146
|
left: SHELL.fitInsetSide
|
|
127
147
|
};
|
|
128
148
|
const bottomSheetOpen = inspector !== void 0 && container.width <= SHELL.narrow;
|
|
@@ -276,6 +296,7 @@ function decideKey(event, context) {
|
|
|
276
296
|
}
|
|
277
297
|
const chromeKey = matchChromeKey(event);
|
|
278
298
|
if (chromeKey !== void 0) {
|
|
299
|
+
if (context.startScreen === true && !stop) return { preventDefault: false, stopPropagation: false };
|
|
279
300
|
const blocked = composing || context.inTextField || context.modalOpen;
|
|
280
301
|
if (!blocked) return { action: chromeKey, preventDefault: true, stopPropagation: stop };
|
|
281
302
|
return { preventDefault: false, stopPropagation: chromeKey === "fit" ? false : stop };
|
|
@@ -301,6 +322,9 @@ function shortcutLabel(key, platform, keyNames) {
|
|
|
301
322
|
return `${keyNames.shift}+1`;
|
|
302
323
|
}
|
|
303
324
|
}
|
|
325
|
+
function commandKeyLabel(key, platform, keyNames) {
|
|
326
|
+
return platform === "mac" ? `\u2318${key}` : `${keyNames.ctrl}+${key}`;
|
|
327
|
+
}
|
|
304
328
|
var MAC_SHORTCUT_LABELS = {
|
|
305
329
|
zoomIn: "\u2318+",
|
|
306
330
|
zoomOut: "\u2318\u2212",
|
|
@@ -463,7 +487,27 @@ var defaultMessages = {
|
|
|
463
487
|
emptyHint: "Pick a tool from the palette to start.",
|
|
464
488
|
insertExample: "Insert example",
|
|
465
489
|
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" }
|
|
490
|
+
keyNames: { ctrl: "Ctrl", shift: "Shift", plus: "Plus", minus: "Minus" },
|
|
491
|
+
share: "Share",
|
|
492
|
+
legal: "Legal",
|
|
493
|
+
copyright: (year) => `\xA9 ${year} Miragon GmbH`,
|
|
494
|
+
opensInNewTab: "Opens in a new tab",
|
|
495
|
+
newDiagram: "New diagram",
|
|
496
|
+
openFile: "Open\u2026",
|
|
497
|
+
showExample: "Show example",
|
|
498
|
+
importFile: "Import\u2026",
|
|
499
|
+
appearance: "Appearance",
|
|
500
|
+
appearanceSystem: "System",
|
|
501
|
+
appearanceLight: "Light",
|
|
502
|
+
appearanceDark: "Dark",
|
|
503
|
+
problemsTitle: "Problems",
|
|
504
|
+
problemSeverity: (severity) => severity === "error" ? "Error" : "Warning",
|
|
505
|
+
close: "Close",
|
|
506
|
+
dropFile: "Drop file here",
|
|
507
|
+
dropForbidden: (reason) => reason === "multiple" ? "Drop one file at a time" : reason === "dialog" ? "Close the dialog to drop a file" : "This modeler can't open this file type",
|
|
508
|
+
dropHint: "Or drop a file here",
|
|
509
|
+
openFileLink: "Open a file\u2026",
|
|
510
|
+
cannotOpenFile: (name, accepted) => `Couldn't open \u201C${name}\u201D. Drop a ${accepted} file.`
|
|
467
511
|
};
|
|
468
512
|
function resolveMessages(overrides) {
|
|
469
513
|
const { keyNames, ...texts } = overrides ?? {};
|
|
@@ -503,11 +547,184 @@ function placeTooltip(input) {
|
|
|
503
547
|
return { x, y };
|
|
504
548
|
}
|
|
505
549
|
|
|
550
|
+
// src/core/web-chrome/top-row.ts
|
|
551
|
+
function decideTopRow(input) {
|
|
552
|
+
const { containerWidth, paletteWidth, menuRight, primary } = input;
|
|
553
|
+
if (!primary) {
|
|
554
|
+
return {
|
|
555
|
+
paletteRow: decidePaletteRow({ containerWidth, paletteWidth, menuRight }),
|
|
556
|
+
primaryLabel: "shown"
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
const paletteRowBeside = (primaryWidth) => decidePaletteRow({
|
|
560
|
+
containerWidth,
|
|
561
|
+
paletteWidth,
|
|
562
|
+
menuRight,
|
|
563
|
+
primaryLeft: containerWidth - SHELL.edge - primaryWidth
|
|
564
|
+
});
|
|
565
|
+
if (paletteRowBeside(primary.withLabel) === "top") return { paletteRow: "top", primaryLabel: "shown" };
|
|
566
|
+
if (paletteRowBeside(primary.iconOnly) === "top") return { paletteRow: "top", primaryLabel: "collapsed" };
|
|
567
|
+
const labelledLeft = containerWidth - SHELL.edge - primary.withLabel;
|
|
568
|
+
return {
|
|
569
|
+
paletteRow: "second-row",
|
|
570
|
+
primaryLabel: menuRight + PALETTE_CLEARANCE <= labelledLeft ? "shown" : "collapsed"
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// src/core/web-chrome/drop.ts
|
|
575
|
+
function acceptEntries(accept) {
|
|
576
|
+
const entries = accept.map((entry) => entry.trim().toLowerCase()).filter((entry) => entry !== "");
|
|
577
|
+
return {
|
|
578
|
+
extensions: entries.filter((entry) => entry.startsWith(".")),
|
|
579
|
+
mimeTypes: entries.filter((entry) => !entry.startsWith(".") && entry.includes("/"))
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
function matchesMimeType(type, mimeTypes) {
|
|
583
|
+
const normalized = type.trim().toLowerCase();
|
|
584
|
+
if (normalized === "") return false;
|
|
585
|
+
return mimeTypes.some(
|
|
586
|
+
(entry) => entry.endsWith("/*") ? normalized.startsWith(entry.slice(0, -1)) : normalized === entry
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
function dragVerdict(items, context) {
|
|
590
|
+
const files = items.filter((item) => item.kind === "file");
|
|
591
|
+
if (files.length === 0) return { state: "none" };
|
|
592
|
+
if (context.dialogOpen) return { state: "forbidden", reason: "dialog" };
|
|
593
|
+
if (files.length > 1 && !context.multiple) return { state: "forbidden", reason: "multiple" };
|
|
594
|
+
const { mimeTypes } = acceptEntries(context.accept);
|
|
595
|
+
const foreignType = (item) => item.type.trim() !== "" && !matchesMimeType(item.type, mimeTypes);
|
|
596
|
+
if (mimeTypes.length > 0 && files.every(foreignType)) return { state: "forbidden", reason: "type" };
|
|
597
|
+
return { state: "over" };
|
|
598
|
+
}
|
|
599
|
+
function acceptsFile(file, accept) {
|
|
600
|
+
const { extensions, mimeTypes } = acceptEntries(accept);
|
|
601
|
+
if (extensions.length === 0 && mimeTypes.length === 0) return true;
|
|
602
|
+
const name = file.name.toLowerCase();
|
|
603
|
+
return extensions.some((extension) => name.endsWith(extension)) || matchesMimeType(file.type, mimeTypes);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// src/core/feedback/toasts.ts
|
|
607
|
+
var MAX_TOASTS = 3;
|
|
608
|
+
var TOAST_DURATION_MS = {
|
|
609
|
+
success: 2800,
|
|
610
|
+
info: 2800,
|
|
611
|
+
warning: 5e3,
|
|
612
|
+
danger: 5e3
|
|
613
|
+
};
|
|
614
|
+
var TOAST_MAX_WIDTH = 480;
|
|
615
|
+
function toastLifetime(request) {
|
|
616
|
+
if (request.action) return void 0;
|
|
617
|
+
const requested = request.durationMs;
|
|
618
|
+
const longer = requested !== void 0 && Number.isFinite(requested) ? requested : 0;
|
|
619
|
+
return Math.max(TOAST_DURATION_MS[request.status], longer);
|
|
620
|
+
}
|
|
621
|
+
function toastToEvict(stack) {
|
|
622
|
+
if (stack.length < MAX_TOASTS) return void 0;
|
|
623
|
+
const evicted = stack.find((slot) => !slot.engaged && !slot.hasAction) ?? stack.find((slot) => !slot.engaged) ?? stack[0];
|
|
624
|
+
return evicted?.id;
|
|
625
|
+
}
|
|
626
|
+
function toastStackWidth(containerWidth) {
|
|
627
|
+
return Math.max(0, Math.min(TOAST_MAX_WIDTH, containerWidth - 2 * SHELL.edge));
|
|
628
|
+
}
|
|
629
|
+
function toastStackBottom(input) {
|
|
630
|
+
const stackWidth = toastStackWidth(input.containerWidth);
|
|
631
|
+
const { cardWidth } = input;
|
|
632
|
+
const width = cardWidth !== void 0 && Number.isFinite(cardWidth) ? Math.min(stackWidth, Math.max(0, cardWidth)) : stackWidth;
|
|
633
|
+
const left = (input.containerWidth - width) / 2;
|
|
634
|
+
const right = left + width;
|
|
635
|
+
let highest;
|
|
636
|
+
for (const obstacle of input.obstacles) {
|
|
637
|
+
const distance = Math.max(obstacle.left - right, left - obstacle.right);
|
|
638
|
+
if (distance >= SHELL.gap) continue;
|
|
639
|
+
highest = highest === void 0 ? obstacle.top : Math.max(highest, obstacle.top);
|
|
640
|
+
}
|
|
641
|
+
return highest === void 0 ? SHELL.edge : Math.max(SHELL.edge, highest + SHELL.gap);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// src/core/feedback/appearance.ts
|
|
645
|
+
var APPEARANCE_STORAGE_KEY = "miragon.darstellung";
|
|
646
|
+
function parseAppearanceChoice(stored) {
|
|
647
|
+
return stored === "light" || stored === "dark" ? stored : "system";
|
|
648
|
+
}
|
|
649
|
+
function documentMode(choice, themes, colors) {
|
|
650
|
+
if (!themes.includes("dark")) return fixedMode("light", colors.light);
|
|
651
|
+
const parsed = parseAppearanceChoice(choice);
|
|
652
|
+
if (parsed === "system") {
|
|
653
|
+
return {
|
|
654
|
+
theme: "system",
|
|
655
|
+
colorScheme: "light dark",
|
|
656
|
+
themeColor: { light: colors.light, dark: colors.dark }
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
return fixedMode(parsed, colors[parsed]);
|
|
660
|
+
}
|
|
661
|
+
function fixedMode(theme, color) {
|
|
662
|
+
return { theme, colorScheme: theme, themeColor: { light: color, dark: color } };
|
|
663
|
+
}
|
|
664
|
+
function webModeScript(options) {
|
|
665
|
+
const contrast = [
|
|
666
|
+
`if (window.matchMedia && window.matchMedia(${scriptString("(prefers-contrast: more)")}).matches)`,
|
|
667
|
+
` document.documentElement.setAttribute("data-cd-kontrast", "hoch");`
|
|
668
|
+
];
|
|
669
|
+
if (!options.themes.includes("dark")) return contrast.join("\n");
|
|
670
|
+
const { light, dark } = options.themeColors;
|
|
671
|
+
return [
|
|
672
|
+
"(function () {",
|
|
673
|
+
" var html = document.documentElement;",
|
|
674
|
+
" var choice = null;",
|
|
675
|
+
" try {",
|
|
676
|
+
` choice = window.localStorage.getItem(${scriptString(APPEARANCE_STORAGE_KEY)});`,
|
|
677
|
+
" } catch (error) {}",
|
|
678
|
+
` if (choice === "light" || choice === "dark") {`,
|
|
679
|
+
` var color = choice === "dark" ? ${scriptString(dark)} : ${scriptString(light)};`,
|
|
680
|
+
` html.setAttribute("data-cd-theme", choice);`,
|
|
681
|
+
` var scheme = document.querySelector("meta[name=color-scheme]");`,
|
|
682
|
+
` if (scheme) scheme.setAttribute("content", choice);`,
|
|
683
|
+
` var metas = document.querySelectorAll("meta[name=theme-color]");`,
|
|
684
|
+
` for (var index = 0; index < metas.length; index++) metas[index].setAttribute("content", color);`,
|
|
685
|
+
" }",
|
|
686
|
+
...contrast.map((line) => ` ${line}`),
|
|
687
|
+
"})();"
|
|
688
|
+
].join("\n");
|
|
689
|
+
}
|
|
690
|
+
function scriptString(value) {
|
|
691
|
+
return JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
692
|
+
}
|
|
693
|
+
function webHeadMarkup(options) {
|
|
694
|
+
const mode = documentMode("system", options.themes, options.themeColors);
|
|
695
|
+
const script = `<script>
|
|
696
|
+
${webModeScript(options)}
|
|
697
|
+
</script>`;
|
|
698
|
+
if (mode.theme !== "system") {
|
|
699
|
+
return [`<meta name="theme-color" content="${escapeAttribute(mode.themeColor.light)}">`, script].join(
|
|
700
|
+
"\n"
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
return [
|
|
704
|
+
`<meta name="color-scheme" content="${escapeAttribute(mode.colorScheme)}">`,
|
|
705
|
+
`<meta name="theme-color" content="${escapeAttribute(mode.themeColor.light)}" media="(prefers-color-scheme: light)">`,
|
|
706
|
+
`<meta name="theme-color" content="${escapeAttribute(mode.themeColor.dark)}" media="(prefers-color-scheme: dark)">`,
|
|
707
|
+
script
|
|
708
|
+
].join("\n");
|
|
709
|
+
}
|
|
710
|
+
function escapeAttribute(value) {
|
|
711
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// src/core/feedback/problems.ts
|
|
715
|
+
function problemSummary(items) {
|
|
716
|
+
const errors = items.filter((item) => item.severity === "error").length;
|
|
717
|
+
return { count: items.length, errors, items };
|
|
718
|
+
}
|
|
719
|
+
|
|
506
720
|
export {
|
|
507
721
|
CHROME_ISLANDS,
|
|
508
722
|
SHELL_TOKENS,
|
|
509
723
|
SHELL,
|
|
510
724
|
shellSizesFromTokens,
|
|
725
|
+
LEGAL_LINE_HEIGHT,
|
|
726
|
+
legalLineHeight,
|
|
727
|
+
zoomRowRaised,
|
|
511
728
|
MAX_FIT_ZOOM,
|
|
512
729
|
FIT_ANIMATION_MS,
|
|
513
730
|
fitViewbox,
|
|
@@ -530,6 +747,7 @@ export {
|
|
|
530
747
|
decideKey,
|
|
531
748
|
detectPlatform,
|
|
532
749
|
shortcutLabel,
|
|
750
|
+
commandKeyLabel,
|
|
533
751
|
ariaKeyShortcuts,
|
|
534
752
|
ariaKeyShortcutsFromLabel,
|
|
535
753
|
resolveAppearance,
|
|
@@ -546,6 +764,22 @@ export {
|
|
|
546
764
|
INVALID_STATUS_DELAY_MS,
|
|
547
765
|
decidePaletteRow,
|
|
548
766
|
legendStartsOpen,
|
|
549
|
-
placeTooltip
|
|
767
|
+
placeTooltip,
|
|
768
|
+
decideTopRow,
|
|
769
|
+
dragVerdict,
|
|
770
|
+
acceptsFile,
|
|
771
|
+
MAX_TOASTS,
|
|
772
|
+
TOAST_DURATION_MS,
|
|
773
|
+
TOAST_MAX_WIDTH,
|
|
774
|
+
toastLifetime,
|
|
775
|
+
toastToEvict,
|
|
776
|
+
toastStackWidth,
|
|
777
|
+
toastStackBottom,
|
|
778
|
+
APPEARANCE_STORAGE_KEY,
|
|
779
|
+
parseAppearanceChoice,
|
|
780
|
+
documentMode,
|
|
781
|
+
webModeScript,
|
|
782
|
+
webHeadMarkup,
|
|
783
|
+
problemSummary
|
|
550
784
|
};
|
|
551
|
-
//# sourceMappingURL=chunk-
|
|
785
|
+
//# sourceMappingURL=chunk-OADW3GPA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/types.ts","../src/core/sizes.ts","../src/core/web-chrome/bottom-row.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","../src/core/web-chrome/top-row.ts","../src/core/web-chrome/drop.ts","../src/core/feedback/toasts.ts","../src/core/feedback/appearance.ts","../src/core/feedback/problems.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/**\n * The `data-chrome` names of the chrome (guide §5.2, §13.4). The web column adds `primary`, `legal`,\n * `toasts`, `empty-state`, `drop-zone` and `problems-popover` (0.2); IDE and embedded hosts never\n * show `primary`, `legal`, `empty-state` and `drop-zone`, the IDE host never `toasts` and\n * `problems-popover`.\n */\nexport type ChromeIsland =\n | \"menu\"\n | \"palette\"\n | \"primary\"\n | \"zoom\"\n | \"problems\"\n | \"legend\"\n | \"inspector\"\n | \"status\"\n | \"legal\"\n | \"toasts\"\n | \"empty-state\"\n | \"drop-zone\"\n | \"problems-popover\";\n\nexport const CHROME_ISLANDS: readonly ChromeIsland[] = [\n \"menu\",\n \"palette\",\n \"primary\",\n \"zoom\",\n \"problems\",\n \"legend\",\n \"inspector\",\n \"status\",\n \"legal\",\n \"toasts\",\n \"empty-state\",\n \"drop-zone\",\n \"problems-popover\",\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\";\n\n/**\n * Height of one line of the web app's legal line: 13px text on a 20px line (guide §5.10). Where a\n * narrow container cannot hold it in one line, it wraps (40 for two lines) and everything stacked\n * above it rises by as much: the pill, the legend, the raised zoom panel and the fit's bottom.\n */\nexport const LEGAL_LINE_HEIGHT = 20;\n\n/** A measured legal line's height in whole lines, at least one: what the stack above it stands on. */\nexport function legalLineHeight(measured: number | undefined): number {\n if (measured === undefined || !Number.isFinite(measured) || measured <= 0) return LEGAL_LINE_HEIGHT;\n return Math.max(1, Math.round(measured / LEGAL_LINE_HEIGHT)) * LEGAL_LINE_HEIGHT;\n}\n\nexport interface BottomRowInput {\n containerWidth: number;\n /** Measured right edge of the legal line relative to the shell root; absent while it is not shown. */\n legalRight?: number | undefined;\n /** Measured width of the zoom panel (173 with a 56px readout); absent while it is not shown. */\n zoomWidth?: number | undefined;\n}\n\n/**\n * Web app, bottom row: true when the zoom panel at `right: 12px` would keep less than\n * `SHELL.gap` (8px) to the legal line at `left: 12px`. The zoom panel then stands above the legal\n * line at `bottom: 40px` (12 + 20 + 8; a wrapped line lifts it further), like the pill\n * (`data-zoom-row=\"raised\"` on the root), and the fit keeps 92px at the bottom (`FitLayout.zoomRaised`). Guide §5.10 and §5.15 do not settle\n * this collision of a narrow container; it is the shell's rule until they do.\n * False when either of the two is not shown.\n */\nexport function zoomRowRaised(input: BottomRowInput): boolean {\n const { containerWidth, legalRight, zoomWidth } = input;\n if (legalRight === undefined || zoomWidth === undefined) return false;\n const zoomLeft = containerWidth - SHELL.edge - zoomWidth;\n return legalRight + SHELL.gap > zoomLeft;\n}\n","import { SHELL } from \"./sizes.js\";\nimport { LEGAL_LINE_HEIGHT, legalLineHeight } from \"./web-chrome/bottom-row.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 /**\n * Web app: the zoom panel stands above the legal line (`bottom: 40px`) because the two would\n * meet in the bottom row of a narrow container (`zoomRowRaised`); the bottom then keeps 92px as\n * with the pill.\n */\n zoomRaised?: boolean | undefined;\n /**\n * Web app: the measured height of the legal line (`LEGAL_LINE_HEIGHT`, more where a narrow\n * container wraps it); the raised bottom row stands on it. Default one line.\n */\n legalHeight?: number | undefined;\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 or\n * with the zoom panel raised above the legal line 92, plus 20 per extra line of a wrapped legal line),\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, zoomRaised, legendWidth, inspector } = layout;\n const raisedBottomRow = host === \"web\" && (problemsVisible || zoomRaised === true);\n const wrappedLegalLines = legalLineHeight(layout.legalHeight) - LEGAL_LINE_HEIGHT;\n const insets: Insets = {\n top: paletteRow === \"second-row\" ? SHELL.fitInsetTopSecondRow : SHELL.fitInsetTop,\n right: SHELL.fitInsetSide,\n bottom: raisedBottomRow ? SHELL.fitInsetBottomWeb + wrappedLegalLines : 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 * The web app's start screen shows (`setEmptyState(true)`): there is no diagram to zoom, so the\n * zoom keys and Shift+1 are left to the browser, which zooms the page.\n */\n startScreen?: boolean | undefined;\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 * Behind the web app's start screen untouched: the browser zooms the page (WCAG 1.4.4).\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 if (context.startScreen === true && !stop) return { preventDefault: false, stopPropagation: false };\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\n/**\n * The chip of a Cmd/Ctrl key a modeler binds itself (the web app's File entries, guide §5.4): macOS\n * `⌘O`, elsewhere `Ctrl+O` with the key name of the messages table (`Strg+O`). `key` as printed on\n * the key cap; pass the result to `FileMenuAction.shortcut` or `MenuActionEntry.shortcut`.\n */\nexport function commandKeyLabel(\n key: string,\n platform: Platform,\n keyNames: Pick<ShellMessages[\"keyNames\"], \"ctrl\">,\n): string {\n return platform === \"mac\" ? `⌘${key}` : `${keyNames.ctrl}+${key}`;\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","import type { ProblemSeverity } from \"./feedback/problems.js\";\nimport type { DropForbiddenReason } from \"./web-chrome/drop.js\";\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 // ---- Web host (0.2): guide §2 shared terms, §5.4, §5.7, §5.10, §8, §9 ----\n\n /** Default label and `aria-label` of the primary action (web app, top right). */\n share: string;\n /** `aria-label` of the legal line's `<nav>`. \"Impressum\" and \"Datenschutz\" are never translated (guide §2). */\n legal: string;\n /** First entry of the legal line, linking the Miragon home page: \"© 2026 Miragon GmbH\". */\n copyright(year: number): string;\n /** The description of every legal link for screen readers: they open in a new tab. */\n opensInNewTab: string;\n /** File group (web app) and the empty state's primary button. */\n newDiagram: string;\n /** File group: \"Open…\" (U+2026, it opens the browser's file dialog). */\n openFile: string;\n /** File group and the empty state's ghost button. */\n showExample: string;\n /** File group: \"Import…\". */\n importFile: string;\n /** Heading and `aria-label` of the Appearance radio group (web app with dark only, guide §13.3). */\n appearance: string;\n appearanceSystem: string;\n appearanceLight: string;\n appearanceDark: string;\n /** Title of the problems popover (web app). */\n problemsTitle: string;\n /** A row's severity for screen readers, the `aria-label` of its icon: \"Error\", \"Warning\". */\n problemSeverity(severity: ProblemSeverity): string;\n /** `aria-label` of a Close button (toasts). */\n close: string;\n /** Chip of the drop zone while a drag can be dropped (guide §9 Drag-over). */\n dropFile: string;\n /** Chip of the drop zone while the drag cannot be dropped (guide §9 Drop forbidden). */\n dropForbidden(reason: DropForbiddenReason): string;\n /** The empty state's drag-and-drop hint when the modeler has a drop zone. */\n dropHint: string;\n /** The text link after the empty state's hint that runs the File group's \"Open…\". */\n openFileLink: string;\n /**\n * Danger toast for a dropped file outside `DropZoneOptions.accept`; `accepted` is the accept list\n * joined by `Intl.ListFormat` (disjunction) in `lang`: \".storm, .json, or .txt\".\n */\n cannotOpenFile(name: string, accepted: string): 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 share: \"Share\",\n legal: \"Legal\",\n copyright: (year) => `© ${year} Miragon GmbH`,\n opensInNewTab: \"Opens in a new tab\",\n newDiagram: \"New diagram\",\n openFile: \"Open…\",\n showExample: \"Show example\",\n importFile: \"Import…\",\n appearance: \"Appearance\",\n appearanceSystem: \"System\",\n appearanceLight: \"Light\",\n appearanceDark: \"Dark\",\n problemsTitle: \"Problems\",\n problemSeverity: (severity) => (severity === \"error\" ? \"Error\" : \"Warning\"),\n close: \"Close\",\n dropFile: \"Drop file here\",\n dropForbidden: (reason) =>\n reason === \"multiple\"\n ? \"Drop one file at a time\"\n : reason === \"dialog\"\n ? \"Close the dialog to drop a file\"\n : \"This modeler can't open this file type\",\n dropHint: \"Or drop a file here\",\n openFileLink: \"Open a file…\",\n cannotOpenFile: (name, accepted) => `Couldn't open “${name}”. Drop a ${accepted} file.`,\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","import { PALETTE_CLEARANCE, decidePaletteRow } from \"../layout.js\";\nimport { SHELL } from \"../sizes.js\";\nimport type { PaletteRow } from \"../types.js\";\n\n/** Border-box widths of the web app's primary action, measured with and without its label. */\nexport interface PrimaryWidths {\n /** Icon and label: padding `0 16px`, icon 16px, gap 8px, label 13px/600 (guide §5.7). */\n withLabel: number;\n /** The icon alone: `SHELL.row` (40) square. */\n iconOnly: number;\n}\n\nexport interface TopRowInput {\n containerWidth: number;\n /** The palette's intrinsic border-box width, as for `decidePaletteRow`. */\n paletteWidth: number;\n /** Right edge of the menu trigger, relative to the shell root. */\n menuRight: number;\n /** The primary action at `right: 12px`; absent in IDE and embedded hosts and without one. */\n primary?: PrimaryWidths | undefined;\n}\n\nexport interface TopRow {\n paletteRow: PaletteRow;\n /** `collapsed`: the primary action shows its icon only; its `aria-label` keeps the name. */\n primaryLabel: \"shown\" | \"collapsed\";\n}\n\n/**\n * The top row of guide §5.15 with a primary action: when it gets tight, the primary action loses\n * its label first, and only then the palette moves to the second row.\n *\n * 1. Without `primary`: `decidePaletteRow` alone, label `shown`.\n * 2. With the label (`primaryLeft = containerWidth − SHELL.edge − withLabel`): palette `top` →\n * `{ top, shown }`.\n * 3. Icon only (`primaryLeft = containerWidth − SHELL.edge − iconOnly`): palette `top` →\n * `{ top, collapsed }`.\n * 4. Otherwise the palette is in the second row and the top row holds only menu trigger and\n * primary action: the label shows again when `menuRight + PALETTE_CLEARANCE` stays left of the\n * labelled button, else it stays collapsed.\n *\n * Up to `SHELL.narrow` the palette is always in the second row (step 4).\n */\nexport function decideTopRow(input: TopRowInput): TopRow {\n const { containerWidth, paletteWidth, menuRight, primary } = input;\n if (!primary) {\n return {\n paletteRow: decidePaletteRow({ containerWidth, paletteWidth, menuRight }),\n primaryLabel: \"shown\",\n };\n }\n const paletteRowBeside = (primaryWidth: number): PaletteRow =>\n decidePaletteRow({\n containerWidth,\n paletteWidth,\n menuRight,\n primaryLeft: containerWidth - SHELL.edge - primaryWidth,\n });\n if (paletteRowBeside(primary.withLabel) === \"top\") return { paletteRow: \"top\", primaryLabel: \"shown\" };\n if (paletteRowBeside(primary.iconOnly) === \"top\") return { paletteRow: \"top\", primaryLabel: \"collapsed\" };\n const labelledLeft = containerWidth - SHELL.edge - primary.withLabel;\n return {\n paletteRow: \"second-row\",\n primaryLabel: menuRight + PALETTE_CLEARANCE <= labelledLeft ? \"shown\" : \"collapsed\",\n };\n}\n","/** Why a drag cannot be dropped (guide §9 Drop forbidden); `messages.dropForbidden` words each one. */\nexport type DropForbiddenReason = \"multiple\" | \"type\" | \"dialog\";\n\n/** One entry of `DataTransfer.items` as a drag shows it (names are not readable before the drop). */\nexport interface DragItemLike {\n kind: string;\n type: string;\n}\n\nexport interface DragContext {\n /** `DropZoneOptions.accept`: extensions (\".storm\") and MIME types (\"application/json\", \"text/*\"). */\n accept: readonly string[];\n /** `DropZoneOptions.multiple`. */\n multiple: boolean;\n /** A modal dialog is open (the shell's dialog test, as for the keys). */\n dialogOpen: boolean;\n}\n\n/** What the drop zone shows for the drag over the page right now (guide §9). */\nexport type DragVerdict =\n { state: \"none\" } | { state: \"over\" } | { state: \"forbidden\"; reason: DropForbiddenReason };\n\ninterface AcceptEntries {\n extensions: readonly string[];\n mimeTypes: readonly string[];\n}\n\n/** `<input accept>` tokens: trimmed, lower case, blanks dropped; \".\" starts an extension, \"/\" marks a MIME type. */\nfunction acceptEntries(accept: readonly string[]): AcceptEntries {\n const entries = accept.map((entry) => entry.trim().toLowerCase()).filter((entry) => entry !== \"\");\n return {\n extensions: entries.filter((entry) => entry.startsWith(\".\")),\n mimeTypes: entries.filter((entry) => !entry.startsWith(\".\") && entry.includes(\"/\")),\n };\n}\n\nfunction matchesMimeType(type: string, mimeTypes: readonly string[]): boolean {\n const normalized = type.trim().toLowerCase();\n if (normalized === \"\") return false;\n return mimeTypes.some((entry) =>\n entry.endsWith(\"/*\") ? normalized.startsWith(entry.slice(0, -1)) : normalized === entry,\n );\n}\n\n/**\n * The drop zone's state for a drag (guide §9, Drag-over and Drop forbidden):\n *\n * - `none`: no item of kind `file` (a palette drag, selected text): the drop zone stays out of it.\n * - `forbidden` / `dialog`: a modal dialog is open.\n * - `forbidden` / `multiple`: more than one file and `multiple` is false.\n * - `forbidden` / `type`: `accept` lists at least one MIME type and every file item carries a\n * non-empty type that none of them matches (`type/*` wildcards included). Extensions cannot be\n * checked before the drop, and an empty type (\".storm\" has none) never forbids.\n * - `over` otherwise.\n */\nexport function dragVerdict(items: readonly DragItemLike[], context: DragContext): DragVerdict {\n const files = items.filter((item) => item.kind === \"file\");\n if (files.length === 0) return { state: \"none\" };\n if (context.dialogOpen) return { state: \"forbidden\", reason: \"dialog\" };\n if (files.length > 1 && !context.multiple) return { state: \"forbidden\", reason: \"multiple\" };\n const { mimeTypes } = acceptEntries(context.accept);\n const foreignType = (item: DragItemLike) =>\n item.type.trim() !== \"\" && !matchesMimeType(item.type, mimeTypes);\n if (mimeTypes.length > 0 && files.every(foreignType)) return { state: \"forbidden\", reason: \"type\" };\n return { state: \"over\" };\n}\n\n/**\n * A dropped file matches `accept` like `<input accept>`: an entry starting with \".\" matches the\n * end of the name (case-insensitive), an entry with \"/\" the MIME type exactly or as `type/*`; an\n * empty `accept` matches every file.\n */\nexport function acceptsFile(file: { name: string; type: string }, accept: readonly string[]): boolean {\n const { extensions, mimeTypes } = acceptEntries(accept);\n if (extensions.length === 0 && mimeTypes.length === 0) return true;\n const name = file.name.toLowerCase();\n return extensions.some((extension) => name.endsWith(extension)) || matchesMimeType(file.type, mimeTypes);\n}\n","import { SHELL } from \"../sizes.js\";\n\n/** The statuses of guide §3.3 and §9; each colours bar and dot through `--cd-ui-<status>`. */\nexport type ToastStatus = \"success\" | \"info\" | \"warning\" | \"danger\";\n\n/** A ghost button before Close (guide §8); the toast then never closes by itself. */\nexport interface ToastAction {\n label: string;\n /** Runs after the toast closed. */\n onSelect(): void;\n}\n\nexport interface ToastRequest {\n status: ToastStatus;\n /** What happened and what you can do (guide §2): \"Link copied\", \"Couldn't read this file. …\". */\n text: string;\n action?: ToastAction | undefined;\n /** Only to give a long text more time than `TOAST_DURATION_MS`; ignored with an `action`. */\n durationMs?: number | undefined;\n}\n\n/** At most three toasts at once, the newest at the bottom (guide §8). */\nexport const MAX_TOASTS = 3;\n\n/**\n * How long a toast stays without hover or focus (guide §8: about 2.8 s, errors about 5 s). A\n * warning asks you to read a sentence and act on it like an error, so it gets the error's 5 s.\n */\nexport const TOAST_DURATION_MS: Readonly<Record<ToastStatus, number>> = {\n success: 2800,\n info: 2800,\n warning: 5000,\n danger: 5000,\n};\n\n/** The stack's widest toast: `min(480px, 100cqw − 24px)` (guide §8). */\nexport const TOAST_MAX_WIDTH = 480;\n\n/**\n * Milliseconds until the toast closes by itself: `undefined` with an `action` (it never closes by\n * itself, guide §8), else `max(TOAST_DURATION_MS[status], durationMs ?? 0)`.\n */\nexport function toastLifetime(request: ToastRequest): number | undefined {\n if (request.action) return undefined;\n const requested = request.durationMs;\n // A NaN or an infinite duration would reach setTimeout, which fires at once for both.\n const longer = requested !== undefined && Number.isFinite(requested) ? requested : 0;\n return Math.max(TOAST_DURATION_MS[request.status], longer);\n}\n\n/** A toast in the stack as the eviction sees it, oldest first. */\nexport interface ToastSlot {\n id: number;\n hasAction: boolean;\n /** Hovered or holding the focus. */\n engaged: boolean;\n}\n\n/**\n * Which toast leaves when a fourth arrives (`MAX_TOASTS`): the oldest that is neither engaged nor\n * has an action, else the oldest that is not engaged, else the oldest. `undefined` while the\n * stack has room.\n */\nexport function toastToEvict(stack: readonly ToastSlot[]): number | undefined {\n if (stack.length < MAX_TOASTS) return undefined;\n const evicted =\n stack.find((slot) => !slot.engaged && !slot.hasAction) ?? stack.find((slot) => !slot.engaged) ?? stack[0];\n return evicted?.id;\n}\n\n/** Something in the bottom row the toast stack must clear, relative to the shell root. */\nexport interface BottomObstacle {\n left: number;\n right: number;\n /** Distance of its top edge from the container's bottom edge. */\n top: number;\n}\n\nexport interface ToastStackInput {\n containerWidth: number;\n /** The visible zoom panel, legal line and problems pill (not the legend card). */\n obstacles: readonly BottomObstacle[];\n /**\n * Measured width of the widest toast in the stack. The cards are sized to their text and centred\n * in the stack, so only this column can touch an obstacle; absent = the whole `toastStackWidth`.\n */\n cardWidth?: number | undefined;\n}\n\n/** `min(TOAST_MAX_WIDTH, containerWidth − 2 · SHELL.edge)`. */\nexport function toastStackWidth(containerWidth: number): number {\n return Math.max(0, Math.min(TOAST_MAX_WIDTH, containerWidth - 2 * SHELL.edge));\n}\n\n/**\n * The stack's `bottom` in px. Guide §8 anchors it at `bottom: 12px`, centred; where the column of\n * its widest card (`cardWidth`, at most `toastStackWidth`, centred) comes closer than `SHELL.gap`\n * horizontally to an obstacle, the stack stands `SHELL.gap` above the highest such obstacle instead\n * (above the legal line 40, above the zoom panel 60, above a raised zoom panel or the web pill 88).\n * Guide §8 does not settle this collision of a narrow container; it is the shell's rule until it does.\n */\nexport function toastStackBottom(input: ToastStackInput): number {\n const stackWidth = toastStackWidth(input.containerWidth);\n const { cardWidth } = input;\n const width =\n cardWidth !== undefined && Number.isFinite(cardWidth)\n ? Math.min(stackWidth, Math.max(0, cardWidth))\n : stackWidth;\n const left = (input.containerWidth - width) / 2;\n const right = left + width;\n let highest: number | undefined;\n for (const obstacle of input.obstacles) {\n const distance = Math.max(obstacle.left - right, left - obstacle.right);\n if (distance >= SHELL.gap) continue;\n highest = highest === undefined ? obstacle.top : Math.max(highest, obstacle.top);\n }\n return highest === undefined ? SHELL.edge : Math.max(SHELL.edge, highest + SHELL.gap);\n}\n","import type { Theme } from \"../appearance.js\";\n\n/** The web app's mode choice (guide §13.3): Appearance, System · Light · Dark. */\nexport type AppearanceChoice = \"system\" | \"light\" | \"dark\";\n\n/** Where the web app keeps the choice: `light` or `dark`; \"System\" removes the entry (guide §13.3). */\nexport const APPEARANCE_STORAGE_KEY = \"miragon.darstellung\";\n\n/**\n * The two `theme-color` values (guide §11, §13.3): light is the app's own value (`#335DE5`, `fest`\n * `--cd-blau` of the vendored `cd-tokens.generated.json`), dark `#1D1D1D` (`dunkel`\n * `--cd-ui-grund`). The modeler reads both from its vendored JSON; the shell keeps no colour.\n */\nexport interface ThemeColors {\n light: string;\n dark: string;\n}\n\n/** `\"light\"` and `\"dark\"` pass, everything else (a missing entry, a foreign value) is `\"system\"`. */\nexport function parseAppearanceChoice(stored: unknown): AppearanceChoice {\n return stored === \"light\" || stored === \"dark\" ? stored : \"system\";\n}\n\n/** What `<html>` and the meta tags carry for a choice. */\nexport interface DocumentMode {\n /** `data-cd-theme` on `<html>`. */\n theme: \"system\" | Theme;\n /** `<meta name=\"color-scheme\" content>`. */\n colorScheme: \"light dark\" | Theme;\n /** The `content` of the `theme-color` metas with `media=\"(prefers-color-scheme: light|dark)\"`. */\n themeColor: ThemeColors;\n}\n\n/**\n * The document mode for `choice` (guide §13.3). A modeler that cannot show dark (`themes` without\n * `\"dark\"`) is `light` whatever the choice: theme `light`, color-scheme `light`, both theme\n * colours `colors.light`. Dark-capable: `system` → theme `system`, color-scheme `light dark`,\n * `colors` as they are; `light`/`dark` → that theme and color-scheme, both theme colours the\n * chosen mode's, so the browser bar matches a fixed choice under the other system mode.\n */\nexport function documentMode(\n choice: AppearanceChoice,\n themes: readonly Theme[],\n colors: ThemeColors,\n): DocumentMode {\n if (!themes.includes(\"dark\")) return fixedMode(\"light\", colors.light);\n const parsed = parseAppearanceChoice(choice);\n if (parsed === \"system\") {\n return {\n theme: \"system\",\n colorScheme: \"light dark\",\n themeColor: { light: colors.light, dark: colors.dark },\n };\n }\n return fixedMode(parsed, colors[parsed]);\n}\n\nfunction fixedMode(theme: Theme, color: string): DocumentMode {\n return { theme, colorScheme: theme, themeColor: { light: color, dark: color } };\n}\n\nexport interface WebModeScriptOptions {\n /** The modes the modeler can show, as passed to `mountShell`. */\n themes: readonly Theme[];\n themeColors: ThemeColors;\n}\n\n/**\n * The body of the inline `<script>` in `<head>`, before the first stylesheet (guide §13.3). With\n * `themes` including `\"dark\"`: the guide's script, which reads `APPEARANCE_STORAGE_KEY` inside\n * `try`/`catch`, writes `data-cd-theme`, `color-scheme` and both `theme-color` metas for a fixed\n * choice and sets `data-cd-kontrast=\"hoch\"` under `(prefers-contrast: more)`. Light-only: the\n * contrast line alone. Plain ES5 without the modeler's bundle, so it runs before the first paint.\n */\nexport function webModeScript(options: WebModeScriptOptions): string {\n const contrast = [\n `if (window.matchMedia && window.matchMedia(${scriptString(\"(prefers-contrast: more)\")}).matches)`,\n ` document.documentElement.setAttribute(\"data-cd-kontrast\", \"hoch\");`,\n ];\n if (!options.themes.includes(\"dark\")) return contrast.join(\"\\n\");\n const { light, dark } = options.themeColors;\n return [\n \"(function () {\",\n \" var html = document.documentElement;\",\n \" var choice = null;\",\n \" try {\",\n ` choice = window.localStorage.getItem(${scriptString(APPEARANCE_STORAGE_KEY)});`,\n \" } catch (error) {}\",\n ` if (choice === \"light\" || choice === \"dark\") {`,\n ` var color = choice === \"dark\" ? ${scriptString(dark)} : ${scriptString(light)};`,\n ` html.setAttribute(\"data-cd-theme\", choice);`,\n ` var scheme = document.querySelector(\"meta[name=color-scheme]\");`,\n ` if (scheme) scheme.setAttribute(\"content\", choice);`,\n ` var metas = document.querySelectorAll(\"meta[name=theme-color]\");`,\n ` for (var index = 0; index < metas.length; index++) metas[index].setAttribute(\"content\", color);`,\n \" }\",\n ...contrast.map((line) => ` ${line}`),\n \"})();\",\n ].join(\"\\n\");\n}\n\n// The script sits inline in <head>: \"<\" never appears in a literal, so a value cannot end the\n// element (\"</script>\"), and U+2028/U+2029 are escaped because ES5 forbids them in string literals.\nfunction scriptString(value: string): string {\n return JSON.stringify(value)\n .replace(/</g, \"\\\\u003c\")\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n}\n\n/**\n * The `<head>` markup the web app's `index.html` carries for its mode, as one string (for a Vite\n * `transformIndexHtml` hook or to paste): dark-capable `<meta name=\"color-scheme\" content=\"light\n * dark\">`, two `theme-color` metas with `media` and the script; light-only one `theme-color`\n * without `media` and the contrast script. `<html>` itself carries `lang` and\n * `data-cd-theme=\"<documentMode(\"system\", themes, colors).theme>\"` statically.\n */\nexport function webHeadMarkup(options: WebModeScriptOptions): string {\n const mode = documentMode(\"system\", options.themes, options.themeColors);\n const script = `<script>\\n${webModeScript(options)}\\n</script>`;\n if (mode.theme !== \"system\") {\n return [`<meta name=\"theme-color\" content=\"${escapeAttribute(mode.themeColor.light)}\">`, script].join(\n \"\\n\",\n );\n }\n return [\n `<meta name=\"color-scheme\" content=\"${escapeAttribute(mode.colorScheme)}\">`,\n `<meta name=\"theme-color\" content=\"${escapeAttribute(mode.themeColor.light)}\" media=\"(prefers-color-scheme: light)\">`,\n `<meta name=\"theme-color\" content=\"${escapeAttribute(mode.themeColor.dark)}\" media=\"(prefers-color-scheme: dark)\">`,\n script,\n ].join(\"\\n\");\n}\n\nfunction escapeAttribute(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n","/** The severity of one problem; the pill and the list show `circle-x` or `triangle-alert` (guide §5.10). */\nexport type ProblemSeverity = \"error\" | \"warning\";\n\n/** One row of the web app's problems list (guide §5.10). */\nexport interface ProblemItem {\n severity: ProblemSeverity;\n /** 13px `--cd-ui-text`: \"Unknown sticky type “evnt”.\" */\n message: string;\n /** The place in 13px `--cd-ui-text-leise` below the message: \"Line 12\", \"“Order Placed”\". */\n location?: string | undefined;\n /** The diagram element a click on the row selects (`ProblemsOptions.onSelectItem`). */\n elementId?: string | undefined;\n}\n\n/** `ShellHandle.setProblems` input built from the items: count, errors and the items themselves. */\nexport function problemSummary(items: readonly ProblemItem[]): {\n count: number;\n errors: number;\n items: readonly ProblemItem[];\n} {\n const errors = items.filter((item) => item.severity === \"error\").length;\n return { count: items.length, errors, items };\n}\n"],"mappings":"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","names":[]}
|