@miragon/modeler-shell 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +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-66HCSJNY.js} +1247 -31
- package/dist/chunk-66HCSJNY.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 +1654 -197
- 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 +1541 -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/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
|
-
function attributeList(attributes) {
|
|
986
|
-
return attributes.map(([name, value]) => ` ${name}="${
|
|
1190
|
+
function attributeList(attributes) {
|
|
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 {
|
|
@@ -1655,6 +2525,380 @@ function createTooltip({ root, layer, id }) {
|
|
|
1655
2525
|
};
|
|
1656
2526
|
}
|
|
1657
2527
|
|
|
2528
|
+
// src/dom/web/drop-zone.ts
|
|
2529
|
+
var DRAG_EVENTS = ["dragenter", "dragover", "dragleave", "drop"];
|
|
2530
|
+
function carriesFiles(transfer) {
|
|
2531
|
+
if (!transfer) return false;
|
|
2532
|
+
if (Array.from(transfer.types ?? []).includes("Files")) return true;
|
|
2533
|
+
return Array.from(transfer.items ?? []).some((item) => item.kind === "file");
|
|
2534
|
+
}
|
|
2535
|
+
function dragItems(transfer) {
|
|
2536
|
+
const items = Array.from(transfer.items ?? [], (item) => ({ kind: item.kind, type: item.type }));
|
|
2537
|
+
return items.length > 0 ? items : [{ kind: "file", type: "" }];
|
|
2538
|
+
}
|
|
2539
|
+
function acceptList(accept, lang) {
|
|
2540
|
+
const entries = accept.map((entry) => entry.trim()).filter((entry) => entry !== "");
|
|
2541
|
+
const extensions = entries.filter((entry) => entry.startsWith("."));
|
|
2542
|
+
const listed = extensions.length > 0 ? extensions : entries;
|
|
2543
|
+
let format;
|
|
2544
|
+
try {
|
|
2545
|
+
format = new Intl.ListFormat(lang, { type: "disjunction" });
|
|
2546
|
+
} catch {
|
|
2547
|
+
format = new Intl.ListFormat("en", { type: "disjunction" });
|
|
2548
|
+
}
|
|
2549
|
+
return format.format(listed);
|
|
2550
|
+
}
|
|
2551
|
+
function createDropZone(context) {
|
|
2552
|
+
const { ownerDocument, layer, messages, lang, options, modalOpen, toast, setState } = context;
|
|
2553
|
+
const chip = createElement(ownerDocument, "span", { class: "cd-shell-drop-zone-chip" });
|
|
2554
|
+
const element = createElement(
|
|
2555
|
+
ownerDocument,
|
|
2556
|
+
"div",
|
|
2557
|
+
{ class: "cd-shell-drop-zone", "data-chrome": "drop-zone", "aria-hidden": "true" },
|
|
2558
|
+
chip
|
|
2559
|
+
);
|
|
2560
|
+
element.hidden = true;
|
|
2561
|
+
layer.append(element);
|
|
2562
|
+
const view = ownerDocument.defaultView;
|
|
2563
|
+
let depth = 0;
|
|
2564
|
+
let current = null;
|
|
2565
|
+
function verdictFor(items) {
|
|
2566
|
+
return dragVerdict(items, {
|
|
2567
|
+
accept: options.accept,
|
|
2568
|
+
multiple: options.multiple ?? false,
|
|
2569
|
+
dialogOpen: modalOpen()
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
2572
|
+
function show(verdict) {
|
|
2573
|
+
const next = verdict.state === "none" ? null : verdict.state;
|
|
2574
|
+
if (verdict.state === "over") chip.textContent = messages.dropFile;
|
|
2575
|
+
if (verdict.state === "forbidden") chip.textContent = messages.dropForbidden(verdict.reason);
|
|
2576
|
+
if (next === current) return;
|
|
2577
|
+
current = next;
|
|
2578
|
+
element.hidden = next === null;
|
|
2579
|
+
if (next === null) element.removeAttribute("data-state");
|
|
2580
|
+
else element.setAttribute("data-state", next);
|
|
2581
|
+
setState(next);
|
|
2582
|
+
}
|
|
2583
|
+
function track(transfer) {
|
|
2584
|
+
const verdict = verdictFor(dragItems(transfer));
|
|
2585
|
+
show(verdict);
|
|
2586
|
+
transfer.dropEffect = verdict.state === "forbidden" ? "none" : "copy";
|
|
2587
|
+
}
|
|
2588
|
+
function rejectByDefault(files) {
|
|
2589
|
+
const accepted = acceptList(options.accept, lang);
|
|
2590
|
+
for (const file of files) toast({ status: "danger", text: messages.cannotOpenFile(file.name, accepted) });
|
|
2591
|
+
}
|
|
2592
|
+
function receive(files, verdict) {
|
|
2593
|
+
if (verdict.state === "forbidden" && verdict.reason !== "type") return;
|
|
2594
|
+
const accepted = files.filter((file) => acceptsFile(file, options.accept));
|
|
2595
|
+
const rejected = files.filter((file) => !accepted.includes(file));
|
|
2596
|
+
if (accepted.length > 0) options.onDrop(accepted);
|
|
2597
|
+
if (rejected.length > 0) (options.onReject ?? rejectByDefault)(rejected);
|
|
2598
|
+
}
|
|
2599
|
+
const onDragEvent = (event) => {
|
|
2600
|
+
const transfer = event.dataTransfer;
|
|
2601
|
+
if (!carriesFiles(transfer)) return;
|
|
2602
|
+
event.preventDefault();
|
|
2603
|
+
switch (event.type) {
|
|
2604
|
+
case "dragenter":
|
|
2605
|
+
depth += 1;
|
|
2606
|
+
track(transfer);
|
|
2607
|
+
break;
|
|
2608
|
+
case "dragover":
|
|
2609
|
+
depth = Math.max(depth, 1);
|
|
2610
|
+
track(transfer);
|
|
2611
|
+
break;
|
|
2612
|
+
case "dragleave":
|
|
2613
|
+
depth = Math.max(0, depth - 1);
|
|
2614
|
+
if (depth === 0) show({ state: "none" });
|
|
2615
|
+
break;
|
|
2616
|
+
case "drop": {
|
|
2617
|
+
depth = 0;
|
|
2618
|
+
show({ state: "none" });
|
|
2619
|
+
const files = Array.from(transfer.files ?? []);
|
|
2620
|
+
const items = files.length > 0 ? files.map((file) => ({ kind: "file", type: file.type })) : dragItems(transfer);
|
|
2621
|
+
receive(files, verdictFor(items));
|
|
2622
|
+
break;
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
};
|
|
2626
|
+
for (const type of DRAG_EVENTS) view?.addEventListener(type, onDragEvent);
|
|
2627
|
+
return {
|
|
2628
|
+
element,
|
|
2629
|
+
state: () => current,
|
|
2630
|
+
destroy() {
|
|
2631
|
+
for (const type of DRAG_EVENTS) view?.removeEventListener(type, onDragEvent);
|
|
2632
|
+
depth = 0;
|
|
2633
|
+
if (current !== null) {
|
|
2634
|
+
current = null;
|
|
2635
|
+
setState(null);
|
|
2636
|
+
}
|
|
2637
|
+
element.remove();
|
|
2638
|
+
}
|
|
2639
|
+
};
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
// src/dom/web/empty-state.ts
|
|
2643
|
+
var KOMET_SIZE = "64";
|
|
2644
|
+
function createEmptyState(context) {
|
|
2645
|
+
const { ownerDocument, messages, options, dropHint, open, titleId, onLeave } = context;
|
|
2646
|
+
const button = (variant, label, run) => {
|
|
2647
|
+
const element2 = createElement(
|
|
2648
|
+
ownerDocument,
|
|
2649
|
+
"button",
|
|
2650
|
+
{ type: "button", class: "cd-shell-button", "data-variant": variant },
|
|
2651
|
+
label
|
|
2652
|
+
);
|
|
2653
|
+
element2.addEventListener("click", () => {
|
|
2654
|
+
onLeave();
|
|
2655
|
+
run();
|
|
2656
|
+
});
|
|
2657
|
+
return element2;
|
|
2658
|
+
};
|
|
2659
|
+
const onShowExample = options.onShowExample;
|
|
2660
|
+
const newDiagram = button("primary", messages.newDiagram, () => options.onNewDiagram());
|
|
2661
|
+
const element = createElement(
|
|
2662
|
+
ownerDocument,
|
|
2663
|
+
"section",
|
|
2664
|
+
{ class: "cd-shell-empty-state", "data-chrome": "empty-state", "aria-labelledby": titleId },
|
|
2665
|
+
// Dragging the mark would start an image drag that some browsers carry as a file.
|
|
2666
|
+
createElement(ownerDocument, "img", {
|
|
2667
|
+
class: "cd-shell-empty-state-mark",
|
|
2668
|
+
src: options.komet,
|
|
2669
|
+
width: KOMET_SIZE,
|
|
2670
|
+
height: KOMET_SIZE,
|
|
2671
|
+
alt: "",
|
|
2672
|
+
draggable: "false"
|
|
2673
|
+
}),
|
|
2674
|
+
createElement(ownerDocument, "h1", { class: "cd-shell-empty-state-title", id: titleId }, options.title),
|
|
2675
|
+
createElement(ownerDocument, "p", { class: "cd-shell-empty-state-text" }, options.text),
|
|
2676
|
+
createElement(
|
|
2677
|
+
ownerDocument,
|
|
2678
|
+
"div",
|
|
2679
|
+
{ class: "cd-shell-empty-state-actions" },
|
|
2680
|
+
newDiagram,
|
|
2681
|
+
onShowExample && button("ghost", messages.showExample, () => onShowExample())
|
|
2682
|
+
),
|
|
2683
|
+
hintParagraph()
|
|
2684
|
+
);
|
|
2685
|
+
function hintParagraph() {
|
|
2686
|
+
const hasDropHint = dropHint !== void 0 && dropHint !== "";
|
|
2687
|
+
if (!hasDropHint && !open) return void 0;
|
|
2688
|
+
const paragraph = createElement(ownerDocument, "p", { class: "cd-shell-empty-state-hint" });
|
|
2689
|
+
if (hasDropHint) paragraph.append(dropHint);
|
|
2690
|
+
if (!open) return paragraph;
|
|
2691
|
+
const shortcut = open.shortcut?.trim();
|
|
2692
|
+
const link = createElement(
|
|
2693
|
+
ownerDocument,
|
|
2694
|
+
"button",
|
|
2695
|
+
{
|
|
2696
|
+
type: "button",
|
|
2697
|
+
class: "cd-shell-empty-state-open",
|
|
2698
|
+
"aria-keyshortcuts": shortcut ? ariaKeyShortcutsFromLabel(shortcut) : void 0,
|
|
2699
|
+
"aria-haspopup": open.dialog ? "dialog" : void 0
|
|
2700
|
+
},
|
|
2701
|
+
messages.openFileLink
|
|
2702
|
+
);
|
|
2703
|
+
link.addEventListener("click", () => open.onSelect());
|
|
2704
|
+
if (hasDropHint) {
|
|
2705
|
+
paragraph.append(" ", createElement(ownerDocument, "span", { "aria-hidden": "true" }, "\xB7"), " ");
|
|
2706
|
+
}
|
|
2707
|
+
const openItem = createElement(ownerDocument, "span", { class: "cd-shell-empty-state-open-item" }, link);
|
|
2708
|
+
if (shortcut) {
|
|
2709
|
+
openItem.append(
|
|
2710
|
+
" ",
|
|
2711
|
+
createElement(
|
|
2712
|
+
ownerDocument,
|
|
2713
|
+
"kbd",
|
|
2714
|
+
{ class: "cd-shell-empty-state-key", "aria-hidden": "true" },
|
|
2715
|
+
shortcut
|
|
2716
|
+
)
|
|
2717
|
+
);
|
|
2718
|
+
}
|
|
2719
|
+
paragraph.append(openItem);
|
|
2720
|
+
return paragraph;
|
|
2721
|
+
}
|
|
2722
|
+
element.hidden = true;
|
|
2723
|
+
return {
|
|
2724
|
+
element,
|
|
2725
|
+
isShown: () => !element.hidden,
|
|
2726
|
+
setShown(shown) {
|
|
2727
|
+
element.hidden = !shown;
|
|
2728
|
+
},
|
|
2729
|
+
focus() {
|
|
2730
|
+
newDiagram.focus({ preventScroll: true });
|
|
2731
|
+
},
|
|
2732
|
+
destroy() {
|
|
2733
|
+
element.remove();
|
|
2734
|
+
}
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
// src/dom/web/legal.ts
|
|
2739
|
+
var MIRAGON_HOME = "https://www.miragon.io/";
|
|
2740
|
+
var MIRAGON_IMPRESSUM = "https://www.miragon.io/impressum/";
|
|
2741
|
+
var MIRAGON_DATENSCHUTZ = "https://www.miragon.io/datenschutz/";
|
|
2742
|
+
function createLegalLine(context) {
|
|
2743
|
+
const { ownerDocument, messages, options } = context;
|
|
2744
|
+
const year = options.year ?? (/* @__PURE__ */ new Date()).getFullYear();
|
|
2745
|
+
const newTabId = `${context.idPrefix}-new-tab`;
|
|
2746
|
+
const link = (label, href, lang) => createElement(
|
|
2747
|
+
ownerDocument,
|
|
2748
|
+
"a",
|
|
2749
|
+
{ href, lang, target: "_blank", rel: "noopener noreferrer", "aria-describedby": newTabId },
|
|
2750
|
+
label
|
|
2751
|
+
);
|
|
2752
|
+
const separator = () => createElement(ownerDocument, "span", { "aria-hidden": "true" }, "\xB7");
|
|
2753
|
+
const item = (anchor) => createElement(ownerDocument, "span", { class: "cd-shell-legal-item" }, separator(), anchor);
|
|
2754
|
+
const links = [
|
|
2755
|
+
link(messages.copyright(year), MIRAGON_HOME),
|
|
2756
|
+
link("Impressum", MIRAGON_IMPRESSUM, "de"),
|
|
2757
|
+
link("Datenschutz", MIRAGON_DATENSCHUTZ, "de"),
|
|
2758
|
+
...(options.links ?? []).map((extra) => link(extra.label, extra.href, extra.lang))
|
|
2759
|
+
];
|
|
2760
|
+
const element = createElement(ownerDocument, "nav", {
|
|
2761
|
+
class: "cd-shell-legal",
|
|
2762
|
+
"data-chrome": "legal",
|
|
2763
|
+
"aria-label": messages.legal
|
|
2764
|
+
});
|
|
2765
|
+
element.append(
|
|
2766
|
+
...links.map((anchor, index) => index === 0 ? anchor : item(anchor)),
|
|
2767
|
+
createElement(ownerDocument, "span", { id: newTabId, hidden: "" }, messages.opensInNewTab)
|
|
2768
|
+
);
|
|
2769
|
+
return {
|
|
2770
|
+
element,
|
|
2771
|
+
destroy() {
|
|
2772
|
+
element.remove();
|
|
2773
|
+
}
|
|
2774
|
+
};
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
// src/dom/web/menu-groups.ts
|
|
2778
|
+
function fileEntry(spec, action) {
|
|
2779
|
+
const common = {
|
|
2780
|
+
id: spec.id,
|
|
2781
|
+
label: spec.label,
|
|
2782
|
+
...spec.icon ? { icon: spec.icon } : {},
|
|
2783
|
+
...action.disabled ? { disabled: action.disabled } : {},
|
|
2784
|
+
...action.shortcut ? { shortcut: action.shortcut } : {},
|
|
2785
|
+
onSelect: () => action.onSelect()
|
|
2786
|
+
};
|
|
2787
|
+
return action.dialog ? { kind: "dialog", ...common } : { kind: "action", ...common };
|
|
2788
|
+
}
|
|
2789
|
+
function fileMenuEntries(options, messages) {
|
|
2790
|
+
const specs = [
|
|
2791
|
+
{ option: "newDiagram", id: "new-diagram", label: messages.newDiagram, icon: "file-plus" },
|
|
2792
|
+
{ option: "open", id: "open", label: messages.openFile, icon: "folder-open" },
|
|
2793
|
+
// §5.14 gives "Show example" no icon.
|
|
2794
|
+
{ option: "showExample", id: "show-example", label: messages.showExample, icon: void 0 },
|
|
2795
|
+
{ option: "import", id: "import", label: messages.importFile, icon: "import" }
|
|
2796
|
+
];
|
|
2797
|
+
return specs.flatMap((spec) => {
|
|
2798
|
+
const action = options[spec.option];
|
|
2799
|
+
return action ? [fileEntry(spec, action)] : [];
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2802
|
+
function appearanceMenuEntries(appearance, messages) {
|
|
2803
|
+
return [
|
|
2804
|
+
{
|
|
2805
|
+
kind: "radio",
|
|
2806
|
+
id: "appearance",
|
|
2807
|
+
label: messages.appearance,
|
|
2808
|
+
options: [
|
|
2809
|
+
{ value: "system", label: messages.appearanceSystem },
|
|
2810
|
+
{ value: "light", label: messages.appearanceLight },
|
|
2811
|
+
{ value: "dark", label: messages.appearanceDark }
|
|
2812
|
+
],
|
|
2813
|
+
value: () => appearance.choice(),
|
|
2814
|
+
onSelect: (value) => appearance.setChoice(parseAppearanceChoice(value))
|
|
2815
|
+
}
|
|
2816
|
+
];
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
// src/dom/web/primary.ts
|
|
2820
|
+
var COLLAPSED_ATTRIBUTE = "data-collapsed";
|
|
2821
|
+
function createPrimaryAction(context) {
|
|
2822
|
+
const { ownerDocument, messages, tooltip, options } = context;
|
|
2823
|
+
const label = options.label ?? messages.share;
|
|
2824
|
+
const labelElement = createElement(ownerDocument, "span", { class: "cd-shell-primary-label" }, label);
|
|
2825
|
+
const element = createElement(
|
|
2826
|
+
ownerDocument,
|
|
2827
|
+
"button",
|
|
2828
|
+
{
|
|
2829
|
+
type: "button",
|
|
2830
|
+
class: "cd-shell-primary cd-shell-button",
|
|
2831
|
+
"data-variant": "primary",
|
|
2832
|
+
"data-chrome": "primary",
|
|
2833
|
+
"aria-label": label
|
|
2834
|
+
},
|
|
2835
|
+
iconElement(options.icon ?? "share-2"),
|
|
2836
|
+
labelElement
|
|
2837
|
+
);
|
|
2838
|
+
let collapsed = false;
|
|
2839
|
+
let disabled = false;
|
|
2840
|
+
let labelledWidth;
|
|
2841
|
+
function readDisabled() {
|
|
2842
|
+
disabled = options.disabled?.() ?? false;
|
|
2843
|
+
toggleAttribute(element, "aria-disabled", disabled ? "true" : null);
|
|
2844
|
+
}
|
|
2845
|
+
function showCollapsed(state) {
|
|
2846
|
+
labelElement.hidden = state;
|
|
2847
|
+
toggleAttribute(element, COLLAPSED_ATTRIBUTE, state ? "" : null);
|
|
2848
|
+
}
|
|
2849
|
+
function measureLabelledWidth() {
|
|
2850
|
+
if (!collapsed) return element.offsetWidth;
|
|
2851
|
+
showCollapsed(false);
|
|
2852
|
+
const width = element.offsetWidth;
|
|
2853
|
+
showCollapsed(true);
|
|
2854
|
+
return width;
|
|
2855
|
+
}
|
|
2856
|
+
function isEngaged() {
|
|
2857
|
+
if (ownerDocument.activeElement === element) return true;
|
|
2858
|
+
try {
|
|
2859
|
+
return element.matches(":hover");
|
|
2860
|
+
} catch {
|
|
2861
|
+
return false;
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
const onClick = (event) => {
|
|
2865
|
+
if (disabled) {
|
|
2866
|
+
event.preventDefault();
|
|
2867
|
+
return;
|
|
2868
|
+
}
|
|
2869
|
+
options.onSelect();
|
|
2870
|
+
};
|
|
2871
|
+
element.addEventListener("click", onClick);
|
|
2872
|
+
const detachTooltip = tooltip.attach(element, () => collapsed ? { label } : null);
|
|
2873
|
+
readDisabled();
|
|
2874
|
+
return {
|
|
2875
|
+
element,
|
|
2876
|
+
isCollapsed: () => collapsed,
|
|
2877
|
+
refresh() {
|
|
2878
|
+
readDisabled();
|
|
2879
|
+
labelledWidth = void 0;
|
|
2880
|
+
},
|
|
2881
|
+
widths() {
|
|
2882
|
+
if (!collapsed || labelledWidth === void 0) {
|
|
2883
|
+
const measured = measureLabelledWidth();
|
|
2884
|
+
if (measured > 0) labelledWidth = measured;
|
|
2885
|
+
}
|
|
2886
|
+
return { withLabel: labelledWidth ?? 0, iconOnly: SHELL.row };
|
|
2887
|
+
},
|
|
2888
|
+
setCollapsed(next) {
|
|
2889
|
+
if (next === collapsed) return;
|
|
2890
|
+
collapsed = next;
|
|
2891
|
+
showCollapsed(next);
|
|
2892
|
+
if (!next && isEngaged()) tooltip.hide();
|
|
2893
|
+
},
|
|
2894
|
+
destroy() {
|
|
2895
|
+
detachTooltip();
|
|
2896
|
+
element.removeEventListener("click", onClick);
|
|
2897
|
+
element.remove();
|
|
2898
|
+
}
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2901
|
+
|
|
1658
2902
|
// src/dom/zoom-panel.ts
|
|
1659
2903
|
function createZoomPanel(context) {
|
|
1660
2904
|
const { ownerDocument, messages, lang, platform, tooltip } = context;
|
|
@@ -1772,6 +3016,16 @@ function nextMountIndex() {
|
|
|
1772
3016
|
return index;
|
|
1773
3017
|
}
|
|
1774
3018
|
var DEFAULT_THEMES = ["light"];
|
|
3019
|
+
var LEGAL_HEIGHT_PROPERTY = "--modeler-shell-legal-height";
|
|
3020
|
+
var INERT_TOAST = Object.freeze({ dismiss() {
|
|
3021
|
+
} });
|
|
3022
|
+
function visibleBoxIn(element, root) {
|
|
3023
|
+
if (!element || element.hidden || !element.isConnected) return void 0;
|
|
3024
|
+
if (typeof element.checkVisibility === "function" && !element.checkVisibility({ visibilityProperty: true }))
|
|
3025
|
+
return void 0;
|
|
3026
|
+
const box = boxRelativeTo(element, root);
|
|
3027
|
+
return box.width > 0 && box.height > 0 ? box : void 0;
|
|
3028
|
+
}
|
|
1775
3029
|
function languageOf(root, host, requested) {
|
|
1776
3030
|
if (requested) return requested;
|
|
1777
3031
|
const declared = host === "embedded" ? root.getAttribute("lang") : root.closest("[lang]")?.getAttribute("lang");
|
|
@@ -1800,6 +3054,7 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1800
3054
|
const platform = options.platform ?? detectPlatform(globalThis.navigator);
|
|
1801
3055
|
const lang = languageOf(root, host, options.lang);
|
|
1802
3056
|
const keyScope = options.keyScope ?? (host === "embedded" ? "root" : "document");
|
|
3057
|
+
const web = host === "web";
|
|
1803
3058
|
ledger.addClass(root, "cd-shell");
|
|
1804
3059
|
ledger.attribute(root, "data-cd-shell-host", host);
|
|
1805
3060
|
ledger.attribute(root, "data-palette", "top");
|
|
@@ -1812,6 +3067,9 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1812
3067
|
const setState = (name, value) => {
|
|
1813
3068
|
if (!ledger.isClosed) ledger.attribute(root, name, value);
|
|
1814
3069
|
};
|
|
3070
|
+
const appearance = web ? connectWebAppearance({ ownerDocument, themes, options: options.appearance }) : void 0;
|
|
3071
|
+
if (appearance) ledger.defer(() => appearance.destroy());
|
|
3072
|
+
const darkCapable = themes.includes("dark");
|
|
1815
3073
|
const layer = createElement(ownerDocument, "div", { class: "cd-shell-layer" });
|
|
1816
3074
|
ledger.defer(() => layer.remove());
|
|
1817
3075
|
const tooltip = createTooltip({ root, layer, id: `cd-shell-tooltip-${index}` });
|
|
@@ -1843,6 +3101,14 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1843
3101
|
keepEnabledWhenReadOnly: true
|
|
1844
3102
|
} : void 0;
|
|
1845
3103
|
const shellModelEntries = legendEntry ? [legendEntry] : [];
|
|
3104
|
+
const fileEntries = web && options.file ? fileMenuEntries(options.file, messages) : [];
|
|
3105
|
+
const appearanceEntries = appearance && darkCapable ? appearanceMenuEntries(appearance, messages) : [];
|
|
3106
|
+
const shellEntries = (group) => {
|
|
3107
|
+
if (group === "file") return fileEntries;
|
|
3108
|
+
if (group === "model") return shellModelEntries;
|
|
3109
|
+
if (group === "appearance") return appearanceEntries;
|
|
3110
|
+
return [];
|
|
3111
|
+
};
|
|
1846
3112
|
const menu = createMenu({
|
|
1847
3113
|
root,
|
|
1848
3114
|
layer,
|
|
@@ -1850,55 +3116,133 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1850
3116
|
messages,
|
|
1851
3117
|
tooltip,
|
|
1852
3118
|
groups: options.menu ?? {},
|
|
1853
|
-
|
|
3119
|
+
shellEntries,
|
|
1854
3120
|
isReadOnly: () => readOnly
|
|
1855
3121
|
});
|
|
1856
3122
|
ledger.defer(() => menu.destroy());
|
|
1857
3123
|
ledger.defer(tooltip.attach(menu.trigger, () => menu.isOpen() ? null : { label: messages.menu }));
|
|
3124
|
+
const primary = web && options.primary ? createPrimaryAction({ ownerDocument, messages, tooltip, options: options.primary }) : void 0;
|
|
3125
|
+
if (primary) ledger.defer(() => primary.destroy());
|
|
1858
3126
|
const canvas = options.canvas ?? createElement(ownerDocument, "div");
|
|
1859
3127
|
ledger.addClass(canvas, "cd-shell-canvas");
|
|
3128
|
+
if (web && !canvas.hasAttribute("role")) ledger.attribute(canvas, "role", "main");
|
|
1860
3129
|
if (!options.canvas) ledger.defer(() => canvas.remove());
|
|
3130
|
+
function focusCanvasOrTrigger() {
|
|
3131
|
+
const took = (target) => {
|
|
3132
|
+
target?.focus({ preventScroll: true });
|
|
3133
|
+
return target !== null && target !== void 0 && ownerDocument.activeElement === target;
|
|
3134
|
+
};
|
|
3135
|
+
if (emptyState?.isShown()) {
|
|
3136
|
+
emptyState.focus();
|
|
3137
|
+
if (emptyState.element.contains(ownerDocument.activeElement)) return;
|
|
3138
|
+
}
|
|
3139
|
+
if (took(canvas.querySelector("svg[tabindex]"))) return;
|
|
3140
|
+
if (!menu.trigger.hidden) took(menu.trigger);
|
|
3141
|
+
}
|
|
1861
3142
|
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 });
|
|
3143
|
+
if (island.contains(ownerDocument.activeElement)) focusCanvasOrTrigger();
|
|
1867
3144
|
}
|
|
1868
3145
|
const status = createStatusBox(ownerDocument);
|
|
1869
3146
|
ledger.defer(() => status.destroy());
|
|
1870
3147
|
const hint = options.emptyHint ? createEmptyHint(ownerDocument, messages, options.emptyHint) : void 0;
|
|
1871
3148
|
if (hint) ledger.defer(() => hint.destroy());
|
|
3149
|
+
const emptyState = web && options.emptyState ? createEmptyState({
|
|
3150
|
+
ownerDocument,
|
|
3151
|
+
messages,
|
|
3152
|
+
options: options.emptyState,
|
|
3153
|
+
dropHint: options.dropZone ? options.emptyState.dropHint ?? messages.dropHint : void 0,
|
|
3154
|
+
open: options.file?.open,
|
|
3155
|
+
titleId: `cd-shell-empty-state-title-${index}`,
|
|
3156
|
+
onLeave: () => setEmptyStateShown(false)
|
|
3157
|
+
}) : void 0;
|
|
3158
|
+
if (emptyState) ledger.defer(() => emptyState.destroy());
|
|
1872
3159
|
const inspector = options.inspector ? createInspector(ownerDocument, options.inspector, (open) => {
|
|
1873
3160
|
setState("data-inspector", open ? "open" : null);
|
|
3161
|
+
updateBottomRow();
|
|
1874
3162
|
placeHint();
|
|
1875
3163
|
}) : void 0;
|
|
1876
3164
|
if (inspector) ledger.defer(() => inspector.destroy());
|
|
1877
|
-
const
|
|
3165
|
+
const problemsOptions = options.problems;
|
|
3166
|
+
let popover;
|
|
3167
|
+
const pill = problemsOptions ? createProblemsPill({
|
|
1878
3168
|
ownerDocument,
|
|
1879
3169
|
messages,
|
|
1880
3170
|
lang,
|
|
1881
3171
|
tooltip,
|
|
1882
3172
|
showTooltip: host === "ide",
|
|
1883
|
-
|
|
3173
|
+
onActivate() {
|
|
3174
|
+
if (popover) popover.toggle();
|
|
3175
|
+
else problemsOptions.onActivate?.();
|
|
3176
|
+
}
|
|
1884
3177
|
}) : void 0;
|
|
1885
3178
|
if (pill) ledger.defer(() => pill.destroy());
|
|
3179
|
+
if (pill && (web || host === "embedded" && !problemsOptions?.onActivate)) {
|
|
3180
|
+
const created = createProblemsPopover({
|
|
3181
|
+
ownerDocument,
|
|
3182
|
+
root,
|
|
3183
|
+
layer,
|
|
3184
|
+
pill: pill.element,
|
|
3185
|
+
id: `cd-shell-problems-${index}`,
|
|
3186
|
+
titleId: `cd-shell-problems-title-${index}`,
|
|
3187
|
+
messages,
|
|
3188
|
+
onSelectItem: problemsOptions?.onSelectItem
|
|
3189
|
+
});
|
|
3190
|
+
popover = created;
|
|
3191
|
+
ledger.defer(() => created.destroy());
|
|
3192
|
+
}
|
|
3193
|
+
const legal = web ? createLegalLine({
|
|
3194
|
+
ownerDocument,
|
|
3195
|
+
messages,
|
|
3196
|
+
options: options.legal ?? {},
|
|
3197
|
+
idPrefix: `cd-shell-legal-${index}`
|
|
3198
|
+
}) : void 0;
|
|
3199
|
+
if (legal) ledger.defer(() => legal.destroy());
|
|
1886
3200
|
const zoomPanel = createZoomPanel({ ownerDocument, messages, lang, platform, tooltip });
|
|
1887
3201
|
ledger.defer(() => zoomPanel.destroy());
|
|
1888
|
-
|
|
3202
|
+
const toaster = host === "ide" ? void 0 : createToaster({
|
|
3203
|
+
ownerDocument,
|
|
3204
|
+
host,
|
|
3205
|
+
messages,
|
|
3206
|
+
idPrefix: `cd-shell-toast-${index}`,
|
|
3207
|
+
focusFallback: focusCanvasOrTrigger,
|
|
3208
|
+
onChange: () => updateBottomRow()
|
|
3209
|
+
});
|
|
3210
|
+
if (toaster) ledger.defer(() => toaster.destroy());
|
|
3211
|
+
const defined = (element) => element !== void 0;
|
|
3212
|
+
root.prepend(...[menu.trigger, primary?.element, canvas].filter(defined));
|
|
1889
3213
|
root.append(
|
|
1890
3214
|
...[
|
|
1891
3215
|
status.element,
|
|
1892
3216
|
hint?.element,
|
|
3217
|
+
emptyState?.element,
|
|
1893
3218
|
inspector?.element,
|
|
1894
3219
|
pill?.element,
|
|
1895
3220
|
legend?.element,
|
|
3221
|
+
legal?.element,
|
|
1896
3222
|
zoomPanel.element
|
|
1897
|
-
].filter(
|
|
1898
|
-
layer
|
|
3223
|
+
].filter(defined),
|
|
3224
|
+
layer,
|
|
3225
|
+
...[toaster?.announcer, toaster?.element].filter(defined)
|
|
1899
3226
|
);
|
|
3227
|
+
function showToast(request) {
|
|
3228
|
+
return ledger.isClosed || !toaster ? INERT_TOAST : toaster.show(request);
|
|
3229
|
+
}
|
|
3230
|
+
const dropZone = web && options.dropZone ? createDropZone({
|
|
3231
|
+
ownerDocument,
|
|
3232
|
+
layer,
|
|
3233
|
+
messages,
|
|
3234
|
+
lang,
|
|
3235
|
+
options: options.dropZone,
|
|
3236
|
+
modalOpen: () => isModalOpen(ownerDocument),
|
|
3237
|
+
toast: showToast,
|
|
3238
|
+
setState: (state) => setState("data-drop", state)
|
|
3239
|
+
}) : void 0;
|
|
3240
|
+
if (dropZone) ledger.defer(() => dropZone.destroy());
|
|
1900
3241
|
let palette;
|
|
1901
3242
|
let paletteRow = "top";
|
|
3243
|
+
let zoomRaised = false;
|
|
3244
|
+
let legalHeight = LEGAL_LINE_HEIGHT;
|
|
3245
|
+
if (legal) ledger.defer(() => root.style.removeProperty(LEGAL_HEIGHT_PROPERTY));
|
|
1902
3246
|
function containerSize() {
|
|
1903
3247
|
return { width: root.clientWidth, height: root.clientHeight };
|
|
1904
3248
|
}
|
|
@@ -1918,6 +3262,10 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1918
3262
|
paletteRow,
|
|
1919
3263
|
problemsVisible: pill?.isShown() ?? false
|
|
1920
3264
|
};
|
|
3265
|
+
if (web) {
|
|
3266
|
+
layout.zoomRaised = zoomRaised;
|
|
3267
|
+
layout.legalHeight = legalHeight;
|
|
3268
|
+
}
|
|
1921
3269
|
if (legend?.isOpen()) layout.legendWidth = legend.element.getBoundingClientRect().width;
|
|
1922
3270
|
if (inspector?.isOpen()) {
|
|
1923
3271
|
layout.inspector = container.width > SHELL.narrow ? {} : { sheetTop: boxRelativeTo(inspector.element, root).y };
|
|
@@ -1931,18 +3279,76 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1931
3279
|
if (ledger.isClosed) return;
|
|
1932
3280
|
const size = containerSize();
|
|
1933
3281
|
if (size.width === 0 || size.height === 0) return;
|
|
1934
|
-
|
|
3282
|
+
const topRow = decideTopRow({
|
|
1935
3283
|
containerWidth: size.width,
|
|
1936
3284
|
paletteWidth: paletteWidth(),
|
|
1937
|
-
menuRight: menuRight()
|
|
3285
|
+
menuRight: menuRight(),
|
|
3286
|
+
primary: primary?.widths()
|
|
1938
3287
|
});
|
|
3288
|
+
paletteRow = topRow.paletteRow;
|
|
1939
3289
|
setState("data-palette", paletteRow);
|
|
3290
|
+
if (primary) {
|
|
3291
|
+
const collapsed = topRow.primaryLabel === "collapsed";
|
|
3292
|
+
primary.setCollapsed(collapsed);
|
|
3293
|
+
setState("data-primary", collapsed ? "collapsed" : null);
|
|
3294
|
+
}
|
|
1940
3295
|
if (legend && !legend.isStarted()) legend.start(options.legend?.open ?? legendStartsOpen(size.height));
|
|
3296
|
+
updateBottomRow();
|
|
1941
3297
|
placeHint();
|
|
1942
3298
|
}
|
|
3299
|
+
function updateBottomRow() {
|
|
3300
|
+
if (ledger.isClosed) return;
|
|
3301
|
+
const size = containerSize();
|
|
3302
|
+
if (size.width === 0 || size.height === 0) return;
|
|
3303
|
+
if (legal) {
|
|
3304
|
+
const legalBox = visibleBoxIn(legal.element, root);
|
|
3305
|
+
const height = legalLineHeight(legalBox?.height);
|
|
3306
|
+
if (height !== legalHeight || !root.style.getPropertyValue(LEGAL_HEIGHT_PROPERTY)) {
|
|
3307
|
+
legalHeight = height;
|
|
3308
|
+
root.style.setProperty(LEGAL_HEIGHT_PROPERTY, `${height}px`);
|
|
3309
|
+
}
|
|
3310
|
+
zoomRaised = zoomRowRaised({
|
|
3311
|
+
containerWidth: size.width,
|
|
3312
|
+
legalRight: legalBox && legalBox.x + legalBox.width,
|
|
3313
|
+
zoomWidth: visibleBoxIn(zoomPanel.element, root)?.width
|
|
3314
|
+
});
|
|
3315
|
+
setState("data-zoom-row", zoomRaised ? "raised" : null);
|
|
3316
|
+
}
|
|
3317
|
+
if (toaster) {
|
|
3318
|
+
const obstacles = [zoomPanel.element, legal?.element, pill?.element].flatMap(
|
|
3319
|
+
(element) => {
|
|
3320
|
+
const box = visibleBoxIn(element, root);
|
|
3321
|
+
return box ? [{ left: box.x, right: box.x + box.width, top: size.height - box.y }] : [];
|
|
3322
|
+
}
|
|
3323
|
+
);
|
|
3324
|
+
toaster.setBottom(
|
|
3325
|
+
toastStackBottom({ containerWidth: size.width, obstacles, cardWidth: toaster.widestCard() })
|
|
3326
|
+
);
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
function setEmptyStateShown(shown) {
|
|
3330
|
+
if (ledger.isClosed || !emptyState || emptyState.isShown() === shown) return;
|
|
3331
|
+
const active = ownerDocument.activeElement;
|
|
3332
|
+
const hadFocus = emptyState.element.contains(active);
|
|
3333
|
+
const workHadFocus = shown && active !== null && root.contains(active) && ![emptyState.element, legal?.element, toaster?.element].some((kept) => kept?.contains(active));
|
|
3334
|
+
emptyState.setShown(shown);
|
|
3335
|
+
setState("data-empty-state", shown ? "shown" : null);
|
|
3336
|
+
ledger.attribute(canvas, "inert", shown ? "" : null);
|
|
3337
|
+
if (shown) {
|
|
3338
|
+
menu.close();
|
|
3339
|
+
popover?.close();
|
|
3340
|
+
tooltip.hide();
|
|
3341
|
+
if (workHadFocus) emptyState.focus();
|
|
3342
|
+
} else if (hadFocus) {
|
|
3343
|
+
focusCanvasOrTrigger();
|
|
3344
|
+
}
|
|
3345
|
+
updateLayout();
|
|
3346
|
+
}
|
|
1943
3347
|
const ResizeObserverClass = globalThis.ResizeObserver;
|
|
1944
3348
|
const resizeObserver = ResizeObserverClass ? new ResizeObserverClass(() => updateLayout()) : void 0;
|
|
1945
3349
|
resizeObserver?.observe(root);
|
|
3350
|
+
if (primary) resizeObserver?.observe(primary.element);
|
|
3351
|
+
if (legal) resizeObserver?.observe(legal.element);
|
|
1946
3352
|
ledger.defer(() => resizeObserver?.disconnect());
|
|
1947
3353
|
if (legend && options.legend?.open !== void 0) legend.start(options.legend.open);
|
|
1948
3354
|
updateLayout();
|
|
@@ -1951,12 +3357,16 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1951
3357
|
host,
|
|
1952
3358
|
dialogScope: keyTarget,
|
|
1953
3359
|
menuOpen: () => menu.isOpen(),
|
|
3360
|
+
startScreen: () => emptyState?.isShown() ?? false,
|
|
1954
3361
|
closeMenu() {
|
|
1955
3362
|
const active = ownerDocument.activeElement;
|
|
1956
3363
|
menu.close({ restoreFocus: active === menu.trigger || menu.dropdown.contains(active) });
|
|
1957
3364
|
},
|
|
1958
3365
|
hideTooltip: () => tooltip.hide(),
|
|
1959
|
-
|
|
3366
|
+
// Behind the start screen the keys reach the browser instead: there is no diagram to zoom.
|
|
3367
|
+
run: (key) => {
|
|
3368
|
+
if (!emptyState?.isShown()) zoomPanel.run(key);
|
|
3369
|
+
}
|
|
1960
3370
|
});
|
|
1961
3371
|
keyTarget.addEventListener("keydown", onKeydown);
|
|
1962
3372
|
ledger.defer(() => keyTarget.removeEventListener("keydown", onKeydown));
|
|
@@ -2008,6 +3418,21 @@ function renderShell(root, options, themes, ledger) {
|
|
|
2008
3418
|
if (!ledger.isClosed) legend.toggle();
|
|
2009
3419
|
}
|
|
2010
3420
|
};
|
|
3421
|
+
const primaryHandle = primary && {
|
|
3422
|
+
element: primary.element,
|
|
3423
|
+
isCollapsed: () => primary.isCollapsed(),
|
|
3424
|
+
refresh: () => {
|
|
3425
|
+
if (!ledger.isClosed) primary.refresh();
|
|
3426
|
+
}
|
|
3427
|
+
};
|
|
3428
|
+
const appearanceHandle = appearance && darkCapable ? {
|
|
3429
|
+
choice: () => appearance.choice(),
|
|
3430
|
+
setChoice: (choice) => {
|
|
3431
|
+
if (ledger.isClosed) return;
|
|
3432
|
+
appearance.setChoice(choice);
|
|
3433
|
+
menu.refresh();
|
|
3434
|
+
}
|
|
3435
|
+
} : void 0;
|
|
2011
3436
|
const inspectorHandle = inspector && {
|
|
2012
3437
|
element: inspector.element,
|
|
2013
3438
|
isOpen: () => inspector.isOpen(),
|
|
@@ -2031,6 +3456,8 @@ function renderShell(root, options, themes, ledger) {
|
|
|
2031
3456
|
tooltip: tooltipHandle,
|
|
2032
3457
|
legend: legendHandle,
|
|
2033
3458
|
inspector: inspectorHandle,
|
|
3459
|
+
primary: primaryHandle,
|
|
3460
|
+
appearance: appearanceHandle,
|
|
2034
3461
|
setZoomTarget(target) {
|
|
2035
3462
|
if (!ledger.isClosed) zoomPanel.setTarget(target);
|
|
2036
3463
|
},
|
|
@@ -2059,8 +3486,15 @@ function renderShell(root, options, themes, ledger) {
|
|
|
2059
3486
|
},
|
|
2060
3487
|
setProblems(summary) {
|
|
2061
3488
|
if (ledger.isClosed || !pill) return;
|
|
3489
|
+
const listHadFocus = popover?.element.contains(ownerDocument.activeElement) ?? false;
|
|
2062
3490
|
pill.set(summary);
|
|
2063
3491
|
setState("data-problems", pill.isShown() ? "shown" : null);
|
|
3492
|
+
popover?.setItems(summary.items ?? []);
|
|
3493
|
+
if (!pill.isShown() && popover?.isOpen()) {
|
|
3494
|
+
popover.close();
|
|
3495
|
+
if (listHadFocus) focusCanvasOrTrigger();
|
|
3496
|
+
}
|
|
3497
|
+
updateBottomRow();
|
|
2064
3498
|
placeHint();
|
|
2065
3499
|
},
|
|
2066
3500
|
setStatus(next, statusOptions) {
|
|
@@ -2090,6 +3524,9 @@ function renderShell(root, options, themes, ledger) {
|
|
|
2090
3524
|
applyReadOnly();
|
|
2091
3525
|
},
|
|
2092
3526
|
isReadOnly: () => readOnly,
|
|
3527
|
+
setEmptyState: setEmptyStateShown,
|
|
3528
|
+
isEmptyStateShown: () => emptyState?.isShown() ?? false,
|
|
3529
|
+
toast: showToast,
|
|
2093
3530
|
destroy() {
|
|
2094
3531
|
ledger.close();
|
|
2095
3532
|
}
|
|
@@ -2116,21 +3553,27 @@ function carryOwnMode(root, theme, ledger) {
|
|
|
2116
3553
|
}
|
|
2117
3554
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2118
3555
|
0 && (module.exports = {
|
|
3556
|
+
APPEARANCE_STORAGE_KEY,
|
|
2119
3557
|
CHROME_ISLANDS,
|
|
2120
3558
|
FIT_ANIMATION_MS,
|
|
2121
3559
|
ICONS,
|
|
2122
3560
|
INVALID_STATUS_DELAY_MS,
|
|
3561
|
+
LEGAL_LINE_HEIGHT,
|
|
2123
3562
|
MAX_FIT_ZOOM,
|
|
3563
|
+
MAX_TOASTS,
|
|
2124
3564
|
PALETTE_CLEARANCE,
|
|
2125
3565
|
PALETTE_TOOL_ICON,
|
|
2126
3566
|
REDUCED_MOTION_CLASS,
|
|
2127
3567
|
SHELL,
|
|
2128
3568
|
SHELL_TOKENS,
|
|
2129
3569
|
TEXT_FIELD_SELECTOR,
|
|
3570
|
+
TOAST_DURATION_MS,
|
|
3571
|
+
TOAST_MAX_WIDTH,
|
|
2130
3572
|
TOOLTIP_DELAY_MS,
|
|
2131
3573
|
VIEW_TOLERANCE,
|
|
2132
3574
|
ZOOM_MAX,
|
|
2133
3575
|
ZOOM_MIN,
|
|
3576
|
+
acceptsFile,
|
|
2134
3577
|
appearanceAttributes,
|
|
2135
3578
|
appearanceFromBodyClasses,
|
|
2136
3579
|
appearanceFromColorThemeKind,
|
|
@@ -2138,10 +3581,14 @@ function carryOwnMode(root, theme, ledger) {
|
|
|
2138
3581
|
ariaKeyShortcutsFromLabel,
|
|
2139
3582
|
canZoomIn,
|
|
2140
3583
|
canZoomOut,
|
|
3584
|
+
commandKeyLabel,
|
|
2141
3585
|
decideKey,
|
|
2142
3586
|
decidePaletteRow,
|
|
3587
|
+
decideTopRow,
|
|
2143
3588
|
defaultMessages,
|
|
2144
3589
|
detectPlatform,
|
|
3590
|
+
documentMode,
|
|
3591
|
+
dragVerdict,
|
|
2145
3592
|
easeStandard,
|
|
2146
3593
|
fitInsets,
|
|
2147
3594
|
fitViewbox,
|
|
@@ -2152,10 +3599,13 @@ function carryOwnMode(root, theme, ledger) {
|
|
|
2152
3599
|
iconMarkup,
|
|
2153
3600
|
interpolateViewbox,
|
|
2154
3601
|
isUndoRedoKey,
|
|
3602
|
+
legalLineHeight,
|
|
2155
3603
|
legendStartsOpen,
|
|
2156
3604
|
matchChromeKey,
|
|
2157
3605
|
mountShell,
|
|
3606
|
+
parseAppearanceChoice,
|
|
2158
3607
|
placeTooltip,
|
|
3608
|
+
problemSummary,
|
|
2159
3609
|
reducedMotionFromBodyClasses,
|
|
2160
3610
|
requestedAppearance,
|
|
2161
3611
|
resolveAppearance,
|
|
@@ -2163,7 +3613,14 @@ function carryOwnMode(root, theme, ledger) {
|
|
|
2163
3613
|
sameView,
|
|
2164
3614
|
shellSizesFromTokens,
|
|
2165
3615
|
shortcutLabel,
|
|
2166
|
-
|
|
3616
|
+
toastLifetime,
|
|
3617
|
+
toastStackBottom,
|
|
3618
|
+
toastStackWidth,
|
|
3619
|
+
toastToEvict,
|
|
3620
|
+
webHeadMarkup,
|
|
3621
|
+
webModeScript,
|
|
3622
|
+
zoomReadout,
|
|
3623
|
+
zoomRowRaised
|
|
2167
3624
|
});
|
|
2168
3625
|
/*! @miragon/modeler-shell icon geometry
|
|
2169
3626
|
* Lucide (lucide-static 1.59.0) @license ISC
|