@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,1576 @@
1
+ import {
2
+ SHELL,
3
+ TEXT_FIELD_SELECTOR,
4
+ TOOLTIP_DELAY_MS,
5
+ ariaKeyShortcuts,
6
+ ariaKeyShortcutsFromLabel,
7
+ canZoomIn,
8
+ canZoomOut,
9
+ decideKey,
10
+ decidePaletteRow,
11
+ detectPlatform,
12
+ fitInsets,
13
+ formatNumber,
14
+ formatPercent,
15
+ legendStartsOpen,
16
+ matchChromeKey,
17
+ placeTooltip,
18
+ resolveMessages,
19
+ shortcutLabel,
20
+ zoomReadout
21
+ } from "./chunk-5JSZ4VGA.js";
22
+
23
+ // src/dom/icons.ts
24
+ var ICONS = {
25
+ menu: [
26
+ ["path", { d: "M4 5h16" }],
27
+ ["path", { d: "M4 12h16" }],
28
+ ["path", { d: "M4 19h16" }]
29
+ ],
30
+ minus: [["path", { d: "M5 12h14" }]],
31
+ plus: [
32
+ ["path", { d: "M5 12h14" }],
33
+ ["path", { d: "M12 5v14" }]
34
+ ],
35
+ maximize: [
36
+ ["path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }],
37
+ ["path", { d: "M21 8V5a2 2 0 0 0-2-2h-3" }],
38
+ ["path", { d: "M3 16v3a2 2 0 0 0 2 2h3" }],
39
+ ["path", { d: "M16 21h3a2 2 0 0 0 2-2v-3" }]
40
+ ],
41
+ check: [["path", { d: "M20 6 9 17l-5-5" }]],
42
+ x: [
43
+ ["path", { d: "M18 6 6 18" }],
44
+ ["path", { d: "m6 6 12 12" }]
45
+ ],
46
+ "file-plus": [
47
+ [
48
+ "path",
49
+ {
50
+ d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"
51
+ }
52
+ ],
53
+ ["path", { d: "M14 2v5a1 1 0 0 0 1 1h5" }],
54
+ ["path", { d: "M9 15h6" }],
55
+ ["path", { d: "M12 18v-6" }]
56
+ ],
57
+ "folder-open": [
58
+ [
59
+ "path",
60
+ {
61
+ d: "m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2"
62
+ }
63
+ ]
64
+ ],
65
+ import: [
66
+ ["path", { d: "M12 3v12" }],
67
+ ["path", { d: "m8 11 4 4 4-4" }],
68
+ ["path", { d: "M8 5H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-4" }]
69
+ ],
70
+ "file-image": [
71
+ [
72
+ "path",
73
+ {
74
+ d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"
75
+ }
76
+ ],
77
+ ["path", { d: "M14 2v5a1 1 0 0 0 1 1h5" }],
78
+ ["circle", { cx: "10", cy: "12", r: "2" }],
79
+ ["path", { d: "m20 17-1.296-1.296a2.41 2.41 0 0 0-3.408 0L9 22" }]
80
+ ],
81
+ "file-code-corner": [
82
+ [
83
+ "path",
84
+ {
85
+ d: "M4 12.15V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2h-3.35"
86
+ }
87
+ ],
88
+ ["path", { d: "M14 2v5a1 1 0 0 0 1 1h5" }],
89
+ ["path", { d: "m5 16-3 3 3 3" }],
90
+ ["path", { d: "m9 22 3-3-3-3" }]
91
+ ],
92
+ keyboard: [
93
+ ["path", { d: "M10 8h.01" }],
94
+ ["path", { d: "M12 12h.01" }],
95
+ ["path", { d: "M14 8h.01" }],
96
+ ["path", { d: "M16 12h.01" }],
97
+ ["path", { d: "M18 8h.01" }],
98
+ ["path", { d: "M6 8h.01" }],
99
+ ["path", { d: "M7 16h10" }],
100
+ ["path", { d: "M8 12h.01" }],
101
+ ["rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" }]
102
+ ],
103
+ "share-2": [
104
+ ["circle", { cx: "18", cy: "5", r: "3" }],
105
+ ["circle", { cx: "6", cy: "12", r: "3" }],
106
+ ["circle", { cx: "18", cy: "19", r: "3" }],
107
+ ["line", { x1: "8.59", x2: "15.42", y1: "13.51", y2: "17.49" }],
108
+ ["line", { x1: "15.41", x2: "8.59", y1: "6.51", y2: "10.49" }]
109
+ ],
110
+ "circle-x": [
111
+ ["circle", { cx: "12", cy: "12", r: "10" }],
112
+ ["path", { d: "m15 9-6 6" }],
113
+ ["path", { d: "m9 9 6 6" }]
114
+ ],
115
+ "triangle-alert": [
116
+ ["path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" }],
117
+ ["path", { d: "M12 9v4" }],
118
+ ["path", { d: "M12 17h.01" }]
119
+ ],
120
+ "square-dashed-mouse-pointer": [
121
+ [
122
+ "path",
123
+ {
124
+ d: "M12.034 12.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.943l-3.444 1.068a1 1 0 0 0-.66.66l-1.067 3.443a.5.5 0 0 1-.943.033z"
125
+ }
126
+ ],
127
+ ["path", { d: "M5 3a2 2 0 0 0-2 2" }],
128
+ ["path", { d: "M19 3a2 2 0 0 1 2 2" }],
129
+ ["path", { d: "M5 21a2 2 0 0 1-2-2" }],
130
+ ["path", { d: "M9 3h1" }],
131
+ ["path", { d: "M9 21h2" }],
132
+ ["path", { d: "M14 3h1" }],
133
+ ["path", { d: "M3 9v1" }],
134
+ ["path", { d: "M21 9v2" }],
135
+ ["path", { d: "M3 14v1" }]
136
+ ],
137
+ hand: [
138
+ ["path", { d: "M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2" }],
139
+ ["path", { d: "M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2" }],
140
+ ["path", { d: "M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8" }],
141
+ [
142
+ "path",
143
+ {
144
+ d: "M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15"
145
+ }
146
+ ]
147
+ ],
148
+ "line-segment": [
149
+ ["circle", { cx: "19", cy: "5", r: "2" }],
150
+ ["circle", { cx: "5", cy: "19", r: "2" }],
151
+ ["path", { d: "M6.5 17.5 17.5 6.5" }]
152
+ ],
153
+ "unfold-horizontal": [
154
+ ["path", { d: "M16 12h6" }],
155
+ ["path", { d: "M8 12H2" }],
156
+ ["path", { d: "M12 2v2" }],
157
+ ["path", { d: "M12 8v2" }],
158
+ ["path", { d: "M12 14v2" }],
159
+ ["path", { d: "M12 20v2" }],
160
+ ["path", { d: "m19 15 3-3-3-3" }],
161
+ ["path", { d: "m5 9-3 3 3 3" }]
162
+ ],
163
+ workflow: [
164
+ ["rect", { width: "8", height: "8", x: "3", y: "3", rx: "2" }],
165
+ ["path", { d: "M7 11v4a2 2 0 0 0 2 2h4" }],
166
+ ["rect", { width: "8", height: "8", x: "13", y: "13", rx: "2" }]
167
+ ]
168
+ };
169
+ var PALETTE_TOOL_ICON = { size: 20, strokeWidth: 2 };
170
+ var DEFAULT_SIZE = 16;
171
+ var DEFAULT_STROKE_WIDTH = 2.5;
172
+ var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
173
+ var ICON_ELEMENTS = /* @__PURE__ */ new Set([
174
+ "path",
175
+ "circle",
176
+ "ellipse",
177
+ "line",
178
+ "polyline",
179
+ "polygon",
180
+ "rect"
181
+ ]);
182
+ var GEOMETRY_ATTRIBUTES = /* @__PURE__ */ new Set([
183
+ "d",
184
+ "cx",
185
+ "cy",
186
+ "r",
187
+ "rx",
188
+ "ry",
189
+ "x",
190
+ "y",
191
+ "x1",
192
+ "y1",
193
+ "x2",
194
+ "y2",
195
+ "width",
196
+ "height",
197
+ "points"
198
+ ]);
199
+ function positiveOr(value, fallback) {
200
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
201
+ }
202
+ function iconNodeOf(icon) {
203
+ if (typeof icon !== "string") return icon;
204
+ return Object.prototype.hasOwnProperty.call(ICONS, icon) ? ICONS[icon] : [];
205
+ }
206
+ function sanitize(icon, options = {}) {
207
+ const size = String(positiveOr(options.size, DEFAULT_SIZE));
208
+ const root = [];
209
+ if (options.className) root.push(["class", options.className]);
210
+ root.push(
211
+ ["width", size],
212
+ ["height", size],
213
+ ["viewBox", "0 0 24 24"],
214
+ ["fill", "none"],
215
+ ["stroke", "currentColor"],
216
+ ["stroke-width", String(positiveOr(options.strokeWidth, DEFAULT_STROKE_WIDTH))],
217
+ ["stroke-linecap", "round"],
218
+ ["stroke-linejoin", "round"],
219
+ ["aria-hidden", "true"],
220
+ ["focusable", "false"]
221
+ );
222
+ const children = [];
223
+ for (const node of iconNodeOf(icon)) {
224
+ const [tag, attributes] = node;
225
+ if (!ICON_ELEMENTS.has(tag)) continue;
226
+ const kept = Object.entries(attributes).filter(([name]) => GEOMETRY_ATTRIBUTES.has(name)).map(([name, value]) => [name, String(value)]);
227
+ children.push([tag, kept]);
228
+ }
229
+ return { root, children };
230
+ }
231
+ function escapeAttribute(value) {
232
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
233
+ }
234
+ function attributeList(attributes) {
235
+ return attributes.map(([name, value]) => ` ${name}="${escapeAttribute(value)}"`).join("");
236
+ }
237
+ function iconMarkup(icon, options) {
238
+ const { root, children } = sanitize(icon, options);
239
+ const content = children.map(([tag, attributes]) => `<${tag}${attributeList(attributes)}/>`).join("");
240
+ return `<svg xmlns="${SVG_NAMESPACE}"${attributeList(root)}>${content}</svg>`;
241
+ }
242
+ function iconElement(icon, options) {
243
+ const { root, children } = sanitize(icon, options);
244
+ const svg = document.createElementNS(SVG_NAMESPACE, "svg");
245
+ for (const [name, value] of root) svg.setAttribute(name, value);
246
+ for (const [tag, attributes] of children) {
247
+ const child = document.createElementNS(SVG_NAMESPACE, tag);
248
+ for (const [name, value] of attributes) child.setAttribute(name, value);
249
+ svg.append(child);
250
+ }
251
+ return svg;
252
+ }
253
+
254
+ // src/dom/element.ts
255
+ function createElement(ownerDocument, tag, attributes = {}, ...children) {
256
+ const element = ownerDocument.createElement(tag);
257
+ for (const [name, value] of Object.entries(attributes)) {
258
+ if (value !== void 0) element.setAttribute(name, value);
259
+ }
260
+ for (const child of children) {
261
+ if (child === void 0 || child === null || child === false) continue;
262
+ element.append(child);
263
+ }
264
+ return element;
265
+ }
266
+ function toggleAttribute(element, name, value) {
267
+ if (value === null || value === false) element.removeAttribute(name);
268
+ else element.setAttribute(name, value);
269
+ }
270
+ function boxRelativeTo(element, root) {
271
+ const box = element.getBoundingClientRect();
272
+ const origin = root.getBoundingClientRect();
273
+ return {
274
+ x: box.left - origin.left - root.clientLeft,
275
+ y: box.top - origin.top - root.clientTop,
276
+ width: box.width,
277
+ height: box.height
278
+ };
279
+ }
280
+ function accessibleName(control) {
281
+ return (control.getAttribute("aria-label") ?? control.textContent ?? "").trim();
282
+ }
283
+ function hasVisibleFocus(element) {
284
+ try {
285
+ return element.matches(":focus-visible");
286
+ } catch {
287
+ return true;
288
+ }
289
+ }
290
+
291
+ // src/dom/hint.ts
292
+ function createEmptyHint(ownerDocument, messages, options) {
293
+ const { onInsertExample } = options;
294
+ const action = onInsertExample ? createElement(
295
+ ownerDocument,
296
+ "button",
297
+ { type: "button", class: "cd-shell-hint-action" },
298
+ messages.insertExample
299
+ ) : void 0;
300
+ action?.addEventListener("click", () => onInsertExample?.());
301
+ const element = createElement(
302
+ ownerDocument,
303
+ "div",
304
+ { class: "cd-shell-hint" },
305
+ createElement(ownerDocument, "p", { class: "cd-shell-hint-text" }, messages.emptyHint),
306
+ action
307
+ );
308
+ element.hidden = true;
309
+ return {
310
+ element,
311
+ isShown: () => !element.hidden,
312
+ setShown(shown) {
313
+ element.hidden = !shown;
314
+ },
315
+ place({ top, right, bottom, left }) {
316
+ element.style.inset = `${top}px ${right}px ${bottom}px ${left}px`;
317
+ },
318
+ destroy() {
319
+ element.remove();
320
+ }
321
+ };
322
+ }
323
+
324
+ // src/dom/inspector.ts
325
+ function createInspector(ownerDocument, options, onChange) {
326
+ const element = createElement(ownerDocument, "aside", {
327
+ class: "cd-shell-inspector",
328
+ "data-chrome": "inspector",
329
+ "aria-label": options.label
330
+ });
331
+ element.hidden = true;
332
+ function setOpen(open) {
333
+ if (open === !element.hidden) return;
334
+ element.hidden = !open;
335
+ onChange(open);
336
+ }
337
+ return {
338
+ element,
339
+ isOpen: () => !element.hidden,
340
+ open: () => setOpen(true),
341
+ close: () => setOpen(false),
342
+ destroy() {
343
+ element.remove();
344
+ }
345
+ };
346
+ }
347
+
348
+ // src/dom/keys.ts
349
+ function isTextField(target) {
350
+ if (!target || typeof target.closest !== "function") return false;
351
+ const element = target;
352
+ return element.isContentEditable === true || element.closest(TEXT_FIELD_SELECTOR) !== null;
353
+ }
354
+ function isShown(element) {
355
+ if (element.localName === "dialog" && !element.hasAttribute("open")) return false;
356
+ if (element.closest("[hidden]")) return false;
357
+ return typeof element.checkVisibility !== "function" || element.checkVisibility({ visibilityProperty: true });
358
+ }
359
+ function isModal(element) {
360
+ if (element.getAttribute("aria-modal") === "true") return true;
361
+ try {
362
+ return element.matches(":modal");
363
+ } catch {
364
+ return false;
365
+ }
366
+ }
367
+ function openDialogs(scope) {
368
+ return [...scope.querySelectorAll('[role="dialog"], [role="alertdialog"], dialog')].filter(isShown);
369
+ }
370
+ function createKeyListener(context) {
371
+ return (event) => {
372
+ if (event.key === "Escape") context.hideTooltip();
373
+ const dialogs = openDialogs(context.dialogScope);
374
+ const decision = decideKey(event, {
375
+ host: context.host,
376
+ inTextField: isTextField(event.target),
377
+ menuOpen: context.menuOpen(),
378
+ dialogOpen: dialogs.length > 0,
379
+ modalOpen: dialogs.some(isModal),
380
+ defaultPrevented: event.defaultPrevented
381
+ });
382
+ if (decision.preventDefault) event.preventDefault();
383
+ if (decision.stopPropagation) event.stopPropagation();
384
+ if (decision.action === "closeMenu") context.closeMenu();
385
+ else if (decision.action) context.run(decision.action);
386
+ };
387
+ }
388
+
389
+ // src/dom/ledger.ts
390
+ var Ledger = class {
391
+ undo = [];
392
+ originalAttributes = /* @__PURE__ */ new Map();
393
+ closed = false;
394
+ get isClosed() {
395
+ return this.closed;
396
+ }
397
+ /** Runs `task` on `close()`, in reverse order of registration. */
398
+ defer(task) {
399
+ this.undo.push(task);
400
+ }
401
+ /** Adds `className` unless present; `close()` removes it only if this ledger added it. */
402
+ addClass(element, className) {
403
+ if (element.classList.contains(className)) return;
404
+ const hadClassAttribute = element.hasAttribute("class");
405
+ element.classList.add(className);
406
+ this.defer(() => {
407
+ element.classList.remove(className);
408
+ if (!hadClassAttribute && element.classList.length === 0) element.removeAttribute("class");
409
+ });
410
+ }
411
+ /** Sets (string) or removes (`null`) an attribute; `close()` restores the value before the first change. */
412
+ attribute(element, name, value) {
413
+ let originals = this.originalAttributes.get(element);
414
+ if (!originals) {
415
+ originals = /* @__PURE__ */ new Map();
416
+ this.originalAttributes.set(element, originals);
417
+ }
418
+ if (!originals.has(name)) {
419
+ originals.set(name, element.getAttribute(name));
420
+ this.defer(() => {
421
+ const original = originals.get(name) ?? null;
422
+ if (original === null) element.removeAttribute(name);
423
+ else element.setAttribute(name, original);
424
+ });
425
+ }
426
+ if (value === null) element.removeAttribute(name);
427
+ else element.setAttribute(name, value);
428
+ }
429
+ listen(target, type, listener, options) {
430
+ const handler = listener;
431
+ target.addEventListener(type, handler, options);
432
+ this.defer(() => target.removeEventListener(type, handler, options));
433
+ }
434
+ /** Runs every undo task once; later calls are no-ops. */
435
+ close() {
436
+ if (this.closed) return;
437
+ this.closed = true;
438
+ for (const task of this.undo.reverse()) {
439
+ try {
440
+ task();
441
+ } catch {
442
+ }
443
+ }
444
+ this.undo.length = 0;
445
+ this.originalAttributes.clear();
446
+ }
447
+ };
448
+
449
+ // src/dom/legend.ts
450
+ function createLegend(context) {
451
+ const { ownerDocument, messages, tooltip, titleId, options } = context;
452
+ const close = createElement(ownerDocument, "button", {
453
+ type: "button",
454
+ class: "cd-shell-legend-close",
455
+ "aria-label": messages.closeLegend
456
+ });
457
+ close.append(iconElement("x"));
458
+ const body = createElement(ownerDocument, "div", { class: "cd-shell-legend-body" });
459
+ const element = createElement(
460
+ ownerDocument,
461
+ "section",
462
+ { class: "cd-shell-legend cd-shell-island", "data-chrome": "legend", "aria-labelledby": titleId },
463
+ createElement(
464
+ ownerDocument,
465
+ "div",
466
+ { class: "cd-shell-legend-header" },
467
+ createElement(ownerDocument, "div", { class: "cd-shell-legend-title", id: titleId }, messages.legend),
468
+ close
469
+ ),
470
+ body
471
+ );
472
+ element.hidden = true;
473
+ options.render(body);
474
+ let open = false;
475
+ let started = false;
476
+ const detachTooltip = tooltip.attach(close, { label: messages.closeLegend });
477
+ function apply(next) {
478
+ started = true;
479
+ if (next === open) return false;
480
+ open = next;
481
+ element.hidden = !open;
482
+ if (!open && element.contains(ownerDocument.activeElement)) context.focusAfterClose();
483
+ context.onChange(open);
484
+ return true;
485
+ }
486
+ function setOpen(next) {
487
+ if (apply(next)) options.onToggle?.(next);
488
+ }
489
+ close.addEventListener("click", () => setOpen(false));
490
+ return {
491
+ element,
492
+ body,
493
+ isOpen: () => open,
494
+ setOpen,
495
+ toggle: () => setOpen(!open),
496
+ start(initial) {
497
+ if (!started) apply(initial);
498
+ },
499
+ isStarted: () => started,
500
+ destroy() {
501
+ detachTooltip();
502
+ element.remove();
503
+ }
504
+ };
505
+ }
506
+
507
+ // src/dom/menu.ts
508
+ var GROUP_ORDER = ["model", "export", "help"];
509
+ function isPrintable(event) {
510
+ return event.key.length === 1 && /\S/.test(event.key) && !event.ctrlKey && !event.metaKey && !event.altKey;
511
+ }
512
+ function createMenu(context) {
513
+ const { root, layer, id, messages, tooltip } = context;
514
+ const ownerDocument = root.ownerDocument;
515
+ let groups = context.groups;
516
+ let items = [];
517
+ let destroyed = false;
518
+ const trigger = createElement(ownerDocument, "button", {
519
+ type: "button",
520
+ class: "cd-shell-menu-trigger cd-shell-island",
521
+ "data-chrome": "menu",
522
+ "aria-label": messages.menu,
523
+ "aria-haspopup": "menu",
524
+ "aria-expanded": "false",
525
+ "aria-controls": id
526
+ });
527
+ trigger.append(iconElement("menu"));
528
+ const dropdown = createElement(ownerDocument, "div", {
529
+ class: "cd-shell-menu",
530
+ id,
531
+ role: "menu",
532
+ "aria-label": messages.menu
533
+ });
534
+ dropdown.hidden = true;
535
+ layer.prepend(dropdown);
536
+ const isOpen = () => !dropdown.hidden;
537
+ function entriesOf(name) {
538
+ const own = groups[name] ?? [];
539
+ return name === "model" ? [...own, ...context.shellModelEntries()] : own;
540
+ }
541
+ function hasEntries() {
542
+ return GROUP_ORDER.some((name) => entriesOf(name).length > 0);
543
+ }
544
+ function updateTriggerVisibility() {
545
+ trigger.hidden = !hasEntries();
546
+ if (trigger.hidden) close();
547
+ }
548
+ function isDisabled(entry, group) {
549
+ if (group === "model" && context.isReadOnly() && entry.keepEnabledWhenReadOnly !== true) return true;
550
+ return entry.disabled?.() ?? false;
551
+ }
552
+ function itemElement(role, label, options) {
553
+ const iconSlot = createElement(ownerDocument, "span", { class: "cd-shell-menu-item-icon" });
554
+ if (options.icon) iconSlot.append(iconElement(options.icon));
555
+ const element = createElement(
556
+ ownerDocument,
557
+ "button",
558
+ { type: "button", class: "cd-shell-menu-item", role, tabindex: "-1" },
559
+ iconSlot,
560
+ createElement(ownerDocument, "span", { class: "cd-shell-menu-item-label" }, label),
561
+ options.end && createElement(ownerDocument, "span", { class: "cd-shell-menu-item-end" }, options.end)
562
+ );
563
+ toggleAttribute(element, "aria-disabled", options.disabled && "true");
564
+ if (options.checked !== void 0) element.setAttribute("aria-checked", String(options.checked));
565
+ return element;
566
+ }
567
+ function checkMark() {
568
+ return iconElement("check", { className: "cd-shell-menu-check" });
569
+ }
570
+ function renderEntry(entry, group) {
571
+ const disabled = isDisabled(entry, group);
572
+ if (entry.kind === "checkbox") {
573
+ const checked = entry.checked();
574
+ const element2 = itemElement("menuitemcheckbox", entry.label, {
575
+ icon: entry.icon,
576
+ end: checkMark(),
577
+ disabled,
578
+ checked
579
+ });
580
+ element2.dataset.menuId = entry.id;
581
+ element2.dataset.menuGroup = group;
582
+ return {
583
+ key: entry.id,
584
+ element: element2,
585
+ label: entry.label,
586
+ disabled,
587
+ activate() {
588
+ entry.onToggle(!entry.checked());
589
+ refresh();
590
+ }
591
+ };
592
+ }
593
+ const shortcut = entry.kind === "action" && entry.shortcut ? createElement(ownerDocument, "span", { class: "cd-shell-menu-shortcut" }, entry.shortcut) : void 0;
594
+ const element = itemElement("menuitem", entry.label, { icon: entry.icon, end: shortcut, disabled });
595
+ element.dataset.menuId = entry.id;
596
+ element.dataset.menuGroup = group;
597
+ if (entry.kind === "action") {
598
+ const keyShortcuts = entry.ariaKeyShortcuts ?? (entry.shortcut ? ariaKeyShortcutsFromLabel(entry.shortcut) : void 0);
599
+ toggleAttribute(element, "aria-keyshortcuts", keyShortcuts || null);
600
+ }
601
+ if (entry.kind === "dialog") element.setAttribute("aria-haspopup", "dialog");
602
+ if (entry.kind === "action" && entry.danger) element.dataset.danger = "";
603
+ return {
604
+ key: entry.id,
605
+ element,
606
+ label: entry.label,
607
+ disabled,
608
+ activate() {
609
+ close({ restoreFocus: true });
610
+ entry.onSelect();
611
+ }
612
+ };
613
+ }
614
+ function renderRadioGroup(entry, group) {
615
+ const disabled = isDisabled(entry, group);
616
+ const value = entry.value();
617
+ const container = createElement(
618
+ ownerDocument,
619
+ "div",
620
+ {
621
+ class: "cd-shell-menu-group",
622
+ role: "group",
623
+ "aria-label": entry.label,
624
+ "data-menu-id": entry.id,
625
+ "data-menu-group": group
626
+ },
627
+ // The group's aria-label carries the heading; a menu holds no loose text.
628
+ createElement(
629
+ ownerDocument,
630
+ "div",
631
+ { class: "cd-shell-menu-heading", "aria-hidden": "true" },
632
+ entry.label
633
+ )
634
+ );
635
+ const radioItems = entry.options.map((option) => {
636
+ const element = itemElement("menuitemradio", option.label, {
637
+ end: checkMark(),
638
+ disabled,
639
+ checked: option.value === value
640
+ });
641
+ element.dataset.menuValue = option.value;
642
+ container.append(element);
643
+ return {
644
+ key: `${entry.id}\0${option.value}`,
645
+ element,
646
+ label: option.label,
647
+ disabled,
648
+ activate() {
649
+ if (entry.value() !== option.value) entry.onSelect(option.value);
650
+ refresh();
651
+ }
652
+ };
653
+ });
654
+ return { element: container, items: radioItems };
655
+ }
656
+ function render() {
657
+ const focusedKey = items.find((item) => item.element === ownerDocument.activeElement)?.key;
658
+ const nodes = [];
659
+ const rendered = [];
660
+ for (const name of GROUP_ORDER) {
661
+ const entries = entriesOf(name);
662
+ if (entries.length === 0) continue;
663
+ if (nodes.length > 0) {
664
+ nodes.push(
665
+ createElement(ownerDocument, "div", { class: "cd-shell-menu-separator", role: "separator" })
666
+ );
667
+ }
668
+ for (const entry of entries) {
669
+ if (entry.kind === "radio") {
670
+ const radio = renderRadioGroup(entry, name);
671
+ nodes.push(radio.element);
672
+ rendered.push(...radio.items);
673
+ } else {
674
+ const item = renderEntry(entry, name);
675
+ nodes.push(item.element);
676
+ rendered.push(item);
677
+ }
678
+ }
679
+ }
680
+ for (const item of rendered) {
681
+ item.element.addEventListener("click", () => {
682
+ if (!item.disabled) item.activate();
683
+ });
684
+ }
685
+ dropdown.replaceChildren(...nodes);
686
+ items = rendered;
687
+ if (focusedKey !== void 0 && isOpen()) {
688
+ (items.find((item) => item.key === focusedKey) ?? items[0])?.element.focus();
689
+ }
690
+ }
691
+ function focusAt(index) {
692
+ if (items.length === 0) return;
693
+ items[(index + items.length) % items.length]?.element.focus();
694
+ }
695
+ function currentIndex() {
696
+ return items.findIndex((item) => item.element === ownerDocument.activeElement);
697
+ }
698
+ function typeAhead(character) {
699
+ const start = currentIndex();
700
+ const lower = character.toLowerCase();
701
+ for (let step = 1; step <= items.length; step++) {
702
+ const index = (Math.max(start, -1) + step) % items.length;
703
+ if (items[index]?.label.toLowerCase().startsWith(lower)) {
704
+ focusAt(index);
705
+ return;
706
+ }
707
+ }
708
+ }
709
+ const onOutsidePointerDown = (event) => {
710
+ const target = event.target;
711
+ if (target && (dropdown.contains(target) || trigger.contains(target))) return;
712
+ close();
713
+ };
714
+ const onWindowBlur = () => close();
715
+ function open(focus = "first") {
716
+ if (destroyed || !hasEntries()) return;
717
+ tooltip.hide();
718
+ const wasOpen = isOpen();
719
+ render();
720
+ if (!wasOpen) {
721
+ dropdown.hidden = false;
722
+ trigger.setAttribute("aria-expanded", "true");
723
+ ownerDocument.addEventListener("pointerdown", onOutsidePointerDown, true);
724
+ ownerDocument.defaultView?.addEventListener("blur", onWindowBlur);
725
+ }
726
+ focusAt(focus === "last" ? items.length - 1 : 0);
727
+ }
728
+ function close(options = {}) {
729
+ if (!isOpen()) return;
730
+ dropdown.hidden = true;
731
+ trigger.setAttribute("aria-expanded", "false");
732
+ ownerDocument.removeEventListener("pointerdown", onOutsidePointerDown, true);
733
+ ownerDocument.defaultView?.removeEventListener("blur", onWindowBlur);
734
+ if (options.restoreFocus && !trigger.hidden) trigger.focus();
735
+ }
736
+ function refresh() {
737
+ if (isOpen()) render();
738
+ }
739
+ trigger.addEventListener("click", () => {
740
+ if (isOpen()) close();
741
+ else open("first");
742
+ });
743
+ function consume(event) {
744
+ event.preventDefault();
745
+ event.stopPropagation();
746
+ }
747
+ trigger.addEventListener("keydown", (event) => {
748
+ if (event.key === "Enter" || event.key === " " || event.key === "ArrowDown") {
749
+ consume(event);
750
+ open("first");
751
+ } else if (event.key === "ArrowUp") {
752
+ consume(event);
753
+ open("last");
754
+ }
755
+ });
756
+ dropdown.addEventListener("keydown", (event) => {
757
+ if (matchChromeKey(event)) return;
758
+ switch (event.key) {
759
+ case "ArrowDown":
760
+ focusAt(currentIndex() + 1);
761
+ break;
762
+ case "ArrowUp": {
763
+ const index = currentIndex();
764
+ focusAt(index < 0 ? items.length - 1 : index - 1);
765
+ break;
766
+ }
767
+ case "Home":
768
+ focusAt(0);
769
+ break;
770
+ case "End":
771
+ focusAt(items.length - 1);
772
+ break;
773
+ case "Tab":
774
+ close({ restoreFocus: true });
775
+ return;
776
+ case "Escape":
777
+ return;
778
+ case "Enter":
779
+ case " ":
780
+ case "Delete":
781
+ case "Backspace":
782
+ event.stopPropagation();
783
+ return;
784
+ default:
785
+ if (!isPrintable(event)) return;
786
+ typeAhead(event.key);
787
+ }
788
+ consume(event);
789
+ });
790
+ updateTriggerVisibility();
791
+ return {
792
+ trigger,
793
+ dropdown,
794
+ isOpen,
795
+ open,
796
+ close,
797
+ refresh,
798
+ setGroups(next) {
799
+ groups = next;
800
+ updateTriggerVisibility();
801
+ refresh();
802
+ },
803
+ destroy() {
804
+ if (destroyed) return;
805
+ close();
806
+ destroyed = true;
807
+ trigger.remove();
808
+ dropdown.remove();
809
+ }
810
+ };
811
+ }
812
+
813
+ // src/dom/problems.ts
814
+ function createProblemsPill(context) {
815
+ const { ownerDocument, messages, lang, tooltip, options } = context;
816
+ const icon = createElement(ownerDocument, "span", { class: "cd-shell-problems-icon" });
817
+ const text = createElement(ownerDocument, "span", { class: "cd-shell-problems-text" });
818
+ const element = createElement(
819
+ ownerDocument,
820
+ "button",
821
+ { type: "button", class: "cd-shell-problems cd-shell-island", "data-chrome": "problems" },
822
+ icon,
823
+ text
824
+ );
825
+ element.hidden = true;
826
+ element.addEventListener("click", () => options.onActivate());
827
+ const detachTooltip = context.showTooltip ? tooltip.attach(element, { label: messages.showProblems }) : void 0;
828
+ let severity;
829
+ return {
830
+ element,
831
+ isShown: () => !element.hidden,
832
+ set({ count, errors }) {
833
+ const shown = count > 0;
834
+ element.hidden = !shown;
835
+ if (!shown) {
836
+ tooltip.hide();
837
+ return;
838
+ }
839
+ const formattedCount = formatNumber(count, lang);
840
+ text.textContent = messages.problems(count, formattedCount);
841
+ element.setAttribute(
842
+ "aria-label",
843
+ messages.problemsLabel(count, errors, formattedCount, formatNumber(errors, lang))
844
+ );
845
+ const next = errors > 0 ? "danger" : "warning";
846
+ if (next !== severity) {
847
+ severity = next;
848
+ element.dataset.severity = next;
849
+ icon.replaceChildren(iconElement(next === "danger" ? "circle-x" : "triangle-alert"));
850
+ }
851
+ },
852
+ destroy() {
853
+ detachTooltip?.();
854
+ element.remove();
855
+ }
856
+ };
857
+ }
858
+
859
+ // src/dom/status.ts
860
+ function createStatusBox(ownerDocument) {
861
+ const icon = createElement(ownerDocument, "span", { class: "cd-shell-status-icon" });
862
+ const text = createElement(ownerDocument, "p", { class: "cd-shell-status-text" });
863
+ const element = createElement(
864
+ ownerDocument,
865
+ "div",
866
+ { class: "cd-shell-status", "data-chrome": "status" },
867
+ icon,
868
+ text
869
+ );
870
+ element.hidden = true;
871
+ let pending;
872
+ function cancelPending() {
873
+ if (pending) clearTimeout(pending.timer);
874
+ pending = void 0;
875
+ }
876
+ function show(status) {
877
+ pending = void 0;
878
+ if (element.dataset.severity !== status.severity) {
879
+ element.dataset.severity = status.severity;
880
+ element.setAttribute("role", status.severity === "danger" ? "alert" : "status");
881
+ icon.replaceChildren(iconElement(status.severity === "danger" ? "circle-x" : "triangle-alert"));
882
+ }
883
+ if (text.textContent !== status.text) text.textContent = status.text;
884
+ element.hidden = false;
885
+ }
886
+ return {
887
+ element,
888
+ set(status, delayMs) {
889
+ if (status === null) {
890
+ cancelPending();
891
+ element.hidden = true;
892
+ return;
893
+ }
894
+ if (!element.hidden) {
895
+ show(status);
896
+ return;
897
+ }
898
+ if (pending) {
899
+ pending.status = status;
900
+ return;
901
+ }
902
+ if (delayMs > 0) {
903
+ const entry = { status, timer: setTimeout(() => show(entry.status), delayMs) };
904
+ pending = entry;
905
+ } else {
906
+ show(status);
907
+ }
908
+ },
909
+ destroy() {
910
+ cancelPending();
911
+ element.remove();
912
+ }
913
+ };
914
+ }
915
+
916
+ // src/dom/tooltip.ts
917
+ function createTooltip({ root, layer, id }) {
918
+ const ownerDocument = root.ownerDocument;
919
+ const label = createElement(ownerDocument, "span", { class: "cd-shell-tooltip-label" });
920
+ const shortcut = createElement(ownerDocument, "kbd", { class: "cd-shell-kbd" });
921
+ const element = createElement(ownerDocument, "div", { class: "cd-shell-tooltip", role: "tooltip", id });
922
+ element.hidden = true;
923
+ element.append(label, shortcut);
924
+ layer.append(element);
925
+ let shown;
926
+ let pending;
927
+ let warmUntil = 0;
928
+ let pressed;
929
+ const detachers = /* @__PURE__ */ new Set();
930
+ function cancelPending() {
931
+ if (pending) clearTimeout(pending.timer);
932
+ pending = void 0;
933
+ }
934
+ function resolve(source) {
935
+ return typeof source === "function" ? source() : source;
936
+ }
937
+ function place(control) {
938
+ const island = control.closest("[data-chrome]") ?? control;
939
+ const point = placeTooltip({
940
+ anchor: boxRelativeTo(control, root),
941
+ island: boxRelativeTo(island, root),
942
+ tooltip: { width: element.offsetWidth, height: element.offsetHeight },
943
+ container: { width: root.clientWidth, height: root.clientHeight }
944
+ });
945
+ element.style.left = `${point.x}px`;
946
+ element.style.top = `${point.y}px`;
947
+ }
948
+ function hide() {
949
+ cancelPending();
950
+ if (!shown) return;
951
+ const { control, describedBy } = shown;
952
+ shown = void 0;
953
+ element.hidden = true;
954
+ if (describedBy === null) control.removeAttribute("aria-describedby");
955
+ else control.setAttribute("aria-describedby", describedBy);
956
+ }
957
+ function show(control, source) {
958
+ cancelPending();
959
+ const content = resolve(source);
960
+ if (!content || !control.isConnected) {
961
+ if (shown?.control === control) hide();
962
+ return;
963
+ }
964
+ if (shown && shown.control !== control) hide();
965
+ label.textContent = content.label;
966
+ shortcut.textContent = content.shortcut ?? "";
967
+ shortcut.hidden = !content.shortcut;
968
+ element.hidden = false;
969
+ if (!shown) {
970
+ const describedBy = control.getAttribute("aria-describedby");
971
+ shown = { control, describedBy };
972
+ const saysMore = !accessibleName(control).includes(content.label) || Boolean(content.shortcut) && !control.hasAttribute("aria-keyshortcuts");
973
+ if (saysMore) {
974
+ control.setAttribute("aria-describedby", [describedBy, id].filter(Boolean).join(" "));
975
+ }
976
+ }
977
+ place(control);
978
+ }
979
+ function onEnter(control, source, event) {
980
+ if (event.pointerType === "touch" || pressed === control) return;
981
+ if (shown || Date.now() < warmUntil) {
982
+ show(control, source);
983
+ return;
984
+ }
985
+ cancelPending();
986
+ pending = { control, timer: setTimeout(() => show(control, source), TOOLTIP_DELAY_MS) };
987
+ }
988
+ function onLeave(control) {
989
+ if (pressed === control) pressed = void 0;
990
+ if (pending?.control === control) cancelPending();
991
+ if (shown?.control === control) {
992
+ hide();
993
+ warmUntil = Date.now() + TOOLTIP_DELAY_MS;
994
+ }
995
+ }
996
+ function onFocus(control, source) {
997
+ if (hasVisibleFocus(control)) show(control, source);
998
+ }
999
+ function onBlur(control) {
1000
+ if (pending?.control === control) cancelPending();
1001
+ if (shown?.control === control) hide();
1002
+ }
1003
+ function onPress(control, event) {
1004
+ if (event.type === "pointerdown") pressed = control;
1005
+ if (pending?.control === control) cancelPending();
1006
+ if (shown?.control === control) hide();
1007
+ }
1008
+ function attach(control, source) {
1009
+ const enter = (event) => onEnter(control, source, event);
1010
+ const leave = () => onLeave(control);
1011
+ const focus = () => onFocus(control, source);
1012
+ const blur = () => onBlur(control);
1013
+ const press = (event) => onPress(control, event);
1014
+ control.addEventListener("pointerenter", enter);
1015
+ control.addEventListener("pointerleave", leave);
1016
+ control.addEventListener("focus", focus);
1017
+ control.addEventListener("blur", blur);
1018
+ control.addEventListener("pointerdown", press);
1019
+ control.addEventListener("click", press);
1020
+ const detach = () => {
1021
+ if (!detachers.delete(detach)) return;
1022
+ control.removeEventListener("pointerenter", enter);
1023
+ control.removeEventListener("pointerleave", leave);
1024
+ control.removeEventListener("focus", focus);
1025
+ control.removeEventListener("blur", blur);
1026
+ control.removeEventListener("pointerdown", press);
1027
+ control.removeEventListener("click", press);
1028
+ if (shown?.control === control || pending?.control === control) hide();
1029
+ if (pressed === control) pressed = void 0;
1030
+ };
1031
+ detachers.add(detach);
1032
+ return detach;
1033
+ }
1034
+ function delegate(container, selector, content) {
1035
+ let hovered;
1036
+ const controlOf = (target) => {
1037
+ if (!target || typeof target.closest !== "function") return void 0;
1038
+ const match = target.closest(selector);
1039
+ return match && container.contains(match) ? match : void 0;
1040
+ };
1041
+ const sourceOf = (control) => () => content(control);
1042
+ const over = (event) => {
1043
+ const control = controlOf(event.target);
1044
+ if (control === hovered) return;
1045
+ if (hovered) onLeave(hovered);
1046
+ hovered = control;
1047
+ if (control) onEnter(control, sourceOf(control), event);
1048
+ };
1049
+ const out = (event) => {
1050
+ if (!hovered) return;
1051
+ if (controlOf(event.relatedTarget) === hovered) return;
1052
+ onLeave(hovered);
1053
+ hovered = void 0;
1054
+ };
1055
+ const focusIn = (event) => {
1056
+ const control = controlOf(event.target);
1057
+ if (control) onFocus(control, sourceOf(control));
1058
+ };
1059
+ const focusOut = (event) => {
1060
+ const control = controlOf(event.target);
1061
+ if (control) onBlur(control);
1062
+ };
1063
+ const press = (event) => {
1064
+ const control = controlOf(event.target);
1065
+ if (control) onPress(control, event);
1066
+ };
1067
+ container.addEventListener("pointerover", over);
1068
+ container.addEventListener("pointerout", out);
1069
+ container.addEventListener("focusin", focusIn);
1070
+ container.addEventListener("focusout", focusOut);
1071
+ container.addEventListener("pointerdown", press);
1072
+ container.addEventListener("click", press);
1073
+ const detach = () => {
1074
+ if (!detachers.delete(detach)) return;
1075
+ container.removeEventListener("pointerover", over);
1076
+ container.removeEventListener("pointerout", out);
1077
+ container.removeEventListener("focusin", focusIn);
1078
+ container.removeEventListener("focusout", focusOut);
1079
+ container.removeEventListener("pointerdown", press);
1080
+ container.removeEventListener("click", press);
1081
+ if (shown && container.contains(shown.control) || pending && container.contains(pending.control)) {
1082
+ hide();
1083
+ }
1084
+ hovered = void 0;
1085
+ };
1086
+ detachers.add(detach);
1087
+ return detach;
1088
+ }
1089
+ return {
1090
+ element,
1091
+ attach,
1092
+ delegate,
1093
+ hide,
1094
+ destroy() {
1095
+ hide();
1096
+ for (const detach of [...detachers]) detach();
1097
+ element.remove();
1098
+ }
1099
+ };
1100
+ }
1101
+
1102
+ // src/dom/zoom-panel.ts
1103
+ function createZoomPanel(context) {
1104
+ const { ownerDocument, messages, lang, platform, tooltip } = context;
1105
+ let target = null;
1106
+ let unsubscribe;
1107
+ let frame;
1108
+ const detachers = [];
1109
+ function button(zoom, key, tooltipLabel, options) {
1110
+ const element2 = createElement(ownerDocument, "button", {
1111
+ type: "button",
1112
+ class: options.className ?? "cd-shell-target",
1113
+ "data-zoom": zoom,
1114
+ "aria-label": options.label,
1115
+ "aria-keyshortcuts": ariaKeyShortcuts(key, platform)
1116
+ });
1117
+ if (options.icon) element2.append(iconElement(options.icon));
1118
+ element2.addEventListener("click", () => run(key));
1119
+ detachers.push(
1120
+ tooltip.attach(element2, {
1121
+ label: tooltipLabel,
1122
+ shortcut: shortcutLabel(key, platform, messages.keyNames)
1123
+ })
1124
+ );
1125
+ return element2;
1126
+ }
1127
+ const zoomOut = button("out", "zoomOut", messages.zoomOut, { label: messages.zoomOut, icon: "minus" });
1128
+ const readout = button("actual", "zoomToActual", messages.zoomToActual(formatPercent(1, lang)), {
1129
+ className: "cd-shell-target cd-shell-zoom-readout"
1130
+ });
1131
+ const zoomIn = button("in", "zoomIn", messages.zoomIn, { label: messages.zoomIn, icon: "plus" });
1132
+ const fit = button("fit", "fit", messages.fit, { label: messages.fit, icon: "maximize" });
1133
+ const separator = createElement(ownerDocument, "span", {
1134
+ class: "cd-shell-zoom-separator",
1135
+ "aria-hidden": "true"
1136
+ });
1137
+ const element = createElement(
1138
+ ownerDocument,
1139
+ "div",
1140
+ {
1141
+ class: "cd-shell-zoom cd-shell-island",
1142
+ "data-chrome": "zoom",
1143
+ role: "group",
1144
+ "aria-label": messages.zoom
1145
+ },
1146
+ zoomOut,
1147
+ readout,
1148
+ zoomIn,
1149
+ separator,
1150
+ fit
1151
+ );
1152
+ function render() {
1153
+ frame = void 0;
1154
+ const zoom = target?.zoom() ?? 1;
1155
+ const state = zoomReadout(zoom, lang, messages);
1156
+ if (readout.textContent !== state.text) readout.textContent = state.text;
1157
+ readout.setAttribute("aria-label", state.ariaLabel);
1158
+ toggleAttribute(zoomOut, "aria-disabled", (!target || !state.canZoomOut) && "true");
1159
+ toggleAttribute(zoomIn, "aria-disabled", (!target || !state.canZoomIn) && "true");
1160
+ toggleAttribute(fit, "aria-disabled", (!target || !target.canFit()) && "true");
1161
+ }
1162
+ function schedule() {
1163
+ if (frame === void 0) frame = requestAnimationFrame(render);
1164
+ }
1165
+ function cancelFrame() {
1166
+ if (frame !== void 0) cancelAnimationFrame(frame);
1167
+ frame = void 0;
1168
+ }
1169
+ function run(key) {
1170
+ if (!target) return;
1171
+ const zoom = target.zoom();
1172
+ switch (key) {
1173
+ case "zoomIn":
1174
+ if (canZoomIn(zoom)) target.stepZoom(1);
1175
+ break;
1176
+ case "zoomOut":
1177
+ if (canZoomOut(zoom)) target.stepZoom(-1);
1178
+ break;
1179
+ case "zoomToActual":
1180
+ if (zoom !== 1) target.zoomToActual();
1181
+ break;
1182
+ case "fit":
1183
+ if (target.canFit()) target.fit();
1184
+ break;
1185
+ }
1186
+ }
1187
+ render();
1188
+ return {
1189
+ element,
1190
+ run,
1191
+ setTarget(next) {
1192
+ unsubscribe?.();
1193
+ unsubscribe = void 0;
1194
+ cancelFrame();
1195
+ target = next;
1196
+ if (next) unsubscribe = next.onChange(schedule);
1197
+ render();
1198
+ },
1199
+ destroy() {
1200
+ unsubscribe?.();
1201
+ unsubscribe = void 0;
1202
+ cancelFrame();
1203
+ target = null;
1204
+ for (const detach of detachers) detach();
1205
+ element.remove();
1206
+ }
1207
+ };
1208
+ }
1209
+
1210
+ // src/dom/mount.ts
1211
+ var MOUNT_COUNTER = /* @__PURE__ */ Symbol.for("@miragon/modeler-shell.mounts");
1212
+ function nextMountIndex() {
1213
+ const scope = globalThis;
1214
+ const index = (scope[MOUNT_COUNTER] ?? 0) + 1;
1215
+ scope[MOUNT_COUNTER] = index;
1216
+ return index;
1217
+ }
1218
+ var DEFAULT_THEMES = ["light"];
1219
+ function languageOf(root, host, requested) {
1220
+ if (requested) return requested;
1221
+ const declared = host === "embedded" ? root.getAttribute("lang") : root.closest("[lang]")?.getAttribute("lang");
1222
+ return declared || "en";
1223
+ }
1224
+ function hostKontrast(root) {
1225
+ const carrier = root.parentElement?.closest("[data-cd-theme], [data-cd-kontrast]");
1226
+ return carrier?.getAttribute("data-cd-kontrast") === "hoch" ? "hoch" : null;
1227
+ }
1228
+ function mountShell(root, options) {
1229
+ const themes = options.themes ?? DEFAULT_THEMES;
1230
+ if (themes.length === 0) throw new Error("@miragon/modeler-shell: mountShell needs at least one theme");
1231
+ const ledger = new Ledger();
1232
+ try {
1233
+ return renderShell(root, options, themes, ledger);
1234
+ } catch (error) {
1235
+ ledger.close();
1236
+ throw error;
1237
+ }
1238
+ }
1239
+ function renderShell(root, options, themes, ledger) {
1240
+ const ownerDocument = root.ownerDocument;
1241
+ const { host } = options;
1242
+ const index = nextMountIndex();
1243
+ const messages = resolveMessages(options.messages);
1244
+ const platform = options.platform ?? detectPlatform(globalThis.navigator);
1245
+ const lang = languageOf(root, host, options.lang);
1246
+ const keyScope = options.keyScope ?? (host === "embedded" ? "root" : "document");
1247
+ ledger.addClass(root, "cd-shell");
1248
+ ledger.attribute(root, "data-cd-shell-host", host);
1249
+ ledger.attribute(root, "data-palette", "top");
1250
+ if (host === "embedded") {
1251
+ ledger.attribute(root, "lang", lang);
1252
+ carryOwnMode(root, themes[0] ?? "light", ledger);
1253
+ } else {
1254
+ ledger.addClass(ownerDocument.documentElement, "cd-shell-document");
1255
+ }
1256
+ const setState = (name, value) => {
1257
+ if (!ledger.isClosed) ledger.attribute(root, name, value);
1258
+ };
1259
+ const layer = createElement(ownerDocument, "div", { class: "cd-shell-layer" });
1260
+ ledger.defer(() => layer.remove());
1261
+ const tooltip = createTooltip({ root, layer, id: `cd-shell-tooltip-${index}` });
1262
+ ledger.defer(() => tooltip.destroy());
1263
+ let readOnly = false;
1264
+ let readOnlyShown = false;
1265
+ let readOnlyTimer;
1266
+ ledger.defer(() => clearTimeout(readOnlyTimer));
1267
+ const legend = options.legend ? createLegend({
1268
+ ownerDocument,
1269
+ messages,
1270
+ tooltip,
1271
+ titleId: `cd-shell-legend-title-${index}`,
1272
+ options: options.legend,
1273
+ onChange(open) {
1274
+ setState("data-legend", open ? "open" : null);
1275
+ placeHint();
1276
+ menu.refresh();
1277
+ },
1278
+ focusAfterClose: () => menu.trigger.focus()
1279
+ }) : void 0;
1280
+ if (legend) ledger.defer(() => legend.destroy());
1281
+ const legendEntry = legend && options.legend?.menuEntry !== false ? {
1282
+ kind: "checkbox",
1283
+ id: "legend",
1284
+ label: messages.legend,
1285
+ checked: () => legend.isOpen(),
1286
+ onToggle: (checked) => legend.setOpen(checked),
1287
+ keepEnabledWhenReadOnly: true
1288
+ } : void 0;
1289
+ const shellModelEntries = legendEntry ? [legendEntry] : [];
1290
+ const menu = createMenu({
1291
+ root,
1292
+ layer,
1293
+ id: `cd-shell-menu-${index}`,
1294
+ messages,
1295
+ tooltip,
1296
+ groups: options.menu ?? {},
1297
+ shellModelEntries: () => shellModelEntries,
1298
+ isReadOnly: () => readOnly
1299
+ });
1300
+ ledger.defer(() => menu.destroy());
1301
+ ledger.defer(tooltip.attach(menu.trigger, () => menu.isOpen() ? null : { label: messages.menu }));
1302
+ const canvas = options.canvas ?? createElement(ownerDocument, "div");
1303
+ ledger.addClass(canvas, "cd-shell-canvas");
1304
+ if (!options.canvas) ledger.defer(() => canvas.remove());
1305
+ function moveFocusOutOf(island) {
1306
+ if (!island.contains(ownerDocument.activeElement)) return;
1307
+ const canvasTarget = canvas.querySelector("svg[tabindex]");
1308
+ canvasTarget?.focus({ preventScroll: true });
1309
+ if (canvasTarget && ownerDocument.activeElement === canvasTarget) return;
1310
+ if (!menu.trigger.hidden) menu.trigger.focus({ preventScroll: true });
1311
+ }
1312
+ const status = createStatusBox(ownerDocument);
1313
+ ledger.defer(() => status.destroy());
1314
+ const hint = options.emptyHint ? createEmptyHint(ownerDocument, messages, options.emptyHint) : void 0;
1315
+ if (hint) ledger.defer(() => hint.destroy());
1316
+ const inspector = options.inspector ? createInspector(ownerDocument, options.inspector, (open) => {
1317
+ setState("data-inspector", open ? "open" : null);
1318
+ placeHint();
1319
+ }) : void 0;
1320
+ if (inspector) ledger.defer(() => inspector.destroy());
1321
+ const pill = options.problems ? createProblemsPill({
1322
+ ownerDocument,
1323
+ messages,
1324
+ lang,
1325
+ tooltip,
1326
+ showTooltip: host === "ide",
1327
+ options: options.problems
1328
+ }) : void 0;
1329
+ if (pill) ledger.defer(() => pill.destroy());
1330
+ const zoomPanel = createZoomPanel({ ownerDocument, messages, lang, platform, tooltip });
1331
+ ledger.defer(() => zoomPanel.destroy());
1332
+ root.prepend(menu.trigger, canvas);
1333
+ root.append(
1334
+ ...[
1335
+ status.element,
1336
+ hint?.element,
1337
+ inspector?.element,
1338
+ pill?.element,
1339
+ legend?.element,
1340
+ zoomPanel.element
1341
+ ].filter((element) => element !== void 0),
1342
+ layer
1343
+ );
1344
+ let palette;
1345
+ let paletteRow = "top";
1346
+ function containerSize() {
1347
+ return { width: root.clientWidth, height: root.clientHeight };
1348
+ }
1349
+ function menuRight() {
1350
+ const box = boxRelativeTo(menu.trigger, root);
1351
+ return box.width > 0 ? box.x + box.width : SHELL.edge + SHELL.row;
1352
+ }
1353
+ function paletteWidth() {
1354
+ const element = palette?.registration.element;
1355
+ return element ? element.offsetWidth - element.clientWidth + element.scrollWidth : 0;
1356
+ }
1357
+ function currentFitInsets() {
1358
+ const container = containerSize();
1359
+ const layout = {
1360
+ container,
1361
+ host,
1362
+ paletteRow,
1363
+ problemsVisible: pill?.isShown() ?? false
1364
+ };
1365
+ if (legend?.isOpen()) layout.legendWidth = legend.element.getBoundingClientRect().width;
1366
+ if (inspector?.isOpen()) {
1367
+ layout.inspector = container.width > SHELL.narrow ? {} : { sheetTop: boxRelativeTo(inspector.element, root).y };
1368
+ }
1369
+ return fitInsets(layout);
1370
+ }
1371
+ function placeHint() {
1372
+ if (hint?.isShown()) hint.place(currentFitInsets());
1373
+ }
1374
+ function updateLayout() {
1375
+ if (ledger.isClosed) return;
1376
+ const size = containerSize();
1377
+ if (size.width === 0 || size.height === 0) return;
1378
+ paletteRow = decidePaletteRow({
1379
+ containerWidth: size.width,
1380
+ paletteWidth: paletteWidth(),
1381
+ menuRight: menuRight()
1382
+ });
1383
+ setState("data-palette", paletteRow);
1384
+ if (legend && !legend.isStarted()) legend.start(options.legend?.open ?? legendStartsOpen(size.height));
1385
+ placeHint();
1386
+ }
1387
+ const ResizeObserverClass = globalThis.ResizeObserver;
1388
+ const resizeObserver = ResizeObserverClass ? new ResizeObserverClass(() => updateLayout()) : void 0;
1389
+ resizeObserver?.observe(root);
1390
+ ledger.defer(() => resizeObserver?.disconnect());
1391
+ if (legend && options.legend?.open !== void 0) legend.start(options.legend.open);
1392
+ updateLayout();
1393
+ const keyTarget = keyScope === "root" ? root : ownerDocument;
1394
+ const onKeydown = createKeyListener({
1395
+ host,
1396
+ dialogScope: keyTarget,
1397
+ menuOpen: () => menu.isOpen(),
1398
+ closeMenu() {
1399
+ const active = ownerDocument.activeElement;
1400
+ menu.close({ restoreFocus: active === menu.trigger || menu.dropdown.contains(active) });
1401
+ },
1402
+ hideTooltip: () => tooltip.hide(),
1403
+ run: (key) => zoomPanel.run(key)
1404
+ });
1405
+ keyTarget.addEventListener("keydown", onKeydown);
1406
+ ledger.defer(() => keyTarget.removeEventListener("keydown", onKeydown));
1407
+ function readOnlyState() {
1408
+ return { locked: readOnly, shown: readOnlyShown };
1409
+ }
1410
+ function applyReadOnly() {
1411
+ setState("data-readonly", readOnlyShown ? "" : null);
1412
+ palette?.registration.setReadOnly(readOnlyState());
1413
+ menu.refresh();
1414
+ }
1415
+ function showReadOnly() {
1416
+ readOnlyTimer = void 0;
1417
+ if (ledger.isClosed || !readOnly) return;
1418
+ readOnlyShown = true;
1419
+ applyReadOnly();
1420
+ }
1421
+ function unregisterPalette() {
1422
+ if (!palette) return;
1423
+ palette.detachTooltip();
1424
+ resizeObserver?.unobserve(palette.registration.element);
1425
+ palette = void 0;
1426
+ }
1427
+ ledger.defer(unregisterPalette);
1428
+ const menuHandle = {
1429
+ trigger: menu.trigger,
1430
+ dropdown: menu.dropdown,
1431
+ isOpen: () => menu.isOpen(),
1432
+ open: (focus) => menu.open(focus),
1433
+ close: (closeOptions) => menu.close(closeOptions),
1434
+ refresh: () => menu.refresh(),
1435
+ setGroups: (groups) => {
1436
+ if (!ledger.isClosed) menu.setGroups(groups);
1437
+ }
1438
+ };
1439
+ const tooltipHandle = {
1440
+ attach: (control, content) => ledger.isClosed ? () => {
1441
+ } : tooltip.attach(control, content),
1442
+ hide: () => tooltip.hide()
1443
+ };
1444
+ const legendHandle = legend && {
1445
+ element: legend.element,
1446
+ body: legend.body,
1447
+ isOpen: () => legend.isOpen(),
1448
+ setOpen: (open) => {
1449
+ if (!ledger.isClosed) legend.setOpen(open);
1450
+ },
1451
+ toggle: () => {
1452
+ if (!ledger.isClosed) legend.toggle();
1453
+ }
1454
+ };
1455
+ const inspectorHandle = inspector && {
1456
+ element: inspector.element,
1457
+ isOpen: () => inspector.isOpen(),
1458
+ open: () => {
1459
+ if (!ledger.isClosed) inspector.open();
1460
+ },
1461
+ close: () => {
1462
+ if (ledger.isClosed) return;
1463
+ if (inspector.isOpen()) moveFocusOutOf(inspector.element);
1464
+ inspector.close();
1465
+ }
1466
+ };
1467
+ return {
1468
+ root,
1469
+ canvas,
1470
+ host,
1471
+ lang,
1472
+ platform,
1473
+ messages,
1474
+ menu: menuHandle,
1475
+ tooltip: tooltipHandle,
1476
+ legend: legendHandle,
1477
+ inspector: inspectorHandle,
1478
+ setZoomTarget(target) {
1479
+ if (!ledger.isClosed) zoomPanel.setTarget(target);
1480
+ },
1481
+ fitInsets: currentFitInsets,
1482
+ registerPalette(registration) {
1483
+ if (ledger.isClosed) return () => {
1484
+ };
1485
+ unregisterPalette();
1486
+ palette = {
1487
+ registration,
1488
+ detachTooltip: tooltip.delegate(
1489
+ registration.element,
1490
+ ".entry",
1491
+ (entry) => registration.entryTooltip(entry)
1492
+ )
1493
+ };
1494
+ resizeObserver?.observe(registration.element);
1495
+ registration.setReadOnly(readOnlyState());
1496
+ updateLayout();
1497
+ const registered = palette;
1498
+ return () => {
1499
+ if (palette !== registered) return;
1500
+ unregisterPalette();
1501
+ updateLayout();
1502
+ };
1503
+ },
1504
+ setProblems(summary) {
1505
+ if (ledger.isClosed || !pill) return;
1506
+ pill.set(summary);
1507
+ setState("data-problems", pill.isShown() ? "shown" : null);
1508
+ placeHint();
1509
+ },
1510
+ setStatus(next, statusOptions) {
1511
+ if (!ledger.isClosed) status.set(next, statusOptions?.delayMs ?? 0);
1512
+ },
1513
+ setEmptyHint(visible) {
1514
+ if (ledger.isClosed || !hint) return;
1515
+ if (!visible && hint.isShown()) moveFocusOutOf(hint.element);
1516
+ hint.setShown(visible);
1517
+ placeHint();
1518
+ },
1519
+ setReadOnly(next, readOnlyOptions) {
1520
+ if (ledger.isClosed) return;
1521
+ if (!next) {
1522
+ clearTimeout(readOnlyTimer);
1523
+ readOnlyTimer = void 0;
1524
+ readOnly = false;
1525
+ readOnlyShown = false;
1526
+ } else {
1527
+ readOnly = true;
1528
+ const delayMs = readOnlyOptions?.delayMs ?? 0;
1529
+ if (!readOnlyShown && readOnlyTimer === void 0) {
1530
+ if (delayMs > 0) readOnlyTimer = setTimeout(showReadOnly, delayMs);
1531
+ else readOnlyShown = true;
1532
+ }
1533
+ }
1534
+ applyReadOnly();
1535
+ },
1536
+ isReadOnly: () => readOnly,
1537
+ destroy() {
1538
+ ledger.close();
1539
+ }
1540
+ };
1541
+ }
1542
+ function carryOwnMode(root, theme, ledger) {
1543
+ ledger.attribute(root, "data-cd-theme", theme);
1544
+ const mirror = () => {
1545
+ if (ledger.isClosed) return;
1546
+ const kontrast = hostKontrast(root);
1547
+ if (root.getAttribute("data-cd-kontrast") !== kontrast)
1548
+ ledger.attribute(root, "data-cd-kontrast", kontrast);
1549
+ };
1550
+ mirror();
1551
+ const MutationObserverClass = globalThis.MutationObserver;
1552
+ if (!MutationObserverClass) return;
1553
+ const observer = new MutationObserverClass(mirror);
1554
+ observer.observe(root.ownerDocument.documentElement, {
1555
+ attributes: true,
1556
+ attributeFilter: ["data-cd-theme", "data-cd-kontrast"],
1557
+ subtree: true
1558
+ });
1559
+ ledger.defer(() => observer.disconnect());
1560
+ }
1561
+
1562
+ export {
1563
+ ICONS,
1564
+ PALETTE_TOOL_ICON,
1565
+ iconMarkup,
1566
+ iconElement,
1567
+ mountShell
1568
+ };
1569
+ /*! @miragon/modeler-shell icon geometry
1570
+ * Lucide (lucide-static 1.59.0) @license ISC
1571
+ * Copyright (c) 2026 Lucide Icons and Contributors
1572
+ * Derived from Feather, as Lucide's LICENSE lists them: check, circle-x (x-circle), maximize,
1573
+ * minus, plus, triangle-alert (alert-triangle) and x. Feather @license MIT
1574
+ * Copyright (c) 2013-present Cole Bemis
1575
+ * Full notices: THIRD_PARTY_NOTICES.md of @miragon/modeler-shell */
1576
+ //# sourceMappingURL=chunk-6LQOFACK.js.map