@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,386 @@
1
+ /*
2
+ * Web-app chrome of @miragon/modeler-shell (owned by src/dom/web): primary action, legal line,
3
+ * empty-state card, drop zone, the start screen hiding the work chrome and the raised zoom row
4
+ * (guide §5.7, §5.10, §5.15, §8, §9). Colours only from var(--cd-ui-*), sizes from
5
+ * var(--cd-shell-*); the primary fill comes from ../buttons.css. Every rule is scoped to
6
+ * .cd-shell[data-cd-shell-host="web"] or to classes only the web host renders.
7
+ *
8
+ * 20px is the legal line's height (13px text on a 20px line, guide §5.10); it has no token. Where a
9
+ * narrow container wraps the line, the shell measures it into --modeler-shell-legal-height on the
10
+ * root, and everything stacked above the line stands on that height.
11
+ */
12
+
13
+ /* The app's own element rules (h1 { font-size: 2em }, a { text-transform … }) stay out. */
14
+ :where(.cd-shell)
15
+ :is(
16
+ .cd-shell-primary,
17
+ .cd-shell-primary-label,
18
+ .cd-shell-legal,
19
+ .cd-shell-legal a,
20
+ .cd-shell-empty-state,
21
+ .cd-shell-empty-state-title,
22
+ .cd-shell-empty-state-text,
23
+ .cd-shell-empty-state-hint,
24
+ .cd-shell-empty-state-open,
25
+ .cd-shell-empty-state-key,
26
+ .cd-shell-empty-state .cd-shell-button,
27
+ .cd-shell-drop-zone-chip
28
+ ) {
29
+ letter-spacing: normal;
30
+ word-spacing: normal;
31
+ text-transform: none;
32
+ text-indent: 0;
33
+ text-shadow: none;
34
+ }
35
+
36
+ /* ---- Primary action, top right (guide §5.7, §5.15) ------------------------------------------ */
37
+
38
+ .cd-shell .cd-shell-primary {
39
+ position: absolute;
40
+ top: var(--cd-shell-rand);
41
+ right: var(--cd-shell-rand);
42
+ z-index: var(--cd-shell-ebene-reihe);
43
+ height: var(--cd-shell-zeile);
44
+ padding: 0 16px;
45
+ }
46
+
47
+ /* Icon only: 40 × 40, the label gone, the aria-label unchanged. */
48
+ .cd-shell .cd-shell-primary[data-collapsed] {
49
+ width: var(--cd-shell-zeile);
50
+ padding: 0;
51
+ }
52
+
53
+ .cd-shell-primary-label[hidden] {
54
+ display: none;
55
+ }
56
+
57
+ /* ---- Legal line, bottom left (guide §5.10, §2) ---------------------------------------------- */
58
+
59
+ /*
60
+ * One line where it fits; in a narrower container it wraps between its entries instead of being
61
+ * cut off (Datenschutz must stay reachable, WCAG 1.4.10), and grows upwards from bottom 12.
62
+ */
63
+ .cd-shell-legal {
64
+ position: absolute;
65
+ left: var(--cd-shell-rand);
66
+ bottom: var(--cd-shell-rand);
67
+ z-index: var(--cd-shell-ebene-inseln);
68
+ box-sizing: border-box;
69
+ display: flex;
70
+ flex-wrap: wrap;
71
+ align-items: center;
72
+ column-gap: 6px;
73
+ row-gap: 0;
74
+ min-height: 20px;
75
+ max-width: calc(100cqw - 2 * var(--cd-shell-rand));
76
+ margin: 0;
77
+ padding: 0 8px;
78
+ color: var(--cd-ui-text-leise);
79
+ font-size: 13px;
80
+ font-weight: 400;
81
+ line-height: 20px;
82
+ white-space: nowrap;
83
+ background: color-mix(in srgb, var(--cd-ui-flaeche-hoch) var(--cd-ui-chrome-deckkraft), transparent);
84
+ -webkit-backdrop-filter: blur(8px);
85
+ backdrop-filter: blur(8px);
86
+ border: 0;
87
+ border-radius: var(--cd-radius-sm);
88
+ box-shadow: none;
89
+ user-select: none;
90
+ -webkit-user-select: none;
91
+ }
92
+
93
+ .cd-shell-legal[hidden] {
94
+ display: none;
95
+ }
96
+
97
+ .cd-shell-legal > * {
98
+ flex: none;
99
+ }
100
+
101
+ /* A separator and the link after it: a wrap never leaves a separator alone at a line's end. */
102
+ .cd-shell-legal-item {
103
+ display: inline-flex;
104
+ align-items: center;
105
+ gap: 6px;
106
+ }
107
+
108
+ .cd-shell .cd-shell-legal a,
109
+ .cd-shell .cd-shell-legal a:visited {
110
+ color: var(--cd-ui-link);
111
+ font: inherit;
112
+ text-decoration: none;
113
+ border-radius: 2px;
114
+ }
115
+
116
+ .cd-shell .cd-shell-legal a:hover {
117
+ color: var(--cd-ui-link);
118
+ text-decoration: underline;
119
+ }
120
+
121
+ .cd-shell .cd-shell-legal a:focus {
122
+ outline: none;
123
+ }
124
+
125
+ .cd-shell .cd-shell-legal a:focus-visible {
126
+ text-decoration: underline;
127
+ outline: 2px solid var(--cd-ui-fokus);
128
+ outline-offset: 2px;
129
+ }
130
+
131
+ /* ---- Empty state, centre (guide §8) --------------------------------------------------------- */
132
+
133
+ .cd-shell-empty-state {
134
+ position: absolute;
135
+ top: 50%;
136
+ left: 50%;
137
+ translate: -50% -50%;
138
+ z-index: var(--cd-shell-ebene-inseln);
139
+ box-sizing: border-box;
140
+ display: flex;
141
+ flex-direction: column;
142
+ align-items: center;
143
+ gap: 16px;
144
+ width: min(400px, 100cqw - 2 * var(--cd-shell-rand));
145
+ max-height: calc(100cqh - 2 * var(--cd-shell-rand));
146
+ overflow-y: auto;
147
+ overscroll-behavior: contain;
148
+ margin: 0;
149
+ padding: 32px 40px;
150
+ color: var(--cd-ui-text);
151
+ font-size: 13px;
152
+ line-height: 1.5;
153
+ text-align: center;
154
+ background: color-mix(in srgb, var(--cd-ui-flaeche-hoch) var(--cd-ui-chrome-deckkraft), transparent);
155
+ -webkit-backdrop-filter: blur(8px);
156
+ backdrop-filter: blur(8px);
157
+ border: 1px solid var(--cd-ui-linie);
158
+ border-radius: var(--cd-radius-lg);
159
+ box-shadow: var(--cd-shadow-2);
160
+ user-select: none;
161
+ -webkit-user-select: none;
162
+ animation: cd-shell-rise-in var(--cd-motion-base) var(--cd-ease-out);
163
+ }
164
+
165
+ .cd-shell-empty-state[hidden] {
166
+ display: none;
167
+ }
168
+
169
+ .cd-shell-empty-state-mark {
170
+ display: block;
171
+ flex: none;
172
+ width: 64px;
173
+ height: 64px;
174
+ margin: 0;
175
+ }
176
+
177
+ .cd-shell .cd-shell-empty-state-title {
178
+ margin: 0;
179
+ color: var(--cd-ui-text);
180
+ font-size: 21px;
181
+ font-weight: 600;
182
+ line-height: 1.3;
183
+ }
184
+
185
+ .cd-shell .cd-shell-empty-state-text {
186
+ margin: 0;
187
+ color: var(--cd-ui-text-leise);
188
+ font-size: 16px;
189
+ font-weight: 400;
190
+ line-height: 1.6;
191
+ }
192
+
193
+ .cd-shell-empty-state-actions {
194
+ display: flex;
195
+ flex-wrap: wrap;
196
+ justify-content: center;
197
+ gap: var(--cd-shell-luecke);
198
+ }
199
+
200
+ .cd-shell .cd-shell-empty-state-hint {
201
+ margin: 0;
202
+ color: var(--cd-ui-text-leise);
203
+ font-size: 13px;
204
+ font-weight: 400;
205
+ line-height: 1.5;
206
+ }
207
+
208
+ /* The File group's "Open…" as a text link in the hint: the keyboard's way to a file (guide §8). */
209
+ .cd-shell .cd-shell-empty-state-open {
210
+ display: inline;
211
+ margin: 0;
212
+ padding: 0;
213
+ color: var(--cd-ui-link);
214
+ font: inherit;
215
+ text-decoration: none;
216
+ background: transparent;
217
+ border: 0;
218
+ border-radius: 2px;
219
+ cursor: pointer;
220
+ }
221
+
222
+ .cd-shell .cd-shell-empty-state-open:hover {
223
+ text-decoration: underline;
224
+ }
225
+
226
+ .cd-shell .cd-shell-empty-state-open:focus {
227
+ outline: none;
228
+ }
229
+
230
+ .cd-shell .cd-shell-empty-state-open:focus-visible {
231
+ text-decoration: underline;
232
+ outline: 2px solid var(--cd-ui-fokus);
233
+ outline-offset: 2px;
234
+ }
235
+
236
+ .cd-shell-empty-state-open-item {
237
+ white-space: nowrap;
238
+ }
239
+
240
+ .cd-shell .cd-shell-empty-state-key {
241
+ color: var(--cd-ui-text-leise);
242
+ font-family: var(--modeler-shell-font-mono);
243
+ font-size: 13px;
244
+ }
245
+
246
+ /* ---- Start screen: only the card and the legal line compete (guide §8) ---------------------- */
247
+
248
+ .cd-shell[data-cd-shell-host="web"][data-empty-state="shown"]
249
+ :is(
250
+ .cd-shell-menu-trigger,
251
+ .cd-shell-primary,
252
+ .djs-palette,
253
+ .cd-shell-zoom,
254
+ .cd-shell-problems,
255
+ .cd-shell-legend,
256
+ .cd-shell-status,
257
+ .cd-shell-hint,
258
+ .cd-shell-inspector
259
+ ) {
260
+ visibility: hidden;
261
+ }
262
+
263
+ /* ---- Bottom row: the zoom panel above the legal line where they meet (shell rule) ----------- */
264
+
265
+ .cd-shell[data-cd-shell-host="web"][data-zoom-row="raised"] .cd-shell-zoom {
266
+ bottom: calc(var(--cd-shell-rand) + var(--modeler-shell-legal-height, 20px) + var(--cd-shell-luecke));
267
+ }
268
+
269
+ /* Up to 440px the legend stands above the raised zoom panel too: 40 + 40 + 8 = 88. */
270
+ @container cd-shell (max-width: 440px) {
271
+ .cd-shell[data-cd-shell-host="web"][data-zoom-row="raised"] .cd-shell-legend {
272
+ --modeler-shell-legend-min-bottom: calc(
273
+ var(--cd-shell-rand) + var(--modeler-shell-legal-height, 20px) + var(--cd-shell-luecke) +
274
+ var(--cd-shell-zeile) + var(--cd-shell-luecke)
275
+ );
276
+ }
277
+ }
278
+
279
+ /* ---- Drop zone, whole page (guide §9 Drag-over, Drop forbidden) ----------------------------- */
280
+
281
+ .cd-shell-drop-zone {
282
+ position: absolute;
283
+ inset: var(--cd-shell-rand);
284
+ box-sizing: border-box;
285
+ display: grid;
286
+ place-items: center;
287
+ margin: 0;
288
+ padding: var(--cd-shell-rand);
289
+ background: var(--cd-ui-hover);
290
+ border: 2px dashed var(--cd-ui-interaktion);
291
+ border-radius: var(--cd-radius-lg);
292
+ /* Fills the 12px around it, so no island corner shows beside the rounded frame. */
293
+ box-shadow: 0 0 0 var(--cd-shell-rand) var(--cd-ui-grund);
294
+ pointer-events: none;
295
+ user-select: none;
296
+ -webkit-user-select: none;
297
+ }
298
+
299
+ .cd-shell-drop-zone[hidden] {
300
+ display: none;
301
+ }
302
+
303
+ .cd-shell-drop-zone[data-state="forbidden"] {
304
+ background: var(--cd-ui-danger-flaeche);
305
+ border-color: var(--cd-ui-danger);
306
+ }
307
+
308
+ .cd-shell-drop-zone-chip {
309
+ box-sizing: border-box;
310
+ max-width: 100%;
311
+ padding: 7px 16px;
312
+ color: var(--cd-ui-text);
313
+ font-size: 13px;
314
+ font-weight: 600;
315
+ line-height: 20px;
316
+ text-align: center;
317
+ background: var(--cd-ui-flaeche-hoch);
318
+ border: 1px solid var(--cd-ui-linie);
319
+ border-radius: var(--cd-radius-md);
320
+ box-shadow: var(--cd-shadow-2);
321
+ }
322
+
323
+ /* ---- Motion (guide §5.14, §7): no rise under reduced motion (the buttons' lift: ../buttons.css) */
324
+
325
+ @media (prefers-reduced-motion: reduce) {
326
+ .cd-shell .cd-shell-empty-state {
327
+ animation: none;
328
+ }
329
+ }
330
+
331
+ html.cd-shell-reduce-motion .cd-shell .cd-shell-empty-state {
332
+ animation: none;
333
+ }
334
+
335
+ /* ---- High contrast (guide §5.3): opaque glass through the role, no shadows, 3px focus -------- */
336
+
337
+ .cd-shell:is([data-cd-kontrast="hoch"], [data-cd-kontrast="hoch"] *)
338
+ :is(.cd-shell-empty-state, .cd-shell-drop-zone-chip) {
339
+ box-shadow: none;
340
+ }
341
+
342
+ .cd-shell:is([data-cd-kontrast="hoch"], [data-cd-kontrast="hoch"] *)
343
+ :is(.cd-shell-legal a, .cd-shell-empty-state-open) {
344
+ text-decoration: underline;
345
+ }
346
+
347
+ .cd-shell:is([data-cd-kontrast="hoch"], [data-cd-kontrast="hoch"] *)
348
+ :is(.cd-shell-legal a, .cd-shell-empty-state-open):focus-visible {
349
+ outline-width: 3px;
350
+ }
351
+
352
+ /* ---- Forced colours (guide §5.14, §12): nothing disappears ---------------------------------- */
353
+
354
+ @media (forced-colors: active) {
355
+ .cd-shell .cd-shell-empty-state,
356
+ .cd-shell .cd-shell-drop-zone-chip {
357
+ border-color: CanvasText;
358
+ }
359
+
360
+ .cd-shell :is(.cd-shell-legal a, .cd-shell-empty-state-open) {
361
+ text-decoration: underline;
362
+ }
363
+
364
+ .cd-shell :is(.cd-shell-legal a, .cd-shell-empty-state-open):focus-visible {
365
+ outline-color: Highlight;
366
+ }
367
+
368
+ /* Over and forbidden keep their own dashed frame; the chip's words name the state as well. */
369
+ .cd-shell .cd-shell-drop-zone {
370
+ forced-color-adjust: none;
371
+ background: Canvas;
372
+ border-color: Highlight;
373
+ box-shadow: 0 0 0 var(--cd-shell-rand) Canvas;
374
+ }
375
+
376
+ .cd-shell .cd-shell-drop-zone[data-state="forbidden"] {
377
+ border-color: CanvasText;
378
+ border-style: dotted;
379
+ }
380
+
381
+ .cd-shell .cd-shell-drop-zone-chip {
382
+ color: CanvasText;
383
+ background: Canvas;
384
+ box-shadow: none;
385
+ }
386
+ }
@@ -0,0 +1,154 @@
1
+ import {
2
+ ariaKeyShortcuts,
3
+ canZoomIn,
4
+ canZoomOut,
5
+ formatPercent,
6
+ shortcutLabel,
7
+ zoomReadout,
8
+ type ChromeKey,
9
+ type Platform,
10
+ type ShellMessages,
11
+ type ZoomTarget,
12
+ } from "../core/index.js";
13
+ import { createElement, toggleAttribute } from "./element.js";
14
+ import { iconElement, type ShellIconName } from "./icons.js";
15
+ import type { TooltipController } from "./tooltip.js";
16
+
17
+ export interface ZoomPanelContext {
18
+ ownerDocument: Document;
19
+ messages: ShellMessages;
20
+ lang: string;
21
+ platform: Platform;
22
+ tooltip: TooltipController;
23
+ }
24
+
25
+ export interface ZoomPanel {
26
+ readonly element: HTMLElement;
27
+ setTarget(target: ZoomTarget | null): void;
28
+ /** Runs a chrome key on the bound target; zoom out never zooms in (guide §5.8). */
29
+ run(key: ChromeKey): void;
30
+ destroy(): void;
31
+ }
32
+
33
+ export function createZoomPanel(context: ZoomPanelContext): ZoomPanel {
34
+ const { ownerDocument, messages, lang, platform, tooltip } = context;
35
+ let target: ZoomTarget | null = null;
36
+ let unsubscribe: (() => void) | undefined;
37
+ let frame: number | undefined;
38
+ const detachers: (() => void)[] = [];
39
+
40
+ function button(
41
+ zoom: "out" | "actual" | "in" | "fit",
42
+ key: ChromeKey,
43
+ tooltipLabel: string,
44
+ options: { label?: string; icon?: ShellIconName; className?: string },
45
+ ): HTMLButtonElement {
46
+ const element = createElement(ownerDocument, "button", {
47
+ type: "button",
48
+ class: options.className ?? "cd-shell-target",
49
+ "data-zoom": zoom,
50
+ "aria-label": options.label,
51
+ "aria-keyshortcuts": ariaKeyShortcuts(key, platform),
52
+ });
53
+ if (options.icon) element.append(iconElement(options.icon));
54
+ element.addEventListener("click", () => run(key));
55
+ detachers.push(
56
+ tooltip.attach(element, {
57
+ label: tooltipLabel,
58
+ shortcut: shortcutLabel(key, platform, messages.keyNames),
59
+ }),
60
+ );
61
+ return element;
62
+ }
63
+
64
+ const zoomOut = button("out", "zoomOut", messages.zoomOut, { label: messages.zoomOut, icon: "minus" });
65
+ const readout = button("actual", "zoomToActual", messages.zoomToActual(formatPercent(1, lang)), {
66
+ className: "cd-shell-target cd-shell-zoom-readout",
67
+ });
68
+ const zoomIn = button("in", "zoomIn", messages.zoomIn, { label: messages.zoomIn, icon: "plus" });
69
+ const fit = button("fit", "fit", messages.fit, { label: messages.fit, icon: "maximize" });
70
+ const separator = createElement(ownerDocument, "span", {
71
+ class: "cd-shell-zoom-separator",
72
+ "aria-hidden": "true",
73
+ });
74
+
75
+ const element = createElement(
76
+ ownerDocument,
77
+ "div",
78
+ {
79
+ class: "cd-shell-zoom cd-shell-island",
80
+ "data-chrome": "zoom",
81
+ role: "group",
82
+ "aria-label": messages.zoom,
83
+ },
84
+ zoomOut,
85
+ readout,
86
+ zoomIn,
87
+ separator,
88
+ fit,
89
+ );
90
+
91
+ function render(): void {
92
+ frame = undefined;
93
+ const zoom = target?.zoom() ?? 1;
94
+ const state = zoomReadout(zoom, lang, messages);
95
+ if (readout.textContent !== state.text) readout.textContent = state.text;
96
+ readout.setAttribute("aria-label", state.ariaLabel);
97
+ // aria-disabled, not disabled: a button pressed up to the limit keeps the focus (guide §5.8).
98
+ toggleAttribute(zoomOut, "aria-disabled", (!target || !state.canZoomOut) && "true");
99
+ toggleAttribute(zoomIn, "aria-disabled", (!target || !state.canZoomIn) && "true");
100
+ toggleAttribute(fit, "aria-disabled", (!target || !target.canFit()) && "true");
101
+ // The readout is never disabled (guide §5.8); without a target its click does nothing.
102
+ }
103
+
104
+ function schedule(): void {
105
+ if (frame === undefined) frame = requestAnimationFrame(render);
106
+ }
107
+
108
+ function cancelFrame(): void {
109
+ if (frame !== undefined) cancelAnimationFrame(frame);
110
+ frame = undefined;
111
+ }
112
+
113
+ function run(key: ChromeKey): void {
114
+ if (!target) return;
115
+ const zoom = target.zoom();
116
+ switch (key) {
117
+ case "zoomIn":
118
+ if (canZoomIn(zoom)) target.stepZoom(1);
119
+ break;
120
+ case "zoomOut":
121
+ if (canZoomOut(zoom)) target.stepZoom(-1);
122
+ break;
123
+ case "zoomToActual":
124
+ if (zoom !== 1) target.zoomToActual();
125
+ break;
126
+ case "fit":
127
+ if (target.canFit()) target.fit();
128
+ break;
129
+ }
130
+ }
131
+
132
+ render();
133
+
134
+ return {
135
+ element,
136
+ run,
137
+ setTarget(next) {
138
+ unsubscribe?.();
139
+ unsubscribe = undefined;
140
+ cancelFrame();
141
+ target = next;
142
+ if (next) unsubscribe = next.onChange(schedule);
143
+ render();
144
+ },
145
+ destroy() {
146
+ unsubscribe?.();
147
+ unsubscribe = undefined;
148
+ cancelFrame();
149
+ target = null;
150
+ for (const detach of detachers) detach();
151
+ element.remove();
152
+ },
153
+ };
154
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ // "@miragon/modeler-shell": the DOM chrome plus everything DOM-free from "./core".
2
+ export * from "./core/index.js";
3
+ export * from "./dom/index.js";
package/src/shell.css ADDED
@@ -0,0 +1,9 @@
1
+ /*
2
+ * @miragon/modeler-shell/shell.css. Load after the vendored cd-tokens.generated.css and after
3
+ * diagram-js.css (and the renderer's CSS), so the palette skin wins regardless of its specificity.
4
+ */
5
+ @import "./dom/shell-dom.css";
6
+ @import "./dom/buttons.css";
7
+ @import "./dom/web/web.css";
8
+ @import "./dom/feedback/feedback.css";
9
+ @import "./diagram-js/palette.css";