@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.
Files changed (97) hide show
  1. package/API.md +1051 -84
  2. package/README.md +162 -31
  3. package/dist/appearance-BBaM_4bD.d.ts +323 -0
  4. package/dist/appearance-XX3JC3pI.d.cts +323 -0
  5. package/dist/{chunk-6LQOFACK.js → chunk-66HCSJNY.js} +1247 -31
  6. package/dist/chunk-66HCSJNY.js.map +1 -0
  7. package/dist/{chunk-RJRFR6N3.js → chunk-KYDG5DMX.js} +2 -2
  8. package/dist/{chunk-5JSZ4VGA.js → chunk-OADW3GPA.js} +240 -6
  9. package/dist/chunk-OADW3GPA.js.map +1 -0
  10. package/dist/core.cjs +260 -6
  11. package/dist/core.cjs.map +1 -1
  12. package/dist/core.d.cts +81 -6
  13. package/dist/core.d.ts +81 -6
  14. package/dist/core.js +43 -3
  15. package/dist/diagram-js.cjs +34 -2
  16. package/dist/diagram-js.cjs.map +1 -1
  17. package/dist/diagram-js.d.cts +10 -3
  18. package/dist/diagram-js.d.ts +10 -3
  19. package/dist/diagram-js.js +14 -2
  20. package/dist/diagram-js.js.map +1 -1
  21. package/dist/index.cjs +1654 -197
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +18 -9
  24. package/dist/index.d.ts +18 -9
  25. package/dist/index.js +44 -4
  26. package/dist/shell.css +692 -3
  27. package/dist/testing-vscode-stub.js +2 -2
  28. package/dist/testing.cjs +2849 -550
  29. package/dist/testing.cjs.map +1 -1
  30. package/dist/testing.d.cts +75 -9
  31. package/dist/testing.d.ts +75 -9
  32. package/dist/testing.js +2744 -558
  33. package/dist/testing.js.map +1 -1
  34. package/dist/toasts-Bp0REqdq.d.cts +142 -0
  35. package/dist/toasts-Bp0REqdq.d.ts +142 -0
  36. package/dist/{types-OuFlqZp0.d.cts → types-CnRrINRs.d.cts} +170 -9
  37. package/dist/{types-DnZn9CPw.d.ts → types-PuDZczzy.d.ts} +170 -9
  38. package/dist/vscode-extension.js +2 -2
  39. package/dist/vscode-webview.cjs +1541 -178
  40. package/dist/vscode-webview.cjs.map +1 -1
  41. package/dist/vscode-webview.d.cts +6 -4
  42. package/dist/vscode-webview.d.ts +6 -4
  43. package/dist/vscode-webview.js +11 -2
  44. package/dist/vscode-webview.js.map +1 -1
  45. package/package.json +2 -2
  46. package/src/core/feedback/appearance.ts +141 -0
  47. package/src/core/feedback/problems.ts +23 -0
  48. package/src/core/feedback/toasts.ts +118 -0
  49. package/src/core/fit.ts +18 -3
  50. package/src/core/index.ts +43 -0
  51. package/src/core/keymap.ts +20 -0
  52. package/src/core/messages.ts +71 -0
  53. package/src/core/types.ts +24 -2
  54. package/src/core/web-chrome/bottom-row.ts +37 -0
  55. package/src/core/web-chrome/drop.ts +78 -0
  56. package/src/core/web-chrome/top-row.ts +66 -0
  57. package/src/diagram-js/service.ts +4 -1
  58. package/src/diagram-js/viewport.ts +19 -0
  59. package/src/dom/buttons.css +124 -0
  60. package/src/dom/feedback/appearance.ts +184 -0
  61. package/src/dom/feedback/feedback.css +435 -0
  62. package/src/dom/feedback/problems-popover.ts +287 -0
  63. package/src/dom/feedback/toasts.ts +431 -0
  64. package/src/dom/index.ts +13 -0
  65. package/src/dom/keys.ts +8 -0
  66. package/src/dom/menu.ts +28 -17
  67. package/src/dom/mount.ts +290 -20
  68. package/src/dom/problems.ts +5 -4
  69. package/src/dom/shell-dom.css +7 -4
  70. package/src/dom/types.ts +188 -6
  71. package/src/dom/web/drop-zone.ts +181 -0
  72. package/src/dom/web/empty-state.ts +144 -0
  73. package/src/dom/web/legal.ts +75 -0
  74. package/src/dom/web/menu-groups.ts +69 -0
  75. package/src/dom/web/primary.ts +124 -0
  76. package/src/dom/web/web.css +386 -0
  77. package/src/shell.css +3 -0
  78. package/src/testing/case-tools.ts +164 -0
  79. package/src/testing/cases.ts +209 -144
  80. package/src/testing/checks.ts +51 -15
  81. package/src/testing/facts.ts +56 -2
  82. package/src/testing/geometry.ts +206 -26
  83. package/src/testing/harness.ts +56 -7
  84. package/src/testing/index.ts +4 -0
  85. package/src/testing/page-tools.ts +24 -1
  86. package/src/testing/page.ts +10 -0
  87. package/src/testing/web-cases.ts +913 -0
  88. package/src/testing/web-checks.ts +1049 -0
  89. package/src/testing/web-facts.ts +713 -0
  90. package/src/vscode/webview/mount.ts +13 -1
  91. package/dist/chunk-5JSZ4VGA.js.map +0 -1
  92. package/dist/chunk-6LQOFACK.js.map +0 -1
  93. package/dist/keymap-CObQE6q6.d.cts +0 -151
  94. package/dist/keymap-DORzHnK-.d.ts +0 -151
  95. package/dist/types-B6NfkiUG.d.cts +0 -66
  96. package/dist/types-B6NfkiUG.d.ts +0 -66
  97. /package/dist/{chunk-RJRFR6N3.js.map → chunk-KYDG5DMX.js.map} +0 -0
package/README.md CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  The shared, framework-free chrome around Miragon diagram-js modelers: the menu button with its
4
4
  dropdown, the horizontal palette skin, the chrome tooltip, the zoom panel with its keys and fit, the
5
- problems pill, the status box, the empty-file hint, legend and inspector slots, and the plumbing to
6
- a VS Code webview. One implementation for every modeler and every host (VS Code webview, web app,
5
+ problems pill, the status box, the empty-file hint, legend and inspector slots, the plumbing to a
6
+ VS Code webview, and the web-app chrome (primary action, legal line, File and Appearance groups,
7
+ problems list, toasts, empty-state card, drop zone, the web app's mode). One implementation for every modeler and every host (VS Code webview, web app,
7
8
  embedded in a foreign page), so the chrome looks and behaves the same everywhere.
8
9
 
9
10
  - **Look and behaviour** follow the Miragon design system, guide
@@ -154,7 +155,7 @@ const shell = mountVsCodeShell(document.getElementById("app")!, {
154
155
  menu: {
155
156
  model: [/* Level; Type captions with keepEnabledWhenReadOnly, a view toggle */],
156
157
  export: [/* Export as PNG, SVG */],
157
- help: [/* Keyboard shortcuts… (the modeler's dialog in 0.1) */],
158
+ help: [/* Keyboard shortcuts… (the modeler's own dialog) */],
158
159
  },
159
160
  legend: {
160
161
  render: renderLegend,
@@ -225,31 +226,94 @@ resize, delete, paste or label editor. Content the modeler adds to `shell.root`
225
226
  shortcuts…" dialog) is the modeler's too: it removes it in its own teardown, for example on
226
227
  `diagram.destroy`, since `shell.destroy()` removes only what the shell added. The complete Event
227
228
  Storming example, including the export notification, is in [API.md][api]. Document sync, undo through
228
- the document and diagnostics stay in the modeler for 0.1.
229
+ the document and diagnostics stay in the modeler for now.
229
230
 
230
231
  ## Usage in a web app
231
232
 
233
+ The page's head writes the mode before the first paint. A light-only modeler keeps
234
+ `<html lang="en" data-cd-theme="light">` static and lets the build put the metas and the contrast
235
+ line in place of a marker in `index.html`:
236
+
237
+ ```ts
238
+ // vite.config.ts
239
+ import { webHeadMarkup } from "@miragon/modeler-shell/core";
240
+ import tokens from "./src/theme/cd-tokens.generated.json" with { type: "json" };
241
+
242
+ const themeColors = { light: tokens.fest["--cd-blau"], dark: tokens.dunkel["--cd-ui-grund"] };
243
+ const modePlugin = {
244
+ name: "miragon-mode",
245
+ transformIndexHtml: (html: string) =>
246
+ html.replace("<!-- miragon-mode -->", webHeadMarkup({ themes: ["light"], themeColors })),
247
+ };
248
+ ```
249
+
232
250
  ```ts
233
- import { mountShell } from "@miragon/modeler-shell";
251
+ // src/main.ts
252
+ import {
253
+ commandKeyLabel,
254
+ defaultMessages,
255
+ detectPlatform,
256
+ mountShell,
257
+ problemSummary,
258
+ } from "@miragon/modeler-shell";
234
259
  import { connectDiagramJs, shellModule } from "@miragon/modeler-shell/diagram-js";
235
260
 
236
- // index.html carries <html lang="en" data-cd-theme="light"> statically while the modeler is light-only
261
+ // The diagram first: a modeler keyboard on document must run before the shell's key listener.
237
262
  const modeler = new Modeler({ width: "100%", height: "100%", additionalModules: [shellModule] });
263
+ modeler.get("canvas");
264
+ const platform = detectPlatform(navigator);
238
265
  const shell = mountShell(document.getElementById("app")!, {
239
266
  host: "web",
240
267
  canvas: modeler.container,
241
- menu: { export: [/* … */], help: [/* Keyboard shortcuts… */] },
268
+ lang: "en",
269
+ themes: ["light"], // with "dark": also appearance: { themeColors }, and the menu gets Appearance
270
+ primary: { onSelect: share }, // "Share", top right; the label collapses before the palette moves
271
+ legal: { links: [{ label: "GitHub", href: "https://github.com/Miragon/…" }] },
272
+ file: {
273
+ newDiagram: { onSelect: newDiagram },
274
+ // ⌘O or Ctrl+O; also the start screen's "Open a file…" link
275
+ open: { onSelect: pickFile, shortcut: commandKeyLabel("O", platform, defaultMessages.keyNames) },
276
+ showExample: { onSelect: example },
277
+ },
278
+ menu: { model: [/* … */], export: [/* … */], help: [/* Keyboard shortcuts… */] },
279
+ problems: { onSelectItem: (item) => selectElement(item.elementId) },
242
280
  legend: { render: renderLegend, open: storedLegendOpen(), onToggle: storeLegendOpen },
281
+ emptyState: {
282
+ title: "BPMN Modeler",
283
+ text: "Start a new diagram, or explore the example.",
284
+ komet: kometUrl, // the vendored, unchanged miragon-komet-blau.svg
285
+ onNewDiagram: newDiagram,
286
+ onShowExample: example,
287
+ },
288
+ dropZone: { accept: [".bpmn", "application/xml", "text/xml"], onDrop: ([file]) => openFile(file) },
243
289
  });
244
290
  const { viewport } = connectDiagramJs(shell, modeler, { contentBounds: () => elementBounds() });
245
- await modeler.importXML(xml);
246
- viewport.afterImport();
291
+
292
+ // Every new document opens like the first, and the start screen goes with it.
293
+ async function openDocument(xml: string): Promise<void> {
294
+ viewport.newDocument();
295
+ const { warnings } = await modeler.importXML(xml);
296
+ viewport.afterImport();
297
+ shell.setEmptyState(false);
298
+ shell.setProblems(
299
+ problemSummary(warnings.map((warning) => ({ severity: "warning", message: warning.message }))),
300
+ );
301
+ }
302
+
303
+ // A fresh visit without a document in the link shows the start screen.
304
+ if (!linkedDocument) shell.setEmptyState(true);
305
+ shell.toast({ status: "success", text: "Link copied" }); // after a share
247
306
  ```
248
307
 
249
- The `web` host prevents the browser's page zoom for the chrome keys (outside text fields and
250
- dialogs), lifts pill and legend above the legal line and fits with the web insets. The rest of the
251
- web-app chrome (primary action, legal line, File and Appearance groups, toasts, empty state) is not
252
- part of 0.1.
308
+ The `web` host renders the whole web column of guide §5.2: the primary action at the top right,
309
+ the legal line at the bottom left with pill and legend above it, the File group first and, for a
310
+ dark-capable modeler, the Appearance group in the menu, the problems list above the pill, toasts at
311
+ the bottom centre (announced through two live regions), the start screen and a page-wide drop zone.
312
+ It prevents the browser's page zoom for the chrome keys (outside text fields, modal dialogs and the
313
+ start screen) and fits with the web insets. The web app's
314
+ own menu, zoom, fit, toast, legal and empty-state markup goes away; what stays the modeler's is its
315
+ content: the share link, file reading, examples, the Model and Export entries and the shortcuts
316
+ dialog. See [API.md][api], "Web host (0.2)", and the complete Event Storming example there.
253
317
 
254
318
  ## Usage in a foreign page
255
319
 
@@ -259,8 +323,9 @@ modeler, and the page's `<html>` stays untouched. Until the `@miragon/modeler-ap
259
323
  its `appearance` surface (a later 1.x minor), an embedded modeler has no appearance channel, so the
260
324
  root carries its own `data-cd-theme` (the first of `themes`, light) and stays light inside a dark
261
325
  host page; it takes the page's `data-cd-kontrast="hoch"` over from the nearest mode carrier above
262
- it. The page's element styles
263
- (`button { text-transform: uppercase }`) do not reach the chrome.
326
+ it. The page's element styles (`button { text-transform: uppercase }`) do not reach the chrome.
327
+ Toasts show at the bottom centre of the modeler, and the pill opens the problems list unless the
328
+ modeler passes `problems.onActivate` (an embedding host with a list of its own).
264
329
 
265
330
  ## The conformance kit
266
331
 
@@ -269,7 +334,9 @@ the geometry of every island (±1px), ARIA and the keyboard models, that every c
269
334
  once and never reaches VS Code, that the canvas's own keys (Cmd/Ctrl+A) stay on the canvas, that
270
335
  nothing is ever half-dark (the painted canvas surround and the palette's colours included), that
271
336
  Enter on a creating palette entry adds one selected element, that an invalid file locks the canvas,
272
- and that `destroy()` leaves nothing behind.
337
+ and that `destroy()` leaves nothing behind. Five more cases cover the web column (`webChrome`,
338
+ `emptyState`, `toasts`, `problemsList`, `dropZone`); in the IDE and embedded hosts they assert that
339
+ none of it shows.
273
340
 
274
341
  For the `ide` host the suite serves a test page that puts a stand-in for VS Code around the
275
342
  modeler's webview code: VS Code's default webview styles, `<html>` as `renderWebviewHtml` writes it,
@@ -352,6 +419,60 @@ test.describe("shell conformance", () => {
352
419
  });
353
420
  ```
354
421
 
422
+ A web app's harness opens its page and adds the web column's hooks and the files to drop. The kit
423
+ emulates the system's colour scheme and contrast (`page.emulateMedia`) before every `open`, so the
424
+ page loads without a stored mode choice:
425
+
426
+ ```ts
427
+ // e2e/shell-conformance.spec.ts of a web app
428
+ registerShellConformance(
429
+ {
430
+ host: "web",
431
+ themes: ["light"],
432
+ absent: ["inspector", "invalid"],
433
+ async open(page, { size }) {
434
+ await page.setViewportSize(size);
435
+ await page.goto("/?example");
436
+ await page.waitForFunction(() => window.appReady === true);
437
+ },
438
+ async destroy(page) {
439
+ await page.evaluate(() => window.app.destroy());
440
+ },
441
+ show: {
442
+ async legend(page) {
443
+ /* … */
444
+ },
445
+ async problems(page) {
446
+ /* setProblems with items, so the list has rows */
447
+ },
448
+ async status(page) {
449
+ /* … */
450
+ },
451
+ async emptyState(page) {
452
+ /* the start screen of a fresh visit */
453
+ },
454
+ async toast(page, toast) {
455
+ await page.evaluate(
456
+ ({ status, text, actionLabel }) =>
457
+ window.app.shell.toast({
458
+ status,
459
+ text,
460
+ ...(actionLabel ? { action: { label: actionLabel, onSelect() {} } } : {}),
461
+ }),
462
+ toast,
463
+ );
464
+ },
465
+ },
466
+ drop: {
467
+ accepted: { name: "board.json", type: "application/json", text: EXAMPLE_JSON },
468
+ rejected: { name: "notes.md", type: "", text: "# Notes" },
469
+ },
470
+ },
471
+ test,
472
+ { onSkip: failOnSkips },
473
+ );
474
+ ```
475
+
355
476
  The harness opens the modeler with the appearance the kit requests, the way its host would; for
356
477
  the `embedded` host the kit puts the foreign page itself in that mode. A skip names the harness
357
478
  field it misses (`{ hook: "legend", reason }`; `hook: "destroy"` without a `destroy`; `hook: "open"`
@@ -399,23 +520,32 @@ host requests (`theme`, `kontrast=hoch`; embedded: the foreign page's own mode),
399
520
  inside the stub of `./testing/vscode-stub`, and keeps view and legend state through
400
521
  `acquireVsCodeApi()`.
401
522
 
523
+ The web demo (`/web.html`) is the same modeler as a Miragon web app passes it to `mountShell`: the
524
+ whole web column, the head markup of `webHeadMarkup` through the Vite config, and `window.__webDemo`
525
+ for the harness. Its query parameters: `themes=light,dark`, `empty` (the start screen of a fresh
526
+ visit), `legend` (`legend=open`), `inspector`, `problems` (the pill with its list) and `invalid`.
527
+
402
528
  The e2e suite runs the conformance kit for the `ide`, `web` and `embedded` hosts with a light-only
403
529
  and a dark-capable modeler, switches VS Code themes and the `colorTheme` setting at runtime, checks
404
530
  VS Code's reduce motion, the chrome's typography against VS Code's default styles and a hostile
405
- foreign page, the canvas slot's paint and focus ring, and proves with saboteurs that every case of
406
- the kit fails when its rule is broken.
407
-
408
- | Directory | Module |
409
- | --------------------------- | ------------------------------------------------------------------ |
410
- | `src/core` | DOM-free: fit math, keymap, zoom readout, appearance, sizes, texts |
411
- | `src/dom` | `mountShell`: menu, zoom panel, tooltip, pill, status, slots, keys |
412
- | `src/diagram-js` | `shellModule`, viewport, `connectDiagramJs`, palette skin |
413
- | `src/vscode` | extension side (HTML, theme bridge) and webview side |
414
- | `src/testing` | the Playwright-driven conformance kit and the VS Code stub |
415
- | `src/*.ts`, `src/shell.css` | the entry points |
416
- | `test/` | package boundaries, token drift, the unit tests' helpers |
417
- | `demo/`, `e2e/` | the demo modeler and the e2e suite |
418
- | `fixtures/tokens` | the token files, vendored unchanged for the tests and the demo |
531
+ foreign page, the canvas slot's paint and focus ring, the web chrome's colours, radii and states
532
+ (primary action, legal line, toasts, drop zone, empty state, high contrast and forced colours) and
533
+ the web demo's own handlers, and proves with saboteurs that every case of the kit fails when its
534
+ rule is broken.
535
+
536
+ | Directory | Module |
537
+ | --------------------------------------- | ------------------------------------------------------------------------------------------ |
538
+ | `src/core` | DOM-free: fit math, keymap, zoom readout, appearance, sizes, texts |
539
+ | `src/dom` | `mountShell`: menu, zoom panel, tooltip, pill, status, slots, keys |
540
+ | `src/dom/web`, `src/core/web-chrome` | web column: primary action, legal line, File and Appearance groups, empty state, drop zone |
541
+ | `src/dom/feedback`, `src/core/feedback` | toasts, problems popover, the web app's mode |
542
+ | `src/diagram-js` | `shellModule`, viewport, `connectDiagramJs`, palette skin |
543
+ | `src/vscode` | extension side (HTML, theme bridge) and webview side |
544
+ | `src/testing` | the Playwright-driven conformance kit and the VS Code stub |
545
+ | `src/*.ts`, `src/shell.css` | the entry points |
546
+ | `test/` | package boundaries, token drift, the unit tests' helpers |
547
+ | `demo/`, `e2e/` | the demo modeler and the e2e suite |
548
+ | `fixtures/tokens` | the token files, vendored unchanged for the tests and the demo |
419
549
 
420
550
  A module imports another one only through its `index.ts`; `src/core` imports nothing outside
421
551
  itself and compiles without the DOM library. `test/package-boundaries.test.ts` enforces this, the
@@ -429,7 +559,8 @@ MIT. The inlined Lucide icons are ISC (© Lucide Icons and Contributors), and `c
429
559
  Lucide's LICENSE lists them; see [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). `dist` keeps that
430
560
  notice as a legal comment in the chunk with the icons, but a bundler's `legalComments` setting may
431
561
  drop it: a VSIX or a web bundle that contains the shell carries the Lucide and Feather notices of
432
- THIRD_PARTY_NOTICES.md itself.
562
+ THIRD_PARTY_NOTICES.md itself. Vite 8 (Rolldown with the Oxc minifier) drops legal comments by
563
+ default; `build.rolldownOptions.output.comments.legal: true` keeps the notice in the bundle.
433
564
 
434
565
  [guide]: https://github.com/Miragon/corporate-identity/blob/main/plugins/miragon-brand/skills/modeler-tool-design/assets/modeler-design-system.md
435
566
  [notes]: https://github.com/Miragon/modeler-api/blob/main/docs/shell/implementation-notes.md
@@ -0,0 +1,323 @@
1
+ import { d as ShellHost } from './toasts-Bp0REqdq.js';
2
+ import { T as Theme } from './appearance-D3FUcbu6.js';
3
+
4
+ /** The severity of one problem; the pill and the list show `circle-x` or `triangle-alert` (guide §5.10). */
5
+ type ProblemSeverity = "error" | "warning";
6
+ /** One row of the web app's problems list (guide §5.10). */
7
+ interface ProblemItem {
8
+ severity: ProblemSeverity;
9
+ /** 13px `--cd-ui-text`: "Unknown sticky type “evnt”." */
10
+ message: string;
11
+ /** The place in 13px `--cd-ui-text-leise` below the message: "Line 12", "“Order Placed”". */
12
+ location?: string | undefined;
13
+ /** The diagram element a click on the row selects (`ProblemsOptions.onSelectItem`). */
14
+ elementId?: string | undefined;
15
+ }
16
+ /** `ShellHandle.setProblems` input built from the items: count, errors and the items themselves. */
17
+ declare function problemSummary(items: readonly ProblemItem[]): {
18
+ count: number;
19
+ errors: number;
20
+ items: readonly ProblemItem[];
21
+ };
22
+
23
+ /** Why a drag cannot be dropped (guide §9 Drop forbidden); `messages.dropForbidden` words each one. */
24
+ type DropForbiddenReason = "multiple" | "type" | "dialog";
25
+ /** One entry of `DataTransfer.items` as a drag shows it (names are not readable before the drop). */
26
+ interface DragItemLike {
27
+ kind: string;
28
+ type: string;
29
+ }
30
+ interface DragContext {
31
+ /** `DropZoneOptions.accept`: extensions (".storm") and MIME types ("application/json", "text/*"). */
32
+ accept: readonly string[];
33
+ /** `DropZoneOptions.multiple`. */
34
+ multiple: boolean;
35
+ /** A modal dialog is open (the shell's dialog test, as for the keys). */
36
+ dialogOpen: boolean;
37
+ }
38
+ /** What the drop zone shows for the drag over the page right now (guide §9). */
39
+ type DragVerdict = {
40
+ state: "none";
41
+ } | {
42
+ state: "over";
43
+ } | {
44
+ state: "forbidden";
45
+ reason: DropForbiddenReason;
46
+ };
47
+ /**
48
+ * The drop zone's state for a drag (guide §9, Drag-over and Drop forbidden):
49
+ *
50
+ * - `none`: no item of kind `file` (a palette drag, selected text): the drop zone stays out of it.
51
+ * - `forbidden` / `dialog`: a modal dialog is open.
52
+ * - `forbidden` / `multiple`: more than one file and `multiple` is false.
53
+ * - `forbidden` / `type`: `accept` lists at least one MIME type and every file item carries a
54
+ * non-empty type that none of them matches (`type/*` wildcards included). Extensions cannot be
55
+ * checked before the drop, and an empty type (".storm" has none) never forbids.
56
+ * - `over` otherwise.
57
+ */
58
+ declare function dragVerdict(items: readonly DragItemLike[], context: DragContext): DragVerdict;
59
+ /**
60
+ * A dropped file matches `accept` like `<input accept>`: an entry starting with "." matches the
61
+ * end of the name (case-insensitive), an entry with "/" the MIME type exactly or as `type/*`; an
62
+ * empty `accept` matches every file.
63
+ */
64
+ declare function acceptsFile(file: {
65
+ name: string;
66
+ type: string;
67
+ }, accept: readonly string[]): boolean;
68
+
69
+ /**
70
+ * Every text the shell shows or announces, in one table (guide §2, shared terms). English by
71
+ * default; a localized modeler overrides the whole table in its language, numbers arrive already
72
+ * formatted through `Intl` in that language.
73
+ */
74
+ interface ShellMessages {
75
+ /** Menu trigger `aria-label` and tooltip, dropdown `aria-label`. */
76
+ menu: string;
77
+ /** Palette `aria-label`. */
78
+ tools: string;
79
+ /** `aria-label` of the focusable canvas `<svg>` (`role="application"`); a modeler names its notation. */
80
+ canvas: string;
81
+ /** Zoom panel `aria-label`. */
82
+ zoom: string;
83
+ zoomOut: string;
84
+ zoomIn: string;
85
+ /** Tooltip of the readout, e.g. `(percent) => "Zoom to " + percent` with percent "100%". */
86
+ zoomToActual(percent: string): string;
87
+ /** `aria-label` of the readout: "Zoom to 100% (now 86%)". */
88
+ zoomToActualNow(percent: string, now: string): string;
89
+ fit: string;
90
+ /** Legend title and the Model-group checkbox. */
91
+ legend: string;
92
+ closeLegend: string;
93
+ /** Pill text: "1 problem", "2 problems". */
94
+ problems(count: number, formattedCount: string): string;
95
+ /** Pill `aria-label`: "2 problems, 1 error", "2 problems, no errors". */
96
+ problemsLabel(count: number, errors: number, formattedCount: string, formattedErrors: string): string;
97
+ /** Pill tooltip in the IDE host. */
98
+ showProblems: string;
99
+ /** Empty-file hint (guide §13.5). */
100
+ emptyHint: string;
101
+ insertExample: string;
102
+ /** Status box for an invalid file; `diagram` names the notation ("Event Storming board"). */
103
+ invalidFile(diagram: string): string;
104
+ /** Key names in shortcut chips outside macOS (German: Strg, Umschalt, Plus, Minus). */
105
+ keyNames: {
106
+ ctrl: string;
107
+ shift: string;
108
+ plus: string;
109
+ minus: string;
110
+ };
111
+ /** Default label and `aria-label` of the primary action (web app, top right). */
112
+ share: string;
113
+ /** `aria-label` of the legal line's `<nav>`. "Impressum" and "Datenschutz" are never translated (guide §2). */
114
+ legal: string;
115
+ /** First entry of the legal line, linking the Miragon home page: "© 2026 Miragon GmbH". */
116
+ copyright(year: number): string;
117
+ /** The description of every legal link for screen readers: they open in a new tab. */
118
+ opensInNewTab: string;
119
+ /** File group (web app) and the empty state's primary button. */
120
+ newDiagram: string;
121
+ /** File group: "Open…" (U+2026, it opens the browser's file dialog). */
122
+ openFile: string;
123
+ /** File group and the empty state's ghost button. */
124
+ showExample: string;
125
+ /** File group: "Import…". */
126
+ importFile: string;
127
+ /** Heading and `aria-label` of the Appearance radio group (web app with dark only, guide §13.3). */
128
+ appearance: string;
129
+ appearanceSystem: string;
130
+ appearanceLight: string;
131
+ appearanceDark: string;
132
+ /** Title of the problems popover (web app). */
133
+ problemsTitle: string;
134
+ /** A row's severity for screen readers, the `aria-label` of its icon: "Error", "Warning". */
135
+ problemSeverity(severity: ProblemSeverity): string;
136
+ /** `aria-label` of a Close button (toasts). */
137
+ close: string;
138
+ /** Chip of the drop zone while a drag can be dropped (guide §9 Drag-over). */
139
+ dropFile: string;
140
+ /** Chip of the drop zone while the drag cannot be dropped (guide §9 Drop forbidden). */
141
+ dropForbidden(reason: DropForbiddenReason): string;
142
+ /** The empty state's drag-and-drop hint when the modeler has a drop zone. */
143
+ dropHint: string;
144
+ /** The text link after the empty state's hint that runs the File group's "Open…". */
145
+ openFileLink: string;
146
+ /**
147
+ * Danger toast for a dropped file outside `DropZoneOptions.accept`; `accepted` is the accept list
148
+ * joined by `Intl.ListFormat` (disjunction) in `lang`: ".storm, .json, or .txt".
149
+ */
150
+ cannotOpenFile(name: string, accepted: string): string;
151
+ }
152
+ /** Every key optional; an `undefined` value keeps the default (a conditional override). */
153
+ type ShellMessageOverrides = {
154
+ [Key in Exclude<keyof ShellMessages, "keyNames">]?: ShellMessages[Key] | undefined;
155
+ } & {
156
+ keyNames?: {
157
+ [Key in keyof ShellMessages["keyNames"]]?: string | undefined;
158
+ } | undefined;
159
+ };
160
+ declare const defaultMessages: ShellMessages;
161
+ /** `defaultMessages` with `overrides` applied (`keyNames` merged key by key). */
162
+ declare function resolveMessages(overrides?: ShellMessageOverrides | undefined): ShellMessages;
163
+
164
+ /** The only keys the chrome handles (guide §5.8). */
165
+ type ChromeKey = "zoomIn" | "zoomOut" | "zoomToActual" | "fit";
166
+ /** The `KeyboardEvent` fields the keymap reads. */
167
+ interface KeyLike {
168
+ key: string;
169
+ code: string;
170
+ metaKey: boolean;
171
+ ctrlKey: boolean;
172
+ shiftKey: boolean;
173
+ altKey: boolean;
174
+ isComposing?: boolean | undefined;
175
+ }
176
+ /**
177
+ * Which chrome key `event` is, ignoring where it was pressed:
178
+ * - `zoomIn`: Cmd/Ctrl + `+` or `=` (by `key`, with or without Shift), or `NumpadAdd` (by `code`);
179
+ * - `zoomOut`: Cmd/Ctrl + `-` or `_`, or `NumpadSubtract`;
180
+ * - `zoomToActual`: Cmd/Ctrl + `0`, or `Numpad0` (also with NumLock off);
181
+ * - `fit`: Shift + `Digit1` (by `code`, every layout) without Cmd, Ctrl or Alt.
182
+ * Cmd/Ctrl is `metaKey || ctrlKey` on every platform; nothing matches with `altKey` (AltGr).
183
+ */
184
+ declare function matchChromeKey(event: KeyLike): ChromeKey | undefined;
185
+ /**
186
+ * Cmd/Ctrl + Z, Cmd/Ctrl + Shift + Z or Cmd/Ctrl + Y, without Alt (by `key`, `code` as fallback).
187
+ * Cmd/Ctrl + Shift + Y is not redo (VS Code toggles its Debug Console with it).
188
+ */
189
+ declare function isUndoRedoKey(event: KeyLike): boolean;
190
+ /** A text field: every input, select list and editable text (label editor, inspector, dialogs). */
191
+ declare const TEXT_FIELD_SELECTOR = "input, textarea, select, [contenteditable]:not([contenteditable='false'])";
192
+ /** Where a key was pressed, as the shell's one `keydown` listener sees it. */
193
+ interface KeyContext {
194
+ host: ShellHost;
195
+ /** The event target matches `TEXT_FIELD_SELECTOR` or is `isContentEditable`. */
196
+ inTextField: boolean;
197
+ /** The shell's menu dropdown is open. */
198
+ menuOpen: boolean;
199
+ /** A popover or dialog (`[role="dialog"]`, modal or not) is open. */
200
+ dialogOpen: boolean;
201
+ /** A modal dialog (`[role="dialog"][aria-modal="true"]`) is open: chrome keys do not act. */
202
+ modalOpen: boolean;
203
+ /** `event.defaultPrevented` when the listener ran: the canvas (or the modeler) handled the key. */
204
+ defaultPrevented: boolean;
205
+ /**
206
+ * The web app's start screen shows (`setEmptyState(true)`): there is no diagram to zoom, so the
207
+ * zoom keys and Shift+1 are left to the browser, which zooms the page.
208
+ */
209
+ startScreen?: boolean | undefined;
210
+ }
211
+ type KeyAction = ChromeKey | "closeMenu";
212
+ interface KeyDecision {
213
+ /** What the shell executes; `undefined` = nothing. */
214
+ action?: KeyAction;
215
+ preventDefault: boolean;
216
+ stopPropagation: boolean;
217
+ }
218
+ /**
219
+ * The key rules of guide §5.8 and §13.5 in one place:
220
+ * 1. Escape with the menu open: `closeMenu`, prevented; in the IDE host stopped whenever the menu
221
+ * or a dialog/popover is open. During IME composition Escape belongs to the IME: no `closeMenu`.
222
+ * 2. Zoom keys: executed and prevented unless composing, in a text field or with a modal dialog;
223
+ * in the IDE host always stopped, also when not executed (VS Code must never zoom the window).
224
+ * Behind the web app's start screen untouched: the browser zooms the page (WCAG 1.4.4).
225
+ * 3. Shift+1: `fit` under the same conditions; otherwise untouched (it types "!").
226
+ * 4. IDE host, text field, undo/redo key: stopped only (the field keeps its native undo).
227
+ * 5. IDE host, `defaultPrevented`: stopped (the canvas handled it; VS Code must not run it again).
228
+ * Everything else passes untouched. `stopPropagation` is only ever true in the IDE host.
229
+ */
230
+ declare function decideKey(event: KeyLike, context: KeyContext): KeyDecision;
231
+ /** The keyboard the user sits at; decides the shortcut labels (guide §5.8). */
232
+ type Platform = "mac" | "other";
233
+ /** The `navigator` fields `detectPlatform` reads. */
234
+ interface NavigatorLike {
235
+ platform?: string | undefined;
236
+ userAgentData?: {
237
+ platform?: string | undefined;
238
+ } | undefined;
239
+ }
240
+ /**
241
+ * `mac` when `userAgentData.platform ?? platform` matches `/mac/i`, else `other`; `other` without a
242
+ * navigator. Read it in the webview or page itself, never from the extension host (VS Code Remote).
243
+ */
244
+ declare function detectPlatform(navigator?: NavigatorLike | undefined): Platform;
245
+ /**
246
+ * The tooltip chip text: macOS `⌘+`, `⌘−` (U+2212), `⌘0`, `⇧1`; elsewhere `Ctrl+Plus`,
247
+ * `Ctrl+Minus`, `Ctrl+0`, `Shift+1` with the key names of the messages table.
248
+ */
249
+ declare function shortcutLabel(key: ChromeKey, platform: Platform, keyNames: ShellMessages["keyNames"]): string;
250
+ /**
251
+ * The chip of a Cmd/Ctrl key a modeler binds itself (the web app's File entries, guide §5.4): macOS
252
+ * `⌘O`, elsewhere `Ctrl+O` with the key name of the messages table (`Strg+O`). `key` as printed on
253
+ * the key cap; pass the result to `FileMenuAction.shortcut` or `MenuActionEntry.shortcut`.
254
+ */
255
+ declare function commandKeyLabel(key: string, platform: Platform, keyNames: Pick<ShellMessages["keyNames"], "ctrl">): string;
256
+ /**
257
+ * The `aria-keyshortcuts` value (WAI-ARIA names): zoom in `Meta+= Meta+Plus`, zoom out `Meta+-`,
258
+ * 100 % `Meta+0`, fit `Shift+1`; outside macOS `Control` instead of `Meta`.
259
+ */
260
+ declare function ariaKeyShortcuts(key: ChromeKey, platform: Platform): string;
261
+ /**
262
+ * The `aria-keyshortcuts` value (WAI-ARIA names, guide §5.6) for a shortcut chip label: `"L"` →
263
+ * `"L"`, `"⇧L"` → `"Shift+L"`, `"⌘⇧Z"` → `"Meta+Shift+Z"`, `"Ctrl+Shift+E"` → `"Control+Shift+E"`,
264
+ * `"Del"` → `"Delete"`, `"Ctrl++"` → `"Control+Plus"`. `undefined` for a label that is not one key
265
+ * combination (`"G then D"`, `"1–9"`, empty).
266
+ */
267
+ declare function ariaKeyShortcutsFromLabel(label: string): string | undefined;
268
+
269
+ /** The web app's mode choice (guide §13.3): Appearance, System · Light · Dark. */
270
+ type AppearanceChoice = "system" | "light" | "dark";
271
+ /** Where the web app keeps the choice: `light` or `dark`; "System" removes the entry (guide §13.3). */
272
+ declare const APPEARANCE_STORAGE_KEY = "miragon.darstellung";
273
+ /**
274
+ * The two `theme-color` values (guide §11, §13.3): light is the app's own value (`#335DE5`, `fest`
275
+ * `--cd-blau` of the vendored `cd-tokens.generated.json`), dark `#1D1D1D` (`dunkel`
276
+ * `--cd-ui-grund`). The modeler reads both from its vendored JSON; the shell keeps no colour.
277
+ */
278
+ interface ThemeColors {
279
+ light: string;
280
+ dark: string;
281
+ }
282
+ /** `"light"` and `"dark"` pass, everything else (a missing entry, a foreign value) is `"system"`. */
283
+ declare function parseAppearanceChoice(stored: unknown): AppearanceChoice;
284
+ /** What `<html>` and the meta tags carry for a choice. */
285
+ interface DocumentMode {
286
+ /** `data-cd-theme` on `<html>`. */
287
+ theme: "system" | Theme;
288
+ /** `<meta name="color-scheme" content>`. */
289
+ colorScheme: "light dark" | Theme;
290
+ /** The `content` of the `theme-color` metas with `media="(prefers-color-scheme: light|dark)"`. */
291
+ themeColor: ThemeColors;
292
+ }
293
+ /**
294
+ * The document mode for `choice` (guide §13.3). A modeler that cannot show dark (`themes` without
295
+ * `"dark"`) is `light` whatever the choice: theme `light`, color-scheme `light`, both theme
296
+ * colours `colors.light`. Dark-capable: `system` → theme `system`, color-scheme `light dark`,
297
+ * `colors` as they are; `light`/`dark` → that theme and color-scheme, both theme colours the
298
+ * chosen mode's, so the browser bar matches a fixed choice under the other system mode.
299
+ */
300
+ declare function documentMode(choice: AppearanceChoice, themes: readonly Theme[], colors: ThemeColors): DocumentMode;
301
+ interface WebModeScriptOptions {
302
+ /** The modes the modeler can show, as passed to `mountShell`. */
303
+ themes: readonly Theme[];
304
+ themeColors: ThemeColors;
305
+ }
306
+ /**
307
+ * The body of the inline `<script>` in `<head>`, before the first stylesheet (guide §13.3). With
308
+ * `themes` including `"dark"`: the guide's script, which reads `APPEARANCE_STORAGE_KEY` inside
309
+ * `try`/`catch`, writes `data-cd-theme`, `color-scheme` and both `theme-color` metas for a fixed
310
+ * choice and sets `data-cd-kontrast="hoch"` under `(prefers-contrast: more)`. Light-only: the
311
+ * contrast line alone. Plain ES5 without the modeler's bundle, so it runs before the first paint.
312
+ */
313
+ declare function webModeScript(options: WebModeScriptOptions): string;
314
+ /**
315
+ * The `<head>` markup the web app's `index.html` carries for its mode, as one string (for a Vite
316
+ * `transformIndexHtml` hook or to paste): dark-capable `<meta name="color-scheme" content="light
317
+ * dark">`, two `theme-color` metas with `media` and the script; light-only one `theme-color`
318
+ * without `media` and the contrast script. `<html>` itself carries `lang` and
319
+ * `data-cd-theme="<documentMode("system", themes, colors).theme>"` statically.
320
+ */
321
+ declare function webHeadMarkup(options: WebModeScriptOptions): string;
322
+
323
+ export { APPEARANCE_STORAGE_KEY as A, shortcutLabel as B, type ChromeKey as C, type DocumentMode as D, webHeadMarkup as E, webModeScript as F, type KeyAction as K, type NavigatorLike as N, type Platform as P, type ShellMessageOverrides as S, TEXT_FIELD_SELECTOR as T, type WebModeScriptOptions as W, type AppearanceChoice as a, type DragContext as b, type DragItemLike as c, type DragVerdict as d, type DropForbiddenReason as e, type KeyContext as f, type KeyDecision as g, type KeyLike as h, type ProblemItem as i, type ProblemSeverity as j, type ShellMessages as k, type ThemeColors as l, acceptsFile as m, ariaKeyShortcuts as n, ariaKeyShortcutsFromLabel as o, commandKeyLabel as p, decideKey as q, defaultMessages as r, detectPlatform as s, documentMode as t, dragVerdict as u, isUndoRedoKey as v, matchChromeKey as w, parseAppearanceChoice as x, problemSummary as y, resolveMessages as z };