@miragon/modeler-shell 0.0.0-stage → 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 (147) hide show
  1. package/API.md +2869 -0
  2. package/LICENSE +21 -0
  3. package/README.md +566 -2
  4. package/THIRD_PARTY_NOTICES.md +52 -0
  5. package/dist/appearance-BBaM_4bD.d.ts +323 -0
  6. package/dist/appearance-D3FUcbu6.d.cts +53 -0
  7. package/dist/appearance-D3FUcbu6.d.ts +53 -0
  8. package/dist/appearance-XX3JC3pI.d.cts +323 -0
  9. package/dist/chunk-66HCSJNY.js +2792 -0
  10. package/dist/chunk-66HCSJNY.js.map +1 -0
  11. package/dist/chunk-KYDG5DMX.js +136 -0
  12. package/dist/chunk-KYDG5DMX.js.map +1 -0
  13. package/dist/chunk-N4QXFF3A.js +23 -0
  14. package/dist/chunk-N4QXFF3A.js.map +1 -0
  15. package/dist/chunk-OADW3GPA.js +785 -0
  16. package/dist/chunk-OADW3GPA.js.map +1 -0
  17. package/dist/core.cjs +873 -0
  18. package/dist/core.cjs.map +1 -0
  19. package/dist/core.d.cts +246 -0
  20. package/dist/core.d.ts +246 -0
  21. package/dist/core.js +131 -0
  22. package/dist/core.js.map +1 -0
  23. package/dist/diagram-js.cjs +1147 -0
  24. package/dist/diagram-js.cjs.map +1 -0
  25. package/dist/diagram-js.d.cts +170 -0
  26. package/dist/diagram-js.d.ts +170 -0
  27. package/dist/diagram-js.js +879 -0
  28. package/dist/diagram-js.js.map +1 -0
  29. package/dist/index.cjs +3632 -0
  30. package/dist/index.cjs.map +1 -0
  31. package/dist/index.d.cts +29 -0
  32. package/dist/index.d.ts +29 -0
  33. package/dist/index.js +143 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/protocol-DhYBakWL.d.cts +15 -0
  36. package/dist/protocol-IeW8bf4f.d.ts +15 -0
  37. package/dist/shell.css +1603 -0
  38. package/dist/testing-vscode-stub.cjs +332 -0
  39. package/dist/testing-vscode-stub.cjs.map +1 -0
  40. package/dist/testing-vscode-stub.d.cts +65 -0
  41. package/dist/testing-vscode-stub.d.ts +65 -0
  42. package/dist/testing-vscode-stub.js +154 -0
  43. package/dist/testing-vscode-stub.js.map +1 -0
  44. package/dist/testing.cjs +4663 -0
  45. package/dist/testing.cjs.map +1 -0
  46. package/dist/testing.d.cts +231 -0
  47. package/dist/testing.d.ts +231 -0
  48. package/dist/testing.js +4483 -0
  49. package/dist/testing.js.map +1 -0
  50. package/dist/toasts-Bp0REqdq.d.cts +142 -0
  51. package/dist/toasts-Bp0REqdq.d.ts +142 -0
  52. package/dist/types-CnRrINRs.d.cts +445 -0
  53. package/dist/types-PuDZczzy.d.ts +445 -0
  54. package/dist/vscode-extension.cjs +215 -0
  55. package/dist/vscode-extension.cjs.map +1 -0
  56. package/dist/vscode-extension.d.cts +103 -0
  57. package/dist/vscode-extension.d.ts +103 -0
  58. package/dist/vscode-extension.js +24 -0
  59. package/dist/vscode-extension.js.map +1 -0
  60. package/dist/vscode-webview.cjs +3561 -0
  61. package/dist/vscode-webview.cjs.map +1 -0
  62. package/dist/vscode-webview.d.cts +84 -0
  63. package/dist/vscode-webview.d.ts +84 -0
  64. package/dist/vscode-webview.js +259 -0
  65. package/dist/vscode-webview.js.map +1 -0
  66. package/package.json +145 -4
  67. package/src/core/appearance.ts +104 -0
  68. package/src/core/feedback/appearance.ts +141 -0
  69. package/src/core/feedback/problems.ts +23 -0
  70. package/src/core/feedback/toasts.ts +118 -0
  71. package/src/core/fit.ts +220 -0
  72. package/src/core/index.ts +127 -0
  73. package/src/core/keymap.ts +295 -0
  74. package/src/core/layout.ts +69 -0
  75. package/src/core/messages.ts +159 -0
  76. package/src/core/sizes.ts +85 -0
  77. package/src/core/types.ts +106 -0
  78. package/src/core/web-chrome/bottom-row.ts +37 -0
  79. package/src/core/web-chrome/drop.ts +78 -0
  80. package/src/core/web-chrome/top-row.ts +66 -0
  81. package/src/core/zoom.ts +74 -0
  82. package/src/core.ts +2 -0
  83. package/src/diagram-js/connect.ts +75 -0
  84. package/src/diagram-js/index.ts +16 -0
  85. package/src/diagram-js/module.ts +20 -0
  86. package/src/diagram-js/palette-skin.ts +267 -0
  87. package/src/diagram-js/palette.css +269 -0
  88. package/src/diagram-js/service.ts +243 -0
  89. package/src/diagram-js/types.ts +67 -0
  90. package/src/diagram-js/viewport.ts +583 -0
  91. package/src/diagram-js.ts +2 -0
  92. package/src/dom/buttons.css +124 -0
  93. package/src/dom/element.ts +51 -0
  94. package/src/dom/feedback/appearance.ts +184 -0
  95. package/src/dom/feedback/feedback.css +435 -0
  96. package/src/dom/feedback/problems-popover.ts +287 -0
  97. package/src/dom/feedback/toasts.ts +431 -0
  98. package/src/dom/hint.ts +51 -0
  99. package/src/dom/icons.ts +309 -0
  100. package/src/dom/index.ts +42 -0
  101. package/src/dom/inspector.ts +35 -0
  102. package/src/dom/keys.ts +67 -0
  103. package/src/dom/ledger.ts +87 -0
  104. package/src/dom/legend.ts +85 -0
  105. package/src/dom/menu.ts +404 -0
  106. package/src/dom/mount.ts +731 -0
  107. package/src/dom/problems.ts +73 -0
  108. package/src/dom/shell-dom.css +969 -0
  109. package/src/dom/status.ts +72 -0
  110. package/src/dom/tooltip.ts +237 -0
  111. package/src/dom/types.ts +459 -0
  112. package/src/dom/web/drop-zone.ts +181 -0
  113. package/src/dom/web/empty-state.ts +144 -0
  114. package/src/dom/web/legal.ts +75 -0
  115. package/src/dom/web/menu-groups.ts +69 -0
  116. package/src/dom/web/primary.ts +124 -0
  117. package/src/dom/web/web.css +386 -0
  118. package/src/dom/zoom-panel.ts +154 -0
  119. package/src/index.ts +3 -0
  120. package/src/shell.css +9 -0
  121. package/src/testing/case-tools.ts +164 -0
  122. package/src/testing/cases.ts +1077 -0
  123. package/src/testing/checks.ts +868 -0
  124. package/src/testing/facts.ts +641 -0
  125. package/src/testing/geometry.ts +652 -0
  126. package/src/testing/harness.ts +187 -0
  127. package/src/testing/index.ts +19 -0
  128. package/src/testing/page-tools.ts +457 -0
  129. package/src/testing/page.ts +27 -0
  130. package/src/testing/vscode-stub.ts +237 -0
  131. package/src/testing/web-cases.ts +913 -0
  132. package/src/testing/web-checks.ts +1049 -0
  133. package/src/testing/web-facts.ts +713 -0
  134. package/src/testing-vscode-stub.ts +2 -0
  135. package/src/testing.ts +2 -0
  136. package/src/vscode/extension/html.ts +107 -0
  137. package/src/vscode/extension/index.ts +8 -0
  138. package/src/vscode/extension/reveal.ts +9 -0
  139. package/src/vscode/extension/theme-bridge.ts +85 -0
  140. package/src/vscode/extension/types.ts +36 -0
  141. package/src/vscode/protocol.ts +31 -0
  142. package/src/vscode/webview/appearance.ts +141 -0
  143. package/src/vscode/webview/index.ts +8 -0
  144. package/src/vscode/webview/mount.ts +88 -0
  145. package/src/vscode/webview/state.ts +116 -0
  146. package/src/vscode-extension.ts +2 -0
  147. package/src/vscode-webview.ts +2 -0
@@ -0,0 +1,51 @@
1
+ type Child = Node | string | undefined | null | false;
2
+
3
+ /** `document.createElement` with attributes and children; `undefined` attribute values are skipped. */
4
+ export function createElement<K extends keyof HTMLElementTagNameMap>(
5
+ ownerDocument: Document,
6
+ tag: K,
7
+ attributes: Readonly<Record<string, string | undefined>> = {},
8
+ ...children: Child[]
9
+ ): HTMLElementTagNameMap[K] {
10
+ const element = ownerDocument.createElement(tag);
11
+ for (const [name, value] of Object.entries(attributes)) {
12
+ if (value !== undefined) element.setAttribute(name, value);
13
+ }
14
+ for (const child of children) {
15
+ if (child === undefined || child === null || child === false) continue;
16
+ element.append(child);
17
+ }
18
+ return element;
19
+ }
20
+
21
+ /** Sets an attribute, or removes it for `null`/`false`. */
22
+ export function toggleAttribute(element: Element, name: string, value: string | null | false): void {
23
+ if (value === null || value === false) element.removeAttribute(name);
24
+ else element.setAttribute(name, value);
25
+ }
26
+
27
+ /** The element's box relative to the padding box of `root` (the containing block of the islands). */
28
+ export function boxRelativeTo(element: Element, root: HTMLElement) {
29
+ const box = element.getBoundingClientRect();
30
+ const origin = root.getBoundingClientRect();
31
+ return {
32
+ x: box.left - origin.left - root.clientLeft,
33
+ y: box.top - origin.top - root.clientTop,
34
+ width: box.width,
35
+ height: box.height,
36
+ };
37
+ }
38
+
39
+ /** The accessible name a control shows without its tooltip: `aria-label`, else its text. */
40
+ export function accessibleName(control: Element): string {
41
+ return (control.getAttribute("aria-label") ?? control.textContent ?? "").trim();
42
+ }
43
+
44
+ /** True for keyboard focus (`:focus-visible`); engines without the selector count every focus. */
45
+ export function hasVisibleFocus(element: Element): boolean {
46
+ try {
47
+ return element.matches(":focus-visible");
48
+ } catch {
49
+ return true;
50
+ }
51
+ }
@@ -0,0 +1,184 @@
1
+ import {
2
+ APPEARANCE_STORAGE_KEY,
3
+ documentMode,
4
+ parseAppearanceChoice,
5
+ type AppearanceChoice,
6
+ type Theme,
7
+ } from "../../core/index.js";
8
+ import { createElement, toggleAttribute } from "../element.js";
9
+ import type { AppearanceHandle, StorageLike, WebAppearanceOptions } from "../types.js";
10
+
11
+ export interface WebAppearanceContext {
12
+ ownerDocument: Document;
13
+ themes: readonly Theme[];
14
+ /** Required when `themes` includes `"dark"`; read only then. */
15
+ options: WebAppearanceOptions | undefined;
16
+ }
17
+
18
+ export interface WebAppearanceController extends AppearanceHandle {
19
+ /** Stops following `prefers-contrast` and other tabs' `storage` events; leaves `<html>` as it is. */
20
+ destroy(): void;
21
+ }
22
+
23
+ /** On `<html>` for one frame of a mode switch (guide §7); shell-dom.css turns every transition off. */
24
+ export const NO_TRANSITION_CLASS = "cd-shell-no-transition";
25
+
26
+ const CONTRAST_QUERY = "(prefers-contrast: more)";
27
+ const SCHEME_MEDIA: Readonly<Record<Theme, string>> = {
28
+ light: "(prefers-color-scheme: light)",
29
+ dark: "(prefers-color-scheme: dark)",
30
+ };
31
+
32
+ /** `change` on a media query list; Safari before 14 only has `addListener`. */
33
+ function onMediaChange(query: MediaQueryList, listener: () => void): () => void {
34
+ if (typeof query.addEventListener === "function") {
35
+ query.addEventListener("change", listener);
36
+ return () => query.removeEventListener("change", listener);
37
+ }
38
+ query.addListener(listener);
39
+ return () => query.removeListener(listener);
40
+ }
41
+
42
+ /**
43
+ * The web host's mode at runtime (guide §13.3), after the head script (`webModeScript`) set it
44
+ * before the first paint. Always: mirrors `(prefers-contrast: more)` as `data-cd-kontrast="hoch"`
45
+ * on `<html>`, at once and on every change. With `"dark"` in `themes` (throws without `options`):
46
+ * `choice()` is the stored choice, read once through `parseAppearanceChoice` (a blocked storage
47
+ * reads `"system"`) and then kept by `setChoice` and other tabs' `storage` events, so a choice the
48
+ * storage refused still holds for the page. `setChoice` writes it (`"system"` removes the key; a
49
+ * throwing storage is ignored) and applies `documentMode` on `<html data-cd-theme>`,
50
+ * `<meta name="color-scheme">` and both `theme-color` metas (created where missing) in one task,
51
+ * with `cd-shell-no-transition` on `<html>` until a nested `requestAnimationFrame`; a `storage`
52
+ * event for the key applies the same way. Light-only: `choice()` is `"system"`, `setChoice` does
53
+ * nothing.
54
+ */
55
+ export function connectWebAppearance(context: WebAppearanceContext): WebAppearanceController {
56
+ const { ownerDocument, themes, options } = context;
57
+ const darkCapable = themes.includes("dark");
58
+ if (darkCapable && !options) {
59
+ throw new Error(
60
+ '@miragon/modeler-shell: a web modeler whose themes include "dark" needs the appearance option ' +
61
+ "({ themeColors } from its vendored cd-tokens.generated.json)",
62
+ );
63
+ }
64
+ const html = ownerDocument.documentElement;
65
+ const view = ownerDocument.defaultView;
66
+ const cleanups: (() => void)[] = [];
67
+
68
+ const contrast = typeof view?.matchMedia === "function" ? view.matchMedia(CONTRAST_QUERY) : undefined;
69
+ if (contrast) {
70
+ const mirrorContrast = (): void => toggleAttribute(html, "data-cd-kontrast", contrast.matches && "hoch");
71
+ mirrorContrast();
72
+ cleanups.push(onMediaChange(contrast, mirrorContrast));
73
+ }
74
+
75
+ function destroy(): void {
76
+ for (const cleanup of cleanups.splice(0)) cleanup();
77
+ }
78
+
79
+ if (!darkCapable || !options) {
80
+ return { choice: () => "system", setChoice() {}, destroy };
81
+ }
82
+ const { themeColors } = options;
83
+
84
+ function storage(): StorageLike | undefined {
85
+ if (options?.storage) return options.storage;
86
+ try {
87
+ return globalThis.localStorage;
88
+ } catch {
89
+ return undefined;
90
+ }
91
+ }
92
+
93
+ function readStored(): AppearanceChoice {
94
+ try {
95
+ return parseAppearanceChoice(storage()?.getItem(APPEARANCE_STORAGE_KEY));
96
+ } catch {
97
+ return "system";
98
+ }
99
+ }
100
+
101
+ function store(choice: AppearanceChoice): void {
102
+ try {
103
+ const target = storage();
104
+ if (choice === "system") target?.removeItem(APPEARANCE_STORAGE_KEY);
105
+ else target?.setItem(APPEARANCE_STORAGE_KEY, choice);
106
+ } catch {
107
+ // Blocked or full: the choice still applies for this page.
108
+ }
109
+ }
110
+
111
+ function nextFrame(callback: () => void): () => void {
112
+ if (view && typeof view.requestAnimationFrame === "function") {
113
+ const frame = view.requestAnimationFrame(() => callback());
114
+ return () => view.cancelAnimationFrame(frame);
115
+ }
116
+ const timer = setTimeout(callback, 16);
117
+ return () => clearTimeout(timer);
118
+ }
119
+
120
+ let cancelTransitionFrame: (() => void) | undefined;
121
+ function allowTransitions(): void {
122
+ cancelTransitionFrame?.();
123
+ cancelTransitionFrame = undefined;
124
+ html.classList.remove(NO_TRANSITION_CLASS);
125
+ }
126
+ cleanups.push(() => {
127
+ if (cancelTransitionFrame) allowTransitions();
128
+ });
129
+
130
+ // The first frame still computes styles with the new colours; only after it may they fade again.
131
+ function suspendTransitions(): void {
132
+ cancelTransitionFrame?.();
133
+ html.classList.add(NO_TRANSITION_CLASS);
134
+ cancelTransitionFrame = nextFrame(() => {
135
+ cancelTransitionFrame = nextFrame(allowTransitions);
136
+ });
137
+ }
138
+
139
+ function metaFor(name: string, media?: string): HTMLMetaElement {
140
+ const normalize = (value: string | null): string => (value ?? "").replace(/\s+/g, "").toLowerCase();
141
+ const existing = [...ownerDocument.querySelectorAll<HTMLMetaElement>(`meta[name="${name}"]`)].find(
142
+ (meta) => (media === undefined ? true : normalize(meta.getAttribute("media")) === normalize(media)),
143
+ );
144
+ if (existing) return existing;
145
+ const created = createElement(ownerDocument, "meta", { name, media });
146
+ (ownerDocument.head ?? html).append(created);
147
+ return created;
148
+ }
149
+
150
+ function apply(choice: AppearanceChoice): void {
151
+ const mode = documentMode(choice, themes, themeColors);
152
+ suspendTransitions();
153
+ html.setAttribute("data-cd-theme", mode.theme);
154
+ metaFor("color-scheme").setAttribute("content", mode.colorScheme);
155
+ metaFor("theme-color", SCHEME_MEDIA.light).setAttribute("content", mode.themeColor.light);
156
+ metaFor("theme-color", SCHEME_MEDIA.dark).setAttribute("content", mode.themeColor.dark);
157
+ }
158
+
159
+ let current = readStored();
160
+
161
+ const onStorage = (event: StorageEvent): void => {
162
+ // A null key is a clear() of the whole storage: the choice is gone, so "system".
163
+ if (event.key !== null && event.key !== APPEARANCE_STORAGE_KEY) return;
164
+ const next = event.key === null ? "system" : parseAppearanceChoice(event.newValue);
165
+ if (next === current) return;
166
+ current = next;
167
+ apply(next);
168
+ };
169
+ if (view) {
170
+ view.addEventListener("storage", onStorage);
171
+ cleanups.push(() => view.removeEventListener("storage", onStorage));
172
+ }
173
+
174
+ return {
175
+ choice: () => current,
176
+ setChoice(choice) {
177
+ const next = parseAppearanceChoice(choice);
178
+ current = next;
179
+ store(next);
180
+ apply(next);
181
+ },
182
+ destroy,
183
+ };
184
+ }
@@ -0,0 +1,435 @@
1
+ /*
2
+ * Feedback of @miragon/modeler-shell (owned by src/dom/feedback): the toast stack and the problems
3
+ * popover (guide §5.10, §8, §9). Colours only from var(--cd-ui-*), sizes from var(--cd-shell-*).
4
+ * Toasts render in the web and embedded hosts, so their rules also reset what a foreign page's
5
+ * element rules would set (font, colour, letter spacing, text transform), like shell-dom.css.
6
+ * The toast action is a .cd-shell-button (buttons.css), which resets itself and is left out here.
7
+ * Private helper properties are named --modeler-shell-*, never --cd-*.
8
+ */
9
+
10
+ /* ---- Foreign-page reset --------------------------------------------------------------------- */
11
+
12
+ :where(.cd-shell)
13
+ :is(
14
+ .cd-shell-toasts,
15
+ .cd-shell-toast,
16
+ .cd-shell-toast-text,
17
+ .cd-shell-toast-close,
18
+ .cd-shell-problems-popover,
19
+ .cd-shell-problems-popover-title,
20
+ .cd-shell-problems-list,
21
+ .cd-shell-problem,
22
+ .cd-shell-problem-message,
23
+ .cd-shell-problem-location
24
+ ) {
25
+ color: inherit;
26
+ font: inherit;
27
+ letter-spacing: normal;
28
+ word-spacing: normal;
29
+ text-transform: none;
30
+ text-indent: 0;
31
+ text-shadow: none;
32
+ }
33
+
34
+ /* ---- Toast stack (guide §8, §9) ------------------------------------------------------------- */
35
+
36
+ /*
37
+ * Centred column at the bottom; the script sets `bottom` (toastStackBottom). In the web host it is
38
+ * a manual popover in the top layer, so the UA's popover box (fixed, centred, bordered, padded,
39
+ * Canvas colours) is reset; in the embedded host it is absolute in the root at layer 60.
40
+ */
41
+ .cd-shell-toasts {
42
+ position: absolute;
43
+ top: auto;
44
+ right: 0;
45
+ bottom: var(--cd-shell-rand);
46
+ left: 0;
47
+ z-index: var(--cd-shell-ebene-toast);
48
+ box-sizing: border-box;
49
+ display: flex;
50
+ flex-direction: column;
51
+ align-items: center;
52
+ gap: var(--cd-shell-luecke);
53
+ width: min(480px, 100cqw - 2 * var(--cd-shell-rand));
54
+ height: auto;
55
+ min-height: 0;
56
+ max-height: none;
57
+ margin: 0 auto;
58
+ padding: 0;
59
+ overflow: visible;
60
+ color: var(--cd-ui-text);
61
+ background: transparent;
62
+ border: 0;
63
+ pointer-events: none;
64
+ }
65
+
66
+ /* Always in the DOM, no box while empty (data-chrome="toasts" measures nothing then). */
67
+ .cd-shell-toasts:empty {
68
+ display: none;
69
+ }
70
+
71
+ /* The author display above would otherwise show a closed popover; engines without it drop the rule. */
72
+ .cd-shell-toasts[popover]:not(:popover-open) {
73
+ display: none;
74
+ }
75
+
76
+ .cd-shell-toast {
77
+ --modeler-shell-toast-status: var(--cd-ui-info);
78
+ position: relative;
79
+ box-sizing: border-box;
80
+ display: flex;
81
+ flex: none;
82
+ align-items: center;
83
+ gap: var(--cd-shell-luecke);
84
+ max-width: 100%;
85
+ min-height: var(--cd-shell-zeile);
86
+ margin: 0;
87
+ padding: 4px 4px 4px 16px;
88
+ overflow: hidden;
89
+ color: var(--cd-ui-text);
90
+ font-size: 13px;
91
+ font-weight: 400;
92
+ line-height: 1.5;
93
+ text-align: left;
94
+ background: var(--cd-ui-flaeche-hoch);
95
+ border: 1px solid var(--cd-ui-linie);
96
+ border-radius: var(--cd-radius-md);
97
+ box-shadow: var(--cd-shadow-3);
98
+ pointer-events: auto;
99
+ user-select: none;
100
+ -webkit-user-select: none;
101
+ animation: cd-shell-toast-in var(--cd-motion-base) var(--cd-ease-out);
102
+ }
103
+
104
+ .cd-shell-toast[data-status="success"] {
105
+ --modeler-shell-toast-status: var(--cd-ui-success);
106
+ }
107
+
108
+ .cd-shell-toast[data-status="warning"] {
109
+ --modeler-shell-toast-status: var(--cd-ui-warning);
110
+ }
111
+
112
+ .cd-shell-toast[data-status="danger"] {
113
+ --modeler-shell-toast-status: var(--cd-ui-danger);
114
+ }
115
+
116
+ /* Its own element, clipped by the card: a border-left would follow the radius into a crescent. */
117
+ .cd-shell-toast::before {
118
+ content: "";
119
+ position: absolute;
120
+ top: 0;
121
+ bottom: 0;
122
+ left: 0;
123
+ width: 4px;
124
+ background: var(--modeler-shell-toast-status);
125
+ }
126
+
127
+ .cd-shell-toast-dot {
128
+ flex: none;
129
+ width: 8px;
130
+ height: 8px;
131
+ background: var(--modeler-shell-toast-status);
132
+ border-radius: var(--cd-radius-pill);
133
+ }
134
+
135
+ .cd-shell-toast-text {
136
+ flex: 1 1 auto;
137
+ min-width: 0;
138
+ margin: 0;
139
+ padding: 6px 0;
140
+ color: var(--cd-ui-text);
141
+ font-size: 13px;
142
+ line-height: 1.5;
143
+ overflow-wrap: anywhere;
144
+ }
145
+
146
+ /* A 32px ghost button (buttons.css), before Close. */
147
+ .cd-shell-toast .cd-shell-toast-action {
148
+ height: var(--cd-shell-ziel);
149
+ }
150
+
151
+ .cd-shell-toast-close {
152
+ box-sizing: border-box;
153
+ display: inline-grid;
154
+ flex: none;
155
+ place-items: center;
156
+ width: var(--cd-shell-ziel);
157
+ height: var(--cd-shell-ziel);
158
+ margin: 0;
159
+ padding: 0;
160
+ color: var(--cd-ui-text);
161
+ background: transparent;
162
+ border: 0;
163
+ border-radius: var(--cd-radius-sm);
164
+ cursor: pointer;
165
+ transition:
166
+ background-color var(--cd-motion-fast) var(--cd-ease),
167
+ color var(--cd-motion-fast) var(--cd-ease);
168
+ }
169
+
170
+ .cd-shell-toast-close:hover {
171
+ color: var(--cd-ui-interaktion);
172
+ background: var(--cd-ui-hover);
173
+ }
174
+
175
+ .cd-shell-toast-close svg {
176
+ display: block;
177
+ flex: none;
178
+ }
179
+
180
+ .cd-shell-toast[data-leaving] {
181
+ pointer-events: none;
182
+ animation: cd-shell-toast-out var(--cd-motion-base) var(--cd-ease) forwards;
183
+ }
184
+
185
+ @keyframes cd-shell-toast-in {
186
+ from {
187
+ opacity: 0;
188
+ transform: translateY(8px);
189
+ }
190
+ }
191
+
192
+ @keyframes cd-shell-toast-out {
193
+ to {
194
+ opacity: 0;
195
+ transform: translateY(8px);
196
+ }
197
+ }
198
+
199
+ /* The toasts' live regions: in the accessibility tree at all times, never on screen. */
200
+ .cd-shell-live {
201
+ position: absolute;
202
+ width: 1px;
203
+ height: 1px;
204
+ margin: -1px;
205
+ padding: 0;
206
+ overflow: hidden;
207
+ clip-path: inset(50%);
208
+ white-space: nowrap;
209
+ border: 0;
210
+ pointer-events: none;
211
+ }
212
+
213
+ /* ---- Problems popover (guide §5.10) --------------------------------------------------------- */
214
+
215
+ /* Left-aligned with the pill, its bottom 8px above the pill's top: 12 + 40 + 8, web 12 + 20 + 8 + 40 + 8. */
216
+ .cd-shell-problems-popover {
217
+ --modeler-shell-problems-bottom: calc(
218
+ var(--cd-shell-rand) + var(--cd-shell-zeile) + var(--cd-shell-luecke)
219
+ );
220
+ position: absolute;
221
+ left: var(--cd-shell-rand);
222
+ bottom: var(--modeler-shell-problems-bottom);
223
+ box-sizing: border-box;
224
+ display: flex;
225
+ flex-direction: column;
226
+ width: min(320px, 100cqw - 2 * var(--cd-shell-rand));
227
+ max-height: min(320px, 100cqh - var(--modeler-shell-problems-bottom) - var(--cd-shell-rand));
228
+ margin: 0;
229
+ padding: 6px;
230
+ color: var(--cd-ui-text);
231
+ font-size: 13px;
232
+ font-weight: 400;
233
+ line-height: 1.5;
234
+ background: var(--cd-ui-flaeche-hoch);
235
+ border: 1px solid var(--cd-ui-linie);
236
+ border-radius: var(--cd-radius-md);
237
+ box-shadow: var(--cd-shadow-3);
238
+ pointer-events: auto;
239
+ user-select: none;
240
+ -webkit-user-select: none;
241
+ animation: cd-shell-problems-in var(--cd-motion-fast) var(--cd-ease-out);
242
+ }
243
+
244
+ .cd-shell[data-cd-shell-host="web"] .cd-shell-problems-popover {
245
+ --modeler-shell-problems-bottom: calc(
246
+ var(--cd-shell-rand) + var(--modeler-shell-legal-height, 20px) + var(--cd-shell-luecke) +
247
+ var(--cd-shell-zeile) + var(--cd-shell-luecke)
248
+ );
249
+ }
250
+
251
+ .cd-shell-problems-popover[hidden] {
252
+ display: none;
253
+ }
254
+
255
+ /* Focus lands on the dialog itself only when it has no row to take it; inside, as in a scroller. */
256
+ .cd-shell-problems-popover:focus {
257
+ outline: none;
258
+ }
259
+
260
+ .cd-shell-problems-popover:focus-visible {
261
+ outline: 2px solid var(--cd-ui-fokus);
262
+ outline-offset: -2px;
263
+ }
264
+
265
+ .cd-shell-problems-popover-title {
266
+ flex: none;
267
+ margin: 0;
268
+ padding: 8px 12px 4px;
269
+ color: var(--cd-ui-text);
270
+ font-size: 13px;
271
+ font-weight: 600;
272
+ line-height: 1.5;
273
+ }
274
+
275
+ .cd-shell-problems-list {
276
+ flex: 1 1 auto;
277
+ min-height: 0;
278
+ margin: 0;
279
+ padding: 0;
280
+ overflow-y: auto;
281
+ overscroll-behavior: contain;
282
+ list-style: none;
283
+ }
284
+
285
+ .cd-shell-problems-list > li {
286
+ display: block;
287
+ margin: 0;
288
+ padding: 0;
289
+ list-style: none;
290
+ }
291
+
292
+ .cd-shell-problem {
293
+ box-sizing: border-box;
294
+ display: grid;
295
+ grid-template-columns: 16px minmax(0, 1fr);
296
+ column-gap: 12px;
297
+ align-items: start;
298
+ width: 100%;
299
+ min-height: var(--cd-shell-eintrag);
300
+ margin: 0;
301
+ padding: 8px 12px;
302
+ color: var(--cd-ui-text);
303
+ font-size: 13px;
304
+ font-weight: 400;
305
+ line-height: 1.5;
306
+ text-align: left;
307
+ background: transparent;
308
+ border: 0;
309
+ border-radius: var(--cd-radius-sm);
310
+ }
311
+
312
+ button.cd-shell-problem {
313
+ cursor: pointer;
314
+ transition: background-color var(--cd-motion-fast) var(--cd-ease);
315
+ }
316
+
317
+ button.cd-shell-problem:hover,
318
+ button.cd-shell-problem:focus-visible {
319
+ background: var(--cd-ui-hover);
320
+ }
321
+
322
+ /* The pill's icon, centred on the message's first line (19.5px). */
323
+ .cd-shell-problem-icon {
324
+ display: grid;
325
+ grid-row: 1 / span 2;
326
+ place-items: center;
327
+ width: 16px;
328
+ height: 16px;
329
+ margin-top: 2px;
330
+ }
331
+
332
+ .cd-shell-problem-icon svg {
333
+ display: block;
334
+ flex: none;
335
+ }
336
+
337
+ .cd-shell-problem[data-severity="error"] .cd-shell-problem-icon {
338
+ color: var(--cd-ui-danger);
339
+ }
340
+
341
+ .cd-shell-problem[data-severity="warning"] .cd-shell-problem-icon {
342
+ color: var(--cd-ui-warning);
343
+ }
344
+
345
+ .cd-shell-problem-message {
346
+ grid-column: 2;
347
+ color: var(--cd-ui-text);
348
+ overflow-wrap: anywhere;
349
+ }
350
+
351
+ .cd-shell-problem-location {
352
+ grid-column: 2;
353
+ color: var(--cd-ui-text-leise);
354
+ overflow-wrap: anywhere;
355
+ }
356
+
357
+ @keyframes cd-shell-problems-in {
358
+ from {
359
+ opacity: 0;
360
+ transform: translateY(4px);
361
+ }
362
+ }
363
+
364
+ /* ---- Focus (guide §8): inside the dense toast and the scrolling list ------------------------ */
365
+
366
+ .cd-shell :is(.cd-shell-toast-close, .cd-shell-problem):focus {
367
+ outline: none;
368
+ }
369
+
370
+ .cd-shell :is(.cd-shell-toast-close, .cd-shell-toast-action, .cd-shell-problem):focus-visible {
371
+ outline: 2px solid var(--cd-ui-fokus);
372
+ outline-offset: -2px;
373
+ }
374
+
375
+ /* ---- High contrast (guide §5.3): no shadows, 3px focus -------------------------------------- */
376
+
377
+ .cd-shell:is([data-cd-kontrast="hoch"], [data-cd-kontrast="hoch"] *)
378
+ :is(.cd-shell-toast, .cd-shell-problems-popover) {
379
+ box-shadow: none;
380
+ }
381
+
382
+ .cd-shell:is([data-cd-kontrast="hoch"], [data-cd-kontrast="hoch"] *)
383
+ :is(
384
+ .cd-shell-toast-close,
385
+ .cd-shell-toast-action,
386
+ .cd-shell-problem,
387
+ .cd-shell-problems-popover
388
+ ):focus-visible {
389
+ outline-width: 3px;
390
+ outline-offset: -3px;
391
+ }
392
+
393
+ /* ---- Forced colours (guide §5.14): nothing disappears --------------------------------------- */
394
+
395
+ @media (forced-colors: active) {
396
+ .cd-shell :is(.cd-shell-toast, .cd-shell-problems-popover) {
397
+ border-color: CanvasText;
398
+ }
399
+
400
+ /* Bar and dot keep their shape; the text carries the status. */
401
+ .cd-shell .cd-shell-toast::before,
402
+ .cd-shell .cd-shell-toast-dot {
403
+ forced-color-adjust: none;
404
+ background: CanvasText;
405
+ }
406
+
407
+ .cd-shell
408
+ :is(
409
+ .cd-shell-toast-close,
410
+ .cd-shell-toast-action,
411
+ .cd-shell-problem,
412
+ .cd-shell-problems-popover
413
+ ):focus-visible {
414
+ outline-color: Highlight;
415
+ }
416
+
417
+ /* The hover fill is forced away; a thin ring shows which row the pointer is on. */
418
+ .cd-shell button.cd-shell-problem:hover:not(:focus-visible) {
419
+ outline: 1px solid Highlight;
420
+ outline-offset: -1px;
421
+ }
422
+ }
423
+
424
+ /* ---- Reduced motion (guide §7): no slide, no pop-in ----------------------------------------- */
425
+
426
+ @media (prefers-reduced-motion: reduce) {
427
+ .cd-shell :is(.cd-shell-toast, .cd-shell-problems-popover) {
428
+ animation: none;
429
+ }
430
+ }
431
+
432
+ /* The host asks for it although the OS does not: VS Code's workbench.reduceMotion. */
433
+ html.cd-shell-reduce-motion .cd-shell :is(.cd-shell-toast, .cd-shell-problems-popover) {
434
+ animation: none;
435
+ }