@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,2792 @@
1
+ import {
2
+ APPEARANCE_STORAGE_KEY,
3
+ LEGAL_LINE_HEIGHT,
4
+ MAX_TOASTS,
5
+ REDUCED_MOTION_CLASS,
6
+ SHELL,
7
+ TEXT_FIELD_SELECTOR,
8
+ TOOLTIP_DELAY_MS,
9
+ acceptsFile,
10
+ ariaKeyShortcuts,
11
+ ariaKeyShortcutsFromLabel,
12
+ canZoomIn,
13
+ canZoomOut,
14
+ decideKey,
15
+ decideTopRow,
16
+ detectPlatform,
17
+ documentMode,
18
+ dragVerdict,
19
+ fitInsets,
20
+ formatNumber,
21
+ formatPercent,
22
+ legalLineHeight,
23
+ legendStartsOpen,
24
+ matchChromeKey,
25
+ parseAppearanceChoice,
26
+ placeTooltip,
27
+ resolveMessages,
28
+ shortcutLabel,
29
+ toastLifetime,
30
+ toastStackBottom,
31
+ toastToEvict,
32
+ zoomReadout,
33
+ zoomRowRaised
34
+ } from "./chunk-OADW3GPA.js";
35
+
36
+ // src/dom/icons.ts
37
+ var ICONS = {
38
+ menu: [
39
+ ["path", { d: "M4 5h16" }],
40
+ ["path", { d: "M4 12h16" }],
41
+ ["path", { d: "M4 19h16" }]
42
+ ],
43
+ minus: [["path", { d: "M5 12h14" }]],
44
+ plus: [
45
+ ["path", { d: "M5 12h14" }],
46
+ ["path", { d: "M12 5v14" }]
47
+ ],
48
+ maximize: [
49
+ ["path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }],
50
+ ["path", { d: "M21 8V5a2 2 0 0 0-2-2h-3" }],
51
+ ["path", { d: "M3 16v3a2 2 0 0 0 2 2h3" }],
52
+ ["path", { d: "M16 21h3a2 2 0 0 0 2-2v-3" }]
53
+ ],
54
+ check: [["path", { d: "M20 6 9 17l-5-5" }]],
55
+ x: [
56
+ ["path", { d: "M18 6 6 18" }],
57
+ ["path", { d: "m6 6 12 12" }]
58
+ ],
59
+ "file-plus": [
60
+ [
61
+ "path",
62
+ {
63
+ 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"
64
+ }
65
+ ],
66
+ ["path", { d: "M14 2v5a1 1 0 0 0 1 1h5" }],
67
+ ["path", { d: "M9 15h6" }],
68
+ ["path", { d: "M12 18v-6" }]
69
+ ],
70
+ "folder-open": [
71
+ [
72
+ "path",
73
+ {
74
+ 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"
75
+ }
76
+ ]
77
+ ],
78
+ import: [
79
+ ["path", { d: "M12 3v12" }],
80
+ ["path", { d: "m8 11 4 4 4-4" }],
81
+ ["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" }]
82
+ ],
83
+ "file-image": [
84
+ [
85
+ "path",
86
+ {
87
+ 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"
88
+ }
89
+ ],
90
+ ["path", { d: "M14 2v5a1 1 0 0 0 1 1h5" }],
91
+ ["circle", { cx: "10", cy: "12", r: "2" }],
92
+ ["path", { d: "m20 17-1.296-1.296a2.41 2.41 0 0 0-3.408 0L9 22" }]
93
+ ],
94
+ "file-code-corner": [
95
+ [
96
+ "path",
97
+ {
98
+ 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"
99
+ }
100
+ ],
101
+ ["path", { d: "M14 2v5a1 1 0 0 0 1 1h5" }],
102
+ ["path", { d: "m5 16-3 3 3 3" }],
103
+ ["path", { d: "m9 22 3-3-3-3" }]
104
+ ],
105
+ keyboard: [
106
+ ["path", { d: "M10 8h.01" }],
107
+ ["path", { d: "M12 12h.01" }],
108
+ ["path", { d: "M14 8h.01" }],
109
+ ["path", { d: "M16 12h.01" }],
110
+ ["path", { d: "M18 8h.01" }],
111
+ ["path", { d: "M6 8h.01" }],
112
+ ["path", { d: "M7 16h10" }],
113
+ ["path", { d: "M8 12h.01" }],
114
+ ["rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" }]
115
+ ],
116
+ "share-2": [
117
+ ["circle", { cx: "18", cy: "5", r: "3" }],
118
+ ["circle", { cx: "6", cy: "12", r: "3" }],
119
+ ["circle", { cx: "18", cy: "19", r: "3" }],
120
+ ["line", { x1: "8.59", x2: "15.42", y1: "13.51", y2: "17.49" }],
121
+ ["line", { x1: "15.41", x2: "8.59", y1: "6.51", y2: "10.49" }]
122
+ ],
123
+ "circle-x": [
124
+ ["circle", { cx: "12", cy: "12", r: "10" }],
125
+ ["path", { d: "m15 9-6 6" }],
126
+ ["path", { d: "m9 9 6 6" }]
127
+ ],
128
+ "triangle-alert": [
129
+ ["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" }],
130
+ ["path", { d: "M12 9v4" }],
131
+ ["path", { d: "M12 17h.01" }]
132
+ ],
133
+ "square-dashed-mouse-pointer": [
134
+ [
135
+ "path",
136
+ {
137
+ 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"
138
+ }
139
+ ],
140
+ ["path", { d: "M5 3a2 2 0 0 0-2 2" }],
141
+ ["path", { d: "M19 3a2 2 0 0 1 2 2" }],
142
+ ["path", { d: "M5 21a2 2 0 0 1-2-2" }],
143
+ ["path", { d: "M9 3h1" }],
144
+ ["path", { d: "M9 21h2" }],
145
+ ["path", { d: "M14 3h1" }],
146
+ ["path", { d: "M3 9v1" }],
147
+ ["path", { d: "M21 9v2" }],
148
+ ["path", { d: "M3 14v1" }]
149
+ ],
150
+ hand: [
151
+ ["path", { d: "M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2" }],
152
+ ["path", { d: "M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2" }],
153
+ ["path", { d: "M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8" }],
154
+ [
155
+ "path",
156
+ {
157
+ 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"
158
+ }
159
+ ]
160
+ ],
161
+ "line-segment": [
162
+ ["circle", { cx: "19", cy: "5", r: "2" }],
163
+ ["circle", { cx: "5", cy: "19", r: "2" }],
164
+ ["path", { d: "M6.5 17.5 17.5 6.5" }]
165
+ ],
166
+ "unfold-horizontal": [
167
+ ["path", { d: "M16 12h6" }],
168
+ ["path", { d: "M8 12H2" }],
169
+ ["path", { d: "M12 2v2" }],
170
+ ["path", { d: "M12 8v2" }],
171
+ ["path", { d: "M12 14v2" }],
172
+ ["path", { d: "M12 20v2" }],
173
+ ["path", { d: "m19 15 3-3-3-3" }],
174
+ ["path", { d: "m5 9-3 3 3 3" }]
175
+ ],
176
+ workflow: [
177
+ ["rect", { width: "8", height: "8", x: "3", y: "3", rx: "2" }],
178
+ ["path", { d: "M7 11v4a2 2 0 0 0 2 2h4" }],
179
+ ["rect", { width: "8", height: "8", x: "13", y: "13", rx: "2" }]
180
+ ]
181
+ };
182
+ var PALETTE_TOOL_ICON = { size: 20, strokeWidth: 2 };
183
+ var DEFAULT_SIZE = 16;
184
+ var DEFAULT_STROKE_WIDTH = 2.5;
185
+ var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
186
+ var ICON_ELEMENTS = /* @__PURE__ */ new Set([
187
+ "path",
188
+ "circle",
189
+ "ellipse",
190
+ "line",
191
+ "polyline",
192
+ "polygon",
193
+ "rect"
194
+ ]);
195
+ var GEOMETRY_ATTRIBUTES = /* @__PURE__ */ new Set([
196
+ "d",
197
+ "cx",
198
+ "cy",
199
+ "r",
200
+ "rx",
201
+ "ry",
202
+ "x",
203
+ "y",
204
+ "x1",
205
+ "y1",
206
+ "x2",
207
+ "y2",
208
+ "width",
209
+ "height",
210
+ "points"
211
+ ]);
212
+ function positiveOr(value, fallback) {
213
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
214
+ }
215
+ function iconNodeOf(icon) {
216
+ if (typeof icon !== "string") return icon;
217
+ return Object.prototype.hasOwnProperty.call(ICONS, icon) ? ICONS[icon] : [];
218
+ }
219
+ function sanitize(icon, options = {}) {
220
+ const size = String(positiveOr(options.size, DEFAULT_SIZE));
221
+ const root = [];
222
+ if (options.className) root.push(["class", options.className]);
223
+ root.push(
224
+ ["width", size],
225
+ ["height", size],
226
+ ["viewBox", "0 0 24 24"],
227
+ ["fill", "none"],
228
+ ["stroke", "currentColor"],
229
+ ["stroke-width", String(positiveOr(options.strokeWidth, DEFAULT_STROKE_WIDTH))],
230
+ ["stroke-linecap", "round"],
231
+ ["stroke-linejoin", "round"],
232
+ ["aria-hidden", "true"],
233
+ ["focusable", "false"]
234
+ );
235
+ const children = [];
236
+ for (const node of iconNodeOf(icon)) {
237
+ const [tag, attributes] = node;
238
+ if (!ICON_ELEMENTS.has(tag)) continue;
239
+ const kept = Object.entries(attributes).filter(([name]) => GEOMETRY_ATTRIBUTES.has(name)).map(([name, value]) => [name, String(value)]);
240
+ children.push([tag, kept]);
241
+ }
242
+ return { root, children };
243
+ }
244
+ function escapeAttribute(value) {
245
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
246
+ }
247
+ function attributeList(attributes) {
248
+ return attributes.map(([name, value]) => ` ${name}="${escapeAttribute(value)}"`).join("");
249
+ }
250
+ function iconMarkup(icon, options) {
251
+ const { root, children } = sanitize(icon, options);
252
+ const content = children.map(([tag, attributes]) => `<${tag}${attributeList(attributes)}/>`).join("");
253
+ return `<svg xmlns="${SVG_NAMESPACE}"${attributeList(root)}>${content}</svg>`;
254
+ }
255
+ function iconElement(icon, options) {
256
+ const { root, children } = sanitize(icon, options);
257
+ const svg = document.createElementNS(SVG_NAMESPACE, "svg");
258
+ for (const [name, value] of root) svg.setAttribute(name, value);
259
+ for (const [tag, attributes] of children) {
260
+ const child = document.createElementNS(SVG_NAMESPACE, tag);
261
+ for (const [name, value] of attributes) child.setAttribute(name, value);
262
+ svg.append(child);
263
+ }
264
+ return svg;
265
+ }
266
+
267
+ // src/dom/element.ts
268
+ function createElement(ownerDocument, tag, attributes = {}, ...children) {
269
+ const element = ownerDocument.createElement(tag);
270
+ for (const [name, value] of Object.entries(attributes)) {
271
+ if (value !== void 0) element.setAttribute(name, value);
272
+ }
273
+ for (const child of children) {
274
+ if (child === void 0 || child === null || child === false) continue;
275
+ element.append(child);
276
+ }
277
+ return element;
278
+ }
279
+ function toggleAttribute(element, name, value) {
280
+ if (value === null || value === false) element.removeAttribute(name);
281
+ else element.setAttribute(name, value);
282
+ }
283
+ function boxRelativeTo(element, root) {
284
+ const box = element.getBoundingClientRect();
285
+ const origin = root.getBoundingClientRect();
286
+ return {
287
+ x: box.left - origin.left - root.clientLeft,
288
+ y: box.top - origin.top - root.clientTop,
289
+ width: box.width,
290
+ height: box.height
291
+ };
292
+ }
293
+ function accessibleName(control) {
294
+ return (control.getAttribute("aria-label") ?? control.textContent ?? "").trim();
295
+ }
296
+ function hasVisibleFocus(element) {
297
+ try {
298
+ return element.matches(":focus-visible");
299
+ } catch {
300
+ return true;
301
+ }
302
+ }
303
+
304
+ // src/dom/feedback/appearance.ts
305
+ var NO_TRANSITION_CLASS = "cd-shell-no-transition";
306
+ var CONTRAST_QUERY = "(prefers-contrast: more)";
307
+ var SCHEME_MEDIA = {
308
+ light: "(prefers-color-scheme: light)",
309
+ dark: "(prefers-color-scheme: dark)"
310
+ };
311
+ function onMediaChange(query, listener) {
312
+ if (typeof query.addEventListener === "function") {
313
+ query.addEventListener("change", listener);
314
+ return () => query.removeEventListener("change", listener);
315
+ }
316
+ query.addListener(listener);
317
+ return () => query.removeListener(listener);
318
+ }
319
+ function connectWebAppearance(context) {
320
+ const { ownerDocument, themes, options } = context;
321
+ const darkCapable = themes.includes("dark");
322
+ if (darkCapable && !options) {
323
+ throw new Error(
324
+ '@miragon/modeler-shell: a web modeler whose themes include "dark" needs the appearance option ({ themeColors } from its vendored cd-tokens.generated.json)'
325
+ );
326
+ }
327
+ const html = ownerDocument.documentElement;
328
+ const view = ownerDocument.defaultView;
329
+ const cleanups = [];
330
+ const contrast = typeof view?.matchMedia === "function" ? view.matchMedia(CONTRAST_QUERY) : void 0;
331
+ if (contrast) {
332
+ const mirrorContrast = () => toggleAttribute(html, "data-cd-kontrast", contrast.matches && "hoch");
333
+ mirrorContrast();
334
+ cleanups.push(onMediaChange(contrast, mirrorContrast));
335
+ }
336
+ function destroy() {
337
+ for (const cleanup of cleanups.splice(0)) cleanup();
338
+ }
339
+ if (!darkCapable || !options) {
340
+ return { choice: () => "system", setChoice() {
341
+ }, destroy };
342
+ }
343
+ const { themeColors } = options;
344
+ function storage() {
345
+ if (options?.storage) return options.storage;
346
+ try {
347
+ return globalThis.localStorage;
348
+ } catch {
349
+ return void 0;
350
+ }
351
+ }
352
+ function readStored() {
353
+ try {
354
+ return parseAppearanceChoice(storage()?.getItem(APPEARANCE_STORAGE_KEY));
355
+ } catch {
356
+ return "system";
357
+ }
358
+ }
359
+ function store(choice) {
360
+ try {
361
+ const target = storage();
362
+ if (choice === "system") target?.removeItem(APPEARANCE_STORAGE_KEY);
363
+ else target?.setItem(APPEARANCE_STORAGE_KEY, choice);
364
+ } catch {
365
+ }
366
+ }
367
+ function nextFrame(callback) {
368
+ if (view && typeof view.requestAnimationFrame === "function") {
369
+ const frame = view.requestAnimationFrame(() => callback());
370
+ return () => view.cancelAnimationFrame(frame);
371
+ }
372
+ const timer = setTimeout(callback, 16);
373
+ return () => clearTimeout(timer);
374
+ }
375
+ let cancelTransitionFrame;
376
+ function allowTransitions() {
377
+ cancelTransitionFrame?.();
378
+ cancelTransitionFrame = void 0;
379
+ html.classList.remove(NO_TRANSITION_CLASS);
380
+ }
381
+ cleanups.push(() => {
382
+ if (cancelTransitionFrame) allowTransitions();
383
+ });
384
+ function suspendTransitions() {
385
+ cancelTransitionFrame?.();
386
+ html.classList.add(NO_TRANSITION_CLASS);
387
+ cancelTransitionFrame = nextFrame(() => {
388
+ cancelTransitionFrame = nextFrame(allowTransitions);
389
+ });
390
+ }
391
+ function metaFor(name, media) {
392
+ const normalize = (value) => (value ?? "").replace(/\s+/g, "").toLowerCase();
393
+ const existing = [...ownerDocument.querySelectorAll(`meta[name="${name}"]`)].find(
394
+ (meta) => media === void 0 ? true : normalize(meta.getAttribute("media")) === normalize(media)
395
+ );
396
+ if (existing) return existing;
397
+ const created = createElement(ownerDocument, "meta", { name, media });
398
+ (ownerDocument.head ?? html).append(created);
399
+ return created;
400
+ }
401
+ function apply(choice) {
402
+ const mode = documentMode(choice, themes, themeColors);
403
+ suspendTransitions();
404
+ html.setAttribute("data-cd-theme", mode.theme);
405
+ metaFor("color-scheme").setAttribute("content", mode.colorScheme);
406
+ metaFor("theme-color", SCHEME_MEDIA.light).setAttribute("content", mode.themeColor.light);
407
+ metaFor("theme-color", SCHEME_MEDIA.dark).setAttribute("content", mode.themeColor.dark);
408
+ }
409
+ let current = readStored();
410
+ const onStorage = (event) => {
411
+ if (event.key !== null && event.key !== APPEARANCE_STORAGE_KEY) return;
412
+ const next = event.key === null ? "system" : parseAppearanceChoice(event.newValue);
413
+ if (next === current) return;
414
+ current = next;
415
+ apply(next);
416
+ };
417
+ if (view) {
418
+ view.addEventListener("storage", onStorage);
419
+ cleanups.push(() => view.removeEventListener("storage", onStorage));
420
+ }
421
+ return {
422
+ choice: () => current,
423
+ setChoice(choice) {
424
+ const next = parseAppearanceChoice(choice);
425
+ current = next;
426
+ store(next);
427
+ apply(next);
428
+ },
429
+ destroy
430
+ };
431
+ }
432
+
433
+ // src/dom/feedback/problems-popover.ts
434
+ var PILL_ATTRIBUTES = ["aria-haspopup", "aria-expanded", "aria-controls"];
435
+ function isPrintable(event) {
436
+ return event.key.length === 1 && /\S/.test(event.key) && !event.ctrlKey && !event.metaKey && !event.altKey;
437
+ }
438
+ function createProblemsPopover(context) {
439
+ const { ownerDocument, root, layer, pill, id, titleId, messages } = context;
440
+ const view = ownerDocument.defaultView;
441
+ const list = createElement(ownerDocument, "ul", { class: "cd-shell-problems-list", role: "list" });
442
+ const element = createElement(
443
+ ownerDocument,
444
+ "div",
445
+ {
446
+ class: "cd-shell-problems-popover",
447
+ "data-chrome": "problems-popover",
448
+ role: "dialog",
449
+ id,
450
+ "aria-labelledby": titleId,
451
+ tabindex: "-1"
452
+ },
453
+ createElement(
454
+ ownerDocument,
455
+ "h2",
456
+ { class: "cd-shell-problems-popover-title", id: titleId },
457
+ messages.problemsTitle
458
+ ),
459
+ list
460
+ );
461
+ element.hidden = true;
462
+ layer.append(element);
463
+ pill.setAttribute("aria-haspopup", "dialog");
464
+ pill.setAttribute("aria-expanded", "false");
465
+ pill.setAttribute("aria-controls", id);
466
+ let items = [];
467
+ let rows = [];
468
+ let destroyed = false;
469
+ const isOpen = () => !element.hidden;
470
+ function keyOf(item) {
471
+ return `${item.elementId ?? ""}\0${item.severity}\0${item.message}`;
472
+ }
473
+ function rowElement(item) {
474
+ const severity = item.severity === "error" ? "error" : "warning";
475
+ const parts = [
476
+ // The shape and colour of the icon carry the severity on screen, its name for screen readers.
477
+ createElement(
478
+ ownerDocument,
479
+ "span",
480
+ { class: "cd-shell-problem-icon", role: "img", "aria-label": messages.problemSeverity(severity) },
481
+ iconElement(severity === "error" ? "circle-x" : "triangle-alert")
482
+ ),
483
+ createElement(ownerDocument, "span", { class: "cd-shell-problem-message" }, item.message),
484
+ item.location ? createElement(ownerDocument, "span", { class: "cd-shell-problem-location" }, item.location) : void 0
485
+ ];
486
+ const attributes = { class: "cd-shell-problem", "data-severity": severity };
487
+ if (!item.elementId) {
488
+ const text = createElement(ownerDocument, "div", { ...attributes, tabindex: "-1" }, ...parts);
489
+ rows.push({ key: keyOf(item), element: text });
490
+ return text;
491
+ }
492
+ const button = createElement(ownerDocument, "button", { type: "button", ...attributes }, ...parts);
493
+ button.addEventListener("click", () => select(item));
494
+ rows.push({ key: keyOf(item), element: button });
495
+ return button;
496
+ }
497
+ function focusedRowIndex() {
498
+ return rows.findIndex((row) => row.element === ownerDocument.activeElement);
499
+ }
500
+ function render() {
501
+ const focusedIndex = focusedRowIndex();
502
+ const focusedKey = rows[focusedIndex]?.key;
503
+ rows = [];
504
+ list.replaceChildren(...items.map((item) => createElement(ownerDocument, "li", {}, rowElement(item))));
505
+ if (focusedKey === void 0 || !isOpen()) return;
506
+ const kept = rows.find((row) => row.key === focusedKey) ?? rows[Math.min(focusedIndex, rows.length - 1)];
507
+ if (kept) focusRowElement(kept.element);
508
+ else element.focus({ preventScroll: true });
509
+ }
510
+ function revealInList(row) {
511
+ const rowBox = row.getBoundingClientRect();
512
+ const listBox = list.getBoundingClientRect();
513
+ if (rowBox.top < listBox.top) list.scrollTop -= listBox.top - rowBox.top;
514
+ else if (rowBox.bottom > listBox.bottom) list.scrollTop += rowBox.bottom - listBox.bottom;
515
+ }
516
+ function focusRowElement(row) {
517
+ row.focus({ preventScroll: true });
518
+ revealInList(row);
519
+ }
520
+ function focusCanvas() {
521
+ const canvas = root.querySelector(".cd-shell-canvas svg[tabindex]");
522
+ canvas?.focus({ preventScroll: true });
523
+ if ((!canvas || ownerDocument.activeElement !== canvas) && !pill.hidden)
524
+ pill.focus({ preventScroll: true });
525
+ }
526
+ function select(item) {
527
+ close();
528
+ focusCanvas();
529
+ context.onSelectItem?.(item);
530
+ }
531
+ function focusRow(index) {
532
+ if (rows.length === 0) return;
533
+ const row = rows[(index + rows.length) % rows.length];
534
+ if (row) focusRowElement(row.element);
535
+ }
536
+ const onOutsidePointerDown = (event) => {
537
+ const target = event.target;
538
+ if (target && (element.contains(target) || pill.contains(target))) return;
539
+ close();
540
+ };
541
+ const onWindowBlur = () => close();
542
+ function open() {
543
+ if (destroyed || isOpen()) return;
544
+ element.hidden = false;
545
+ pill.setAttribute("aria-expanded", "true");
546
+ ownerDocument.addEventListener("pointerdown", onOutsidePointerDown, true);
547
+ view?.addEventListener("blur", onWindowBlur);
548
+ list.scrollTop = 0;
549
+ const first = rows[0];
550
+ if (first) focusRowElement(first.element);
551
+ else element.focus({ preventScroll: true });
552
+ }
553
+ function close(options = {}) {
554
+ if (!isOpen()) return;
555
+ element.hidden = true;
556
+ pill.setAttribute("aria-expanded", "false");
557
+ ownerDocument.removeEventListener("pointerdown", onOutsidePointerDown, true);
558
+ view?.removeEventListener("blur", onWindowBlur);
559
+ if (options.restoreFocus && !pill.hidden) pill.focus({ preventScroll: true });
560
+ }
561
+ function consume(event) {
562
+ event.preventDefault();
563
+ event.stopPropagation();
564
+ }
565
+ element.addEventListener("keydown", (event) => {
566
+ if (matchChromeKey(event)) return;
567
+ const index = focusedRowIndex();
568
+ switch (event.key) {
569
+ case "Escape":
570
+ consume(event);
571
+ close({ restoreFocus: true });
572
+ return;
573
+ case "Tab": {
574
+ const first = index <= 0;
575
+ const last = rows.length === 0 || index === rows.length - 1;
576
+ if (event.shiftKey && first) {
577
+ consume(event);
578
+ close({ restoreFocus: true });
579
+ } else if (!event.shiftKey && last) {
580
+ event.stopPropagation();
581
+ close({ restoreFocus: true });
582
+ }
583
+ return;
584
+ }
585
+ case "ArrowDown":
586
+ focusRow(index + 1);
587
+ break;
588
+ case "ArrowUp":
589
+ focusRow(index < 0 ? rows.length - 1 : index - 1);
590
+ break;
591
+ case "Home":
592
+ focusRow(0);
593
+ break;
594
+ case "End":
595
+ focusRow(rows.length - 1);
596
+ break;
597
+ case "Enter":
598
+ case " ":
599
+ case "Delete":
600
+ case "Backspace":
601
+ event.stopPropagation();
602
+ return;
603
+ default:
604
+ if (isPrintable(event)) event.stopPropagation();
605
+ return;
606
+ }
607
+ consume(event);
608
+ });
609
+ return {
610
+ element,
611
+ isOpen,
612
+ toggle() {
613
+ if (isOpen()) close({ restoreFocus: true });
614
+ else open();
615
+ },
616
+ open,
617
+ close,
618
+ setItems(next) {
619
+ items = next;
620
+ if (!destroyed) render();
621
+ },
622
+ destroy() {
623
+ if (destroyed) return;
624
+ close();
625
+ destroyed = true;
626
+ for (const name of PILL_ATTRIBUTES) pill.removeAttribute(name);
627
+ element.remove();
628
+ }
629
+ };
630
+ }
631
+
632
+ // src/dom/feedback/toasts.ts
633
+ var TOAST_RESUME_MIN_MS = 1500;
634
+ var TOAST_LEAVE_FALLBACK_MS = 400;
635
+ var LEAVE_ANIMATION = "cd-shell-toast-out";
636
+ var TOAST_ANNOUNCE_DELAY_MS = 100;
637
+ var INERT_HANDLE = Object.freeze({ dismiss() {
638
+ } });
639
+ function createToaster(context) {
640
+ const { ownerDocument, messages } = context;
641
+ const web = context.host === "web";
642
+ const element = createElement(ownerDocument, "div", {
643
+ class: "cd-shell-toasts",
644
+ "data-chrome": "toasts",
645
+ popover: web ? "manual" : void 0
646
+ });
647
+ const liveRegion = (politeness) => createElement(ownerDocument, "div", {
648
+ class: "cd-shell-live-region",
649
+ "data-live": politeness,
650
+ role: politeness === "polite" ? "status" : "alert",
651
+ "aria-live": politeness,
652
+ "aria-atomic": "true"
653
+ });
654
+ const politeRegion = liveRegion("polite");
655
+ const assertiveRegion = liveRegion("assertive");
656
+ const announcer = createElement(
657
+ ownerDocument,
658
+ "div",
659
+ { class: "cd-shell-live" },
660
+ politeRegion,
661
+ assertiveRegion
662
+ );
663
+ const live = [];
664
+ const leaveTimers = /* @__PURE__ */ new Set();
665
+ const announceTimers = /* @__PURE__ */ new Map();
666
+ let lastId = 0;
667
+ let destroyed = false;
668
+ function regionFor(request) {
669
+ return request.status === "danger" ? assertiveRegion : politeRegion;
670
+ }
671
+ function announce(request) {
672
+ const region = regionFor(request);
673
+ clearTimeout(announceTimers.get(region));
674
+ region.textContent = "";
675
+ announceTimers.set(
676
+ region,
677
+ setTimeout(() => {
678
+ announceTimers.delete(region);
679
+ if (!destroyed) region.textContent = request.text;
680
+ }, TOAST_ANNOUNCE_DELAY_MS)
681
+ );
682
+ }
683
+ function silence(request) {
684
+ const region = regionFor(request);
685
+ if (region.textContent === request.text) region.textContent = "";
686
+ }
687
+ function changed() {
688
+ if (!destroyed) context.onChange?.();
689
+ }
690
+ function supportsPopover() {
691
+ return web && typeof element.showPopover === "function" && typeof element.hidePopover === "function";
692
+ }
693
+ function popoverOpen() {
694
+ try {
695
+ return element.matches(":popover-open");
696
+ } catch {
697
+ return false;
698
+ }
699
+ }
700
+ function modalOpen() {
701
+ try {
702
+ return ownerDocument.querySelector(":modal") !== null;
703
+ } catch {
704
+ return true;
705
+ }
706
+ }
707
+ function raise() {
708
+ if (!supportsPopover() || !element.isConnected) return;
709
+ if (popoverOpen() && !modalOpen()) return;
710
+ const focused = ownerDocument.activeElement;
711
+ const keepFocus = focused && element.contains(focused) ? focused : void 0;
712
+ try {
713
+ element.hidePopover();
714
+ element.showPopover();
715
+ } catch {
716
+ return;
717
+ }
718
+ if (keepFocus && ownerDocument.activeElement !== keepFocus) keepFocus.focus({ preventScroll: true });
719
+ }
720
+ function lowerWhenEmpty() {
721
+ if (element.childElementCount > 0 || !supportsPopover()) return;
722
+ try {
723
+ element.hidePopover();
724
+ } catch {
725
+ }
726
+ }
727
+ function engaged(toast) {
728
+ return toast.hovered || toast.focused;
729
+ }
730
+ function stopTimer(toast) {
731
+ clearTimeout(toast.timer);
732
+ toast.timer = void 0;
733
+ }
734
+ function runTimer(toast) {
735
+ stopTimer(toast);
736
+ if (toast.remaining === void 0 || engaged(toast)) return;
737
+ toast.startedAt = Date.now();
738
+ toast.timer = setTimeout(() => close(toast), toast.remaining);
739
+ }
740
+ function pause(toast) {
741
+ if (toast.timer === void 0) return;
742
+ stopTimer(toast);
743
+ if (toast.remaining !== void 0) {
744
+ toast.remaining = Math.max(0, toast.remaining - (Date.now() - toast.startedAt));
745
+ }
746
+ }
747
+ function setEngagement(toast, hovered, focused) {
748
+ if (!live.includes(toast)) return;
749
+ const wasEngaged = engaged(toast);
750
+ toast.hovered = hovered;
751
+ toast.focused = focused;
752
+ if (engaged(toast) === wasEngaged) return;
753
+ if (wasEngaged) {
754
+ if (toast.remaining !== void 0) toast.remaining = Math.max(toast.remaining, TOAST_RESUME_MIN_MS);
755
+ runTimer(toast);
756
+ } else {
757
+ pause(toast);
758
+ }
759
+ }
760
+ function reducedMotion() {
761
+ if (ownerDocument.documentElement.classList.contains(REDUCED_MOTION_CLASS)) return true;
762
+ const view = ownerDocument.defaultView;
763
+ return typeof view?.matchMedia === "function" && view.matchMedia("(prefers-reduced-motion: reduce)").matches;
764
+ }
765
+ function removeElement(toastElement) {
766
+ toastElement.remove();
767
+ lowerWhenEmpty();
768
+ changed();
769
+ }
770
+ function leave(toastElement) {
771
+ toastElement.setAttribute("data-leaving", "");
772
+ toastElement.inert = true;
773
+ if (reducedMotion()) {
774
+ removeElement(toastElement);
775
+ return;
776
+ }
777
+ let done = false;
778
+ const finish = () => {
779
+ if (done) return;
780
+ done = true;
781
+ clearTimeout(fallback);
782
+ leaveTimers.delete(fallback);
783
+ removeElement(toastElement);
784
+ };
785
+ toastElement.addEventListener("animationend", (event) => {
786
+ if (event.target === toastElement && event.animationName === LEAVE_ANIMATION) finish();
787
+ });
788
+ const fallback = setTimeout(finish, TOAST_LEAVE_FALLBACK_MS);
789
+ leaveTimers.add(fallback);
790
+ }
791
+ function close(toast) {
792
+ const index = live.indexOf(toast);
793
+ if (index < 0) return;
794
+ live.splice(index, 1);
795
+ stopTimer(toast);
796
+ silence(toast.request);
797
+ const heldFocus = toast.element.contains(ownerDocument.activeElement);
798
+ if (heldFocus) {
799
+ const next = live[index] ?? live[index - 1];
800
+ if (next) next.closeButton.focus({ preventScroll: true });
801
+ else context.focusFallback();
802
+ }
803
+ leave(toast.element);
804
+ }
805
+ function setAction(toast, action) {
806
+ const previous = toast.actionButton;
807
+ const hadFocus = previous !== void 0 && ownerDocument.activeElement === previous;
808
+ previous?.remove();
809
+ toast.actionButton = void 0;
810
+ if (action) {
811
+ const button = createElement(
812
+ ownerDocument,
813
+ "button",
814
+ { type: "button", class: "cd-shell-toast-action cd-shell-button", "data-variant": "ghost" },
815
+ action.label
816
+ );
817
+ button.addEventListener("click", () => {
818
+ close(toast);
819
+ action.onSelect();
820
+ });
821
+ toast.closeButton.before(button);
822
+ toast.actionButton = button;
823
+ }
824
+ if (hadFocus) (toast.actionButton ?? toast.closeButton).focus({ preventScroll: true });
825
+ }
826
+ function slotOf(toast) {
827
+ return { id: toast.id, hasAction: toast.request.action !== void 0, engaged: engaged(toast) };
828
+ }
829
+ function render(request) {
830
+ const id = ++lastId;
831
+ const textId = `${context.idPrefix}-${id}`;
832
+ const closeButton = createElement(
833
+ ownerDocument,
834
+ "button",
835
+ {
836
+ type: "button",
837
+ class: "cd-shell-toast-close",
838
+ "aria-label": messages.close,
839
+ "aria-describedby": textId
840
+ },
841
+ iconElement("x")
842
+ );
843
+ const toastElement = createElement(
844
+ ownerDocument,
845
+ "div",
846
+ { class: "cd-shell-toast", "data-status": request.status },
847
+ createElement(ownerDocument, "span", { class: "cd-shell-toast-dot", "aria-hidden": "true" }),
848
+ createElement(ownerDocument, "span", { class: "cd-shell-toast-text", id: textId }, request.text),
849
+ closeButton
850
+ );
851
+ const toast = {
852
+ id,
853
+ element: toastElement,
854
+ closeButton,
855
+ handle: Object.freeze({ dismiss: () => close(toast) }),
856
+ request,
857
+ actionButton: void 0,
858
+ hovered: false,
859
+ focused: false,
860
+ remaining: toastLifetime(request),
861
+ startedAt: Date.now(),
862
+ timer: void 0
863
+ };
864
+ setAction(toast, request.action);
865
+ closeButton.addEventListener("click", () => close(toast));
866
+ toastElement.addEventListener("mouseenter", () => setEngagement(toast, true, toast.focused));
867
+ toastElement.addEventListener("mouseleave", () => setEngagement(toast, false, toast.focused));
868
+ toastElement.addEventListener("focusin", () => setEngagement(toast, toast.hovered, true));
869
+ toastElement.addEventListener(
870
+ "focusout",
871
+ (event) => setEngagement(toast, toast.hovered, toastElement.contains(event.relatedTarget))
872
+ );
873
+ return toast;
874
+ }
875
+ function show(request) {
876
+ if (destroyed) return INERT_HANDLE;
877
+ const same = live.find(
878
+ (toast2) => toast2.request.status === request.status && toast2.request.text === request.text
879
+ );
880
+ if (same) {
881
+ same.request = request;
882
+ setAction(same, request.action);
883
+ same.remaining = toastLifetime(request);
884
+ runTimer(same);
885
+ raise();
886
+ announce(request);
887
+ changed();
888
+ return same.handle;
889
+ }
890
+ while (live.length >= MAX_TOASTS) {
891
+ const evictedId = toastToEvict(live.map(slotOf));
892
+ const evicted = live.find((toast2) => toast2.id === evictedId) ?? live[0];
893
+ if (!evicted) break;
894
+ close(evicted);
895
+ }
896
+ const toast = render(request);
897
+ live.push(toast);
898
+ element.append(toast.element);
899
+ raise();
900
+ runTimer(toast);
901
+ announce(request);
902
+ changed();
903
+ return toast.handle;
904
+ }
905
+ return {
906
+ element,
907
+ announcer,
908
+ show,
909
+ setBottom(bottom) {
910
+ element.style.bottom = `${bottom}px`;
911
+ },
912
+ widestCard() {
913
+ let widest;
914
+ for (const card of element.children) {
915
+ const width = card.getBoundingClientRect().width;
916
+ if (width > 0) widest = Math.max(widest ?? 0, width);
917
+ }
918
+ return widest;
919
+ },
920
+ destroy() {
921
+ if (destroyed) return;
922
+ destroyed = true;
923
+ for (const toast of live.splice(0)) stopTimer(toast);
924
+ for (const timer of leaveTimers) clearTimeout(timer);
925
+ leaveTimers.clear();
926
+ for (const timer of announceTimers.values()) clearTimeout(timer);
927
+ announceTimers.clear();
928
+ element.remove();
929
+ announcer.remove();
930
+ }
931
+ };
932
+ }
933
+
934
+ // src/dom/hint.ts
935
+ function createEmptyHint(ownerDocument, messages, options) {
936
+ const { onInsertExample } = options;
937
+ const action = onInsertExample ? createElement(
938
+ ownerDocument,
939
+ "button",
940
+ { type: "button", class: "cd-shell-hint-action" },
941
+ messages.insertExample
942
+ ) : void 0;
943
+ action?.addEventListener("click", () => onInsertExample?.());
944
+ const element = createElement(
945
+ ownerDocument,
946
+ "div",
947
+ { class: "cd-shell-hint" },
948
+ createElement(ownerDocument, "p", { class: "cd-shell-hint-text" }, messages.emptyHint),
949
+ action
950
+ );
951
+ element.hidden = true;
952
+ return {
953
+ element,
954
+ isShown: () => !element.hidden,
955
+ setShown(shown) {
956
+ element.hidden = !shown;
957
+ },
958
+ place({ top, right, bottom, left }) {
959
+ element.style.inset = `${top}px ${right}px ${bottom}px ${left}px`;
960
+ },
961
+ destroy() {
962
+ element.remove();
963
+ }
964
+ };
965
+ }
966
+
967
+ // src/dom/inspector.ts
968
+ function createInspector(ownerDocument, options, onChange) {
969
+ const element = createElement(ownerDocument, "aside", {
970
+ class: "cd-shell-inspector",
971
+ "data-chrome": "inspector",
972
+ "aria-label": options.label
973
+ });
974
+ element.hidden = true;
975
+ function setOpen(open) {
976
+ if (open === !element.hidden) return;
977
+ element.hidden = !open;
978
+ onChange(open);
979
+ }
980
+ return {
981
+ element,
982
+ isOpen: () => !element.hidden,
983
+ open: () => setOpen(true),
984
+ close: () => setOpen(false),
985
+ destroy() {
986
+ element.remove();
987
+ }
988
+ };
989
+ }
990
+
991
+ // src/dom/keys.ts
992
+ function isTextField(target) {
993
+ if (!target || typeof target.closest !== "function") return false;
994
+ const element = target;
995
+ return element.isContentEditable === true || element.closest(TEXT_FIELD_SELECTOR) !== null;
996
+ }
997
+ function isShown(element) {
998
+ if (element.localName === "dialog" && !element.hasAttribute("open")) return false;
999
+ if (element.closest("[hidden]")) return false;
1000
+ return typeof element.checkVisibility !== "function" || element.checkVisibility({ visibilityProperty: true });
1001
+ }
1002
+ function isModal(element) {
1003
+ if (element.getAttribute("aria-modal") === "true") return true;
1004
+ try {
1005
+ return element.matches(":modal");
1006
+ } catch {
1007
+ return false;
1008
+ }
1009
+ }
1010
+ function openDialogs(scope) {
1011
+ return [...scope.querySelectorAll('[role="dialog"], [role="alertdialog"], dialog')].filter(isShown);
1012
+ }
1013
+ function isModalOpen(scope) {
1014
+ return openDialogs(scope).some(isModal);
1015
+ }
1016
+ function createKeyListener(context) {
1017
+ return (event) => {
1018
+ if (event.key === "Escape") context.hideTooltip();
1019
+ const dialogs = openDialogs(context.dialogScope);
1020
+ const decision = decideKey(event, {
1021
+ host: context.host,
1022
+ inTextField: isTextField(event.target),
1023
+ menuOpen: context.menuOpen(),
1024
+ dialogOpen: dialogs.length > 0,
1025
+ modalOpen: dialogs.some(isModal),
1026
+ defaultPrevented: event.defaultPrevented,
1027
+ startScreen: context.startScreen?.() ?? false
1028
+ });
1029
+ if (decision.preventDefault) event.preventDefault();
1030
+ if (decision.stopPropagation) event.stopPropagation();
1031
+ if (decision.action === "closeMenu") context.closeMenu();
1032
+ else if (decision.action) context.run(decision.action);
1033
+ };
1034
+ }
1035
+
1036
+ // src/dom/ledger.ts
1037
+ var Ledger = class {
1038
+ undo = [];
1039
+ originalAttributes = /* @__PURE__ */ new Map();
1040
+ closed = false;
1041
+ get isClosed() {
1042
+ return this.closed;
1043
+ }
1044
+ /** Runs `task` on `close()`, in reverse order of registration. */
1045
+ defer(task) {
1046
+ this.undo.push(task);
1047
+ }
1048
+ /** Adds `className` unless present; `close()` removes it only if this ledger added it. */
1049
+ addClass(element, className) {
1050
+ if (element.classList.contains(className)) return;
1051
+ const hadClassAttribute = element.hasAttribute("class");
1052
+ element.classList.add(className);
1053
+ this.defer(() => {
1054
+ element.classList.remove(className);
1055
+ if (!hadClassAttribute && element.classList.length === 0) element.removeAttribute("class");
1056
+ });
1057
+ }
1058
+ /** Sets (string) or removes (`null`) an attribute; `close()` restores the value before the first change. */
1059
+ attribute(element, name, value) {
1060
+ let originals = this.originalAttributes.get(element);
1061
+ if (!originals) {
1062
+ originals = /* @__PURE__ */ new Map();
1063
+ this.originalAttributes.set(element, originals);
1064
+ }
1065
+ if (!originals.has(name)) {
1066
+ originals.set(name, element.getAttribute(name));
1067
+ this.defer(() => {
1068
+ const original = originals.get(name) ?? null;
1069
+ if (original === null) element.removeAttribute(name);
1070
+ else element.setAttribute(name, original);
1071
+ });
1072
+ }
1073
+ if (value === null) element.removeAttribute(name);
1074
+ else element.setAttribute(name, value);
1075
+ }
1076
+ listen(target, type, listener, options) {
1077
+ const handler = listener;
1078
+ target.addEventListener(type, handler, options);
1079
+ this.defer(() => target.removeEventListener(type, handler, options));
1080
+ }
1081
+ /** Runs every undo task once; later calls are no-ops. */
1082
+ close() {
1083
+ if (this.closed) return;
1084
+ this.closed = true;
1085
+ for (const task of this.undo.reverse()) {
1086
+ try {
1087
+ task();
1088
+ } catch {
1089
+ }
1090
+ }
1091
+ this.undo.length = 0;
1092
+ this.originalAttributes.clear();
1093
+ }
1094
+ };
1095
+
1096
+ // src/dom/legend.ts
1097
+ function createLegend(context) {
1098
+ const { ownerDocument, messages, tooltip, titleId, options } = context;
1099
+ const close = createElement(ownerDocument, "button", {
1100
+ type: "button",
1101
+ class: "cd-shell-legend-close",
1102
+ "aria-label": messages.closeLegend
1103
+ });
1104
+ close.append(iconElement("x"));
1105
+ const body = createElement(ownerDocument, "div", { class: "cd-shell-legend-body" });
1106
+ const element = createElement(
1107
+ ownerDocument,
1108
+ "section",
1109
+ { class: "cd-shell-legend cd-shell-island", "data-chrome": "legend", "aria-labelledby": titleId },
1110
+ createElement(
1111
+ ownerDocument,
1112
+ "div",
1113
+ { class: "cd-shell-legend-header" },
1114
+ createElement(ownerDocument, "div", { class: "cd-shell-legend-title", id: titleId }, messages.legend),
1115
+ close
1116
+ ),
1117
+ body
1118
+ );
1119
+ element.hidden = true;
1120
+ options.render(body);
1121
+ let open = false;
1122
+ let started = false;
1123
+ const detachTooltip = tooltip.attach(close, { label: messages.closeLegend });
1124
+ function apply(next) {
1125
+ started = true;
1126
+ if (next === open) return false;
1127
+ open = next;
1128
+ element.hidden = !open;
1129
+ if (!open && element.contains(ownerDocument.activeElement)) context.focusAfterClose();
1130
+ context.onChange(open);
1131
+ return true;
1132
+ }
1133
+ function setOpen(next) {
1134
+ if (apply(next)) options.onToggle?.(next);
1135
+ }
1136
+ close.addEventListener("click", () => setOpen(false));
1137
+ return {
1138
+ element,
1139
+ body,
1140
+ isOpen: () => open,
1141
+ setOpen,
1142
+ toggle: () => setOpen(!open),
1143
+ start(initial) {
1144
+ if (!started) apply(initial);
1145
+ },
1146
+ isStarted: () => started,
1147
+ destroy() {
1148
+ detachTooltip();
1149
+ element.remove();
1150
+ }
1151
+ };
1152
+ }
1153
+
1154
+ // src/dom/menu.ts
1155
+ var GROUP_ORDER = ["file", "model", "export", "appearance", "help"];
1156
+ function isModelerGroup(name) {
1157
+ return name === "model" || name === "export" || name === "help";
1158
+ }
1159
+ function isPrintable2(event) {
1160
+ return event.key.length === 1 && /\S/.test(event.key) && !event.ctrlKey && !event.metaKey && !event.altKey;
1161
+ }
1162
+ function createMenu(context) {
1163
+ const { root, layer, id, messages, tooltip } = context;
1164
+ const ownerDocument = root.ownerDocument;
1165
+ let groups = context.groups;
1166
+ let items = [];
1167
+ let destroyed = false;
1168
+ const trigger = createElement(ownerDocument, "button", {
1169
+ type: "button",
1170
+ class: "cd-shell-menu-trigger cd-shell-island",
1171
+ "data-chrome": "menu",
1172
+ "aria-label": messages.menu,
1173
+ "aria-haspopup": "menu",
1174
+ "aria-expanded": "false",
1175
+ "aria-controls": id
1176
+ });
1177
+ trigger.append(iconElement("menu"));
1178
+ const dropdown = createElement(ownerDocument, "div", {
1179
+ class: "cd-shell-menu",
1180
+ id,
1181
+ role: "menu",
1182
+ "aria-label": messages.menu
1183
+ });
1184
+ dropdown.hidden = true;
1185
+ layer.prepend(dropdown);
1186
+ const isOpen = () => !dropdown.hidden;
1187
+ function entriesOf(name) {
1188
+ const own = isModelerGroup(name) ? groups[name] ?? [] : [];
1189
+ const added = context.shellEntries(name);
1190
+ return added.length > 0 ? [...own, ...added] : own;
1191
+ }
1192
+ function hasEntries() {
1193
+ return GROUP_ORDER.some((name) => entriesOf(name).length > 0);
1194
+ }
1195
+ function updateTriggerVisibility() {
1196
+ trigger.hidden = !hasEntries();
1197
+ if (trigger.hidden) close();
1198
+ }
1199
+ function isDisabled(entry, group) {
1200
+ if (group === "model" && context.isReadOnly() && entry.keepEnabledWhenReadOnly !== true) return true;
1201
+ return entry.disabled?.() ?? false;
1202
+ }
1203
+ function itemElement(role, label, options) {
1204
+ const iconSlot = createElement(ownerDocument, "span", { class: "cd-shell-menu-item-icon" });
1205
+ if (options.icon) iconSlot.append(iconElement(options.icon));
1206
+ const element = createElement(
1207
+ ownerDocument,
1208
+ "button",
1209
+ { type: "button", class: "cd-shell-menu-item", role, tabindex: "-1" },
1210
+ iconSlot,
1211
+ createElement(ownerDocument, "span", { class: "cd-shell-menu-item-label" }, label),
1212
+ options.end && createElement(ownerDocument, "span", { class: "cd-shell-menu-item-end" }, options.end)
1213
+ );
1214
+ toggleAttribute(element, "aria-disabled", options.disabled && "true");
1215
+ if (options.checked !== void 0) element.setAttribute("aria-checked", String(options.checked));
1216
+ return element;
1217
+ }
1218
+ function checkMark() {
1219
+ return iconElement("check", { className: "cd-shell-menu-check" });
1220
+ }
1221
+ function renderEntry(entry, group) {
1222
+ const disabled = isDisabled(entry, group);
1223
+ if (entry.kind === "checkbox") {
1224
+ const checked = entry.checked();
1225
+ const element2 = itemElement("menuitemcheckbox", entry.label, {
1226
+ icon: entry.icon,
1227
+ end: checkMark(),
1228
+ disabled,
1229
+ checked
1230
+ });
1231
+ element2.dataset.menuId = entry.id;
1232
+ element2.dataset.menuGroup = group;
1233
+ return {
1234
+ key: entry.id,
1235
+ element: element2,
1236
+ label: entry.label,
1237
+ disabled,
1238
+ activate() {
1239
+ entry.onToggle(!entry.checked());
1240
+ refresh();
1241
+ }
1242
+ };
1243
+ }
1244
+ const shortcut = entry.shortcut ? createElement(ownerDocument, "span", { class: "cd-shell-menu-shortcut" }, entry.shortcut) : void 0;
1245
+ const element = itemElement("menuitem", entry.label, { icon: entry.icon, end: shortcut, disabled });
1246
+ element.dataset.menuId = entry.id;
1247
+ element.dataset.menuGroup = group;
1248
+ const keyShortcuts = entry.ariaKeyShortcuts ?? (entry.shortcut ? ariaKeyShortcutsFromLabel(entry.shortcut) : void 0);
1249
+ toggleAttribute(element, "aria-keyshortcuts", keyShortcuts || null);
1250
+ if (entry.kind === "dialog") element.setAttribute("aria-haspopup", "dialog");
1251
+ if (entry.kind === "action" && entry.danger) element.dataset.danger = "";
1252
+ return {
1253
+ key: entry.id,
1254
+ element,
1255
+ label: entry.label,
1256
+ disabled,
1257
+ activate() {
1258
+ close({ restoreFocus: true });
1259
+ entry.onSelect();
1260
+ }
1261
+ };
1262
+ }
1263
+ function renderRadioGroup(entry, group) {
1264
+ const disabled = isDisabled(entry, group);
1265
+ const value = entry.value();
1266
+ const container = createElement(
1267
+ ownerDocument,
1268
+ "div",
1269
+ {
1270
+ class: "cd-shell-menu-group",
1271
+ role: "group",
1272
+ "aria-label": entry.label,
1273
+ "data-menu-id": entry.id,
1274
+ "data-menu-group": group
1275
+ },
1276
+ // The group's aria-label carries the heading; a menu holds no loose text.
1277
+ createElement(
1278
+ ownerDocument,
1279
+ "div",
1280
+ { class: "cd-shell-menu-heading", "aria-hidden": "true" },
1281
+ entry.label
1282
+ )
1283
+ );
1284
+ const radioItems = entry.options.map((option) => {
1285
+ const element = itemElement("menuitemradio", option.label, {
1286
+ end: checkMark(),
1287
+ disabled,
1288
+ checked: option.value === value
1289
+ });
1290
+ element.dataset.menuValue = option.value;
1291
+ container.append(element);
1292
+ return {
1293
+ key: `${entry.id}\0${option.value}`,
1294
+ element,
1295
+ label: option.label,
1296
+ disabled,
1297
+ activate() {
1298
+ if (entry.value() !== option.value) entry.onSelect(option.value);
1299
+ refresh();
1300
+ }
1301
+ };
1302
+ });
1303
+ return { element: container, items: radioItems };
1304
+ }
1305
+ function render() {
1306
+ const focusedKey = items.find((item) => item.element === ownerDocument.activeElement)?.key;
1307
+ const nodes = [];
1308
+ const rendered = [];
1309
+ for (const name of GROUP_ORDER) {
1310
+ const entries = entriesOf(name);
1311
+ if (entries.length === 0) continue;
1312
+ if (nodes.length > 0) {
1313
+ nodes.push(
1314
+ createElement(ownerDocument, "div", { class: "cd-shell-menu-separator", role: "separator" })
1315
+ );
1316
+ }
1317
+ for (const entry of entries) {
1318
+ if (entry.kind === "radio") {
1319
+ const radio = renderRadioGroup(entry, name);
1320
+ nodes.push(radio.element);
1321
+ rendered.push(...radio.items);
1322
+ } else {
1323
+ const item = renderEntry(entry, name);
1324
+ nodes.push(item.element);
1325
+ rendered.push(item);
1326
+ }
1327
+ }
1328
+ }
1329
+ for (const item of rendered) {
1330
+ item.element.addEventListener("click", () => {
1331
+ if (!item.disabled) item.activate();
1332
+ });
1333
+ }
1334
+ dropdown.replaceChildren(...nodes);
1335
+ items = rendered;
1336
+ if (focusedKey !== void 0 && isOpen()) {
1337
+ (items.find((item) => item.key === focusedKey) ?? items[0])?.element.focus();
1338
+ }
1339
+ }
1340
+ function focusAt(index) {
1341
+ if (items.length === 0) return;
1342
+ items[(index + items.length) % items.length]?.element.focus();
1343
+ }
1344
+ function currentIndex() {
1345
+ return items.findIndex((item) => item.element === ownerDocument.activeElement);
1346
+ }
1347
+ function typeAhead(character) {
1348
+ const start = currentIndex();
1349
+ const lower = character.toLowerCase();
1350
+ for (let step = 1; step <= items.length; step++) {
1351
+ const index = (Math.max(start, -1) + step) % items.length;
1352
+ if (items[index]?.label.toLowerCase().startsWith(lower)) {
1353
+ focusAt(index);
1354
+ return;
1355
+ }
1356
+ }
1357
+ }
1358
+ const onOutsidePointerDown = (event) => {
1359
+ const target = event.target;
1360
+ if (target && (dropdown.contains(target) || trigger.contains(target))) return;
1361
+ close();
1362
+ };
1363
+ const onWindowBlur = () => close();
1364
+ function open(focus = "first") {
1365
+ if (destroyed || !hasEntries()) return;
1366
+ tooltip.hide();
1367
+ const wasOpen = isOpen();
1368
+ render();
1369
+ if (!wasOpen) {
1370
+ dropdown.hidden = false;
1371
+ trigger.setAttribute("aria-expanded", "true");
1372
+ ownerDocument.addEventListener("pointerdown", onOutsidePointerDown, true);
1373
+ ownerDocument.defaultView?.addEventListener("blur", onWindowBlur);
1374
+ }
1375
+ focusAt(focus === "last" ? items.length - 1 : 0);
1376
+ }
1377
+ function close(options = {}) {
1378
+ if (!isOpen()) return;
1379
+ dropdown.hidden = true;
1380
+ trigger.setAttribute("aria-expanded", "false");
1381
+ ownerDocument.removeEventListener("pointerdown", onOutsidePointerDown, true);
1382
+ ownerDocument.defaultView?.removeEventListener("blur", onWindowBlur);
1383
+ if (options.restoreFocus && !trigger.hidden) trigger.focus();
1384
+ }
1385
+ function refresh() {
1386
+ if (isOpen()) render();
1387
+ }
1388
+ trigger.addEventListener("click", () => {
1389
+ if (isOpen()) close();
1390
+ else open("first");
1391
+ });
1392
+ function consume(event) {
1393
+ event.preventDefault();
1394
+ event.stopPropagation();
1395
+ }
1396
+ trigger.addEventListener("keydown", (event) => {
1397
+ if (event.key === "Enter" || event.key === " " || event.key === "ArrowDown") {
1398
+ consume(event);
1399
+ open("first");
1400
+ } else if (event.key === "ArrowUp") {
1401
+ consume(event);
1402
+ open("last");
1403
+ }
1404
+ });
1405
+ dropdown.addEventListener("keydown", (event) => {
1406
+ if (matchChromeKey(event)) return;
1407
+ switch (event.key) {
1408
+ case "ArrowDown":
1409
+ focusAt(currentIndex() + 1);
1410
+ break;
1411
+ case "ArrowUp": {
1412
+ const index = currentIndex();
1413
+ focusAt(index < 0 ? items.length - 1 : index - 1);
1414
+ break;
1415
+ }
1416
+ case "Home":
1417
+ focusAt(0);
1418
+ break;
1419
+ case "End":
1420
+ focusAt(items.length - 1);
1421
+ break;
1422
+ case "Tab":
1423
+ close({ restoreFocus: true });
1424
+ return;
1425
+ case "Escape":
1426
+ return;
1427
+ case "Enter":
1428
+ case " ":
1429
+ case "Delete":
1430
+ case "Backspace":
1431
+ event.stopPropagation();
1432
+ return;
1433
+ default:
1434
+ if (!isPrintable2(event)) return;
1435
+ typeAhead(event.key);
1436
+ }
1437
+ consume(event);
1438
+ });
1439
+ updateTriggerVisibility();
1440
+ return {
1441
+ trigger,
1442
+ dropdown,
1443
+ isOpen,
1444
+ open,
1445
+ close,
1446
+ refresh,
1447
+ setGroups(next) {
1448
+ groups = next;
1449
+ updateTriggerVisibility();
1450
+ refresh();
1451
+ },
1452
+ destroy() {
1453
+ if (destroyed) return;
1454
+ close();
1455
+ destroyed = true;
1456
+ trigger.remove();
1457
+ dropdown.remove();
1458
+ }
1459
+ };
1460
+ }
1461
+
1462
+ // src/dom/problems.ts
1463
+ function createProblemsPill(context) {
1464
+ const { ownerDocument, messages, lang, tooltip } = context;
1465
+ const icon = createElement(ownerDocument, "span", { class: "cd-shell-problems-icon" });
1466
+ const text = createElement(ownerDocument, "span", { class: "cd-shell-problems-text" });
1467
+ const element = createElement(
1468
+ ownerDocument,
1469
+ "button",
1470
+ { type: "button", class: "cd-shell-problems cd-shell-island", "data-chrome": "problems" },
1471
+ icon,
1472
+ text
1473
+ );
1474
+ element.hidden = true;
1475
+ element.addEventListener("click", () => context.onActivate());
1476
+ const detachTooltip = context.showTooltip ? tooltip.attach(element, { label: messages.showProblems }) : void 0;
1477
+ let severity;
1478
+ return {
1479
+ element,
1480
+ isShown: () => !element.hidden,
1481
+ set({ count, errors }) {
1482
+ const shown = count > 0;
1483
+ element.hidden = !shown;
1484
+ if (!shown) {
1485
+ tooltip.hide();
1486
+ return;
1487
+ }
1488
+ const formattedCount = formatNumber(count, lang);
1489
+ text.textContent = messages.problems(count, formattedCount);
1490
+ element.setAttribute(
1491
+ "aria-label",
1492
+ messages.problemsLabel(count, errors, formattedCount, formatNumber(errors, lang))
1493
+ );
1494
+ const next = errors > 0 ? "danger" : "warning";
1495
+ if (next !== severity) {
1496
+ severity = next;
1497
+ element.dataset.severity = next;
1498
+ icon.replaceChildren(iconElement(next === "danger" ? "circle-x" : "triangle-alert"));
1499
+ }
1500
+ },
1501
+ destroy() {
1502
+ detachTooltip?.();
1503
+ element.remove();
1504
+ }
1505
+ };
1506
+ }
1507
+
1508
+ // src/dom/status.ts
1509
+ function createStatusBox(ownerDocument) {
1510
+ const icon = createElement(ownerDocument, "span", { class: "cd-shell-status-icon" });
1511
+ const text = createElement(ownerDocument, "p", { class: "cd-shell-status-text" });
1512
+ const element = createElement(
1513
+ ownerDocument,
1514
+ "div",
1515
+ { class: "cd-shell-status", "data-chrome": "status" },
1516
+ icon,
1517
+ text
1518
+ );
1519
+ element.hidden = true;
1520
+ let pending;
1521
+ function cancelPending() {
1522
+ if (pending) clearTimeout(pending.timer);
1523
+ pending = void 0;
1524
+ }
1525
+ function show(status) {
1526
+ pending = void 0;
1527
+ if (element.dataset.severity !== status.severity) {
1528
+ element.dataset.severity = status.severity;
1529
+ element.setAttribute("role", status.severity === "danger" ? "alert" : "status");
1530
+ icon.replaceChildren(iconElement(status.severity === "danger" ? "circle-x" : "triangle-alert"));
1531
+ }
1532
+ if (text.textContent !== status.text) text.textContent = status.text;
1533
+ element.hidden = false;
1534
+ }
1535
+ return {
1536
+ element,
1537
+ set(status, delayMs) {
1538
+ if (status === null) {
1539
+ cancelPending();
1540
+ element.hidden = true;
1541
+ return;
1542
+ }
1543
+ if (!element.hidden) {
1544
+ show(status);
1545
+ return;
1546
+ }
1547
+ if (pending) {
1548
+ pending.status = status;
1549
+ return;
1550
+ }
1551
+ if (delayMs > 0) {
1552
+ const entry = { status, timer: setTimeout(() => show(entry.status), delayMs) };
1553
+ pending = entry;
1554
+ } else {
1555
+ show(status);
1556
+ }
1557
+ },
1558
+ destroy() {
1559
+ cancelPending();
1560
+ element.remove();
1561
+ }
1562
+ };
1563
+ }
1564
+
1565
+ // src/dom/tooltip.ts
1566
+ function createTooltip({ root, layer, id }) {
1567
+ const ownerDocument = root.ownerDocument;
1568
+ const label = createElement(ownerDocument, "span", { class: "cd-shell-tooltip-label" });
1569
+ const shortcut = createElement(ownerDocument, "kbd", { class: "cd-shell-kbd" });
1570
+ const element = createElement(ownerDocument, "div", { class: "cd-shell-tooltip", role: "tooltip", id });
1571
+ element.hidden = true;
1572
+ element.append(label, shortcut);
1573
+ layer.append(element);
1574
+ let shown;
1575
+ let pending;
1576
+ let warmUntil = 0;
1577
+ let pressed;
1578
+ const detachers = /* @__PURE__ */ new Set();
1579
+ function cancelPending() {
1580
+ if (pending) clearTimeout(pending.timer);
1581
+ pending = void 0;
1582
+ }
1583
+ function resolve(source) {
1584
+ return typeof source === "function" ? source() : source;
1585
+ }
1586
+ function place(control) {
1587
+ const island = control.closest("[data-chrome]") ?? control;
1588
+ const point = placeTooltip({
1589
+ anchor: boxRelativeTo(control, root),
1590
+ island: boxRelativeTo(island, root),
1591
+ tooltip: { width: element.offsetWidth, height: element.offsetHeight },
1592
+ container: { width: root.clientWidth, height: root.clientHeight }
1593
+ });
1594
+ element.style.left = `${point.x}px`;
1595
+ element.style.top = `${point.y}px`;
1596
+ }
1597
+ function hide() {
1598
+ cancelPending();
1599
+ if (!shown) return;
1600
+ const { control, describedBy } = shown;
1601
+ shown = void 0;
1602
+ element.hidden = true;
1603
+ if (describedBy === null) control.removeAttribute("aria-describedby");
1604
+ else control.setAttribute("aria-describedby", describedBy);
1605
+ }
1606
+ function show(control, source) {
1607
+ cancelPending();
1608
+ const content = resolve(source);
1609
+ if (!content || !control.isConnected) {
1610
+ if (shown?.control === control) hide();
1611
+ return;
1612
+ }
1613
+ if (shown && shown.control !== control) hide();
1614
+ label.textContent = content.label;
1615
+ shortcut.textContent = content.shortcut ?? "";
1616
+ shortcut.hidden = !content.shortcut;
1617
+ element.hidden = false;
1618
+ if (!shown) {
1619
+ const describedBy = control.getAttribute("aria-describedby");
1620
+ shown = { control, describedBy };
1621
+ const saysMore = !accessibleName(control).includes(content.label) || Boolean(content.shortcut) && !control.hasAttribute("aria-keyshortcuts");
1622
+ if (saysMore) {
1623
+ control.setAttribute("aria-describedby", [describedBy, id].filter(Boolean).join(" "));
1624
+ }
1625
+ }
1626
+ place(control);
1627
+ }
1628
+ function onEnter(control, source, event) {
1629
+ if (event.pointerType === "touch" || pressed === control) return;
1630
+ if (shown || Date.now() < warmUntil) {
1631
+ show(control, source);
1632
+ return;
1633
+ }
1634
+ cancelPending();
1635
+ pending = { control, timer: setTimeout(() => show(control, source), TOOLTIP_DELAY_MS) };
1636
+ }
1637
+ function onLeave(control) {
1638
+ if (pressed === control) pressed = void 0;
1639
+ if (pending?.control === control) cancelPending();
1640
+ if (shown?.control === control) {
1641
+ hide();
1642
+ warmUntil = Date.now() + TOOLTIP_DELAY_MS;
1643
+ }
1644
+ }
1645
+ function onFocus(control, source) {
1646
+ if (hasVisibleFocus(control)) show(control, source);
1647
+ }
1648
+ function onBlur(control) {
1649
+ if (pending?.control === control) cancelPending();
1650
+ if (shown?.control === control) hide();
1651
+ }
1652
+ function onPress(control, event) {
1653
+ if (event.type === "pointerdown") pressed = control;
1654
+ if (pending?.control === control) cancelPending();
1655
+ if (shown?.control === control) hide();
1656
+ }
1657
+ function attach(control, source) {
1658
+ const enter = (event) => onEnter(control, source, event);
1659
+ const leave = () => onLeave(control);
1660
+ const focus = () => onFocus(control, source);
1661
+ const blur = () => onBlur(control);
1662
+ const press = (event) => onPress(control, event);
1663
+ control.addEventListener("pointerenter", enter);
1664
+ control.addEventListener("pointerleave", leave);
1665
+ control.addEventListener("focus", focus);
1666
+ control.addEventListener("blur", blur);
1667
+ control.addEventListener("pointerdown", press);
1668
+ control.addEventListener("click", press);
1669
+ const detach = () => {
1670
+ if (!detachers.delete(detach)) return;
1671
+ control.removeEventListener("pointerenter", enter);
1672
+ control.removeEventListener("pointerleave", leave);
1673
+ control.removeEventListener("focus", focus);
1674
+ control.removeEventListener("blur", blur);
1675
+ control.removeEventListener("pointerdown", press);
1676
+ control.removeEventListener("click", press);
1677
+ if (shown?.control === control || pending?.control === control) hide();
1678
+ if (pressed === control) pressed = void 0;
1679
+ };
1680
+ detachers.add(detach);
1681
+ return detach;
1682
+ }
1683
+ function delegate(container, selector, content) {
1684
+ let hovered;
1685
+ const controlOf = (target) => {
1686
+ if (!target || typeof target.closest !== "function") return void 0;
1687
+ const match = target.closest(selector);
1688
+ return match && container.contains(match) ? match : void 0;
1689
+ };
1690
+ const sourceOf = (control) => () => content(control);
1691
+ const over = (event) => {
1692
+ const control = controlOf(event.target);
1693
+ if (control === hovered) return;
1694
+ if (hovered) onLeave(hovered);
1695
+ hovered = control;
1696
+ if (control) onEnter(control, sourceOf(control), event);
1697
+ };
1698
+ const out = (event) => {
1699
+ if (!hovered) return;
1700
+ if (controlOf(event.relatedTarget) === hovered) return;
1701
+ onLeave(hovered);
1702
+ hovered = void 0;
1703
+ };
1704
+ const focusIn = (event) => {
1705
+ const control = controlOf(event.target);
1706
+ if (control) onFocus(control, sourceOf(control));
1707
+ };
1708
+ const focusOut = (event) => {
1709
+ const control = controlOf(event.target);
1710
+ if (control) onBlur(control);
1711
+ };
1712
+ const press = (event) => {
1713
+ const control = controlOf(event.target);
1714
+ if (control) onPress(control, event);
1715
+ };
1716
+ container.addEventListener("pointerover", over);
1717
+ container.addEventListener("pointerout", out);
1718
+ container.addEventListener("focusin", focusIn);
1719
+ container.addEventListener("focusout", focusOut);
1720
+ container.addEventListener("pointerdown", press);
1721
+ container.addEventListener("click", press);
1722
+ const detach = () => {
1723
+ if (!detachers.delete(detach)) return;
1724
+ container.removeEventListener("pointerover", over);
1725
+ container.removeEventListener("pointerout", out);
1726
+ container.removeEventListener("focusin", focusIn);
1727
+ container.removeEventListener("focusout", focusOut);
1728
+ container.removeEventListener("pointerdown", press);
1729
+ container.removeEventListener("click", press);
1730
+ if (shown && container.contains(shown.control) || pending && container.contains(pending.control)) {
1731
+ hide();
1732
+ }
1733
+ hovered = void 0;
1734
+ };
1735
+ detachers.add(detach);
1736
+ return detach;
1737
+ }
1738
+ return {
1739
+ element,
1740
+ attach,
1741
+ delegate,
1742
+ hide,
1743
+ destroy() {
1744
+ hide();
1745
+ for (const detach of [...detachers]) detach();
1746
+ element.remove();
1747
+ }
1748
+ };
1749
+ }
1750
+
1751
+ // src/dom/web/drop-zone.ts
1752
+ var DRAG_EVENTS = ["dragenter", "dragover", "dragleave", "drop"];
1753
+ function carriesFiles(transfer) {
1754
+ if (!transfer) return false;
1755
+ if (Array.from(transfer.types ?? []).includes("Files")) return true;
1756
+ return Array.from(transfer.items ?? []).some((item) => item.kind === "file");
1757
+ }
1758
+ function dragItems(transfer) {
1759
+ const items = Array.from(transfer.items ?? [], (item) => ({ kind: item.kind, type: item.type }));
1760
+ return items.length > 0 ? items : [{ kind: "file", type: "" }];
1761
+ }
1762
+ function acceptList(accept, lang) {
1763
+ const entries = accept.map((entry) => entry.trim()).filter((entry) => entry !== "");
1764
+ const extensions = entries.filter((entry) => entry.startsWith("."));
1765
+ const listed = extensions.length > 0 ? extensions : entries;
1766
+ let format;
1767
+ try {
1768
+ format = new Intl.ListFormat(lang, { type: "disjunction" });
1769
+ } catch {
1770
+ format = new Intl.ListFormat("en", { type: "disjunction" });
1771
+ }
1772
+ return format.format(listed);
1773
+ }
1774
+ function createDropZone(context) {
1775
+ const { ownerDocument, layer, messages, lang, options, modalOpen, toast, setState } = context;
1776
+ const chip = createElement(ownerDocument, "span", { class: "cd-shell-drop-zone-chip" });
1777
+ const element = createElement(
1778
+ ownerDocument,
1779
+ "div",
1780
+ { class: "cd-shell-drop-zone", "data-chrome": "drop-zone", "aria-hidden": "true" },
1781
+ chip
1782
+ );
1783
+ element.hidden = true;
1784
+ layer.append(element);
1785
+ const view = ownerDocument.defaultView;
1786
+ let depth = 0;
1787
+ let current = null;
1788
+ function verdictFor(items) {
1789
+ return dragVerdict(items, {
1790
+ accept: options.accept,
1791
+ multiple: options.multiple ?? false,
1792
+ dialogOpen: modalOpen()
1793
+ });
1794
+ }
1795
+ function show(verdict) {
1796
+ const next = verdict.state === "none" ? null : verdict.state;
1797
+ if (verdict.state === "over") chip.textContent = messages.dropFile;
1798
+ if (verdict.state === "forbidden") chip.textContent = messages.dropForbidden(verdict.reason);
1799
+ if (next === current) return;
1800
+ current = next;
1801
+ element.hidden = next === null;
1802
+ if (next === null) element.removeAttribute("data-state");
1803
+ else element.setAttribute("data-state", next);
1804
+ setState(next);
1805
+ }
1806
+ function track(transfer) {
1807
+ const verdict = verdictFor(dragItems(transfer));
1808
+ show(verdict);
1809
+ transfer.dropEffect = verdict.state === "forbidden" ? "none" : "copy";
1810
+ }
1811
+ function rejectByDefault(files) {
1812
+ const accepted = acceptList(options.accept, lang);
1813
+ for (const file of files) toast({ status: "danger", text: messages.cannotOpenFile(file.name, accepted) });
1814
+ }
1815
+ function receive(files, verdict) {
1816
+ if (verdict.state === "forbidden" && verdict.reason !== "type") return;
1817
+ const accepted = files.filter((file) => acceptsFile(file, options.accept));
1818
+ const rejected = files.filter((file) => !accepted.includes(file));
1819
+ if (accepted.length > 0) options.onDrop(accepted);
1820
+ if (rejected.length > 0) (options.onReject ?? rejectByDefault)(rejected);
1821
+ }
1822
+ const onDragEvent = (event) => {
1823
+ const transfer = event.dataTransfer;
1824
+ if (!carriesFiles(transfer)) return;
1825
+ event.preventDefault();
1826
+ switch (event.type) {
1827
+ case "dragenter":
1828
+ depth += 1;
1829
+ track(transfer);
1830
+ break;
1831
+ case "dragover":
1832
+ depth = Math.max(depth, 1);
1833
+ track(transfer);
1834
+ break;
1835
+ case "dragleave":
1836
+ depth = Math.max(0, depth - 1);
1837
+ if (depth === 0) show({ state: "none" });
1838
+ break;
1839
+ case "drop": {
1840
+ depth = 0;
1841
+ show({ state: "none" });
1842
+ const files = Array.from(transfer.files ?? []);
1843
+ const items = files.length > 0 ? files.map((file) => ({ kind: "file", type: file.type })) : dragItems(transfer);
1844
+ receive(files, verdictFor(items));
1845
+ break;
1846
+ }
1847
+ }
1848
+ };
1849
+ for (const type of DRAG_EVENTS) view?.addEventListener(type, onDragEvent);
1850
+ return {
1851
+ element,
1852
+ state: () => current,
1853
+ destroy() {
1854
+ for (const type of DRAG_EVENTS) view?.removeEventListener(type, onDragEvent);
1855
+ depth = 0;
1856
+ if (current !== null) {
1857
+ current = null;
1858
+ setState(null);
1859
+ }
1860
+ element.remove();
1861
+ }
1862
+ };
1863
+ }
1864
+
1865
+ // src/dom/web/empty-state.ts
1866
+ var KOMET_SIZE = "64";
1867
+ function createEmptyState(context) {
1868
+ const { ownerDocument, messages, options, dropHint, open, titleId, onLeave } = context;
1869
+ const button = (variant, label, run) => {
1870
+ const element2 = createElement(
1871
+ ownerDocument,
1872
+ "button",
1873
+ { type: "button", class: "cd-shell-button", "data-variant": variant },
1874
+ label
1875
+ );
1876
+ element2.addEventListener("click", () => {
1877
+ onLeave();
1878
+ run();
1879
+ });
1880
+ return element2;
1881
+ };
1882
+ const onShowExample = options.onShowExample;
1883
+ const newDiagram = button("primary", messages.newDiagram, () => options.onNewDiagram());
1884
+ const element = createElement(
1885
+ ownerDocument,
1886
+ "section",
1887
+ { class: "cd-shell-empty-state", "data-chrome": "empty-state", "aria-labelledby": titleId },
1888
+ // Dragging the mark would start an image drag that some browsers carry as a file.
1889
+ createElement(ownerDocument, "img", {
1890
+ class: "cd-shell-empty-state-mark",
1891
+ src: options.komet,
1892
+ width: KOMET_SIZE,
1893
+ height: KOMET_SIZE,
1894
+ alt: "",
1895
+ draggable: "false"
1896
+ }),
1897
+ createElement(ownerDocument, "h1", { class: "cd-shell-empty-state-title", id: titleId }, options.title),
1898
+ createElement(ownerDocument, "p", { class: "cd-shell-empty-state-text" }, options.text),
1899
+ createElement(
1900
+ ownerDocument,
1901
+ "div",
1902
+ { class: "cd-shell-empty-state-actions" },
1903
+ newDiagram,
1904
+ onShowExample && button("ghost", messages.showExample, () => onShowExample())
1905
+ ),
1906
+ hintParagraph()
1907
+ );
1908
+ function hintParagraph() {
1909
+ const hasDropHint = dropHint !== void 0 && dropHint !== "";
1910
+ if (!hasDropHint && !open) return void 0;
1911
+ const paragraph = createElement(ownerDocument, "p", { class: "cd-shell-empty-state-hint" });
1912
+ if (hasDropHint) paragraph.append(dropHint);
1913
+ if (!open) return paragraph;
1914
+ const shortcut = open.shortcut?.trim();
1915
+ const link = createElement(
1916
+ ownerDocument,
1917
+ "button",
1918
+ {
1919
+ type: "button",
1920
+ class: "cd-shell-empty-state-open",
1921
+ "aria-keyshortcuts": shortcut ? ariaKeyShortcutsFromLabel(shortcut) : void 0,
1922
+ "aria-haspopup": open.dialog ? "dialog" : void 0
1923
+ },
1924
+ messages.openFileLink
1925
+ );
1926
+ link.addEventListener("click", () => open.onSelect());
1927
+ if (hasDropHint) {
1928
+ paragraph.append(" ", createElement(ownerDocument, "span", { "aria-hidden": "true" }, "\xB7"), " ");
1929
+ }
1930
+ const openItem = createElement(ownerDocument, "span", { class: "cd-shell-empty-state-open-item" }, link);
1931
+ if (shortcut) {
1932
+ openItem.append(
1933
+ " ",
1934
+ createElement(
1935
+ ownerDocument,
1936
+ "kbd",
1937
+ { class: "cd-shell-empty-state-key", "aria-hidden": "true" },
1938
+ shortcut
1939
+ )
1940
+ );
1941
+ }
1942
+ paragraph.append(openItem);
1943
+ return paragraph;
1944
+ }
1945
+ element.hidden = true;
1946
+ return {
1947
+ element,
1948
+ isShown: () => !element.hidden,
1949
+ setShown(shown) {
1950
+ element.hidden = !shown;
1951
+ },
1952
+ focus() {
1953
+ newDiagram.focus({ preventScroll: true });
1954
+ },
1955
+ destroy() {
1956
+ element.remove();
1957
+ }
1958
+ };
1959
+ }
1960
+
1961
+ // src/dom/web/legal.ts
1962
+ var MIRAGON_HOME = "https://www.miragon.io/";
1963
+ var MIRAGON_IMPRESSUM = "https://www.miragon.io/impressum/";
1964
+ var MIRAGON_DATENSCHUTZ = "https://www.miragon.io/datenschutz/";
1965
+ function createLegalLine(context) {
1966
+ const { ownerDocument, messages, options } = context;
1967
+ const year = options.year ?? (/* @__PURE__ */ new Date()).getFullYear();
1968
+ const newTabId = `${context.idPrefix}-new-tab`;
1969
+ const link = (label, href, lang) => createElement(
1970
+ ownerDocument,
1971
+ "a",
1972
+ { href, lang, target: "_blank", rel: "noopener noreferrer", "aria-describedby": newTabId },
1973
+ label
1974
+ );
1975
+ const separator = () => createElement(ownerDocument, "span", { "aria-hidden": "true" }, "\xB7");
1976
+ const item = (anchor) => createElement(ownerDocument, "span", { class: "cd-shell-legal-item" }, separator(), anchor);
1977
+ const links = [
1978
+ link(messages.copyright(year), MIRAGON_HOME),
1979
+ link("Impressum", MIRAGON_IMPRESSUM, "de"),
1980
+ link("Datenschutz", MIRAGON_DATENSCHUTZ, "de"),
1981
+ ...(options.links ?? []).map((extra) => link(extra.label, extra.href, extra.lang))
1982
+ ];
1983
+ const element = createElement(ownerDocument, "nav", {
1984
+ class: "cd-shell-legal",
1985
+ "data-chrome": "legal",
1986
+ "aria-label": messages.legal
1987
+ });
1988
+ element.append(
1989
+ ...links.map((anchor, index) => index === 0 ? anchor : item(anchor)),
1990
+ createElement(ownerDocument, "span", { id: newTabId, hidden: "" }, messages.opensInNewTab)
1991
+ );
1992
+ return {
1993
+ element,
1994
+ destroy() {
1995
+ element.remove();
1996
+ }
1997
+ };
1998
+ }
1999
+
2000
+ // src/dom/web/menu-groups.ts
2001
+ function fileEntry(spec, action) {
2002
+ const common = {
2003
+ id: spec.id,
2004
+ label: spec.label,
2005
+ ...spec.icon ? { icon: spec.icon } : {},
2006
+ ...action.disabled ? { disabled: action.disabled } : {},
2007
+ ...action.shortcut ? { shortcut: action.shortcut } : {},
2008
+ onSelect: () => action.onSelect()
2009
+ };
2010
+ return action.dialog ? { kind: "dialog", ...common } : { kind: "action", ...common };
2011
+ }
2012
+ function fileMenuEntries(options, messages) {
2013
+ const specs = [
2014
+ { option: "newDiagram", id: "new-diagram", label: messages.newDiagram, icon: "file-plus" },
2015
+ { option: "open", id: "open", label: messages.openFile, icon: "folder-open" },
2016
+ // §5.14 gives "Show example" no icon.
2017
+ { option: "showExample", id: "show-example", label: messages.showExample, icon: void 0 },
2018
+ { option: "import", id: "import", label: messages.importFile, icon: "import" }
2019
+ ];
2020
+ return specs.flatMap((spec) => {
2021
+ const action = options[spec.option];
2022
+ return action ? [fileEntry(spec, action)] : [];
2023
+ });
2024
+ }
2025
+ function appearanceMenuEntries(appearance, messages) {
2026
+ return [
2027
+ {
2028
+ kind: "radio",
2029
+ id: "appearance",
2030
+ label: messages.appearance,
2031
+ options: [
2032
+ { value: "system", label: messages.appearanceSystem },
2033
+ { value: "light", label: messages.appearanceLight },
2034
+ { value: "dark", label: messages.appearanceDark }
2035
+ ],
2036
+ value: () => appearance.choice(),
2037
+ onSelect: (value) => appearance.setChoice(parseAppearanceChoice(value))
2038
+ }
2039
+ ];
2040
+ }
2041
+
2042
+ // src/dom/web/primary.ts
2043
+ var COLLAPSED_ATTRIBUTE = "data-collapsed";
2044
+ function createPrimaryAction(context) {
2045
+ const { ownerDocument, messages, tooltip, options } = context;
2046
+ const label = options.label ?? messages.share;
2047
+ const labelElement = createElement(ownerDocument, "span", { class: "cd-shell-primary-label" }, label);
2048
+ const element = createElement(
2049
+ ownerDocument,
2050
+ "button",
2051
+ {
2052
+ type: "button",
2053
+ class: "cd-shell-primary cd-shell-button",
2054
+ "data-variant": "primary",
2055
+ "data-chrome": "primary",
2056
+ "aria-label": label
2057
+ },
2058
+ iconElement(options.icon ?? "share-2"),
2059
+ labelElement
2060
+ );
2061
+ let collapsed = false;
2062
+ let disabled = false;
2063
+ let labelledWidth;
2064
+ function readDisabled() {
2065
+ disabled = options.disabled?.() ?? false;
2066
+ toggleAttribute(element, "aria-disabled", disabled ? "true" : null);
2067
+ }
2068
+ function showCollapsed(state) {
2069
+ labelElement.hidden = state;
2070
+ toggleAttribute(element, COLLAPSED_ATTRIBUTE, state ? "" : null);
2071
+ }
2072
+ function measureLabelledWidth() {
2073
+ if (!collapsed) return element.offsetWidth;
2074
+ showCollapsed(false);
2075
+ const width = element.offsetWidth;
2076
+ showCollapsed(true);
2077
+ return width;
2078
+ }
2079
+ function isEngaged() {
2080
+ if (ownerDocument.activeElement === element) return true;
2081
+ try {
2082
+ return element.matches(":hover");
2083
+ } catch {
2084
+ return false;
2085
+ }
2086
+ }
2087
+ const onClick = (event) => {
2088
+ if (disabled) {
2089
+ event.preventDefault();
2090
+ return;
2091
+ }
2092
+ options.onSelect();
2093
+ };
2094
+ element.addEventListener("click", onClick);
2095
+ const detachTooltip = tooltip.attach(element, () => collapsed ? { label } : null);
2096
+ readDisabled();
2097
+ return {
2098
+ element,
2099
+ isCollapsed: () => collapsed,
2100
+ refresh() {
2101
+ readDisabled();
2102
+ labelledWidth = void 0;
2103
+ },
2104
+ widths() {
2105
+ if (!collapsed || labelledWidth === void 0) {
2106
+ const measured = measureLabelledWidth();
2107
+ if (measured > 0) labelledWidth = measured;
2108
+ }
2109
+ return { withLabel: labelledWidth ?? 0, iconOnly: SHELL.row };
2110
+ },
2111
+ setCollapsed(next) {
2112
+ if (next === collapsed) return;
2113
+ collapsed = next;
2114
+ showCollapsed(next);
2115
+ if (!next && isEngaged()) tooltip.hide();
2116
+ },
2117
+ destroy() {
2118
+ detachTooltip();
2119
+ element.removeEventListener("click", onClick);
2120
+ element.remove();
2121
+ }
2122
+ };
2123
+ }
2124
+
2125
+ // src/dom/zoom-panel.ts
2126
+ function createZoomPanel(context) {
2127
+ const { ownerDocument, messages, lang, platform, tooltip } = context;
2128
+ let target = null;
2129
+ let unsubscribe;
2130
+ let frame;
2131
+ const detachers = [];
2132
+ function button(zoom, key, tooltipLabel, options) {
2133
+ const element2 = createElement(ownerDocument, "button", {
2134
+ type: "button",
2135
+ class: options.className ?? "cd-shell-target",
2136
+ "data-zoom": zoom,
2137
+ "aria-label": options.label,
2138
+ "aria-keyshortcuts": ariaKeyShortcuts(key, platform)
2139
+ });
2140
+ if (options.icon) element2.append(iconElement(options.icon));
2141
+ element2.addEventListener("click", () => run(key));
2142
+ detachers.push(
2143
+ tooltip.attach(element2, {
2144
+ label: tooltipLabel,
2145
+ shortcut: shortcutLabel(key, platform, messages.keyNames)
2146
+ })
2147
+ );
2148
+ return element2;
2149
+ }
2150
+ const zoomOut = button("out", "zoomOut", messages.zoomOut, { label: messages.zoomOut, icon: "minus" });
2151
+ const readout = button("actual", "zoomToActual", messages.zoomToActual(formatPercent(1, lang)), {
2152
+ className: "cd-shell-target cd-shell-zoom-readout"
2153
+ });
2154
+ const zoomIn = button("in", "zoomIn", messages.zoomIn, { label: messages.zoomIn, icon: "plus" });
2155
+ const fit = button("fit", "fit", messages.fit, { label: messages.fit, icon: "maximize" });
2156
+ const separator = createElement(ownerDocument, "span", {
2157
+ class: "cd-shell-zoom-separator",
2158
+ "aria-hidden": "true"
2159
+ });
2160
+ const element = createElement(
2161
+ ownerDocument,
2162
+ "div",
2163
+ {
2164
+ class: "cd-shell-zoom cd-shell-island",
2165
+ "data-chrome": "zoom",
2166
+ role: "group",
2167
+ "aria-label": messages.zoom
2168
+ },
2169
+ zoomOut,
2170
+ readout,
2171
+ zoomIn,
2172
+ separator,
2173
+ fit
2174
+ );
2175
+ function render() {
2176
+ frame = void 0;
2177
+ const zoom = target?.zoom() ?? 1;
2178
+ const state = zoomReadout(zoom, lang, messages);
2179
+ if (readout.textContent !== state.text) readout.textContent = state.text;
2180
+ readout.setAttribute("aria-label", state.ariaLabel);
2181
+ toggleAttribute(zoomOut, "aria-disabled", (!target || !state.canZoomOut) && "true");
2182
+ toggleAttribute(zoomIn, "aria-disabled", (!target || !state.canZoomIn) && "true");
2183
+ toggleAttribute(fit, "aria-disabled", (!target || !target.canFit()) && "true");
2184
+ }
2185
+ function schedule() {
2186
+ if (frame === void 0) frame = requestAnimationFrame(render);
2187
+ }
2188
+ function cancelFrame() {
2189
+ if (frame !== void 0) cancelAnimationFrame(frame);
2190
+ frame = void 0;
2191
+ }
2192
+ function run(key) {
2193
+ if (!target) return;
2194
+ const zoom = target.zoom();
2195
+ switch (key) {
2196
+ case "zoomIn":
2197
+ if (canZoomIn(zoom)) target.stepZoom(1);
2198
+ break;
2199
+ case "zoomOut":
2200
+ if (canZoomOut(zoom)) target.stepZoom(-1);
2201
+ break;
2202
+ case "zoomToActual":
2203
+ if (zoom !== 1) target.zoomToActual();
2204
+ break;
2205
+ case "fit":
2206
+ if (target.canFit()) target.fit();
2207
+ break;
2208
+ }
2209
+ }
2210
+ render();
2211
+ return {
2212
+ element,
2213
+ run,
2214
+ setTarget(next) {
2215
+ unsubscribe?.();
2216
+ unsubscribe = void 0;
2217
+ cancelFrame();
2218
+ target = next;
2219
+ if (next) unsubscribe = next.onChange(schedule);
2220
+ render();
2221
+ },
2222
+ destroy() {
2223
+ unsubscribe?.();
2224
+ unsubscribe = void 0;
2225
+ cancelFrame();
2226
+ target = null;
2227
+ for (const detach of detachers) detach();
2228
+ element.remove();
2229
+ }
2230
+ };
2231
+ }
2232
+
2233
+ // src/dom/mount.ts
2234
+ var MOUNT_COUNTER = /* @__PURE__ */ Symbol.for("@miragon/modeler-shell.mounts");
2235
+ function nextMountIndex() {
2236
+ const scope = globalThis;
2237
+ const index = (scope[MOUNT_COUNTER] ?? 0) + 1;
2238
+ scope[MOUNT_COUNTER] = index;
2239
+ return index;
2240
+ }
2241
+ var DEFAULT_THEMES = ["light"];
2242
+ var LEGAL_HEIGHT_PROPERTY = "--modeler-shell-legal-height";
2243
+ var INERT_TOAST = Object.freeze({ dismiss() {
2244
+ } });
2245
+ function visibleBoxIn(element, root) {
2246
+ if (!element || element.hidden || !element.isConnected) return void 0;
2247
+ if (typeof element.checkVisibility === "function" && !element.checkVisibility({ visibilityProperty: true }))
2248
+ return void 0;
2249
+ const box = boxRelativeTo(element, root);
2250
+ return box.width > 0 && box.height > 0 ? box : void 0;
2251
+ }
2252
+ function languageOf(root, host, requested) {
2253
+ if (requested) return requested;
2254
+ const declared = host === "embedded" ? root.getAttribute("lang") : root.closest("[lang]")?.getAttribute("lang");
2255
+ return declared || "en";
2256
+ }
2257
+ function hostKontrast(root) {
2258
+ const carrier = root.parentElement?.closest("[data-cd-theme], [data-cd-kontrast]");
2259
+ return carrier?.getAttribute("data-cd-kontrast") === "hoch" ? "hoch" : null;
2260
+ }
2261
+ function mountShell(root, options) {
2262
+ const themes = options.themes ?? DEFAULT_THEMES;
2263
+ if (themes.length === 0) throw new Error("@miragon/modeler-shell: mountShell needs at least one theme");
2264
+ const ledger = new Ledger();
2265
+ try {
2266
+ return renderShell(root, options, themes, ledger);
2267
+ } catch (error) {
2268
+ ledger.close();
2269
+ throw error;
2270
+ }
2271
+ }
2272
+ function renderShell(root, options, themes, ledger) {
2273
+ const ownerDocument = root.ownerDocument;
2274
+ const { host } = options;
2275
+ const index = nextMountIndex();
2276
+ const messages = resolveMessages(options.messages);
2277
+ const platform = options.platform ?? detectPlatform(globalThis.navigator);
2278
+ const lang = languageOf(root, host, options.lang);
2279
+ const keyScope = options.keyScope ?? (host === "embedded" ? "root" : "document");
2280
+ const web = host === "web";
2281
+ ledger.addClass(root, "cd-shell");
2282
+ ledger.attribute(root, "data-cd-shell-host", host);
2283
+ ledger.attribute(root, "data-palette", "top");
2284
+ if (host === "embedded") {
2285
+ ledger.attribute(root, "lang", lang);
2286
+ carryOwnMode(root, themes[0] ?? "light", ledger);
2287
+ } else {
2288
+ ledger.addClass(ownerDocument.documentElement, "cd-shell-document");
2289
+ }
2290
+ const setState = (name, value) => {
2291
+ if (!ledger.isClosed) ledger.attribute(root, name, value);
2292
+ };
2293
+ const appearance = web ? connectWebAppearance({ ownerDocument, themes, options: options.appearance }) : void 0;
2294
+ if (appearance) ledger.defer(() => appearance.destroy());
2295
+ const darkCapable = themes.includes("dark");
2296
+ const layer = createElement(ownerDocument, "div", { class: "cd-shell-layer" });
2297
+ ledger.defer(() => layer.remove());
2298
+ const tooltip = createTooltip({ root, layer, id: `cd-shell-tooltip-${index}` });
2299
+ ledger.defer(() => tooltip.destroy());
2300
+ let readOnly = false;
2301
+ let readOnlyShown = false;
2302
+ let readOnlyTimer;
2303
+ ledger.defer(() => clearTimeout(readOnlyTimer));
2304
+ const legend = options.legend ? createLegend({
2305
+ ownerDocument,
2306
+ messages,
2307
+ tooltip,
2308
+ titleId: `cd-shell-legend-title-${index}`,
2309
+ options: options.legend,
2310
+ onChange(open) {
2311
+ setState("data-legend", open ? "open" : null);
2312
+ placeHint();
2313
+ menu.refresh();
2314
+ },
2315
+ focusAfterClose: () => menu.trigger.focus()
2316
+ }) : void 0;
2317
+ if (legend) ledger.defer(() => legend.destroy());
2318
+ const legendEntry = legend && options.legend?.menuEntry !== false ? {
2319
+ kind: "checkbox",
2320
+ id: "legend",
2321
+ label: messages.legend,
2322
+ checked: () => legend.isOpen(),
2323
+ onToggle: (checked) => legend.setOpen(checked),
2324
+ keepEnabledWhenReadOnly: true
2325
+ } : void 0;
2326
+ const shellModelEntries = legendEntry ? [legendEntry] : [];
2327
+ const fileEntries = web && options.file ? fileMenuEntries(options.file, messages) : [];
2328
+ const appearanceEntries = appearance && darkCapable ? appearanceMenuEntries(appearance, messages) : [];
2329
+ const shellEntries = (group) => {
2330
+ if (group === "file") return fileEntries;
2331
+ if (group === "model") return shellModelEntries;
2332
+ if (group === "appearance") return appearanceEntries;
2333
+ return [];
2334
+ };
2335
+ const menu = createMenu({
2336
+ root,
2337
+ layer,
2338
+ id: `cd-shell-menu-${index}`,
2339
+ messages,
2340
+ tooltip,
2341
+ groups: options.menu ?? {},
2342
+ shellEntries,
2343
+ isReadOnly: () => readOnly
2344
+ });
2345
+ ledger.defer(() => menu.destroy());
2346
+ ledger.defer(tooltip.attach(menu.trigger, () => menu.isOpen() ? null : { label: messages.menu }));
2347
+ const primary = web && options.primary ? createPrimaryAction({ ownerDocument, messages, tooltip, options: options.primary }) : void 0;
2348
+ if (primary) ledger.defer(() => primary.destroy());
2349
+ const canvas = options.canvas ?? createElement(ownerDocument, "div");
2350
+ ledger.addClass(canvas, "cd-shell-canvas");
2351
+ if (web && !canvas.hasAttribute("role")) ledger.attribute(canvas, "role", "main");
2352
+ if (!options.canvas) ledger.defer(() => canvas.remove());
2353
+ function focusCanvasOrTrigger() {
2354
+ const took = (target) => {
2355
+ target?.focus({ preventScroll: true });
2356
+ return target !== null && target !== void 0 && ownerDocument.activeElement === target;
2357
+ };
2358
+ if (emptyState?.isShown()) {
2359
+ emptyState.focus();
2360
+ if (emptyState.element.contains(ownerDocument.activeElement)) return;
2361
+ }
2362
+ if (took(canvas.querySelector("svg[tabindex]"))) return;
2363
+ if (!menu.trigger.hidden) took(menu.trigger);
2364
+ }
2365
+ function moveFocusOutOf(island) {
2366
+ if (island.contains(ownerDocument.activeElement)) focusCanvasOrTrigger();
2367
+ }
2368
+ const status = createStatusBox(ownerDocument);
2369
+ ledger.defer(() => status.destroy());
2370
+ const hint = options.emptyHint ? createEmptyHint(ownerDocument, messages, options.emptyHint) : void 0;
2371
+ if (hint) ledger.defer(() => hint.destroy());
2372
+ const emptyState = web && options.emptyState ? createEmptyState({
2373
+ ownerDocument,
2374
+ messages,
2375
+ options: options.emptyState,
2376
+ dropHint: options.dropZone ? options.emptyState.dropHint ?? messages.dropHint : void 0,
2377
+ open: options.file?.open,
2378
+ titleId: `cd-shell-empty-state-title-${index}`,
2379
+ onLeave: () => setEmptyStateShown(false)
2380
+ }) : void 0;
2381
+ if (emptyState) ledger.defer(() => emptyState.destroy());
2382
+ const inspector = options.inspector ? createInspector(ownerDocument, options.inspector, (open) => {
2383
+ setState("data-inspector", open ? "open" : null);
2384
+ updateBottomRow();
2385
+ placeHint();
2386
+ }) : void 0;
2387
+ if (inspector) ledger.defer(() => inspector.destroy());
2388
+ const problemsOptions = options.problems;
2389
+ let popover;
2390
+ const pill = problemsOptions ? createProblemsPill({
2391
+ ownerDocument,
2392
+ messages,
2393
+ lang,
2394
+ tooltip,
2395
+ showTooltip: host === "ide",
2396
+ onActivate() {
2397
+ if (popover) popover.toggle();
2398
+ else problemsOptions.onActivate?.();
2399
+ }
2400
+ }) : void 0;
2401
+ if (pill) ledger.defer(() => pill.destroy());
2402
+ if (pill && (web || host === "embedded" && !problemsOptions?.onActivate)) {
2403
+ const created = createProblemsPopover({
2404
+ ownerDocument,
2405
+ root,
2406
+ layer,
2407
+ pill: pill.element,
2408
+ id: `cd-shell-problems-${index}`,
2409
+ titleId: `cd-shell-problems-title-${index}`,
2410
+ messages,
2411
+ onSelectItem: problemsOptions?.onSelectItem
2412
+ });
2413
+ popover = created;
2414
+ ledger.defer(() => created.destroy());
2415
+ }
2416
+ const legal = web ? createLegalLine({
2417
+ ownerDocument,
2418
+ messages,
2419
+ options: options.legal ?? {},
2420
+ idPrefix: `cd-shell-legal-${index}`
2421
+ }) : void 0;
2422
+ if (legal) ledger.defer(() => legal.destroy());
2423
+ const zoomPanel = createZoomPanel({ ownerDocument, messages, lang, platform, tooltip });
2424
+ ledger.defer(() => zoomPanel.destroy());
2425
+ const toaster = host === "ide" ? void 0 : createToaster({
2426
+ ownerDocument,
2427
+ host,
2428
+ messages,
2429
+ idPrefix: `cd-shell-toast-${index}`,
2430
+ focusFallback: focusCanvasOrTrigger,
2431
+ onChange: () => updateBottomRow()
2432
+ });
2433
+ if (toaster) ledger.defer(() => toaster.destroy());
2434
+ const defined = (element) => element !== void 0;
2435
+ root.prepend(...[menu.trigger, primary?.element, canvas].filter(defined));
2436
+ root.append(
2437
+ ...[
2438
+ status.element,
2439
+ hint?.element,
2440
+ emptyState?.element,
2441
+ inspector?.element,
2442
+ pill?.element,
2443
+ legend?.element,
2444
+ legal?.element,
2445
+ zoomPanel.element
2446
+ ].filter(defined),
2447
+ layer,
2448
+ ...[toaster?.announcer, toaster?.element].filter(defined)
2449
+ );
2450
+ function showToast(request) {
2451
+ return ledger.isClosed || !toaster ? INERT_TOAST : toaster.show(request);
2452
+ }
2453
+ const dropZone = web && options.dropZone ? createDropZone({
2454
+ ownerDocument,
2455
+ layer,
2456
+ messages,
2457
+ lang,
2458
+ options: options.dropZone,
2459
+ modalOpen: () => isModalOpen(ownerDocument),
2460
+ toast: showToast,
2461
+ setState: (state) => setState("data-drop", state)
2462
+ }) : void 0;
2463
+ if (dropZone) ledger.defer(() => dropZone.destroy());
2464
+ let palette;
2465
+ let paletteRow = "top";
2466
+ let zoomRaised = false;
2467
+ let legalHeight = LEGAL_LINE_HEIGHT;
2468
+ if (legal) ledger.defer(() => root.style.removeProperty(LEGAL_HEIGHT_PROPERTY));
2469
+ function containerSize() {
2470
+ return { width: root.clientWidth, height: root.clientHeight };
2471
+ }
2472
+ function menuRight() {
2473
+ const box = boxRelativeTo(menu.trigger, root);
2474
+ return box.width > 0 ? box.x + box.width : SHELL.edge + SHELL.row;
2475
+ }
2476
+ function paletteWidth() {
2477
+ const element = palette?.registration.element;
2478
+ return element ? element.offsetWidth - element.clientWidth + element.scrollWidth : 0;
2479
+ }
2480
+ function currentFitInsets() {
2481
+ const container = containerSize();
2482
+ const layout = {
2483
+ container,
2484
+ host,
2485
+ paletteRow,
2486
+ problemsVisible: pill?.isShown() ?? false
2487
+ };
2488
+ if (web) {
2489
+ layout.zoomRaised = zoomRaised;
2490
+ layout.legalHeight = legalHeight;
2491
+ }
2492
+ if (legend?.isOpen()) layout.legendWidth = legend.element.getBoundingClientRect().width;
2493
+ if (inspector?.isOpen()) {
2494
+ layout.inspector = container.width > SHELL.narrow ? {} : { sheetTop: boxRelativeTo(inspector.element, root).y };
2495
+ }
2496
+ return fitInsets(layout);
2497
+ }
2498
+ function placeHint() {
2499
+ if (hint?.isShown()) hint.place(currentFitInsets());
2500
+ }
2501
+ function updateLayout() {
2502
+ if (ledger.isClosed) return;
2503
+ const size = containerSize();
2504
+ if (size.width === 0 || size.height === 0) return;
2505
+ const topRow = decideTopRow({
2506
+ containerWidth: size.width,
2507
+ paletteWidth: paletteWidth(),
2508
+ menuRight: menuRight(),
2509
+ primary: primary?.widths()
2510
+ });
2511
+ paletteRow = topRow.paletteRow;
2512
+ setState("data-palette", paletteRow);
2513
+ if (primary) {
2514
+ const collapsed = topRow.primaryLabel === "collapsed";
2515
+ primary.setCollapsed(collapsed);
2516
+ setState("data-primary", collapsed ? "collapsed" : null);
2517
+ }
2518
+ if (legend && !legend.isStarted()) legend.start(options.legend?.open ?? legendStartsOpen(size.height));
2519
+ updateBottomRow();
2520
+ placeHint();
2521
+ }
2522
+ function updateBottomRow() {
2523
+ if (ledger.isClosed) return;
2524
+ const size = containerSize();
2525
+ if (size.width === 0 || size.height === 0) return;
2526
+ if (legal) {
2527
+ const legalBox = visibleBoxIn(legal.element, root);
2528
+ const height = legalLineHeight(legalBox?.height);
2529
+ if (height !== legalHeight || !root.style.getPropertyValue(LEGAL_HEIGHT_PROPERTY)) {
2530
+ legalHeight = height;
2531
+ root.style.setProperty(LEGAL_HEIGHT_PROPERTY, `${height}px`);
2532
+ }
2533
+ zoomRaised = zoomRowRaised({
2534
+ containerWidth: size.width,
2535
+ legalRight: legalBox && legalBox.x + legalBox.width,
2536
+ zoomWidth: visibleBoxIn(zoomPanel.element, root)?.width
2537
+ });
2538
+ setState("data-zoom-row", zoomRaised ? "raised" : null);
2539
+ }
2540
+ if (toaster) {
2541
+ const obstacles = [zoomPanel.element, legal?.element, pill?.element].flatMap(
2542
+ (element) => {
2543
+ const box = visibleBoxIn(element, root);
2544
+ return box ? [{ left: box.x, right: box.x + box.width, top: size.height - box.y }] : [];
2545
+ }
2546
+ );
2547
+ toaster.setBottom(
2548
+ toastStackBottom({ containerWidth: size.width, obstacles, cardWidth: toaster.widestCard() })
2549
+ );
2550
+ }
2551
+ }
2552
+ function setEmptyStateShown(shown) {
2553
+ if (ledger.isClosed || !emptyState || emptyState.isShown() === shown) return;
2554
+ const active = ownerDocument.activeElement;
2555
+ const hadFocus = emptyState.element.contains(active);
2556
+ const workHadFocus = shown && active !== null && root.contains(active) && ![emptyState.element, legal?.element, toaster?.element].some((kept) => kept?.contains(active));
2557
+ emptyState.setShown(shown);
2558
+ setState("data-empty-state", shown ? "shown" : null);
2559
+ ledger.attribute(canvas, "inert", shown ? "" : null);
2560
+ if (shown) {
2561
+ menu.close();
2562
+ popover?.close();
2563
+ tooltip.hide();
2564
+ if (workHadFocus) emptyState.focus();
2565
+ } else if (hadFocus) {
2566
+ focusCanvasOrTrigger();
2567
+ }
2568
+ updateLayout();
2569
+ }
2570
+ const ResizeObserverClass = globalThis.ResizeObserver;
2571
+ const resizeObserver = ResizeObserverClass ? new ResizeObserverClass(() => updateLayout()) : void 0;
2572
+ resizeObserver?.observe(root);
2573
+ if (primary) resizeObserver?.observe(primary.element);
2574
+ if (legal) resizeObserver?.observe(legal.element);
2575
+ ledger.defer(() => resizeObserver?.disconnect());
2576
+ if (legend && options.legend?.open !== void 0) legend.start(options.legend.open);
2577
+ updateLayout();
2578
+ const keyTarget = keyScope === "root" ? root : ownerDocument;
2579
+ const onKeydown = createKeyListener({
2580
+ host,
2581
+ dialogScope: keyTarget,
2582
+ menuOpen: () => menu.isOpen(),
2583
+ startScreen: () => emptyState?.isShown() ?? false,
2584
+ closeMenu() {
2585
+ const active = ownerDocument.activeElement;
2586
+ menu.close({ restoreFocus: active === menu.trigger || menu.dropdown.contains(active) });
2587
+ },
2588
+ hideTooltip: () => tooltip.hide(),
2589
+ // Behind the start screen the keys reach the browser instead: there is no diagram to zoom.
2590
+ run: (key) => {
2591
+ if (!emptyState?.isShown()) zoomPanel.run(key);
2592
+ }
2593
+ });
2594
+ keyTarget.addEventListener("keydown", onKeydown);
2595
+ ledger.defer(() => keyTarget.removeEventListener("keydown", onKeydown));
2596
+ function readOnlyState() {
2597
+ return { locked: readOnly, shown: readOnlyShown };
2598
+ }
2599
+ function applyReadOnly() {
2600
+ setState("data-readonly", readOnlyShown ? "" : null);
2601
+ palette?.registration.setReadOnly(readOnlyState());
2602
+ menu.refresh();
2603
+ }
2604
+ function showReadOnly() {
2605
+ readOnlyTimer = void 0;
2606
+ if (ledger.isClosed || !readOnly) return;
2607
+ readOnlyShown = true;
2608
+ applyReadOnly();
2609
+ }
2610
+ function unregisterPalette() {
2611
+ if (!palette) return;
2612
+ palette.detachTooltip();
2613
+ resizeObserver?.unobserve(palette.registration.element);
2614
+ palette = void 0;
2615
+ }
2616
+ ledger.defer(unregisterPalette);
2617
+ const menuHandle = {
2618
+ trigger: menu.trigger,
2619
+ dropdown: menu.dropdown,
2620
+ isOpen: () => menu.isOpen(),
2621
+ open: (focus) => menu.open(focus),
2622
+ close: (closeOptions) => menu.close(closeOptions),
2623
+ refresh: () => menu.refresh(),
2624
+ setGroups: (groups) => {
2625
+ if (!ledger.isClosed) menu.setGroups(groups);
2626
+ }
2627
+ };
2628
+ const tooltipHandle = {
2629
+ attach: (control, content) => ledger.isClosed ? () => {
2630
+ } : tooltip.attach(control, content),
2631
+ hide: () => tooltip.hide()
2632
+ };
2633
+ const legendHandle = legend && {
2634
+ element: legend.element,
2635
+ body: legend.body,
2636
+ isOpen: () => legend.isOpen(),
2637
+ setOpen: (open) => {
2638
+ if (!ledger.isClosed) legend.setOpen(open);
2639
+ },
2640
+ toggle: () => {
2641
+ if (!ledger.isClosed) legend.toggle();
2642
+ }
2643
+ };
2644
+ const primaryHandle = primary && {
2645
+ element: primary.element,
2646
+ isCollapsed: () => primary.isCollapsed(),
2647
+ refresh: () => {
2648
+ if (!ledger.isClosed) primary.refresh();
2649
+ }
2650
+ };
2651
+ const appearanceHandle = appearance && darkCapable ? {
2652
+ choice: () => appearance.choice(),
2653
+ setChoice: (choice) => {
2654
+ if (ledger.isClosed) return;
2655
+ appearance.setChoice(choice);
2656
+ menu.refresh();
2657
+ }
2658
+ } : void 0;
2659
+ const inspectorHandle = inspector && {
2660
+ element: inspector.element,
2661
+ isOpen: () => inspector.isOpen(),
2662
+ open: () => {
2663
+ if (!ledger.isClosed) inspector.open();
2664
+ },
2665
+ close: () => {
2666
+ if (ledger.isClosed) return;
2667
+ if (inspector.isOpen()) moveFocusOutOf(inspector.element);
2668
+ inspector.close();
2669
+ }
2670
+ };
2671
+ return {
2672
+ root,
2673
+ canvas,
2674
+ host,
2675
+ lang,
2676
+ platform,
2677
+ messages,
2678
+ menu: menuHandle,
2679
+ tooltip: tooltipHandle,
2680
+ legend: legendHandle,
2681
+ inspector: inspectorHandle,
2682
+ primary: primaryHandle,
2683
+ appearance: appearanceHandle,
2684
+ setZoomTarget(target) {
2685
+ if (!ledger.isClosed) zoomPanel.setTarget(target);
2686
+ },
2687
+ fitInsets: currentFitInsets,
2688
+ registerPalette(registration) {
2689
+ if (ledger.isClosed) return () => {
2690
+ };
2691
+ unregisterPalette();
2692
+ palette = {
2693
+ registration,
2694
+ detachTooltip: tooltip.delegate(
2695
+ registration.element,
2696
+ ".entry",
2697
+ (entry) => registration.entryTooltip(entry)
2698
+ )
2699
+ };
2700
+ resizeObserver?.observe(registration.element);
2701
+ registration.setReadOnly(readOnlyState());
2702
+ updateLayout();
2703
+ const registered = palette;
2704
+ return () => {
2705
+ if (palette !== registered) return;
2706
+ unregisterPalette();
2707
+ updateLayout();
2708
+ };
2709
+ },
2710
+ setProblems(summary) {
2711
+ if (ledger.isClosed || !pill) return;
2712
+ const listHadFocus = popover?.element.contains(ownerDocument.activeElement) ?? false;
2713
+ pill.set(summary);
2714
+ setState("data-problems", pill.isShown() ? "shown" : null);
2715
+ popover?.setItems(summary.items ?? []);
2716
+ if (!pill.isShown() && popover?.isOpen()) {
2717
+ popover.close();
2718
+ if (listHadFocus) focusCanvasOrTrigger();
2719
+ }
2720
+ updateBottomRow();
2721
+ placeHint();
2722
+ },
2723
+ setStatus(next, statusOptions) {
2724
+ if (!ledger.isClosed) status.set(next, statusOptions?.delayMs ?? 0);
2725
+ },
2726
+ setEmptyHint(visible) {
2727
+ if (ledger.isClosed || !hint) return;
2728
+ if (!visible && hint.isShown()) moveFocusOutOf(hint.element);
2729
+ hint.setShown(visible);
2730
+ placeHint();
2731
+ },
2732
+ setReadOnly(next, readOnlyOptions) {
2733
+ if (ledger.isClosed) return;
2734
+ if (!next) {
2735
+ clearTimeout(readOnlyTimer);
2736
+ readOnlyTimer = void 0;
2737
+ readOnly = false;
2738
+ readOnlyShown = false;
2739
+ } else {
2740
+ readOnly = true;
2741
+ const delayMs = readOnlyOptions?.delayMs ?? 0;
2742
+ if (!readOnlyShown && readOnlyTimer === void 0) {
2743
+ if (delayMs > 0) readOnlyTimer = setTimeout(showReadOnly, delayMs);
2744
+ else readOnlyShown = true;
2745
+ }
2746
+ }
2747
+ applyReadOnly();
2748
+ },
2749
+ isReadOnly: () => readOnly,
2750
+ setEmptyState: setEmptyStateShown,
2751
+ isEmptyStateShown: () => emptyState?.isShown() ?? false,
2752
+ toast: showToast,
2753
+ destroy() {
2754
+ ledger.close();
2755
+ }
2756
+ };
2757
+ }
2758
+ function carryOwnMode(root, theme, ledger) {
2759
+ ledger.attribute(root, "data-cd-theme", theme);
2760
+ const mirror = () => {
2761
+ if (ledger.isClosed) return;
2762
+ const kontrast = hostKontrast(root);
2763
+ if (root.getAttribute("data-cd-kontrast") !== kontrast)
2764
+ ledger.attribute(root, "data-cd-kontrast", kontrast);
2765
+ };
2766
+ mirror();
2767
+ const MutationObserverClass = globalThis.MutationObserver;
2768
+ if (!MutationObserverClass) return;
2769
+ const observer = new MutationObserverClass(mirror);
2770
+ observer.observe(root.ownerDocument.documentElement, {
2771
+ attributes: true,
2772
+ attributeFilter: ["data-cd-theme", "data-cd-kontrast"],
2773
+ subtree: true
2774
+ });
2775
+ ledger.defer(() => observer.disconnect());
2776
+ }
2777
+
2778
+ export {
2779
+ ICONS,
2780
+ PALETTE_TOOL_ICON,
2781
+ iconMarkup,
2782
+ iconElement,
2783
+ mountShell
2784
+ };
2785
+ /*! @miragon/modeler-shell icon geometry
2786
+ * Lucide (lucide-static 1.59.0) @license ISC
2787
+ * Copyright (c) 2026 Lucide Icons and Contributors
2788
+ * Derived from Feather, as Lucide's LICENSE lists them: check, circle-x (x-circle), maximize,
2789
+ * minus, plus, triangle-alert (alert-triangle) and x. Feather @license MIT
2790
+ * Copyright (c) 2013-present Cole Bemis
2791
+ * Full notices: THIRD_PARTY_NOTICES.md of @miragon/modeler-shell */
2792
+ //# sourceMappingURL=chunk-66HCSJNY.js.map