@miragon/modeler-shell 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/API.md +1902 -0
  2. package/LICENSE +21 -0
  3. package/README.md +435 -2
  4. package/THIRD_PARTY_NOTICES.md +52 -0
  5. package/dist/appearance-D3FUcbu6.d.cts +53 -0
  6. package/dist/appearance-D3FUcbu6.d.ts +53 -0
  7. package/dist/chunk-5JSZ4VGA.js +551 -0
  8. package/dist/chunk-5JSZ4VGA.js.map +1 -0
  9. package/dist/chunk-6LQOFACK.js +1576 -0
  10. package/dist/chunk-6LQOFACK.js.map +1 -0
  11. package/dist/chunk-N4QXFF3A.js +23 -0
  12. package/dist/chunk-N4QXFF3A.js.map +1 -0
  13. package/dist/chunk-RJRFR6N3.js +136 -0
  14. package/dist/chunk-RJRFR6N3.js.map +1 -0
  15. package/dist/core.cjs +619 -0
  16. package/dist/core.cjs.map +1 -0
  17. package/dist/core.d.cts +171 -0
  18. package/dist/core.d.ts +171 -0
  19. package/dist/core.js +91 -0
  20. package/dist/core.js.map +1 -0
  21. package/dist/diagram-js.cjs +1115 -0
  22. package/dist/diagram-js.cjs.map +1 -0
  23. package/dist/diagram-js.d.cts +163 -0
  24. package/dist/diagram-js.d.ts +163 -0
  25. package/dist/diagram-js.js +867 -0
  26. package/dist/diagram-js.js.map +1 -0
  27. package/dist/index.cjs +2175 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.d.cts +20 -0
  30. package/dist/index.d.ts +20 -0
  31. package/dist/index.js +103 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/keymap-CObQE6q6.d.cts +151 -0
  34. package/dist/keymap-DORzHnK-.d.ts +151 -0
  35. package/dist/protocol-DhYBakWL.d.cts +15 -0
  36. package/dist/protocol-IeW8bf4f.d.ts +15 -0
  37. package/dist/shell.css +914 -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 +2364 -0
  45. package/dist/testing.cjs.map +1 -0
  46. package/dist/testing.d.cts +165 -0
  47. package/dist/testing.d.ts +165 -0
  48. package/dist/testing.js +2297 -0
  49. package/dist/testing.js.map +1 -0
  50. package/dist/types-B6NfkiUG.d.cts +66 -0
  51. package/dist/types-B6NfkiUG.d.ts +66 -0
  52. package/dist/types-DnZn9CPw.d.ts +284 -0
  53. package/dist/types-OuFlqZp0.d.cts +284 -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 +2198 -0
  61. package/dist/vscode-webview.cjs.map +1 -0
  62. package/dist/vscode-webview.d.cts +82 -0
  63. package/dist/vscode-webview.d.ts +82 -0
  64. package/dist/vscode-webview.js +250 -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/fit.ts +205 -0
  69. package/src/core/index.ts +84 -0
  70. package/src/core/keymap.ts +275 -0
  71. package/src/core/layout.ts +69 -0
  72. package/src/core/messages.ts +88 -0
  73. package/src/core/sizes.ts +85 -0
  74. package/src/core/types.ts +84 -0
  75. package/src/core/zoom.ts +74 -0
  76. package/src/core.ts +2 -0
  77. package/src/diagram-js/connect.ts +75 -0
  78. package/src/diagram-js/index.ts +16 -0
  79. package/src/diagram-js/module.ts +20 -0
  80. package/src/diagram-js/palette-skin.ts +267 -0
  81. package/src/diagram-js/palette.css +269 -0
  82. package/src/diagram-js/service.ts +240 -0
  83. package/src/diagram-js/types.ts +67 -0
  84. package/src/diagram-js/viewport.ts +564 -0
  85. package/src/diagram-js.ts +2 -0
  86. package/src/dom/element.ts +51 -0
  87. package/src/dom/hint.ts +51 -0
  88. package/src/dom/icons.ts +309 -0
  89. package/src/dom/index.ts +29 -0
  90. package/src/dom/inspector.ts +35 -0
  91. package/src/dom/keys.ts +59 -0
  92. package/src/dom/ledger.ts +87 -0
  93. package/src/dom/legend.ts +85 -0
  94. package/src/dom/menu.ts +393 -0
  95. package/src/dom/mount.ts +461 -0
  96. package/src/dom/problems.ts +72 -0
  97. package/src/dom/shell-dom.css +966 -0
  98. package/src/dom/status.ts +72 -0
  99. package/src/dom/tooltip.ts +237 -0
  100. package/src/dom/types.ts +277 -0
  101. package/src/dom/zoom-panel.ts +154 -0
  102. package/src/index.ts +3 -0
  103. package/src/shell.css +6 -0
  104. package/src/testing/cases.ts +1012 -0
  105. package/src/testing/checks.ts +832 -0
  106. package/src/testing/facts.ts +587 -0
  107. package/src/testing/geometry.ts +472 -0
  108. package/src/testing/harness.ts +138 -0
  109. package/src/testing/index.ts +15 -0
  110. package/src/testing/page-tools.ts +434 -0
  111. package/src/testing/page.ts +17 -0
  112. package/src/testing/vscode-stub.ts +237 -0
  113. package/src/testing-vscode-stub.ts +2 -0
  114. package/src/testing.ts +2 -0
  115. package/src/vscode/extension/html.ts +107 -0
  116. package/src/vscode/extension/index.ts +8 -0
  117. package/src/vscode/extension/reveal.ts +9 -0
  118. package/src/vscode/extension/theme-bridge.ts +85 -0
  119. package/src/vscode/extension/types.ts +36 -0
  120. package/src/vscode/protocol.ts +31 -0
  121. package/src/vscode/webview/appearance.ts +141 -0
  122. package/src/vscode/webview/index.ts +8 -0
  123. package/src/vscode/webview/mount.ts +76 -0
  124. package/src/vscode/webview/state.ts +116 -0
  125. package/src/vscode-extension.ts +2 -0
  126. package/src/vscode-webview.ts +2 -0
@@ -0,0 +1,267 @@
1
+ import { ariaKeyShortcutsFromLabel, defaultMessages } from "../core/index.js";
2
+ import type { PaletteReadOnlyState, TooltipContent } from "../dom/index.js";
3
+
4
+ /** The diagram-js palette group of the tools (`Palette#updateToolHighlight` looks for it). */
5
+ export const TOOLS_GROUP = "tools";
6
+
7
+ /**
8
+ * The entries that stay active on a read-only canvas (guide §13.5: Lasso and Hand). Every other
9
+ * entry changes the diagram, also tools such as drawing, space or connect.
10
+ */
11
+ export const READ_ONLY_ACTIONS: ReadonlySet<string> = new Set(["lasso-tool", "hand-tool"]);
12
+
13
+ const TOOL_ACTION_SUFFIX = "-tool";
14
+ const ENTRIES_SELECTOR = ".djs-palette-entries";
15
+ const ENTRY_SELECTOR = ".entry";
16
+
17
+ /** One entry of `palette.getEntries()`, as far as the skin reads it. */
18
+ export interface PaletteEntryLike {
19
+ title?: string | undefined;
20
+ shortcut?: string | undefined;
21
+ /** `aria-keyshortcuts` (WAI-ARIA names); default `ariaKeyShortcutsFromLabel(shortcut)`. */
22
+ ariaKeyShortcuts?: string | undefined;
23
+ group?: string | undefined;
24
+ }
25
+
26
+ /**
27
+ * The shell's additions to the diagram-js palette container (guide §5.5): `data-chrome`, the
28
+ * toolbar semantics of the entries, the roving tab stop with its keys, `aria-pressed` for the
29
+ * active tool and `aria-disabled` in read-only. diagram-js clears and rebuilds every entry on each
30
+ * change, so `refresh()` re-applies all of it after every `palette.changed`.
31
+ */
32
+ export class PaletteSkin {
33
+ private label = defaultMessages.tools;
34
+ private activeTool: string | null = null;
35
+ /** The veto of a read-only canvas, at once with the invalid import. */
36
+ private locked = false;
37
+ /** The `aria-disabled` marks of a read-only canvas, once the shell's delay has passed. */
38
+ private readOnlyShown = false;
39
+ private entries: Readonly<Record<string, PaletteEntryLike>> = {};
40
+ private rovingAction: string | undefined;
41
+ private focusedEntry: HTMLElement | undefined;
42
+ /** The `aria-disabled` an entry had before read-only marked it (`null`: none). */
43
+ private readonly disabledEntries = new WeakMap<Element, string | null>();
44
+
45
+ constructor(
46
+ readonly element: HTMLElement,
47
+ private readonly readEntries: () => Readonly<Record<string, PaletteEntryLike>>,
48
+ ) {
49
+ element.setAttribute("data-chrome", "palette");
50
+ element.addEventListener("keydown", this.onKeydown);
51
+ element.addEventListener("focusin", this.onFocusIn);
52
+ element.addEventListener("focusout", this.onFocusOut);
53
+ this.refresh();
54
+ }
55
+
56
+ setLabel(label: string): void {
57
+ this.label = label;
58
+ this.apply();
59
+ }
60
+
61
+ setActiveTool(tool: string | null): void {
62
+ this.activeTool = tool;
63
+ this.apply();
64
+ }
65
+
66
+ setReadOnly(state: PaletteReadOnlyState): void {
67
+ this.locked = state.locked;
68
+ this.readOnlyShown = state.locked && state.shown;
69
+ this.apply();
70
+ }
71
+
72
+ /**
73
+ * Whether `palette.trigger` must refuse `entry`: on a locked canvas every entry but Lasso and
74
+ * Hand, also before their marks show. `palette.trigger` carries the entry definition without its
75
+ * id, so the entry element is found through the DOM event; without one, only the tools group
76
+ * passes.
77
+ */
78
+ vetoes(entry: PaletteEntryLike, event: Event | undefined): boolean {
79
+ if (!this.locked) return false;
80
+ const element = this.entryOf(
81
+ (event as { delegateTarget?: EventTarget } | undefined)?.delegateTarget ?? event?.target ?? null,
82
+ );
83
+ if (element) return !staysActive(element);
84
+ return entry.group !== TOOLS_GROUP;
85
+ }
86
+
87
+ /** After `palette.changed`: re-read the entries and re-apply every attribute. */
88
+ refresh(): void {
89
+ this.entries = this.readEntries();
90
+ this.apply();
91
+ }
92
+
93
+ entryTooltip(entry: HTMLElement): TooltipContent | null {
94
+ const action = entry.getAttribute("data-action");
95
+ const definition = action === null ? undefined : this.entries[action];
96
+ if (!definition?.title) return null;
97
+ return definition.shortcut
98
+ ? { label: definition.title, shortcut: definition.shortcut }
99
+ : { label: definition.title };
100
+ }
101
+
102
+ destroy(): void {
103
+ this.element.removeEventListener("keydown", this.onKeydown);
104
+ this.element.removeEventListener("focusin", this.onFocusIn);
105
+ this.element.removeEventListener("focusout", this.onFocusOut);
106
+ this.focusedEntry = undefined;
107
+ }
108
+
109
+ private apply(): void {
110
+ const container = this.element.querySelector<HTMLElement>(ENTRIES_SELECTOR);
111
+ if (container) {
112
+ container.setAttribute("role", "toolbar");
113
+ container.setAttribute("aria-label", this.label);
114
+ }
115
+ const entries = this.entryElements();
116
+ for (const entry of entries) {
117
+ if (!entry.hasAttribute("role")) entry.setAttribute("role", "button");
118
+ this.applyKeyShortcuts(entry);
119
+ this.applyPressed(entry);
120
+ this.applyDisabled(entry);
121
+ }
122
+ this.applyTabStops(entries);
123
+ this.restoreFocus(entries);
124
+ }
125
+
126
+ // Guide §5.6: a button with a shortcut names it for assistive technology, not only in the chip.
127
+ private applyKeyShortcuts(entry: HTMLElement): void {
128
+ const action = actionOf(entry);
129
+ const definition = action === undefined ? undefined : this.entries[action];
130
+ const keyShortcuts =
131
+ definition?.ariaKeyShortcuts ??
132
+ (definition?.shortcut ? ariaKeyShortcutsFromLabel(definition.shortcut) : undefined);
133
+ if (keyShortcuts) entry.setAttribute("aria-keyshortcuts", keyShortcuts);
134
+ }
135
+
136
+ private applyPressed(entry: HTMLElement): void {
137
+ const action = actionOf(entry);
138
+ if (groupOf(entry) !== TOOLS_GROUP || !action?.endsWith(TOOL_ACTION_SUFFIX)) return;
139
+ const tool = action.slice(0, -TOOL_ACTION_SUFFIX.length);
140
+ entry.setAttribute("aria-pressed", String(tool === this.activeTool));
141
+ }
142
+
143
+ private applyDisabled(entry: HTMLElement): void {
144
+ if (this.readOnlyShown && !staysActive(entry)) {
145
+ if (!this.disabledEntries.has(entry)) {
146
+ this.disabledEntries.set(entry, entry.getAttribute("aria-disabled"));
147
+ }
148
+ entry.setAttribute("aria-disabled", "true");
149
+ return;
150
+ }
151
+ if (!this.disabledEntries.has(entry)) return;
152
+ const previous = this.disabledEntries.get(entry);
153
+ this.disabledEntries.delete(entry);
154
+ if (previous === null || previous === undefined) entry.removeAttribute("aria-disabled");
155
+ else entry.setAttribute("aria-disabled", previous);
156
+ }
157
+
158
+ private applyTabStops(entries: readonly HTMLElement[]): void {
159
+ const stop = entries.find((entry) => actionOf(entry) === this.rovingAction) ?? entries[0];
160
+ for (const entry of entries) entry.tabIndex = entry === stop ? 0 : -1;
161
+ }
162
+
163
+ // A rebuild removes the focused entry; without this the focus would fall back to <body>.
164
+ private restoreFocus(entries: readonly HTMLElement[]): void {
165
+ const lost = this.focusedEntry;
166
+ if (!lost || lost.isConnected) return;
167
+ this.focusedEntry = undefined;
168
+ const replacement = entries.find((entry) => actionOf(entry) === actionOf(lost));
169
+ const active = this.element.ownerDocument.activeElement;
170
+ const focusIsNowhere = !active || active === this.element.ownerDocument.body;
171
+ if (replacement && focusIsNowhere) replacement.focus();
172
+ }
173
+
174
+ private entryElements(): HTMLElement[] {
175
+ const container = this.element.querySelector(ENTRIES_SELECTOR);
176
+ return container ? Array.from(container.querySelectorAll<HTMLElement>(ENTRY_SELECTOR)) : [];
177
+ }
178
+
179
+ private entryOf(target: EventTarget | null): HTMLElement | undefined {
180
+ if (!(target instanceof Element)) return undefined;
181
+ const entry = target.closest<HTMLElement>(ENTRY_SELECTOR);
182
+ return entry && this.element.contains(entry) ? entry : undefined;
183
+ }
184
+
185
+ private readonly onFocusIn = (event: FocusEvent): void => {
186
+ const entry = this.entryOf(event.target);
187
+ if (!entry) return;
188
+ this.focusedEntry = entry;
189
+ this.rovingAction = actionOf(entry);
190
+ this.applyTabStops(this.entryElements());
191
+ };
192
+
193
+ private readonly onFocusOut = (event: FocusEvent): void => {
194
+ const entry = this.entryOf(event.target);
195
+ if (!entry || entry !== this.focusedEntry || !entry.isConnected) return;
196
+ const next = event.relatedTarget;
197
+ if (!(next instanceof Node) || !this.element.contains(next)) this.focusedEntry = undefined;
198
+ };
199
+
200
+ private readonly onKeydown = (event: KeyboardEvent): void => {
201
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
202
+ const entry = this.entryOf(event.target);
203
+ if (!entry) return;
204
+ const entries = this.entryElements();
205
+ const index = entries.indexOf(entry);
206
+ const last = entries.length - 1;
207
+ let next: HTMLElement | undefined;
208
+ switch (event.key) {
209
+ case "ArrowLeft":
210
+ next = entries[index <= 0 ? last : index - 1];
211
+ break;
212
+ case "ArrowRight":
213
+ next = entries[index >= last ? 0 : index + 1];
214
+ break;
215
+ case "Home":
216
+ next = entries[0];
217
+ break;
218
+ case "End":
219
+ next = entries[last];
220
+ break;
221
+ case "Enter":
222
+ case " ":
223
+ event.preventDefault();
224
+ event.stopPropagation();
225
+ if (!event.repeat) activate(entry);
226
+ return;
227
+ default:
228
+ return;
229
+ }
230
+ event.preventDefault();
231
+ event.stopPropagation();
232
+ if (!next) return;
233
+ this.rovingAction = actionOf(next);
234
+ this.applyTabStops(entries);
235
+ next.focus();
236
+ };
237
+ }
238
+
239
+ function actionOf(entry: Element): string | undefined {
240
+ return entry.getAttribute("data-action") ?? undefined;
241
+ }
242
+
243
+ function staysActive(entry: Element): boolean {
244
+ const action = actionOf(entry);
245
+ return action !== undefined && READ_ONLY_ACTIONS.has(action);
246
+ }
247
+
248
+ function groupOf(entry: Element): string | undefined {
249
+ return entry.closest(".group")?.getAttribute("data-group") ?? undefined;
250
+ }
251
+
252
+ // Enter and Space activate an entry with a synthetic click: `detail` 0, clientX/Y at the entry's
253
+ // centre. A creating entry tells it from a pointer click (`detail` ≥ 1) by that and places its
254
+ // element at `viewport.freeRectCenter()` (guide §5.5). diagram-js handles entry clicks through a
255
+ // delegate on the palette container, so it goes through `palette.trigger` and its read-only veto
256
+ // exactly like a pointer click.
257
+ function activate(entry: HTMLElement): void {
258
+ const bounds = entry.getBoundingClientRect();
259
+ entry.dispatchEvent(
260
+ new MouseEvent("click", {
261
+ bubbles: true,
262
+ cancelable: true,
263
+ clientX: bounds.left + bounds.width / 2,
264
+ clientY: bounds.top + bounds.height / 2,
265
+ }),
266
+ );
267
+ }
@@ -0,0 +1,269 @@
1
+ /*
2
+ * Horizontal palette skin of @miragon/modeler-shell (owned by src/diagram-js), on
3
+ * .djs-palette[data-chrome="palette"] (specificity of at least 0,2,0): top-centre glass island,
4
+ * 32px entries, group separators, active-tool ring, second row, read-only entries.
5
+ */
6
+
7
+ .djs-palette[data-chrome="palette"] {
8
+ --modeler-shell-palette-focus-width: 2px;
9
+ --modeler-shell-palette-focus-offset: 2px;
10
+ --modeler-shell-palette-focus-offset-pressed: 2px;
11
+
12
+ position: absolute;
13
+ top: var(--cd-shell-rand);
14
+ left: 50%;
15
+ z-index: 110;
16
+ display: flex;
17
+ align-items: center;
18
+ box-sizing: border-box;
19
+ width: auto;
20
+ height: var(--cd-shell-zeile);
21
+ padding: 3px;
22
+ border: 1px solid var(--cd-ui-linie);
23
+ border-radius: var(--cd-radius-md);
24
+ background: color-mix(in srgb, var(--cd-ui-flaeche-hoch) var(--cd-ui-chrome-deckkraft), transparent);
25
+ -webkit-backdrop-filter: blur(8px);
26
+ backdrop-filter: blur(8px);
27
+ box-shadow: var(--cd-shadow-1);
28
+ color: var(--cd-ui-text);
29
+ transform: translateX(-50%);
30
+ white-space: nowrap;
31
+ -webkit-user-select: none;
32
+ user-select: none;
33
+ }
34
+
35
+ /* The vertical layout's two columns and its open/close toggle have no place in the horizontal island. */
36
+ .djs-palette[data-chrome="palette"].two-column.open {
37
+ width: auto;
38
+ }
39
+
40
+ .djs-palette[data-chrome="palette"] .djs-palette-toggle,
41
+ .djs-palette[data-chrome="palette"] .djs-palette-entries::after {
42
+ display: none;
43
+ }
44
+
45
+ .djs-palette[data-chrome="palette"] .djs-palette-entries,
46
+ .djs-palette[data-chrome="palette"]:not(.open) .djs-palette-entries {
47
+ display: flex;
48
+ flex: none;
49
+ align-items: center;
50
+ }
51
+
52
+ .djs-palette[data-chrome="palette"] .group {
53
+ position: relative;
54
+ display: flex;
55
+ flex: none;
56
+ align-items: center;
57
+ gap: 2px;
58
+ }
59
+
60
+ /* 8px, the 1px line, 8px. */
61
+ .djs-palette[data-chrome="palette"] .group + .group {
62
+ margin-left: 8px;
63
+ padding-left: 9px;
64
+ }
65
+
66
+ .djs-palette[data-chrome="palette"] .group + .group::before {
67
+ content: "";
68
+ position: absolute;
69
+ top: 4px;
70
+ bottom: 4px;
71
+ left: 0;
72
+ width: 1px;
73
+ background: var(--cd-ui-linie);
74
+ }
75
+
76
+ .djs-palette[data-chrome="palette"] .separator {
77
+ flex: none;
78
+ align-self: stretch;
79
+ width: 1px;
80
+ height: auto;
81
+ margin: 4px 6px;
82
+ padding: 0;
83
+ border: 0;
84
+ background: var(--cd-ui-linie);
85
+ }
86
+
87
+ .djs-palette[data-chrome="palette"] .entry {
88
+ position: relative;
89
+ display: flex;
90
+ flex: none;
91
+ align-items: center;
92
+ justify-content: center;
93
+ box-sizing: border-box;
94
+ width: var(--cd-shell-ziel);
95
+ height: var(--cd-shell-ziel);
96
+ margin: 0;
97
+ padding: 0;
98
+ float: none;
99
+ border: 0;
100
+ border-radius: var(--cd-radius-sm);
101
+ background: transparent;
102
+ color: var(--cd-ui-text);
103
+ font-size: 24px;
104
+ line-height: 1;
105
+ text-align: center;
106
+ cursor: default;
107
+ transition:
108
+ background-color var(--cd-motion-fast) var(--cd-ease),
109
+ color var(--cd-motion-fast) var(--cd-ease),
110
+ box-shadow var(--cd-motion-fast) var(--cd-ease);
111
+ }
112
+
113
+ .djs-palette[data-chrome="palette"] .entry > img,
114
+ .djs-palette[data-chrome="palette"] .entry > svg {
115
+ display: block;
116
+ flex: none;
117
+ max-width: 100%;
118
+ max-height: 100%;
119
+ }
120
+
121
+ /*
122
+ * Tool icons take the entry's colour in every state, also against renderer or diagram-js rules on
123
+ * the icon itself. Notation previews keep their own colours (guide §5.5), so only tools inherit.
124
+ */
125
+ .djs-palette[data-chrome="palette"] .group[data-group="tools"] .entry > svg,
126
+ .djs-palette[data-chrome="palette"] .group[data-group="tools"] .entry::before,
127
+ .djs-palette[data-chrome="palette"] .entry[data-action$="-tool"] > svg,
128
+ .djs-palette[data-chrome="palette"] .entry[data-action$="-tool"]::before {
129
+ color: inherit;
130
+ }
131
+
132
+ .djs-palette[data-chrome="palette"] .entry:hover {
133
+ background: var(--cd-ui-hover);
134
+ color: var(--cd-ui-interaktion);
135
+ }
136
+
137
+ /*
138
+ * diagram-js forces the colour of `highlighted-entry` with !important, and the class goes stale after
139
+ * a rebuild; the active tool is marked through aria-pressed instead.
140
+ */
141
+ .djs-palette[data-chrome="palette"] .entry.highlighted-entry {
142
+ color: var(--cd-ui-text) !important;
143
+ }
144
+
145
+ .djs-palette[data-chrome="palette"] .entry.highlighted-entry:hover {
146
+ color: var(--cd-ui-interaktion) !important;
147
+ }
148
+
149
+ .djs-palette[data-chrome="palette"] .entry[aria-pressed="true"] {
150
+ background: var(--cd-ui-hover);
151
+ box-shadow: inset 0 0 0 2px var(--cd-ui-interaktion);
152
+ color: var(--cd-ui-text);
153
+ }
154
+
155
+ .djs-palette[data-chrome="palette"] .entry[aria-pressed="true"]:hover {
156
+ color: var(--cd-ui-interaktion);
157
+ }
158
+
159
+ .djs-palette[data-chrome="palette"] .entry:focus {
160
+ outline: none;
161
+ }
162
+
163
+ .djs-palette[data-chrome="palette"] .entry:focus-visible {
164
+ outline: var(--modeler-shell-palette-focus-width) solid var(--cd-ui-fokus);
165
+ outline-offset: var(--modeler-shell-palette-focus-offset);
166
+ }
167
+
168
+ .djs-palette[data-chrome="palette"] .entry[aria-pressed="true"]:focus-visible {
169
+ outline-offset: var(--modeler-shell-palette-focus-offset-pressed);
170
+ }
171
+
172
+ /* Read-only canvas (guide §13.5): tool icons muted, notation previews grey, never by opacity. */
173
+ .djs-palette[data-chrome="palette"] .entry[aria-disabled="true"],
174
+ .djs-palette[data-chrome="palette"] .entry[aria-disabled="true"]:hover {
175
+ background: transparent;
176
+ color: var(--cd-ui-text-leise);
177
+ cursor: not-allowed;
178
+ }
179
+
180
+ .djs-palette[data-chrome="palette"] .entry.highlighted-entry[aria-disabled="true"] {
181
+ color: var(--cd-ui-text-leise) !important;
182
+ }
183
+
184
+ .djs-palette[data-chrome="palette"] .entry[aria-disabled="true"] > *,
185
+ .djs-palette[data-chrome="palette"] .entry[aria-disabled="true"]::before {
186
+ filter: grayscale(1);
187
+ }
188
+
189
+ [data-cd-kontrast="hoch"] .djs-palette[data-chrome="palette"] {
190
+ --modeler-shell-palette-focus-width: 3px;
191
+
192
+ box-shadow: none;
193
+ }
194
+
195
+ /*
196
+ * Second row (guide §5.15): set by the shell's measurement or by the container width. It scrolls, so
197
+ * the focus ring moves inside the entry; on the active tool 1px inside its 2px ring.
198
+ */
199
+ .cd-shell[data-palette="second-row"] .djs-palette[data-chrome="palette"] {
200
+ --modeler-shell-palette-focus-offset: calc(-1 * var(--modeler-shell-palette-focus-width));
201
+ --modeler-shell-palette-focus-offset-pressed: calc(-3px - var(--modeler-shell-palette-focus-width));
202
+
203
+ top: var(--cd-shell-frei);
204
+ max-width: calc(100cqw - 2 * var(--cd-shell-rand));
205
+ overflow-x: auto;
206
+ overflow-y: hidden;
207
+ overscroll-behavior-x: contain;
208
+ scrollbar-width: none;
209
+ }
210
+
211
+ .cd-shell[data-palette="second-row"] .djs-palette[data-chrome="palette"]::-webkit-scrollbar {
212
+ display: none;
213
+ }
214
+
215
+ @container cd-shell (max-width: 760px) {
216
+ .djs-palette[data-chrome="palette"] {
217
+ --modeler-shell-palette-focus-offset: calc(-1 * var(--modeler-shell-palette-focus-width));
218
+ --modeler-shell-palette-focus-offset-pressed: calc(-3px - var(--modeler-shell-palette-focus-width));
219
+
220
+ top: var(--cd-shell-frei);
221
+ max-width: calc(100cqw - 2 * var(--cd-shell-rand));
222
+ overflow-x: auto;
223
+ overflow-y: hidden;
224
+ overscroll-behavior-x: contain;
225
+ scrollbar-width: none;
226
+ }
227
+
228
+ .djs-palette[data-chrome="palette"]::-webkit-scrollbar {
229
+ display: none;
230
+ }
231
+ }
232
+
233
+ @media (forced-colors: active) {
234
+ .djs-palette[data-chrome="palette"] {
235
+ border-color: CanvasText;
236
+ }
237
+
238
+ .djs-palette[data-chrome="palette"] .group + .group::before,
239
+ .djs-palette[data-chrome="palette"] .separator {
240
+ forced-color-adjust: none;
241
+ background: CanvasText;
242
+ }
243
+
244
+ .djs-palette[data-chrome="palette"] .entry:focus-visible {
245
+ outline-color: Highlight;
246
+ }
247
+
248
+ .djs-palette[data-chrome="palette"] .entry[aria-pressed="true"],
249
+ .djs-palette[data-chrome="palette"] .entry[aria-pressed="true"]:hover {
250
+ forced-color-adjust: none;
251
+ background: Canvas;
252
+ color: CanvasText;
253
+ box-shadow: inset 0 0 0 2px Highlight;
254
+ }
255
+
256
+ .djs-palette[data-chrome="palette"] .entry[aria-disabled="true"] {
257
+ color: GrayText;
258
+ }
259
+ }
260
+
261
+ @media (prefers-reduced-motion: reduce) {
262
+ .djs-palette[data-chrome="palette"] .entry {
263
+ transition: none;
264
+ }
265
+ }
266
+
267
+ html.cd-shell-reduce-motion .djs-palette[data-chrome="palette"] .entry {
268
+ transition: none;
269
+ }