@miragon/modeler-shell 0.1.0 → 0.2.1
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 +1051 -84
- package/README.md +162 -31
- package/dist/appearance-BBaM_4bD.d.ts +323 -0
- package/dist/appearance-XX3JC3pI.d.cts +323 -0
- package/dist/{chunk-6LQOFACK.js → chunk-A4YGKH4Y.js} +1252 -31
- package/dist/chunk-A4YGKH4Y.js.map +1 -0
- package/dist/{chunk-RJRFR6N3.js → chunk-KYDG5DMX.js} +2 -2
- package/dist/{chunk-5JSZ4VGA.js → chunk-OADW3GPA.js} +240 -6
- package/dist/chunk-OADW3GPA.js.map +1 -0
- package/dist/core.cjs +260 -6
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +81 -6
- package/dist/core.d.ts +81 -6
- package/dist/core.js +43 -3
- package/dist/diagram-js.cjs +34 -2
- package/dist/diagram-js.cjs.map +1 -1
- package/dist/diagram-js.d.cts +10 -3
- package/dist/diagram-js.d.ts +10 -3
- package/dist/diagram-js.js +14 -2
- package/dist/diagram-js.js.map +1 -1
- package/dist/index.cjs +1658 -196
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -9
- package/dist/index.d.ts +18 -9
- package/dist/index.js +44 -4
- package/dist/shell.css +692 -3
- package/dist/testing-vscode-stub.js +2 -2
- package/dist/testing.cjs +2849 -550
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +75 -9
- package/dist/testing.d.ts +75 -9
- package/dist/testing.js +2744 -558
- package/dist/testing.js.map +1 -1
- package/dist/toasts-Bp0REqdq.d.cts +142 -0
- package/dist/toasts-Bp0REqdq.d.ts +142 -0
- package/dist/{types-OuFlqZp0.d.cts → types-CnRrINRs.d.cts} +170 -9
- package/dist/{types-DnZn9CPw.d.ts → types-PuDZczzy.d.ts} +170 -9
- package/dist/vscode-extension.js +2 -2
- package/dist/vscode-webview.cjs +1546 -178
- package/dist/vscode-webview.cjs.map +1 -1
- package/dist/vscode-webview.d.cts +6 -4
- package/dist/vscode-webview.d.ts +6 -4
- package/dist/vscode-webview.js +11 -2
- package/dist/vscode-webview.js.map +1 -1
- package/package.json +2 -2
- 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 +18 -3
- package/src/core/index.ts +43 -0
- package/src/core/keymap.ts +20 -0
- package/src/core/messages.ts +71 -0
- package/src/core/types.ts +24 -2
- 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/diagram-js/service.ts +4 -1
- package/src/diagram-js/viewport.ts +19 -0
- package/src/dom/buttons.css +124 -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/index.ts +13 -0
- package/src/dom/keys.ts +8 -0
- package/src/dom/menu.ts +28 -17
- package/src/dom/mount.ts +290 -20
- package/src/dom/problems.ts +5 -4
- package/src/dom/shell-dom.css +7 -4
- package/src/dom/tooltip.ts +10 -0
- package/src/dom/types.ts +188 -6
- 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/shell.css +3 -0
- package/src/testing/case-tools.ts +164 -0
- package/src/testing/cases.ts +209 -144
- package/src/testing/checks.ts +51 -15
- package/src/testing/facts.ts +56 -2
- package/src/testing/geometry.ts +206 -26
- package/src/testing/harness.ts +56 -7
- package/src/testing/index.ts +4 -0
- package/src/testing/page-tools.ts +24 -1
- package/src/testing/page.ts +10 -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/vscode/webview/mount.ts +13 -1
- package/dist/chunk-5JSZ4VGA.js.map +0 -1
- package/dist/chunk-6LQOFACK.js.map +0 -1
- package/dist/keymap-CObQE6q6.d.cts +0 -151
- package/dist/keymap-DORzHnK-.d.ts +0 -151
- package/dist/types-B6NfkiUG.d.cts +0 -66
- package/dist/types-B6NfkiUG.d.ts +0 -66
- /package/dist/{chunk-RJRFR6N3.js.map → chunk-KYDG5DMX.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -20,21 +20,27 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
|
+
APPEARANCE_STORAGE_KEY: () => APPEARANCE_STORAGE_KEY,
|
|
23
24
|
CHROME_ISLANDS: () => CHROME_ISLANDS,
|
|
24
25
|
FIT_ANIMATION_MS: () => FIT_ANIMATION_MS,
|
|
25
26
|
ICONS: () => ICONS,
|
|
26
27
|
INVALID_STATUS_DELAY_MS: () => INVALID_STATUS_DELAY_MS,
|
|
28
|
+
LEGAL_LINE_HEIGHT: () => LEGAL_LINE_HEIGHT,
|
|
27
29
|
MAX_FIT_ZOOM: () => MAX_FIT_ZOOM,
|
|
30
|
+
MAX_TOASTS: () => MAX_TOASTS,
|
|
28
31
|
PALETTE_CLEARANCE: () => PALETTE_CLEARANCE,
|
|
29
32
|
PALETTE_TOOL_ICON: () => PALETTE_TOOL_ICON,
|
|
30
33
|
REDUCED_MOTION_CLASS: () => REDUCED_MOTION_CLASS,
|
|
31
34
|
SHELL: () => SHELL,
|
|
32
35
|
SHELL_TOKENS: () => SHELL_TOKENS,
|
|
33
36
|
TEXT_FIELD_SELECTOR: () => TEXT_FIELD_SELECTOR,
|
|
37
|
+
TOAST_DURATION_MS: () => TOAST_DURATION_MS,
|
|
38
|
+
TOAST_MAX_WIDTH: () => TOAST_MAX_WIDTH,
|
|
34
39
|
TOOLTIP_DELAY_MS: () => TOOLTIP_DELAY_MS,
|
|
35
40
|
VIEW_TOLERANCE: () => VIEW_TOLERANCE,
|
|
36
41
|
ZOOM_MAX: () => ZOOM_MAX,
|
|
37
42
|
ZOOM_MIN: () => ZOOM_MIN,
|
|
43
|
+
acceptsFile: () => acceptsFile,
|
|
38
44
|
appearanceAttributes: () => appearanceAttributes,
|
|
39
45
|
appearanceFromBodyClasses: () => appearanceFromBodyClasses,
|
|
40
46
|
appearanceFromColorThemeKind: () => appearanceFromColorThemeKind,
|
|
@@ -42,10 +48,14 @@ __export(src_exports, {
|
|
|
42
48
|
ariaKeyShortcutsFromLabel: () => ariaKeyShortcutsFromLabel,
|
|
43
49
|
canZoomIn: () => canZoomIn,
|
|
44
50
|
canZoomOut: () => canZoomOut,
|
|
51
|
+
commandKeyLabel: () => commandKeyLabel,
|
|
45
52
|
decideKey: () => decideKey,
|
|
46
53
|
decidePaletteRow: () => decidePaletteRow,
|
|
54
|
+
decideTopRow: () => decideTopRow,
|
|
47
55
|
defaultMessages: () => defaultMessages,
|
|
48
56
|
detectPlatform: () => detectPlatform,
|
|
57
|
+
documentMode: () => documentMode,
|
|
58
|
+
dragVerdict: () => dragVerdict,
|
|
49
59
|
easeStandard: () => easeStandard,
|
|
50
60
|
fitInsets: () => fitInsets,
|
|
51
61
|
fitViewbox: () => fitViewbox,
|
|
@@ -56,10 +66,13 @@ __export(src_exports, {
|
|
|
56
66
|
iconMarkup: () => iconMarkup,
|
|
57
67
|
interpolateViewbox: () => interpolateViewbox,
|
|
58
68
|
isUndoRedoKey: () => isUndoRedoKey,
|
|
69
|
+
legalLineHeight: () => legalLineHeight,
|
|
59
70
|
legendStartsOpen: () => legendStartsOpen,
|
|
60
71
|
matchChromeKey: () => matchChromeKey,
|
|
61
72
|
mountShell: () => mountShell,
|
|
73
|
+
parseAppearanceChoice: () => parseAppearanceChoice,
|
|
62
74
|
placeTooltip: () => placeTooltip,
|
|
75
|
+
problemSummary: () => problemSummary,
|
|
63
76
|
reducedMotionFromBodyClasses: () => reducedMotionFromBodyClasses,
|
|
64
77
|
requestedAppearance: () => requestedAppearance,
|
|
65
78
|
resolveAppearance: () => resolveAppearance,
|
|
@@ -67,7 +80,14 @@ __export(src_exports, {
|
|
|
67
80
|
sameView: () => sameView,
|
|
68
81
|
shellSizesFromTokens: () => shellSizesFromTokens,
|
|
69
82
|
shortcutLabel: () => shortcutLabel,
|
|
70
|
-
|
|
83
|
+
toastLifetime: () => toastLifetime,
|
|
84
|
+
toastStackBottom: () => toastStackBottom,
|
|
85
|
+
toastStackWidth: () => toastStackWidth,
|
|
86
|
+
toastToEvict: () => toastToEvict,
|
|
87
|
+
webHeadMarkup: () => webHeadMarkup,
|
|
88
|
+
webModeScript: () => webModeScript,
|
|
89
|
+
zoomReadout: () => zoomReadout,
|
|
90
|
+
zoomRowRaised: () => zoomRowRaised
|
|
71
91
|
});
|
|
72
92
|
module.exports = __toCommonJS(src_exports);
|
|
73
93
|
|
|
@@ -80,7 +100,12 @@ var CHROME_ISLANDS = [
|
|
|
80
100
|
"problems",
|
|
81
101
|
"legend",
|
|
82
102
|
"inspector",
|
|
83
|
-
"status"
|
|
103
|
+
"status",
|
|
104
|
+
"legal",
|
|
105
|
+
"toasts",
|
|
106
|
+
"empty-state",
|
|
107
|
+
"drop-zone",
|
|
108
|
+
"problems-popover"
|
|
84
109
|
];
|
|
85
110
|
|
|
86
111
|
// src/core/sizes.ts
|
|
@@ -151,6 +176,19 @@ function shellSizesFromTokens(shellGroup) {
|
|
|
151
176
|
return sizes;
|
|
152
177
|
}
|
|
153
178
|
|
|
179
|
+
// src/core/web-chrome/bottom-row.ts
|
|
180
|
+
var LEGAL_LINE_HEIGHT = 20;
|
|
181
|
+
function legalLineHeight(measured) {
|
|
182
|
+
if (measured === void 0 || !Number.isFinite(measured) || measured <= 0) return LEGAL_LINE_HEIGHT;
|
|
183
|
+
return Math.max(1, Math.round(measured / LEGAL_LINE_HEIGHT)) * LEGAL_LINE_HEIGHT;
|
|
184
|
+
}
|
|
185
|
+
function zoomRowRaised(input) {
|
|
186
|
+
const { containerWidth, legalRight, zoomWidth } = input;
|
|
187
|
+
if (legalRight === void 0 || zoomWidth === void 0) return false;
|
|
188
|
+
const zoomLeft = containerWidth - SHELL.edge - zoomWidth;
|
|
189
|
+
return legalRight + SHELL.gap > zoomLeft;
|
|
190
|
+
}
|
|
191
|
+
|
|
154
192
|
// src/core/fit.ts
|
|
155
193
|
var MAX_FIT_ZOOM = 1;
|
|
156
194
|
var FIT_ANIMATION_MS = 250;
|
|
@@ -191,11 +229,13 @@ function freeRectCenter(container, insets) {
|
|
|
191
229
|
};
|
|
192
230
|
}
|
|
193
231
|
function fitInsets(layout) {
|
|
194
|
-
const { container, host, paletteRow, problemsVisible, legendWidth, inspector } = layout;
|
|
232
|
+
const { container, host, paletteRow, problemsVisible, zoomRaised, legendWidth, inspector } = layout;
|
|
233
|
+
const raisedBottomRow = host === "web" && (problemsVisible || zoomRaised === true);
|
|
234
|
+
const wrappedLegalLines = legalLineHeight(layout.legalHeight) - LEGAL_LINE_HEIGHT;
|
|
195
235
|
const insets = {
|
|
196
236
|
top: paletteRow === "second-row" ? SHELL.fitInsetTopSecondRow : SHELL.fitInsetTop,
|
|
197
237
|
right: SHELL.fitInsetSide,
|
|
198
|
-
bottom:
|
|
238
|
+
bottom: raisedBottomRow ? SHELL.fitInsetBottomWeb + wrappedLegalLines : SHELL.fitInsetBottom,
|
|
199
239
|
left: SHELL.fitInsetSide
|
|
200
240
|
};
|
|
201
241
|
const bottomSheetOpen = inspector !== void 0 && container.width <= SHELL.narrow;
|
|
@@ -349,6 +389,7 @@ function decideKey(event, context) {
|
|
|
349
389
|
}
|
|
350
390
|
const chromeKey = matchChromeKey(event);
|
|
351
391
|
if (chromeKey !== void 0) {
|
|
392
|
+
if (context.startScreen === true && !stop) return { preventDefault: false, stopPropagation: false };
|
|
352
393
|
const blocked = composing || context.inTextField || context.modalOpen;
|
|
353
394
|
if (!blocked) return { action: chromeKey, preventDefault: true, stopPropagation: stop };
|
|
354
395
|
return { preventDefault: false, stopPropagation: chromeKey === "fit" ? false : stop };
|
|
@@ -374,6 +415,9 @@ function shortcutLabel(key, platform, keyNames) {
|
|
|
374
415
|
return `${keyNames.shift}+1`;
|
|
375
416
|
}
|
|
376
417
|
}
|
|
418
|
+
function commandKeyLabel(key, platform, keyNames) {
|
|
419
|
+
return platform === "mac" ? `\u2318${key}` : `${keyNames.ctrl}+${key}`;
|
|
420
|
+
}
|
|
377
421
|
var MAC_SHORTCUT_LABELS = {
|
|
378
422
|
zoomIn: "\u2318+",
|
|
379
423
|
zoomOut: "\u2318\u2212",
|
|
@@ -536,7 +580,27 @@ var defaultMessages = {
|
|
|
536
580
|
emptyHint: "Pick a tool from the palette to start.",
|
|
537
581
|
insertExample: "Insert example",
|
|
538
582
|
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.`,
|
|
539
|
-
keyNames: { ctrl: "Ctrl", shift: "Shift", plus: "Plus", minus: "Minus" }
|
|
583
|
+
keyNames: { ctrl: "Ctrl", shift: "Shift", plus: "Plus", minus: "Minus" },
|
|
584
|
+
share: "Share",
|
|
585
|
+
legal: "Legal",
|
|
586
|
+
copyright: (year) => `\xA9 ${year} Miragon GmbH`,
|
|
587
|
+
opensInNewTab: "Opens in a new tab",
|
|
588
|
+
newDiagram: "New diagram",
|
|
589
|
+
openFile: "Open\u2026",
|
|
590
|
+
showExample: "Show example",
|
|
591
|
+
importFile: "Import\u2026",
|
|
592
|
+
appearance: "Appearance",
|
|
593
|
+
appearanceSystem: "System",
|
|
594
|
+
appearanceLight: "Light",
|
|
595
|
+
appearanceDark: "Dark",
|
|
596
|
+
problemsTitle: "Problems",
|
|
597
|
+
problemSeverity: (severity) => severity === "error" ? "Error" : "Warning",
|
|
598
|
+
close: "Close",
|
|
599
|
+
dropFile: "Drop file here",
|
|
600
|
+
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",
|
|
601
|
+
dropHint: "Or drop a file here",
|
|
602
|
+
openFileLink: "Open a file\u2026",
|
|
603
|
+
cannotOpenFile: (name, accepted) => `Couldn't open \u201C${name}\u201D. Drop a ${accepted} file.`
|
|
540
604
|
};
|
|
541
605
|
function resolveMessages(overrides) {
|
|
542
606
|
const { keyNames, ...texts } = overrides ?? {};
|
|
@@ -576,6 +640,176 @@ function placeTooltip(input) {
|
|
|
576
640
|
return { x, y };
|
|
577
641
|
}
|
|
578
642
|
|
|
643
|
+
// src/core/web-chrome/top-row.ts
|
|
644
|
+
function decideTopRow(input) {
|
|
645
|
+
const { containerWidth, paletteWidth, menuRight, primary } = input;
|
|
646
|
+
if (!primary) {
|
|
647
|
+
return {
|
|
648
|
+
paletteRow: decidePaletteRow({ containerWidth, paletteWidth, menuRight }),
|
|
649
|
+
primaryLabel: "shown"
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
const paletteRowBeside = (primaryWidth) => decidePaletteRow({
|
|
653
|
+
containerWidth,
|
|
654
|
+
paletteWidth,
|
|
655
|
+
menuRight,
|
|
656
|
+
primaryLeft: containerWidth - SHELL.edge - primaryWidth
|
|
657
|
+
});
|
|
658
|
+
if (paletteRowBeside(primary.withLabel) === "top") return { paletteRow: "top", primaryLabel: "shown" };
|
|
659
|
+
if (paletteRowBeside(primary.iconOnly) === "top") return { paletteRow: "top", primaryLabel: "collapsed" };
|
|
660
|
+
const labelledLeft = containerWidth - SHELL.edge - primary.withLabel;
|
|
661
|
+
return {
|
|
662
|
+
paletteRow: "second-row",
|
|
663
|
+
primaryLabel: menuRight + PALETTE_CLEARANCE <= labelledLeft ? "shown" : "collapsed"
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// src/core/web-chrome/drop.ts
|
|
668
|
+
function acceptEntries(accept) {
|
|
669
|
+
const entries = accept.map((entry) => entry.trim().toLowerCase()).filter((entry) => entry !== "");
|
|
670
|
+
return {
|
|
671
|
+
extensions: entries.filter((entry) => entry.startsWith(".")),
|
|
672
|
+
mimeTypes: entries.filter((entry) => !entry.startsWith(".") && entry.includes("/"))
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
function matchesMimeType(type, mimeTypes) {
|
|
676
|
+
const normalized = type.trim().toLowerCase();
|
|
677
|
+
if (normalized === "") return false;
|
|
678
|
+
return mimeTypes.some(
|
|
679
|
+
(entry) => entry.endsWith("/*") ? normalized.startsWith(entry.slice(0, -1)) : normalized === entry
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
function dragVerdict(items, context) {
|
|
683
|
+
const files = items.filter((item) => item.kind === "file");
|
|
684
|
+
if (files.length === 0) return { state: "none" };
|
|
685
|
+
if (context.dialogOpen) return { state: "forbidden", reason: "dialog" };
|
|
686
|
+
if (files.length > 1 && !context.multiple) return { state: "forbidden", reason: "multiple" };
|
|
687
|
+
const { mimeTypes } = acceptEntries(context.accept);
|
|
688
|
+
const foreignType = (item) => item.type.trim() !== "" && !matchesMimeType(item.type, mimeTypes);
|
|
689
|
+
if (mimeTypes.length > 0 && files.every(foreignType)) return { state: "forbidden", reason: "type" };
|
|
690
|
+
return { state: "over" };
|
|
691
|
+
}
|
|
692
|
+
function acceptsFile(file, accept) {
|
|
693
|
+
const { extensions, mimeTypes } = acceptEntries(accept);
|
|
694
|
+
if (extensions.length === 0 && mimeTypes.length === 0) return true;
|
|
695
|
+
const name = file.name.toLowerCase();
|
|
696
|
+
return extensions.some((extension) => name.endsWith(extension)) || matchesMimeType(file.type, mimeTypes);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// src/core/feedback/toasts.ts
|
|
700
|
+
var MAX_TOASTS = 3;
|
|
701
|
+
var TOAST_DURATION_MS = {
|
|
702
|
+
success: 2800,
|
|
703
|
+
info: 2800,
|
|
704
|
+
warning: 5e3,
|
|
705
|
+
danger: 5e3
|
|
706
|
+
};
|
|
707
|
+
var TOAST_MAX_WIDTH = 480;
|
|
708
|
+
function toastLifetime(request) {
|
|
709
|
+
if (request.action) return void 0;
|
|
710
|
+
const requested = request.durationMs;
|
|
711
|
+
const longer = requested !== void 0 && Number.isFinite(requested) ? requested : 0;
|
|
712
|
+
return Math.max(TOAST_DURATION_MS[request.status], longer);
|
|
713
|
+
}
|
|
714
|
+
function toastToEvict(stack) {
|
|
715
|
+
if (stack.length < MAX_TOASTS) return void 0;
|
|
716
|
+
const evicted = stack.find((slot) => !slot.engaged && !slot.hasAction) ?? stack.find((slot) => !slot.engaged) ?? stack[0];
|
|
717
|
+
return evicted?.id;
|
|
718
|
+
}
|
|
719
|
+
function toastStackWidth(containerWidth) {
|
|
720
|
+
return Math.max(0, Math.min(TOAST_MAX_WIDTH, containerWidth - 2 * SHELL.edge));
|
|
721
|
+
}
|
|
722
|
+
function toastStackBottom(input) {
|
|
723
|
+
const stackWidth = toastStackWidth(input.containerWidth);
|
|
724
|
+
const { cardWidth } = input;
|
|
725
|
+
const width = cardWidth !== void 0 && Number.isFinite(cardWidth) ? Math.min(stackWidth, Math.max(0, cardWidth)) : stackWidth;
|
|
726
|
+
const left = (input.containerWidth - width) / 2;
|
|
727
|
+
const right = left + width;
|
|
728
|
+
let highest;
|
|
729
|
+
for (const obstacle of input.obstacles) {
|
|
730
|
+
const distance = Math.max(obstacle.left - right, left - obstacle.right);
|
|
731
|
+
if (distance >= SHELL.gap) continue;
|
|
732
|
+
highest = highest === void 0 ? obstacle.top : Math.max(highest, obstacle.top);
|
|
733
|
+
}
|
|
734
|
+
return highest === void 0 ? SHELL.edge : Math.max(SHELL.edge, highest + SHELL.gap);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// src/core/feedback/appearance.ts
|
|
738
|
+
var APPEARANCE_STORAGE_KEY = "miragon.darstellung";
|
|
739
|
+
function parseAppearanceChoice(stored) {
|
|
740
|
+
return stored === "light" || stored === "dark" ? stored : "system";
|
|
741
|
+
}
|
|
742
|
+
function documentMode(choice, themes, colors) {
|
|
743
|
+
if (!themes.includes("dark")) return fixedMode("light", colors.light);
|
|
744
|
+
const parsed = parseAppearanceChoice(choice);
|
|
745
|
+
if (parsed === "system") {
|
|
746
|
+
return {
|
|
747
|
+
theme: "system",
|
|
748
|
+
colorScheme: "light dark",
|
|
749
|
+
themeColor: { light: colors.light, dark: colors.dark }
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
return fixedMode(parsed, colors[parsed]);
|
|
753
|
+
}
|
|
754
|
+
function fixedMode(theme, color) {
|
|
755
|
+
return { theme, colorScheme: theme, themeColor: { light: color, dark: color } };
|
|
756
|
+
}
|
|
757
|
+
function webModeScript(options) {
|
|
758
|
+
const contrast = [
|
|
759
|
+
`if (window.matchMedia && window.matchMedia(${scriptString("(prefers-contrast: more)")}).matches)`,
|
|
760
|
+
` document.documentElement.setAttribute("data-cd-kontrast", "hoch");`
|
|
761
|
+
];
|
|
762
|
+
if (!options.themes.includes("dark")) return contrast.join("\n");
|
|
763
|
+
const { light, dark } = options.themeColors;
|
|
764
|
+
return [
|
|
765
|
+
"(function () {",
|
|
766
|
+
" var html = document.documentElement;",
|
|
767
|
+
" var choice = null;",
|
|
768
|
+
" try {",
|
|
769
|
+
` choice = window.localStorage.getItem(${scriptString(APPEARANCE_STORAGE_KEY)});`,
|
|
770
|
+
" } catch (error) {}",
|
|
771
|
+
` if (choice === "light" || choice === "dark") {`,
|
|
772
|
+
` var color = choice === "dark" ? ${scriptString(dark)} : ${scriptString(light)};`,
|
|
773
|
+
` html.setAttribute("data-cd-theme", choice);`,
|
|
774
|
+
` var scheme = document.querySelector("meta[name=color-scheme]");`,
|
|
775
|
+
` if (scheme) scheme.setAttribute("content", choice);`,
|
|
776
|
+
` var metas = document.querySelectorAll("meta[name=theme-color]");`,
|
|
777
|
+
` for (var index = 0; index < metas.length; index++) metas[index].setAttribute("content", color);`,
|
|
778
|
+
" }",
|
|
779
|
+
...contrast.map((line) => ` ${line}`),
|
|
780
|
+
"})();"
|
|
781
|
+
].join("\n");
|
|
782
|
+
}
|
|
783
|
+
function scriptString(value) {
|
|
784
|
+
return JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
785
|
+
}
|
|
786
|
+
function webHeadMarkup(options) {
|
|
787
|
+
const mode = documentMode("system", options.themes, options.themeColors);
|
|
788
|
+
const script = `<script>
|
|
789
|
+
${webModeScript(options)}
|
|
790
|
+
</script>`;
|
|
791
|
+
if (mode.theme !== "system") {
|
|
792
|
+
return [`<meta name="theme-color" content="${escapeAttribute(mode.themeColor.light)}">`, script].join(
|
|
793
|
+
"\n"
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
return [
|
|
797
|
+
`<meta name="color-scheme" content="${escapeAttribute(mode.colorScheme)}">`,
|
|
798
|
+
`<meta name="theme-color" content="${escapeAttribute(mode.themeColor.light)}" media="(prefers-color-scheme: light)">`,
|
|
799
|
+
`<meta name="theme-color" content="${escapeAttribute(mode.themeColor.dark)}" media="(prefers-color-scheme: dark)">`,
|
|
800
|
+
script
|
|
801
|
+
].join("\n");
|
|
802
|
+
}
|
|
803
|
+
function escapeAttribute(value) {
|
|
804
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// src/core/feedback/problems.ts
|
|
808
|
+
function problemSummary(items) {
|
|
809
|
+
const errors = items.filter((item) => item.severity === "error").length;
|
|
810
|
+
return { count: items.length, errors, items };
|
|
811
|
+
}
|
|
812
|
+
|
|
579
813
|
// src/dom/element.ts
|
|
580
814
|
function createElement(ownerDocument, tag, attributes = {}, ...children) {
|
|
581
815
|
const element = ownerDocument.createElement(tag);
|
|
@@ -613,163 +847,134 @@ function hasVisibleFocus(element) {
|
|
|
613
847
|
}
|
|
614
848
|
}
|
|
615
849
|
|
|
616
|
-
// src/dom/
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
element.hidden = !shown;
|
|
639
|
-
},
|
|
640
|
-
place({ top, right, bottom, left }) {
|
|
641
|
-
element.style.inset = `${top}px ${right}px ${bottom}px ${left}px`;
|
|
642
|
-
},
|
|
643
|
-
destroy() {
|
|
644
|
-
element.remove();
|
|
645
|
-
}
|
|
646
|
-
};
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// src/dom/inspector.ts
|
|
650
|
-
function createInspector(ownerDocument, options, onChange) {
|
|
651
|
-
const element = createElement(ownerDocument, "aside", {
|
|
652
|
-
class: "cd-shell-inspector",
|
|
653
|
-
"data-chrome": "inspector",
|
|
654
|
-
"aria-label": options.label
|
|
655
|
-
});
|
|
656
|
-
element.hidden = true;
|
|
657
|
-
function setOpen(open) {
|
|
658
|
-
if (open === !element.hidden) return;
|
|
659
|
-
element.hidden = !open;
|
|
660
|
-
onChange(open);
|
|
850
|
+
// src/dom/feedback/appearance.ts
|
|
851
|
+
var NO_TRANSITION_CLASS = "cd-shell-no-transition";
|
|
852
|
+
var CONTRAST_QUERY = "(prefers-contrast: more)";
|
|
853
|
+
var SCHEME_MEDIA = {
|
|
854
|
+
light: "(prefers-color-scheme: light)",
|
|
855
|
+
dark: "(prefers-color-scheme: dark)"
|
|
856
|
+
};
|
|
857
|
+
function onMediaChange(query, listener) {
|
|
858
|
+
if (typeof query.addEventListener === "function") {
|
|
859
|
+
query.addEventListener("change", listener);
|
|
860
|
+
return () => query.removeEventListener("change", listener);
|
|
861
|
+
}
|
|
862
|
+
query.addListener(listener);
|
|
863
|
+
return () => query.removeListener(listener);
|
|
864
|
+
}
|
|
865
|
+
function connectWebAppearance(context) {
|
|
866
|
+
const { ownerDocument, themes, options } = context;
|
|
867
|
+
const darkCapable = themes.includes("dark");
|
|
868
|
+
if (darkCapable && !options) {
|
|
869
|
+
throw new Error(
|
|
870
|
+
'@miragon/modeler-shell: a web modeler whose themes include "dark" needs the appearance option ({ themeColors } from its vendored cd-tokens.generated.json)'
|
|
871
|
+
);
|
|
661
872
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
873
|
+
const html = ownerDocument.documentElement;
|
|
874
|
+
const view = ownerDocument.defaultView;
|
|
875
|
+
const cleanups = [];
|
|
876
|
+
const contrast = typeof view?.matchMedia === "function" ? view.matchMedia(CONTRAST_QUERY) : void 0;
|
|
877
|
+
if (contrast) {
|
|
878
|
+
const mirrorContrast = () => toggleAttribute(html, "data-cd-kontrast", contrast.matches && "hoch");
|
|
879
|
+
mirrorContrast();
|
|
880
|
+
cleanups.push(onMediaChange(contrast, mirrorContrast));
|
|
881
|
+
}
|
|
882
|
+
function destroy() {
|
|
883
|
+
for (const cleanup of cleanups.splice(0)) cleanup();
|
|
884
|
+
}
|
|
885
|
+
if (!darkCapable || !options) {
|
|
886
|
+
return { choice: () => "system", setChoice() {
|
|
887
|
+
}, destroy };
|
|
888
|
+
}
|
|
889
|
+
const { themeColors } = options;
|
|
890
|
+
function storage() {
|
|
891
|
+
if (options?.storage) return options.storage;
|
|
892
|
+
try {
|
|
893
|
+
return globalThis.localStorage;
|
|
894
|
+
} catch {
|
|
895
|
+
return void 0;
|
|
669
896
|
}
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
// src/dom/keys.ts
|
|
674
|
-
function isTextField(target) {
|
|
675
|
-
if (!target || typeof target.closest !== "function") return false;
|
|
676
|
-
const element = target;
|
|
677
|
-
return element.isContentEditable === true || element.closest(TEXT_FIELD_SELECTOR) !== null;
|
|
678
|
-
}
|
|
679
|
-
function isShown(element) {
|
|
680
|
-
if (element.localName === "dialog" && !element.hasAttribute("open")) return false;
|
|
681
|
-
if (element.closest("[hidden]")) return false;
|
|
682
|
-
return typeof element.checkVisibility !== "function" || element.checkVisibility({ visibilityProperty: true });
|
|
683
|
-
}
|
|
684
|
-
function isModal(element) {
|
|
685
|
-
if (element.getAttribute("aria-modal") === "true") return true;
|
|
686
|
-
try {
|
|
687
|
-
return element.matches(":modal");
|
|
688
|
-
} catch {
|
|
689
|
-
return false;
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
function openDialogs(scope) {
|
|
693
|
-
return [...scope.querySelectorAll('[role="dialog"], [role="alertdialog"], dialog')].filter(isShown);
|
|
694
|
-
}
|
|
695
|
-
function createKeyListener(context) {
|
|
696
|
-
return (event) => {
|
|
697
|
-
if (event.key === "Escape") context.hideTooltip();
|
|
698
|
-
const dialogs = openDialogs(context.dialogScope);
|
|
699
|
-
const decision = decideKey(event, {
|
|
700
|
-
host: context.host,
|
|
701
|
-
inTextField: isTextField(event.target),
|
|
702
|
-
menuOpen: context.menuOpen(),
|
|
703
|
-
dialogOpen: dialogs.length > 0,
|
|
704
|
-
modalOpen: dialogs.some(isModal),
|
|
705
|
-
defaultPrevented: event.defaultPrevented
|
|
706
|
-
});
|
|
707
|
-
if (decision.preventDefault) event.preventDefault();
|
|
708
|
-
if (decision.stopPropagation) event.stopPropagation();
|
|
709
|
-
if (decision.action === "closeMenu") context.closeMenu();
|
|
710
|
-
else if (decision.action) context.run(decision.action);
|
|
711
|
-
};
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
// src/dom/ledger.ts
|
|
715
|
-
var Ledger = class {
|
|
716
|
-
undo = [];
|
|
717
|
-
originalAttributes = /* @__PURE__ */ new Map();
|
|
718
|
-
closed = false;
|
|
719
|
-
get isClosed() {
|
|
720
|
-
return this.closed;
|
|
721
897
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
if (element.classList.contains(className)) return;
|
|
729
|
-
const hadClassAttribute = element.hasAttribute("class");
|
|
730
|
-
element.classList.add(className);
|
|
731
|
-
this.defer(() => {
|
|
732
|
-
element.classList.remove(className);
|
|
733
|
-
if (!hadClassAttribute && element.classList.length === 0) element.removeAttribute("class");
|
|
734
|
-
});
|
|
898
|
+
function readStored() {
|
|
899
|
+
try {
|
|
900
|
+
return parseAppearanceChoice(storage()?.getItem(APPEARANCE_STORAGE_KEY));
|
|
901
|
+
} catch {
|
|
902
|
+
return "system";
|
|
903
|
+
}
|
|
735
904
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
905
|
+
function store(choice) {
|
|
906
|
+
try {
|
|
907
|
+
const target = storage();
|
|
908
|
+
if (choice === "system") target?.removeItem(APPEARANCE_STORAGE_KEY);
|
|
909
|
+
else target?.setItem(APPEARANCE_STORAGE_KEY, choice);
|
|
910
|
+
} catch {
|
|
742
911
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
else element.setAttribute(name, original);
|
|
749
|
-
});
|
|
912
|
+
}
|
|
913
|
+
function nextFrame(callback) {
|
|
914
|
+
if (view && typeof view.requestAnimationFrame === "function") {
|
|
915
|
+
const frame = view.requestAnimationFrame(() => callback());
|
|
916
|
+
return () => view.cancelAnimationFrame(frame);
|
|
750
917
|
}
|
|
751
|
-
|
|
752
|
-
|
|
918
|
+
const timer = setTimeout(callback, 16);
|
|
919
|
+
return () => clearTimeout(timer);
|
|
753
920
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
921
|
+
let cancelTransitionFrame;
|
|
922
|
+
function allowTransitions() {
|
|
923
|
+
cancelTransitionFrame?.();
|
|
924
|
+
cancelTransitionFrame = void 0;
|
|
925
|
+
html.classList.remove(NO_TRANSITION_CLASS);
|
|
758
926
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
}
|
|
769
|
-
this.undo.length = 0;
|
|
770
|
-
this.originalAttributes.clear();
|
|
927
|
+
cleanups.push(() => {
|
|
928
|
+
if (cancelTransitionFrame) allowTransitions();
|
|
929
|
+
});
|
|
930
|
+
function suspendTransitions() {
|
|
931
|
+
cancelTransitionFrame?.();
|
|
932
|
+
html.classList.add(NO_TRANSITION_CLASS);
|
|
933
|
+
cancelTransitionFrame = nextFrame(() => {
|
|
934
|
+
cancelTransitionFrame = nextFrame(allowTransitions);
|
|
935
|
+
});
|
|
771
936
|
}
|
|
772
|
-
|
|
937
|
+
function metaFor(name, media) {
|
|
938
|
+
const normalize = (value) => (value ?? "").replace(/\s+/g, "").toLowerCase();
|
|
939
|
+
const existing = [...ownerDocument.querySelectorAll(`meta[name="${name}"]`)].find(
|
|
940
|
+
(meta) => media === void 0 ? true : normalize(meta.getAttribute("media")) === normalize(media)
|
|
941
|
+
);
|
|
942
|
+
if (existing) return existing;
|
|
943
|
+
const created = createElement(ownerDocument, "meta", { name, media });
|
|
944
|
+
(ownerDocument.head ?? html).append(created);
|
|
945
|
+
return created;
|
|
946
|
+
}
|
|
947
|
+
function apply(choice) {
|
|
948
|
+
const mode = documentMode(choice, themes, themeColors);
|
|
949
|
+
suspendTransitions();
|
|
950
|
+
html.setAttribute("data-cd-theme", mode.theme);
|
|
951
|
+
metaFor("color-scheme").setAttribute("content", mode.colorScheme);
|
|
952
|
+
metaFor("theme-color", SCHEME_MEDIA.light).setAttribute("content", mode.themeColor.light);
|
|
953
|
+
metaFor("theme-color", SCHEME_MEDIA.dark).setAttribute("content", mode.themeColor.dark);
|
|
954
|
+
}
|
|
955
|
+
let current = readStored();
|
|
956
|
+
const onStorage = (event) => {
|
|
957
|
+
if (event.key !== null && event.key !== APPEARANCE_STORAGE_KEY) return;
|
|
958
|
+
const next = event.key === null ? "system" : parseAppearanceChoice(event.newValue);
|
|
959
|
+
if (next === current) return;
|
|
960
|
+
current = next;
|
|
961
|
+
apply(next);
|
|
962
|
+
};
|
|
963
|
+
if (view) {
|
|
964
|
+
view.addEventListener("storage", onStorage);
|
|
965
|
+
cleanups.push(() => view.removeEventListener("storage", onStorage));
|
|
966
|
+
}
|
|
967
|
+
return {
|
|
968
|
+
choice: () => current,
|
|
969
|
+
setChoice(choice) {
|
|
970
|
+
const next = parseAppearanceChoice(choice);
|
|
971
|
+
current = next;
|
|
972
|
+
store(next);
|
|
973
|
+
apply(next);
|
|
974
|
+
},
|
|
975
|
+
destroy
|
|
976
|
+
};
|
|
977
|
+
}
|
|
773
978
|
|
|
774
979
|
// src/dom/icons.ts
|
|
775
980
|
var ICONS = {
|
|
@@ -979,28 +1184,691 @@ function sanitize(icon, options = {}) {
|
|
|
979
1184
|
}
|
|
980
1185
|
return { root, children };
|
|
981
1186
|
}
|
|
982
|
-
function
|
|
1187
|
+
function escapeAttribute2(value) {
|
|
983
1188
|
return value.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
984
1189
|
}
|
|
985
1190
|
function attributeList(attributes) {
|
|
986
|
-
return attributes.map(([name, value]) => ` ${name}="${
|
|
1191
|
+
return attributes.map(([name, value]) => ` ${name}="${escapeAttribute2(value)}"`).join("");
|
|
1192
|
+
}
|
|
1193
|
+
function iconMarkup(icon, options) {
|
|
1194
|
+
const { root, children } = sanitize(icon, options);
|
|
1195
|
+
const content = children.map(([tag, attributes]) => `<${tag}${attributeList(attributes)}/>`).join("");
|
|
1196
|
+
return `<svg xmlns="${SVG_NAMESPACE}"${attributeList(root)}>${content}</svg>`;
|
|
1197
|
+
}
|
|
1198
|
+
function iconElement(icon, options) {
|
|
1199
|
+
const { root, children } = sanitize(icon, options);
|
|
1200
|
+
const svg = document.createElementNS(SVG_NAMESPACE, "svg");
|
|
1201
|
+
for (const [name, value] of root) svg.setAttribute(name, value);
|
|
1202
|
+
for (const [tag, attributes] of children) {
|
|
1203
|
+
const child = document.createElementNS(SVG_NAMESPACE, tag);
|
|
1204
|
+
for (const [name, value] of attributes) child.setAttribute(name, value);
|
|
1205
|
+
svg.append(child);
|
|
1206
|
+
}
|
|
1207
|
+
return svg;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// src/dom/feedback/problems-popover.ts
|
|
1211
|
+
var PILL_ATTRIBUTES = ["aria-haspopup", "aria-expanded", "aria-controls"];
|
|
1212
|
+
function isPrintable(event) {
|
|
1213
|
+
return event.key.length === 1 && /\S/.test(event.key) && !event.ctrlKey && !event.metaKey && !event.altKey;
|
|
1214
|
+
}
|
|
1215
|
+
function createProblemsPopover(context) {
|
|
1216
|
+
const { ownerDocument, root, layer, pill, id, titleId, messages } = context;
|
|
1217
|
+
const view = ownerDocument.defaultView;
|
|
1218
|
+
const list = createElement(ownerDocument, "ul", { class: "cd-shell-problems-list", role: "list" });
|
|
1219
|
+
const element = createElement(
|
|
1220
|
+
ownerDocument,
|
|
1221
|
+
"div",
|
|
1222
|
+
{
|
|
1223
|
+
class: "cd-shell-problems-popover",
|
|
1224
|
+
"data-chrome": "problems-popover",
|
|
1225
|
+
role: "dialog",
|
|
1226
|
+
id,
|
|
1227
|
+
"aria-labelledby": titleId,
|
|
1228
|
+
tabindex: "-1"
|
|
1229
|
+
},
|
|
1230
|
+
createElement(
|
|
1231
|
+
ownerDocument,
|
|
1232
|
+
"h2",
|
|
1233
|
+
{ class: "cd-shell-problems-popover-title", id: titleId },
|
|
1234
|
+
messages.problemsTitle
|
|
1235
|
+
),
|
|
1236
|
+
list
|
|
1237
|
+
);
|
|
1238
|
+
element.hidden = true;
|
|
1239
|
+
layer.append(element);
|
|
1240
|
+
pill.setAttribute("aria-haspopup", "dialog");
|
|
1241
|
+
pill.setAttribute("aria-expanded", "false");
|
|
1242
|
+
pill.setAttribute("aria-controls", id);
|
|
1243
|
+
let items = [];
|
|
1244
|
+
let rows = [];
|
|
1245
|
+
let destroyed = false;
|
|
1246
|
+
const isOpen = () => !element.hidden;
|
|
1247
|
+
function keyOf(item) {
|
|
1248
|
+
return `${item.elementId ?? ""}\0${item.severity}\0${item.message}`;
|
|
1249
|
+
}
|
|
1250
|
+
function rowElement(item) {
|
|
1251
|
+
const severity = item.severity === "error" ? "error" : "warning";
|
|
1252
|
+
const parts = [
|
|
1253
|
+
// The shape and colour of the icon carry the severity on screen, its name for screen readers.
|
|
1254
|
+
createElement(
|
|
1255
|
+
ownerDocument,
|
|
1256
|
+
"span",
|
|
1257
|
+
{ class: "cd-shell-problem-icon", role: "img", "aria-label": messages.problemSeverity(severity) },
|
|
1258
|
+
iconElement(severity === "error" ? "circle-x" : "triangle-alert")
|
|
1259
|
+
),
|
|
1260
|
+
createElement(ownerDocument, "span", { class: "cd-shell-problem-message" }, item.message),
|
|
1261
|
+
item.location ? createElement(ownerDocument, "span", { class: "cd-shell-problem-location" }, item.location) : void 0
|
|
1262
|
+
];
|
|
1263
|
+
const attributes = { class: "cd-shell-problem", "data-severity": severity };
|
|
1264
|
+
if (!item.elementId) {
|
|
1265
|
+
const text = createElement(ownerDocument, "div", { ...attributes, tabindex: "-1" }, ...parts);
|
|
1266
|
+
rows.push({ key: keyOf(item), element: text });
|
|
1267
|
+
return text;
|
|
1268
|
+
}
|
|
1269
|
+
const button = createElement(ownerDocument, "button", { type: "button", ...attributes }, ...parts);
|
|
1270
|
+
button.addEventListener("click", () => select(item));
|
|
1271
|
+
rows.push({ key: keyOf(item), element: button });
|
|
1272
|
+
return button;
|
|
1273
|
+
}
|
|
1274
|
+
function focusedRowIndex() {
|
|
1275
|
+
return rows.findIndex((row) => row.element === ownerDocument.activeElement);
|
|
1276
|
+
}
|
|
1277
|
+
function render() {
|
|
1278
|
+
const focusedIndex = focusedRowIndex();
|
|
1279
|
+
const focusedKey = rows[focusedIndex]?.key;
|
|
1280
|
+
rows = [];
|
|
1281
|
+
list.replaceChildren(...items.map((item) => createElement(ownerDocument, "li", {}, rowElement(item))));
|
|
1282
|
+
if (focusedKey === void 0 || !isOpen()) return;
|
|
1283
|
+
const kept = rows.find((row) => row.key === focusedKey) ?? rows[Math.min(focusedIndex, rows.length - 1)];
|
|
1284
|
+
if (kept) focusRowElement(kept.element);
|
|
1285
|
+
else element.focus({ preventScroll: true });
|
|
1286
|
+
}
|
|
1287
|
+
function revealInList(row) {
|
|
1288
|
+
const rowBox = row.getBoundingClientRect();
|
|
1289
|
+
const listBox = list.getBoundingClientRect();
|
|
1290
|
+
if (rowBox.top < listBox.top) list.scrollTop -= listBox.top - rowBox.top;
|
|
1291
|
+
else if (rowBox.bottom > listBox.bottom) list.scrollTop += rowBox.bottom - listBox.bottom;
|
|
1292
|
+
}
|
|
1293
|
+
function focusRowElement(row) {
|
|
1294
|
+
row.focus({ preventScroll: true });
|
|
1295
|
+
revealInList(row);
|
|
1296
|
+
}
|
|
1297
|
+
function focusCanvas() {
|
|
1298
|
+
const canvas = root.querySelector(".cd-shell-canvas svg[tabindex]");
|
|
1299
|
+
canvas?.focus({ preventScroll: true });
|
|
1300
|
+
if ((!canvas || ownerDocument.activeElement !== canvas) && !pill.hidden)
|
|
1301
|
+
pill.focus({ preventScroll: true });
|
|
1302
|
+
}
|
|
1303
|
+
function select(item) {
|
|
1304
|
+
close();
|
|
1305
|
+
focusCanvas();
|
|
1306
|
+
context.onSelectItem?.(item);
|
|
1307
|
+
}
|
|
1308
|
+
function focusRow(index) {
|
|
1309
|
+
if (rows.length === 0) return;
|
|
1310
|
+
const row = rows[(index + rows.length) % rows.length];
|
|
1311
|
+
if (row) focusRowElement(row.element);
|
|
1312
|
+
}
|
|
1313
|
+
const onOutsidePointerDown = (event) => {
|
|
1314
|
+
const target = event.target;
|
|
1315
|
+
if (target && (element.contains(target) || pill.contains(target))) return;
|
|
1316
|
+
close();
|
|
1317
|
+
};
|
|
1318
|
+
const onWindowBlur = () => close();
|
|
1319
|
+
function open() {
|
|
1320
|
+
if (destroyed || isOpen()) return;
|
|
1321
|
+
element.hidden = false;
|
|
1322
|
+
pill.setAttribute("aria-expanded", "true");
|
|
1323
|
+
ownerDocument.addEventListener("pointerdown", onOutsidePointerDown, true);
|
|
1324
|
+
view?.addEventListener("blur", onWindowBlur);
|
|
1325
|
+
list.scrollTop = 0;
|
|
1326
|
+
const first = rows[0];
|
|
1327
|
+
if (first) focusRowElement(first.element);
|
|
1328
|
+
else element.focus({ preventScroll: true });
|
|
1329
|
+
}
|
|
1330
|
+
function close(options = {}) {
|
|
1331
|
+
if (!isOpen()) return;
|
|
1332
|
+
element.hidden = true;
|
|
1333
|
+
pill.setAttribute("aria-expanded", "false");
|
|
1334
|
+
ownerDocument.removeEventListener("pointerdown", onOutsidePointerDown, true);
|
|
1335
|
+
view?.removeEventListener("blur", onWindowBlur);
|
|
1336
|
+
if (options.restoreFocus && !pill.hidden) pill.focus({ preventScroll: true });
|
|
1337
|
+
}
|
|
1338
|
+
function consume(event) {
|
|
1339
|
+
event.preventDefault();
|
|
1340
|
+
event.stopPropagation();
|
|
1341
|
+
}
|
|
1342
|
+
element.addEventListener("keydown", (event) => {
|
|
1343
|
+
if (matchChromeKey(event)) return;
|
|
1344
|
+
const index = focusedRowIndex();
|
|
1345
|
+
switch (event.key) {
|
|
1346
|
+
case "Escape":
|
|
1347
|
+
consume(event);
|
|
1348
|
+
close({ restoreFocus: true });
|
|
1349
|
+
return;
|
|
1350
|
+
case "Tab": {
|
|
1351
|
+
const first = index <= 0;
|
|
1352
|
+
const last = rows.length === 0 || index === rows.length - 1;
|
|
1353
|
+
if (event.shiftKey && first) {
|
|
1354
|
+
consume(event);
|
|
1355
|
+
close({ restoreFocus: true });
|
|
1356
|
+
} else if (!event.shiftKey && last) {
|
|
1357
|
+
event.stopPropagation();
|
|
1358
|
+
close({ restoreFocus: true });
|
|
1359
|
+
}
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
case "ArrowDown":
|
|
1363
|
+
focusRow(index + 1);
|
|
1364
|
+
break;
|
|
1365
|
+
case "ArrowUp":
|
|
1366
|
+
focusRow(index < 0 ? rows.length - 1 : index - 1);
|
|
1367
|
+
break;
|
|
1368
|
+
case "Home":
|
|
1369
|
+
focusRow(0);
|
|
1370
|
+
break;
|
|
1371
|
+
case "End":
|
|
1372
|
+
focusRow(rows.length - 1);
|
|
1373
|
+
break;
|
|
1374
|
+
case "Enter":
|
|
1375
|
+
case " ":
|
|
1376
|
+
case "Delete":
|
|
1377
|
+
case "Backspace":
|
|
1378
|
+
event.stopPropagation();
|
|
1379
|
+
return;
|
|
1380
|
+
default:
|
|
1381
|
+
if (isPrintable(event)) event.stopPropagation();
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
consume(event);
|
|
1385
|
+
});
|
|
1386
|
+
return {
|
|
1387
|
+
element,
|
|
1388
|
+
isOpen,
|
|
1389
|
+
toggle() {
|
|
1390
|
+
if (isOpen()) close({ restoreFocus: true });
|
|
1391
|
+
else open();
|
|
1392
|
+
},
|
|
1393
|
+
open,
|
|
1394
|
+
close,
|
|
1395
|
+
setItems(next) {
|
|
1396
|
+
items = next;
|
|
1397
|
+
if (!destroyed) render();
|
|
1398
|
+
},
|
|
1399
|
+
destroy() {
|
|
1400
|
+
if (destroyed) return;
|
|
1401
|
+
close();
|
|
1402
|
+
destroyed = true;
|
|
1403
|
+
for (const name of PILL_ATTRIBUTES) pill.removeAttribute(name);
|
|
1404
|
+
element.remove();
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
// src/dom/feedback/toasts.ts
|
|
1410
|
+
var TOAST_RESUME_MIN_MS = 1500;
|
|
1411
|
+
var TOAST_LEAVE_FALLBACK_MS = 400;
|
|
1412
|
+
var LEAVE_ANIMATION = "cd-shell-toast-out";
|
|
1413
|
+
var TOAST_ANNOUNCE_DELAY_MS = 100;
|
|
1414
|
+
var INERT_HANDLE = Object.freeze({ dismiss() {
|
|
1415
|
+
} });
|
|
1416
|
+
function createToaster(context) {
|
|
1417
|
+
const { ownerDocument, messages } = context;
|
|
1418
|
+
const web = context.host === "web";
|
|
1419
|
+
const element = createElement(ownerDocument, "div", {
|
|
1420
|
+
class: "cd-shell-toasts",
|
|
1421
|
+
"data-chrome": "toasts",
|
|
1422
|
+
popover: web ? "manual" : void 0
|
|
1423
|
+
});
|
|
1424
|
+
const liveRegion = (politeness) => createElement(ownerDocument, "div", {
|
|
1425
|
+
class: "cd-shell-live-region",
|
|
1426
|
+
"data-live": politeness,
|
|
1427
|
+
role: politeness === "polite" ? "status" : "alert",
|
|
1428
|
+
"aria-live": politeness,
|
|
1429
|
+
"aria-atomic": "true"
|
|
1430
|
+
});
|
|
1431
|
+
const politeRegion = liveRegion("polite");
|
|
1432
|
+
const assertiveRegion = liveRegion("assertive");
|
|
1433
|
+
const announcer = createElement(
|
|
1434
|
+
ownerDocument,
|
|
1435
|
+
"div",
|
|
1436
|
+
{ class: "cd-shell-live" },
|
|
1437
|
+
politeRegion,
|
|
1438
|
+
assertiveRegion
|
|
1439
|
+
);
|
|
1440
|
+
const live = [];
|
|
1441
|
+
const leaveTimers = /* @__PURE__ */ new Set();
|
|
1442
|
+
const announceTimers = /* @__PURE__ */ new Map();
|
|
1443
|
+
let lastId = 0;
|
|
1444
|
+
let destroyed = false;
|
|
1445
|
+
function regionFor(request) {
|
|
1446
|
+
return request.status === "danger" ? assertiveRegion : politeRegion;
|
|
1447
|
+
}
|
|
1448
|
+
function announce(request) {
|
|
1449
|
+
const region = regionFor(request);
|
|
1450
|
+
clearTimeout(announceTimers.get(region));
|
|
1451
|
+
region.textContent = "";
|
|
1452
|
+
announceTimers.set(
|
|
1453
|
+
region,
|
|
1454
|
+
setTimeout(() => {
|
|
1455
|
+
announceTimers.delete(region);
|
|
1456
|
+
if (!destroyed) region.textContent = request.text;
|
|
1457
|
+
}, TOAST_ANNOUNCE_DELAY_MS)
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
function silence(request) {
|
|
1461
|
+
const region = regionFor(request);
|
|
1462
|
+
if (region.textContent === request.text) region.textContent = "";
|
|
1463
|
+
}
|
|
1464
|
+
function changed() {
|
|
1465
|
+
if (!destroyed) context.onChange?.();
|
|
1466
|
+
}
|
|
1467
|
+
function supportsPopover() {
|
|
1468
|
+
return web && typeof element.showPopover === "function" && typeof element.hidePopover === "function";
|
|
1469
|
+
}
|
|
1470
|
+
function popoverOpen() {
|
|
1471
|
+
try {
|
|
1472
|
+
return element.matches(":popover-open");
|
|
1473
|
+
} catch {
|
|
1474
|
+
return false;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
function modalOpen() {
|
|
1478
|
+
try {
|
|
1479
|
+
return ownerDocument.querySelector(":modal") !== null;
|
|
1480
|
+
} catch {
|
|
1481
|
+
return true;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
function raise() {
|
|
1485
|
+
if (!supportsPopover() || !element.isConnected) return;
|
|
1486
|
+
if (popoverOpen() && !modalOpen()) return;
|
|
1487
|
+
const focused = ownerDocument.activeElement;
|
|
1488
|
+
const keepFocus = focused && element.contains(focused) ? focused : void 0;
|
|
1489
|
+
try {
|
|
1490
|
+
element.hidePopover();
|
|
1491
|
+
element.showPopover();
|
|
1492
|
+
} catch {
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
if (keepFocus && ownerDocument.activeElement !== keepFocus) keepFocus.focus({ preventScroll: true });
|
|
1496
|
+
}
|
|
1497
|
+
function lowerWhenEmpty() {
|
|
1498
|
+
if (element.childElementCount > 0 || !supportsPopover()) return;
|
|
1499
|
+
try {
|
|
1500
|
+
element.hidePopover();
|
|
1501
|
+
} catch {
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
function engaged(toast) {
|
|
1505
|
+
return toast.hovered || toast.focused;
|
|
1506
|
+
}
|
|
1507
|
+
function stopTimer(toast) {
|
|
1508
|
+
clearTimeout(toast.timer);
|
|
1509
|
+
toast.timer = void 0;
|
|
1510
|
+
}
|
|
1511
|
+
function runTimer(toast) {
|
|
1512
|
+
stopTimer(toast);
|
|
1513
|
+
if (toast.remaining === void 0 || engaged(toast)) return;
|
|
1514
|
+
toast.startedAt = Date.now();
|
|
1515
|
+
toast.timer = setTimeout(() => close(toast), toast.remaining);
|
|
1516
|
+
}
|
|
1517
|
+
function pause(toast) {
|
|
1518
|
+
if (toast.timer === void 0) return;
|
|
1519
|
+
stopTimer(toast);
|
|
1520
|
+
if (toast.remaining !== void 0) {
|
|
1521
|
+
toast.remaining = Math.max(0, toast.remaining - (Date.now() - toast.startedAt));
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
function setEngagement(toast, hovered, focused) {
|
|
1525
|
+
if (!live.includes(toast)) return;
|
|
1526
|
+
const wasEngaged = engaged(toast);
|
|
1527
|
+
toast.hovered = hovered;
|
|
1528
|
+
toast.focused = focused;
|
|
1529
|
+
if (engaged(toast) === wasEngaged) return;
|
|
1530
|
+
if (wasEngaged) {
|
|
1531
|
+
if (toast.remaining !== void 0) toast.remaining = Math.max(toast.remaining, TOAST_RESUME_MIN_MS);
|
|
1532
|
+
runTimer(toast);
|
|
1533
|
+
} else {
|
|
1534
|
+
pause(toast);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
function reducedMotion() {
|
|
1538
|
+
if (ownerDocument.documentElement.classList.contains(REDUCED_MOTION_CLASS)) return true;
|
|
1539
|
+
const view = ownerDocument.defaultView;
|
|
1540
|
+
return typeof view?.matchMedia === "function" && view.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1541
|
+
}
|
|
1542
|
+
function removeElement(toastElement) {
|
|
1543
|
+
toastElement.remove();
|
|
1544
|
+
lowerWhenEmpty();
|
|
1545
|
+
changed();
|
|
1546
|
+
}
|
|
1547
|
+
function leave(toastElement) {
|
|
1548
|
+
toastElement.setAttribute("data-leaving", "");
|
|
1549
|
+
toastElement.inert = true;
|
|
1550
|
+
if (reducedMotion()) {
|
|
1551
|
+
removeElement(toastElement);
|
|
1552
|
+
return;
|
|
1553
|
+
}
|
|
1554
|
+
let done = false;
|
|
1555
|
+
const finish = () => {
|
|
1556
|
+
if (done) return;
|
|
1557
|
+
done = true;
|
|
1558
|
+
clearTimeout(fallback);
|
|
1559
|
+
leaveTimers.delete(fallback);
|
|
1560
|
+
removeElement(toastElement);
|
|
1561
|
+
};
|
|
1562
|
+
toastElement.addEventListener("animationend", (event) => {
|
|
1563
|
+
if (event.target === toastElement && event.animationName === LEAVE_ANIMATION) finish();
|
|
1564
|
+
});
|
|
1565
|
+
const fallback = setTimeout(finish, TOAST_LEAVE_FALLBACK_MS);
|
|
1566
|
+
leaveTimers.add(fallback);
|
|
1567
|
+
}
|
|
1568
|
+
function close(toast) {
|
|
1569
|
+
const index = live.indexOf(toast);
|
|
1570
|
+
if (index < 0) return;
|
|
1571
|
+
live.splice(index, 1);
|
|
1572
|
+
stopTimer(toast);
|
|
1573
|
+
silence(toast.request);
|
|
1574
|
+
const heldFocus = toast.element.contains(ownerDocument.activeElement);
|
|
1575
|
+
if (heldFocus) {
|
|
1576
|
+
const next = live[index] ?? live[index - 1];
|
|
1577
|
+
if (next) next.closeButton.focus({ preventScroll: true });
|
|
1578
|
+
else context.focusFallback();
|
|
1579
|
+
}
|
|
1580
|
+
leave(toast.element);
|
|
1581
|
+
}
|
|
1582
|
+
function setAction(toast, action) {
|
|
1583
|
+
const previous = toast.actionButton;
|
|
1584
|
+
const hadFocus = previous !== void 0 && ownerDocument.activeElement === previous;
|
|
1585
|
+
previous?.remove();
|
|
1586
|
+
toast.actionButton = void 0;
|
|
1587
|
+
if (action) {
|
|
1588
|
+
const button = createElement(
|
|
1589
|
+
ownerDocument,
|
|
1590
|
+
"button",
|
|
1591
|
+
{ type: "button", class: "cd-shell-toast-action cd-shell-button", "data-variant": "ghost" },
|
|
1592
|
+
action.label
|
|
1593
|
+
);
|
|
1594
|
+
button.addEventListener("click", () => {
|
|
1595
|
+
close(toast);
|
|
1596
|
+
action.onSelect();
|
|
1597
|
+
});
|
|
1598
|
+
toast.closeButton.before(button);
|
|
1599
|
+
toast.actionButton = button;
|
|
1600
|
+
}
|
|
1601
|
+
if (hadFocus) (toast.actionButton ?? toast.closeButton).focus({ preventScroll: true });
|
|
1602
|
+
}
|
|
1603
|
+
function slotOf(toast) {
|
|
1604
|
+
return { id: toast.id, hasAction: toast.request.action !== void 0, engaged: engaged(toast) };
|
|
1605
|
+
}
|
|
1606
|
+
function render(request) {
|
|
1607
|
+
const id = ++lastId;
|
|
1608
|
+
const textId = `${context.idPrefix}-${id}`;
|
|
1609
|
+
const closeButton = createElement(
|
|
1610
|
+
ownerDocument,
|
|
1611
|
+
"button",
|
|
1612
|
+
{
|
|
1613
|
+
type: "button",
|
|
1614
|
+
class: "cd-shell-toast-close",
|
|
1615
|
+
"aria-label": messages.close,
|
|
1616
|
+
"aria-describedby": textId
|
|
1617
|
+
},
|
|
1618
|
+
iconElement("x")
|
|
1619
|
+
);
|
|
1620
|
+
const toastElement = createElement(
|
|
1621
|
+
ownerDocument,
|
|
1622
|
+
"div",
|
|
1623
|
+
{ class: "cd-shell-toast", "data-status": request.status },
|
|
1624
|
+
createElement(ownerDocument, "span", { class: "cd-shell-toast-dot", "aria-hidden": "true" }),
|
|
1625
|
+
createElement(ownerDocument, "span", { class: "cd-shell-toast-text", id: textId }, request.text),
|
|
1626
|
+
closeButton
|
|
1627
|
+
);
|
|
1628
|
+
const toast = {
|
|
1629
|
+
id,
|
|
1630
|
+
element: toastElement,
|
|
1631
|
+
closeButton,
|
|
1632
|
+
handle: Object.freeze({ dismiss: () => close(toast) }),
|
|
1633
|
+
request,
|
|
1634
|
+
actionButton: void 0,
|
|
1635
|
+
hovered: false,
|
|
1636
|
+
focused: false,
|
|
1637
|
+
remaining: toastLifetime(request),
|
|
1638
|
+
startedAt: Date.now(),
|
|
1639
|
+
timer: void 0
|
|
1640
|
+
};
|
|
1641
|
+
setAction(toast, request.action);
|
|
1642
|
+
closeButton.addEventListener("click", () => close(toast));
|
|
1643
|
+
toastElement.addEventListener("mouseenter", () => setEngagement(toast, true, toast.focused));
|
|
1644
|
+
toastElement.addEventListener("mouseleave", () => setEngagement(toast, false, toast.focused));
|
|
1645
|
+
toastElement.addEventListener("focusin", () => setEngagement(toast, toast.hovered, true));
|
|
1646
|
+
toastElement.addEventListener(
|
|
1647
|
+
"focusout",
|
|
1648
|
+
(event) => setEngagement(toast, toast.hovered, toastElement.contains(event.relatedTarget))
|
|
1649
|
+
);
|
|
1650
|
+
return toast;
|
|
1651
|
+
}
|
|
1652
|
+
function show(request) {
|
|
1653
|
+
if (destroyed) return INERT_HANDLE;
|
|
1654
|
+
const same = live.find(
|
|
1655
|
+
(toast2) => toast2.request.status === request.status && toast2.request.text === request.text
|
|
1656
|
+
);
|
|
1657
|
+
if (same) {
|
|
1658
|
+
same.request = request;
|
|
1659
|
+
setAction(same, request.action);
|
|
1660
|
+
same.remaining = toastLifetime(request);
|
|
1661
|
+
runTimer(same);
|
|
1662
|
+
raise();
|
|
1663
|
+
announce(request);
|
|
1664
|
+
changed();
|
|
1665
|
+
return same.handle;
|
|
1666
|
+
}
|
|
1667
|
+
while (live.length >= MAX_TOASTS) {
|
|
1668
|
+
const evictedId = toastToEvict(live.map(slotOf));
|
|
1669
|
+
const evicted = live.find((toast2) => toast2.id === evictedId) ?? live[0];
|
|
1670
|
+
if (!evicted) break;
|
|
1671
|
+
close(evicted);
|
|
1672
|
+
}
|
|
1673
|
+
const toast = render(request);
|
|
1674
|
+
live.push(toast);
|
|
1675
|
+
element.append(toast.element);
|
|
1676
|
+
raise();
|
|
1677
|
+
runTimer(toast);
|
|
1678
|
+
announce(request);
|
|
1679
|
+
changed();
|
|
1680
|
+
return toast.handle;
|
|
1681
|
+
}
|
|
1682
|
+
return {
|
|
1683
|
+
element,
|
|
1684
|
+
announcer,
|
|
1685
|
+
show,
|
|
1686
|
+
setBottom(bottom) {
|
|
1687
|
+
element.style.bottom = `${bottom}px`;
|
|
1688
|
+
},
|
|
1689
|
+
widestCard() {
|
|
1690
|
+
let widest;
|
|
1691
|
+
for (const card of element.children) {
|
|
1692
|
+
const width = card.getBoundingClientRect().width;
|
|
1693
|
+
if (width > 0) widest = Math.max(widest ?? 0, width);
|
|
1694
|
+
}
|
|
1695
|
+
return widest;
|
|
1696
|
+
},
|
|
1697
|
+
destroy() {
|
|
1698
|
+
if (destroyed) return;
|
|
1699
|
+
destroyed = true;
|
|
1700
|
+
for (const toast of live.splice(0)) stopTimer(toast);
|
|
1701
|
+
for (const timer of leaveTimers) clearTimeout(timer);
|
|
1702
|
+
leaveTimers.clear();
|
|
1703
|
+
for (const timer of announceTimers.values()) clearTimeout(timer);
|
|
1704
|
+
announceTimers.clear();
|
|
1705
|
+
element.remove();
|
|
1706
|
+
announcer.remove();
|
|
1707
|
+
}
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
// src/dom/hint.ts
|
|
1712
|
+
function createEmptyHint(ownerDocument, messages, options) {
|
|
1713
|
+
const { onInsertExample } = options;
|
|
1714
|
+
const action = onInsertExample ? createElement(
|
|
1715
|
+
ownerDocument,
|
|
1716
|
+
"button",
|
|
1717
|
+
{ type: "button", class: "cd-shell-hint-action" },
|
|
1718
|
+
messages.insertExample
|
|
1719
|
+
) : void 0;
|
|
1720
|
+
action?.addEventListener("click", () => onInsertExample?.());
|
|
1721
|
+
const element = createElement(
|
|
1722
|
+
ownerDocument,
|
|
1723
|
+
"div",
|
|
1724
|
+
{ class: "cd-shell-hint" },
|
|
1725
|
+
createElement(ownerDocument, "p", { class: "cd-shell-hint-text" }, messages.emptyHint),
|
|
1726
|
+
action
|
|
1727
|
+
);
|
|
1728
|
+
element.hidden = true;
|
|
1729
|
+
return {
|
|
1730
|
+
element,
|
|
1731
|
+
isShown: () => !element.hidden,
|
|
1732
|
+
setShown(shown) {
|
|
1733
|
+
element.hidden = !shown;
|
|
1734
|
+
},
|
|
1735
|
+
place({ top, right, bottom, left }) {
|
|
1736
|
+
element.style.inset = `${top}px ${right}px ${bottom}px ${left}px`;
|
|
1737
|
+
},
|
|
1738
|
+
destroy() {
|
|
1739
|
+
element.remove();
|
|
1740
|
+
}
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
// src/dom/inspector.ts
|
|
1745
|
+
function createInspector(ownerDocument, options, onChange) {
|
|
1746
|
+
const element = createElement(ownerDocument, "aside", {
|
|
1747
|
+
class: "cd-shell-inspector",
|
|
1748
|
+
"data-chrome": "inspector",
|
|
1749
|
+
"aria-label": options.label
|
|
1750
|
+
});
|
|
1751
|
+
element.hidden = true;
|
|
1752
|
+
function setOpen(open) {
|
|
1753
|
+
if (open === !element.hidden) return;
|
|
1754
|
+
element.hidden = !open;
|
|
1755
|
+
onChange(open);
|
|
1756
|
+
}
|
|
1757
|
+
return {
|
|
1758
|
+
element,
|
|
1759
|
+
isOpen: () => !element.hidden,
|
|
1760
|
+
open: () => setOpen(true),
|
|
1761
|
+
close: () => setOpen(false),
|
|
1762
|
+
destroy() {
|
|
1763
|
+
element.remove();
|
|
1764
|
+
}
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
// src/dom/keys.ts
|
|
1769
|
+
function isTextField(target) {
|
|
1770
|
+
if (!target || typeof target.closest !== "function") return false;
|
|
1771
|
+
const element = target;
|
|
1772
|
+
return element.isContentEditable === true || element.closest(TEXT_FIELD_SELECTOR) !== null;
|
|
1773
|
+
}
|
|
1774
|
+
function isShown(element) {
|
|
1775
|
+
if (element.localName === "dialog" && !element.hasAttribute("open")) return false;
|
|
1776
|
+
if (element.closest("[hidden]")) return false;
|
|
1777
|
+
return typeof element.checkVisibility !== "function" || element.checkVisibility({ visibilityProperty: true });
|
|
1778
|
+
}
|
|
1779
|
+
function isModal(element) {
|
|
1780
|
+
if (element.getAttribute("aria-modal") === "true") return true;
|
|
1781
|
+
try {
|
|
1782
|
+
return element.matches(":modal");
|
|
1783
|
+
} catch {
|
|
1784
|
+
return false;
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
function openDialogs(scope) {
|
|
1788
|
+
return [...scope.querySelectorAll('[role="dialog"], [role="alertdialog"], dialog')].filter(isShown);
|
|
1789
|
+
}
|
|
1790
|
+
function isModalOpen(scope) {
|
|
1791
|
+
return openDialogs(scope).some(isModal);
|
|
987
1792
|
}
|
|
988
|
-
function
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1793
|
+
function createKeyListener(context) {
|
|
1794
|
+
return (event) => {
|
|
1795
|
+
if (event.key === "Escape") context.hideTooltip();
|
|
1796
|
+
const dialogs = openDialogs(context.dialogScope);
|
|
1797
|
+
const decision = decideKey(event, {
|
|
1798
|
+
host: context.host,
|
|
1799
|
+
inTextField: isTextField(event.target),
|
|
1800
|
+
menuOpen: context.menuOpen(),
|
|
1801
|
+
dialogOpen: dialogs.length > 0,
|
|
1802
|
+
modalOpen: dialogs.some(isModal),
|
|
1803
|
+
defaultPrevented: event.defaultPrevented,
|
|
1804
|
+
startScreen: context.startScreen?.() ?? false
|
|
1805
|
+
});
|
|
1806
|
+
if (decision.preventDefault) event.preventDefault();
|
|
1807
|
+
if (decision.stopPropagation) event.stopPropagation();
|
|
1808
|
+
if (decision.action === "closeMenu") context.closeMenu();
|
|
1809
|
+
else if (decision.action) context.run(decision.action);
|
|
1810
|
+
};
|
|
992
1811
|
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1812
|
+
|
|
1813
|
+
// src/dom/ledger.ts
|
|
1814
|
+
var Ledger = class {
|
|
1815
|
+
undo = [];
|
|
1816
|
+
originalAttributes = /* @__PURE__ */ new Map();
|
|
1817
|
+
closed = false;
|
|
1818
|
+
get isClosed() {
|
|
1819
|
+
return this.closed;
|
|
1001
1820
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1821
|
+
/** Runs `task` on `close()`, in reverse order of registration. */
|
|
1822
|
+
defer(task) {
|
|
1823
|
+
this.undo.push(task);
|
|
1824
|
+
}
|
|
1825
|
+
/** Adds `className` unless present; `close()` removes it only if this ledger added it. */
|
|
1826
|
+
addClass(element, className) {
|
|
1827
|
+
if (element.classList.contains(className)) return;
|
|
1828
|
+
const hadClassAttribute = element.hasAttribute("class");
|
|
1829
|
+
element.classList.add(className);
|
|
1830
|
+
this.defer(() => {
|
|
1831
|
+
element.classList.remove(className);
|
|
1832
|
+
if (!hadClassAttribute && element.classList.length === 0) element.removeAttribute("class");
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
/** Sets (string) or removes (`null`) an attribute; `close()` restores the value before the first change. */
|
|
1836
|
+
attribute(element, name, value) {
|
|
1837
|
+
let originals = this.originalAttributes.get(element);
|
|
1838
|
+
if (!originals) {
|
|
1839
|
+
originals = /* @__PURE__ */ new Map();
|
|
1840
|
+
this.originalAttributes.set(element, originals);
|
|
1841
|
+
}
|
|
1842
|
+
if (!originals.has(name)) {
|
|
1843
|
+
originals.set(name, element.getAttribute(name));
|
|
1844
|
+
this.defer(() => {
|
|
1845
|
+
const original = originals.get(name) ?? null;
|
|
1846
|
+
if (original === null) element.removeAttribute(name);
|
|
1847
|
+
else element.setAttribute(name, original);
|
|
1848
|
+
});
|
|
1849
|
+
}
|
|
1850
|
+
if (value === null) element.removeAttribute(name);
|
|
1851
|
+
else element.setAttribute(name, value);
|
|
1852
|
+
}
|
|
1853
|
+
listen(target, type, listener, options) {
|
|
1854
|
+
const handler = listener;
|
|
1855
|
+
target.addEventListener(type, handler, options);
|
|
1856
|
+
this.defer(() => target.removeEventListener(type, handler, options));
|
|
1857
|
+
}
|
|
1858
|
+
/** Runs every undo task once; later calls are no-ops. */
|
|
1859
|
+
close() {
|
|
1860
|
+
if (this.closed) return;
|
|
1861
|
+
this.closed = true;
|
|
1862
|
+
for (const task of this.undo.reverse()) {
|
|
1863
|
+
try {
|
|
1864
|
+
task();
|
|
1865
|
+
} catch {
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
this.undo.length = 0;
|
|
1869
|
+
this.originalAttributes.clear();
|
|
1870
|
+
}
|
|
1871
|
+
};
|
|
1004
1872
|
|
|
1005
1873
|
// src/dom/legend.ts
|
|
1006
1874
|
function createLegend(context) {
|
|
@@ -1061,8 +1929,11 @@ function createLegend(context) {
|
|
|
1061
1929
|
}
|
|
1062
1930
|
|
|
1063
1931
|
// src/dom/menu.ts
|
|
1064
|
-
var GROUP_ORDER = ["model", "export", "help"];
|
|
1065
|
-
function
|
|
1932
|
+
var GROUP_ORDER = ["file", "model", "export", "appearance", "help"];
|
|
1933
|
+
function isModelerGroup(name) {
|
|
1934
|
+
return name === "model" || name === "export" || name === "help";
|
|
1935
|
+
}
|
|
1936
|
+
function isPrintable2(event) {
|
|
1066
1937
|
return event.key.length === 1 && /\S/.test(event.key) && !event.ctrlKey && !event.metaKey && !event.altKey;
|
|
1067
1938
|
}
|
|
1068
1939
|
function createMenu(context) {
|
|
@@ -1091,8 +1962,9 @@ function createMenu(context) {
|
|
|
1091
1962
|
layer.prepend(dropdown);
|
|
1092
1963
|
const isOpen = () => !dropdown.hidden;
|
|
1093
1964
|
function entriesOf(name) {
|
|
1094
|
-
const own = groups[name] ?? [];
|
|
1095
|
-
|
|
1965
|
+
const own = isModelerGroup(name) ? groups[name] ?? [] : [];
|
|
1966
|
+
const added = context.shellEntries(name);
|
|
1967
|
+
return added.length > 0 ? [...own, ...added] : own;
|
|
1096
1968
|
}
|
|
1097
1969
|
function hasEntries() {
|
|
1098
1970
|
return GROUP_ORDER.some((name) => entriesOf(name).length > 0);
|
|
@@ -1146,14 +2018,12 @@ function createMenu(context) {
|
|
|
1146
2018
|
}
|
|
1147
2019
|
};
|
|
1148
2020
|
}
|
|
1149
|
-
const shortcut = entry.
|
|
2021
|
+
const shortcut = entry.shortcut ? createElement(ownerDocument, "span", { class: "cd-shell-menu-shortcut" }, entry.shortcut) : void 0;
|
|
1150
2022
|
const element = itemElement("menuitem", entry.label, { icon: entry.icon, end: shortcut, disabled });
|
|
1151
2023
|
element.dataset.menuId = entry.id;
|
|
1152
2024
|
element.dataset.menuGroup = group;
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
toggleAttribute(element, "aria-keyshortcuts", keyShortcuts || null);
|
|
1156
|
-
}
|
|
2025
|
+
const keyShortcuts = entry.ariaKeyShortcuts ?? (entry.shortcut ? ariaKeyShortcutsFromLabel(entry.shortcut) : void 0);
|
|
2026
|
+
toggleAttribute(element, "aria-keyshortcuts", keyShortcuts || null);
|
|
1157
2027
|
if (entry.kind === "dialog") element.setAttribute("aria-haspopup", "dialog");
|
|
1158
2028
|
if (entry.kind === "action" && entry.danger) element.dataset.danger = "";
|
|
1159
2029
|
return {
|
|
@@ -1338,7 +2208,7 @@ function createMenu(context) {
|
|
|
1338
2208
|
event.stopPropagation();
|
|
1339
2209
|
return;
|
|
1340
2210
|
default:
|
|
1341
|
-
if (!
|
|
2211
|
+
if (!isPrintable2(event)) return;
|
|
1342
2212
|
typeAhead(event.key);
|
|
1343
2213
|
}
|
|
1344
2214
|
consume(event);
|
|
@@ -1368,7 +2238,7 @@ function createMenu(context) {
|
|
|
1368
2238
|
|
|
1369
2239
|
// src/dom/problems.ts
|
|
1370
2240
|
function createProblemsPill(context) {
|
|
1371
|
-
const { ownerDocument, messages, lang, tooltip
|
|
2241
|
+
const { ownerDocument, messages, lang, tooltip } = context;
|
|
1372
2242
|
const icon = createElement(ownerDocument, "span", { class: "cd-shell-problems-icon" });
|
|
1373
2243
|
const text = createElement(ownerDocument, "span", { class: "cd-shell-problems-text" });
|
|
1374
2244
|
const element = createElement(
|
|
@@ -1379,7 +2249,7 @@ function createProblemsPill(context) {
|
|
|
1379
2249
|
text
|
|
1380
2250
|
);
|
|
1381
2251
|
element.hidden = true;
|
|
1382
|
-
element.addEventListener("click", () =>
|
|
2252
|
+
element.addEventListener("click", () => context.onActivate());
|
|
1383
2253
|
const detachTooltip = context.showTooltip ? tooltip.attach(element, { label: messages.showProblems }) : void 0;
|
|
1384
2254
|
let severity;
|
|
1385
2255
|
return {
|
|
@@ -1483,6 +2353,10 @@ function createTooltip({ root, layer, id }) {
|
|
|
1483
2353
|
let warmUntil = 0;
|
|
1484
2354
|
let pressed;
|
|
1485
2355
|
const detachers = /* @__PURE__ */ new Set();
|
|
2356
|
+
const resizeObserver = typeof ResizeObserver === "function" ? new ResizeObserver(() => {
|
|
2357
|
+
if (shown) place(shown.control);
|
|
2358
|
+
}) : void 0;
|
|
2359
|
+
resizeObserver?.observe(element);
|
|
1486
2360
|
function cancelPending() {
|
|
1487
2361
|
if (pending) clearTimeout(pending.timer);
|
|
1488
2362
|
pending = void 0;
|
|
@@ -1650,6 +2524,381 @@ function createTooltip({ root, layer, id }) {
|
|
|
1650
2524
|
destroy() {
|
|
1651
2525
|
hide();
|
|
1652
2526
|
for (const detach of [...detachers]) detach();
|
|
2527
|
+
resizeObserver?.disconnect();
|
|
2528
|
+
element.remove();
|
|
2529
|
+
}
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
// src/dom/web/drop-zone.ts
|
|
2534
|
+
var DRAG_EVENTS = ["dragenter", "dragover", "dragleave", "drop"];
|
|
2535
|
+
function carriesFiles(transfer) {
|
|
2536
|
+
if (!transfer) return false;
|
|
2537
|
+
if (Array.from(transfer.types ?? []).includes("Files")) return true;
|
|
2538
|
+
return Array.from(transfer.items ?? []).some((item) => item.kind === "file");
|
|
2539
|
+
}
|
|
2540
|
+
function dragItems(transfer) {
|
|
2541
|
+
const items = Array.from(transfer.items ?? [], (item) => ({ kind: item.kind, type: item.type }));
|
|
2542
|
+
return items.length > 0 ? items : [{ kind: "file", type: "" }];
|
|
2543
|
+
}
|
|
2544
|
+
function acceptList(accept, lang) {
|
|
2545
|
+
const entries = accept.map((entry) => entry.trim()).filter((entry) => entry !== "");
|
|
2546
|
+
const extensions = entries.filter((entry) => entry.startsWith("."));
|
|
2547
|
+
const listed = extensions.length > 0 ? extensions : entries;
|
|
2548
|
+
let format;
|
|
2549
|
+
try {
|
|
2550
|
+
format = new Intl.ListFormat(lang, { type: "disjunction" });
|
|
2551
|
+
} catch {
|
|
2552
|
+
format = new Intl.ListFormat("en", { type: "disjunction" });
|
|
2553
|
+
}
|
|
2554
|
+
return format.format(listed);
|
|
2555
|
+
}
|
|
2556
|
+
function createDropZone(context) {
|
|
2557
|
+
const { ownerDocument, layer, messages, lang, options, modalOpen, toast, setState } = context;
|
|
2558
|
+
const chip = createElement(ownerDocument, "span", { class: "cd-shell-drop-zone-chip" });
|
|
2559
|
+
const element = createElement(
|
|
2560
|
+
ownerDocument,
|
|
2561
|
+
"div",
|
|
2562
|
+
{ class: "cd-shell-drop-zone", "data-chrome": "drop-zone", "aria-hidden": "true" },
|
|
2563
|
+
chip
|
|
2564
|
+
);
|
|
2565
|
+
element.hidden = true;
|
|
2566
|
+
layer.append(element);
|
|
2567
|
+
const view = ownerDocument.defaultView;
|
|
2568
|
+
let depth = 0;
|
|
2569
|
+
let current = null;
|
|
2570
|
+
function verdictFor(items) {
|
|
2571
|
+
return dragVerdict(items, {
|
|
2572
|
+
accept: options.accept,
|
|
2573
|
+
multiple: options.multiple ?? false,
|
|
2574
|
+
dialogOpen: modalOpen()
|
|
2575
|
+
});
|
|
2576
|
+
}
|
|
2577
|
+
function show(verdict) {
|
|
2578
|
+
const next = verdict.state === "none" ? null : verdict.state;
|
|
2579
|
+
if (verdict.state === "over") chip.textContent = messages.dropFile;
|
|
2580
|
+
if (verdict.state === "forbidden") chip.textContent = messages.dropForbidden(verdict.reason);
|
|
2581
|
+
if (next === current) return;
|
|
2582
|
+
current = next;
|
|
2583
|
+
element.hidden = next === null;
|
|
2584
|
+
if (next === null) element.removeAttribute("data-state");
|
|
2585
|
+
else element.setAttribute("data-state", next);
|
|
2586
|
+
setState(next);
|
|
2587
|
+
}
|
|
2588
|
+
function track(transfer) {
|
|
2589
|
+
const verdict = verdictFor(dragItems(transfer));
|
|
2590
|
+
show(verdict);
|
|
2591
|
+
transfer.dropEffect = verdict.state === "forbidden" ? "none" : "copy";
|
|
2592
|
+
}
|
|
2593
|
+
function rejectByDefault(files) {
|
|
2594
|
+
const accepted = acceptList(options.accept, lang);
|
|
2595
|
+
for (const file of files) toast({ status: "danger", text: messages.cannotOpenFile(file.name, accepted) });
|
|
2596
|
+
}
|
|
2597
|
+
function receive(files, verdict) {
|
|
2598
|
+
if (verdict.state === "forbidden" && verdict.reason !== "type") return;
|
|
2599
|
+
const accepted = files.filter((file) => acceptsFile(file, options.accept));
|
|
2600
|
+
const rejected = files.filter((file) => !accepted.includes(file));
|
|
2601
|
+
if (accepted.length > 0) options.onDrop(accepted);
|
|
2602
|
+
if (rejected.length > 0) (options.onReject ?? rejectByDefault)(rejected);
|
|
2603
|
+
}
|
|
2604
|
+
const onDragEvent = (event) => {
|
|
2605
|
+
const transfer = event.dataTransfer;
|
|
2606
|
+
if (!carriesFiles(transfer)) return;
|
|
2607
|
+
event.preventDefault();
|
|
2608
|
+
switch (event.type) {
|
|
2609
|
+
case "dragenter":
|
|
2610
|
+
depth += 1;
|
|
2611
|
+
track(transfer);
|
|
2612
|
+
break;
|
|
2613
|
+
case "dragover":
|
|
2614
|
+
depth = Math.max(depth, 1);
|
|
2615
|
+
track(transfer);
|
|
2616
|
+
break;
|
|
2617
|
+
case "dragleave":
|
|
2618
|
+
depth = Math.max(0, depth - 1);
|
|
2619
|
+
if (depth === 0) show({ state: "none" });
|
|
2620
|
+
break;
|
|
2621
|
+
case "drop": {
|
|
2622
|
+
depth = 0;
|
|
2623
|
+
show({ state: "none" });
|
|
2624
|
+
const files = Array.from(transfer.files ?? []);
|
|
2625
|
+
const items = files.length > 0 ? files.map((file) => ({ kind: "file", type: file.type })) : dragItems(transfer);
|
|
2626
|
+
receive(files, verdictFor(items));
|
|
2627
|
+
break;
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
};
|
|
2631
|
+
for (const type of DRAG_EVENTS) view?.addEventListener(type, onDragEvent);
|
|
2632
|
+
return {
|
|
2633
|
+
element,
|
|
2634
|
+
state: () => current,
|
|
2635
|
+
destroy() {
|
|
2636
|
+
for (const type of DRAG_EVENTS) view?.removeEventListener(type, onDragEvent);
|
|
2637
|
+
depth = 0;
|
|
2638
|
+
if (current !== null) {
|
|
2639
|
+
current = null;
|
|
2640
|
+
setState(null);
|
|
2641
|
+
}
|
|
2642
|
+
element.remove();
|
|
2643
|
+
}
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
// src/dom/web/empty-state.ts
|
|
2648
|
+
var KOMET_SIZE = "64";
|
|
2649
|
+
function createEmptyState(context) {
|
|
2650
|
+
const { ownerDocument, messages, options, dropHint, open, titleId, onLeave } = context;
|
|
2651
|
+
const button = (variant, label, run) => {
|
|
2652
|
+
const element2 = createElement(
|
|
2653
|
+
ownerDocument,
|
|
2654
|
+
"button",
|
|
2655
|
+
{ type: "button", class: "cd-shell-button", "data-variant": variant },
|
|
2656
|
+
label
|
|
2657
|
+
);
|
|
2658
|
+
element2.addEventListener("click", () => {
|
|
2659
|
+
onLeave();
|
|
2660
|
+
run();
|
|
2661
|
+
});
|
|
2662
|
+
return element2;
|
|
2663
|
+
};
|
|
2664
|
+
const onShowExample = options.onShowExample;
|
|
2665
|
+
const newDiagram = button("primary", messages.newDiagram, () => options.onNewDiagram());
|
|
2666
|
+
const element = createElement(
|
|
2667
|
+
ownerDocument,
|
|
2668
|
+
"section",
|
|
2669
|
+
{ class: "cd-shell-empty-state", "data-chrome": "empty-state", "aria-labelledby": titleId },
|
|
2670
|
+
// Dragging the mark would start an image drag that some browsers carry as a file.
|
|
2671
|
+
createElement(ownerDocument, "img", {
|
|
2672
|
+
class: "cd-shell-empty-state-mark",
|
|
2673
|
+
src: options.komet,
|
|
2674
|
+
width: KOMET_SIZE,
|
|
2675
|
+
height: KOMET_SIZE,
|
|
2676
|
+
alt: "",
|
|
2677
|
+
draggable: "false"
|
|
2678
|
+
}),
|
|
2679
|
+
createElement(ownerDocument, "h1", { class: "cd-shell-empty-state-title", id: titleId }, options.title),
|
|
2680
|
+
createElement(ownerDocument, "p", { class: "cd-shell-empty-state-text" }, options.text),
|
|
2681
|
+
createElement(
|
|
2682
|
+
ownerDocument,
|
|
2683
|
+
"div",
|
|
2684
|
+
{ class: "cd-shell-empty-state-actions" },
|
|
2685
|
+
newDiagram,
|
|
2686
|
+
onShowExample && button("ghost", messages.showExample, () => onShowExample())
|
|
2687
|
+
),
|
|
2688
|
+
hintParagraph()
|
|
2689
|
+
);
|
|
2690
|
+
function hintParagraph() {
|
|
2691
|
+
const hasDropHint = dropHint !== void 0 && dropHint !== "";
|
|
2692
|
+
if (!hasDropHint && !open) return void 0;
|
|
2693
|
+
const paragraph = createElement(ownerDocument, "p", { class: "cd-shell-empty-state-hint" });
|
|
2694
|
+
if (hasDropHint) paragraph.append(dropHint);
|
|
2695
|
+
if (!open) return paragraph;
|
|
2696
|
+
const shortcut = open.shortcut?.trim();
|
|
2697
|
+
const link = createElement(
|
|
2698
|
+
ownerDocument,
|
|
2699
|
+
"button",
|
|
2700
|
+
{
|
|
2701
|
+
type: "button",
|
|
2702
|
+
class: "cd-shell-empty-state-open",
|
|
2703
|
+
"aria-keyshortcuts": shortcut ? ariaKeyShortcutsFromLabel(shortcut) : void 0,
|
|
2704
|
+
"aria-haspopup": open.dialog ? "dialog" : void 0
|
|
2705
|
+
},
|
|
2706
|
+
messages.openFileLink
|
|
2707
|
+
);
|
|
2708
|
+
link.addEventListener("click", () => open.onSelect());
|
|
2709
|
+
if (hasDropHint) {
|
|
2710
|
+
paragraph.append(" ", createElement(ownerDocument, "span", { "aria-hidden": "true" }, "\xB7"), " ");
|
|
2711
|
+
}
|
|
2712
|
+
const openItem = createElement(ownerDocument, "span", { class: "cd-shell-empty-state-open-item" }, link);
|
|
2713
|
+
if (shortcut) {
|
|
2714
|
+
openItem.append(
|
|
2715
|
+
" ",
|
|
2716
|
+
createElement(
|
|
2717
|
+
ownerDocument,
|
|
2718
|
+
"kbd",
|
|
2719
|
+
{ class: "cd-shell-empty-state-key", "aria-hidden": "true" },
|
|
2720
|
+
shortcut
|
|
2721
|
+
)
|
|
2722
|
+
);
|
|
2723
|
+
}
|
|
2724
|
+
paragraph.append(openItem);
|
|
2725
|
+
return paragraph;
|
|
2726
|
+
}
|
|
2727
|
+
element.hidden = true;
|
|
2728
|
+
return {
|
|
2729
|
+
element,
|
|
2730
|
+
isShown: () => !element.hidden,
|
|
2731
|
+
setShown(shown) {
|
|
2732
|
+
element.hidden = !shown;
|
|
2733
|
+
},
|
|
2734
|
+
focus() {
|
|
2735
|
+
newDiagram.focus({ preventScroll: true });
|
|
2736
|
+
},
|
|
2737
|
+
destroy() {
|
|
2738
|
+
element.remove();
|
|
2739
|
+
}
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
// src/dom/web/legal.ts
|
|
2744
|
+
var MIRAGON_HOME = "https://www.miragon.io/";
|
|
2745
|
+
var MIRAGON_IMPRESSUM = "https://www.miragon.io/impressum/";
|
|
2746
|
+
var MIRAGON_DATENSCHUTZ = "https://www.miragon.io/datenschutz/";
|
|
2747
|
+
function createLegalLine(context) {
|
|
2748
|
+
const { ownerDocument, messages, options } = context;
|
|
2749
|
+
const year = options.year ?? (/* @__PURE__ */ new Date()).getFullYear();
|
|
2750
|
+
const newTabId = `${context.idPrefix}-new-tab`;
|
|
2751
|
+
const link = (label, href, lang) => createElement(
|
|
2752
|
+
ownerDocument,
|
|
2753
|
+
"a",
|
|
2754
|
+
{ href, lang, target: "_blank", rel: "noopener noreferrer", "aria-describedby": newTabId },
|
|
2755
|
+
label
|
|
2756
|
+
);
|
|
2757
|
+
const separator = () => createElement(ownerDocument, "span", { "aria-hidden": "true" }, "\xB7");
|
|
2758
|
+
const item = (anchor) => createElement(ownerDocument, "span", { class: "cd-shell-legal-item" }, separator(), anchor);
|
|
2759
|
+
const links = [
|
|
2760
|
+
link(messages.copyright(year), MIRAGON_HOME),
|
|
2761
|
+
link("Impressum", MIRAGON_IMPRESSUM, "de"),
|
|
2762
|
+
link("Datenschutz", MIRAGON_DATENSCHUTZ, "de"),
|
|
2763
|
+
...(options.links ?? []).map((extra) => link(extra.label, extra.href, extra.lang))
|
|
2764
|
+
];
|
|
2765
|
+
const element = createElement(ownerDocument, "nav", {
|
|
2766
|
+
class: "cd-shell-legal",
|
|
2767
|
+
"data-chrome": "legal",
|
|
2768
|
+
"aria-label": messages.legal
|
|
2769
|
+
});
|
|
2770
|
+
element.append(
|
|
2771
|
+
...links.map((anchor, index) => index === 0 ? anchor : item(anchor)),
|
|
2772
|
+
createElement(ownerDocument, "span", { id: newTabId, hidden: "" }, messages.opensInNewTab)
|
|
2773
|
+
);
|
|
2774
|
+
return {
|
|
2775
|
+
element,
|
|
2776
|
+
destroy() {
|
|
2777
|
+
element.remove();
|
|
2778
|
+
}
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
// src/dom/web/menu-groups.ts
|
|
2783
|
+
function fileEntry(spec, action) {
|
|
2784
|
+
const common = {
|
|
2785
|
+
id: spec.id,
|
|
2786
|
+
label: spec.label,
|
|
2787
|
+
...spec.icon ? { icon: spec.icon } : {},
|
|
2788
|
+
...action.disabled ? { disabled: action.disabled } : {},
|
|
2789
|
+
...action.shortcut ? { shortcut: action.shortcut } : {},
|
|
2790
|
+
onSelect: () => action.onSelect()
|
|
2791
|
+
};
|
|
2792
|
+
return action.dialog ? { kind: "dialog", ...common } : { kind: "action", ...common };
|
|
2793
|
+
}
|
|
2794
|
+
function fileMenuEntries(options, messages) {
|
|
2795
|
+
const specs = [
|
|
2796
|
+
{ option: "newDiagram", id: "new-diagram", label: messages.newDiagram, icon: "file-plus" },
|
|
2797
|
+
{ option: "open", id: "open", label: messages.openFile, icon: "folder-open" },
|
|
2798
|
+
// §5.14 gives "Show example" no icon.
|
|
2799
|
+
{ option: "showExample", id: "show-example", label: messages.showExample, icon: void 0 },
|
|
2800
|
+
{ option: "import", id: "import", label: messages.importFile, icon: "import" }
|
|
2801
|
+
];
|
|
2802
|
+
return specs.flatMap((spec) => {
|
|
2803
|
+
const action = options[spec.option];
|
|
2804
|
+
return action ? [fileEntry(spec, action)] : [];
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
function appearanceMenuEntries(appearance, messages) {
|
|
2808
|
+
return [
|
|
2809
|
+
{
|
|
2810
|
+
kind: "radio",
|
|
2811
|
+
id: "appearance",
|
|
2812
|
+
label: messages.appearance,
|
|
2813
|
+
options: [
|
|
2814
|
+
{ value: "system", label: messages.appearanceSystem },
|
|
2815
|
+
{ value: "light", label: messages.appearanceLight },
|
|
2816
|
+
{ value: "dark", label: messages.appearanceDark }
|
|
2817
|
+
],
|
|
2818
|
+
value: () => appearance.choice(),
|
|
2819
|
+
onSelect: (value) => appearance.setChoice(parseAppearanceChoice(value))
|
|
2820
|
+
}
|
|
2821
|
+
];
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
// src/dom/web/primary.ts
|
|
2825
|
+
var COLLAPSED_ATTRIBUTE = "data-collapsed";
|
|
2826
|
+
function createPrimaryAction(context) {
|
|
2827
|
+
const { ownerDocument, messages, tooltip, options } = context;
|
|
2828
|
+
const label = options.label ?? messages.share;
|
|
2829
|
+
const labelElement = createElement(ownerDocument, "span", { class: "cd-shell-primary-label" }, label);
|
|
2830
|
+
const element = createElement(
|
|
2831
|
+
ownerDocument,
|
|
2832
|
+
"button",
|
|
2833
|
+
{
|
|
2834
|
+
type: "button",
|
|
2835
|
+
class: "cd-shell-primary cd-shell-button",
|
|
2836
|
+
"data-variant": "primary",
|
|
2837
|
+
"data-chrome": "primary",
|
|
2838
|
+
"aria-label": label
|
|
2839
|
+
},
|
|
2840
|
+
iconElement(options.icon ?? "share-2"),
|
|
2841
|
+
labelElement
|
|
2842
|
+
);
|
|
2843
|
+
let collapsed = false;
|
|
2844
|
+
let disabled = false;
|
|
2845
|
+
let labelledWidth;
|
|
2846
|
+
function readDisabled() {
|
|
2847
|
+
disabled = options.disabled?.() ?? false;
|
|
2848
|
+
toggleAttribute(element, "aria-disabled", disabled ? "true" : null);
|
|
2849
|
+
}
|
|
2850
|
+
function showCollapsed(state) {
|
|
2851
|
+
labelElement.hidden = state;
|
|
2852
|
+
toggleAttribute(element, COLLAPSED_ATTRIBUTE, state ? "" : null);
|
|
2853
|
+
}
|
|
2854
|
+
function measureLabelledWidth() {
|
|
2855
|
+
if (!collapsed) return element.offsetWidth;
|
|
2856
|
+
showCollapsed(false);
|
|
2857
|
+
const width = element.offsetWidth;
|
|
2858
|
+
showCollapsed(true);
|
|
2859
|
+
return width;
|
|
2860
|
+
}
|
|
2861
|
+
function isEngaged() {
|
|
2862
|
+
if (ownerDocument.activeElement === element) return true;
|
|
2863
|
+
try {
|
|
2864
|
+
return element.matches(":hover");
|
|
2865
|
+
} catch {
|
|
2866
|
+
return false;
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
const onClick = (event) => {
|
|
2870
|
+
if (disabled) {
|
|
2871
|
+
event.preventDefault();
|
|
2872
|
+
return;
|
|
2873
|
+
}
|
|
2874
|
+
options.onSelect();
|
|
2875
|
+
};
|
|
2876
|
+
element.addEventListener("click", onClick);
|
|
2877
|
+
const detachTooltip = tooltip.attach(element, () => collapsed ? { label } : null);
|
|
2878
|
+
readDisabled();
|
|
2879
|
+
return {
|
|
2880
|
+
element,
|
|
2881
|
+
isCollapsed: () => collapsed,
|
|
2882
|
+
refresh() {
|
|
2883
|
+
readDisabled();
|
|
2884
|
+
labelledWidth = void 0;
|
|
2885
|
+
},
|
|
2886
|
+
widths() {
|
|
2887
|
+
if (!collapsed || labelledWidth === void 0) {
|
|
2888
|
+
const measured = measureLabelledWidth();
|
|
2889
|
+
if (measured > 0) labelledWidth = measured;
|
|
2890
|
+
}
|
|
2891
|
+
return { withLabel: labelledWidth ?? 0, iconOnly: SHELL.row };
|
|
2892
|
+
},
|
|
2893
|
+
setCollapsed(next) {
|
|
2894
|
+
if (next === collapsed) return;
|
|
2895
|
+
collapsed = next;
|
|
2896
|
+
showCollapsed(next);
|
|
2897
|
+
if (!next && isEngaged()) tooltip.hide();
|
|
2898
|
+
},
|
|
2899
|
+
destroy() {
|
|
2900
|
+
detachTooltip();
|
|
2901
|
+
element.removeEventListener("click", onClick);
|
|
1653
2902
|
element.remove();
|
|
1654
2903
|
}
|
|
1655
2904
|
};
|
|
@@ -1772,6 +3021,16 @@ function nextMountIndex() {
|
|
|
1772
3021
|
return index;
|
|
1773
3022
|
}
|
|
1774
3023
|
var DEFAULT_THEMES = ["light"];
|
|
3024
|
+
var LEGAL_HEIGHT_PROPERTY = "--modeler-shell-legal-height";
|
|
3025
|
+
var INERT_TOAST = Object.freeze({ dismiss() {
|
|
3026
|
+
} });
|
|
3027
|
+
function visibleBoxIn(element, root) {
|
|
3028
|
+
if (!element || element.hidden || !element.isConnected) return void 0;
|
|
3029
|
+
if (typeof element.checkVisibility === "function" && !element.checkVisibility({ visibilityProperty: true }))
|
|
3030
|
+
return void 0;
|
|
3031
|
+
const box = boxRelativeTo(element, root);
|
|
3032
|
+
return box.width > 0 && box.height > 0 ? box : void 0;
|
|
3033
|
+
}
|
|
1775
3034
|
function languageOf(root, host, requested) {
|
|
1776
3035
|
if (requested) return requested;
|
|
1777
3036
|
const declared = host === "embedded" ? root.getAttribute("lang") : root.closest("[lang]")?.getAttribute("lang");
|
|
@@ -1800,6 +3059,7 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1800
3059
|
const platform = options.platform ?? detectPlatform(globalThis.navigator);
|
|
1801
3060
|
const lang = languageOf(root, host, options.lang);
|
|
1802
3061
|
const keyScope = options.keyScope ?? (host === "embedded" ? "root" : "document");
|
|
3062
|
+
const web = host === "web";
|
|
1803
3063
|
ledger.addClass(root, "cd-shell");
|
|
1804
3064
|
ledger.attribute(root, "data-cd-shell-host", host);
|
|
1805
3065
|
ledger.attribute(root, "data-palette", "top");
|
|
@@ -1812,6 +3072,9 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1812
3072
|
const setState = (name, value) => {
|
|
1813
3073
|
if (!ledger.isClosed) ledger.attribute(root, name, value);
|
|
1814
3074
|
};
|
|
3075
|
+
const appearance = web ? connectWebAppearance({ ownerDocument, themes, options: options.appearance }) : void 0;
|
|
3076
|
+
if (appearance) ledger.defer(() => appearance.destroy());
|
|
3077
|
+
const darkCapable = themes.includes("dark");
|
|
1815
3078
|
const layer = createElement(ownerDocument, "div", { class: "cd-shell-layer" });
|
|
1816
3079
|
ledger.defer(() => layer.remove());
|
|
1817
3080
|
const tooltip = createTooltip({ root, layer, id: `cd-shell-tooltip-${index}` });
|
|
@@ -1843,6 +3106,14 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1843
3106
|
keepEnabledWhenReadOnly: true
|
|
1844
3107
|
} : void 0;
|
|
1845
3108
|
const shellModelEntries = legendEntry ? [legendEntry] : [];
|
|
3109
|
+
const fileEntries = web && options.file ? fileMenuEntries(options.file, messages) : [];
|
|
3110
|
+
const appearanceEntries = appearance && darkCapable ? appearanceMenuEntries(appearance, messages) : [];
|
|
3111
|
+
const shellEntries = (group) => {
|
|
3112
|
+
if (group === "file") return fileEntries;
|
|
3113
|
+
if (group === "model") return shellModelEntries;
|
|
3114
|
+
if (group === "appearance") return appearanceEntries;
|
|
3115
|
+
return [];
|
|
3116
|
+
};
|
|
1846
3117
|
const menu = createMenu({
|
|
1847
3118
|
root,
|
|
1848
3119
|
layer,
|
|
@@ -1850,55 +3121,133 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1850
3121
|
messages,
|
|
1851
3122
|
tooltip,
|
|
1852
3123
|
groups: options.menu ?? {},
|
|
1853
|
-
|
|
3124
|
+
shellEntries,
|
|
1854
3125
|
isReadOnly: () => readOnly
|
|
1855
3126
|
});
|
|
1856
3127
|
ledger.defer(() => menu.destroy());
|
|
1857
3128
|
ledger.defer(tooltip.attach(menu.trigger, () => menu.isOpen() ? null : { label: messages.menu }));
|
|
3129
|
+
const primary = web && options.primary ? createPrimaryAction({ ownerDocument, messages, tooltip, options: options.primary }) : void 0;
|
|
3130
|
+
if (primary) ledger.defer(() => primary.destroy());
|
|
1858
3131
|
const canvas = options.canvas ?? createElement(ownerDocument, "div");
|
|
1859
3132
|
ledger.addClass(canvas, "cd-shell-canvas");
|
|
3133
|
+
if (web && !canvas.hasAttribute("role")) ledger.attribute(canvas, "role", "main");
|
|
1860
3134
|
if (!options.canvas) ledger.defer(() => canvas.remove());
|
|
3135
|
+
function focusCanvasOrTrigger() {
|
|
3136
|
+
const took = (target) => {
|
|
3137
|
+
target?.focus({ preventScroll: true });
|
|
3138
|
+
return target !== null && target !== void 0 && ownerDocument.activeElement === target;
|
|
3139
|
+
};
|
|
3140
|
+
if (emptyState?.isShown()) {
|
|
3141
|
+
emptyState.focus();
|
|
3142
|
+
if (emptyState.element.contains(ownerDocument.activeElement)) return;
|
|
3143
|
+
}
|
|
3144
|
+
if (took(canvas.querySelector("svg[tabindex]"))) return;
|
|
3145
|
+
if (!menu.trigger.hidden) took(menu.trigger);
|
|
3146
|
+
}
|
|
1861
3147
|
function moveFocusOutOf(island) {
|
|
1862
|
-
if (
|
|
1863
|
-
const canvasTarget = canvas.querySelector("svg[tabindex]");
|
|
1864
|
-
canvasTarget?.focus({ preventScroll: true });
|
|
1865
|
-
if (canvasTarget && ownerDocument.activeElement === canvasTarget) return;
|
|
1866
|
-
if (!menu.trigger.hidden) menu.trigger.focus({ preventScroll: true });
|
|
3148
|
+
if (island.contains(ownerDocument.activeElement)) focusCanvasOrTrigger();
|
|
1867
3149
|
}
|
|
1868
3150
|
const status = createStatusBox(ownerDocument);
|
|
1869
3151
|
ledger.defer(() => status.destroy());
|
|
1870
3152
|
const hint = options.emptyHint ? createEmptyHint(ownerDocument, messages, options.emptyHint) : void 0;
|
|
1871
3153
|
if (hint) ledger.defer(() => hint.destroy());
|
|
3154
|
+
const emptyState = web && options.emptyState ? createEmptyState({
|
|
3155
|
+
ownerDocument,
|
|
3156
|
+
messages,
|
|
3157
|
+
options: options.emptyState,
|
|
3158
|
+
dropHint: options.dropZone ? options.emptyState.dropHint ?? messages.dropHint : void 0,
|
|
3159
|
+
open: options.file?.open,
|
|
3160
|
+
titleId: `cd-shell-empty-state-title-${index}`,
|
|
3161
|
+
onLeave: () => setEmptyStateShown(false)
|
|
3162
|
+
}) : void 0;
|
|
3163
|
+
if (emptyState) ledger.defer(() => emptyState.destroy());
|
|
1872
3164
|
const inspector = options.inspector ? createInspector(ownerDocument, options.inspector, (open) => {
|
|
1873
3165
|
setState("data-inspector", open ? "open" : null);
|
|
3166
|
+
updateBottomRow();
|
|
1874
3167
|
placeHint();
|
|
1875
3168
|
}) : void 0;
|
|
1876
3169
|
if (inspector) ledger.defer(() => inspector.destroy());
|
|
1877
|
-
const
|
|
3170
|
+
const problemsOptions = options.problems;
|
|
3171
|
+
let popover;
|
|
3172
|
+
const pill = problemsOptions ? createProblemsPill({
|
|
1878
3173
|
ownerDocument,
|
|
1879
3174
|
messages,
|
|
1880
3175
|
lang,
|
|
1881
3176
|
tooltip,
|
|
1882
3177
|
showTooltip: host === "ide",
|
|
1883
|
-
|
|
3178
|
+
onActivate() {
|
|
3179
|
+
if (popover) popover.toggle();
|
|
3180
|
+
else problemsOptions.onActivate?.();
|
|
3181
|
+
}
|
|
1884
3182
|
}) : void 0;
|
|
1885
3183
|
if (pill) ledger.defer(() => pill.destroy());
|
|
3184
|
+
if (pill && (web || host === "embedded" && !problemsOptions?.onActivate)) {
|
|
3185
|
+
const created = createProblemsPopover({
|
|
3186
|
+
ownerDocument,
|
|
3187
|
+
root,
|
|
3188
|
+
layer,
|
|
3189
|
+
pill: pill.element,
|
|
3190
|
+
id: `cd-shell-problems-${index}`,
|
|
3191
|
+
titleId: `cd-shell-problems-title-${index}`,
|
|
3192
|
+
messages,
|
|
3193
|
+
onSelectItem: problemsOptions?.onSelectItem
|
|
3194
|
+
});
|
|
3195
|
+
popover = created;
|
|
3196
|
+
ledger.defer(() => created.destroy());
|
|
3197
|
+
}
|
|
3198
|
+
const legal = web ? createLegalLine({
|
|
3199
|
+
ownerDocument,
|
|
3200
|
+
messages,
|
|
3201
|
+
options: options.legal ?? {},
|
|
3202
|
+
idPrefix: `cd-shell-legal-${index}`
|
|
3203
|
+
}) : void 0;
|
|
3204
|
+
if (legal) ledger.defer(() => legal.destroy());
|
|
1886
3205
|
const zoomPanel = createZoomPanel({ ownerDocument, messages, lang, platform, tooltip });
|
|
1887
3206
|
ledger.defer(() => zoomPanel.destroy());
|
|
1888
|
-
|
|
3207
|
+
const toaster = host === "ide" ? void 0 : createToaster({
|
|
3208
|
+
ownerDocument,
|
|
3209
|
+
host,
|
|
3210
|
+
messages,
|
|
3211
|
+
idPrefix: `cd-shell-toast-${index}`,
|
|
3212
|
+
focusFallback: focusCanvasOrTrigger,
|
|
3213
|
+
onChange: () => updateBottomRow()
|
|
3214
|
+
});
|
|
3215
|
+
if (toaster) ledger.defer(() => toaster.destroy());
|
|
3216
|
+
const defined = (element) => element !== void 0;
|
|
3217
|
+
root.prepend(...[menu.trigger, primary?.element, canvas].filter(defined));
|
|
1889
3218
|
root.append(
|
|
1890
3219
|
...[
|
|
1891
3220
|
status.element,
|
|
1892
3221
|
hint?.element,
|
|
3222
|
+
emptyState?.element,
|
|
1893
3223
|
inspector?.element,
|
|
1894
3224
|
pill?.element,
|
|
1895
3225
|
legend?.element,
|
|
3226
|
+
legal?.element,
|
|
1896
3227
|
zoomPanel.element
|
|
1897
|
-
].filter(
|
|
1898
|
-
layer
|
|
3228
|
+
].filter(defined),
|
|
3229
|
+
layer,
|
|
3230
|
+
...[toaster?.announcer, toaster?.element].filter(defined)
|
|
1899
3231
|
);
|
|
3232
|
+
function showToast(request) {
|
|
3233
|
+
return ledger.isClosed || !toaster ? INERT_TOAST : toaster.show(request);
|
|
3234
|
+
}
|
|
3235
|
+
const dropZone = web && options.dropZone ? createDropZone({
|
|
3236
|
+
ownerDocument,
|
|
3237
|
+
layer,
|
|
3238
|
+
messages,
|
|
3239
|
+
lang,
|
|
3240
|
+
options: options.dropZone,
|
|
3241
|
+
modalOpen: () => isModalOpen(ownerDocument),
|
|
3242
|
+
toast: showToast,
|
|
3243
|
+
setState: (state) => setState("data-drop", state)
|
|
3244
|
+
}) : void 0;
|
|
3245
|
+
if (dropZone) ledger.defer(() => dropZone.destroy());
|
|
1900
3246
|
let palette;
|
|
1901
3247
|
let paletteRow = "top";
|
|
3248
|
+
let zoomRaised = false;
|
|
3249
|
+
let legalHeight = LEGAL_LINE_HEIGHT;
|
|
3250
|
+
if (legal) ledger.defer(() => root.style.removeProperty(LEGAL_HEIGHT_PROPERTY));
|
|
1902
3251
|
function containerSize() {
|
|
1903
3252
|
return { width: root.clientWidth, height: root.clientHeight };
|
|
1904
3253
|
}
|
|
@@ -1918,6 +3267,10 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1918
3267
|
paletteRow,
|
|
1919
3268
|
problemsVisible: pill?.isShown() ?? false
|
|
1920
3269
|
};
|
|
3270
|
+
if (web) {
|
|
3271
|
+
layout.zoomRaised = zoomRaised;
|
|
3272
|
+
layout.legalHeight = legalHeight;
|
|
3273
|
+
}
|
|
1921
3274
|
if (legend?.isOpen()) layout.legendWidth = legend.element.getBoundingClientRect().width;
|
|
1922
3275
|
if (inspector?.isOpen()) {
|
|
1923
3276
|
layout.inspector = container.width > SHELL.narrow ? {} : { sheetTop: boxRelativeTo(inspector.element, root).y };
|
|
@@ -1931,18 +3284,76 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1931
3284
|
if (ledger.isClosed) return;
|
|
1932
3285
|
const size = containerSize();
|
|
1933
3286
|
if (size.width === 0 || size.height === 0) return;
|
|
1934
|
-
|
|
3287
|
+
const topRow = decideTopRow({
|
|
1935
3288
|
containerWidth: size.width,
|
|
1936
3289
|
paletteWidth: paletteWidth(),
|
|
1937
|
-
menuRight: menuRight()
|
|
3290
|
+
menuRight: menuRight(),
|
|
3291
|
+
primary: primary?.widths()
|
|
1938
3292
|
});
|
|
3293
|
+
paletteRow = topRow.paletteRow;
|
|
1939
3294
|
setState("data-palette", paletteRow);
|
|
3295
|
+
if (primary) {
|
|
3296
|
+
const collapsed = topRow.primaryLabel === "collapsed";
|
|
3297
|
+
primary.setCollapsed(collapsed);
|
|
3298
|
+
setState("data-primary", collapsed ? "collapsed" : null);
|
|
3299
|
+
}
|
|
1940
3300
|
if (legend && !legend.isStarted()) legend.start(options.legend?.open ?? legendStartsOpen(size.height));
|
|
3301
|
+
updateBottomRow();
|
|
1941
3302
|
placeHint();
|
|
1942
3303
|
}
|
|
3304
|
+
function updateBottomRow() {
|
|
3305
|
+
if (ledger.isClosed) return;
|
|
3306
|
+
const size = containerSize();
|
|
3307
|
+
if (size.width === 0 || size.height === 0) return;
|
|
3308
|
+
if (legal) {
|
|
3309
|
+
const legalBox = visibleBoxIn(legal.element, root);
|
|
3310
|
+
const height = legalLineHeight(legalBox?.height);
|
|
3311
|
+
if (height !== legalHeight || !root.style.getPropertyValue(LEGAL_HEIGHT_PROPERTY)) {
|
|
3312
|
+
legalHeight = height;
|
|
3313
|
+
root.style.setProperty(LEGAL_HEIGHT_PROPERTY, `${height}px`);
|
|
3314
|
+
}
|
|
3315
|
+
zoomRaised = zoomRowRaised({
|
|
3316
|
+
containerWidth: size.width,
|
|
3317
|
+
legalRight: legalBox && legalBox.x + legalBox.width,
|
|
3318
|
+
zoomWidth: visibleBoxIn(zoomPanel.element, root)?.width
|
|
3319
|
+
});
|
|
3320
|
+
setState("data-zoom-row", zoomRaised ? "raised" : null);
|
|
3321
|
+
}
|
|
3322
|
+
if (toaster) {
|
|
3323
|
+
const obstacles = [zoomPanel.element, legal?.element, pill?.element].flatMap(
|
|
3324
|
+
(element) => {
|
|
3325
|
+
const box = visibleBoxIn(element, root);
|
|
3326
|
+
return box ? [{ left: box.x, right: box.x + box.width, top: size.height - box.y }] : [];
|
|
3327
|
+
}
|
|
3328
|
+
);
|
|
3329
|
+
toaster.setBottom(
|
|
3330
|
+
toastStackBottom({ containerWidth: size.width, obstacles, cardWidth: toaster.widestCard() })
|
|
3331
|
+
);
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
function setEmptyStateShown(shown) {
|
|
3335
|
+
if (ledger.isClosed || !emptyState || emptyState.isShown() === shown) return;
|
|
3336
|
+
const active = ownerDocument.activeElement;
|
|
3337
|
+
const hadFocus = emptyState.element.contains(active);
|
|
3338
|
+
const workHadFocus = shown && active !== null && root.contains(active) && ![emptyState.element, legal?.element, toaster?.element].some((kept) => kept?.contains(active));
|
|
3339
|
+
emptyState.setShown(shown);
|
|
3340
|
+
setState("data-empty-state", shown ? "shown" : null);
|
|
3341
|
+
ledger.attribute(canvas, "inert", shown ? "" : null);
|
|
3342
|
+
if (shown) {
|
|
3343
|
+
menu.close();
|
|
3344
|
+
popover?.close();
|
|
3345
|
+
tooltip.hide();
|
|
3346
|
+
if (workHadFocus) emptyState.focus();
|
|
3347
|
+
} else if (hadFocus) {
|
|
3348
|
+
focusCanvasOrTrigger();
|
|
3349
|
+
}
|
|
3350
|
+
updateLayout();
|
|
3351
|
+
}
|
|
1943
3352
|
const ResizeObserverClass = globalThis.ResizeObserver;
|
|
1944
3353
|
const resizeObserver = ResizeObserverClass ? new ResizeObserverClass(() => updateLayout()) : void 0;
|
|
1945
3354
|
resizeObserver?.observe(root);
|
|
3355
|
+
if (primary) resizeObserver?.observe(primary.element);
|
|
3356
|
+
if (legal) resizeObserver?.observe(legal.element);
|
|
1946
3357
|
ledger.defer(() => resizeObserver?.disconnect());
|
|
1947
3358
|
if (legend && options.legend?.open !== void 0) legend.start(options.legend.open);
|
|
1948
3359
|
updateLayout();
|
|
@@ -1951,12 +3362,16 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1951
3362
|
host,
|
|
1952
3363
|
dialogScope: keyTarget,
|
|
1953
3364
|
menuOpen: () => menu.isOpen(),
|
|
3365
|
+
startScreen: () => emptyState?.isShown() ?? false,
|
|
1954
3366
|
closeMenu() {
|
|
1955
3367
|
const active = ownerDocument.activeElement;
|
|
1956
3368
|
menu.close({ restoreFocus: active === menu.trigger || menu.dropdown.contains(active) });
|
|
1957
3369
|
},
|
|
1958
3370
|
hideTooltip: () => tooltip.hide(),
|
|
1959
|
-
|
|
3371
|
+
// Behind the start screen the keys reach the browser instead: there is no diagram to zoom.
|
|
3372
|
+
run: (key) => {
|
|
3373
|
+
if (!emptyState?.isShown()) zoomPanel.run(key);
|
|
3374
|
+
}
|
|
1960
3375
|
});
|
|
1961
3376
|
keyTarget.addEventListener("keydown", onKeydown);
|
|
1962
3377
|
ledger.defer(() => keyTarget.removeEventListener("keydown", onKeydown));
|
|
@@ -2008,6 +3423,21 @@ function renderShell(root, options, themes, ledger) {
|
|
|
2008
3423
|
if (!ledger.isClosed) legend.toggle();
|
|
2009
3424
|
}
|
|
2010
3425
|
};
|
|
3426
|
+
const primaryHandle = primary && {
|
|
3427
|
+
element: primary.element,
|
|
3428
|
+
isCollapsed: () => primary.isCollapsed(),
|
|
3429
|
+
refresh: () => {
|
|
3430
|
+
if (!ledger.isClosed) primary.refresh();
|
|
3431
|
+
}
|
|
3432
|
+
};
|
|
3433
|
+
const appearanceHandle = appearance && darkCapable ? {
|
|
3434
|
+
choice: () => appearance.choice(),
|
|
3435
|
+
setChoice: (choice) => {
|
|
3436
|
+
if (ledger.isClosed) return;
|
|
3437
|
+
appearance.setChoice(choice);
|
|
3438
|
+
menu.refresh();
|
|
3439
|
+
}
|
|
3440
|
+
} : void 0;
|
|
2011
3441
|
const inspectorHandle = inspector && {
|
|
2012
3442
|
element: inspector.element,
|
|
2013
3443
|
isOpen: () => inspector.isOpen(),
|
|
@@ -2031,6 +3461,8 @@ function renderShell(root, options, themes, ledger) {
|
|
|
2031
3461
|
tooltip: tooltipHandle,
|
|
2032
3462
|
legend: legendHandle,
|
|
2033
3463
|
inspector: inspectorHandle,
|
|
3464
|
+
primary: primaryHandle,
|
|
3465
|
+
appearance: appearanceHandle,
|
|
2034
3466
|
setZoomTarget(target) {
|
|
2035
3467
|
if (!ledger.isClosed) zoomPanel.setTarget(target);
|
|
2036
3468
|
},
|
|
@@ -2059,8 +3491,15 @@ function renderShell(root, options, themes, ledger) {
|
|
|
2059
3491
|
},
|
|
2060
3492
|
setProblems(summary) {
|
|
2061
3493
|
if (ledger.isClosed || !pill) return;
|
|
3494
|
+
const listHadFocus = popover?.element.contains(ownerDocument.activeElement) ?? false;
|
|
2062
3495
|
pill.set(summary);
|
|
2063
3496
|
setState("data-problems", pill.isShown() ? "shown" : null);
|
|
3497
|
+
popover?.setItems(summary.items ?? []);
|
|
3498
|
+
if (!pill.isShown() && popover?.isOpen()) {
|
|
3499
|
+
popover.close();
|
|
3500
|
+
if (listHadFocus) focusCanvasOrTrigger();
|
|
3501
|
+
}
|
|
3502
|
+
updateBottomRow();
|
|
2064
3503
|
placeHint();
|
|
2065
3504
|
},
|
|
2066
3505
|
setStatus(next, statusOptions) {
|
|
@@ -2090,6 +3529,9 @@ function renderShell(root, options, themes, ledger) {
|
|
|
2090
3529
|
applyReadOnly();
|
|
2091
3530
|
},
|
|
2092
3531
|
isReadOnly: () => readOnly,
|
|
3532
|
+
setEmptyState: setEmptyStateShown,
|
|
3533
|
+
isEmptyStateShown: () => emptyState?.isShown() ?? false,
|
|
3534
|
+
toast: showToast,
|
|
2093
3535
|
destroy() {
|
|
2094
3536
|
ledger.close();
|
|
2095
3537
|
}
|
|
@@ -2116,21 +3558,27 @@ function carryOwnMode(root, theme, ledger) {
|
|
|
2116
3558
|
}
|
|
2117
3559
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2118
3560
|
0 && (module.exports = {
|
|
3561
|
+
APPEARANCE_STORAGE_KEY,
|
|
2119
3562
|
CHROME_ISLANDS,
|
|
2120
3563
|
FIT_ANIMATION_MS,
|
|
2121
3564
|
ICONS,
|
|
2122
3565
|
INVALID_STATUS_DELAY_MS,
|
|
3566
|
+
LEGAL_LINE_HEIGHT,
|
|
2123
3567
|
MAX_FIT_ZOOM,
|
|
3568
|
+
MAX_TOASTS,
|
|
2124
3569
|
PALETTE_CLEARANCE,
|
|
2125
3570
|
PALETTE_TOOL_ICON,
|
|
2126
3571
|
REDUCED_MOTION_CLASS,
|
|
2127
3572
|
SHELL,
|
|
2128
3573
|
SHELL_TOKENS,
|
|
2129
3574
|
TEXT_FIELD_SELECTOR,
|
|
3575
|
+
TOAST_DURATION_MS,
|
|
3576
|
+
TOAST_MAX_WIDTH,
|
|
2130
3577
|
TOOLTIP_DELAY_MS,
|
|
2131
3578
|
VIEW_TOLERANCE,
|
|
2132
3579
|
ZOOM_MAX,
|
|
2133
3580
|
ZOOM_MIN,
|
|
3581
|
+
acceptsFile,
|
|
2134
3582
|
appearanceAttributes,
|
|
2135
3583
|
appearanceFromBodyClasses,
|
|
2136
3584
|
appearanceFromColorThemeKind,
|
|
@@ -2138,10 +3586,14 @@ function carryOwnMode(root, theme, ledger) {
|
|
|
2138
3586
|
ariaKeyShortcutsFromLabel,
|
|
2139
3587
|
canZoomIn,
|
|
2140
3588
|
canZoomOut,
|
|
3589
|
+
commandKeyLabel,
|
|
2141
3590
|
decideKey,
|
|
2142
3591
|
decidePaletteRow,
|
|
3592
|
+
decideTopRow,
|
|
2143
3593
|
defaultMessages,
|
|
2144
3594
|
detectPlatform,
|
|
3595
|
+
documentMode,
|
|
3596
|
+
dragVerdict,
|
|
2145
3597
|
easeStandard,
|
|
2146
3598
|
fitInsets,
|
|
2147
3599
|
fitViewbox,
|
|
@@ -2152,10 +3604,13 @@ function carryOwnMode(root, theme, ledger) {
|
|
|
2152
3604
|
iconMarkup,
|
|
2153
3605
|
interpolateViewbox,
|
|
2154
3606
|
isUndoRedoKey,
|
|
3607
|
+
legalLineHeight,
|
|
2155
3608
|
legendStartsOpen,
|
|
2156
3609
|
matchChromeKey,
|
|
2157
3610
|
mountShell,
|
|
3611
|
+
parseAppearanceChoice,
|
|
2158
3612
|
placeTooltip,
|
|
3613
|
+
problemSummary,
|
|
2159
3614
|
reducedMotionFromBodyClasses,
|
|
2160
3615
|
requestedAppearance,
|
|
2161
3616
|
resolveAppearance,
|
|
@@ -2163,7 +3618,14 @@ function carryOwnMode(root, theme, ledger) {
|
|
|
2163
3618
|
sameView,
|
|
2164
3619
|
shellSizesFromTokens,
|
|
2165
3620
|
shortcutLabel,
|
|
2166
|
-
|
|
3621
|
+
toastLifetime,
|
|
3622
|
+
toastStackBottom,
|
|
3623
|
+
toastStackWidth,
|
|
3624
|
+
toastToEvict,
|
|
3625
|
+
webHeadMarkup,
|
|
3626
|
+
webModeScript,
|
|
3627
|
+
zoomReadout,
|
|
3628
|
+
zoomRowRaised
|
|
2167
3629
|
});
|
|
2168
3630
|
/*! @miragon/modeler-shell icon geometry
|
|
2169
3631
|
* Lucide (lucide-static 1.59.0) @license ISC
|