@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.
- package/API.md +2869 -0
- package/LICENSE +21 -0
- package/README.md +566 -2
- package/THIRD_PARTY_NOTICES.md +52 -0
- package/dist/appearance-BBaM_4bD.d.ts +323 -0
- package/dist/appearance-D3FUcbu6.d.cts +53 -0
- package/dist/appearance-D3FUcbu6.d.ts +53 -0
- package/dist/appearance-XX3JC3pI.d.cts +323 -0
- package/dist/chunk-66HCSJNY.js +2792 -0
- package/dist/chunk-66HCSJNY.js.map +1 -0
- package/dist/chunk-KYDG5DMX.js +136 -0
- package/dist/chunk-KYDG5DMX.js.map +1 -0
- package/dist/chunk-N4QXFF3A.js +23 -0
- package/dist/chunk-N4QXFF3A.js.map +1 -0
- package/dist/chunk-OADW3GPA.js +785 -0
- package/dist/chunk-OADW3GPA.js.map +1 -0
- package/dist/core.cjs +873 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +246 -0
- package/dist/core.d.ts +246 -0
- package/dist/core.js +131 -0
- package/dist/core.js.map +1 -0
- package/dist/diagram-js.cjs +1147 -0
- package/dist/diagram-js.cjs.map +1 -0
- package/dist/diagram-js.d.cts +170 -0
- package/dist/diagram-js.d.ts +170 -0
- package/dist/diagram-js.js +879 -0
- package/dist/diagram-js.js.map +1 -0
- package/dist/index.cjs +3632 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +29 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +143 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol-DhYBakWL.d.cts +15 -0
- package/dist/protocol-IeW8bf4f.d.ts +15 -0
- package/dist/shell.css +1603 -0
- package/dist/testing-vscode-stub.cjs +332 -0
- package/dist/testing-vscode-stub.cjs.map +1 -0
- package/dist/testing-vscode-stub.d.cts +65 -0
- package/dist/testing-vscode-stub.d.ts +65 -0
- package/dist/testing-vscode-stub.js +154 -0
- package/dist/testing-vscode-stub.js.map +1 -0
- package/dist/testing.cjs +4663 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +231 -0
- package/dist/testing.d.ts +231 -0
- package/dist/testing.js +4483 -0
- package/dist/testing.js.map +1 -0
- package/dist/toasts-Bp0REqdq.d.cts +142 -0
- package/dist/toasts-Bp0REqdq.d.ts +142 -0
- package/dist/types-CnRrINRs.d.cts +445 -0
- package/dist/types-PuDZczzy.d.ts +445 -0
- package/dist/vscode-extension.cjs +215 -0
- package/dist/vscode-extension.cjs.map +1 -0
- package/dist/vscode-extension.d.cts +103 -0
- package/dist/vscode-extension.d.ts +103 -0
- package/dist/vscode-extension.js +24 -0
- package/dist/vscode-extension.js.map +1 -0
- package/dist/vscode-webview.cjs +3561 -0
- package/dist/vscode-webview.cjs.map +1 -0
- package/dist/vscode-webview.d.cts +84 -0
- package/dist/vscode-webview.d.ts +84 -0
- package/dist/vscode-webview.js +259 -0
- package/dist/vscode-webview.js.map +1 -0
- package/package.json +145 -4
- package/src/core/appearance.ts +104 -0
- package/src/core/feedback/appearance.ts +141 -0
- package/src/core/feedback/problems.ts +23 -0
- package/src/core/feedback/toasts.ts +118 -0
- package/src/core/fit.ts +220 -0
- package/src/core/index.ts +127 -0
- package/src/core/keymap.ts +295 -0
- package/src/core/layout.ts +69 -0
- package/src/core/messages.ts +159 -0
- package/src/core/sizes.ts +85 -0
- package/src/core/types.ts +106 -0
- package/src/core/web-chrome/bottom-row.ts +37 -0
- package/src/core/web-chrome/drop.ts +78 -0
- package/src/core/web-chrome/top-row.ts +66 -0
- package/src/core/zoom.ts +74 -0
- package/src/core.ts +2 -0
- package/src/diagram-js/connect.ts +75 -0
- package/src/diagram-js/index.ts +16 -0
- package/src/diagram-js/module.ts +20 -0
- package/src/diagram-js/palette-skin.ts +267 -0
- package/src/diagram-js/palette.css +269 -0
- package/src/diagram-js/service.ts +243 -0
- package/src/diagram-js/types.ts +67 -0
- package/src/diagram-js/viewport.ts +583 -0
- package/src/diagram-js.ts +2 -0
- package/src/dom/buttons.css +124 -0
- package/src/dom/element.ts +51 -0
- package/src/dom/feedback/appearance.ts +184 -0
- package/src/dom/feedback/feedback.css +435 -0
- package/src/dom/feedback/problems-popover.ts +287 -0
- package/src/dom/feedback/toasts.ts +431 -0
- package/src/dom/hint.ts +51 -0
- package/src/dom/icons.ts +309 -0
- package/src/dom/index.ts +42 -0
- package/src/dom/inspector.ts +35 -0
- package/src/dom/keys.ts +67 -0
- package/src/dom/ledger.ts +87 -0
- package/src/dom/legend.ts +85 -0
- package/src/dom/menu.ts +404 -0
- package/src/dom/mount.ts +731 -0
- package/src/dom/problems.ts +73 -0
- package/src/dom/shell-dom.css +969 -0
- package/src/dom/status.ts +72 -0
- package/src/dom/tooltip.ts +237 -0
- package/src/dom/types.ts +459 -0
- package/src/dom/web/drop-zone.ts +181 -0
- package/src/dom/web/empty-state.ts +144 -0
- package/src/dom/web/legal.ts +75 -0
- package/src/dom/web/menu-groups.ts +69 -0
- package/src/dom/web/primary.ts +124 -0
- package/src/dom/web/web.css +386 -0
- package/src/dom/zoom-panel.ts +154 -0
- package/src/index.ts +3 -0
- package/src/shell.css +9 -0
- package/src/testing/case-tools.ts +164 -0
- package/src/testing/cases.ts +1077 -0
- package/src/testing/checks.ts +868 -0
- package/src/testing/facts.ts +641 -0
- package/src/testing/geometry.ts +652 -0
- package/src/testing/harness.ts +187 -0
- package/src/testing/index.ts +19 -0
- package/src/testing/page-tools.ts +457 -0
- package/src/testing/page.ts +27 -0
- package/src/testing/vscode-stub.ts +237 -0
- package/src/testing/web-cases.ts +913 -0
- package/src/testing/web-checks.ts +1049 -0
- package/src/testing/web-facts.ts +713 -0
- package/src/testing-vscode-stub.ts +2 -0
- package/src/testing.ts +2 -0
- package/src/vscode/extension/html.ts +107 -0
- package/src/vscode/extension/index.ts +8 -0
- package/src/vscode/extension/reveal.ts +9 -0
- package/src/vscode/extension/theme-bridge.ts +85 -0
- package/src/vscode/extension/types.ts +36 -0
- package/src/vscode/protocol.ts +31 -0
- package/src/vscode/webview/appearance.ts +141 -0
- package/src/vscode/webview/index.ts +8 -0
- package/src/vscode/webview/mount.ts +88 -0
- package/src/vscode/webview/state.ts +116 -0
- package/src/vscode-extension.ts +2 -0
- package/src/vscode-webview.ts +2 -0
|
@@ -0,0 +1,3561 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/vscode-webview.ts
|
|
21
|
+
var vscode_webview_exports = {};
|
|
22
|
+
__export(vscode_webview_exports, {
|
|
23
|
+
APPEARANCE_MESSAGE_TYPE: () => APPEARANCE_MESSAGE_TYPE,
|
|
24
|
+
SHELL_STATE_KEY: () => SHELL_STATE_KEY,
|
|
25
|
+
appearanceMessage: () => appearanceMessage,
|
|
26
|
+
applyAppearance: () => applyAppearance,
|
|
27
|
+
connectAppearance: () => connectAppearance,
|
|
28
|
+
connectReducedMotion: () => connectReducedMotion,
|
|
29
|
+
isAppearanceMessage: () => isAppearanceMessage,
|
|
30
|
+
mountVsCodeShell: () => mountVsCodeShell,
|
|
31
|
+
persistView: () => persistView,
|
|
32
|
+
readShellState: () => readShellState,
|
|
33
|
+
writeShellState: () => writeShellState
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(vscode_webview_exports);
|
|
36
|
+
|
|
37
|
+
// src/core/sizes.ts
|
|
38
|
+
var SHELL = {
|
|
39
|
+
edge: 12,
|
|
40
|
+
gap: 8,
|
|
41
|
+
row: 40,
|
|
42
|
+
target: 32,
|
|
43
|
+
entry: 36,
|
|
44
|
+
free: 64,
|
|
45
|
+
menuMinWidth: 248,
|
|
46
|
+
inspectorWidth: 288,
|
|
47
|
+
fitInsetTop: 88,
|
|
48
|
+
fitInsetTopSecondRow: 140,
|
|
49
|
+
fitInsetSide: 24,
|
|
50
|
+
fitInsetBottom: 64,
|
|
51
|
+
fitInsetBottomWeb: 92,
|
|
52
|
+
narrow: 760,
|
|
53
|
+
veryNarrow: 440,
|
|
54
|
+
low: 820,
|
|
55
|
+
layerIslands: 20,
|
|
56
|
+
layerInspector: 25,
|
|
57
|
+
layerTopRow: 30,
|
|
58
|
+
layerPopups: 40,
|
|
59
|
+
layerDialog: 50,
|
|
60
|
+
layerToast: 60
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// src/core/web-chrome/bottom-row.ts
|
|
64
|
+
var LEGAL_LINE_HEIGHT = 20;
|
|
65
|
+
function legalLineHeight(measured) {
|
|
66
|
+
if (measured === void 0 || !Number.isFinite(measured) || measured <= 0) return LEGAL_LINE_HEIGHT;
|
|
67
|
+
return Math.max(1, Math.round(measured / LEGAL_LINE_HEIGHT)) * LEGAL_LINE_HEIGHT;
|
|
68
|
+
}
|
|
69
|
+
function zoomRowRaised(input) {
|
|
70
|
+
const { containerWidth, legalRight, zoomWidth } = input;
|
|
71
|
+
if (legalRight === void 0 || zoomWidth === void 0) return false;
|
|
72
|
+
const zoomLeft = containerWidth - SHELL.edge - zoomWidth;
|
|
73
|
+
return legalRight + SHELL.gap > zoomLeft;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/core/fit.ts
|
|
77
|
+
function fitInsets(layout) {
|
|
78
|
+
const { container, host, paletteRow, problemsVisible, zoomRaised, legendWidth, inspector } = layout;
|
|
79
|
+
const raisedBottomRow = host === "web" && (problemsVisible || zoomRaised === true);
|
|
80
|
+
const wrappedLegalLines = legalLineHeight(layout.legalHeight) - LEGAL_LINE_HEIGHT;
|
|
81
|
+
const insets = {
|
|
82
|
+
top: paletteRow === "second-row" ? SHELL.fitInsetTopSecondRow : SHELL.fitInsetTop,
|
|
83
|
+
right: SHELL.fitInsetSide,
|
|
84
|
+
bottom: raisedBottomRow ? SHELL.fitInsetBottomWeb + wrappedLegalLines : SHELL.fitInsetBottom,
|
|
85
|
+
left: SHELL.fitInsetSide
|
|
86
|
+
};
|
|
87
|
+
const bottomSheetOpen = inspector !== void 0 && container.width <= SHELL.narrow;
|
|
88
|
+
if (inspector !== void 0 && !bottomSheetOpen) {
|
|
89
|
+
insets.right = SHELL.edge + SHELL.inspectorWidth + SHELL.fitInsetSide;
|
|
90
|
+
}
|
|
91
|
+
if (bottomSheetOpen) {
|
|
92
|
+
insets.bottom = inspector.sheetTop === void 0 ? SHELL.fitInsetBottom : container.height - inspector.sheetTop + SHELL.edge;
|
|
93
|
+
} else if (legendWidth !== void 0 && legendWidth > 0 && container.width > SHELL.veryNarrow) {
|
|
94
|
+
insets.left = SHELL.edge + legendWidth + SHELL.fitInsetSide;
|
|
95
|
+
}
|
|
96
|
+
return insets;
|
|
97
|
+
}
|
|
98
|
+
var STANDARD_EASING = cubicBezier(0.4, 0, 0.2, 1);
|
|
99
|
+
function cubicBezier(x1, y1, x2, y2) {
|
|
100
|
+
const polynomial = (first, second) => {
|
|
101
|
+
const c = 3 * first;
|
|
102
|
+
const b = 3 * (second - first) - c;
|
|
103
|
+
const a = 1 - c - b;
|
|
104
|
+
return {
|
|
105
|
+
at: (t) => ((a * t + b) * t + c) * t,
|
|
106
|
+
slope: (t) => (3 * a * t + 2 * b) * t + c
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
const curveX = polynomial(x1, x2);
|
|
110
|
+
const curveY = polynomial(y1, y2);
|
|
111
|
+
const precision = 1e-7;
|
|
112
|
+
const parameterFor = (x) => {
|
|
113
|
+
let t = x;
|
|
114
|
+
for (let iteration = 0; iteration < 8; iteration++) {
|
|
115
|
+
const error = curveX.at(t) - x;
|
|
116
|
+
if (Math.abs(error) < precision) return t;
|
|
117
|
+
const slope = curveX.slope(t);
|
|
118
|
+
if (Math.abs(slope) < 1e-6) break;
|
|
119
|
+
t -= error / slope;
|
|
120
|
+
if (t < 0 || t > 1) break;
|
|
121
|
+
}
|
|
122
|
+
let low = 0;
|
|
123
|
+
let high = 1;
|
|
124
|
+
t = x;
|
|
125
|
+
for (let iteration = 0; iteration < 60; iteration++) {
|
|
126
|
+
const value = curveX.at(t);
|
|
127
|
+
if (Math.abs(value - x) < precision) break;
|
|
128
|
+
if (value < x) low = t;
|
|
129
|
+
else high = t;
|
|
130
|
+
t = (low + high) / 2;
|
|
131
|
+
}
|
|
132
|
+
return t;
|
|
133
|
+
};
|
|
134
|
+
return (progress) => {
|
|
135
|
+
if (!(progress > 0)) return 0;
|
|
136
|
+
if (progress >= 1) return 1;
|
|
137
|
+
return curveY.at(parameterFor(progress));
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/core/zoom.ts
|
|
142
|
+
var ZOOM_MIN = 0.2;
|
|
143
|
+
var ZOOM_MAX = 4;
|
|
144
|
+
function canZoomOut(zoom) {
|
|
145
|
+
return zoom > ZOOM_MIN * 1.001;
|
|
146
|
+
}
|
|
147
|
+
function canZoomIn(zoom) {
|
|
148
|
+
return zoom < ZOOM_MAX * 0.999;
|
|
149
|
+
}
|
|
150
|
+
function formatPercent(zoom, lang) {
|
|
151
|
+
return numberFormat(lang, "percent").format(zoom);
|
|
152
|
+
}
|
|
153
|
+
function formatNumber(value, lang) {
|
|
154
|
+
return numberFormat(lang, "decimal").format(value);
|
|
155
|
+
}
|
|
156
|
+
var FALLBACK_LANG = "en";
|
|
157
|
+
var formats = /* @__PURE__ */ new Map();
|
|
158
|
+
function numberFormat(lang, style) {
|
|
159
|
+
const key = `${style}|${lang}`;
|
|
160
|
+
let format = formats.get(key);
|
|
161
|
+
if (!format) {
|
|
162
|
+
const options = style === "percent" ? { style, maximumFractionDigits: 0 } : { style };
|
|
163
|
+
try {
|
|
164
|
+
format = new Intl.NumberFormat(lang, options);
|
|
165
|
+
} catch {
|
|
166
|
+
format = new Intl.NumberFormat(FALLBACK_LANG, options);
|
|
167
|
+
}
|
|
168
|
+
formats.set(key, format);
|
|
169
|
+
}
|
|
170
|
+
return format;
|
|
171
|
+
}
|
|
172
|
+
function zoomReadout(zoom, lang, messages) {
|
|
173
|
+
const text = formatPercent(zoom, lang);
|
|
174
|
+
return {
|
|
175
|
+
text,
|
|
176
|
+
ariaLabel: messages.zoomToActualNow(formatPercent(1, lang), text),
|
|
177
|
+
canZoomOut: canZoomOut(zoom),
|
|
178
|
+
canZoomIn: canZoomIn(zoom)
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/core/keymap.ts
|
|
183
|
+
function matchChromeKey(event) {
|
|
184
|
+
if (event.altKey) return void 0;
|
|
185
|
+
if (isCommand(event)) {
|
|
186
|
+
if (event.key === "+" || event.key === "=" || event.code === "NumpadAdd") return "zoomIn";
|
|
187
|
+
if (event.key === "-" || event.key === "_" || event.code === "NumpadSubtract") return "zoomOut";
|
|
188
|
+
if (event.key === "0" || event.code === "Numpad0") return "zoomToActual";
|
|
189
|
+
return void 0;
|
|
190
|
+
}
|
|
191
|
+
return event.shiftKey && event.code === "Digit1" ? "fit" : void 0;
|
|
192
|
+
}
|
|
193
|
+
function isCommand(event) {
|
|
194
|
+
return event.metaKey || event.ctrlKey;
|
|
195
|
+
}
|
|
196
|
+
function isUndoRedoKey(event) {
|
|
197
|
+
if (event.altKey || !isCommand(event)) return false;
|
|
198
|
+
const letter = undoRedoLetter(event);
|
|
199
|
+
return letter === "z" || letter === "y" && !event.shiftKey;
|
|
200
|
+
}
|
|
201
|
+
function undoRedoLetter(event) {
|
|
202
|
+
if (event.key.length === 1 && event.key.charCodeAt(0) < 128) return event.key.toLowerCase();
|
|
203
|
+
if (event.code === "KeyZ") return "z";
|
|
204
|
+
if (event.code === "KeyY") return "y";
|
|
205
|
+
return void 0;
|
|
206
|
+
}
|
|
207
|
+
var TEXT_FIELD_SELECTOR = "input, textarea, select, [contenteditable]:not([contenteditable='false'])";
|
|
208
|
+
function decideKey(event, context) {
|
|
209
|
+
const stop = context.host === "ide";
|
|
210
|
+
const composing = event.isComposing === true;
|
|
211
|
+
if (event.key === "Escape" && !event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey) {
|
|
212
|
+
if (context.menuOpen && !composing) {
|
|
213
|
+
return { action: "closeMenu", preventDefault: true, stopPropagation: stop };
|
|
214
|
+
}
|
|
215
|
+
if (context.menuOpen || context.dialogOpen) return { preventDefault: false, stopPropagation: stop };
|
|
216
|
+
}
|
|
217
|
+
const chromeKey = matchChromeKey(event);
|
|
218
|
+
if (chromeKey !== void 0) {
|
|
219
|
+
if (context.startScreen === true && !stop) return { preventDefault: false, stopPropagation: false };
|
|
220
|
+
const blocked = composing || context.inTextField || context.modalOpen;
|
|
221
|
+
if (!blocked) return { action: chromeKey, preventDefault: true, stopPropagation: stop };
|
|
222
|
+
return { preventDefault: false, stopPropagation: chromeKey === "fit" ? false : stop };
|
|
223
|
+
}
|
|
224
|
+
if (context.inTextField && isUndoRedoKey(event)) return { preventDefault: false, stopPropagation: stop };
|
|
225
|
+
if (context.defaultPrevented) return { preventDefault: false, stopPropagation: stop };
|
|
226
|
+
return { preventDefault: false, stopPropagation: false };
|
|
227
|
+
}
|
|
228
|
+
function detectPlatform(navigator) {
|
|
229
|
+
const platform = navigator?.userAgentData?.platform || navigator?.platform || "";
|
|
230
|
+
return /mac/i.test(platform) ? "mac" : "other";
|
|
231
|
+
}
|
|
232
|
+
function shortcutLabel(key, platform, keyNames) {
|
|
233
|
+
if (platform === "mac") return MAC_SHORTCUT_LABELS[key];
|
|
234
|
+
switch (key) {
|
|
235
|
+
case "zoomIn":
|
|
236
|
+
return `${keyNames.ctrl}+${keyNames.plus}`;
|
|
237
|
+
case "zoomOut":
|
|
238
|
+
return `${keyNames.ctrl}+${keyNames.minus}`;
|
|
239
|
+
case "zoomToActual":
|
|
240
|
+
return `${keyNames.ctrl}+0`;
|
|
241
|
+
case "fit":
|
|
242
|
+
return `${keyNames.shift}+1`;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
var MAC_SHORTCUT_LABELS = {
|
|
246
|
+
zoomIn: "\u2318+",
|
|
247
|
+
zoomOut: "\u2318\u2212",
|
|
248
|
+
zoomToActual: "\u23180",
|
|
249
|
+
fit: "\u21E71"
|
|
250
|
+
};
|
|
251
|
+
function ariaKeyShortcuts(key, platform) {
|
|
252
|
+
const command = platform === "mac" ? "Meta" : "Control";
|
|
253
|
+
switch (key) {
|
|
254
|
+
case "zoomIn":
|
|
255
|
+
return `${command}+= ${command}+Plus`;
|
|
256
|
+
case "zoomOut":
|
|
257
|
+
return `${command}+-`;
|
|
258
|
+
case "zoomToActual":
|
|
259
|
+
return `${command}+0`;
|
|
260
|
+
case "fit":
|
|
261
|
+
return "Shift+1";
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
var MAC_MODIFIER_SYMBOLS = {
|
|
265
|
+
"\u2318": "Meta",
|
|
266
|
+
"\u21E7": "Shift",
|
|
267
|
+
"\u2325": "Alt",
|
|
268
|
+
"\u2303": "Control"
|
|
269
|
+
};
|
|
270
|
+
var MODIFIER_NAMES = {
|
|
271
|
+
cmd: "Meta",
|
|
272
|
+
command: "Meta",
|
|
273
|
+
meta: "Meta",
|
|
274
|
+
ctrl: "Control",
|
|
275
|
+
control: "Control",
|
|
276
|
+
strg: "Control",
|
|
277
|
+
shift: "Shift",
|
|
278
|
+
umschalt: "Shift",
|
|
279
|
+
alt: "Alt",
|
|
280
|
+
option: "Alt",
|
|
281
|
+
opt: "Alt"
|
|
282
|
+
};
|
|
283
|
+
var NAMED_KEYS = {
|
|
284
|
+
esc: "Escape",
|
|
285
|
+
escape: "Escape",
|
|
286
|
+
del: "Delete",
|
|
287
|
+
delete: "Delete",
|
|
288
|
+
entf: "Delete",
|
|
289
|
+
backspace: "Backspace",
|
|
290
|
+
enter: "Enter",
|
|
291
|
+
return: "Enter",
|
|
292
|
+
tab: "Tab",
|
|
293
|
+
space: "Space",
|
|
294
|
+
home: "Home",
|
|
295
|
+
end: "End",
|
|
296
|
+
pageup: "PageUp",
|
|
297
|
+
pagedown: "PageDown",
|
|
298
|
+
arrowup: "ArrowUp",
|
|
299
|
+
arrowdown: "ArrowDown",
|
|
300
|
+
arrowleft: "ArrowLeft",
|
|
301
|
+
arrowright: "ArrowRight",
|
|
302
|
+
"\u2191": "ArrowUp",
|
|
303
|
+
"\u2193": "ArrowDown",
|
|
304
|
+
"\u2190": "ArrowLeft",
|
|
305
|
+
"\u2192": "ArrowRight",
|
|
306
|
+
plus: "Plus"
|
|
307
|
+
};
|
|
308
|
+
function ariaKeyName(key) {
|
|
309
|
+
const named = NAMED_KEYS[key.toLowerCase()];
|
|
310
|
+
if (named) return named;
|
|
311
|
+
if (/^f\d{1,2}$/i.test(key)) return key.toUpperCase();
|
|
312
|
+
if (key.length !== 1 || MAC_MODIFIER_SYMBOLS[key]) return void 0;
|
|
313
|
+
if (key === "+") return "Plus";
|
|
314
|
+
if (key === " ") return "Space";
|
|
315
|
+
return key.toUpperCase();
|
|
316
|
+
}
|
|
317
|
+
function ariaKeyShortcutsFromLabel(label) {
|
|
318
|
+
let rest = label.trim();
|
|
319
|
+
const modifiers = [];
|
|
320
|
+
for (let symbol = MAC_MODIFIER_SYMBOLS[rest.charAt(0)]; symbol && rest.length > 1; ) {
|
|
321
|
+
modifiers.push(symbol);
|
|
322
|
+
rest = rest.slice(1).trim();
|
|
323
|
+
symbol = MAC_MODIFIER_SYMBOLS[rest.charAt(0)];
|
|
324
|
+
}
|
|
325
|
+
if (!rest || /\s/.test(rest.replace(/\s*\+\s*/g, "+"))) return void 0;
|
|
326
|
+
const parts = rest.split("+").map((part) => part.trim());
|
|
327
|
+
let key = parts.pop() ?? "";
|
|
328
|
+
if (key === "" && parts.at(-1) === "") {
|
|
329
|
+
parts.pop();
|
|
330
|
+
key = "+";
|
|
331
|
+
}
|
|
332
|
+
for (const part of parts) {
|
|
333
|
+
const modifier = MODIFIER_NAMES[part.toLowerCase()];
|
|
334
|
+
if (!modifier) return void 0;
|
|
335
|
+
modifiers.push(modifier);
|
|
336
|
+
}
|
|
337
|
+
const name = ariaKeyName(key);
|
|
338
|
+
return name ? [...new Set(modifiers), name].join("+") : void 0;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/core/appearance.ts
|
|
342
|
+
function resolveAppearance(requested, themes) {
|
|
343
|
+
const [fallback] = themes;
|
|
344
|
+
if (fallback === void 0) {
|
|
345
|
+
throw new Error(
|
|
346
|
+
"@miragon/modeler-shell: resolveAppearance needs at least one theme the modeler can show"
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
const theme = themes.includes(requested.theme) ? requested.theme : fallback;
|
|
350
|
+
return withKontrast(theme, requested.kontrast);
|
|
351
|
+
}
|
|
352
|
+
function withKontrast(theme, kontrast) {
|
|
353
|
+
return kontrast === "hoch" ? { theme, kontrast } : { theme };
|
|
354
|
+
}
|
|
355
|
+
function appearanceFromBodyClasses(classes) {
|
|
356
|
+
const present = new Set(classes);
|
|
357
|
+
if (present.has("vscode-high-contrast-light")) return { theme: "light", kontrast: "hoch" };
|
|
358
|
+
if (present.has("vscode-high-contrast")) return { theme: "dark", kontrast: "hoch" };
|
|
359
|
+
if (present.has("vscode-dark")) return { theme: "dark" };
|
|
360
|
+
if (present.has("vscode-light")) return { theme: "light" };
|
|
361
|
+
return void 0;
|
|
362
|
+
}
|
|
363
|
+
function reducedMotionFromBodyClasses(classes) {
|
|
364
|
+
return new Set(classes).has("vscode-reduce-motion");
|
|
365
|
+
}
|
|
366
|
+
var REDUCED_MOTION_CLASS = "cd-shell-reduce-motion";
|
|
367
|
+
function appearanceAttributes(appearance) {
|
|
368
|
+
return appearance.kontrast === "hoch" ? { "data-cd-theme": appearance.theme, "data-cd-kontrast": "hoch" } : { "data-cd-theme": appearance.theme };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/core/messages.ts
|
|
372
|
+
var defaultMessages = {
|
|
373
|
+
menu: "Menu",
|
|
374
|
+
tools: "Tools",
|
|
375
|
+
canvas: "Diagram",
|
|
376
|
+
zoom: "Zoom",
|
|
377
|
+
zoomOut: "Zoom out",
|
|
378
|
+
zoomIn: "Zoom in",
|
|
379
|
+
zoomToActual: (percent) => `Zoom to ${percent}`,
|
|
380
|
+
zoomToActualNow: (percent, now) => `Zoom to ${percent} (now ${now})`,
|
|
381
|
+
fit: "Fit to view",
|
|
382
|
+
legend: "Legend",
|
|
383
|
+
closeLegend: "Close legend",
|
|
384
|
+
problems: (count, formattedCount) => `${formattedCount} ${count === 1 ? "problem" : "problems"}`,
|
|
385
|
+
problemsLabel: (count, errors, formattedCount, formattedErrors) => `${formattedCount} ${count === 1 ? "problem" : "problems"}, ${errors === 0 ? "no errors" : `${formattedErrors} ${errors === 1 ? "error" : "errors"}`}`,
|
|
386
|
+
showProblems: "Show in Problems view",
|
|
387
|
+
emptyHint: "Pick a tool from the palette to start.",
|
|
388
|
+
insertExample: "Insert example",
|
|
389
|
+
invalidFile: (diagram) => `This file isn't a valid ${diagram} right now. The diagram shows the last valid version and is read-only until you fix the text.`,
|
|
390
|
+
keyNames: { ctrl: "Ctrl", shift: "Shift", plus: "Plus", minus: "Minus" },
|
|
391
|
+
share: "Share",
|
|
392
|
+
legal: "Legal",
|
|
393
|
+
copyright: (year) => `\xA9 ${year} Miragon GmbH`,
|
|
394
|
+
opensInNewTab: "Opens in a new tab",
|
|
395
|
+
newDiagram: "New diagram",
|
|
396
|
+
openFile: "Open\u2026",
|
|
397
|
+
showExample: "Show example",
|
|
398
|
+
importFile: "Import\u2026",
|
|
399
|
+
appearance: "Appearance",
|
|
400
|
+
appearanceSystem: "System",
|
|
401
|
+
appearanceLight: "Light",
|
|
402
|
+
appearanceDark: "Dark",
|
|
403
|
+
problemsTitle: "Problems",
|
|
404
|
+
problemSeverity: (severity) => severity === "error" ? "Error" : "Warning",
|
|
405
|
+
close: "Close",
|
|
406
|
+
dropFile: "Drop file here",
|
|
407
|
+
dropForbidden: (reason) => reason === "multiple" ? "Drop one file at a time" : reason === "dialog" ? "Close the dialog to drop a file" : "This modeler can't open this file type",
|
|
408
|
+
dropHint: "Or drop a file here",
|
|
409
|
+
openFileLink: "Open a file\u2026",
|
|
410
|
+
cannotOpenFile: (name, accepted) => `Couldn't open \u201C${name}\u201D. Drop a ${accepted} file.`
|
|
411
|
+
};
|
|
412
|
+
function resolveMessages(overrides) {
|
|
413
|
+
const { keyNames, ...texts } = overrides ?? {};
|
|
414
|
+
return {
|
|
415
|
+
...defaultMessages,
|
|
416
|
+
...definedEntries(texts),
|
|
417
|
+
keyNames: { ...defaultMessages.keyNames, ...definedEntries(keyNames ?? {}) }
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
function definedEntries(partial) {
|
|
421
|
+
return Object.fromEntries(Object.entries(partial).filter(([, value]) => value !== void 0));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/core/layout.ts
|
|
425
|
+
var PALETTE_CLEARANCE = 16;
|
|
426
|
+
var TOOLTIP_DELAY_MS = 300;
|
|
427
|
+
function decidePaletteRow(input) {
|
|
428
|
+
const { containerWidth, paletteWidth, menuRight, primaryLeft } = input;
|
|
429
|
+
if (containerWidth <= SHELL.narrow) return "second-row";
|
|
430
|
+
const paletteLeft = (containerWidth - paletteWidth) / 2;
|
|
431
|
+
const paletteRight = (containerWidth + paletteWidth) / 2;
|
|
432
|
+
if (paletteLeft < menuRight + PALETTE_CLEARANCE) return "second-row";
|
|
433
|
+
if (primaryLeft !== void 0 && paletteRight > primaryLeft - PALETTE_CLEARANCE) return "second-row";
|
|
434
|
+
return "top";
|
|
435
|
+
}
|
|
436
|
+
function legendStartsOpen(containerHeight) {
|
|
437
|
+
return containerHeight > SHELL.low;
|
|
438
|
+
}
|
|
439
|
+
function placeTooltip(input) {
|
|
440
|
+
const { anchor, island, tooltip, container } = input;
|
|
441
|
+
const inUpperHalf = island.y + island.height / 2 < container.height / 2;
|
|
442
|
+
const y = inUpperHalf ? island.y + island.height + SHELL.gap : island.y - SHELL.gap - tooltip.height;
|
|
443
|
+
const centred = anchor.x + anchor.width / 2 - tooltip.width / 2;
|
|
444
|
+
const rightmost = container.width - SHELL.edge - tooltip.width;
|
|
445
|
+
const x = Math.max(SHELL.edge, Math.min(centred, rightmost));
|
|
446
|
+
return { x, y };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// src/core/web-chrome/top-row.ts
|
|
450
|
+
function decideTopRow(input) {
|
|
451
|
+
const { containerWidth, paletteWidth, menuRight, primary } = input;
|
|
452
|
+
if (!primary) {
|
|
453
|
+
return {
|
|
454
|
+
paletteRow: decidePaletteRow({ containerWidth, paletteWidth, menuRight }),
|
|
455
|
+
primaryLabel: "shown"
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
const paletteRowBeside = (primaryWidth) => decidePaletteRow({
|
|
459
|
+
containerWidth,
|
|
460
|
+
paletteWidth,
|
|
461
|
+
menuRight,
|
|
462
|
+
primaryLeft: containerWidth - SHELL.edge - primaryWidth
|
|
463
|
+
});
|
|
464
|
+
if (paletteRowBeside(primary.withLabel) === "top") return { paletteRow: "top", primaryLabel: "shown" };
|
|
465
|
+
if (paletteRowBeside(primary.iconOnly) === "top") return { paletteRow: "top", primaryLabel: "collapsed" };
|
|
466
|
+
const labelledLeft = containerWidth - SHELL.edge - primary.withLabel;
|
|
467
|
+
return {
|
|
468
|
+
paletteRow: "second-row",
|
|
469
|
+
primaryLabel: menuRight + PALETTE_CLEARANCE <= labelledLeft ? "shown" : "collapsed"
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// src/core/web-chrome/drop.ts
|
|
474
|
+
function acceptEntries(accept) {
|
|
475
|
+
const entries = accept.map((entry) => entry.trim().toLowerCase()).filter((entry) => entry !== "");
|
|
476
|
+
return {
|
|
477
|
+
extensions: entries.filter((entry) => entry.startsWith(".")),
|
|
478
|
+
mimeTypes: entries.filter((entry) => !entry.startsWith(".") && entry.includes("/"))
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function matchesMimeType(type, mimeTypes) {
|
|
482
|
+
const normalized = type.trim().toLowerCase();
|
|
483
|
+
if (normalized === "") return false;
|
|
484
|
+
return mimeTypes.some(
|
|
485
|
+
(entry) => entry.endsWith("/*") ? normalized.startsWith(entry.slice(0, -1)) : normalized === entry
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
function dragVerdict(items, context) {
|
|
489
|
+
const files = items.filter((item) => item.kind === "file");
|
|
490
|
+
if (files.length === 0) return { state: "none" };
|
|
491
|
+
if (context.dialogOpen) return { state: "forbidden", reason: "dialog" };
|
|
492
|
+
if (files.length > 1 && !context.multiple) return { state: "forbidden", reason: "multiple" };
|
|
493
|
+
const { mimeTypes } = acceptEntries(context.accept);
|
|
494
|
+
const foreignType = (item) => item.type.trim() !== "" && !matchesMimeType(item.type, mimeTypes);
|
|
495
|
+
if (mimeTypes.length > 0 && files.every(foreignType)) return { state: "forbidden", reason: "type" };
|
|
496
|
+
return { state: "over" };
|
|
497
|
+
}
|
|
498
|
+
function acceptsFile(file, accept) {
|
|
499
|
+
const { extensions, mimeTypes } = acceptEntries(accept);
|
|
500
|
+
if (extensions.length === 0 && mimeTypes.length === 0) return true;
|
|
501
|
+
const name = file.name.toLowerCase();
|
|
502
|
+
return extensions.some((extension) => name.endsWith(extension)) || matchesMimeType(file.type, mimeTypes);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// src/core/feedback/toasts.ts
|
|
506
|
+
var MAX_TOASTS = 3;
|
|
507
|
+
var TOAST_DURATION_MS = {
|
|
508
|
+
success: 2800,
|
|
509
|
+
info: 2800,
|
|
510
|
+
warning: 5e3,
|
|
511
|
+
danger: 5e3
|
|
512
|
+
};
|
|
513
|
+
var TOAST_MAX_WIDTH = 480;
|
|
514
|
+
function toastLifetime(request) {
|
|
515
|
+
if (request.action) return void 0;
|
|
516
|
+
const requested = request.durationMs;
|
|
517
|
+
const longer = requested !== void 0 && Number.isFinite(requested) ? requested : 0;
|
|
518
|
+
return Math.max(TOAST_DURATION_MS[request.status], longer);
|
|
519
|
+
}
|
|
520
|
+
function toastToEvict(stack) {
|
|
521
|
+
if (stack.length < MAX_TOASTS) return void 0;
|
|
522
|
+
const evicted = stack.find((slot) => !slot.engaged && !slot.hasAction) ?? stack.find((slot) => !slot.engaged) ?? stack[0];
|
|
523
|
+
return evicted?.id;
|
|
524
|
+
}
|
|
525
|
+
function toastStackWidth(containerWidth) {
|
|
526
|
+
return Math.max(0, Math.min(TOAST_MAX_WIDTH, containerWidth - 2 * SHELL.edge));
|
|
527
|
+
}
|
|
528
|
+
function toastStackBottom(input) {
|
|
529
|
+
const stackWidth = toastStackWidth(input.containerWidth);
|
|
530
|
+
const { cardWidth } = input;
|
|
531
|
+
const width = cardWidth !== void 0 && Number.isFinite(cardWidth) ? Math.min(stackWidth, Math.max(0, cardWidth)) : stackWidth;
|
|
532
|
+
const left = (input.containerWidth - width) / 2;
|
|
533
|
+
const right = left + width;
|
|
534
|
+
let highest;
|
|
535
|
+
for (const obstacle of input.obstacles) {
|
|
536
|
+
const distance = Math.max(obstacle.left - right, left - obstacle.right);
|
|
537
|
+
if (distance >= SHELL.gap) continue;
|
|
538
|
+
highest = highest === void 0 ? obstacle.top : Math.max(highest, obstacle.top);
|
|
539
|
+
}
|
|
540
|
+
return highest === void 0 ? SHELL.edge : Math.max(SHELL.edge, highest + SHELL.gap);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/core/feedback/appearance.ts
|
|
544
|
+
var APPEARANCE_STORAGE_KEY = "miragon.darstellung";
|
|
545
|
+
function parseAppearanceChoice(stored) {
|
|
546
|
+
return stored === "light" || stored === "dark" ? stored : "system";
|
|
547
|
+
}
|
|
548
|
+
function documentMode(choice, themes, colors) {
|
|
549
|
+
if (!themes.includes("dark")) return fixedMode("light", colors.light);
|
|
550
|
+
const parsed = parseAppearanceChoice(choice);
|
|
551
|
+
if (parsed === "system") {
|
|
552
|
+
return {
|
|
553
|
+
theme: "system",
|
|
554
|
+
colorScheme: "light dark",
|
|
555
|
+
themeColor: { light: colors.light, dark: colors.dark }
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
return fixedMode(parsed, colors[parsed]);
|
|
559
|
+
}
|
|
560
|
+
function fixedMode(theme, color) {
|
|
561
|
+
return { theme, colorScheme: theme, themeColor: { light: color, dark: color } };
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/vscode/protocol.ts
|
|
565
|
+
var APPEARANCE_MESSAGE_TYPE = "modelerShell.appearance";
|
|
566
|
+
function appearanceMessage(appearance) {
|
|
567
|
+
return {
|
|
568
|
+
type: APPEARANCE_MESSAGE_TYPE,
|
|
569
|
+
appearance: appearance.kontrast ? { theme: appearance.theme, kontrast: appearance.kontrast } : { theme: appearance.theme }
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
function isRecord(value) {
|
|
573
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
574
|
+
}
|
|
575
|
+
function isAppearanceMessage(value) {
|
|
576
|
+
if (!isRecord(value) || value.type !== APPEARANCE_MESSAGE_TYPE || !isRecord(value.appearance)) return false;
|
|
577
|
+
const { theme, kontrast } = value.appearance;
|
|
578
|
+
return (theme === "light" || theme === "dark") && (kontrast === void 0 || kontrast === "hoch");
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/vscode/webview/appearance.ts
|
|
582
|
+
var NO_TRANSITION_CLASS = "cd-shell-no-transition";
|
|
583
|
+
var latestSwitch = /* @__PURE__ */ new WeakMap();
|
|
584
|
+
function showsAppearance(element, appearance) {
|
|
585
|
+
const attributes = appearanceAttributes(appearance);
|
|
586
|
+
return element.getAttribute("data-cd-theme") === attributes["data-cd-theme"] && element.getAttribute("data-cd-kontrast") === (attributes["data-cd-kontrast"] ?? null);
|
|
587
|
+
}
|
|
588
|
+
function applyAppearance(appearance, element) {
|
|
589
|
+
const target = element ?? document.documentElement;
|
|
590
|
+
const html = target.ownerDocument.documentElement;
|
|
591
|
+
const attributes = appearanceAttributes(appearance);
|
|
592
|
+
const kontrast = attributes["data-cd-kontrast"];
|
|
593
|
+
const switchId = (latestSwitch.get(html) ?? 0) + 1;
|
|
594
|
+
latestSwitch.set(html, switchId);
|
|
595
|
+
html.classList.add(NO_TRANSITION_CLASS);
|
|
596
|
+
target.setAttribute("data-cd-theme", attributes["data-cd-theme"]);
|
|
597
|
+
if (kontrast) target.setAttribute("data-cd-kontrast", kontrast);
|
|
598
|
+
else target.removeAttribute("data-cd-kontrast");
|
|
599
|
+
requestAnimationFrame(
|
|
600
|
+
() => requestAnimationFrame(() => {
|
|
601
|
+
if (latestSwitch.get(html) !== switchId) return;
|
|
602
|
+
latestSwitch.delete(html);
|
|
603
|
+
html.classList.remove(NO_TRANSITION_CLASS);
|
|
604
|
+
})
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
function connectAppearance(options) {
|
|
608
|
+
const { themes } = options;
|
|
609
|
+
if (themes.length === 0) {
|
|
610
|
+
throw new Error("@miragon/modeler-shell: connectAppearance needs at least one theme");
|
|
611
|
+
}
|
|
612
|
+
const html = document.documentElement;
|
|
613
|
+
const show = (requested) => {
|
|
614
|
+
const resolved = resolveAppearance(requested, themes);
|
|
615
|
+
if (!showsAppearance(html, resolved)) applyAppearance(resolved, html);
|
|
616
|
+
};
|
|
617
|
+
let fallbackActive = !html.hasAttribute("data-cd-theme");
|
|
618
|
+
const applyBodyClasses = () => {
|
|
619
|
+
if (!fallbackActive || !document.body) return;
|
|
620
|
+
const requested = appearanceFromBodyClasses(document.body.classList);
|
|
621
|
+
if (requested) show(requested);
|
|
622
|
+
};
|
|
623
|
+
const observer = new MutationObserver(applyBodyClasses);
|
|
624
|
+
const startFallback = () => {
|
|
625
|
+
if (!fallbackActive || !document.body) return;
|
|
626
|
+
observer.observe(document.body, { attributes: true, attributeFilter: ["class"] });
|
|
627
|
+
applyBodyClasses();
|
|
628
|
+
};
|
|
629
|
+
const stopFallback = () => {
|
|
630
|
+
if (!fallbackActive) return;
|
|
631
|
+
fallbackActive = false;
|
|
632
|
+
observer.disconnect();
|
|
633
|
+
document.removeEventListener("DOMContentLoaded", startFallback);
|
|
634
|
+
};
|
|
635
|
+
const onMessage = (event) => {
|
|
636
|
+
if (!isAppearanceMessage(event.data)) return;
|
|
637
|
+
stopFallback();
|
|
638
|
+
show(event.data.appearance);
|
|
639
|
+
};
|
|
640
|
+
window.addEventListener("message", onMessage);
|
|
641
|
+
if (document.body) startFallback();
|
|
642
|
+
else if (fallbackActive) document.addEventListener("DOMContentLoaded", startFallback, { once: true });
|
|
643
|
+
return () => {
|
|
644
|
+
window.removeEventListener("message", onMessage);
|
|
645
|
+
fallbackActive = false;
|
|
646
|
+
observer.disconnect();
|
|
647
|
+
document.removeEventListener("DOMContentLoaded", startFallback);
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
function connectReducedMotion() {
|
|
651
|
+
const html = document.documentElement;
|
|
652
|
+
const apply = () => {
|
|
653
|
+
if (document.body)
|
|
654
|
+
html.classList.toggle(REDUCED_MOTION_CLASS, reducedMotionFromBodyClasses(document.body.classList));
|
|
655
|
+
};
|
|
656
|
+
const observer = new MutationObserver(apply);
|
|
657
|
+
const start = () => {
|
|
658
|
+
if (!document.body) return;
|
|
659
|
+
observer.observe(document.body, { attributes: true, attributeFilter: ["class"] });
|
|
660
|
+
apply();
|
|
661
|
+
};
|
|
662
|
+
if (document.body) start();
|
|
663
|
+
else document.addEventListener("DOMContentLoaded", start, { once: true });
|
|
664
|
+
return () => {
|
|
665
|
+
observer.disconnect();
|
|
666
|
+
document.removeEventListener("DOMContentLoaded", start);
|
|
667
|
+
html.classList.remove(REDUCED_MOTION_CLASS);
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// src/dom/element.ts
|
|
672
|
+
function createElement(ownerDocument, tag, attributes = {}, ...children) {
|
|
673
|
+
const element = ownerDocument.createElement(tag);
|
|
674
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
675
|
+
if (value !== void 0) element.setAttribute(name, value);
|
|
676
|
+
}
|
|
677
|
+
for (const child of children) {
|
|
678
|
+
if (child === void 0 || child === null || child === false) continue;
|
|
679
|
+
element.append(child);
|
|
680
|
+
}
|
|
681
|
+
return element;
|
|
682
|
+
}
|
|
683
|
+
function toggleAttribute(element, name, value) {
|
|
684
|
+
if (value === null || value === false) element.removeAttribute(name);
|
|
685
|
+
else element.setAttribute(name, value);
|
|
686
|
+
}
|
|
687
|
+
function boxRelativeTo(element, root) {
|
|
688
|
+
const box = element.getBoundingClientRect();
|
|
689
|
+
const origin = root.getBoundingClientRect();
|
|
690
|
+
return {
|
|
691
|
+
x: box.left - origin.left - root.clientLeft,
|
|
692
|
+
y: box.top - origin.top - root.clientTop,
|
|
693
|
+
width: box.width,
|
|
694
|
+
height: box.height
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
function accessibleName(control) {
|
|
698
|
+
return (control.getAttribute("aria-label") ?? control.textContent ?? "").trim();
|
|
699
|
+
}
|
|
700
|
+
function hasVisibleFocus(element) {
|
|
701
|
+
try {
|
|
702
|
+
return element.matches(":focus-visible");
|
|
703
|
+
} catch {
|
|
704
|
+
return true;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// src/dom/feedback/appearance.ts
|
|
709
|
+
var NO_TRANSITION_CLASS2 = "cd-shell-no-transition";
|
|
710
|
+
var CONTRAST_QUERY = "(prefers-contrast: more)";
|
|
711
|
+
var SCHEME_MEDIA = {
|
|
712
|
+
light: "(prefers-color-scheme: light)",
|
|
713
|
+
dark: "(prefers-color-scheme: dark)"
|
|
714
|
+
};
|
|
715
|
+
function onMediaChange(query, listener) {
|
|
716
|
+
if (typeof query.addEventListener === "function") {
|
|
717
|
+
query.addEventListener("change", listener);
|
|
718
|
+
return () => query.removeEventListener("change", listener);
|
|
719
|
+
}
|
|
720
|
+
query.addListener(listener);
|
|
721
|
+
return () => query.removeListener(listener);
|
|
722
|
+
}
|
|
723
|
+
function connectWebAppearance(context) {
|
|
724
|
+
const { ownerDocument, themes, options } = context;
|
|
725
|
+
const darkCapable = themes.includes("dark");
|
|
726
|
+
if (darkCapable && !options) {
|
|
727
|
+
throw new Error(
|
|
728
|
+
'@miragon/modeler-shell: a web modeler whose themes include "dark" needs the appearance option ({ themeColors } from its vendored cd-tokens.generated.json)'
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
const html = ownerDocument.documentElement;
|
|
732
|
+
const view = ownerDocument.defaultView;
|
|
733
|
+
const cleanups = [];
|
|
734
|
+
const contrast = typeof view?.matchMedia === "function" ? view.matchMedia(CONTRAST_QUERY) : void 0;
|
|
735
|
+
if (contrast) {
|
|
736
|
+
const mirrorContrast = () => toggleAttribute(html, "data-cd-kontrast", contrast.matches && "hoch");
|
|
737
|
+
mirrorContrast();
|
|
738
|
+
cleanups.push(onMediaChange(contrast, mirrorContrast));
|
|
739
|
+
}
|
|
740
|
+
function destroy() {
|
|
741
|
+
for (const cleanup of cleanups.splice(0)) cleanup();
|
|
742
|
+
}
|
|
743
|
+
if (!darkCapable || !options) {
|
|
744
|
+
return { choice: () => "system", setChoice() {
|
|
745
|
+
}, destroy };
|
|
746
|
+
}
|
|
747
|
+
const { themeColors } = options;
|
|
748
|
+
function storage() {
|
|
749
|
+
if (options?.storage) return options.storage;
|
|
750
|
+
try {
|
|
751
|
+
return globalThis.localStorage;
|
|
752
|
+
} catch {
|
|
753
|
+
return void 0;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function readStored() {
|
|
757
|
+
try {
|
|
758
|
+
return parseAppearanceChoice(storage()?.getItem(APPEARANCE_STORAGE_KEY));
|
|
759
|
+
} catch {
|
|
760
|
+
return "system";
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
function store(choice) {
|
|
764
|
+
try {
|
|
765
|
+
const target = storage();
|
|
766
|
+
if (choice === "system") target?.removeItem(APPEARANCE_STORAGE_KEY);
|
|
767
|
+
else target?.setItem(APPEARANCE_STORAGE_KEY, choice);
|
|
768
|
+
} catch {
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function nextFrame(callback) {
|
|
772
|
+
if (view && typeof view.requestAnimationFrame === "function") {
|
|
773
|
+
const frame = view.requestAnimationFrame(() => callback());
|
|
774
|
+
return () => view.cancelAnimationFrame(frame);
|
|
775
|
+
}
|
|
776
|
+
const timer = setTimeout(callback, 16);
|
|
777
|
+
return () => clearTimeout(timer);
|
|
778
|
+
}
|
|
779
|
+
let cancelTransitionFrame;
|
|
780
|
+
function allowTransitions() {
|
|
781
|
+
cancelTransitionFrame?.();
|
|
782
|
+
cancelTransitionFrame = void 0;
|
|
783
|
+
html.classList.remove(NO_TRANSITION_CLASS2);
|
|
784
|
+
}
|
|
785
|
+
cleanups.push(() => {
|
|
786
|
+
if (cancelTransitionFrame) allowTransitions();
|
|
787
|
+
});
|
|
788
|
+
function suspendTransitions() {
|
|
789
|
+
cancelTransitionFrame?.();
|
|
790
|
+
html.classList.add(NO_TRANSITION_CLASS2);
|
|
791
|
+
cancelTransitionFrame = nextFrame(() => {
|
|
792
|
+
cancelTransitionFrame = nextFrame(allowTransitions);
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
function metaFor(name, media) {
|
|
796
|
+
const normalize = (value) => (value ?? "").replace(/\s+/g, "").toLowerCase();
|
|
797
|
+
const existing = [...ownerDocument.querySelectorAll(`meta[name="${name}"]`)].find(
|
|
798
|
+
(meta) => media === void 0 ? true : normalize(meta.getAttribute("media")) === normalize(media)
|
|
799
|
+
);
|
|
800
|
+
if (existing) return existing;
|
|
801
|
+
const created = createElement(ownerDocument, "meta", { name, media });
|
|
802
|
+
(ownerDocument.head ?? html).append(created);
|
|
803
|
+
return created;
|
|
804
|
+
}
|
|
805
|
+
function apply(choice) {
|
|
806
|
+
const mode = documentMode(choice, themes, themeColors);
|
|
807
|
+
suspendTransitions();
|
|
808
|
+
html.setAttribute("data-cd-theme", mode.theme);
|
|
809
|
+
metaFor("color-scheme").setAttribute("content", mode.colorScheme);
|
|
810
|
+
metaFor("theme-color", SCHEME_MEDIA.light).setAttribute("content", mode.themeColor.light);
|
|
811
|
+
metaFor("theme-color", SCHEME_MEDIA.dark).setAttribute("content", mode.themeColor.dark);
|
|
812
|
+
}
|
|
813
|
+
let current = readStored();
|
|
814
|
+
const onStorage = (event) => {
|
|
815
|
+
if (event.key !== null && event.key !== APPEARANCE_STORAGE_KEY) return;
|
|
816
|
+
const next = event.key === null ? "system" : parseAppearanceChoice(event.newValue);
|
|
817
|
+
if (next === current) return;
|
|
818
|
+
current = next;
|
|
819
|
+
apply(next);
|
|
820
|
+
};
|
|
821
|
+
if (view) {
|
|
822
|
+
view.addEventListener("storage", onStorage);
|
|
823
|
+
cleanups.push(() => view.removeEventListener("storage", onStorage));
|
|
824
|
+
}
|
|
825
|
+
return {
|
|
826
|
+
choice: () => current,
|
|
827
|
+
setChoice(choice) {
|
|
828
|
+
const next = parseAppearanceChoice(choice);
|
|
829
|
+
current = next;
|
|
830
|
+
store(next);
|
|
831
|
+
apply(next);
|
|
832
|
+
},
|
|
833
|
+
destroy
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// src/dom/icons.ts
|
|
838
|
+
var ICONS = {
|
|
839
|
+
menu: [
|
|
840
|
+
["path", { d: "M4 5h16" }],
|
|
841
|
+
["path", { d: "M4 12h16" }],
|
|
842
|
+
["path", { d: "M4 19h16" }]
|
|
843
|
+
],
|
|
844
|
+
minus: [["path", { d: "M5 12h14" }]],
|
|
845
|
+
plus: [
|
|
846
|
+
["path", { d: "M5 12h14" }],
|
|
847
|
+
["path", { d: "M12 5v14" }]
|
|
848
|
+
],
|
|
849
|
+
maximize: [
|
|
850
|
+
["path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }],
|
|
851
|
+
["path", { d: "M21 8V5a2 2 0 0 0-2-2h-3" }],
|
|
852
|
+
["path", { d: "M3 16v3a2 2 0 0 0 2 2h3" }],
|
|
853
|
+
["path", { d: "M16 21h3a2 2 0 0 0 2-2v-3" }]
|
|
854
|
+
],
|
|
855
|
+
check: [["path", { d: "M20 6 9 17l-5-5" }]],
|
|
856
|
+
x: [
|
|
857
|
+
["path", { d: "M18 6 6 18" }],
|
|
858
|
+
["path", { d: "m6 6 12 12" }]
|
|
859
|
+
],
|
|
860
|
+
"file-plus": [
|
|
861
|
+
[
|
|
862
|
+
"path",
|
|
863
|
+
{
|
|
864
|
+
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"
|
|
865
|
+
}
|
|
866
|
+
],
|
|
867
|
+
["path", { d: "M14 2v5a1 1 0 0 0 1 1h5" }],
|
|
868
|
+
["path", { d: "M9 15h6" }],
|
|
869
|
+
["path", { d: "M12 18v-6" }]
|
|
870
|
+
],
|
|
871
|
+
"folder-open": [
|
|
872
|
+
[
|
|
873
|
+
"path",
|
|
874
|
+
{
|
|
875
|
+
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"
|
|
876
|
+
}
|
|
877
|
+
]
|
|
878
|
+
],
|
|
879
|
+
import: [
|
|
880
|
+
["path", { d: "M12 3v12" }],
|
|
881
|
+
["path", { d: "m8 11 4 4 4-4" }],
|
|
882
|
+
["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" }]
|
|
883
|
+
],
|
|
884
|
+
"file-image": [
|
|
885
|
+
[
|
|
886
|
+
"path",
|
|
887
|
+
{
|
|
888
|
+
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"
|
|
889
|
+
}
|
|
890
|
+
],
|
|
891
|
+
["path", { d: "M14 2v5a1 1 0 0 0 1 1h5" }],
|
|
892
|
+
["circle", { cx: "10", cy: "12", r: "2" }],
|
|
893
|
+
["path", { d: "m20 17-1.296-1.296a2.41 2.41 0 0 0-3.408 0L9 22" }]
|
|
894
|
+
],
|
|
895
|
+
"file-code-corner": [
|
|
896
|
+
[
|
|
897
|
+
"path",
|
|
898
|
+
{
|
|
899
|
+
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"
|
|
900
|
+
}
|
|
901
|
+
],
|
|
902
|
+
["path", { d: "M14 2v5a1 1 0 0 0 1 1h5" }],
|
|
903
|
+
["path", { d: "m5 16-3 3 3 3" }],
|
|
904
|
+
["path", { d: "m9 22 3-3-3-3" }]
|
|
905
|
+
],
|
|
906
|
+
keyboard: [
|
|
907
|
+
["path", { d: "M10 8h.01" }],
|
|
908
|
+
["path", { d: "M12 12h.01" }],
|
|
909
|
+
["path", { d: "M14 8h.01" }],
|
|
910
|
+
["path", { d: "M16 12h.01" }],
|
|
911
|
+
["path", { d: "M18 8h.01" }],
|
|
912
|
+
["path", { d: "M6 8h.01" }],
|
|
913
|
+
["path", { d: "M7 16h10" }],
|
|
914
|
+
["path", { d: "M8 12h.01" }],
|
|
915
|
+
["rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" }]
|
|
916
|
+
],
|
|
917
|
+
"share-2": [
|
|
918
|
+
["circle", { cx: "18", cy: "5", r: "3" }],
|
|
919
|
+
["circle", { cx: "6", cy: "12", r: "3" }],
|
|
920
|
+
["circle", { cx: "18", cy: "19", r: "3" }],
|
|
921
|
+
["line", { x1: "8.59", x2: "15.42", y1: "13.51", y2: "17.49" }],
|
|
922
|
+
["line", { x1: "15.41", x2: "8.59", y1: "6.51", y2: "10.49" }]
|
|
923
|
+
],
|
|
924
|
+
"circle-x": [
|
|
925
|
+
["circle", { cx: "12", cy: "12", r: "10" }],
|
|
926
|
+
["path", { d: "m15 9-6 6" }],
|
|
927
|
+
["path", { d: "m9 9 6 6" }]
|
|
928
|
+
],
|
|
929
|
+
"triangle-alert": [
|
|
930
|
+
["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" }],
|
|
931
|
+
["path", { d: "M12 9v4" }],
|
|
932
|
+
["path", { d: "M12 17h.01" }]
|
|
933
|
+
],
|
|
934
|
+
"square-dashed-mouse-pointer": [
|
|
935
|
+
[
|
|
936
|
+
"path",
|
|
937
|
+
{
|
|
938
|
+
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"
|
|
939
|
+
}
|
|
940
|
+
],
|
|
941
|
+
["path", { d: "M5 3a2 2 0 0 0-2 2" }],
|
|
942
|
+
["path", { d: "M19 3a2 2 0 0 1 2 2" }],
|
|
943
|
+
["path", { d: "M5 21a2 2 0 0 1-2-2" }],
|
|
944
|
+
["path", { d: "M9 3h1" }],
|
|
945
|
+
["path", { d: "M9 21h2" }],
|
|
946
|
+
["path", { d: "M14 3h1" }],
|
|
947
|
+
["path", { d: "M3 9v1" }],
|
|
948
|
+
["path", { d: "M21 9v2" }],
|
|
949
|
+
["path", { d: "M3 14v1" }]
|
|
950
|
+
],
|
|
951
|
+
hand: [
|
|
952
|
+
["path", { d: "M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2" }],
|
|
953
|
+
["path", { d: "M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2" }],
|
|
954
|
+
["path", { d: "M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8" }],
|
|
955
|
+
[
|
|
956
|
+
"path",
|
|
957
|
+
{
|
|
958
|
+
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"
|
|
959
|
+
}
|
|
960
|
+
]
|
|
961
|
+
],
|
|
962
|
+
"line-segment": [
|
|
963
|
+
["circle", { cx: "19", cy: "5", r: "2" }],
|
|
964
|
+
["circle", { cx: "5", cy: "19", r: "2" }],
|
|
965
|
+
["path", { d: "M6.5 17.5 17.5 6.5" }]
|
|
966
|
+
],
|
|
967
|
+
"unfold-horizontal": [
|
|
968
|
+
["path", { d: "M16 12h6" }],
|
|
969
|
+
["path", { d: "M8 12H2" }],
|
|
970
|
+
["path", { d: "M12 2v2" }],
|
|
971
|
+
["path", { d: "M12 8v2" }],
|
|
972
|
+
["path", { d: "M12 14v2" }],
|
|
973
|
+
["path", { d: "M12 20v2" }],
|
|
974
|
+
["path", { d: "m19 15 3-3-3-3" }],
|
|
975
|
+
["path", { d: "m5 9-3 3 3 3" }]
|
|
976
|
+
],
|
|
977
|
+
workflow: [
|
|
978
|
+
["rect", { width: "8", height: "8", x: "3", y: "3", rx: "2" }],
|
|
979
|
+
["path", { d: "M7 11v4a2 2 0 0 0 2 2h4" }],
|
|
980
|
+
["rect", { width: "8", height: "8", x: "13", y: "13", rx: "2" }]
|
|
981
|
+
]
|
|
982
|
+
};
|
|
983
|
+
var DEFAULT_SIZE = 16;
|
|
984
|
+
var DEFAULT_STROKE_WIDTH = 2.5;
|
|
985
|
+
var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
986
|
+
var ICON_ELEMENTS = /* @__PURE__ */ new Set([
|
|
987
|
+
"path",
|
|
988
|
+
"circle",
|
|
989
|
+
"ellipse",
|
|
990
|
+
"line",
|
|
991
|
+
"polyline",
|
|
992
|
+
"polygon",
|
|
993
|
+
"rect"
|
|
994
|
+
]);
|
|
995
|
+
var GEOMETRY_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
996
|
+
"d",
|
|
997
|
+
"cx",
|
|
998
|
+
"cy",
|
|
999
|
+
"r",
|
|
1000
|
+
"rx",
|
|
1001
|
+
"ry",
|
|
1002
|
+
"x",
|
|
1003
|
+
"y",
|
|
1004
|
+
"x1",
|
|
1005
|
+
"y1",
|
|
1006
|
+
"x2",
|
|
1007
|
+
"y2",
|
|
1008
|
+
"width",
|
|
1009
|
+
"height",
|
|
1010
|
+
"points"
|
|
1011
|
+
]);
|
|
1012
|
+
function positiveOr(value, fallback) {
|
|
1013
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
1014
|
+
}
|
|
1015
|
+
function iconNodeOf(icon) {
|
|
1016
|
+
if (typeof icon !== "string") return icon;
|
|
1017
|
+
return Object.prototype.hasOwnProperty.call(ICONS, icon) ? ICONS[icon] : [];
|
|
1018
|
+
}
|
|
1019
|
+
function sanitize(icon, options = {}) {
|
|
1020
|
+
const size = String(positiveOr(options.size, DEFAULT_SIZE));
|
|
1021
|
+
const root = [];
|
|
1022
|
+
if (options.className) root.push(["class", options.className]);
|
|
1023
|
+
root.push(
|
|
1024
|
+
["width", size],
|
|
1025
|
+
["height", size],
|
|
1026
|
+
["viewBox", "0 0 24 24"],
|
|
1027
|
+
["fill", "none"],
|
|
1028
|
+
["stroke", "currentColor"],
|
|
1029
|
+
["stroke-width", String(positiveOr(options.strokeWidth, DEFAULT_STROKE_WIDTH))],
|
|
1030
|
+
["stroke-linecap", "round"],
|
|
1031
|
+
["stroke-linejoin", "round"],
|
|
1032
|
+
["aria-hidden", "true"],
|
|
1033
|
+
["focusable", "false"]
|
|
1034
|
+
);
|
|
1035
|
+
const children = [];
|
|
1036
|
+
for (const node of iconNodeOf(icon)) {
|
|
1037
|
+
const [tag, attributes] = node;
|
|
1038
|
+
if (!ICON_ELEMENTS.has(tag)) continue;
|
|
1039
|
+
const kept = Object.entries(attributes).filter(([name]) => GEOMETRY_ATTRIBUTES.has(name)).map(([name, value]) => [name, String(value)]);
|
|
1040
|
+
children.push([tag, kept]);
|
|
1041
|
+
}
|
|
1042
|
+
return { root, children };
|
|
1043
|
+
}
|
|
1044
|
+
function iconElement(icon, options) {
|
|
1045
|
+
const { root, children } = sanitize(icon, options);
|
|
1046
|
+
const svg = document.createElementNS(SVG_NAMESPACE, "svg");
|
|
1047
|
+
for (const [name, value] of root) svg.setAttribute(name, value);
|
|
1048
|
+
for (const [tag, attributes] of children) {
|
|
1049
|
+
const child = document.createElementNS(SVG_NAMESPACE, tag);
|
|
1050
|
+
for (const [name, value] of attributes) child.setAttribute(name, value);
|
|
1051
|
+
svg.append(child);
|
|
1052
|
+
}
|
|
1053
|
+
return svg;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// src/dom/feedback/problems-popover.ts
|
|
1057
|
+
var PILL_ATTRIBUTES = ["aria-haspopup", "aria-expanded", "aria-controls"];
|
|
1058
|
+
function isPrintable(event) {
|
|
1059
|
+
return event.key.length === 1 && /\S/.test(event.key) && !event.ctrlKey && !event.metaKey && !event.altKey;
|
|
1060
|
+
}
|
|
1061
|
+
function createProblemsPopover(context) {
|
|
1062
|
+
const { ownerDocument, root, layer, pill, id, titleId, messages } = context;
|
|
1063
|
+
const view = ownerDocument.defaultView;
|
|
1064
|
+
const list = createElement(ownerDocument, "ul", { class: "cd-shell-problems-list", role: "list" });
|
|
1065
|
+
const element = createElement(
|
|
1066
|
+
ownerDocument,
|
|
1067
|
+
"div",
|
|
1068
|
+
{
|
|
1069
|
+
class: "cd-shell-problems-popover",
|
|
1070
|
+
"data-chrome": "problems-popover",
|
|
1071
|
+
role: "dialog",
|
|
1072
|
+
id,
|
|
1073
|
+
"aria-labelledby": titleId,
|
|
1074
|
+
tabindex: "-1"
|
|
1075
|
+
},
|
|
1076
|
+
createElement(
|
|
1077
|
+
ownerDocument,
|
|
1078
|
+
"h2",
|
|
1079
|
+
{ class: "cd-shell-problems-popover-title", id: titleId },
|
|
1080
|
+
messages.problemsTitle
|
|
1081
|
+
),
|
|
1082
|
+
list
|
|
1083
|
+
);
|
|
1084
|
+
element.hidden = true;
|
|
1085
|
+
layer.append(element);
|
|
1086
|
+
pill.setAttribute("aria-haspopup", "dialog");
|
|
1087
|
+
pill.setAttribute("aria-expanded", "false");
|
|
1088
|
+
pill.setAttribute("aria-controls", id);
|
|
1089
|
+
let items = [];
|
|
1090
|
+
let rows = [];
|
|
1091
|
+
let destroyed = false;
|
|
1092
|
+
const isOpen = () => !element.hidden;
|
|
1093
|
+
function keyOf(item) {
|
|
1094
|
+
return `${item.elementId ?? ""}\0${item.severity}\0${item.message}`;
|
|
1095
|
+
}
|
|
1096
|
+
function rowElement(item) {
|
|
1097
|
+
const severity = item.severity === "error" ? "error" : "warning";
|
|
1098
|
+
const parts = [
|
|
1099
|
+
// The shape and colour of the icon carry the severity on screen, its name for screen readers.
|
|
1100
|
+
createElement(
|
|
1101
|
+
ownerDocument,
|
|
1102
|
+
"span",
|
|
1103
|
+
{ class: "cd-shell-problem-icon", role: "img", "aria-label": messages.problemSeverity(severity) },
|
|
1104
|
+
iconElement(severity === "error" ? "circle-x" : "triangle-alert")
|
|
1105
|
+
),
|
|
1106
|
+
createElement(ownerDocument, "span", { class: "cd-shell-problem-message" }, item.message),
|
|
1107
|
+
item.location ? createElement(ownerDocument, "span", { class: "cd-shell-problem-location" }, item.location) : void 0
|
|
1108
|
+
];
|
|
1109
|
+
const attributes = { class: "cd-shell-problem", "data-severity": severity };
|
|
1110
|
+
if (!item.elementId) {
|
|
1111
|
+
const text = createElement(ownerDocument, "div", { ...attributes, tabindex: "-1" }, ...parts);
|
|
1112
|
+
rows.push({ key: keyOf(item), element: text });
|
|
1113
|
+
return text;
|
|
1114
|
+
}
|
|
1115
|
+
const button = createElement(ownerDocument, "button", { type: "button", ...attributes }, ...parts);
|
|
1116
|
+
button.addEventListener("click", () => select(item));
|
|
1117
|
+
rows.push({ key: keyOf(item), element: button });
|
|
1118
|
+
return button;
|
|
1119
|
+
}
|
|
1120
|
+
function focusedRowIndex() {
|
|
1121
|
+
return rows.findIndex((row) => row.element === ownerDocument.activeElement);
|
|
1122
|
+
}
|
|
1123
|
+
function render() {
|
|
1124
|
+
const focusedIndex = focusedRowIndex();
|
|
1125
|
+
const focusedKey = rows[focusedIndex]?.key;
|
|
1126
|
+
rows = [];
|
|
1127
|
+
list.replaceChildren(...items.map((item) => createElement(ownerDocument, "li", {}, rowElement(item))));
|
|
1128
|
+
if (focusedKey === void 0 || !isOpen()) return;
|
|
1129
|
+
const kept = rows.find((row) => row.key === focusedKey) ?? rows[Math.min(focusedIndex, rows.length - 1)];
|
|
1130
|
+
if (kept) focusRowElement(kept.element);
|
|
1131
|
+
else element.focus({ preventScroll: true });
|
|
1132
|
+
}
|
|
1133
|
+
function revealInList(row) {
|
|
1134
|
+
const rowBox = row.getBoundingClientRect();
|
|
1135
|
+
const listBox = list.getBoundingClientRect();
|
|
1136
|
+
if (rowBox.top < listBox.top) list.scrollTop -= listBox.top - rowBox.top;
|
|
1137
|
+
else if (rowBox.bottom > listBox.bottom) list.scrollTop += rowBox.bottom - listBox.bottom;
|
|
1138
|
+
}
|
|
1139
|
+
function focusRowElement(row) {
|
|
1140
|
+
row.focus({ preventScroll: true });
|
|
1141
|
+
revealInList(row);
|
|
1142
|
+
}
|
|
1143
|
+
function focusCanvas() {
|
|
1144
|
+
const canvas = root.querySelector(".cd-shell-canvas svg[tabindex]");
|
|
1145
|
+
canvas?.focus({ preventScroll: true });
|
|
1146
|
+
if ((!canvas || ownerDocument.activeElement !== canvas) && !pill.hidden)
|
|
1147
|
+
pill.focus({ preventScroll: true });
|
|
1148
|
+
}
|
|
1149
|
+
function select(item) {
|
|
1150
|
+
close();
|
|
1151
|
+
focusCanvas();
|
|
1152
|
+
context.onSelectItem?.(item);
|
|
1153
|
+
}
|
|
1154
|
+
function focusRow(index) {
|
|
1155
|
+
if (rows.length === 0) return;
|
|
1156
|
+
const row = rows[(index + rows.length) % rows.length];
|
|
1157
|
+
if (row) focusRowElement(row.element);
|
|
1158
|
+
}
|
|
1159
|
+
const onOutsidePointerDown = (event) => {
|
|
1160
|
+
const target = event.target;
|
|
1161
|
+
if (target && (element.contains(target) || pill.contains(target))) return;
|
|
1162
|
+
close();
|
|
1163
|
+
};
|
|
1164
|
+
const onWindowBlur = () => close();
|
|
1165
|
+
function open() {
|
|
1166
|
+
if (destroyed || isOpen()) return;
|
|
1167
|
+
element.hidden = false;
|
|
1168
|
+
pill.setAttribute("aria-expanded", "true");
|
|
1169
|
+
ownerDocument.addEventListener("pointerdown", onOutsidePointerDown, true);
|
|
1170
|
+
view?.addEventListener("blur", onWindowBlur);
|
|
1171
|
+
list.scrollTop = 0;
|
|
1172
|
+
const first = rows[0];
|
|
1173
|
+
if (first) focusRowElement(first.element);
|
|
1174
|
+
else element.focus({ preventScroll: true });
|
|
1175
|
+
}
|
|
1176
|
+
function close(options = {}) {
|
|
1177
|
+
if (!isOpen()) return;
|
|
1178
|
+
element.hidden = true;
|
|
1179
|
+
pill.setAttribute("aria-expanded", "false");
|
|
1180
|
+
ownerDocument.removeEventListener("pointerdown", onOutsidePointerDown, true);
|
|
1181
|
+
view?.removeEventListener("blur", onWindowBlur);
|
|
1182
|
+
if (options.restoreFocus && !pill.hidden) pill.focus({ preventScroll: true });
|
|
1183
|
+
}
|
|
1184
|
+
function consume(event) {
|
|
1185
|
+
event.preventDefault();
|
|
1186
|
+
event.stopPropagation();
|
|
1187
|
+
}
|
|
1188
|
+
element.addEventListener("keydown", (event) => {
|
|
1189
|
+
if (matchChromeKey(event)) return;
|
|
1190
|
+
const index = focusedRowIndex();
|
|
1191
|
+
switch (event.key) {
|
|
1192
|
+
case "Escape":
|
|
1193
|
+
consume(event);
|
|
1194
|
+
close({ restoreFocus: true });
|
|
1195
|
+
return;
|
|
1196
|
+
case "Tab": {
|
|
1197
|
+
const first = index <= 0;
|
|
1198
|
+
const last = rows.length === 0 || index === rows.length - 1;
|
|
1199
|
+
if (event.shiftKey && first) {
|
|
1200
|
+
consume(event);
|
|
1201
|
+
close({ restoreFocus: true });
|
|
1202
|
+
} else if (!event.shiftKey && last) {
|
|
1203
|
+
event.stopPropagation();
|
|
1204
|
+
close({ restoreFocus: true });
|
|
1205
|
+
}
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
case "ArrowDown":
|
|
1209
|
+
focusRow(index + 1);
|
|
1210
|
+
break;
|
|
1211
|
+
case "ArrowUp":
|
|
1212
|
+
focusRow(index < 0 ? rows.length - 1 : index - 1);
|
|
1213
|
+
break;
|
|
1214
|
+
case "Home":
|
|
1215
|
+
focusRow(0);
|
|
1216
|
+
break;
|
|
1217
|
+
case "End":
|
|
1218
|
+
focusRow(rows.length - 1);
|
|
1219
|
+
break;
|
|
1220
|
+
case "Enter":
|
|
1221
|
+
case " ":
|
|
1222
|
+
case "Delete":
|
|
1223
|
+
case "Backspace":
|
|
1224
|
+
event.stopPropagation();
|
|
1225
|
+
return;
|
|
1226
|
+
default:
|
|
1227
|
+
if (isPrintable(event)) event.stopPropagation();
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
consume(event);
|
|
1231
|
+
});
|
|
1232
|
+
return {
|
|
1233
|
+
element,
|
|
1234
|
+
isOpen,
|
|
1235
|
+
toggle() {
|
|
1236
|
+
if (isOpen()) close({ restoreFocus: true });
|
|
1237
|
+
else open();
|
|
1238
|
+
},
|
|
1239
|
+
open,
|
|
1240
|
+
close,
|
|
1241
|
+
setItems(next) {
|
|
1242
|
+
items = next;
|
|
1243
|
+
if (!destroyed) render();
|
|
1244
|
+
},
|
|
1245
|
+
destroy() {
|
|
1246
|
+
if (destroyed) return;
|
|
1247
|
+
close();
|
|
1248
|
+
destroyed = true;
|
|
1249
|
+
for (const name of PILL_ATTRIBUTES) pill.removeAttribute(name);
|
|
1250
|
+
element.remove();
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// src/dom/feedback/toasts.ts
|
|
1256
|
+
var TOAST_RESUME_MIN_MS = 1500;
|
|
1257
|
+
var TOAST_LEAVE_FALLBACK_MS = 400;
|
|
1258
|
+
var LEAVE_ANIMATION = "cd-shell-toast-out";
|
|
1259
|
+
var TOAST_ANNOUNCE_DELAY_MS = 100;
|
|
1260
|
+
var INERT_HANDLE = Object.freeze({ dismiss() {
|
|
1261
|
+
} });
|
|
1262
|
+
function createToaster(context) {
|
|
1263
|
+
const { ownerDocument, messages } = context;
|
|
1264
|
+
const web = context.host === "web";
|
|
1265
|
+
const element = createElement(ownerDocument, "div", {
|
|
1266
|
+
class: "cd-shell-toasts",
|
|
1267
|
+
"data-chrome": "toasts",
|
|
1268
|
+
popover: web ? "manual" : void 0
|
|
1269
|
+
});
|
|
1270
|
+
const liveRegion = (politeness) => createElement(ownerDocument, "div", {
|
|
1271
|
+
class: "cd-shell-live-region",
|
|
1272
|
+
"data-live": politeness,
|
|
1273
|
+
role: politeness === "polite" ? "status" : "alert",
|
|
1274
|
+
"aria-live": politeness,
|
|
1275
|
+
"aria-atomic": "true"
|
|
1276
|
+
});
|
|
1277
|
+
const politeRegion = liveRegion("polite");
|
|
1278
|
+
const assertiveRegion = liveRegion("assertive");
|
|
1279
|
+
const announcer = createElement(
|
|
1280
|
+
ownerDocument,
|
|
1281
|
+
"div",
|
|
1282
|
+
{ class: "cd-shell-live" },
|
|
1283
|
+
politeRegion,
|
|
1284
|
+
assertiveRegion
|
|
1285
|
+
);
|
|
1286
|
+
const live = [];
|
|
1287
|
+
const leaveTimers = /* @__PURE__ */ new Set();
|
|
1288
|
+
const announceTimers = /* @__PURE__ */ new Map();
|
|
1289
|
+
let lastId = 0;
|
|
1290
|
+
let destroyed = false;
|
|
1291
|
+
function regionFor(request) {
|
|
1292
|
+
return request.status === "danger" ? assertiveRegion : politeRegion;
|
|
1293
|
+
}
|
|
1294
|
+
function announce(request) {
|
|
1295
|
+
const region = regionFor(request);
|
|
1296
|
+
clearTimeout(announceTimers.get(region));
|
|
1297
|
+
region.textContent = "";
|
|
1298
|
+
announceTimers.set(
|
|
1299
|
+
region,
|
|
1300
|
+
setTimeout(() => {
|
|
1301
|
+
announceTimers.delete(region);
|
|
1302
|
+
if (!destroyed) region.textContent = request.text;
|
|
1303
|
+
}, TOAST_ANNOUNCE_DELAY_MS)
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
function silence(request) {
|
|
1307
|
+
const region = regionFor(request);
|
|
1308
|
+
if (region.textContent === request.text) region.textContent = "";
|
|
1309
|
+
}
|
|
1310
|
+
function changed() {
|
|
1311
|
+
if (!destroyed) context.onChange?.();
|
|
1312
|
+
}
|
|
1313
|
+
function supportsPopover() {
|
|
1314
|
+
return web && typeof element.showPopover === "function" && typeof element.hidePopover === "function";
|
|
1315
|
+
}
|
|
1316
|
+
function popoverOpen() {
|
|
1317
|
+
try {
|
|
1318
|
+
return element.matches(":popover-open");
|
|
1319
|
+
} catch {
|
|
1320
|
+
return false;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
function modalOpen() {
|
|
1324
|
+
try {
|
|
1325
|
+
return ownerDocument.querySelector(":modal") !== null;
|
|
1326
|
+
} catch {
|
|
1327
|
+
return true;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
function raise() {
|
|
1331
|
+
if (!supportsPopover() || !element.isConnected) return;
|
|
1332
|
+
if (popoverOpen() && !modalOpen()) return;
|
|
1333
|
+
const focused = ownerDocument.activeElement;
|
|
1334
|
+
const keepFocus = focused && element.contains(focused) ? focused : void 0;
|
|
1335
|
+
try {
|
|
1336
|
+
element.hidePopover();
|
|
1337
|
+
element.showPopover();
|
|
1338
|
+
} catch {
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
if (keepFocus && ownerDocument.activeElement !== keepFocus) keepFocus.focus({ preventScroll: true });
|
|
1342
|
+
}
|
|
1343
|
+
function lowerWhenEmpty() {
|
|
1344
|
+
if (element.childElementCount > 0 || !supportsPopover()) return;
|
|
1345
|
+
try {
|
|
1346
|
+
element.hidePopover();
|
|
1347
|
+
} catch {
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
function engaged(toast) {
|
|
1351
|
+
return toast.hovered || toast.focused;
|
|
1352
|
+
}
|
|
1353
|
+
function stopTimer(toast) {
|
|
1354
|
+
clearTimeout(toast.timer);
|
|
1355
|
+
toast.timer = void 0;
|
|
1356
|
+
}
|
|
1357
|
+
function runTimer(toast) {
|
|
1358
|
+
stopTimer(toast);
|
|
1359
|
+
if (toast.remaining === void 0 || engaged(toast)) return;
|
|
1360
|
+
toast.startedAt = Date.now();
|
|
1361
|
+
toast.timer = setTimeout(() => close(toast), toast.remaining);
|
|
1362
|
+
}
|
|
1363
|
+
function pause(toast) {
|
|
1364
|
+
if (toast.timer === void 0) return;
|
|
1365
|
+
stopTimer(toast);
|
|
1366
|
+
if (toast.remaining !== void 0) {
|
|
1367
|
+
toast.remaining = Math.max(0, toast.remaining - (Date.now() - toast.startedAt));
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
function setEngagement(toast, hovered, focused) {
|
|
1371
|
+
if (!live.includes(toast)) return;
|
|
1372
|
+
const wasEngaged = engaged(toast);
|
|
1373
|
+
toast.hovered = hovered;
|
|
1374
|
+
toast.focused = focused;
|
|
1375
|
+
if (engaged(toast) === wasEngaged) return;
|
|
1376
|
+
if (wasEngaged) {
|
|
1377
|
+
if (toast.remaining !== void 0) toast.remaining = Math.max(toast.remaining, TOAST_RESUME_MIN_MS);
|
|
1378
|
+
runTimer(toast);
|
|
1379
|
+
} else {
|
|
1380
|
+
pause(toast);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
function reducedMotion() {
|
|
1384
|
+
if (ownerDocument.documentElement.classList.contains(REDUCED_MOTION_CLASS)) return true;
|
|
1385
|
+
const view = ownerDocument.defaultView;
|
|
1386
|
+
return typeof view?.matchMedia === "function" && view.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1387
|
+
}
|
|
1388
|
+
function removeElement(toastElement) {
|
|
1389
|
+
toastElement.remove();
|
|
1390
|
+
lowerWhenEmpty();
|
|
1391
|
+
changed();
|
|
1392
|
+
}
|
|
1393
|
+
function leave(toastElement) {
|
|
1394
|
+
toastElement.setAttribute("data-leaving", "");
|
|
1395
|
+
toastElement.inert = true;
|
|
1396
|
+
if (reducedMotion()) {
|
|
1397
|
+
removeElement(toastElement);
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
let done = false;
|
|
1401
|
+
const finish = () => {
|
|
1402
|
+
if (done) return;
|
|
1403
|
+
done = true;
|
|
1404
|
+
clearTimeout(fallback);
|
|
1405
|
+
leaveTimers.delete(fallback);
|
|
1406
|
+
removeElement(toastElement);
|
|
1407
|
+
};
|
|
1408
|
+
toastElement.addEventListener("animationend", (event) => {
|
|
1409
|
+
if (event.target === toastElement && event.animationName === LEAVE_ANIMATION) finish();
|
|
1410
|
+
});
|
|
1411
|
+
const fallback = setTimeout(finish, TOAST_LEAVE_FALLBACK_MS);
|
|
1412
|
+
leaveTimers.add(fallback);
|
|
1413
|
+
}
|
|
1414
|
+
function close(toast) {
|
|
1415
|
+
const index = live.indexOf(toast);
|
|
1416
|
+
if (index < 0) return;
|
|
1417
|
+
live.splice(index, 1);
|
|
1418
|
+
stopTimer(toast);
|
|
1419
|
+
silence(toast.request);
|
|
1420
|
+
const heldFocus = toast.element.contains(ownerDocument.activeElement);
|
|
1421
|
+
if (heldFocus) {
|
|
1422
|
+
const next = live[index] ?? live[index - 1];
|
|
1423
|
+
if (next) next.closeButton.focus({ preventScroll: true });
|
|
1424
|
+
else context.focusFallback();
|
|
1425
|
+
}
|
|
1426
|
+
leave(toast.element);
|
|
1427
|
+
}
|
|
1428
|
+
function setAction(toast, action) {
|
|
1429
|
+
const previous = toast.actionButton;
|
|
1430
|
+
const hadFocus = previous !== void 0 && ownerDocument.activeElement === previous;
|
|
1431
|
+
previous?.remove();
|
|
1432
|
+
toast.actionButton = void 0;
|
|
1433
|
+
if (action) {
|
|
1434
|
+
const button = createElement(
|
|
1435
|
+
ownerDocument,
|
|
1436
|
+
"button",
|
|
1437
|
+
{ type: "button", class: "cd-shell-toast-action cd-shell-button", "data-variant": "ghost" },
|
|
1438
|
+
action.label
|
|
1439
|
+
);
|
|
1440
|
+
button.addEventListener("click", () => {
|
|
1441
|
+
close(toast);
|
|
1442
|
+
action.onSelect();
|
|
1443
|
+
});
|
|
1444
|
+
toast.closeButton.before(button);
|
|
1445
|
+
toast.actionButton = button;
|
|
1446
|
+
}
|
|
1447
|
+
if (hadFocus) (toast.actionButton ?? toast.closeButton).focus({ preventScroll: true });
|
|
1448
|
+
}
|
|
1449
|
+
function slotOf(toast) {
|
|
1450
|
+
return { id: toast.id, hasAction: toast.request.action !== void 0, engaged: engaged(toast) };
|
|
1451
|
+
}
|
|
1452
|
+
function render(request) {
|
|
1453
|
+
const id = ++lastId;
|
|
1454
|
+
const textId = `${context.idPrefix}-${id}`;
|
|
1455
|
+
const closeButton = createElement(
|
|
1456
|
+
ownerDocument,
|
|
1457
|
+
"button",
|
|
1458
|
+
{
|
|
1459
|
+
type: "button",
|
|
1460
|
+
class: "cd-shell-toast-close",
|
|
1461
|
+
"aria-label": messages.close,
|
|
1462
|
+
"aria-describedby": textId
|
|
1463
|
+
},
|
|
1464
|
+
iconElement("x")
|
|
1465
|
+
);
|
|
1466
|
+
const toastElement = createElement(
|
|
1467
|
+
ownerDocument,
|
|
1468
|
+
"div",
|
|
1469
|
+
{ class: "cd-shell-toast", "data-status": request.status },
|
|
1470
|
+
createElement(ownerDocument, "span", { class: "cd-shell-toast-dot", "aria-hidden": "true" }),
|
|
1471
|
+
createElement(ownerDocument, "span", { class: "cd-shell-toast-text", id: textId }, request.text),
|
|
1472
|
+
closeButton
|
|
1473
|
+
);
|
|
1474
|
+
const toast = {
|
|
1475
|
+
id,
|
|
1476
|
+
element: toastElement,
|
|
1477
|
+
closeButton,
|
|
1478
|
+
handle: Object.freeze({ dismiss: () => close(toast) }),
|
|
1479
|
+
request,
|
|
1480
|
+
actionButton: void 0,
|
|
1481
|
+
hovered: false,
|
|
1482
|
+
focused: false,
|
|
1483
|
+
remaining: toastLifetime(request),
|
|
1484
|
+
startedAt: Date.now(),
|
|
1485
|
+
timer: void 0
|
|
1486
|
+
};
|
|
1487
|
+
setAction(toast, request.action);
|
|
1488
|
+
closeButton.addEventListener("click", () => close(toast));
|
|
1489
|
+
toastElement.addEventListener("mouseenter", () => setEngagement(toast, true, toast.focused));
|
|
1490
|
+
toastElement.addEventListener("mouseleave", () => setEngagement(toast, false, toast.focused));
|
|
1491
|
+
toastElement.addEventListener("focusin", () => setEngagement(toast, toast.hovered, true));
|
|
1492
|
+
toastElement.addEventListener(
|
|
1493
|
+
"focusout",
|
|
1494
|
+
(event) => setEngagement(toast, toast.hovered, toastElement.contains(event.relatedTarget))
|
|
1495
|
+
);
|
|
1496
|
+
return toast;
|
|
1497
|
+
}
|
|
1498
|
+
function show(request) {
|
|
1499
|
+
if (destroyed) return INERT_HANDLE;
|
|
1500
|
+
const same = live.find(
|
|
1501
|
+
(toast2) => toast2.request.status === request.status && toast2.request.text === request.text
|
|
1502
|
+
);
|
|
1503
|
+
if (same) {
|
|
1504
|
+
same.request = request;
|
|
1505
|
+
setAction(same, request.action);
|
|
1506
|
+
same.remaining = toastLifetime(request);
|
|
1507
|
+
runTimer(same);
|
|
1508
|
+
raise();
|
|
1509
|
+
announce(request);
|
|
1510
|
+
changed();
|
|
1511
|
+
return same.handle;
|
|
1512
|
+
}
|
|
1513
|
+
while (live.length >= MAX_TOASTS) {
|
|
1514
|
+
const evictedId = toastToEvict(live.map(slotOf));
|
|
1515
|
+
const evicted = live.find((toast2) => toast2.id === evictedId) ?? live[0];
|
|
1516
|
+
if (!evicted) break;
|
|
1517
|
+
close(evicted);
|
|
1518
|
+
}
|
|
1519
|
+
const toast = render(request);
|
|
1520
|
+
live.push(toast);
|
|
1521
|
+
element.append(toast.element);
|
|
1522
|
+
raise();
|
|
1523
|
+
runTimer(toast);
|
|
1524
|
+
announce(request);
|
|
1525
|
+
changed();
|
|
1526
|
+
return toast.handle;
|
|
1527
|
+
}
|
|
1528
|
+
return {
|
|
1529
|
+
element,
|
|
1530
|
+
announcer,
|
|
1531
|
+
show,
|
|
1532
|
+
setBottom(bottom) {
|
|
1533
|
+
element.style.bottom = `${bottom}px`;
|
|
1534
|
+
},
|
|
1535
|
+
widestCard() {
|
|
1536
|
+
let widest;
|
|
1537
|
+
for (const card of element.children) {
|
|
1538
|
+
const width = card.getBoundingClientRect().width;
|
|
1539
|
+
if (width > 0) widest = Math.max(widest ?? 0, width);
|
|
1540
|
+
}
|
|
1541
|
+
return widest;
|
|
1542
|
+
},
|
|
1543
|
+
destroy() {
|
|
1544
|
+
if (destroyed) return;
|
|
1545
|
+
destroyed = true;
|
|
1546
|
+
for (const toast of live.splice(0)) stopTimer(toast);
|
|
1547
|
+
for (const timer of leaveTimers) clearTimeout(timer);
|
|
1548
|
+
leaveTimers.clear();
|
|
1549
|
+
for (const timer of announceTimers.values()) clearTimeout(timer);
|
|
1550
|
+
announceTimers.clear();
|
|
1551
|
+
element.remove();
|
|
1552
|
+
announcer.remove();
|
|
1553
|
+
}
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
// src/dom/hint.ts
|
|
1558
|
+
function createEmptyHint(ownerDocument, messages, options) {
|
|
1559
|
+
const { onInsertExample } = options;
|
|
1560
|
+
const action = onInsertExample ? createElement(
|
|
1561
|
+
ownerDocument,
|
|
1562
|
+
"button",
|
|
1563
|
+
{ type: "button", class: "cd-shell-hint-action" },
|
|
1564
|
+
messages.insertExample
|
|
1565
|
+
) : void 0;
|
|
1566
|
+
action?.addEventListener("click", () => onInsertExample?.());
|
|
1567
|
+
const element = createElement(
|
|
1568
|
+
ownerDocument,
|
|
1569
|
+
"div",
|
|
1570
|
+
{ class: "cd-shell-hint" },
|
|
1571
|
+
createElement(ownerDocument, "p", { class: "cd-shell-hint-text" }, messages.emptyHint),
|
|
1572
|
+
action
|
|
1573
|
+
);
|
|
1574
|
+
element.hidden = true;
|
|
1575
|
+
return {
|
|
1576
|
+
element,
|
|
1577
|
+
isShown: () => !element.hidden,
|
|
1578
|
+
setShown(shown) {
|
|
1579
|
+
element.hidden = !shown;
|
|
1580
|
+
},
|
|
1581
|
+
place({ top, right, bottom, left }) {
|
|
1582
|
+
element.style.inset = `${top}px ${right}px ${bottom}px ${left}px`;
|
|
1583
|
+
},
|
|
1584
|
+
destroy() {
|
|
1585
|
+
element.remove();
|
|
1586
|
+
}
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
// src/dom/inspector.ts
|
|
1591
|
+
function createInspector(ownerDocument, options, onChange) {
|
|
1592
|
+
const element = createElement(ownerDocument, "aside", {
|
|
1593
|
+
class: "cd-shell-inspector",
|
|
1594
|
+
"data-chrome": "inspector",
|
|
1595
|
+
"aria-label": options.label
|
|
1596
|
+
});
|
|
1597
|
+
element.hidden = true;
|
|
1598
|
+
function setOpen(open) {
|
|
1599
|
+
if (open === !element.hidden) return;
|
|
1600
|
+
element.hidden = !open;
|
|
1601
|
+
onChange(open);
|
|
1602
|
+
}
|
|
1603
|
+
return {
|
|
1604
|
+
element,
|
|
1605
|
+
isOpen: () => !element.hidden,
|
|
1606
|
+
open: () => setOpen(true),
|
|
1607
|
+
close: () => setOpen(false),
|
|
1608
|
+
destroy() {
|
|
1609
|
+
element.remove();
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
// src/dom/keys.ts
|
|
1615
|
+
function isTextField(target) {
|
|
1616
|
+
if (!target || typeof target.closest !== "function") return false;
|
|
1617
|
+
const element = target;
|
|
1618
|
+
return element.isContentEditable === true || element.closest(TEXT_FIELD_SELECTOR) !== null;
|
|
1619
|
+
}
|
|
1620
|
+
function isShown(element) {
|
|
1621
|
+
if (element.localName === "dialog" && !element.hasAttribute("open")) return false;
|
|
1622
|
+
if (element.closest("[hidden]")) return false;
|
|
1623
|
+
return typeof element.checkVisibility !== "function" || element.checkVisibility({ visibilityProperty: true });
|
|
1624
|
+
}
|
|
1625
|
+
function isModal(element) {
|
|
1626
|
+
if (element.getAttribute("aria-modal") === "true") return true;
|
|
1627
|
+
try {
|
|
1628
|
+
return element.matches(":modal");
|
|
1629
|
+
} catch {
|
|
1630
|
+
return false;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
function openDialogs(scope) {
|
|
1634
|
+
return [...scope.querySelectorAll('[role="dialog"], [role="alertdialog"], dialog')].filter(isShown);
|
|
1635
|
+
}
|
|
1636
|
+
function isModalOpen(scope) {
|
|
1637
|
+
return openDialogs(scope).some(isModal);
|
|
1638
|
+
}
|
|
1639
|
+
function createKeyListener(context) {
|
|
1640
|
+
return (event) => {
|
|
1641
|
+
if (event.key === "Escape") context.hideTooltip();
|
|
1642
|
+
const dialogs = openDialogs(context.dialogScope);
|
|
1643
|
+
const decision = decideKey(event, {
|
|
1644
|
+
host: context.host,
|
|
1645
|
+
inTextField: isTextField(event.target),
|
|
1646
|
+
menuOpen: context.menuOpen(),
|
|
1647
|
+
dialogOpen: dialogs.length > 0,
|
|
1648
|
+
modalOpen: dialogs.some(isModal),
|
|
1649
|
+
defaultPrevented: event.defaultPrevented,
|
|
1650
|
+
startScreen: context.startScreen?.() ?? false
|
|
1651
|
+
});
|
|
1652
|
+
if (decision.preventDefault) event.preventDefault();
|
|
1653
|
+
if (decision.stopPropagation) event.stopPropagation();
|
|
1654
|
+
if (decision.action === "closeMenu") context.closeMenu();
|
|
1655
|
+
else if (decision.action) context.run(decision.action);
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
// src/dom/ledger.ts
|
|
1660
|
+
var Ledger = class {
|
|
1661
|
+
undo = [];
|
|
1662
|
+
originalAttributes = /* @__PURE__ */ new Map();
|
|
1663
|
+
closed = false;
|
|
1664
|
+
get isClosed() {
|
|
1665
|
+
return this.closed;
|
|
1666
|
+
}
|
|
1667
|
+
/** Runs `task` on `close()`, in reverse order of registration. */
|
|
1668
|
+
defer(task) {
|
|
1669
|
+
this.undo.push(task);
|
|
1670
|
+
}
|
|
1671
|
+
/** Adds `className` unless present; `close()` removes it only if this ledger added it. */
|
|
1672
|
+
addClass(element, className) {
|
|
1673
|
+
if (element.classList.contains(className)) return;
|
|
1674
|
+
const hadClassAttribute = element.hasAttribute("class");
|
|
1675
|
+
element.classList.add(className);
|
|
1676
|
+
this.defer(() => {
|
|
1677
|
+
element.classList.remove(className);
|
|
1678
|
+
if (!hadClassAttribute && element.classList.length === 0) element.removeAttribute("class");
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
/** Sets (string) or removes (`null`) an attribute; `close()` restores the value before the first change. */
|
|
1682
|
+
attribute(element, name, value) {
|
|
1683
|
+
let originals = this.originalAttributes.get(element);
|
|
1684
|
+
if (!originals) {
|
|
1685
|
+
originals = /* @__PURE__ */ new Map();
|
|
1686
|
+
this.originalAttributes.set(element, originals);
|
|
1687
|
+
}
|
|
1688
|
+
if (!originals.has(name)) {
|
|
1689
|
+
originals.set(name, element.getAttribute(name));
|
|
1690
|
+
this.defer(() => {
|
|
1691
|
+
const original = originals.get(name) ?? null;
|
|
1692
|
+
if (original === null) element.removeAttribute(name);
|
|
1693
|
+
else element.setAttribute(name, original);
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
if (value === null) element.removeAttribute(name);
|
|
1697
|
+
else element.setAttribute(name, value);
|
|
1698
|
+
}
|
|
1699
|
+
listen(target, type, listener, options) {
|
|
1700
|
+
const handler = listener;
|
|
1701
|
+
target.addEventListener(type, handler, options);
|
|
1702
|
+
this.defer(() => target.removeEventListener(type, handler, options));
|
|
1703
|
+
}
|
|
1704
|
+
/** Runs every undo task once; later calls are no-ops. */
|
|
1705
|
+
close() {
|
|
1706
|
+
if (this.closed) return;
|
|
1707
|
+
this.closed = true;
|
|
1708
|
+
for (const task of this.undo.reverse()) {
|
|
1709
|
+
try {
|
|
1710
|
+
task();
|
|
1711
|
+
} catch {
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
this.undo.length = 0;
|
|
1715
|
+
this.originalAttributes.clear();
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
|
|
1719
|
+
// src/dom/legend.ts
|
|
1720
|
+
function createLegend(context) {
|
|
1721
|
+
const { ownerDocument, messages, tooltip, titleId, options } = context;
|
|
1722
|
+
const close = createElement(ownerDocument, "button", {
|
|
1723
|
+
type: "button",
|
|
1724
|
+
class: "cd-shell-legend-close",
|
|
1725
|
+
"aria-label": messages.closeLegend
|
|
1726
|
+
});
|
|
1727
|
+
close.append(iconElement("x"));
|
|
1728
|
+
const body = createElement(ownerDocument, "div", { class: "cd-shell-legend-body" });
|
|
1729
|
+
const element = createElement(
|
|
1730
|
+
ownerDocument,
|
|
1731
|
+
"section",
|
|
1732
|
+
{ class: "cd-shell-legend cd-shell-island", "data-chrome": "legend", "aria-labelledby": titleId },
|
|
1733
|
+
createElement(
|
|
1734
|
+
ownerDocument,
|
|
1735
|
+
"div",
|
|
1736
|
+
{ class: "cd-shell-legend-header" },
|
|
1737
|
+
createElement(ownerDocument, "div", { class: "cd-shell-legend-title", id: titleId }, messages.legend),
|
|
1738
|
+
close
|
|
1739
|
+
),
|
|
1740
|
+
body
|
|
1741
|
+
);
|
|
1742
|
+
element.hidden = true;
|
|
1743
|
+
options.render(body);
|
|
1744
|
+
let open = false;
|
|
1745
|
+
let started = false;
|
|
1746
|
+
const detachTooltip = tooltip.attach(close, { label: messages.closeLegend });
|
|
1747
|
+
function apply(next) {
|
|
1748
|
+
started = true;
|
|
1749
|
+
if (next === open) return false;
|
|
1750
|
+
open = next;
|
|
1751
|
+
element.hidden = !open;
|
|
1752
|
+
if (!open && element.contains(ownerDocument.activeElement)) context.focusAfterClose();
|
|
1753
|
+
context.onChange(open);
|
|
1754
|
+
return true;
|
|
1755
|
+
}
|
|
1756
|
+
function setOpen(next) {
|
|
1757
|
+
if (apply(next)) options.onToggle?.(next);
|
|
1758
|
+
}
|
|
1759
|
+
close.addEventListener("click", () => setOpen(false));
|
|
1760
|
+
return {
|
|
1761
|
+
element,
|
|
1762
|
+
body,
|
|
1763
|
+
isOpen: () => open,
|
|
1764
|
+
setOpen,
|
|
1765
|
+
toggle: () => setOpen(!open),
|
|
1766
|
+
start(initial) {
|
|
1767
|
+
if (!started) apply(initial);
|
|
1768
|
+
},
|
|
1769
|
+
isStarted: () => started,
|
|
1770
|
+
destroy() {
|
|
1771
|
+
detachTooltip();
|
|
1772
|
+
element.remove();
|
|
1773
|
+
}
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
// src/dom/menu.ts
|
|
1778
|
+
var GROUP_ORDER = ["file", "model", "export", "appearance", "help"];
|
|
1779
|
+
function isModelerGroup(name) {
|
|
1780
|
+
return name === "model" || name === "export" || name === "help";
|
|
1781
|
+
}
|
|
1782
|
+
function isPrintable2(event) {
|
|
1783
|
+
return event.key.length === 1 && /\S/.test(event.key) && !event.ctrlKey && !event.metaKey && !event.altKey;
|
|
1784
|
+
}
|
|
1785
|
+
function createMenu(context) {
|
|
1786
|
+
const { root, layer, id, messages, tooltip } = context;
|
|
1787
|
+
const ownerDocument = root.ownerDocument;
|
|
1788
|
+
let groups = context.groups;
|
|
1789
|
+
let items = [];
|
|
1790
|
+
let destroyed = false;
|
|
1791
|
+
const trigger = createElement(ownerDocument, "button", {
|
|
1792
|
+
type: "button",
|
|
1793
|
+
class: "cd-shell-menu-trigger cd-shell-island",
|
|
1794
|
+
"data-chrome": "menu",
|
|
1795
|
+
"aria-label": messages.menu,
|
|
1796
|
+
"aria-haspopup": "menu",
|
|
1797
|
+
"aria-expanded": "false",
|
|
1798
|
+
"aria-controls": id
|
|
1799
|
+
});
|
|
1800
|
+
trigger.append(iconElement("menu"));
|
|
1801
|
+
const dropdown = createElement(ownerDocument, "div", {
|
|
1802
|
+
class: "cd-shell-menu",
|
|
1803
|
+
id,
|
|
1804
|
+
role: "menu",
|
|
1805
|
+
"aria-label": messages.menu
|
|
1806
|
+
});
|
|
1807
|
+
dropdown.hidden = true;
|
|
1808
|
+
layer.prepend(dropdown);
|
|
1809
|
+
const isOpen = () => !dropdown.hidden;
|
|
1810
|
+
function entriesOf(name) {
|
|
1811
|
+
const own = isModelerGroup(name) ? groups[name] ?? [] : [];
|
|
1812
|
+
const added = context.shellEntries(name);
|
|
1813
|
+
return added.length > 0 ? [...own, ...added] : own;
|
|
1814
|
+
}
|
|
1815
|
+
function hasEntries() {
|
|
1816
|
+
return GROUP_ORDER.some((name) => entriesOf(name).length > 0);
|
|
1817
|
+
}
|
|
1818
|
+
function updateTriggerVisibility() {
|
|
1819
|
+
trigger.hidden = !hasEntries();
|
|
1820
|
+
if (trigger.hidden) close();
|
|
1821
|
+
}
|
|
1822
|
+
function isDisabled(entry, group) {
|
|
1823
|
+
if (group === "model" && context.isReadOnly() && entry.keepEnabledWhenReadOnly !== true) return true;
|
|
1824
|
+
return entry.disabled?.() ?? false;
|
|
1825
|
+
}
|
|
1826
|
+
function itemElement(role, label, options) {
|
|
1827
|
+
const iconSlot = createElement(ownerDocument, "span", { class: "cd-shell-menu-item-icon" });
|
|
1828
|
+
if (options.icon) iconSlot.append(iconElement(options.icon));
|
|
1829
|
+
const element = createElement(
|
|
1830
|
+
ownerDocument,
|
|
1831
|
+
"button",
|
|
1832
|
+
{ type: "button", class: "cd-shell-menu-item", role, tabindex: "-1" },
|
|
1833
|
+
iconSlot,
|
|
1834
|
+
createElement(ownerDocument, "span", { class: "cd-shell-menu-item-label" }, label),
|
|
1835
|
+
options.end && createElement(ownerDocument, "span", { class: "cd-shell-menu-item-end" }, options.end)
|
|
1836
|
+
);
|
|
1837
|
+
toggleAttribute(element, "aria-disabled", options.disabled && "true");
|
|
1838
|
+
if (options.checked !== void 0) element.setAttribute("aria-checked", String(options.checked));
|
|
1839
|
+
return element;
|
|
1840
|
+
}
|
|
1841
|
+
function checkMark() {
|
|
1842
|
+
return iconElement("check", { className: "cd-shell-menu-check" });
|
|
1843
|
+
}
|
|
1844
|
+
function renderEntry(entry, group) {
|
|
1845
|
+
const disabled = isDisabled(entry, group);
|
|
1846
|
+
if (entry.kind === "checkbox") {
|
|
1847
|
+
const checked = entry.checked();
|
|
1848
|
+
const element2 = itemElement("menuitemcheckbox", entry.label, {
|
|
1849
|
+
icon: entry.icon,
|
|
1850
|
+
end: checkMark(),
|
|
1851
|
+
disabled,
|
|
1852
|
+
checked
|
|
1853
|
+
});
|
|
1854
|
+
element2.dataset.menuId = entry.id;
|
|
1855
|
+
element2.dataset.menuGroup = group;
|
|
1856
|
+
return {
|
|
1857
|
+
key: entry.id,
|
|
1858
|
+
element: element2,
|
|
1859
|
+
label: entry.label,
|
|
1860
|
+
disabled,
|
|
1861
|
+
activate() {
|
|
1862
|
+
entry.onToggle(!entry.checked());
|
|
1863
|
+
refresh();
|
|
1864
|
+
}
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
const shortcut = entry.shortcut ? createElement(ownerDocument, "span", { class: "cd-shell-menu-shortcut" }, entry.shortcut) : void 0;
|
|
1868
|
+
const element = itemElement("menuitem", entry.label, { icon: entry.icon, end: shortcut, disabled });
|
|
1869
|
+
element.dataset.menuId = entry.id;
|
|
1870
|
+
element.dataset.menuGroup = group;
|
|
1871
|
+
const keyShortcuts = entry.ariaKeyShortcuts ?? (entry.shortcut ? ariaKeyShortcutsFromLabel(entry.shortcut) : void 0);
|
|
1872
|
+
toggleAttribute(element, "aria-keyshortcuts", keyShortcuts || null);
|
|
1873
|
+
if (entry.kind === "dialog") element.setAttribute("aria-haspopup", "dialog");
|
|
1874
|
+
if (entry.kind === "action" && entry.danger) element.dataset.danger = "";
|
|
1875
|
+
return {
|
|
1876
|
+
key: entry.id,
|
|
1877
|
+
element,
|
|
1878
|
+
label: entry.label,
|
|
1879
|
+
disabled,
|
|
1880
|
+
activate() {
|
|
1881
|
+
close({ restoreFocus: true });
|
|
1882
|
+
entry.onSelect();
|
|
1883
|
+
}
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
function renderRadioGroup(entry, group) {
|
|
1887
|
+
const disabled = isDisabled(entry, group);
|
|
1888
|
+
const value = entry.value();
|
|
1889
|
+
const container = createElement(
|
|
1890
|
+
ownerDocument,
|
|
1891
|
+
"div",
|
|
1892
|
+
{
|
|
1893
|
+
class: "cd-shell-menu-group",
|
|
1894
|
+
role: "group",
|
|
1895
|
+
"aria-label": entry.label,
|
|
1896
|
+
"data-menu-id": entry.id,
|
|
1897
|
+
"data-menu-group": group
|
|
1898
|
+
},
|
|
1899
|
+
// The group's aria-label carries the heading; a menu holds no loose text.
|
|
1900
|
+
createElement(
|
|
1901
|
+
ownerDocument,
|
|
1902
|
+
"div",
|
|
1903
|
+
{ class: "cd-shell-menu-heading", "aria-hidden": "true" },
|
|
1904
|
+
entry.label
|
|
1905
|
+
)
|
|
1906
|
+
);
|
|
1907
|
+
const radioItems = entry.options.map((option) => {
|
|
1908
|
+
const element = itemElement("menuitemradio", option.label, {
|
|
1909
|
+
end: checkMark(),
|
|
1910
|
+
disabled,
|
|
1911
|
+
checked: option.value === value
|
|
1912
|
+
});
|
|
1913
|
+
element.dataset.menuValue = option.value;
|
|
1914
|
+
container.append(element);
|
|
1915
|
+
return {
|
|
1916
|
+
key: `${entry.id}\0${option.value}`,
|
|
1917
|
+
element,
|
|
1918
|
+
label: option.label,
|
|
1919
|
+
disabled,
|
|
1920
|
+
activate() {
|
|
1921
|
+
if (entry.value() !== option.value) entry.onSelect(option.value);
|
|
1922
|
+
refresh();
|
|
1923
|
+
}
|
|
1924
|
+
};
|
|
1925
|
+
});
|
|
1926
|
+
return { element: container, items: radioItems };
|
|
1927
|
+
}
|
|
1928
|
+
function render() {
|
|
1929
|
+
const focusedKey = items.find((item) => item.element === ownerDocument.activeElement)?.key;
|
|
1930
|
+
const nodes = [];
|
|
1931
|
+
const rendered = [];
|
|
1932
|
+
for (const name of GROUP_ORDER) {
|
|
1933
|
+
const entries = entriesOf(name);
|
|
1934
|
+
if (entries.length === 0) continue;
|
|
1935
|
+
if (nodes.length > 0) {
|
|
1936
|
+
nodes.push(
|
|
1937
|
+
createElement(ownerDocument, "div", { class: "cd-shell-menu-separator", role: "separator" })
|
|
1938
|
+
);
|
|
1939
|
+
}
|
|
1940
|
+
for (const entry of entries) {
|
|
1941
|
+
if (entry.kind === "radio") {
|
|
1942
|
+
const radio = renderRadioGroup(entry, name);
|
|
1943
|
+
nodes.push(radio.element);
|
|
1944
|
+
rendered.push(...radio.items);
|
|
1945
|
+
} else {
|
|
1946
|
+
const item = renderEntry(entry, name);
|
|
1947
|
+
nodes.push(item.element);
|
|
1948
|
+
rendered.push(item);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
for (const item of rendered) {
|
|
1953
|
+
item.element.addEventListener("click", () => {
|
|
1954
|
+
if (!item.disabled) item.activate();
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
dropdown.replaceChildren(...nodes);
|
|
1958
|
+
items = rendered;
|
|
1959
|
+
if (focusedKey !== void 0 && isOpen()) {
|
|
1960
|
+
(items.find((item) => item.key === focusedKey) ?? items[0])?.element.focus();
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
function focusAt(index) {
|
|
1964
|
+
if (items.length === 0) return;
|
|
1965
|
+
items[(index + items.length) % items.length]?.element.focus();
|
|
1966
|
+
}
|
|
1967
|
+
function currentIndex() {
|
|
1968
|
+
return items.findIndex((item) => item.element === ownerDocument.activeElement);
|
|
1969
|
+
}
|
|
1970
|
+
function typeAhead(character) {
|
|
1971
|
+
const start = currentIndex();
|
|
1972
|
+
const lower = character.toLowerCase();
|
|
1973
|
+
for (let step = 1; step <= items.length; step++) {
|
|
1974
|
+
const index = (Math.max(start, -1) + step) % items.length;
|
|
1975
|
+
if (items[index]?.label.toLowerCase().startsWith(lower)) {
|
|
1976
|
+
focusAt(index);
|
|
1977
|
+
return;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
const onOutsidePointerDown = (event) => {
|
|
1982
|
+
const target = event.target;
|
|
1983
|
+
if (target && (dropdown.contains(target) || trigger.contains(target))) return;
|
|
1984
|
+
close();
|
|
1985
|
+
};
|
|
1986
|
+
const onWindowBlur = () => close();
|
|
1987
|
+
function open(focus = "first") {
|
|
1988
|
+
if (destroyed || !hasEntries()) return;
|
|
1989
|
+
tooltip.hide();
|
|
1990
|
+
const wasOpen = isOpen();
|
|
1991
|
+
render();
|
|
1992
|
+
if (!wasOpen) {
|
|
1993
|
+
dropdown.hidden = false;
|
|
1994
|
+
trigger.setAttribute("aria-expanded", "true");
|
|
1995
|
+
ownerDocument.addEventListener("pointerdown", onOutsidePointerDown, true);
|
|
1996
|
+
ownerDocument.defaultView?.addEventListener("blur", onWindowBlur);
|
|
1997
|
+
}
|
|
1998
|
+
focusAt(focus === "last" ? items.length - 1 : 0);
|
|
1999
|
+
}
|
|
2000
|
+
function close(options = {}) {
|
|
2001
|
+
if (!isOpen()) return;
|
|
2002
|
+
dropdown.hidden = true;
|
|
2003
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
2004
|
+
ownerDocument.removeEventListener("pointerdown", onOutsidePointerDown, true);
|
|
2005
|
+
ownerDocument.defaultView?.removeEventListener("blur", onWindowBlur);
|
|
2006
|
+
if (options.restoreFocus && !trigger.hidden) trigger.focus();
|
|
2007
|
+
}
|
|
2008
|
+
function refresh() {
|
|
2009
|
+
if (isOpen()) render();
|
|
2010
|
+
}
|
|
2011
|
+
trigger.addEventListener("click", () => {
|
|
2012
|
+
if (isOpen()) close();
|
|
2013
|
+
else open("first");
|
|
2014
|
+
});
|
|
2015
|
+
function consume(event) {
|
|
2016
|
+
event.preventDefault();
|
|
2017
|
+
event.stopPropagation();
|
|
2018
|
+
}
|
|
2019
|
+
trigger.addEventListener("keydown", (event) => {
|
|
2020
|
+
if (event.key === "Enter" || event.key === " " || event.key === "ArrowDown") {
|
|
2021
|
+
consume(event);
|
|
2022
|
+
open("first");
|
|
2023
|
+
} else if (event.key === "ArrowUp") {
|
|
2024
|
+
consume(event);
|
|
2025
|
+
open("last");
|
|
2026
|
+
}
|
|
2027
|
+
});
|
|
2028
|
+
dropdown.addEventListener("keydown", (event) => {
|
|
2029
|
+
if (matchChromeKey(event)) return;
|
|
2030
|
+
switch (event.key) {
|
|
2031
|
+
case "ArrowDown":
|
|
2032
|
+
focusAt(currentIndex() + 1);
|
|
2033
|
+
break;
|
|
2034
|
+
case "ArrowUp": {
|
|
2035
|
+
const index = currentIndex();
|
|
2036
|
+
focusAt(index < 0 ? items.length - 1 : index - 1);
|
|
2037
|
+
break;
|
|
2038
|
+
}
|
|
2039
|
+
case "Home":
|
|
2040
|
+
focusAt(0);
|
|
2041
|
+
break;
|
|
2042
|
+
case "End":
|
|
2043
|
+
focusAt(items.length - 1);
|
|
2044
|
+
break;
|
|
2045
|
+
case "Tab":
|
|
2046
|
+
close({ restoreFocus: true });
|
|
2047
|
+
return;
|
|
2048
|
+
case "Escape":
|
|
2049
|
+
return;
|
|
2050
|
+
case "Enter":
|
|
2051
|
+
case " ":
|
|
2052
|
+
case "Delete":
|
|
2053
|
+
case "Backspace":
|
|
2054
|
+
event.stopPropagation();
|
|
2055
|
+
return;
|
|
2056
|
+
default:
|
|
2057
|
+
if (!isPrintable2(event)) return;
|
|
2058
|
+
typeAhead(event.key);
|
|
2059
|
+
}
|
|
2060
|
+
consume(event);
|
|
2061
|
+
});
|
|
2062
|
+
updateTriggerVisibility();
|
|
2063
|
+
return {
|
|
2064
|
+
trigger,
|
|
2065
|
+
dropdown,
|
|
2066
|
+
isOpen,
|
|
2067
|
+
open,
|
|
2068
|
+
close,
|
|
2069
|
+
refresh,
|
|
2070
|
+
setGroups(next) {
|
|
2071
|
+
groups = next;
|
|
2072
|
+
updateTriggerVisibility();
|
|
2073
|
+
refresh();
|
|
2074
|
+
},
|
|
2075
|
+
destroy() {
|
|
2076
|
+
if (destroyed) return;
|
|
2077
|
+
close();
|
|
2078
|
+
destroyed = true;
|
|
2079
|
+
trigger.remove();
|
|
2080
|
+
dropdown.remove();
|
|
2081
|
+
}
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
// src/dom/problems.ts
|
|
2086
|
+
function createProblemsPill(context) {
|
|
2087
|
+
const { ownerDocument, messages, lang, tooltip } = context;
|
|
2088
|
+
const icon = createElement(ownerDocument, "span", { class: "cd-shell-problems-icon" });
|
|
2089
|
+
const text = createElement(ownerDocument, "span", { class: "cd-shell-problems-text" });
|
|
2090
|
+
const element = createElement(
|
|
2091
|
+
ownerDocument,
|
|
2092
|
+
"button",
|
|
2093
|
+
{ type: "button", class: "cd-shell-problems cd-shell-island", "data-chrome": "problems" },
|
|
2094
|
+
icon,
|
|
2095
|
+
text
|
|
2096
|
+
);
|
|
2097
|
+
element.hidden = true;
|
|
2098
|
+
element.addEventListener("click", () => context.onActivate());
|
|
2099
|
+
const detachTooltip = context.showTooltip ? tooltip.attach(element, { label: messages.showProblems }) : void 0;
|
|
2100
|
+
let severity;
|
|
2101
|
+
return {
|
|
2102
|
+
element,
|
|
2103
|
+
isShown: () => !element.hidden,
|
|
2104
|
+
set({ count, errors }) {
|
|
2105
|
+
const shown = count > 0;
|
|
2106
|
+
element.hidden = !shown;
|
|
2107
|
+
if (!shown) {
|
|
2108
|
+
tooltip.hide();
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
const formattedCount = formatNumber(count, lang);
|
|
2112
|
+
text.textContent = messages.problems(count, formattedCount);
|
|
2113
|
+
element.setAttribute(
|
|
2114
|
+
"aria-label",
|
|
2115
|
+
messages.problemsLabel(count, errors, formattedCount, formatNumber(errors, lang))
|
|
2116
|
+
);
|
|
2117
|
+
const next = errors > 0 ? "danger" : "warning";
|
|
2118
|
+
if (next !== severity) {
|
|
2119
|
+
severity = next;
|
|
2120
|
+
element.dataset.severity = next;
|
|
2121
|
+
icon.replaceChildren(iconElement(next === "danger" ? "circle-x" : "triangle-alert"));
|
|
2122
|
+
}
|
|
2123
|
+
},
|
|
2124
|
+
destroy() {
|
|
2125
|
+
detachTooltip?.();
|
|
2126
|
+
element.remove();
|
|
2127
|
+
}
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
// src/dom/status.ts
|
|
2132
|
+
function createStatusBox(ownerDocument) {
|
|
2133
|
+
const icon = createElement(ownerDocument, "span", { class: "cd-shell-status-icon" });
|
|
2134
|
+
const text = createElement(ownerDocument, "p", { class: "cd-shell-status-text" });
|
|
2135
|
+
const element = createElement(
|
|
2136
|
+
ownerDocument,
|
|
2137
|
+
"div",
|
|
2138
|
+
{ class: "cd-shell-status", "data-chrome": "status" },
|
|
2139
|
+
icon,
|
|
2140
|
+
text
|
|
2141
|
+
);
|
|
2142
|
+
element.hidden = true;
|
|
2143
|
+
let pending;
|
|
2144
|
+
function cancelPending() {
|
|
2145
|
+
if (pending) clearTimeout(pending.timer);
|
|
2146
|
+
pending = void 0;
|
|
2147
|
+
}
|
|
2148
|
+
function show(status) {
|
|
2149
|
+
pending = void 0;
|
|
2150
|
+
if (element.dataset.severity !== status.severity) {
|
|
2151
|
+
element.dataset.severity = status.severity;
|
|
2152
|
+
element.setAttribute("role", status.severity === "danger" ? "alert" : "status");
|
|
2153
|
+
icon.replaceChildren(iconElement(status.severity === "danger" ? "circle-x" : "triangle-alert"));
|
|
2154
|
+
}
|
|
2155
|
+
if (text.textContent !== status.text) text.textContent = status.text;
|
|
2156
|
+
element.hidden = false;
|
|
2157
|
+
}
|
|
2158
|
+
return {
|
|
2159
|
+
element,
|
|
2160
|
+
set(status, delayMs) {
|
|
2161
|
+
if (status === null) {
|
|
2162
|
+
cancelPending();
|
|
2163
|
+
element.hidden = true;
|
|
2164
|
+
return;
|
|
2165
|
+
}
|
|
2166
|
+
if (!element.hidden) {
|
|
2167
|
+
show(status);
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
if (pending) {
|
|
2171
|
+
pending.status = status;
|
|
2172
|
+
return;
|
|
2173
|
+
}
|
|
2174
|
+
if (delayMs > 0) {
|
|
2175
|
+
const entry = { status, timer: setTimeout(() => show(entry.status), delayMs) };
|
|
2176
|
+
pending = entry;
|
|
2177
|
+
} else {
|
|
2178
|
+
show(status);
|
|
2179
|
+
}
|
|
2180
|
+
},
|
|
2181
|
+
destroy() {
|
|
2182
|
+
cancelPending();
|
|
2183
|
+
element.remove();
|
|
2184
|
+
}
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
// src/dom/tooltip.ts
|
|
2189
|
+
function createTooltip({ root, layer, id }) {
|
|
2190
|
+
const ownerDocument = root.ownerDocument;
|
|
2191
|
+
const label = createElement(ownerDocument, "span", { class: "cd-shell-tooltip-label" });
|
|
2192
|
+
const shortcut = createElement(ownerDocument, "kbd", { class: "cd-shell-kbd" });
|
|
2193
|
+
const element = createElement(ownerDocument, "div", { class: "cd-shell-tooltip", role: "tooltip", id });
|
|
2194
|
+
element.hidden = true;
|
|
2195
|
+
element.append(label, shortcut);
|
|
2196
|
+
layer.append(element);
|
|
2197
|
+
let shown;
|
|
2198
|
+
let pending;
|
|
2199
|
+
let warmUntil = 0;
|
|
2200
|
+
let pressed;
|
|
2201
|
+
const detachers = /* @__PURE__ */ new Set();
|
|
2202
|
+
function cancelPending() {
|
|
2203
|
+
if (pending) clearTimeout(pending.timer);
|
|
2204
|
+
pending = void 0;
|
|
2205
|
+
}
|
|
2206
|
+
function resolve(source) {
|
|
2207
|
+
return typeof source === "function" ? source() : source;
|
|
2208
|
+
}
|
|
2209
|
+
function place(control) {
|
|
2210
|
+
const island = control.closest("[data-chrome]") ?? control;
|
|
2211
|
+
const point = placeTooltip({
|
|
2212
|
+
anchor: boxRelativeTo(control, root),
|
|
2213
|
+
island: boxRelativeTo(island, root),
|
|
2214
|
+
tooltip: { width: element.offsetWidth, height: element.offsetHeight },
|
|
2215
|
+
container: { width: root.clientWidth, height: root.clientHeight }
|
|
2216
|
+
});
|
|
2217
|
+
element.style.left = `${point.x}px`;
|
|
2218
|
+
element.style.top = `${point.y}px`;
|
|
2219
|
+
}
|
|
2220
|
+
function hide() {
|
|
2221
|
+
cancelPending();
|
|
2222
|
+
if (!shown) return;
|
|
2223
|
+
const { control, describedBy } = shown;
|
|
2224
|
+
shown = void 0;
|
|
2225
|
+
element.hidden = true;
|
|
2226
|
+
if (describedBy === null) control.removeAttribute("aria-describedby");
|
|
2227
|
+
else control.setAttribute("aria-describedby", describedBy);
|
|
2228
|
+
}
|
|
2229
|
+
function show(control, source) {
|
|
2230
|
+
cancelPending();
|
|
2231
|
+
const content = resolve(source);
|
|
2232
|
+
if (!content || !control.isConnected) {
|
|
2233
|
+
if (shown?.control === control) hide();
|
|
2234
|
+
return;
|
|
2235
|
+
}
|
|
2236
|
+
if (shown && shown.control !== control) hide();
|
|
2237
|
+
label.textContent = content.label;
|
|
2238
|
+
shortcut.textContent = content.shortcut ?? "";
|
|
2239
|
+
shortcut.hidden = !content.shortcut;
|
|
2240
|
+
element.hidden = false;
|
|
2241
|
+
if (!shown) {
|
|
2242
|
+
const describedBy = control.getAttribute("aria-describedby");
|
|
2243
|
+
shown = { control, describedBy };
|
|
2244
|
+
const saysMore = !accessibleName(control).includes(content.label) || Boolean(content.shortcut) && !control.hasAttribute("aria-keyshortcuts");
|
|
2245
|
+
if (saysMore) {
|
|
2246
|
+
control.setAttribute("aria-describedby", [describedBy, id].filter(Boolean).join(" "));
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
place(control);
|
|
2250
|
+
}
|
|
2251
|
+
function onEnter(control, source, event) {
|
|
2252
|
+
if (event.pointerType === "touch" || pressed === control) return;
|
|
2253
|
+
if (shown || Date.now() < warmUntil) {
|
|
2254
|
+
show(control, source);
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
cancelPending();
|
|
2258
|
+
pending = { control, timer: setTimeout(() => show(control, source), TOOLTIP_DELAY_MS) };
|
|
2259
|
+
}
|
|
2260
|
+
function onLeave(control) {
|
|
2261
|
+
if (pressed === control) pressed = void 0;
|
|
2262
|
+
if (pending?.control === control) cancelPending();
|
|
2263
|
+
if (shown?.control === control) {
|
|
2264
|
+
hide();
|
|
2265
|
+
warmUntil = Date.now() + TOOLTIP_DELAY_MS;
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
function onFocus(control, source) {
|
|
2269
|
+
if (hasVisibleFocus(control)) show(control, source);
|
|
2270
|
+
}
|
|
2271
|
+
function onBlur(control) {
|
|
2272
|
+
if (pending?.control === control) cancelPending();
|
|
2273
|
+
if (shown?.control === control) hide();
|
|
2274
|
+
}
|
|
2275
|
+
function onPress(control, event) {
|
|
2276
|
+
if (event.type === "pointerdown") pressed = control;
|
|
2277
|
+
if (pending?.control === control) cancelPending();
|
|
2278
|
+
if (shown?.control === control) hide();
|
|
2279
|
+
}
|
|
2280
|
+
function attach(control, source) {
|
|
2281
|
+
const enter = (event) => onEnter(control, source, event);
|
|
2282
|
+
const leave = () => onLeave(control);
|
|
2283
|
+
const focus = () => onFocus(control, source);
|
|
2284
|
+
const blur = () => onBlur(control);
|
|
2285
|
+
const press = (event) => onPress(control, event);
|
|
2286
|
+
control.addEventListener("pointerenter", enter);
|
|
2287
|
+
control.addEventListener("pointerleave", leave);
|
|
2288
|
+
control.addEventListener("focus", focus);
|
|
2289
|
+
control.addEventListener("blur", blur);
|
|
2290
|
+
control.addEventListener("pointerdown", press);
|
|
2291
|
+
control.addEventListener("click", press);
|
|
2292
|
+
const detach = () => {
|
|
2293
|
+
if (!detachers.delete(detach)) return;
|
|
2294
|
+
control.removeEventListener("pointerenter", enter);
|
|
2295
|
+
control.removeEventListener("pointerleave", leave);
|
|
2296
|
+
control.removeEventListener("focus", focus);
|
|
2297
|
+
control.removeEventListener("blur", blur);
|
|
2298
|
+
control.removeEventListener("pointerdown", press);
|
|
2299
|
+
control.removeEventListener("click", press);
|
|
2300
|
+
if (shown?.control === control || pending?.control === control) hide();
|
|
2301
|
+
if (pressed === control) pressed = void 0;
|
|
2302
|
+
};
|
|
2303
|
+
detachers.add(detach);
|
|
2304
|
+
return detach;
|
|
2305
|
+
}
|
|
2306
|
+
function delegate(container, selector, content) {
|
|
2307
|
+
let hovered;
|
|
2308
|
+
const controlOf = (target) => {
|
|
2309
|
+
if (!target || typeof target.closest !== "function") return void 0;
|
|
2310
|
+
const match = target.closest(selector);
|
|
2311
|
+
return match && container.contains(match) ? match : void 0;
|
|
2312
|
+
};
|
|
2313
|
+
const sourceOf = (control) => () => content(control);
|
|
2314
|
+
const over = (event) => {
|
|
2315
|
+
const control = controlOf(event.target);
|
|
2316
|
+
if (control === hovered) return;
|
|
2317
|
+
if (hovered) onLeave(hovered);
|
|
2318
|
+
hovered = control;
|
|
2319
|
+
if (control) onEnter(control, sourceOf(control), event);
|
|
2320
|
+
};
|
|
2321
|
+
const out = (event) => {
|
|
2322
|
+
if (!hovered) return;
|
|
2323
|
+
if (controlOf(event.relatedTarget) === hovered) return;
|
|
2324
|
+
onLeave(hovered);
|
|
2325
|
+
hovered = void 0;
|
|
2326
|
+
};
|
|
2327
|
+
const focusIn = (event) => {
|
|
2328
|
+
const control = controlOf(event.target);
|
|
2329
|
+
if (control) onFocus(control, sourceOf(control));
|
|
2330
|
+
};
|
|
2331
|
+
const focusOut = (event) => {
|
|
2332
|
+
const control = controlOf(event.target);
|
|
2333
|
+
if (control) onBlur(control);
|
|
2334
|
+
};
|
|
2335
|
+
const press = (event) => {
|
|
2336
|
+
const control = controlOf(event.target);
|
|
2337
|
+
if (control) onPress(control, event);
|
|
2338
|
+
};
|
|
2339
|
+
container.addEventListener("pointerover", over);
|
|
2340
|
+
container.addEventListener("pointerout", out);
|
|
2341
|
+
container.addEventListener("focusin", focusIn);
|
|
2342
|
+
container.addEventListener("focusout", focusOut);
|
|
2343
|
+
container.addEventListener("pointerdown", press);
|
|
2344
|
+
container.addEventListener("click", press);
|
|
2345
|
+
const detach = () => {
|
|
2346
|
+
if (!detachers.delete(detach)) return;
|
|
2347
|
+
container.removeEventListener("pointerover", over);
|
|
2348
|
+
container.removeEventListener("pointerout", out);
|
|
2349
|
+
container.removeEventListener("focusin", focusIn);
|
|
2350
|
+
container.removeEventListener("focusout", focusOut);
|
|
2351
|
+
container.removeEventListener("pointerdown", press);
|
|
2352
|
+
container.removeEventListener("click", press);
|
|
2353
|
+
if (shown && container.contains(shown.control) || pending && container.contains(pending.control)) {
|
|
2354
|
+
hide();
|
|
2355
|
+
}
|
|
2356
|
+
hovered = void 0;
|
|
2357
|
+
};
|
|
2358
|
+
detachers.add(detach);
|
|
2359
|
+
return detach;
|
|
2360
|
+
}
|
|
2361
|
+
return {
|
|
2362
|
+
element,
|
|
2363
|
+
attach,
|
|
2364
|
+
delegate,
|
|
2365
|
+
hide,
|
|
2366
|
+
destroy() {
|
|
2367
|
+
hide();
|
|
2368
|
+
for (const detach of [...detachers]) detach();
|
|
2369
|
+
element.remove();
|
|
2370
|
+
}
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
// src/dom/web/drop-zone.ts
|
|
2375
|
+
var DRAG_EVENTS = ["dragenter", "dragover", "dragleave", "drop"];
|
|
2376
|
+
function carriesFiles(transfer) {
|
|
2377
|
+
if (!transfer) return false;
|
|
2378
|
+
if (Array.from(transfer.types ?? []).includes("Files")) return true;
|
|
2379
|
+
return Array.from(transfer.items ?? []).some((item) => item.kind === "file");
|
|
2380
|
+
}
|
|
2381
|
+
function dragItems(transfer) {
|
|
2382
|
+
const items = Array.from(transfer.items ?? [], (item) => ({ kind: item.kind, type: item.type }));
|
|
2383
|
+
return items.length > 0 ? items : [{ kind: "file", type: "" }];
|
|
2384
|
+
}
|
|
2385
|
+
function acceptList(accept, lang) {
|
|
2386
|
+
const entries = accept.map((entry) => entry.trim()).filter((entry) => entry !== "");
|
|
2387
|
+
const extensions = entries.filter((entry) => entry.startsWith("."));
|
|
2388
|
+
const listed = extensions.length > 0 ? extensions : entries;
|
|
2389
|
+
let format;
|
|
2390
|
+
try {
|
|
2391
|
+
format = new Intl.ListFormat(lang, { type: "disjunction" });
|
|
2392
|
+
} catch {
|
|
2393
|
+
format = new Intl.ListFormat("en", { type: "disjunction" });
|
|
2394
|
+
}
|
|
2395
|
+
return format.format(listed);
|
|
2396
|
+
}
|
|
2397
|
+
function createDropZone(context) {
|
|
2398
|
+
const { ownerDocument, layer, messages, lang, options, modalOpen, toast, setState } = context;
|
|
2399
|
+
const chip = createElement(ownerDocument, "span", { class: "cd-shell-drop-zone-chip" });
|
|
2400
|
+
const element = createElement(
|
|
2401
|
+
ownerDocument,
|
|
2402
|
+
"div",
|
|
2403
|
+
{ class: "cd-shell-drop-zone", "data-chrome": "drop-zone", "aria-hidden": "true" },
|
|
2404
|
+
chip
|
|
2405
|
+
);
|
|
2406
|
+
element.hidden = true;
|
|
2407
|
+
layer.append(element);
|
|
2408
|
+
const view = ownerDocument.defaultView;
|
|
2409
|
+
let depth = 0;
|
|
2410
|
+
let current = null;
|
|
2411
|
+
function verdictFor(items) {
|
|
2412
|
+
return dragVerdict(items, {
|
|
2413
|
+
accept: options.accept,
|
|
2414
|
+
multiple: options.multiple ?? false,
|
|
2415
|
+
dialogOpen: modalOpen()
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
function show(verdict) {
|
|
2419
|
+
const next = verdict.state === "none" ? null : verdict.state;
|
|
2420
|
+
if (verdict.state === "over") chip.textContent = messages.dropFile;
|
|
2421
|
+
if (verdict.state === "forbidden") chip.textContent = messages.dropForbidden(verdict.reason);
|
|
2422
|
+
if (next === current) return;
|
|
2423
|
+
current = next;
|
|
2424
|
+
element.hidden = next === null;
|
|
2425
|
+
if (next === null) element.removeAttribute("data-state");
|
|
2426
|
+
else element.setAttribute("data-state", next);
|
|
2427
|
+
setState(next);
|
|
2428
|
+
}
|
|
2429
|
+
function track(transfer) {
|
|
2430
|
+
const verdict = verdictFor(dragItems(transfer));
|
|
2431
|
+
show(verdict);
|
|
2432
|
+
transfer.dropEffect = verdict.state === "forbidden" ? "none" : "copy";
|
|
2433
|
+
}
|
|
2434
|
+
function rejectByDefault(files) {
|
|
2435
|
+
const accepted = acceptList(options.accept, lang);
|
|
2436
|
+
for (const file of files) toast({ status: "danger", text: messages.cannotOpenFile(file.name, accepted) });
|
|
2437
|
+
}
|
|
2438
|
+
function receive(files, verdict) {
|
|
2439
|
+
if (verdict.state === "forbidden" && verdict.reason !== "type") return;
|
|
2440
|
+
const accepted = files.filter((file) => acceptsFile(file, options.accept));
|
|
2441
|
+
const rejected = files.filter((file) => !accepted.includes(file));
|
|
2442
|
+
if (accepted.length > 0) options.onDrop(accepted);
|
|
2443
|
+
if (rejected.length > 0) (options.onReject ?? rejectByDefault)(rejected);
|
|
2444
|
+
}
|
|
2445
|
+
const onDragEvent = (event) => {
|
|
2446
|
+
const transfer = event.dataTransfer;
|
|
2447
|
+
if (!carriesFiles(transfer)) return;
|
|
2448
|
+
event.preventDefault();
|
|
2449
|
+
switch (event.type) {
|
|
2450
|
+
case "dragenter":
|
|
2451
|
+
depth += 1;
|
|
2452
|
+
track(transfer);
|
|
2453
|
+
break;
|
|
2454
|
+
case "dragover":
|
|
2455
|
+
depth = Math.max(depth, 1);
|
|
2456
|
+
track(transfer);
|
|
2457
|
+
break;
|
|
2458
|
+
case "dragleave":
|
|
2459
|
+
depth = Math.max(0, depth - 1);
|
|
2460
|
+
if (depth === 0) show({ state: "none" });
|
|
2461
|
+
break;
|
|
2462
|
+
case "drop": {
|
|
2463
|
+
depth = 0;
|
|
2464
|
+
show({ state: "none" });
|
|
2465
|
+
const files = Array.from(transfer.files ?? []);
|
|
2466
|
+
const items = files.length > 0 ? files.map((file) => ({ kind: "file", type: file.type })) : dragItems(transfer);
|
|
2467
|
+
receive(files, verdictFor(items));
|
|
2468
|
+
break;
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
};
|
|
2472
|
+
for (const type of DRAG_EVENTS) view?.addEventListener(type, onDragEvent);
|
|
2473
|
+
return {
|
|
2474
|
+
element,
|
|
2475
|
+
state: () => current,
|
|
2476
|
+
destroy() {
|
|
2477
|
+
for (const type of DRAG_EVENTS) view?.removeEventListener(type, onDragEvent);
|
|
2478
|
+
depth = 0;
|
|
2479
|
+
if (current !== null) {
|
|
2480
|
+
current = null;
|
|
2481
|
+
setState(null);
|
|
2482
|
+
}
|
|
2483
|
+
element.remove();
|
|
2484
|
+
}
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
// src/dom/web/empty-state.ts
|
|
2489
|
+
var KOMET_SIZE = "64";
|
|
2490
|
+
function createEmptyState(context) {
|
|
2491
|
+
const { ownerDocument, messages, options, dropHint, open, titleId, onLeave } = context;
|
|
2492
|
+
const button = (variant, label, run) => {
|
|
2493
|
+
const element2 = createElement(
|
|
2494
|
+
ownerDocument,
|
|
2495
|
+
"button",
|
|
2496
|
+
{ type: "button", class: "cd-shell-button", "data-variant": variant },
|
|
2497
|
+
label
|
|
2498
|
+
);
|
|
2499
|
+
element2.addEventListener("click", () => {
|
|
2500
|
+
onLeave();
|
|
2501
|
+
run();
|
|
2502
|
+
});
|
|
2503
|
+
return element2;
|
|
2504
|
+
};
|
|
2505
|
+
const onShowExample = options.onShowExample;
|
|
2506
|
+
const newDiagram = button("primary", messages.newDiagram, () => options.onNewDiagram());
|
|
2507
|
+
const element = createElement(
|
|
2508
|
+
ownerDocument,
|
|
2509
|
+
"section",
|
|
2510
|
+
{ class: "cd-shell-empty-state", "data-chrome": "empty-state", "aria-labelledby": titleId },
|
|
2511
|
+
// Dragging the mark would start an image drag that some browsers carry as a file.
|
|
2512
|
+
createElement(ownerDocument, "img", {
|
|
2513
|
+
class: "cd-shell-empty-state-mark",
|
|
2514
|
+
src: options.komet,
|
|
2515
|
+
width: KOMET_SIZE,
|
|
2516
|
+
height: KOMET_SIZE,
|
|
2517
|
+
alt: "",
|
|
2518
|
+
draggable: "false"
|
|
2519
|
+
}),
|
|
2520
|
+
createElement(ownerDocument, "h1", { class: "cd-shell-empty-state-title", id: titleId }, options.title),
|
|
2521
|
+
createElement(ownerDocument, "p", { class: "cd-shell-empty-state-text" }, options.text),
|
|
2522
|
+
createElement(
|
|
2523
|
+
ownerDocument,
|
|
2524
|
+
"div",
|
|
2525
|
+
{ class: "cd-shell-empty-state-actions" },
|
|
2526
|
+
newDiagram,
|
|
2527
|
+
onShowExample && button("ghost", messages.showExample, () => onShowExample())
|
|
2528
|
+
),
|
|
2529
|
+
hintParagraph()
|
|
2530
|
+
);
|
|
2531
|
+
function hintParagraph() {
|
|
2532
|
+
const hasDropHint = dropHint !== void 0 && dropHint !== "";
|
|
2533
|
+
if (!hasDropHint && !open) return void 0;
|
|
2534
|
+
const paragraph = createElement(ownerDocument, "p", { class: "cd-shell-empty-state-hint" });
|
|
2535
|
+
if (hasDropHint) paragraph.append(dropHint);
|
|
2536
|
+
if (!open) return paragraph;
|
|
2537
|
+
const shortcut = open.shortcut?.trim();
|
|
2538
|
+
const link = createElement(
|
|
2539
|
+
ownerDocument,
|
|
2540
|
+
"button",
|
|
2541
|
+
{
|
|
2542
|
+
type: "button",
|
|
2543
|
+
class: "cd-shell-empty-state-open",
|
|
2544
|
+
"aria-keyshortcuts": shortcut ? ariaKeyShortcutsFromLabel(shortcut) : void 0,
|
|
2545
|
+
"aria-haspopup": open.dialog ? "dialog" : void 0
|
|
2546
|
+
},
|
|
2547
|
+
messages.openFileLink
|
|
2548
|
+
);
|
|
2549
|
+
link.addEventListener("click", () => open.onSelect());
|
|
2550
|
+
if (hasDropHint) {
|
|
2551
|
+
paragraph.append(" ", createElement(ownerDocument, "span", { "aria-hidden": "true" }, "\xB7"), " ");
|
|
2552
|
+
}
|
|
2553
|
+
const openItem = createElement(ownerDocument, "span", { class: "cd-shell-empty-state-open-item" }, link);
|
|
2554
|
+
if (shortcut) {
|
|
2555
|
+
openItem.append(
|
|
2556
|
+
" ",
|
|
2557
|
+
createElement(
|
|
2558
|
+
ownerDocument,
|
|
2559
|
+
"kbd",
|
|
2560
|
+
{ class: "cd-shell-empty-state-key", "aria-hidden": "true" },
|
|
2561
|
+
shortcut
|
|
2562
|
+
)
|
|
2563
|
+
);
|
|
2564
|
+
}
|
|
2565
|
+
paragraph.append(openItem);
|
|
2566
|
+
return paragraph;
|
|
2567
|
+
}
|
|
2568
|
+
element.hidden = true;
|
|
2569
|
+
return {
|
|
2570
|
+
element,
|
|
2571
|
+
isShown: () => !element.hidden,
|
|
2572
|
+
setShown(shown) {
|
|
2573
|
+
element.hidden = !shown;
|
|
2574
|
+
},
|
|
2575
|
+
focus() {
|
|
2576
|
+
newDiagram.focus({ preventScroll: true });
|
|
2577
|
+
},
|
|
2578
|
+
destroy() {
|
|
2579
|
+
element.remove();
|
|
2580
|
+
}
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
// src/dom/web/legal.ts
|
|
2585
|
+
var MIRAGON_HOME = "https://www.miragon.io/";
|
|
2586
|
+
var MIRAGON_IMPRESSUM = "https://www.miragon.io/impressum/";
|
|
2587
|
+
var MIRAGON_DATENSCHUTZ = "https://www.miragon.io/datenschutz/";
|
|
2588
|
+
function createLegalLine(context) {
|
|
2589
|
+
const { ownerDocument, messages, options } = context;
|
|
2590
|
+
const year = options.year ?? (/* @__PURE__ */ new Date()).getFullYear();
|
|
2591
|
+
const newTabId = `${context.idPrefix}-new-tab`;
|
|
2592
|
+
const link = (label, href, lang) => createElement(
|
|
2593
|
+
ownerDocument,
|
|
2594
|
+
"a",
|
|
2595
|
+
{ href, lang, target: "_blank", rel: "noopener noreferrer", "aria-describedby": newTabId },
|
|
2596
|
+
label
|
|
2597
|
+
);
|
|
2598
|
+
const separator = () => createElement(ownerDocument, "span", { "aria-hidden": "true" }, "\xB7");
|
|
2599
|
+
const item = (anchor) => createElement(ownerDocument, "span", { class: "cd-shell-legal-item" }, separator(), anchor);
|
|
2600
|
+
const links = [
|
|
2601
|
+
link(messages.copyright(year), MIRAGON_HOME),
|
|
2602
|
+
link("Impressum", MIRAGON_IMPRESSUM, "de"),
|
|
2603
|
+
link("Datenschutz", MIRAGON_DATENSCHUTZ, "de"),
|
|
2604
|
+
...(options.links ?? []).map((extra) => link(extra.label, extra.href, extra.lang))
|
|
2605
|
+
];
|
|
2606
|
+
const element = createElement(ownerDocument, "nav", {
|
|
2607
|
+
class: "cd-shell-legal",
|
|
2608
|
+
"data-chrome": "legal",
|
|
2609
|
+
"aria-label": messages.legal
|
|
2610
|
+
});
|
|
2611
|
+
element.append(
|
|
2612
|
+
...links.map((anchor, index) => index === 0 ? anchor : item(anchor)),
|
|
2613
|
+
createElement(ownerDocument, "span", { id: newTabId, hidden: "" }, messages.opensInNewTab)
|
|
2614
|
+
);
|
|
2615
|
+
return {
|
|
2616
|
+
element,
|
|
2617
|
+
destroy() {
|
|
2618
|
+
element.remove();
|
|
2619
|
+
}
|
|
2620
|
+
};
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
// src/dom/web/menu-groups.ts
|
|
2624
|
+
function fileEntry(spec, action) {
|
|
2625
|
+
const common = {
|
|
2626
|
+
id: spec.id,
|
|
2627
|
+
label: spec.label,
|
|
2628
|
+
...spec.icon ? { icon: spec.icon } : {},
|
|
2629
|
+
...action.disabled ? { disabled: action.disabled } : {},
|
|
2630
|
+
...action.shortcut ? { shortcut: action.shortcut } : {},
|
|
2631
|
+
onSelect: () => action.onSelect()
|
|
2632
|
+
};
|
|
2633
|
+
return action.dialog ? { kind: "dialog", ...common } : { kind: "action", ...common };
|
|
2634
|
+
}
|
|
2635
|
+
function fileMenuEntries(options, messages) {
|
|
2636
|
+
const specs = [
|
|
2637
|
+
{ option: "newDiagram", id: "new-diagram", label: messages.newDiagram, icon: "file-plus" },
|
|
2638
|
+
{ option: "open", id: "open", label: messages.openFile, icon: "folder-open" },
|
|
2639
|
+
// §5.14 gives "Show example" no icon.
|
|
2640
|
+
{ option: "showExample", id: "show-example", label: messages.showExample, icon: void 0 },
|
|
2641
|
+
{ option: "import", id: "import", label: messages.importFile, icon: "import" }
|
|
2642
|
+
];
|
|
2643
|
+
return specs.flatMap((spec) => {
|
|
2644
|
+
const action = options[spec.option];
|
|
2645
|
+
return action ? [fileEntry(spec, action)] : [];
|
|
2646
|
+
});
|
|
2647
|
+
}
|
|
2648
|
+
function appearanceMenuEntries(appearance, messages) {
|
|
2649
|
+
return [
|
|
2650
|
+
{
|
|
2651
|
+
kind: "radio",
|
|
2652
|
+
id: "appearance",
|
|
2653
|
+
label: messages.appearance,
|
|
2654
|
+
options: [
|
|
2655
|
+
{ value: "system", label: messages.appearanceSystem },
|
|
2656
|
+
{ value: "light", label: messages.appearanceLight },
|
|
2657
|
+
{ value: "dark", label: messages.appearanceDark }
|
|
2658
|
+
],
|
|
2659
|
+
value: () => appearance.choice(),
|
|
2660
|
+
onSelect: (value) => appearance.setChoice(parseAppearanceChoice(value))
|
|
2661
|
+
}
|
|
2662
|
+
];
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
// src/dom/web/primary.ts
|
|
2666
|
+
var COLLAPSED_ATTRIBUTE = "data-collapsed";
|
|
2667
|
+
function createPrimaryAction(context) {
|
|
2668
|
+
const { ownerDocument, messages, tooltip, options } = context;
|
|
2669
|
+
const label = options.label ?? messages.share;
|
|
2670
|
+
const labelElement = createElement(ownerDocument, "span", { class: "cd-shell-primary-label" }, label);
|
|
2671
|
+
const element = createElement(
|
|
2672
|
+
ownerDocument,
|
|
2673
|
+
"button",
|
|
2674
|
+
{
|
|
2675
|
+
type: "button",
|
|
2676
|
+
class: "cd-shell-primary cd-shell-button",
|
|
2677
|
+
"data-variant": "primary",
|
|
2678
|
+
"data-chrome": "primary",
|
|
2679
|
+
"aria-label": label
|
|
2680
|
+
},
|
|
2681
|
+
iconElement(options.icon ?? "share-2"),
|
|
2682
|
+
labelElement
|
|
2683
|
+
);
|
|
2684
|
+
let collapsed = false;
|
|
2685
|
+
let disabled = false;
|
|
2686
|
+
let labelledWidth;
|
|
2687
|
+
function readDisabled() {
|
|
2688
|
+
disabled = options.disabled?.() ?? false;
|
|
2689
|
+
toggleAttribute(element, "aria-disabled", disabled ? "true" : null);
|
|
2690
|
+
}
|
|
2691
|
+
function showCollapsed(state) {
|
|
2692
|
+
labelElement.hidden = state;
|
|
2693
|
+
toggleAttribute(element, COLLAPSED_ATTRIBUTE, state ? "" : null);
|
|
2694
|
+
}
|
|
2695
|
+
function measureLabelledWidth() {
|
|
2696
|
+
if (!collapsed) return element.offsetWidth;
|
|
2697
|
+
showCollapsed(false);
|
|
2698
|
+
const width = element.offsetWidth;
|
|
2699
|
+
showCollapsed(true);
|
|
2700
|
+
return width;
|
|
2701
|
+
}
|
|
2702
|
+
function isEngaged() {
|
|
2703
|
+
if (ownerDocument.activeElement === element) return true;
|
|
2704
|
+
try {
|
|
2705
|
+
return element.matches(":hover");
|
|
2706
|
+
} catch {
|
|
2707
|
+
return false;
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
const onClick = (event) => {
|
|
2711
|
+
if (disabled) {
|
|
2712
|
+
event.preventDefault();
|
|
2713
|
+
return;
|
|
2714
|
+
}
|
|
2715
|
+
options.onSelect();
|
|
2716
|
+
};
|
|
2717
|
+
element.addEventListener("click", onClick);
|
|
2718
|
+
const detachTooltip = tooltip.attach(element, () => collapsed ? { label } : null);
|
|
2719
|
+
readDisabled();
|
|
2720
|
+
return {
|
|
2721
|
+
element,
|
|
2722
|
+
isCollapsed: () => collapsed,
|
|
2723
|
+
refresh() {
|
|
2724
|
+
readDisabled();
|
|
2725
|
+
labelledWidth = void 0;
|
|
2726
|
+
},
|
|
2727
|
+
widths() {
|
|
2728
|
+
if (!collapsed || labelledWidth === void 0) {
|
|
2729
|
+
const measured = measureLabelledWidth();
|
|
2730
|
+
if (measured > 0) labelledWidth = measured;
|
|
2731
|
+
}
|
|
2732
|
+
return { withLabel: labelledWidth ?? 0, iconOnly: SHELL.row };
|
|
2733
|
+
},
|
|
2734
|
+
setCollapsed(next) {
|
|
2735
|
+
if (next === collapsed) return;
|
|
2736
|
+
collapsed = next;
|
|
2737
|
+
showCollapsed(next);
|
|
2738
|
+
if (!next && isEngaged()) tooltip.hide();
|
|
2739
|
+
},
|
|
2740
|
+
destroy() {
|
|
2741
|
+
detachTooltip();
|
|
2742
|
+
element.removeEventListener("click", onClick);
|
|
2743
|
+
element.remove();
|
|
2744
|
+
}
|
|
2745
|
+
};
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
// src/dom/zoom-panel.ts
|
|
2749
|
+
function createZoomPanel(context) {
|
|
2750
|
+
const { ownerDocument, messages, lang, platform, tooltip } = context;
|
|
2751
|
+
let target = null;
|
|
2752
|
+
let unsubscribe;
|
|
2753
|
+
let frame;
|
|
2754
|
+
const detachers = [];
|
|
2755
|
+
function button(zoom, key, tooltipLabel, options) {
|
|
2756
|
+
const element2 = createElement(ownerDocument, "button", {
|
|
2757
|
+
type: "button",
|
|
2758
|
+
class: options.className ?? "cd-shell-target",
|
|
2759
|
+
"data-zoom": zoom,
|
|
2760
|
+
"aria-label": options.label,
|
|
2761
|
+
"aria-keyshortcuts": ariaKeyShortcuts(key, platform)
|
|
2762
|
+
});
|
|
2763
|
+
if (options.icon) element2.append(iconElement(options.icon));
|
|
2764
|
+
element2.addEventListener("click", () => run(key));
|
|
2765
|
+
detachers.push(
|
|
2766
|
+
tooltip.attach(element2, {
|
|
2767
|
+
label: tooltipLabel,
|
|
2768
|
+
shortcut: shortcutLabel(key, platform, messages.keyNames)
|
|
2769
|
+
})
|
|
2770
|
+
);
|
|
2771
|
+
return element2;
|
|
2772
|
+
}
|
|
2773
|
+
const zoomOut = button("out", "zoomOut", messages.zoomOut, { label: messages.zoomOut, icon: "minus" });
|
|
2774
|
+
const readout = button("actual", "zoomToActual", messages.zoomToActual(formatPercent(1, lang)), {
|
|
2775
|
+
className: "cd-shell-target cd-shell-zoom-readout"
|
|
2776
|
+
});
|
|
2777
|
+
const zoomIn = button("in", "zoomIn", messages.zoomIn, { label: messages.zoomIn, icon: "plus" });
|
|
2778
|
+
const fit = button("fit", "fit", messages.fit, { label: messages.fit, icon: "maximize" });
|
|
2779
|
+
const separator = createElement(ownerDocument, "span", {
|
|
2780
|
+
class: "cd-shell-zoom-separator",
|
|
2781
|
+
"aria-hidden": "true"
|
|
2782
|
+
});
|
|
2783
|
+
const element = createElement(
|
|
2784
|
+
ownerDocument,
|
|
2785
|
+
"div",
|
|
2786
|
+
{
|
|
2787
|
+
class: "cd-shell-zoom cd-shell-island",
|
|
2788
|
+
"data-chrome": "zoom",
|
|
2789
|
+
role: "group",
|
|
2790
|
+
"aria-label": messages.zoom
|
|
2791
|
+
},
|
|
2792
|
+
zoomOut,
|
|
2793
|
+
readout,
|
|
2794
|
+
zoomIn,
|
|
2795
|
+
separator,
|
|
2796
|
+
fit
|
|
2797
|
+
);
|
|
2798
|
+
function render() {
|
|
2799
|
+
frame = void 0;
|
|
2800
|
+
const zoom = target?.zoom() ?? 1;
|
|
2801
|
+
const state = zoomReadout(zoom, lang, messages);
|
|
2802
|
+
if (readout.textContent !== state.text) readout.textContent = state.text;
|
|
2803
|
+
readout.setAttribute("aria-label", state.ariaLabel);
|
|
2804
|
+
toggleAttribute(zoomOut, "aria-disabled", (!target || !state.canZoomOut) && "true");
|
|
2805
|
+
toggleAttribute(zoomIn, "aria-disabled", (!target || !state.canZoomIn) && "true");
|
|
2806
|
+
toggleAttribute(fit, "aria-disabled", (!target || !target.canFit()) && "true");
|
|
2807
|
+
}
|
|
2808
|
+
function schedule() {
|
|
2809
|
+
if (frame === void 0) frame = requestAnimationFrame(render);
|
|
2810
|
+
}
|
|
2811
|
+
function cancelFrame() {
|
|
2812
|
+
if (frame !== void 0) cancelAnimationFrame(frame);
|
|
2813
|
+
frame = void 0;
|
|
2814
|
+
}
|
|
2815
|
+
function run(key) {
|
|
2816
|
+
if (!target) return;
|
|
2817
|
+
const zoom = target.zoom();
|
|
2818
|
+
switch (key) {
|
|
2819
|
+
case "zoomIn":
|
|
2820
|
+
if (canZoomIn(zoom)) target.stepZoom(1);
|
|
2821
|
+
break;
|
|
2822
|
+
case "zoomOut":
|
|
2823
|
+
if (canZoomOut(zoom)) target.stepZoom(-1);
|
|
2824
|
+
break;
|
|
2825
|
+
case "zoomToActual":
|
|
2826
|
+
if (zoom !== 1) target.zoomToActual();
|
|
2827
|
+
break;
|
|
2828
|
+
case "fit":
|
|
2829
|
+
if (target.canFit()) target.fit();
|
|
2830
|
+
break;
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
render();
|
|
2834
|
+
return {
|
|
2835
|
+
element,
|
|
2836
|
+
run,
|
|
2837
|
+
setTarget(next) {
|
|
2838
|
+
unsubscribe?.();
|
|
2839
|
+
unsubscribe = void 0;
|
|
2840
|
+
cancelFrame();
|
|
2841
|
+
target = next;
|
|
2842
|
+
if (next) unsubscribe = next.onChange(schedule);
|
|
2843
|
+
render();
|
|
2844
|
+
},
|
|
2845
|
+
destroy() {
|
|
2846
|
+
unsubscribe?.();
|
|
2847
|
+
unsubscribe = void 0;
|
|
2848
|
+
cancelFrame();
|
|
2849
|
+
target = null;
|
|
2850
|
+
for (const detach of detachers) detach();
|
|
2851
|
+
element.remove();
|
|
2852
|
+
}
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
// src/dom/mount.ts
|
|
2857
|
+
var MOUNT_COUNTER = /* @__PURE__ */ Symbol.for("@miragon/modeler-shell.mounts");
|
|
2858
|
+
function nextMountIndex() {
|
|
2859
|
+
const scope = globalThis;
|
|
2860
|
+
const index = (scope[MOUNT_COUNTER] ?? 0) + 1;
|
|
2861
|
+
scope[MOUNT_COUNTER] = index;
|
|
2862
|
+
return index;
|
|
2863
|
+
}
|
|
2864
|
+
var DEFAULT_THEMES = ["light"];
|
|
2865
|
+
var LEGAL_HEIGHT_PROPERTY = "--modeler-shell-legal-height";
|
|
2866
|
+
var INERT_TOAST = Object.freeze({ dismiss() {
|
|
2867
|
+
} });
|
|
2868
|
+
function visibleBoxIn(element, root) {
|
|
2869
|
+
if (!element || element.hidden || !element.isConnected) return void 0;
|
|
2870
|
+
if (typeof element.checkVisibility === "function" && !element.checkVisibility({ visibilityProperty: true }))
|
|
2871
|
+
return void 0;
|
|
2872
|
+
const box = boxRelativeTo(element, root);
|
|
2873
|
+
return box.width > 0 && box.height > 0 ? box : void 0;
|
|
2874
|
+
}
|
|
2875
|
+
function languageOf(root, host, requested) {
|
|
2876
|
+
if (requested) return requested;
|
|
2877
|
+
const declared = host === "embedded" ? root.getAttribute("lang") : root.closest("[lang]")?.getAttribute("lang");
|
|
2878
|
+
return declared || "en";
|
|
2879
|
+
}
|
|
2880
|
+
function hostKontrast(root) {
|
|
2881
|
+
const carrier = root.parentElement?.closest("[data-cd-theme], [data-cd-kontrast]");
|
|
2882
|
+
return carrier?.getAttribute("data-cd-kontrast") === "hoch" ? "hoch" : null;
|
|
2883
|
+
}
|
|
2884
|
+
function mountShell(root, options) {
|
|
2885
|
+
const themes = options.themes ?? DEFAULT_THEMES;
|
|
2886
|
+
if (themes.length === 0) throw new Error("@miragon/modeler-shell: mountShell needs at least one theme");
|
|
2887
|
+
const ledger = new Ledger();
|
|
2888
|
+
try {
|
|
2889
|
+
return renderShell(root, options, themes, ledger);
|
|
2890
|
+
} catch (error) {
|
|
2891
|
+
ledger.close();
|
|
2892
|
+
throw error;
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
function renderShell(root, options, themes, ledger) {
|
|
2896
|
+
const ownerDocument = root.ownerDocument;
|
|
2897
|
+
const { host } = options;
|
|
2898
|
+
const index = nextMountIndex();
|
|
2899
|
+
const messages = resolveMessages(options.messages);
|
|
2900
|
+
const platform = options.platform ?? detectPlatform(globalThis.navigator);
|
|
2901
|
+
const lang = languageOf(root, host, options.lang);
|
|
2902
|
+
const keyScope = options.keyScope ?? (host === "embedded" ? "root" : "document");
|
|
2903
|
+
const web = host === "web";
|
|
2904
|
+
ledger.addClass(root, "cd-shell");
|
|
2905
|
+
ledger.attribute(root, "data-cd-shell-host", host);
|
|
2906
|
+
ledger.attribute(root, "data-palette", "top");
|
|
2907
|
+
if (host === "embedded") {
|
|
2908
|
+
ledger.attribute(root, "lang", lang);
|
|
2909
|
+
carryOwnMode(root, themes[0] ?? "light", ledger);
|
|
2910
|
+
} else {
|
|
2911
|
+
ledger.addClass(ownerDocument.documentElement, "cd-shell-document");
|
|
2912
|
+
}
|
|
2913
|
+
const setState = (name, value) => {
|
|
2914
|
+
if (!ledger.isClosed) ledger.attribute(root, name, value);
|
|
2915
|
+
};
|
|
2916
|
+
const appearance = web ? connectWebAppearance({ ownerDocument, themes, options: options.appearance }) : void 0;
|
|
2917
|
+
if (appearance) ledger.defer(() => appearance.destroy());
|
|
2918
|
+
const darkCapable = themes.includes("dark");
|
|
2919
|
+
const layer = createElement(ownerDocument, "div", { class: "cd-shell-layer" });
|
|
2920
|
+
ledger.defer(() => layer.remove());
|
|
2921
|
+
const tooltip = createTooltip({ root, layer, id: `cd-shell-tooltip-${index}` });
|
|
2922
|
+
ledger.defer(() => tooltip.destroy());
|
|
2923
|
+
let readOnly = false;
|
|
2924
|
+
let readOnlyShown = false;
|
|
2925
|
+
let readOnlyTimer;
|
|
2926
|
+
ledger.defer(() => clearTimeout(readOnlyTimer));
|
|
2927
|
+
const legend = options.legend ? createLegend({
|
|
2928
|
+
ownerDocument,
|
|
2929
|
+
messages,
|
|
2930
|
+
tooltip,
|
|
2931
|
+
titleId: `cd-shell-legend-title-${index}`,
|
|
2932
|
+
options: options.legend,
|
|
2933
|
+
onChange(open) {
|
|
2934
|
+
setState("data-legend", open ? "open" : null);
|
|
2935
|
+
placeHint();
|
|
2936
|
+
menu.refresh();
|
|
2937
|
+
},
|
|
2938
|
+
focusAfterClose: () => menu.trigger.focus()
|
|
2939
|
+
}) : void 0;
|
|
2940
|
+
if (legend) ledger.defer(() => legend.destroy());
|
|
2941
|
+
const legendEntry = legend && options.legend?.menuEntry !== false ? {
|
|
2942
|
+
kind: "checkbox",
|
|
2943
|
+
id: "legend",
|
|
2944
|
+
label: messages.legend,
|
|
2945
|
+
checked: () => legend.isOpen(),
|
|
2946
|
+
onToggle: (checked) => legend.setOpen(checked),
|
|
2947
|
+
keepEnabledWhenReadOnly: true
|
|
2948
|
+
} : void 0;
|
|
2949
|
+
const shellModelEntries = legendEntry ? [legendEntry] : [];
|
|
2950
|
+
const fileEntries = web && options.file ? fileMenuEntries(options.file, messages) : [];
|
|
2951
|
+
const appearanceEntries = appearance && darkCapable ? appearanceMenuEntries(appearance, messages) : [];
|
|
2952
|
+
const shellEntries = (group) => {
|
|
2953
|
+
if (group === "file") return fileEntries;
|
|
2954
|
+
if (group === "model") return shellModelEntries;
|
|
2955
|
+
if (group === "appearance") return appearanceEntries;
|
|
2956
|
+
return [];
|
|
2957
|
+
};
|
|
2958
|
+
const menu = createMenu({
|
|
2959
|
+
root,
|
|
2960
|
+
layer,
|
|
2961
|
+
id: `cd-shell-menu-${index}`,
|
|
2962
|
+
messages,
|
|
2963
|
+
tooltip,
|
|
2964
|
+
groups: options.menu ?? {},
|
|
2965
|
+
shellEntries,
|
|
2966
|
+
isReadOnly: () => readOnly
|
|
2967
|
+
});
|
|
2968
|
+
ledger.defer(() => menu.destroy());
|
|
2969
|
+
ledger.defer(tooltip.attach(menu.trigger, () => menu.isOpen() ? null : { label: messages.menu }));
|
|
2970
|
+
const primary = web && options.primary ? createPrimaryAction({ ownerDocument, messages, tooltip, options: options.primary }) : void 0;
|
|
2971
|
+
if (primary) ledger.defer(() => primary.destroy());
|
|
2972
|
+
const canvas = options.canvas ?? createElement(ownerDocument, "div");
|
|
2973
|
+
ledger.addClass(canvas, "cd-shell-canvas");
|
|
2974
|
+
if (web && !canvas.hasAttribute("role")) ledger.attribute(canvas, "role", "main");
|
|
2975
|
+
if (!options.canvas) ledger.defer(() => canvas.remove());
|
|
2976
|
+
function focusCanvasOrTrigger() {
|
|
2977
|
+
const took = (target) => {
|
|
2978
|
+
target?.focus({ preventScroll: true });
|
|
2979
|
+
return target !== null && target !== void 0 && ownerDocument.activeElement === target;
|
|
2980
|
+
};
|
|
2981
|
+
if (emptyState?.isShown()) {
|
|
2982
|
+
emptyState.focus();
|
|
2983
|
+
if (emptyState.element.contains(ownerDocument.activeElement)) return;
|
|
2984
|
+
}
|
|
2985
|
+
if (took(canvas.querySelector("svg[tabindex]"))) return;
|
|
2986
|
+
if (!menu.trigger.hidden) took(menu.trigger);
|
|
2987
|
+
}
|
|
2988
|
+
function moveFocusOutOf(island) {
|
|
2989
|
+
if (island.contains(ownerDocument.activeElement)) focusCanvasOrTrigger();
|
|
2990
|
+
}
|
|
2991
|
+
const status = createStatusBox(ownerDocument);
|
|
2992
|
+
ledger.defer(() => status.destroy());
|
|
2993
|
+
const hint = options.emptyHint ? createEmptyHint(ownerDocument, messages, options.emptyHint) : void 0;
|
|
2994
|
+
if (hint) ledger.defer(() => hint.destroy());
|
|
2995
|
+
const emptyState = web && options.emptyState ? createEmptyState({
|
|
2996
|
+
ownerDocument,
|
|
2997
|
+
messages,
|
|
2998
|
+
options: options.emptyState,
|
|
2999
|
+
dropHint: options.dropZone ? options.emptyState.dropHint ?? messages.dropHint : void 0,
|
|
3000
|
+
open: options.file?.open,
|
|
3001
|
+
titleId: `cd-shell-empty-state-title-${index}`,
|
|
3002
|
+
onLeave: () => setEmptyStateShown(false)
|
|
3003
|
+
}) : void 0;
|
|
3004
|
+
if (emptyState) ledger.defer(() => emptyState.destroy());
|
|
3005
|
+
const inspector = options.inspector ? createInspector(ownerDocument, options.inspector, (open) => {
|
|
3006
|
+
setState("data-inspector", open ? "open" : null);
|
|
3007
|
+
updateBottomRow();
|
|
3008
|
+
placeHint();
|
|
3009
|
+
}) : void 0;
|
|
3010
|
+
if (inspector) ledger.defer(() => inspector.destroy());
|
|
3011
|
+
const problemsOptions = options.problems;
|
|
3012
|
+
let popover;
|
|
3013
|
+
const pill = problemsOptions ? createProblemsPill({
|
|
3014
|
+
ownerDocument,
|
|
3015
|
+
messages,
|
|
3016
|
+
lang,
|
|
3017
|
+
tooltip,
|
|
3018
|
+
showTooltip: host === "ide",
|
|
3019
|
+
onActivate() {
|
|
3020
|
+
if (popover) popover.toggle();
|
|
3021
|
+
else problemsOptions.onActivate?.();
|
|
3022
|
+
}
|
|
3023
|
+
}) : void 0;
|
|
3024
|
+
if (pill) ledger.defer(() => pill.destroy());
|
|
3025
|
+
if (pill && (web || host === "embedded" && !problemsOptions?.onActivate)) {
|
|
3026
|
+
const created = createProblemsPopover({
|
|
3027
|
+
ownerDocument,
|
|
3028
|
+
root,
|
|
3029
|
+
layer,
|
|
3030
|
+
pill: pill.element,
|
|
3031
|
+
id: `cd-shell-problems-${index}`,
|
|
3032
|
+
titleId: `cd-shell-problems-title-${index}`,
|
|
3033
|
+
messages,
|
|
3034
|
+
onSelectItem: problemsOptions?.onSelectItem
|
|
3035
|
+
});
|
|
3036
|
+
popover = created;
|
|
3037
|
+
ledger.defer(() => created.destroy());
|
|
3038
|
+
}
|
|
3039
|
+
const legal = web ? createLegalLine({
|
|
3040
|
+
ownerDocument,
|
|
3041
|
+
messages,
|
|
3042
|
+
options: options.legal ?? {},
|
|
3043
|
+
idPrefix: `cd-shell-legal-${index}`
|
|
3044
|
+
}) : void 0;
|
|
3045
|
+
if (legal) ledger.defer(() => legal.destroy());
|
|
3046
|
+
const zoomPanel = createZoomPanel({ ownerDocument, messages, lang, platform, tooltip });
|
|
3047
|
+
ledger.defer(() => zoomPanel.destroy());
|
|
3048
|
+
const toaster = host === "ide" ? void 0 : createToaster({
|
|
3049
|
+
ownerDocument,
|
|
3050
|
+
host,
|
|
3051
|
+
messages,
|
|
3052
|
+
idPrefix: `cd-shell-toast-${index}`,
|
|
3053
|
+
focusFallback: focusCanvasOrTrigger,
|
|
3054
|
+
onChange: () => updateBottomRow()
|
|
3055
|
+
});
|
|
3056
|
+
if (toaster) ledger.defer(() => toaster.destroy());
|
|
3057
|
+
const defined = (element) => element !== void 0;
|
|
3058
|
+
root.prepend(...[menu.trigger, primary?.element, canvas].filter(defined));
|
|
3059
|
+
root.append(
|
|
3060
|
+
...[
|
|
3061
|
+
status.element,
|
|
3062
|
+
hint?.element,
|
|
3063
|
+
emptyState?.element,
|
|
3064
|
+
inspector?.element,
|
|
3065
|
+
pill?.element,
|
|
3066
|
+
legend?.element,
|
|
3067
|
+
legal?.element,
|
|
3068
|
+
zoomPanel.element
|
|
3069
|
+
].filter(defined),
|
|
3070
|
+
layer,
|
|
3071
|
+
...[toaster?.announcer, toaster?.element].filter(defined)
|
|
3072
|
+
);
|
|
3073
|
+
function showToast(request) {
|
|
3074
|
+
return ledger.isClosed || !toaster ? INERT_TOAST : toaster.show(request);
|
|
3075
|
+
}
|
|
3076
|
+
const dropZone = web && options.dropZone ? createDropZone({
|
|
3077
|
+
ownerDocument,
|
|
3078
|
+
layer,
|
|
3079
|
+
messages,
|
|
3080
|
+
lang,
|
|
3081
|
+
options: options.dropZone,
|
|
3082
|
+
modalOpen: () => isModalOpen(ownerDocument),
|
|
3083
|
+
toast: showToast,
|
|
3084
|
+
setState: (state) => setState("data-drop", state)
|
|
3085
|
+
}) : void 0;
|
|
3086
|
+
if (dropZone) ledger.defer(() => dropZone.destroy());
|
|
3087
|
+
let palette;
|
|
3088
|
+
let paletteRow = "top";
|
|
3089
|
+
let zoomRaised = false;
|
|
3090
|
+
let legalHeight = LEGAL_LINE_HEIGHT;
|
|
3091
|
+
if (legal) ledger.defer(() => root.style.removeProperty(LEGAL_HEIGHT_PROPERTY));
|
|
3092
|
+
function containerSize() {
|
|
3093
|
+
return { width: root.clientWidth, height: root.clientHeight };
|
|
3094
|
+
}
|
|
3095
|
+
function menuRight() {
|
|
3096
|
+
const box = boxRelativeTo(menu.trigger, root);
|
|
3097
|
+
return box.width > 0 ? box.x + box.width : SHELL.edge + SHELL.row;
|
|
3098
|
+
}
|
|
3099
|
+
function paletteWidth() {
|
|
3100
|
+
const element = palette?.registration.element;
|
|
3101
|
+
return element ? element.offsetWidth - element.clientWidth + element.scrollWidth : 0;
|
|
3102
|
+
}
|
|
3103
|
+
function currentFitInsets() {
|
|
3104
|
+
const container = containerSize();
|
|
3105
|
+
const layout = {
|
|
3106
|
+
container,
|
|
3107
|
+
host,
|
|
3108
|
+
paletteRow,
|
|
3109
|
+
problemsVisible: pill?.isShown() ?? false
|
|
3110
|
+
};
|
|
3111
|
+
if (web) {
|
|
3112
|
+
layout.zoomRaised = zoomRaised;
|
|
3113
|
+
layout.legalHeight = legalHeight;
|
|
3114
|
+
}
|
|
3115
|
+
if (legend?.isOpen()) layout.legendWidth = legend.element.getBoundingClientRect().width;
|
|
3116
|
+
if (inspector?.isOpen()) {
|
|
3117
|
+
layout.inspector = container.width > SHELL.narrow ? {} : { sheetTop: boxRelativeTo(inspector.element, root).y };
|
|
3118
|
+
}
|
|
3119
|
+
return fitInsets(layout);
|
|
3120
|
+
}
|
|
3121
|
+
function placeHint() {
|
|
3122
|
+
if (hint?.isShown()) hint.place(currentFitInsets());
|
|
3123
|
+
}
|
|
3124
|
+
function updateLayout() {
|
|
3125
|
+
if (ledger.isClosed) return;
|
|
3126
|
+
const size = containerSize();
|
|
3127
|
+
if (size.width === 0 || size.height === 0) return;
|
|
3128
|
+
const topRow = decideTopRow({
|
|
3129
|
+
containerWidth: size.width,
|
|
3130
|
+
paletteWidth: paletteWidth(),
|
|
3131
|
+
menuRight: menuRight(),
|
|
3132
|
+
primary: primary?.widths()
|
|
3133
|
+
});
|
|
3134
|
+
paletteRow = topRow.paletteRow;
|
|
3135
|
+
setState("data-palette", paletteRow);
|
|
3136
|
+
if (primary) {
|
|
3137
|
+
const collapsed = topRow.primaryLabel === "collapsed";
|
|
3138
|
+
primary.setCollapsed(collapsed);
|
|
3139
|
+
setState("data-primary", collapsed ? "collapsed" : null);
|
|
3140
|
+
}
|
|
3141
|
+
if (legend && !legend.isStarted()) legend.start(options.legend?.open ?? legendStartsOpen(size.height));
|
|
3142
|
+
updateBottomRow();
|
|
3143
|
+
placeHint();
|
|
3144
|
+
}
|
|
3145
|
+
function updateBottomRow() {
|
|
3146
|
+
if (ledger.isClosed) return;
|
|
3147
|
+
const size = containerSize();
|
|
3148
|
+
if (size.width === 0 || size.height === 0) return;
|
|
3149
|
+
if (legal) {
|
|
3150
|
+
const legalBox = visibleBoxIn(legal.element, root);
|
|
3151
|
+
const height = legalLineHeight(legalBox?.height);
|
|
3152
|
+
if (height !== legalHeight || !root.style.getPropertyValue(LEGAL_HEIGHT_PROPERTY)) {
|
|
3153
|
+
legalHeight = height;
|
|
3154
|
+
root.style.setProperty(LEGAL_HEIGHT_PROPERTY, `${height}px`);
|
|
3155
|
+
}
|
|
3156
|
+
zoomRaised = zoomRowRaised({
|
|
3157
|
+
containerWidth: size.width,
|
|
3158
|
+
legalRight: legalBox && legalBox.x + legalBox.width,
|
|
3159
|
+
zoomWidth: visibleBoxIn(zoomPanel.element, root)?.width
|
|
3160
|
+
});
|
|
3161
|
+
setState("data-zoom-row", zoomRaised ? "raised" : null);
|
|
3162
|
+
}
|
|
3163
|
+
if (toaster) {
|
|
3164
|
+
const obstacles = [zoomPanel.element, legal?.element, pill?.element].flatMap(
|
|
3165
|
+
(element) => {
|
|
3166
|
+
const box = visibleBoxIn(element, root);
|
|
3167
|
+
return box ? [{ left: box.x, right: box.x + box.width, top: size.height - box.y }] : [];
|
|
3168
|
+
}
|
|
3169
|
+
);
|
|
3170
|
+
toaster.setBottom(
|
|
3171
|
+
toastStackBottom({ containerWidth: size.width, obstacles, cardWidth: toaster.widestCard() })
|
|
3172
|
+
);
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
function setEmptyStateShown(shown) {
|
|
3176
|
+
if (ledger.isClosed || !emptyState || emptyState.isShown() === shown) return;
|
|
3177
|
+
const active = ownerDocument.activeElement;
|
|
3178
|
+
const hadFocus = emptyState.element.contains(active);
|
|
3179
|
+
const workHadFocus = shown && active !== null && root.contains(active) && ![emptyState.element, legal?.element, toaster?.element].some((kept) => kept?.contains(active));
|
|
3180
|
+
emptyState.setShown(shown);
|
|
3181
|
+
setState("data-empty-state", shown ? "shown" : null);
|
|
3182
|
+
ledger.attribute(canvas, "inert", shown ? "" : null);
|
|
3183
|
+
if (shown) {
|
|
3184
|
+
menu.close();
|
|
3185
|
+
popover?.close();
|
|
3186
|
+
tooltip.hide();
|
|
3187
|
+
if (workHadFocus) emptyState.focus();
|
|
3188
|
+
} else if (hadFocus) {
|
|
3189
|
+
focusCanvasOrTrigger();
|
|
3190
|
+
}
|
|
3191
|
+
updateLayout();
|
|
3192
|
+
}
|
|
3193
|
+
const ResizeObserverClass = globalThis.ResizeObserver;
|
|
3194
|
+
const resizeObserver = ResizeObserverClass ? new ResizeObserverClass(() => updateLayout()) : void 0;
|
|
3195
|
+
resizeObserver?.observe(root);
|
|
3196
|
+
if (primary) resizeObserver?.observe(primary.element);
|
|
3197
|
+
if (legal) resizeObserver?.observe(legal.element);
|
|
3198
|
+
ledger.defer(() => resizeObserver?.disconnect());
|
|
3199
|
+
if (legend && options.legend?.open !== void 0) legend.start(options.legend.open);
|
|
3200
|
+
updateLayout();
|
|
3201
|
+
const keyTarget = keyScope === "root" ? root : ownerDocument;
|
|
3202
|
+
const onKeydown = createKeyListener({
|
|
3203
|
+
host,
|
|
3204
|
+
dialogScope: keyTarget,
|
|
3205
|
+
menuOpen: () => menu.isOpen(),
|
|
3206
|
+
startScreen: () => emptyState?.isShown() ?? false,
|
|
3207
|
+
closeMenu() {
|
|
3208
|
+
const active = ownerDocument.activeElement;
|
|
3209
|
+
menu.close({ restoreFocus: active === menu.trigger || menu.dropdown.contains(active) });
|
|
3210
|
+
},
|
|
3211
|
+
hideTooltip: () => tooltip.hide(),
|
|
3212
|
+
// Behind the start screen the keys reach the browser instead: there is no diagram to zoom.
|
|
3213
|
+
run: (key) => {
|
|
3214
|
+
if (!emptyState?.isShown()) zoomPanel.run(key);
|
|
3215
|
+
}
|
|
3216
|
+
});
|
|
3217
|
+
keyTarget.addEventListener("keydown", onKeydown);
|
|
3218
|
+
ledger.defer(() => keyTarget.removeEventListener("keydown", onKeydown));
|
|
3219
|
+
function readOnlyState() {
|
|
3220
|
+
return { locked: readOnly, shown: readOnlyShown };
|
|
3221
|
+
}
|
|
3222
|
+
function applyReadOnly() {
|
|
3223
|
+
setState("data-readonly", readOnlyShown ? "" : null);
|
|
3224
|
+
palette?.registration.setReadOnly(readOnlyState());
|
|
3225
|
+
menu.refresh();
|
|
3226
|
+
}
|
|
3227
|
+
function showReadOnly() {
|
|
3228
|
+
readOnlyTimer = void 0;
|
|
3229
|
+
if (ledger.isClosed || !readOnly) return;
|
|
3230
|
+
readOnlyShown = true;
|
|
3231
|
+
applyReadOnly();
|
|
3232
|
+
}
|
|
3233
|
+
function unregisterPalette() {
|
|
3234
|
+
if (!palette) return;
|
|
3235
|
+
palette.detachTooltip();
|
|
3236
|
+
resizeObserver?.unobserve(palette.registration.element);
|
|
3237
|
+
palette = void 0;
|
|
3238
|
+
}
|
|
3239
|
+
ledger.defer(unregisterPalette);
|
|
3240
|
+
const menuHandle = {
|
|
3241
|
+
trigger: menu.trigger,
|
|
3242
|
+
dropdown: menu.dropdown,
|
|
3243
|
+
isOpen: () => menu.isOpen(),
|
|
3244
|
+
open: (focus) => menu.open(focus),
|
|
3245
|
+
close: (closeOptions) => menu.close(closeOptions),
|
|
3246
|
+
refresh: () => menu.refresh(),
|
|
3247
|
+
setGroups: (groups) => {
|
|
3248
|
+
if (!ledger.isClosed) menu.setGroups(groups);
|
|
3249
|
+
}
|
|
3250
|
+
};
|
|
3251
|
+
const tooltipHandle = {
|
|
3252
|
+
attach: (control, content) => ledger.isClosed ? () => {
|
|
3253
|
+
} : tooltip.attach(control, content),
|
|
3254
|
+
hide: () => tooltip.hide()
|
|
3255
|
+
};
|
|
3256
|
+
const legendHandle = legend && {
|
|
3257
|
+
element: legend.element,
|
|
3258
|
+
body: legend.body,
|
|
3259
|
+
isOpen: () => legend.isOpen(),
|
|
3260
|
+
setOpen: (open) => {
|
|
3261
|
+
if (!ledger.isClosed) legend.setOpen(open);
|
|
3262
|
+
},
|
|
3263
|
+
toggle: () => {
|
|
3264
|
+
if (!ledger.isClosed) legend.toggle();
|
|
3265
|
+
}
|
|
3266
|
+
};
|
|
3267
|
+
const primaryHandle = primary && {
|
|
3268
|
+
element: primary.element,
|
|
3269
|
+
isCollapsed: () => primary.isCollapsed(),
|
|
3270
|
+
refresh: () => {
|
|
3271
|
+
if (!ledger.isClosed) primary.refresh();
|
|
3272
|
+
}
|
|
3273
|
+
};
|
|
3274
|
+
const appearanceHandle = appearance && darkCapable ? {
|
|
3275
|
+
choice: () => appearance.choice(),
|
|
3276
|
+
setChoice: (choice) => {
|
|
3277
|
+
if (ledger.isClosed) return;
|
|
3278
|
+
appearance.setChoice(choice);
|
|
3279
|
+
menu.refresh();
|
|
3280
|
+
}
|
|
3281
|
+
} : void 0;
|
|
3282
|
+
const inspectorHandle = inspector && {
|
|
3283
|
+
element: inspector.element,
|
|
3284
|
+
isOpen: () => inspector.isOpen(),
|
|
3285
|
+
open: () => {
|
|
3286
|
+
if (!ledger.isClosed) inspector.open();
|
|
3287
|
+
},
|
|
3288
|
+
close: () => {
|
|
3289
|
+
if (ledger.isClosed) return;
|
|
3290
|
+
if (inspector.isOpen()) moveFocusOutOf(inspector.element);
|
|
3291
|
+
inspector.close();
|
|
3292
|
+
}
|
|
3293
|
+
};
|
|
3294
|
+
return {
|
|
3295
|
+
root,
|
|
3296
|
+
canvas,
|
|
3297
|
+
host,
|
|
3298
|
+
lang,
|
|
3299
|
+
platform,
|
|
3300
|
+
messages,
|
|
3301
|
+
menu: menuHandle,
|
|
3302
|
+
tooltip: tooltipHandle,
|
|
3303
|
+
legend: legendHandle,
|
|
3304
|
+
inspector: inspectorHandle,
|
|
3305
|
+
primary: primaryHandle,
|
|
3306
|
+
appearance: appearanceHandle,
|
|
3307
|
+
setZoomTarget(target) {
|
|
3308
|
+
if (!ledger.isClosed) zoomPanel.setTarget(target);
|
|
3309
|
+
},
|
|
3310
|
+
fitInsets: currentFitInsets,
|
|
3311
|
+
registerPalette(registration) {
|
|
3312
|
+
if (ledger.isClosed) return () => {
|
|
3313
|
+
};
|
|
3314
|
+
unregisterPalette();
|
|
3315
|
+
palette = {
|
|
3316
|
+
registration,
|
|
3317
|
+
detachTooltip: tooltip.delegate(
|
|
3318
|
+
registration.element,
|
|
3319
|
+
".entry",
|
|
3320
|
+
(entry) => registration.entryTooltip(entry)
|
|
3321
|
+
)
|
|
3322
|
+
};
|
|
3323
|
+
resizeObserver?.observe(registration.element);
|
|
3324
|
+
registration.setReadOnly(readOnlyState());
|
|
3325
|
+
updateLayout();
|
|
3326
|
+
const registered = palette;
|
|
3327
|
+
return () => {
|
|
3328
|
+
if (palette !== registered) return;
|
|
3329
|
+
unregisterPalette();
|
|
3330
|
+
updateLayout();
|
|
3331
|
+
};
|
|
3332
|
+
},
|
|
3333
|
+
setProblems(summary) {
|
|
3334
|
+
if (ledger.isClosed || !pill) return;
|
|
3335
|
+
const listHadFocus = popover?.element.contains(ownerDocument.activeElement) ?? false;
|
|
3336
|
+
pill.set(summary);
|
|
3337
|
+
setState("data-problems", pill.isShown() ? "shown" : null);
|
|
3338
|
+
popover?.setItems(summary.items ?? []);
|
|
3339
|
+
if (!pill.isShown() && popover?.isOpen()) {
|
|
3340
|
+
popover.close();
|
|
3341
|
+
if (listHadFocus) focusCanvasOrTrigger();
|
|
3342
|
+
}
|
|
3343
|
+
updateBottomRow();
|
|
3344
|
+
placeHint();
|
|
3345
|
+
},
|
|
3346
|
+
setStatus(next, statusOptions) {
|
|
3347
|
+
if (!ledger.isClosed) status.set(next, statusOptions?.delayMs ?? 0);
|
|
3348
|
+
},
|
|
3349
|
+
setEmptyHint(visible) {
|
|
3350
|
+
if (ledger.isClosed || !hint) return;
|
|
3351
|
+
if (!visible && hint.isShown()) moveFocusOutOf(hint.element);
|
|
3352
|
+
hint.setShown(visible);
|
|
3353
|
+
placeHint();
|
|
3354
|
+
},
|
|
3355
|
+
setReadOnly(next, readOnlyOptions) {
|
|
3356
|
+
if (ledger.isClosed) return;
|
|
3357
|
+
if (!next) {
|
|
3358
|
+
clearTimeout(readOnlyTimer);
|
|
3359
|
+
readOnlyTimer = void 0;
|
|
3360
|
+
readOnly = false;
|
|
3361
|
+
readOnlyShown = false;
|
|
3362
|
+
} else {
|
|
3363
|
+
readOnly = true;
|
|
3364
|
+
const delayMs = readOnlyOptions?.delayMs ?? 0;
|
|
3365
|
+
if (!readOnlyShown && readOnlyTimer === void 0) {
|
|
3366
|
+
if (delayMs > 0) readOnlyTimer = setTimeout(showReadOnly, delayMs);
|
|
3367
|
+
else readOnlyShown = true;
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
applyReadOnly();
|
|
3371
|
+
},
|
|
3372
|
+
isReadOnly: () => readOnly,
|
|
3373
|
+
setEmptyState: setEmptyStateShown,
|
|
3374
|
+
isEmptyStateShown: () => emptyState?.isShown() ?? false,
|
|
3375
|
+
toast: showToast,
|
|
3376
|
+
destroy() {
|
|
3377
|
+
ledger.close();
|
|
3378
|
+
}
|
|
3379
|
+
};
|
|
3380
|
+
}
|
|
3381
|
+
function carryOwnMode(root, theme, ledger) {
|
|
3382
|
+
ledger.attribute(root, "data-cd-theme", theme);
|
|
3383
|
+
const mirror = () => {
|
|
3384
|
+
if (ledger.isClosed) return;
|
|
3385
|
+
const kontrast = hostKontrast(root);
|
|
3386
|
+
if (root.getAttribute("data-cd-kontrast") !== kontrast)
|
|
3387
|
+
ledger.attribute(root, "data-cd-kontrast", kontrast);
|
|
3388
|
+
};
|
|
3389
|
+
mirror();
|
|
3390
|
+
const MutationObserverClass = globalThis.MutationObserver;
|
|
3391
|
+
if (!MutationObserverClass) return;
|
|
3392
|
+
const observer = new MutationObserverClass(mirror);
|
|
3393
|
+
observer.observe(root.ownerDocument.documentElement, {
|
|
3394
|
+
attributes: true,
|
|
3395
|
+
attributeFilter: ["data-cd-theme", "data-cd-kontrast"],
|
|
3396
|
+
subtree: true
|
|
3397
|
+
});
|
|
3398
|
+
ledger.defer(() => observer.disconnect());
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
// src/vscode/webview/mount.ts
|
|
3402
|
+
function withDestroyHook(shell, beforeDestroy) {
|
|
3403
|
+
return {
|
|
3404
|
+
get root() {
|
|
3405
|
+
return shell.root;
|
|
3406
|
+
},
|
|
3407
|
+
get canvas() {
|
|
3408
|
+
return shell.canvas;
|
|
3409
|
+
},
|
|
3410
|
+
get host() {
|
|
3411
|
+
return shell.host;
|
|
3412
|
+
},
|
|
3413
|
+
get lang() {
|
|
3414
|
+
return shell.lang;
|
|
3415
|
+
},
|
|
3416
|
+
get platform() {
|
|
3417
|
+
return shell.platform;
|
|
3418
|
+
},
|
|
3419
|
+
get messages() {
|
|
3420
|
+
return shell.messages;
|
|
3421
|
+
},
|
|
3422
|
+
get menu() {
|
|
3423
|
+
return shell.menu;
|
|
3424
|
+
},
|
|
3425
|
+
get tooltip() {
|
|
3426
|
+
return shell.tooltip;
|
|
3427
|
+
},
|
|
3428
|
+
get legend() {
|
|
3429
|
+
return shell.legend;
|
|
3430
|
+
},
|
|
3431
|
+
get inspector() {
|
|
3432
|
+
return shell.inspector;
|
|
3433
|
+
},
|
|
3434
|
+
get primary() {
|
|
3435
|
+
return shell.primary;
|
|
3436
|
+
},
|
|
3437
|
+
get appearance() {
|
|
3438
|
+
return shell.appearance;
|
|
3439
|
+
},
|
|
3440
|
+
setZoomTarget: (target) => shell.setZoomTarget(target),
|
|
3441
|
+
fitInsets: () => shell.fitInsets(),
|
|
3442
|
+
registerPalette: (palette) => shell.registerPalette(palette),
|
|
3443
|
+
setProblems: (summary) => shell.setProblems(summary),
|
|
3444
|
+
setStatus: (status, options) => shell.setStatus(status, options),
|
|
3445
|
+
setEmptyHint: (visible) => shell.setEmptyHint(visible),
|
|
3446
|
+
setReadOnly: (readOnly, options) => shell.setReadOnly(readOnly, options),
|
|
3447
|
+
isReadOnly: () => shell.isReadOnly(),
|
|
3448
|
+
setEmptyState: (shown) => shell.setEmptyState(shown),
|
|
3449
|
+
isEmptyStateShown: () => shell.isEmptyStateShown(),
|
|
3450
|
+
toast: (request) => shell.toast(request),
|
|
3451
|
+
destroy() {
|
|
3452
|
+
beforeDestroy();
|
|
3453
|
+
shell.destroy();
|
|
3454
|
+
}
|
|
3455
|
+
};
|
|
3456
|
+
}
|
|
3457
|
+
function mountVsCodeShell(root, options) {
|
|
3458
|
+
const { themes } = options;
|
|
3459
|
+
const disconnectAppearance = connectAppearance({ themes });
|
|
3460
|
+
const disconnectReducedMotion = connectReducedMotion();
|
|
3461
|
+
const disconnect = () => {
|
|
3462
|
+
disconnectAppearance();
|
|
3463
|
+
disconnectReducedMotion();
|
|
3464
|
+
};
|
|
3465
|
+
let shell;
|
|
3466
|
+
try {
|
|
3467
|
+
shell = mountShell(root, { ...options, host: "ide", keyScope: "document" });
|
|
3468
|
+
} catch (error) {
|
|
3469
|
+
disconnect();
|
|
3470
|
+
throw error;
|
|
3471
|
+
}
|
|
3472
|
+
return withDestroyHook(shell, disconnect);
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3475
|
+
// src/vscode/webview/state.ts
|
|
3476
|
+
var SHELL_STATE_KEY = "modelerShell";
|
|
3477
|
+
function isRecord2(value) {
|
|
3478
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3479
|
+
}
|
|
3480
|
+
function storedState(api) {
|
|
3481
|
+
const state = api.getState();
|
|
3482
|
+
return isRecord2(state) ? state : {};
|
|
3483
|
+
}
|
|
3484
|
+
function viewboxFrom(value) {
|
|
3485
|
+
if (!isRecord2(value)) return void 0;
|
|
3486
|
+
const { x, y, width, height } = value;
|
|
3487
|
+
if (typeof x !== "number" || typeof y !== "number") return void 0;
|
|
3488
|
+
if (typeof width !== "number" || typeof height !== "number") return void 0;
|
|
3489
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(width) || !Number.isFinite(height)) {
|
|
3490
|
+
return void 0;
|
|
3491
|
+
}
|
|
3492
|
+
return width > 0 && height > 0 ? { x, y, width, height } : void 0;
|
|
3493
|
+
}
|
|
3494
|
+
function viewFrom(value) {
|
|
3495
|
+
if (!isRecord2(value) || typeof value.userMoved !== "boolean") return void 0;
|
|
3496
|
+
const viewbox = viewboxFrom(value.viewbox);
|
|
3497
|
+
return viewbox ? { viewbox, userMoved: value.userMoved } : void 0;
|
|
3498
|
+
}
|
|
3499
|
+
function readShellState(api) {
|
|
3500
|
+
const part = storedState(api)[SHELL_STATE_KEY];
|
|
3501
|
+
if (!isRecord2(part)) return {};
|
|
3502
|
+
const state = {};
|
|
3503
|
+
if (typeof part.legendOpen === "boolean") state.legendOpen = part.legendOpen;
|
|
3504
|
+
const view = viewFrom(part.view);
|
|
3505
|
+
if (view) state.view = view;
|
|
3506
|
+
return state;
|
|
3507
|
+
}
|
|
3508
|
+
function writeShellState(api, patch) {
|
|
3509
|
+
const state = storedState(api);
|
|
3510
|
+
const part = isRecord2(state[SHELL_STATE_KEY]) ? state[SHELL_STATE_KEY] : {};
|
|
3511
|
+
const next = { ...part };
|
|
3512
|
+
if (typeof patch.legendOpen === "boolean") next.legendOpen = patch.legendOpen;
|
|
3513
|
+
const view = viewFrom(patch.view);
|
|
3514
|
+
if (view) next.view = view;
|
|
3515
|
+
if (JSON.stringify(next) === JSON.stringify(state[SHELL_STATE_KEY])) return;
|
|
3516
|
+
api.setState({ ...state, [SHELL_STATE_KEY]: next });
|
|
3517
|
+
}
|
|
3518
|
+
var PERSIST_VIEW_DELAY_MS = 250;
|
|
3519
|
+
function persistView(api, viewport, options = {}) {
|
|
3520
|
+
const delayMs = options.delayMs ?? PERSIST_VIEW_DELAY_MS;
|
|
3521
|
+
let timer;
|
|
3522
|
+
const write = () => {
|
|
3523
|
+
timer = void 0;
|
|
3524
|
+
writeShellState(api, { view: viewport.getState() });
|
|
3525
|
+
};
|
|
3526
|
+
const unsubscribe = viewport.onChange(() => {
|
|
3527
|
+
clearTimeout(timer);
|
|
3528
|
+
timer = setTimeout(write, delayMs);
|
|
3529
|
+
});
|
|
3530
|
+
let stopped = false;
|
|
3531
|
+
return () => {
|
|
3532
|
+
if (stopped) return;
|
|
3533
|
+
stopped = true;
|
|
3534
|
+
unsubscribe();
|
|
3535
|
+
if (timer === void 0) return;
|
|
3536
|
+
clearTimeout(timer);
|
|
3537
|
+
write();
|
|
3538
|
+
};
|
|
3539
|
+
}
|
|
3540
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3541
|
+
0 && (module.exports = {
|
|
3542
|
+
APPEARANCE_MESSAGE_TYPE,
|
|
3543
|
+
SHELL_STATE_KEY,
|
|
3544
|
+
appearanceMessage,
|
|
3545
|
+
applyAppearance,
|
|
3546
|
+
connectAppearance,
|
|
3547
|
+
connectReducedMotion,
|
|
3548
|
+
isAppearanceMessage,
|
|
3549
|
+
mountVsCodeShell,
|
|
3550
|
+
persistView,
|
|
3551
|
+
readShellState,
|
|
3552
|
+
writeShellState
|
|
3553
|
+
});
|
|
3554
|
+
/*! @miragon/modeler-shell icon geometry
|
|
3555
|
+
* Lucide (lucide-static 1.59.0) @license ISC
|
|
3556
|
+
* Copyright (c) 2026 Lucide Icons and Contributors
|
|
3557
|
+
* Derived from Feather, as Lucide's LICENSE lists them: check, circle-x (x-circle), maximize,
|
|
3558
|
+
* minus, plus, triangle-alert (alert-triangle) and x. Feather @license MIT
|
|
3559
|
+
* Copyright (c) 2013-present Cole Bemis
|
|
3560
|
+
* Full notices: THIRD_PARTY_NOTICES.md of @miragon/modeler-shell */
|
|
3561
|
+
//# sourceMappingURL=vscode-webview.cjs.map
|