@miragon/modeler-shell 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +1051 -84
- package/README.md +162 -31
- package/dist/appearance-BBaM_4bD.d.ts +323 -0
- package/dist/appearance-XX3JC3pI.d.cts +323 -0
- package/dist/{chunk-6LQOFACK.js → chunk-A4YGKH4Y.js} +1252 -31
- package/dist/chunk-A4YGKH4Y.js.map +1 -0
- package/dist/{chunk-RJRFR6N3.js → chunk-KYDG5DMX.js} +2 -2
- package/dist/{chunk-5JSZ4VGA.js → chunk-OADW3GPA.js} +240 -6
- package/dist/chunk-OADW3GPA.js.map +1 -0
- package/dist/core.cjs +260 -6
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +81 -6
- package/dist/core.d.ts +81 -6
- package/dist/core.js +43 -3
- package/dist/diagram-js.cjs +34 -2
- package/dist/diagram-js.cjs.map +1 -1
- package/dist/diagram-js.d.cts +10 -3
- package/dist/diagram-js.d.ts +10 -3
- package/dist/diagram-js.js +14 -2
- package/dist/diagram-js.js.map +1 -1
- package/dist/index.cjs +1658 -196
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -9
- package/dist/index.d.ts +18 -9
- package/dist/index.js +44 -4
- package/dist/shell.css +692 -3
- package/dist/testing-vscode-stub.js +2 -2
- package/dist/testing.cjs +2849 -550
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +75 -9
- package/dist/testing.d.ts +75 -9
- package/dist/testing.js +2744 -558
- package/dist/testing.js.map +1 -1
- package/dist/toasts-Bp0REqdq.d.cts +142 -0
- package/dist/toasts-Bp0REqdq.d.ts +142 -0
- package/dist/{types-OuFlqZp0.d.cts → types-CnRrINRs.d.cts} +170 -9
- package/dist/{types-DnZn9CPw.d.ts → types-PuDZczzy.d.ts} +170 -9
- package/dist/vscode-extension.js +2 -2
- package/dist/vscode-webview.cjs +1546 -178
- package/dist/vscode-webview.cjs.map +1 -1
- package/dist/vscode-webview.d.cts +6 -4
- package/dist/vscode-webview.d.ts +6 -4
- package/dist/vscode-webview.js +11 -2
- package/dist/vscode-webview.js.map +1 -1
- package/package.json +2 -2
- package/src/core/feedback/appearance.ts +141 -0
- package/src/core/feedback/problems.ts +23 -0
- package/src/core/feedback/toasts.ts +118 -0
- package/src/core/fit.ts +18 -3
- package/src/core/index.ts +43 -0
- package/src/core/keymap.ts +20 -0
- package/src/core/messages.ts +71 -0
- package/src/core/types.ts +24 -2
- package/src/core/web-chrome/bottom-row.ts +37 -0
- package/src/core/web-chrome/drop.ts +78 -0
- package/src/core/web-chrome/top-row.ts +66 -0
- package/src/diagram-js/service.ts +4 -1
- package/src/diagram-js/viewport.ts +19 -0
- package/src/dom/buttons.css +124 -0
- package/src/dom/feedback/appearance.ts +184 -0
- package/src/dom/feedback/feedback.css +435 -0
- package/src/dom/feedback/problems-popover.ts +287 -0
- package/src/dom/feedback/toasts.ts +431 -0
- package/src/dom/index.ts +13 -0
- package/src/dom/keys.ts +8 -0
- package/src/dom/menu.ts +28 -17
- package/src/dom/mount.ts +290 -20
- package/src/dom/problems.ts +5 -4
- package/src/dom/shell-dom.css +7 -4
- package/src/dom/tooltip.ts +10 -0
- package/src/dom/types.ts +188 -6
- package/src/dom/web/drop-zone.ts +181 -0
- package/src/dom/web/empty-state.ts +144 -0
- package/src/dom/web/legal.ts +75 -0
- package/src/dom/web/menu-groups.ts +69 -0
- package/src/dom/web/primary.ts +124 -0
- package/src/dom/web/web.css +386 -0
- package/src/shell.css +3 -0
- package/src/testing/case-tools.ts +164 -0
- package/src/testing/cases.ts +209 -144
- package/src/testing/checks.ts +51 -15
- package/src/testing/facts.ts +56 -2
- package/src/testing/geometry.ts +206 -26
- package/src/testing/harness.ts +56 -7
- package/src/testing/index.ts +4 -0
- package/src/testing/page-tools.ts +24 -1
- package/src/testing/page.ts +10 -0
- package/src/testing/web-cases.ts +913 -0
- package/src/testing/web-checks.ts +1049 -0
- package/src/testing/web-facts.ts +713 -0
- package/src/vscode/webview/mount.ts +13 -1
- package/dist/chunk-5JSZ4VGA.js.map +0 -1
- package/dist/chunk-6LQOFACK.js.map +0 -1
- package/dist/keymap-CObQE6q6.d.cts +0 -151
- package/dist/keymap-DORzHnK-.d.ts +0 -151
- package/dist/types-B6NfkiUG.d.cts +0 -66
- package/dist/types-B6NfkiUG.d.ts +0 -66
- /package/dist/{chunk-RJRFR6N3.js.map → chunk-KYDG5DMX.js.map} +0 -0
|
@@ -1,24 +1,37 @@
|
|
|
1
1
|
import {
|
|
2
|
+
APPEARANCE_STORAGE_KEY,
|
|
3
|
+
LEGAL_LINE_HEIGHT,
|
|
4
|
+
MAX_TOASTS,
|
|
5
|
+
REDUCED_MOTION_CLASS,
|
|
2
6
|
SHELL,
|
|
3
7
|
TEXT_FIELD_SELECTOR,
|
|
4
8
|
TOOLTIP_DELAY_MS,
|
|
9
|
+
acceptsFile,
|
|
5
10
|
ariaKeyShortcuts,
|
|
6
11
|
ariaKeyShortcutsFromLabel,
|
|
7
12
|
canZoomIn,
|
|
8
13
|
canZoomOut,
|
|
9
14
|
decideKey,
|
|
10
|
-
|
|
15
|
+
decideTopRow,
|
|
11
16
|
detectPlatform,
|
|
17
|
+
documentMode,
|
|
18
|
+
dragVerdict,
|
|
12
19
|
fitInsets,
|
|
13
20
|
formatNumber,
|
|
14
21
|
formatPercent,
|
|
22
|
+
legalLineHeight,
|
|
15
23
|
legendStartsOpen,
|
|
16
24
|
matchChromeKey,
|
|
25
|
+
parseAppearanceChoice,
|
|
17
26
|
placeTooltip,
|
|
18
27
|
resolveMessages,
|
|
19
28
|
shortcutLabel,
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
toastLifetime,
|
|
30
|
+
toastStackBottom,
|
|
31
|
+
toastToEvict,
|
|
32
|
+
zoomReadout,
|
|
33
|
+
zoomRowRaised
|
|
34
|
+
} from "./chunk-OADW3GPA.js";
|
|
22
35
|
|
|
23
36
|
// src/dom/icons.ts
|
|
24
37
|
var ICONS = {
|
|
@@ -288,6 +301,636 @@ function hasVisibleFocus(element) {
|
|
|
288
301
|
}
|
|
289
302
|
}
|
|
290
303
|
|
|
304
|
+
// src/dom/feedback/appearance.ts
|
|
305
|
+
var NO_TRANSITION_CLASS = "cd-shell-no-transition";
|
|
306
|
+
var CONTRAST_QUERY = "(prefers-contrast: more)";
|
|
307
|
+
var SCHEME_MEDIA = {
|
|
308
|
+
light: "(prefers-color-scheme: light)",
|
|
309
|
+
dark: "(prefers-color-scheme: dark)"
|
|
310
|
+
};
|
|
311
|
+
function onMediaChange(query, listener) {
|
|
312
|
+
if (typeof query.addEventListener === "function") {
|
|
313
|
+
query.addEventListener("change", listener);
|
|
314
|
+
return () => query.removeEventListener("change", listener);
|
|
315
|
+
}
|
|
316
|
+
query.addListener(listener);
|
|
317
|
+
return () => query.removeListener(listener);
|
|
318
|
+
}
|
|
319
|
+
function connectWebAppearance(context) {
|
|
320
|
+
const { ownerDocument, themes, options } = context;
|
|
321
|
+
const darkCapable = themes.includes("dark");
|
|
322
|
+
if (darkCapable && !options) {
|
|
323
|
+
throw new Error(
|
|
324
|
+
'@miragon/modeler-shell: a web modeler whose themes include "dark" needs the appearance option ({ themeColors } from its vendored cd-tokens.generated.json)'
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
const html = ownerDocument.documentElement;
|
|
328
|
+
const view = ownerDocument.defaultView;
|
|
329
|
+
const cleanups = [];
|
|
330
|
+
const contrast = typeof view?.matchMedia === "function" ? view.matchMedia(CONTRAST_QUERY) : void 0;
|
|
331
|
+
if (contrast) {
|
|
332
|
+
const mirrorContrast = () => toggleAttribute(html, "data-cd-kontrast", contrast.matches && "hoch");
|
|
333
|
+
mirrorContrast();
|
|
334
|
+
cleanups.push(onMediaChange(contrast, mirrorContrast));
|
|
335
|
+
}
|
|
336
|
+
function destroy() {
|
|
337
|
+
for (const cleanup of cleanups.splice(0)) cleanup();
|
|
338
|
+
}
|
|
339
|
+
if (!darkCapable || !options) {
|
|
340
|
+
return { choice: () => "system", setChoice() {
|
|
341
|
+
}, destroy };
|
|
342
|
+
}
|
|
343
|
+
const { themeColors } = options;
|
|
344
|
+
function storage() {
|
|
345
|
+
if (options?.storage) return options.storage;
|
|
346
|
+
try {
|
|
347
|
+
return globalThis.localStorage;
|
|
348
|
+
} catch {
|
|
349
|
+
return void 0;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function readStored() {
|
|
353
|
+
try {
|
|
354
|
+
return parseAppearanceChoice(storage()?.getItem(APPEARANCE_STORAGE_KEY));
|
|
355
|
+
} catch {
|
|
356
|
+
return "system";
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function store(choice) {
|
|
360
|
+
try {
|
|
361
|
+
const target = storage();
|
|
362
|
+
if (choice === "system") target?.removeItem(APPEARANCE_STORAGE_KEY);
|
|
363
|
+
else target?.setItem(APPEARANCE_STORAGE_KEY, choice);
|
|
364
|
+
} catch {
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function nextFrame(callback) {
|
|
368
|
+
if (view && typeof view.requestAnimationFrame === "function") {
|
|
369
|
+
const frame = view.requestAnimationFrame(() => callback());
|
|
370
|
+
return () => view.cancelAnimationFrame(frame);
|
|
371
|
+
}
|
|
372
|
+
const timer = setTimeout(callback, 16);
|
|
373
|
+
return () => clearTimeout(timer);
|
|
374
|
+
}
|
|
375
|
+
let cancelTransitionFrame;
|
|
376
|
+
function allowTransitions() {
|
|
377
|
+
cancelTransitionFrame?.();
|
|
378
|
+
cancelTransitionFrame = void 0;
|
|
379
|
+
html.classList.remove(NO_TRANSITION_CLASS);
|
|
380
|
+
}
|
|
381
|
+
cleanups.push(() => {
|
|
382
|
+
if (cancelTransitionFrame) allowTransitions();
|
|
383
|
+
});
|
|
384
|
+
function suspendTransitions() {
|
|
385
|
+
cancelTransitionFrame?.();
|
|
386
|
+
html.classList.add(NO_TRANSITION_CLASS);
|
|
387
|
+
cancelTransitionFrame = nextFrame(() => {
|
|
388
|
+
cancelTransitionFrame = nextFrame(allowTransitions);
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
function metaFor(name, media) {
|
|
392
|
+
const normalize = (value) => (value ?? "").replace(/\s+/g, "").toLowerCase();
|
|
393
|
+
const existing = [...ownerDocument.querySelectorAll(`meta[name="${name}"]`)].find(
|
|
394
|
+
(meta) => media === void 0 ? true : normalize(meta.getAttribute("media")) === normalize(media)
|
|
395
|
+
);
|
|
396
|
+
if (existing) return existing;
|
|
397
|
+
const created = createElement(ownerDocument, "meta", { name, media });
|
|
398
|
+
(ownerDocument.head ?? html).append(created);
|
|
399
|
+
return created;
|
|
400
|
+
}
|
|
401
|
+
function apply(choice) {
|
|
402
|
+
const mode = documentMode(choice, themes, themeColors);
|
|
403
|
+
suspendTransitions();
|
|
404
|
+
html.setAttribute("data-cd-theme", mode.theme);
|
|
405
|
+
metaFor("color-scheme").setAttribute("content", mode.colorScheme);
|
|
406
|
+
metaFor("theme-color", SCHEME_MEDIA.light).setAttribute("content", mode.themeColor.light);
|
|
407
|
+
metaFor("theme-color", SCHEME_MEDIA.dark).setAttribute("content", mode.themeColor.dark);
|
|
408
|
+
}
|
|
409
|
+
let current = readStored();
|
|
410
|
+
const onStorage = (event) => {
|
|
411
|
+
if (event.key !== null && event.key !== APPEARANCE_STORAGE_KEY) return;
|
|
412
|
+
const next = event.key === null ? "system" : parseAppearanceChoice(event.newValue);
|
|
413
|
+
if (next === current) return;
|
|
414
|
+
current = next;
|
|
415
|
+
apply(next);
|
|
416
|
+
};
|
|
417
|
+
if (view) {
|
|
418
|
+
view.addEventListener("storage", onStorage);
|
|
419
|
+
cleanups.push(() => view.removeEventListener("storage", onStorage));
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
choice: () => current,
|
|
423
|
+
setChoice(choice) {
|
|
424
|
+
const next = parseAppearanceChoice(choice);
|
|
425
|
+
current = next;
|
|
426
|
+
store(next);
|
|
427
|
+
apply(next);
|
|
428
|
+
},
|
|
429
|
+
destroy
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// src/dom/feedback/problems-popover.ts
|
|
434
|
+
var PILL_ATTRIBUTES = ["aria-haspopup", "aria-expanded", "aria-controls"];
|
|
435
|
+
function isPrintable(event) {
|
|
436
|
+
return event.key.length === 1 && /\S/.test(event.key) && !event.ctrlKey && !event.metaKey && !event.altKey;
|
|
437
|
+
}
|
|
438
|
+
function createProblemsPopover(context) {
|
|
439
|
+
const { ownerDocument, root, layer, pill, id, titleId, messages } = context;
|
|
440
|
+
const view = ownerDocument.defaultView;
|
|
441
|
+
const list = createElement(ownerDocument, "ul", { class: "cd-shell-problems-list", role: "list" });
|
|
442
|
+
const element = createElement(
|
|
443
|
+
ownerDocument,
|
|
444
|
+
"div",
|
|
445
|
+
{
|
|
446
|
+
class: "cd-shell-problems-popover",
|
|
447
|
+
"data-chrome": "problems-popover",
|
|
448
|
+
role: "dialog",
|
|
449
|
+
id,
|
|
450
|
+
"aria-labelledby": titleId,
|
|
451
|
+
tabindex: "-1"
|
|
452
|
+
},
|
|
453
|
+
createElement(
|
|
454
|
+
ownerDocument,
|
|
455
|
+
"h2",
|
|
456
|
+
{ class: "cd-shell-problems-popover-title", id: titleId },
|
|
457
|
+
messages.problemsTitle
|
|
458
|
+
),
|
|
459
|
+
list
|
|
460
|
+
);
|
|
461
|
+
element.hidden = true;
|
|
462
|
+
layer.append(element);
|
|
463
|
+
pill.setAttribute("aria-haspopup", "dialog");
|
|
464
|
+
pill.setAttribute("aria-expanded", "false");
|
|
465
|
+
pill.setAttribute("aria-controls", id);
|
|
466
|
+
let items = [];
|
|
467
|
+
let rows = [];
|
|
468
|
+
let destroyed = false;
|
|
469
|
+
const isOpen = () => !element.hidden;
|
|
470
|
+
function keyOf(item) {
|
|
471
|
+
return `${item.elementId ?? ""}\0${item.severity}\0${item.message}`;
|
|
472
|
+
}
|
|
473
|
+
function rowElement(item) {
|
|
474
|
+
const severity = item.severity === "error" ? "error" : "warning";
|
|
475
|
+
const parts = [
|
|
476
|
+
// The shape and colour of the icon carry the severity on screen, its name for screen readers.
|
|
477
|
+
createElement(
|
|
478
|
+
ownerDocument,
|
|
479
|
+
"span",
|
|
480
|
+
{ class: "cd-shell-problem-icon", role: "img", "aria-label": messages.problemSeverity(severity) },
|
|
481
|
+
iconElement(severity === "error" ? "circle-x" : "triangle-alert")
|
|
482
|
+
),
|
|
483
|
+
createElement(ownerDocument, "span", { class: "cd-shell-problem-message" }, item.message),
|
|
484
|
+
item.location ? createElement(ownerDocument, "span", { class: "cd-shell-problem-location" }, item.location) : void 0
|
|
485
|
+
];
|
|
486
|
+
const attributes = { class: "cd-shell-problem", "data-severity": severity };
|
|
487
|
+
if (!item.elementId) {
|
|
488
|
+
const text = createElement(ownerDocument, "div", { ...attributes, tabindex: "-1" }, ...parts);
|
|
489
|
+
rows.push({ key: keyOf(item), element: text });
|
|
490
|
+
return text;
|
|
491
|
+
}
|
|
492
|
+
const button = createElement(ownerDocument, "button", { type: "button", ...attributes }, ...parts);
|
|
493
|
+
button.addEventListener("click", () => select(item));
|
|
494
|
+
rows.push({ key: keyOf(item), element: button });
|
|
495
|
+
return button;
|
|
496
|
+
}
|
|
497
|
+
function focusedRowIndex() {
|
|
498
|
+
return rows.findIndex((row) => row.element === ownerDocument.activeElement);
|
|
499
|
+
}
|
|
500
|
+
function render() {
|
|
501
|
+
const focusedIndex = focusedRowIndex();
|
|
502
|
+
const focusedKey = rows[focusedIndex]?.key;
|
|
503
|
+
rows = [];
|
|
504
|
+
list.replaceChildren(...items.map((item) => createElement(ownerDocument, "li", {}, rowElement(item))));
|
|
505
|
+
if (focusedKey === void 0 || !isOpen()) return;
|
|
506
|
+
const kept = rows.find((row) => row.key === focusedKey) ?? rows[Math.min(focusedIndex, rows.length - 1)];
|
|
507
|
+
if (kept) focusRowElement(kept.element);
|
|
508
|
+
else element.focus({ preventScroll: true });
|
|
509
|
+
}
|
|
510
|
+
function revealInList(row) {
|
|
511
|
+
const rowBox = row.getBoundingClientRect();
|
|
512
|
+
const listBox = list.getBoundingClientRect();
|
|
513
|
+
if (rowBox.top < listBox.top) list.scrollTop -= listBox.top - rowBox.top;
|
|
514
|
+
else if (rowBox.bottom > listBox.bottom) list.scrollTop += rowBox.bottom - listBox.bottom;
|
|
515
|
+
}
|
|
516
|
+
function focusRowElement(row) {
|
|
517
|
+
row.focus({ preventScroll: true });
|
|
518
|
+
revealInList(row);
|
|
519
|
+
}
|
|
520
|
+
function focusCanvas() {
|
|
521
|
+
const canvas = root.querySelector(".cd-shell-canvas svg[tabindex]");
|
|
522
|
+
canvas?.focus({ preventScroll: true });
|
|
523
|
+
if ((!canvas || ownerDocument.activeElement !== canvas) && !pill.hidden)
|
|
524
|
+
pill.focus({ preventScroll: true });
|
|
525
|
+
}
|
|
526
|
+
function select(item) {
|
|
527
|
+
close();
|
|
528
|
+
focusCanvas();
|
|
529
|
+
context.onSelectItem?.(item);
|
|
530
|
+
}
|
|
531
|
+
function focusRow(index) {
|
|
532
|
+
if (rows.length === 0) return;
|
|
533
|
+
const row = rows[(index + rows.length) % rows.length];
|
|
534
|
+
if (row) focusRowElement(row.element);
|
|
535
|
+
}
|
|
536
|
+
const onOutsidePointerDown = (event) => {
|
|
537
|
+
const target = event.target;
|
|
538
|
+
if (target && (element.contains(target) || pill.contains(target))) return;
|
|
539
|
+
close();
|
|
540
|
+
};
|
|
541
|
+
const onWindowBlur = () => close();
|
|
542
|
+
function open() {
|
|
543
|
+
if (destroyed || isOpen()) return;
|
|
544
|
+
element.hidden = false;
|
|
545
|
+
pill.setAttribute("aria-expanded", "true");
|
|
546
|
+
ownerDocument.addEventListener("pointerdown", onOutsidePointerDown, true);
|
|
547
|
+
view?.addEventListener("blur", onWindowBlur);
|
|
548
|
+
list.scrollTop = 0;
|
|
549
|
+
const first = rows[0];
|
|
550
|
+
if (first) focusRowElement(first.element);
|
|
551
|
+
else element.focus({ preventScroll: true });
|
|
552
|
+
}
|
|
553
|
+
function close(options = {}) {
|
|
554
|
+
if (!isOpen()) return;
|
|
555
|
+
element.hidden = true;
|
|
556
|
+
pill.setAttribute("aria-expanded", "false");
|
|
557
|
+
ownerDocument.removeEventListener("pointerdown", onOutsidePointerDown, true);
|
|
558
|
+
view?.removeEventListener("blur", onWindowBlur);
|
|
559
|
+
if (options.restoreFocus && !pill.hidden) pill.focus({ preventScroll: true });
|
|
560
|
+
}
|
|
561
|
+
function consume(event) {
|
|
562
|
+
event.preventDefault();
|
|
563
|
+
event.stopPropagation();
|
|
564
|
+
}
|
|
565
|
+
element.addEventListener("keydown", (event) => {
|
|
566
|
+
if (matchChromeKey(event)) return;
|
|
567
|
+
const index = focusedRowIndex();
|
|
568
|
+
switch (event.key) {
|
|
569
|
+
case "Escape":
|
|
570
|
+
consume(event);
|
|
571
|
+
close({ restoreFocus: true });
|
|
572
|
+
return;
|
|
573
|
+
case "Tab": {
|
|
574
|
+
const first = index <= 0;
|
|
575
|
+
const last = rows.length === 0 || index === rows.length - 1;
|
|
576
|
+
if (event.shiftKey && first) {
|
|
577
|
+
consume(event);
|
|
578
|
+
close({ restoreFocus: true });
|
|
579
|
+
} else if (!event.shiftKey && last) {
|
|
580
|
+
event.stopPropagation();
|
|
581
|
+
close({ restoreFocus: true });
|
|
582
|
+
}
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
case "ArrowDown":
|
|
586
|
+
focusRow(index + 1);
|
|
587
|
+
break;
|
|
588
|
+
case "ArrowUp":
|
|
589
|
+
focusRow(index < 0 ? rows.length - 1 : index - 1);
|
|
590
|
+
break;
|
|
591
|
+
case "Home":
|
|
592
|
+
focusRow(0);
|
|
593
|
+
break;
|
|
594
|
+
case "End":
|
|
595
|
+
focusRow(rows.length - 1);
|
|
596
|
+
break;
|
|
597
|
+
case "Enter":
|
|
598
|
+
case " ":
|
|
599
|
+
case "Delete":
|
|
600
|
+
case "Backspace":
|
|
601
|
+
event.stopPropagation();
|
|
602
|
+
return;
|
|
603
|
+
default:
|
|
604
|
+
if (isPrintable(event)) event.stopPropagation();
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
consume(event);
|
|
608
|
+
});
|
|
609
|
+
return {
|
|
610
|
+
element,
|
|
611
|
+
isOpen,
|
|
612
|
+
toggle() {
|
|
613
|
+
if (isOpen()) close({ restoreFocus: true });
|
|
614
|
+
else open();
|
|
615
|
+
},
|
|
616
|
+
open,
|
|
617
|
+
close,
|
|
618
|
+
setItems(next) {
|
|
619
|
+
items = next;
|
|
620
|
+
if (!destroyed) render();
|
|
621
|
+
},
|
|
622
|
+
destroy() {
|
|
623
|
+
if (destroyed) return;
|
|
624
|
+
close();
|
|
625
|
+
destroyed = true;
|
|
626
|
+
for (const name of PILL_ATTRIBUTES) pill.removeAttribute(name);
|
|
627
|
+
element.remove();
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// src/dom/feedback/toasts.ts
|
|
633
|
+
var TOAST_RESUME_MIN_MS = 1500;
|
|
634
|
+
var TOAST_LEAVE_FALLBACK_MS = 400;
|
|
635
|
+
var LEAVE_ANIMATION = "cd-shell-toast-out";
|
|
636
|
+
var TOAST_ANNOUNCE_DELAY_MS = 100;
|
|
637
|
+
var INERT_HANDLE = Object.freeze({ dismiss() {
|
|
638
|
+
} });
|
|
639
|
+
function createToaster(context) {
|
|
640
|
+
const { ownerDocument, messages } = context;
|
|
641
|
+
const web = context.host === "web";
|
|
642
|
+
const element = createElement(ownerDocument, "div", {
|
|
643
|
+
class: "cd-shell-toasts",
|
|
644
|
+
"data-chrome": "toasts",
|
|
645
|
+
popover: web ? "manual" : void 0
|
|
646
|
+
});
|
|
647
|
+
const liveRegion = (politeness) => createElement(ownerDocument, "div", {
|
|
648
|
+
class: "cd-shell-live-region",
|
|
649
|
+
"data-live": politeness,
|
|
650
|
+
role: politeness === "polite" ? "status" : "alert",
|
|
651
|
+
"aria-live": politeness,
|
|
652
|
+
"aria-atomic": "true"
|
|
653
|
+
});
|
|
654
|
+
const politeRegion = liveRegion("polite");
|
|
655
|
+
const assertiveRegion = liveRegion("assertive");
|
|
656
|
+
const announcer = createElement(
|
|
657
|
+
ownerDocument,
|
|
658
|
+
"div",
|
|
659
|
+
{ class: "cd-shell-live" },
|
|
660
|
+
politeRegion,
|
|
661
|
+
assertiveRegion
|
|
662
|
+
);
|
|
663
|
+
const live = [];
|
|
664
|
+
const leaveTimers = /* @__PURE__ */ new Set();
|
|
665
|
+
const announceTimers = /* @__PURE__ */ new Map();
|
|
666
|
+
let lastId = 0;
|
|
667
|
+
let destroyed = false;
|
|
668
|
+
function regionFor(request) {
|
|
669
|
+
return request.status === "danger" ? assertiveRegion : politeRegion;
|
|
670
|
+
}
|
|
671
|
+
function announce(request) {
|
|
672
|
+
const region = regionFor(request);
|
|
673
|
+
clearTimeout(announceTimers.get(region));
|
|
674
|
+
region.textContent = "";
|
|
675
|
+
announceTimers.set(
|
|
676
|
+
region,
|
|
677
|
+
setTimeout(() => {
|
|
678
|
+
announceTimers.delete(region);
|
|
679
|
+
if (!destroyed) region.textContent = request.text;
|
|
680
|
+
}, TOAST_ANNOUNCE_DELAY_MS)
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
function silence(request) {
|
|
684
|
+
const region = regionFor(request);
|
|
685
|
+
if (region.textContent === request.text) region.textContent = "";
|
|
686
|
+
}
|
|
687
|
+
function changed() {
|
|
688
|
+
if (!destroyed) context.onChange?.();
|
|
689
|
+
}
|
|
690
|
+
function supportsPopover() {
|
|
691
|
+
return web && typeof element.showPopover === "function" && typeof element.hidePopover === "function";
|
|
692
|
+
}
|
|
693
|
+
function popoverOpen() {
|
|
694
|
+
try {
|
|
695
|
+
return element.matches(":popover-open");
|
|
696
|
+
} catch {
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
function modalOpen() {
|
|
701
|
+
try {
|
|
702
|
+
return ownerDocument.querySelector(":modal") !== null;
|
|
703
|
+
} catch {
|
|
704
|
+
return true;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
function raise() {
|
|
708
|
+
if (!supportsPopover() || !element.isConnected) return;
|
|
709
|
+
if (popoverOpen() && !modalOpen()) return;
|
|
710
|
+
const focused = ownerDocument.activeElement;
|
|
711
|
+
const keepFocus = focused && element.contains(focused) ? focused : void 0;
|
|
712
|
+
try {
|
|
713
|
+
element.hidePopover();
|
|
714
|
+
element.showPopover();
|
|
715
|
+
} catch {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (keepFocus && ownerDocument.activeElement !== keepFocus) keepFocus.focus({ preventScroll: true });
|
|
719
|
+
}
|
|
720
|
+
function lowerWhenEmpty() {
|
|
721
|
+
if (element.childElementCount > 0 || !supportsPopover()) return;
|
|
722
|
+
try {
|
|
723
|
+
element.hidePopover();
|
|
724
|
+
} catch {
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
function engaged(toast) {
|
|
728
|
+
return toast.hovered || toast.focused;
|
|
729
|
+
}
|
|
730
|
+
function stopTimer(toast) {
|
|
731
|
+
clearTimeout(toast.timer);
|
|
732
|
+
toast.timer = void 0;
|
|
733
|
+
}
|
|
734
|
+
function runTimer(toast) {
|
|
735
|
+
stopTimer(toast);
|
|
736
|
+
if (toast.remaining === void 0 || engaged(toast)) return;
|
|
737
|
+
toast.startedAt = Date.now();
|
|
738
|
+
toast.timer = setTimeout(() => close(toast), toast.remaining);
|
|
739
|
+
}
|
|
740
|
+
function pause(toast) {
|
|
741
|
+
if (toast.timer === void 0) return;
|
|
742
|
+
stopTimer(toast);
|
|
743
|
+
if (toast.remaining !== void 0) {
|
|
744
|
+
toast.remaining = Math.max(0, toast.remaining - (Date.now() - toast.startedAt));
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
function setEngagement(toast, hovered, focused) {
|
|
748
|
+
if (!live.includes(toast)) return;
|
|
749
|
+
const wasEngaged = engaged(toast);
|
|
750
|
+
toast.hovered = hovered;
|
|
751
|
+
toast.focused = focused;
|
|
752
|
+
if (engaged(toast) === wasEngaged) return;
|
|
753
|
+
if (wasEngaged) {
|
|
754
|
+
if (toast.remaining !== void 0) toast.remaining = Math.max(toast.remaining, TOAST_RESUME_MIN_MS);
|
|
755
|
+
runTimer(toast);
|
|
756
|
+
} else {
|
|
757
|
+
pause(toast);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
function reducedMotion() {
|
|
761
|
+
if (ownerDocument.documentElement.classList.contains(REDUCED_MOTION_CLASS)) return true;
|
|
762
|
+
const view = ownerDocument.defaultView;
|
|
763
|
+
return typeof view?.matchMedia === "function" && view.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
764
|
+
}
|
|
765
|
+
function removeElement(toastElement) {
|
|
766
|
+
toastElement.remove();
|
|
767
|
+
lowerWhenEmpty();
|
|
768
|
+
changed();
|
|
769
|
+
}
|
|
770
|
+
function leave(toastElement) {
|
|
771
|
+
toastElement.setAttribute("data-leaving", "");
|
|
772
|
+
toastElement.inert = true;
|
|
773
|
+
if (reducedMotion()) {
|
|
774
|
+
removeElement(toastElement);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
let done = false;
|
|
778
|
+
const finish = () => {
|
|
779
|
+
if (done) return;
|
|
780
|
+
done = true;
|
|
781
|
+
clearTimeout(fallback);
|
|
782
|
+
leaveTimers.delete(fallback);
|
|
783
|
+
removeElement(toastElement);
|
|
784
|
+
};
|
|
785
|
+
toastElement.addEventListener("animationend", (event) => {
|
|
786
|
+
if (event.target === toastElement && event.animationName === LEAVE_ANIMATION) finish();
|
|
787
|
+
});
|
|
788
|
+
const fallback = setTimeout(finish, TOAST_LEAVE_FALLBACK_MS);
|
|
789
|
+
leaveTimers.add(fallback);
|
|
790
|
+
}
|
|
791
|
+
function close(toast) {
|
|
792
|
+
const index = live.indexOf(toast);
|
|
793
|
+
if (index < 0) return;
|
|
794
|
+
live.splice(index, 1);
|
|
795
|
+
stopTimer(toast);
|
|
796
|
+
silence(toast.request);
|
|
797
|
+
const heldFocus = toast.element.contains(ownerDocument.activeElement);
|
|
798
|
+
if (heldFocus) {
|
|
799
|
+
const next = live[index] ?? live[index - 1];
|
|
800
|
+
if (next) next.closeButton.focus({ preventScroll: true });
|
|
801
|
+
else context.focusFallback();
|
|
802
|
+
}
|
|
803
|
+
leave(toast.element);
|
|
804
|
+
}
|
|
805
|
+
function setAction(toast, action) {
|
|
806
|
+
const previous = toast.actionButton;
|
|
807
|
+
const hadFocus = previous !== void 0 && ownerDocument.activeElement === previous;
|
|
808
|
+
previous?.remove();
|
|
809
|
+
toast.actionButton = void 0;
|
|
810
|
+
if (action) {
|
|
811
|
+
const button = createElement(
|
|
812
|
+
ownerDocument,
|
|
813
|
+
"button",
|
|
814
|
+
{ type: "button", class: "cd-shell-toast-action cd-shell-button", "data-variant": "ghost" },
|
|
815
|
+
action.label
|
|
816
|
+
);
|
|
817
|
+
button.addEventListener("click", () => {
|
|
818
|
+
close(toast);
|
|
819
|
+
action.onSelect();
|
|
820
|
+
});
|
|
821
|
+
toast.closeButton.before(button);
|
|
822
|
+
toast.actionButton = button;
|
|
823
|
+
}
|
|
824
|
+
if (hadFocus) (toast.actionButton ?? toast.closeButton).focus({ preventScroll: true });
|
|
825
|
+
}
|
|
826
|
+
function slotOf(toast) {
|
|
827
|
+
return { id: toast.id, hasAction: toast.request.action !== void 0, engaged: engaged(toast) };
|
|
828
|
+
}
|
|
829
|
+
function render(request) {
|
|
830
|
+
const id = ++lastId;
|
|
831
|
+
const textId = `${context.idPrefix}-${id}`;
|
|
832
|
+
const closeButton = createElement(
|
|
833
|
+
ownerDocument,
|
|
834
|
+
"button",
|
|
835
|
+
{
|
|
836
|
+
type: "button",
|
|
837
|
+
class: "cd-shell-toast-close",
|
|
838
|
+
"aria-label": messages.close,
|
|
839
|
+
"aria-describedby": textId
|
|
840
|
+
},
|
|
841
|
+
iconElement("x")
|
|
842
|
+
);
|
|
843
|
+
const toastElement = createElement(
|
|
844
|
+
ownerDocument,
|
|
845
|
+
"div",
|
|
846
|
+
{ class: "cd-shell-toast", "data-status": request.status },
|
|
847
|
+
createElement(ownerDocument, "span", { class: "cd-shell-toast-dot", "aria-hidden": "true" }),
|
|
848
|
+
createElement(ownerDocument, "span", { class: "cd-shell-toast-text", id: textId }, request.text),
|
|
849
|
+
closeButton
|
|
850
|
+
);
|
|
851
|
+
const toast = {
|
|
852
|
+
id,
|
|
853
|
+
element: toastElement,
|
|
854
|
+
closeButton,
|
|
855
|
+
handle: Object.freeze({ dismiss: () => close(toast) }),
|
|
856
|
+
request,
|
|
857
|
+
actionButton: void 0,
|
|
858
|
+
hovered: false,
|
|
859
|
+
focused: false,
|
|
860
|
+
remaining: toastLifetime(request),
|
|
861
|
+
startedAt: Date.now(),
|
|
862
|
+
timer: void 0
|
|
863
|
+
};
|
|
864
|
+
setAction(toast, request.action);
|
|
865
|
+
closeButton.addEventListener("click", () => close(toast));
|
|
866
|
+
toastElement.addEventListener("mouseenter", () => setEngagement(toast, true, toast.focused));
|
|
867
|
+
toastElement.addEventListener("mouseleave", () => setEngagement(toast, false, toast.focused));
|
|
868
|
+
toastElement.addEventListener("focusin", () => setEngagement(toast, toast.hovered, true));
|
|
869
|
+
toastElement.addEventListener(
|
|
870
|
+
"focusout",
|
|
871
|
+
(event) => setEngagement(toast, toast.hovered, toastElement.contains(event.relatedTarget))
|
|
872
|
+
);
|
|
873
|
+
return toast;
|
|
874
|
+
}
|
|
875
|
+
function show(request) {
|
|
876
|
+
if (destroyed) return INERT_HANDLE;
|
|
877
|
+
const same = live.find(
|
|
878
|
+
(toast2) => toast2.request.status === request.status && toast2.request.text === request.text
|
|
879
|
+
);
|
|
880
|
+
if (same) {
|
|
881
|
+
same.request = request;
|
|
882
|
+
setAction(same, request.action);
|
|
883
|
+
same.remaining = toastLifetime(request);
|
|
884
|
+
runTimer(same);
|
|
885
|
+
raise();
|
|
886
|
+
announce(request);
|
|
887
|
+
changed();
|
|
888
|
+
return same.handle;
|
|
889
|
+
}
|
|
890
|
+
while (live.length >= MAX_TOASTS) {
|
|
891
|
+
const evictedId = toastToEvict(live.map(slotOf));
|
|
892
|
+
const evicted = live.find((toast2) => toast2.id === evictedId) ?? live[0];
|
|
893
|
+
if (!evicted) break;
|
|
894
|
+
close(evicted);
|
|
895
|
+
}
|
|
896
|
+
const toast = render(request);
|
|
897
|
+
live.push(toast);
|
|
898
|
+
element.append(toast.element);
|
|
899
|
+
raise();
|
|
900
|
+
runTimer(toast);
|
|
901
|
+
announce(request);
|
|
902
|
+
changed();
|
|
903
|
+
return toast.handle;
|
|
904
|
+
}
|
|
905
|
+
return {
|
|
906
|
+
element,
|
|
907
|
+
announcer,
|
|
908
|
+
show,
|
|
909
|
+
setBottom(bottom) {
|
|
910
|
+
element.style.bottom = `${bottom}px`;
|
|
911
|
+
},
|
|
912
|
+
widestCard() {
|
|
913
|
+
let widest;
|
|
914
|
+
for (const card of element.children) {
|
|
915
|
+
const width = card.getBoundingClientRect().width;
|
|
916
|
+
if (width > 0) widest = Math.max(widest ?? 0, width);
|
|
917
|
+
}
|
|
918
|
+
return widest;
|
|
919
|
+
},
|
|
920
|
+
destroy() {
|
|
921
|
+
if (destroyed) return;
|
|
922
|
+
destroyed = true;
|
|
923
|
+
for (const toast of live.splice(0)) stopTimer(toast);
|
|
924
|
+
for (const timer of leaveTimers) clearTimeout(timer);
|
|
925
|
+
leaveTimers.clear();
|
|
926
|
+
for (const timer of announceTimers.values()) clearTimeout(timer);
|
|
927
|
+
announceTimers.clear();
|
|
928
|
+
element.remove();
|
|
929
|
+
announcer.remove();
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
|
|
291
934
|
// src/dom/hint.ts
|
|
292
935
|
function createEmptyHint(ownerDocument, messages, options) {
|
|
293
936
|
const { onInsertExample } = options;
|
|
@@ -367,6 +1010,9 @@ function isModal(element) {
|
|
|
367
1010
|
function openDialogs(scope) {
|
|
368
1011
|
return [...scope.querySelectorAll('[role="dialog"], [role="alertdialog"], dialog')].filter(isShown);
|
|
369
1012
|
}
|
|
1013
|
+
function isModalOpen(scope) {
|
|
1014
|
+
return openDialogs(scope).some(isModal);
|
|
1015
|
+
}
|
|
370
1016
|
function createKeyListener(context) {
|
|
371
1017
|
return (event) => {
|
|
372
1018
|
if (event.key === "Escape") context.hideTooltip();
|
|
@@ -377,7 +1023,8 @@ function createKeyListener(context) {
|
|
|
377
1023
|
menuOpen: context.menuOpen(),
|
|
378
1024
|
dialogOpen: dialogs.length > 0,
|
|
379
1025
|
modalOpen: dialogs.some(isModal),
|
|
380
|
-
defaultPrevented: event.defaultPrevented
|
|
1026
|
+
defaultPrevented: event.defaultPrevented,
|
|
1027
|
+
startScreen: context.startScreen?.() ?? false
|
|
381
1028
|
});
|
|
382
1029
|
if (decision.preventDefault) event.preventDefault();
|
|
383
1030
|
if (decision.stopPropagation) event.stopPropagation();
|
|
@@ -505,8 +1152,11 @@ function createLegend(context) {
|
|
|
505
1152
|
}
|
|
506
1153
|
|
|
507
1154
|
// src/dom/menu.ts
|
|
508
|
-
var GROUP_ORDER = ["model", "export", "help"];
|
|
509
|
-
function
|
|
1155
|
+
var GROUP_ORDER = ["file", "model", "export", "appearance", "help"];
|
|
1156
|
+
function isModelerGroup(name) {
|
|
1157
|
+
return name === "model" || name === "export" || name === "help";
|
|
1158
|
+
}
|
|
1159
|
+
function isPrintable2(event) {
|
|
510
1160
|
return event.key.length === 1 && /\S/.test(event.key) && !event.ctrlKey && !event.metaKey && !event.altKey;
|
|
511
1161
|
}
|
|
512
1162
|
function createMenu(context) {
|
|
@@ -535,8 +1185,9 @@ function createMenu(context) {
|
|
|
535
1185
|
layer.prepend(dropdown);
|
|
536
1186
|
const isOpen = () => !dropdown.hidden;
|
|
537
1187
|
function entriesOf(name) {
|
|
538
|
-
const own = groups[name] ?? [];
|
|
539
|
-
|
|
1188
|
+
const own = isModelerGroup(name) ? groups[name] ?? [] : [];
|
|
1189
|
+
const added = context.shellEntries(name);
|
|
1190
|
+
return added.length > 0 ? [...own, ...added] : own;
|
|
540
1191
|
}
|
|
541
1192
|
function hasEntries() {
|
|
542
1193
|
return GROUP_ORDER.some((name) => entriesOf(name).length > 0);
|
|
@@ -590,14 +1241,12 @@ function createMenu(context) {
|
|
|
590
1241
|
}
|
|
591
1242
|
};
|
|
592
1243
|
}
|
|
593
|
-
const shortcut = entry.
|
|
1244
|
+
const shortcut = entry.shortcut ? createElement(ownerDocument, "span", { class: "cd-shell-menu-shortcut" }, entry.shortcut) : void 0;
|
|
594
1245
|
const element = itemElement("menuitem", entry.label, { icon: entry.icon, end: shortcut, disabled });
|
|
595
1246
|
element.dataset.menuId = entry.id;
|
|
596
1247
|
element.dataset.menuGroup = group;
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
toggleAttribute(element, "aria-keyshortcuts", keyShortcuts || null);
|
|
600
|
-
}
|
|
1248
|
+
const keyShortcuts = entry.ariaKeyShortcuts ?? (entry.shortcut ? ariaKeyShortcutsFromLabel(entry.shortcut) : void 0);
|
|
1249
|
+
toggleAttribute(element, "aria-keyshortcuts", keyShortcuts || null);
|
|
601
1250
|
if (entry.kind === "dialog") element.setAttribute("aria-haspopup", "dialog");
|
|
602
1251
|
if (entry.kind === "action" && entry.danger) element.dataset.danger = "";
|
|
603
1252
|
return {
|
|
@@ -782,7 +1431,7 @@ function createMenu(context) {
|
|
|
782
1431
|
event.stopPropagation();
|
|
783
1432
|
return;
|
|
784
1433
|
default:
|
|
785
|
-
if (!
|
|
1434
|
+
if (!isPrintable2(event)) return;
|
|
786
1435
|
typeAhead(event.key);
|
|
787
1436
|
}
|
|
788
1437
|
consume(event);
|
|
@@ -812,7 +1461,7 @@ function createMenu(context) {
|
|
|
812
1461
|
|
|
813
1462
|
// src/dom/problems.ts
|
|
814
1463
|
function createProblemsPill(context) {
|
|
815
|
-
const { ownerDocument, messages, lang, tooltip
|
|
1464
|
+
const { ownerDocument, messages, lang, tooltip } = context;
|
|
816
1465
|
const icon = createElement(ownerDocument, "span", { class: "cd-shell-problems-icon" });
|
|
817
1466
|
const text = createElement(ownerDocument, "span", { class: "cd-shell-problems-text" });
|
|
818
1467
|
const element = createElement(
|
|
@@ -823,7 +1472,7 @@ function createProblemsPill(context) {
|
|
|
823
1472
|
text
|
|
824
1473
|
);
|
|
825
1474
|
element.hidden = true;
|
|
826
|
-
element.addEventListener("click", () =>
|
|
1475
|
+
element.addEventListener("click", () => context.onActivate());
|
|
827
1476
|
const detachTooltip = context.showTooltip ? tooltip.attach(element, { label: messages.showProblems }) : void 0;
|
|
828
1477
|
let severity;
|
|
829
1478
|
return {
|
|
@@ -927,6 +1576,10 @@ function createTooltip({ root, layer, id }) {
|
|
|
927
1576
|
let warmUntil = 0;
|
|
928
1577
|
let pressed;
|
|
929
1578
|
const detachers = /* @__PURE__ */ new Set();
|
|
1579
|
+
const resizeObserver = typeof ResizeObserver === "function" ? new ResizeObserver(() => {
|
|
1580
|
+
if (shown) place(shown.control);
|
|
1581
|
+
}) : void 0;
|
|
1582
|
+
resizeObserver?.observe(element);
|
|
930
1583
|
function cancelPending() {
|
|
931
1584
|
if (pending) clearTimeout(pending.timer);
|
|
932
1585
|
pending = void 0;
|
|
@@ -1094,6 +1747,381 @@ function createTooltip({ root, layer, id }) {
|
|
|
1094
1747
|
destroy() {
|
|
1095
1748
|
hide();
|
|
1096
1749
|
for (const detach of [...detachers]) detach();
|
|
1750
|
+
resizeObserver?.disconnect();
|
|
1751
|
+
element.remove();
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
// src/dom/web/drop-zone.ts
|
|
1757
|
+
var DRAG_EVENTS = ["dragenter", "dragover", "dragleave", "drop"];
|
|
1758
|
+
function carriesFiles(transfer) {
|
|
1759
|
+
if (!transfer) return false;
|
|
1760
|
+
if (Array.from(transfer.types ?? []).includes("Files")) return true;
|
|
1761
|
+
return Array.from(transfer.items ?? []).some((item) => item.kind === "file");
|
|
1762
|
+
}
|
|
1763
|
+
function dragItems(transfer) {
|
|
1764
|
+
const items = Array.from(transfer.items ?? [], (item) => ({ kind: item.kind, type: item.type }));
|
|
1765
|
+
return items.length > 0 ? items : [{ kind: "file", type: "" }];
|
|
1766
|
+
}
|
|
1767
|
+
function acceptList(accept, lang) {
|
|
1768
|
+
const entries = accept.map((entry) => entry.trim()).filter((entry) => entry !== "");
|
|
1769
|
+
const extensions = entries.filter((entry) => entry.startsWith("."));
|
|
1770
|
+
const listed = extensions.length > 0 ? extensions : entries;
|
|
1771
|
+
let format;
|
|
1772
|
+
try {
|
|
1773
|
+
format = new Intl.ListFormat(lang, { type: "disjunction" });
|
|
1774
|
+
} catch {
|
|
1775
|
+
format = new Intl.ListFormat("en", { type: "disjunction" });
|
|
1776
|
+
}
|
|
1777
|
+
return format.format(listed);
|
|
1778
|
+
}
|
|
1779
|
+
function createDropZone(context) {
|
|
1780
|
+
const { ownerDocument, layer, messages, lang, options, modalOpen, toast, setState } = context;
|
|
1781
|
+
const chip = createElement(ownerDocument, "span", { class: "cd-shell-drop-zone-chip" });
|
|
1782
|
+
const element = createElement(
|
|
1783
|
+
ownerDocument,
|
|
1784
|
+
"div",
|
|
1785
|
+
{ class: "cd-shell-drop-zone", "data-chrome": "drop-zone", "aria-hidden": "true" },
|
|
1786
|
+
chip
|
|
1787
|
+
);
|
|
1788
|
+
element.hidden = true;
|
|
1789
|
+
layer.append(element);
|
|
1790
|
+
const view = ownerDocument.defaultView;
|
|
1791
|
+
let depth = 0;
|
|
1792
|
+
let current = null;
|
|
1793
|
+
function verdictFor(items) {
|
|
1794
|
+
return dragVerdict(items, {
|
|
1795
|
+
accept: options.accept,
|
|
1796
|
+
multiple: options.multiple ?? false,
|
|
1797
|
+
dialogOpen: modalOpen()
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1800
|
+
function show(verdict) {
|
|
1801
|
+
const next = verdict.state === "none" ? null : verdict.state;
|
|
1802
|
+
if (verdict.state === "over") chip.textContent = messages.dropFile;
|
|
1803
|
+
if (verdict.state === "forbidden") chip.textContent = messages.dropForbidden(verdict.reason);
|
|
1804
|
+
if (next === current) return;
|
|
1805
|
+
current = next;
|
|
1806
|
+
element.hidden = next === null;
|
|
1807
|
+
if (next === null) element.removeAttribute("data-state");
|
|
1808
|
+
else element.setAttribute("data-state", next);
|
|
1809
|
+
setState(next);
|
|
1810
|
+
}
|
|
1811
|
+
function track(transfer) {
|
|
1812
|
+
const verdict = verdictFor(dragItems(transfer));
|
|
1813
|
+
show(verdict);
|
|
1814
|
+
transfer.dropEffect = verdict.state === "forbidden" ? "none" : "copy";
|
|
1815
|
+
}
|
|
1816
|
+
function rejectByDefault(files) {
|
|
1817
|
+
const accepted = acceptList(options.accept, lang);
|
|
1818
|
+
for (const file of files) toast({ status: "danger", text: messages.cannotOpenFile(file.name, accepted) });
|
|
1819
|
+
}
|
|
1820
|
+
function receive(files, verdict) {
|
|
1821
|
+
if (verdict.state === "forbidden" && verdict.reason !== "type") return;
|
|
1822
|
+
const accepted = files.filter((file) => acceptsFile(file, options.accept));
|
|
1823
|
+
const rejected = files.filter((file) => !accepted.includes(file));
|
|
1824
|
+
if (accepted.length > 0) options.onDrop(accepted);
|
|
1825
|
+
if (rejected.length > 0) (options.onReject ?? rejectByDefault)(rejected);
|
|
1826
|
+
}
|
|
1827
|
+
const onDragEvent = (event) => {
|
|
1828
|
+
const transfer = event.dataTransfer;
|
|
1829
|
+
if (!carriesFiles(transfer)) return;
|
|
1830
|
+
event.preventDefault();
|
|
1831
|
+
switch (event.type) {
|
|
1832
|
+
case "dragenter":
|
|
1833
|
+
depth += 1;
|
|
1834
|
+
track(transfer);
|
|
1835
|
+
break;
|
|
1836
|
+
case "dragover":
|
|
1837
|
+
depth = Math.max(depth, 1);
|
|
1838
|
+
track(transfer);
|
|
1839
|
+
break;
|
|
1840
|
+
case "dragleave":
|
|
1841
|
+
depth = Math.max(0, depth - 1);
|
|
1842
|
+
if (depth === 0) show({ state: "none" });
|
|
1843
|
+
break;
|
|
1844
|
+
case "drop": {
|
|
1845
|
+
depth = 0;
|
|
1846
|
+
show({ state: "none" });
|
|
1847
|
+
const files = Array.from(transfer.files ?? []);
|
|
1848
|
+
const items = files.length > 0 ? files.map((file) => ({ kind: "file", type: file.type })) : dragItems(transfer);
|
|
1849
|
+
receive(files, verdictFor(items));
|
|
1850
|
+
break;
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
};
|
|
1854
|
+
for (const type of DRAG_EVENTS) view?.addEventListener(type, onDragEvent);
|
|
1855
|
+
return {
|
|
1856
|
+
element,
|
|
1857
|
+
state: () => current,
|
|
1858
|
+
destroy() {
|
|
1859
|
+
for (const type of DRAG_EVENTS) view?.removeEventListener(type, onDragEvent);
|
|
1860
|
+
depth = 0;
|
|
1861
|
+
if (current !== null) {
|
|
1862
|
+
current = null;
|
|
1863
|
+
setState(null);
|
|
1864
|
+
}
|
|
1865
|
+
element.remove();
|
|
1866
|
+
}
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
// src/dom/web/empty-state.ts
|
|
1871
|
+
var KOMET_SIZE = "64";
|
|
1872
|
+
function createEmptyState(context) {
|
|
1873
|
+
const { ownerDocument, messages, options, dropHint, open, titleId, onLeave } = context;
|
|
1874
|
+
const button = (variant, label, run) => {
|
|
1875
|
+
const element2 = createElement(
|
|
1876
|
+
ownerDocument,
|
|
1877
|
+
"button",
|
|
1878
|
+
{ type: "button", class: "cd-shell-button", "data-variant": variant },
|
|
1879
|
+
label
|
|
1880
|
+
);
|
|
1881
|
+
element2.addEventListener("click", () => {
|
|
1882
|
+
onLeave();
|
|
1883
|
+
run();
|
|
1884
|
+
});
|
|
1885
|
+
return element2;
|
|
1886
|
+
};
|
|
1887
|
+
const onShowExample = options.onShowExample;
|
|
1888
|
+
const newDiagram = button("primary", messages.newDiagram, () => options.onNewDiagram());
|
|
1889
|
+
const element = createElement(
|
|
1890
|
+
ownerDocument,
|
|
1891
|
+
"section",
|
|
1892
|
+
{ class: "cd-shell-empty-state", "data-chrome": "empty-state", "aria-labelledby": titleId },
|
|
1893
|
+
// Dragging the mark would start an image drag that some browsers carry as a file.
|
|
1894
|
+
createElement(ownerDocument, "img", {
|
|
1895
|
+
class: "cd-shell-empty-state-mark",
|
|
1896
|
+
src: options.komet,
|
|
1897
|
+
width: KOMET_SIZE,
|
|
1898
|
+
height: KOMET_SIZE,
|
|
1899
|
+
alt: "",
|
|
1900
|
+
draggable: "false"
|
|
1901
|
+
}),
|
|
1902
|
+
createElement(ownerDocument, "h1", { class: "cd-shell-empty-state-title", id: titleId }, options.title),
|
|
1903
|
+
createElement(ownerDocument, "p", { class: "cd-shell-empty-state-text" }, options.text),
|
|
1904
|
+
createElement(
|
|
1905
|
+
ownerDocument,
|
|
1906
|
+
"div",
|
|
1907
|
+
{ class: "cd-shell-empty-state-actions" },
|
|
1908
|
+
newDiagram,
|
|
1909
|
+
onShowExample && button("ghost", messages.showExample, () => onShowExample())
|
|
1910
|
+
),
|
|
1911
|
+
hintParagraph()
|
|
1912
|
+
);
|
|
1913
|
+
function hintParagraph() {
|
|
1914
|
+
const hasDropHint = dropHint !== void 0 && dropHint !== "";
|
|
1915
|
+
if (!hasDropHint && !open) return void 0;
|
|
1916
|
+
const paragraph = createElement(ownerDocument, "p", { class: "cd-shell-empty-state-hint" });
|
|
1917
|
+
if (hasDropHint) paragraph.append(dropHint);
|
|
1918
|
+
if (!open) return paragraph;
|
|
1919
|
+
const shortcut = open.shortcut?.trim();
|
|
1920
|
+
const link = createElement(
|
|
1921
|
+
ownerDocument,
|
|
1922
|
+
"button",
|
|
1923
|
+
{
|
|
1924
|
+
type: "button",
|
|
1925
|
+
class: "cd-shell-empty-state-open",
|
|
1926
|
+
"aria-keyshortcuts": shortcut ? ariaKeyShortcutsFromLabel(shortcut) : void 0,
|
|
1927
|
+
"aria-haspopup": open.dialog ? "dialog" : void 0
|
|
1928
|
+
},
|
|
1929
|
+
messages.openFileLink
|
|
1930
|
+
);
|
|
1931
|
+
link.addEventListener("click", () => open.onSelect());
|
|
1932
|
+
if (hasDropHint) {
|
|
1933
|
+
paragraph.append(" ", createElement(ownerDocument, "span", { "aria-hidden": "true" }, "\xB7"), " ");
|
|
1934
|
+
}
|
|
1935
|
+
const openItem = createElement(ownerDocument, "span", { class: "cd-shell-empty-state-open-item" }, link);
|
|
1936
|
+
if (shortcut) {
|
|
1937
|
+
openItem.append(
|
|
1938
|
+
" ",
|
|
1939
|
+
createElement(
|
|
1940
|
+
ownerDocument,
|
|
1941
|
+
"kbd",
|
|
1942
|
+
{ class: "cd-shell-empty-state-key", "aria-hidden": "true" },
|
|
1943
|
+
shortcut
|
|
1944
|
+
)
|
|
1945
|
+
);
|
|
1946
|
+
}
|
|
1947
|
+
paragraph.append(openItem);
|
|
1948
|
+
return paragraph;
|
|
1949
|
+
}
|
|
1950
|
+
element.hidden = true;
|
|
1951
|
+
return {
|
|
1952
|
+
element,
|
|
1953
|
+
isShown: () => !element.hidden,
|
|
1954
|
+
setShown(shown) {
|
|
1955
|
+
element.hidden = !shown;
|
|
1956
|
+
},
|
|
1957
|
+
focus() {
|
|
1958
|
+
newDiagram.focus({ preventScroll: true });
|
|
1959
|
+
},
|
|
1960
|
+
destroy() {
|
|
1961
|
+
element.remove();
|
|
1962
|
+
}
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// src/dom/web/legal.ts
|
|
1967
|
+
var MIRAGON_HOME = "https://www.miragon.io/";
|
|
1968
|
+
var MIRAGON_IMPRESSUM = "https://www.miragon.io/impressum/";
|
|
1969
|
+
var MIRAGON_DATENSCHUTZ = "https://www.miragon.io/datenschutz/";
|
|
1970
|
+
function createLegalLine(context) {
|
|
1971
|
+
const { ownerDocument, messages, options } = context;
|
|
1972
|
+
const year = options.year ?? (/* @__PURE__ */ new Date()).getFullYear();
|
|
1973
|
+
const newTabId = `${context.idPrefix}-new-tab`;
|
|
1974
|
+
const link = (label, href, lang) => createElement(
|
|
1975
|
+
ownerDocument,
|
|
1976
|
+
"a",
|
|
1977
|
+
{ href, lang, target: "_blank", rel: "noopener noreferrer", "aria-describedby": newTabId },
|
|
1978
|
+
label
|
|
1979
|
+
);
|
|
1980
|
+
const separator = () => createElement(ownerDocument, "span", { "aria-hidden": "true" }, "\xB7");
|
|
1981
|
+
const item = (anchor) => createElement(ownerDocument, "span", { class: "cd-shell-legal-item" }, separator(), anchor);
|
|
1982
|
+
const links = [
|
|
1983
|
+
link(messages.copyright(year), MIRAGON_HOME),
|
|
1984
|
+
link("Impressum", MIRAGON_IMPRESSUM, "de"),
|
|
1985
|
+
link("Datenschutz", MIRAGON_DATENSCHUTZ, "de"),
|
|
1986
|
+
...(options.links ?? []).map((extra) => link(extra.label, extra.href, extra.lang))
|
|
1987
|
+
];
|
|
1988
|
+
const element = createElement(ownerDocument, "nav", {
|
|
1989
|
+
class: "cd-shell-legal",
|
|
1990
|
+
"data-chrome": "legal",
|
|
1991
|
+
"aria-label": messages.legal
|
|
1992
|
+
});
|
|
1993
|
+
element.append(
|
|
1994
|
+
...links.map((anchor, index) => index === 0 ? anchor : item(anchor)),
|
|
1995
|
+
createElement(ownerDocument, "span", { id: newTabId, hidden: "" }, messages.opensInNewTab)
|
|
1996
|
+
);
|
|
1997
|
+
return {
|
|
1998
|
+
element,
|
|
1999
|
+
destroy() {
|
|
2000
|
+
element.remove();
|
|
2001
|
+
}
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
// src/dom/web/menu-groups.ts
|
|
2006
|
+
function fileEntry(spec, action) {
|
|
2007
|
+
const common = {
|
|
2008
|
+
id: spec.id,
|
|
2009
|
+
label: spec.label,
|
|
2010
|
+
...spec.icon ? { icon: spec.icon } : {},
|
|
2011
|
+
...action.disabled ? { disabled: action.disabled } : {},
|
|
2012
|
+
...action.shortcut ? { shortcut: action.shortcut } : {},
|
|
2013
|
+
onSelect: () => action.onSelect()
|
|
2014
|
+
};
|
|
2015
|
+
return action.dialog ? { kind: "dialog", ...common } : { kind: "action", ...common };
|
|
2016
|
+
}
|
|
2017
|
+
function fileMenuEntries(options, messages) {
|
|
2018
|
+
const specs = [
|
|
2019
|
+
{ option: "newDiagram", id: "new-diagram", label: messages.newDiagram, icon: "file-plus" },
|
|
2020
|
+
{ option: "open", id: "open", label: messages.openFile, icon: "folder-open" },
|
|
2021
|
+
// §5.14 gives "Show example" no icon.
|
|
2022
|
+
{ option: "showExample", id: "show-example", label: messages.showExample, icon: void 0 },
|
|
2023
|
+
{ option: "import", id: "import", label: messages.importFile, icon: "import" }
|
|
2024
|
+
];
|
|
2025
|
+
return specs.flatMap((spec) => {
|
|
2026
|
+
const action = options[spec.option];
|
|
2027
|
+
return action ? [fileEntry(spec, action)] : [];
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
function appearanceMenuEntries(appearance, messages) {
|
|
2031
|
+
return [
|
|
2032
|
+
{
|
|
2033
|
+
kind: "radio",
|
|
2034
|
+
id: "appearance",
|
|
2035
|
+
label: messages.appearance,
|
|
2036
|
+
options: [
|
|
2037
|
+
{ value: "system", label: messages.appearanceSystem },
|
|
2038
|
+
{ value: "light", label: messages.appearanceLight },
|
|
2039
|
+
{ value: "dark", label: messages.appearanceDark }
|
|
2040
|
+
],
|
|
2041
|
+
value: () => appearance.choice(),
|
|
2042
|
+
onSelect: (value) => appearance.setChoice(parseAppearanceChoice(value))
|
|
2043
|
+
}
|
|
2044
|
+
];
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
// src/dom/web/primary.ts
|
|
2048
|
+
var COLLAPSED_ATTRIBUTE = "data-collapsed";
|
|
2049
|
+
function createPrimaryAction(context) {
|
|
2050
|
+
const { ownerDocument, messages, tooltip, options } = context;
|
|
2051
|
+
const label = options.label ?? messages.share;
|
|
2052
|
+
const labelElement = createElement(ownerDocument, "span", { class: "cd-shell-primary-label" }, label);
|
|
2053
|
+
const element = createElement(
|
|
2054
|
+
ownerDocument,
|
|
2055
|
+
"button",
|
|
2056
|
+
{
|
|
2057
|
+
type: "button",
|
|
2058
|
+
class: "cd-shell-primary cd-shell-button",
|
|
2059
|
+
"data-variant": "primary",
|
|
2060
|
+
"data-chrome": "primary",
|
|
2061
|
+
"aria-label": label
|
|
2062
|
+
},
|
|
2063
|
+
iconElement(options.icon ?? "share-2"),
|
|
2064
|
+
labelElement
|
|
2065
|
+
);
|
|
2066
|
+
let collapsed = false;
|
|
2067
|
+
let disabled = false;
|
|
2068
|
+
let labelledWidth;
|
|
2069
|
+
function readDisabled() {
|
|
2070
|
+
disabled = options.disabled?.() ?? false;
|
|
2071
|
+
toggleAttribute(element, "aria-disabled", disabled ? "true" : null);
|
|
2072
|
+
}
|
|
2073
|
+
function showCollapsed(state) {
|
|
2074
|
+
labelElement.hidden = state;
|
|
2075
|
+
toggleAttribute(element, COLLAPSED_ATTRIBUTE, state ? "" : null);
|
|
2076
|
+
}
|
|
2077
|
+
function measureLabelledWidth() {
|
|
2078
|
+
if (!collapsed) return element.offsetWidth;
|
|
2079
|
+
showCollapsed(false);
|
|
2080
|
+
const width = element.offsetWidth;
|
|
2081
|
+
showCollapsed(true);
|
|
2082
|
+
return width;
|
|
2083
|
+
}
|
|
2084
|
+
function isEngaged() {
|
|
2085
|
+
if (ownerDocument.activeElement === element) return true;
|
|
2086
|
+
try {
|
|
2087
|
+
return element.matches(":hover");
|
|
2088
|
+
} catch {
|
|
2089
|
+
return false;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
const onClick = (event) => {
|
|
2093
|
+
if (disabled) {
|
|
2094
|
+
event.preventDefault();
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
options.onSelect();
|
|
2098
|
+
};
|
|
2099
|
+
element.addEventListener("click", onClick);
|
|
2100
|
+
const detachTooltip = tooltip.attach(element, () => collapsed ? { label } : null);
|
|
2101
|
+
readDisabled();
|
|
2102
|
+
return {
|
|
2103
|
+
element,
|
|
2104
|
+
isCollapsed: () => collapsed,
|
|
2105
|
+
refresh() {
|
|
2106
|
+
readDisabled();
|
|
2107
|
+
labelledWidth = void 0;
|
|
2108
|
+
},
|
|
2109
|
+
widths() {
|
|
2110
|
+
if (!collapsed || labelledWidth === void 0) {
|
|
2111
|
+
const measured = measureLabelledWidth();
|
|
2112
|
+
if (measured > 0) labelledWidth = measured;
|
|
2113
|
+
}
|
|
2114
|
+
return { withLabel: labelledWidth ?? 0, iconOnly: SHELL.row };
|
|
2115
|
+
},
|
|
2116
|
+
setCollapsed(next) {
|
|
2117
|
+
if (next === collapsed) return;
|
|
2118
|
+
collapsed = next;
|
|
2119
|
+
showCollapsed(next);
|
|
2120
|
+
if (!next && isEngaged()) tooltip.hide();
|
|
2121
|
+
},
|
|
2122
|
+
destroy() {
|
|
2123
|
+
detachTooltip();
|
|
2124
|
+
element.removeEventListener("click", onClick);
|
|
1097
2125
|
element.remove();
|
|
1098
2126
|
}
|
|
1099
2127
|
};
|
|
@@ -1216,6 +2244,16 @@ function nextMountIndex() {
|
|
|
1216
2244
|
return index;
|
|
1217
2245
|
}
|
|
1218
2246
|
var DEFAULT_THEMES = ["light"];
|
|
2247
|
+
var LEGAL_HEIGHT_PROPERTY = "--modeler-shell-legal-height";
|
|
2248
|
+
var INERT_TOAST = Object.freeze({ dismiss() {
|
|
2249
|
+
} });
|
|
2250
|
+
function visibleBoxIn(element, root) {
|
|
2251
|
+
if (!element || element.hidden || !element.isConnected) return void 0;
|
|
2252
|
+
if (typeof element.checkVisibility === "function" && !element.checkVisibility({ visibilityProperty: true }))
|
|
2253
|
+
return void 0;
|
|
2254
|
+
const box = boxRelativeTo(element, root);
|
|
2255
|
+
return box.width > 0 && box.height > 0 ? box : void 0;
|
|
2256
|
+
}
|
|
1219
2257
|
function languageOf(root, host, requested) {
|
|
1220
2258
|
if (requested) return requested;
|
|
1221
2259
|
const declared = host === "embedded" ? root.getAttribute("lang") : root.closest("[lang]")?.getAttribute("lang");
|
|
@@ -1244,6 +2282,7 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1244
2282
|
const platform = options.platform ?? detectPlatform(globalThis.navigator);
|
|
1245
2283
|
const lang = languageOf(root, host, options.lang);
|
|
1246
2284
|
const keyScope = options.keyScope ?? (host === "embedded" ? "root" : "document");
|
|
2285
|
+
const web = host === "web";
|
|
1247
2286
|
ledger.addClass(root, "cd-shell");
|
|
1248
2287
|
ledger.attribute(root, "data-cd-shell-host", host);
|
|
1249
2288
|
ledger.attribute(root, "data-palette", "top");
|
|
@@ -1256,6 +2295,9 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1256
2295
|
const setState = (name, value) => {
|
|
1257
2296
|
if (!ledger.isClosed) ledger.attribute(root, name, value);
|
|
1258
2297
|
};
|
|
2298
|
+
const appearance = web ? connectWebAppearance({ ownerDocument, themes, options: options.appearance }) : void 0;
|
|
2299
|
+
if (appearance) ledger.defer(() => appearance.destroy());
|
|
2300
|
+
const darkCapable = themes.includes("dark");
|
|
1259
2301
|
const layer = createElement(ownerDocument, "div", { class: "cd-shell-layer" });
|
|
1260
2302
|
ledger.defer(() => layer.remove());
|
|
1261
2303
|
const tooltip = createTooltip({ root, layer, id: `cd-shell-tooltip-${index}` });
|
|
@@ -1287,6 +2329,14 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1287
2329
|
keepEnabledWhenReadOnly: true
|
|
1288
2330
|
} : void 0;
|
|
1289
2331
|
const shellModelEntries = legendEntry ? [legendEntry] : [];
|
|
2332
|
+
const fileEntries = web && options.file ? fileMenuEntries(options.file, messages) : [];
|
|
2333
|
+
const appearanceEntries = appearance && darkCapable ? appearanceMenuEntries(appearance, messages) : [];
|
|
2334
|
+
const shellEntries = (group) => {
|
|
2335
|
+
if (group === "file") return fileEntries;
|
|
2336
|
+
if (group === "model") return shellModelEntries;
|
|
2337
|
+
if (group === "appearance") return appearanceEntries;
|
|
2338
|
+
return [];
|
|
2339
|
+
};
|
|
1290
2340
|
const menu = createMenu({
|
|
1291
2341
|
root,
|
|
1292
2342
|
layer,
|
|
@@ -1294,55 +2344,133 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1294
2344
|
messages,
|
|
1295
2345
|
tooltip,
|
|
1296
2346
|
groups: options.menu ?? {},
|
|
1297
|
-
|
|
2347
|
+
shellEntries,
|
|
1298
2348
|
isReadOnly: () => readOnly
|
|
1299
2349
|
});
|
|
1300
2350
|
ledger.defer(() => menu.destroy());
|
|
1301
2351
|
ledger.defer(tooltip.attach(menu.trigger, () => menu.isOpen() ? null : { label: messages.menu }));
|
|
2352
|
+
const primary = web && options.primary ? createPrimaryAction({ ownerDocument, messages, tooltip, options: options.primary }) : void 0;
|
|
2353
|
+
if (primary) ledger.defer(() => primary.destroy());
|
|
1302
2354
|
const canvas = options.canvas ?? createElement(ownerDocument, "div");
|
|
1303
2355
|
ledger.addClass(canvas, "cd-shell-canvas");
|
|
2356
|
+
if (web && !canvas.hasAttribute("role")) ledger.attribute(canvas, "role", "main");
|
|
1304
2357
|
if (!options.canvas) ledger.defer(() => canvas.remove());
|
|
2358
|
+
function focusCanvasOrTrigger() {
|
|
2359
|
+
const took = (target) => {
|
|
2360
|
+
target?.focus({ preventScroll: true });
|
|
2361
|
+
return target !== null && target !== void 0 && ownerDocument.activeElement === target;
|
|
2362
|
+
};
|
|
2363
|
+
if (emptyState?.isShown()) {
|
|
2364
|
+
emptyState.focus();
|
|
2365
|
+
if (emptyState.element.contains(ownerDocument.activeElement)) return;
|
|
2366
|
+
}
|
|
2367
|
+
if (took(canvas.querySelector("svg[tabindex]"))) return;
|
|
2368
|
+
if (!menu.trigger.hidden) took(menu.trigger);
|
|
2369
|
+
}
|
|
1305
2370
|
function moveFocusOutOf(island) {
|
|
1306
|
-
if (
|
|
1307
|
-
const canvasTarget = canvas.querySelector("svg[tabindex]");
|
|
1308
|
-
canvasTarget?.focus({ preventScroll: true });
|
|
1309
|
-
if (canvasTarget && ownerDocument.activeElement === canvasTarget) return;
|
|
1310
|
-
if (!menu.trigger.hidden) menu.trigger.focus({ preventScroll: true });
|
|
2371
|
+
if (island.contains(ownerDocument.activeElement)) focusCanvasOrTrigger();
|
|
1311
2372
|
}
|
|
1312
2373
|
const status = createStatusBox(ownerDocument);
|
|
1313
2374
|
ledger.defer(() => status.destroy());
|
|
1314
2375
|
const hint = options.emptyHint ? createEmptyHint(ownerDocument, messages, options.emptyHint) : void 0;
|
|
1315
2376
|
if (hint) ledger.defer(() => hint.destroy());
|
|
2377
|
+
const emptyState = web && options.emptyState ? createEmptyState({
|
|
2378
|
+
ownerDocument,
|
|
2379
|
+
messages,
|
|
2380
|
+
options: options.emptyState,
|
|
2381
|
+
dropHint: options.dropZone ? options.emptyState.dropHint ?? messages.dropHint : void 0,
|
|
2382
|
+
open: options.file?.open,
|
|
2383
|
+
titleId: `cd-shell-empty-state-title-${index}`,
|
|
2384
|
+
onLeave: () => setEmptyStateShown(false)
|
|
2385
|
+
}) : void 0;
|
|
2386
|
+
if (emptyState) ledger.defer(() => emptyState.destroy());
|
|
1316
2387
|
const inspector = options.inspector ? createInspector(ownerDocument, options.inspector, (open) => {
|
|
1317
2388
|
setState("data-inspector", open ? "open" : null);
|
|
2389
|
+
updateBottomRow();
|
|
1318
2390
|
placeHint();
|
|
1319
2391
|
}) : void 0;
|
|
1320
2392
|
if (inspector) ledger.defer(() => inspector.destroy());
|
|
1321
|
-
const
|
|
2393
|
+
const problemsOptions = options.problems;
|
|
2394
|
+
let popover;
|
|
2395
|
+
const pill = problemsOptions ? createProblemsPill({
|
|
1322
2396
|
ownerDocument,
|
|
1323
2397
|
messages,
|
|
1324
2398
|
lang,
|
|
1325
2399
|
tooltip,
|
|
1326
2400
|
showTooltip: host === "ide",
|
|
1327
|
-
|
|
2401
|
+
onActivate() {
|
|
2402
|
+
if (popover) popover.toggle();
|
|
2403
|
+
else problemsOptions.onActivate?.();
|
|
2404
|
+
}
|
|
1328
2405
|
}) : void 0;
|
|
1329
2406
|
if (pill) ledger.defer(() => pill.destroy());
|
|
2407
|
+
if (pill && (web || host === "embedded" && !problemsOptions?.onActivate)) {
|
|
2408
|
+
const created = createProblemsPopover({
|
|
2409
|
+
ownerDocument,
|
|
2410
|
+
root,
|
|
2411
|
+
layer,
|
|
2412
|
+
pill: pill.element,
|
|
2413
|
+
id: `cd-shell-problems-${index}`,
|
|
2414
|
+
titleId: `cd-shell-problems-title-${index}`,
|
|
2415
|
+
messages,
|
|
2416
|
+
onSelectItem: problemsOptions?.onSelectItem
|
|
2417
|
+
});
|
|
2418
|
+
popover = created;
|
|
2419
|
+
ledger.defer(() => created.destroy());
|
|
2420
|
+
}
|
|
2421
|
+
const legal = web ? createLegalLine({
|
|
2422
|
+
ownerDocument,
|
|
2423
|
+
messages,
|
|
2424
|
+
options: options.legal ?? {},
|
|
2425
|
+
idPrefix: `cd-shell-legal-${index}`
|
|
2426
|
+
}) : void 0;
|
|
2427
|
+
if (legal) ledger.defer(() => legal.destroy());
|
|
1330
2428
|
const zoomPanel = createZoomPanel({ ownerDocument, messages, lang, platform, tooltip });
|
|
1331
2429
|
ledger.defer(() => zoomPanel.destroy());
|
|
1332
|
-
|
|
2430
|
+
const toaster = host === "ide" ? void 0 : createToaster({
|
|
2431
|
+
ownerDocument,
|
|
2432
|
+
host,
|
|
2433
|
+
messages,
|
|
2434
|
+
idPrefix: `cd-shell-toast-${index}`,
|
|
2435
|
+
focusFallback: focusCanvasOrTrigger,
|
|
2436
|
+
onChange: () => updateBottomRow()
|
|
2437
|
+
});
|
|
2438
|
+
if (toaster) ledger.defer(() => toaster.destroy());
|
|
2439
|
+
const defined = (element) => element !== void 0;
|
|
2440
|
+
root.prepend(...[menu.trigger, primary?.element, canvas].filter(defined));
|
|
1333
2441
|
root.append(
|
|
1334
2442
|
...[
|
|
1335
2443
|
status.element,
|
|
1336
2444
|
hint?.element,
|
|
2445
|
+
emptyState?.element,
|
|
1337
2446
|
inspector?.element,
|
|
1338
2447
|
pill?.element,
|
|
1339
2448
|
legend?.element,
|
|
2449
|
+
legal?.element,
|
|
1340
2450
|
zoomPanel.element
|
|
1341
|
-
].filter(
|
|
1342
|
-
layer
|
|
2451
|
+
].filter(defined),
|
|
2452
|
+
layer,
|
|
2453
|
+
...[toaster?.announcer, toaster?.element].filter(defined)
|
|
1343
2454
|
);
|
|
2455
|
+
function showToast(request) {
|
|
2456
|
+
return ledger.isClosed || !toaster ? INERT_TOAST : toaster.show(request);
|
|
2457
|
+
}
|
|
2458
|
+
const dropZone = web && options.dropZone ? createDropZone({
|
|
2459
|
+
ownerDocument,
|
|
2460
|
+
layer,
|
|
2461
|
+
messages,
|
|
2462
|
+
lang,
|
|
2463
|
+
options: options.dropZone,
|
|
2464
|
+
modalOpen: () => isModalOpen(ownerDocument),
|
|
2465
|
+
toast: showToast,
|
|
2466
|
+
setState: (state) => setState("data-drop", state)
|
|
2467
|
+
}) : void 0;
|
|
2468
|
+
if (dropZone) ledger.defer(() => dropZone.destroy());
|
|
1344
2469
|
let palette;
|
|
1345
2470
|
let paletteRow = "top";
|
|
2471
|
+
let zoomRaised = false;
|
|
2472
|
+
let legalHeight = LEGAL_LINE_HEIGHT;
|
|
2473
|
+
if (legal) ledger.defer(() => root.style.removeProperty(LEGAL_HEIGHT_PROPERTY));
|
|
1346
2474
|
function containerSize() {
|
|
1347
2475
|
return { width: root.clientWidth, height: root.clientHeight };
|
|
1348
2476
|
}
|
|
@@ -1362,6 +2490,10 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1362
2490
|
paletteRow,
|
|
1363
2491
|
problemsVisible: pill?.isShown() ?? false
|
|
1364
2492
|
};
|
|
2493
|
+
if (web) {
|
|
2494
|
+
layout.zoomRaised = zoomRaised;
|
|
2495
|
+
layout.legalHeight = legalHeight;
|
|
2496
|
+
}
|
|
1365
2497
|
if (legend?.isOpen()) layout.legendWidth = legend.element.getBoundingClientRect().width;
|
|
1366
2498
|
if (inspector?.isOpen()) {
|
|
1367
2499
|
layout.inspector = container.width > SHELL.narrow ? {} : { sheetTop: boxRelativeTo(inspector.element, root).y };
|
|
@@ -1375,18 +2507,76 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1375
2507
|
if (ledger.isClosed) return;
|
|
1376
2508
|
const size = containerSize();
|
|
1377
2509
|
if (size.width === 0 || size.height === 0) return;
|
|
1378
|
-
|
|
2510
|
+
const topRow = decideTopRow({
|
|
1379
2511
|
containerWidth: size.width,
|
|
1380
2512
|
paletteWidth: paletteWidth(),
|
|
1381
|
-
menuRight: menuRight()
|
|
2513
|
+
menuRight: menuRight(),
|
|
2514
|
+
primary: primary?.widths()
|
|
1382
2515
|
});
|
|
2516
|
+
paletteRow = topRow.paletteRow;
|
|
1383
2517
|
setState("data-palette", paletteRow);
|
|
2518
|
+
if (primary) {
|
|
2519
|
+
const collapsed = topRow.primaryLabel === "collapsed";
|
|
2520
|
+
primary.setCollapsed(collapsed);
|
|
2521
|
+
setState("data-primary", collapsed ? "collapsed" : null);
|
|
2522
|
+
}
|
|
1384
2523
|
if (legend && !legend.isStarted()) legend.start(options.legend?.open ?? legendStartsOpen(size.height));
|
|
2524
|
+
updateBottomRow();
|
|
1385
2525
|
placeHint();
|
|
1386
2526
|
}
|
|
2527
|
+
function updateBottomRow() {
|
|
2528
|
+
if (ledger.isClosed) return;
|
|
2529
|
+
const size = containerSize();
|
|
2530
|
+
if (size.width === 0 || size.height === 0) return;
|
|
2531
|
+
if (legal) {
|
|
2532
|
+
const legalBox = visibleBoxIn(legal.element, root);
|
|
2533
|
+
const height = legalLineHeight(legalBox?.height);
|
|
2534
|
+
if (height !== legalHeight || !root.style.getPropertyValue(LEGAL_HEIGHT_PROPERTY)) {
|
|
2535
|
+
legalHeight = height;
|
|
2536
|
+
root.style.setProperty(LEGAL_HEIGHT_PROPERTY, `${height}px`);
|
|
2537
|
+
}
|
|
2538
|
+
zoomRaised = zoomRowRaised({
|
|
2539
|
+
containerWidth: size.width,
|
|
2540
|
+
legalRight: legalBox && legalBox.x + legalBox.width,
|
|
2541
|
+
zoomWidth: visibleBoxIn(zoomPanel.element, root)?.width
|
|
2542
|
+
});
|
|
2543
|
+
setState("data-zoom-row", zoomRaised ? "raised" : null);
|
|
2544
|
+
}
|
|
2545
|
+
if (toaster) {
|
|
2546
|
+
const obstacles = [zoomPanel.element, legal?.element, pill?.element].flatMap(
|
|
2547
|
+
(element) => {
|
|
2548
|
+
const box = visibleBoxIn(element, root);
|
|
2549
|
+
return box ? [{ left: box.x, right: box.x + box.width, top: size.height - box.y }] : [];
|
|
2550
|
+
}
|
|
2551
|
+
);
|
|
2552
|
+
toaster.setBottom(
|
|
2553
|
+
toastStackBottom({ containerWidth: size.width, obstacles, cardWidth: toaster.widestCard() })
|
|
2554
|
+
);
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
function setEmptyStateShown(shown) {
|
|
2558
|
+
if (ledger.isClosed || !emptyState || emptyState.isShown() === shown) return;
|
|
2559
|
+
const active = ownerDocument.activeElement;
|
|
2560
|
+
const hadFocus = emptyState.element.contains(active);
|
|
2561
|
+
const workHadFocus = shown && active !== null && root.contains(active) && ![emptyState.element, legal?.element, toaster?.element].some((kept) => kept?.contains(active));
|
|
2562
|
+
emptyState.setShown(shown);
|
|
2563
|
+
setState("data-empty-state", shown ? "shown" : null);
|
|
2564
|
+
ledger.attribute(canvas, "inert", shown ? "" : null);
|
|
2565
|
+
if (shown) {
|
|
2566
|
+
menu.close();
|
|
2567
|
+
popover?.close();
|
|
2568
|
+
tooltip.hide();
|
|
2569
|
+
if (workHadFocus) emptyState.focus();
|
|
2570
|
+
} else if (hadFocus) {
|
|
2571
|
+
focusCanvasOrTrigger();
|
|
2572
|
+
}
|
|
2573
|
+
updateLayout();
|
|
2574
|
+
}
|
|
1387
2575
|
const ResizeObserverClass = globalThis.ResizeObserver;
|
|
1388
2576
|
const resizeObserver = ResizeObserverClass ? new ResizeObserverClass(() => updateLayout()) : void 0;
|
|
1389
2577
|
resizeObserver?.observe(root);
|
|
2578
|
+
if (primary) resizeObserver?.observe(primary.element);
|
|
2579
|
+
if (legal) resizeObserver?.observe(legal.element);
|
|
1390
2580
|
ledger.defer(() => resizeObserver?.disconnect());
|
|
1391
2581
|
if (legend && options.legend?.open !== void 0) legend.start(options.legend.open);
|
|
1392
2582
|
updateLayout();
|
|
@@ -1395,12 +2585,16 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1395
2585
|
host,
|
|
1396
2586
|
dialogScope: keyTarget,
|
|
1397
2587
|
menuOpen: () => menu.isOpen(),
|
|
2588
|
+
startScreen: () => emptyState?.isShown() ?? false,
|
|
1398
2589
|
closeMenu() {
|
|
1399
2590
|
const active = ownerDocument.activeElement;
|
|
1400
2591
|
menu.close({ restoreFocus: active === menu.trigger || menu.dropdown.contains(active) });
|
|
1401
2592
|
},
|
|
1402
2593
|
hideTooltip: () => tooltip.hide(),
|
|
1403
|
-
|
|
2594
|
+
// Behind the start screen the keys reach the browser instead: there is no diagram to zoom.
|
|
2595
|
+
run: (key) => {
|
|
2596
|
+
if (!emptyState?.isShown()) zoomPanel.run(key);
|
|
2597
|
+
}
|
|
1404
2598
|
});
|
|
1405
2599
|
keyTarget.addEventListener("keydown", onKeydown);
|
|
1406
2600
|
ledger.defer(() => keyTarget.removeEventListener("keydown", onKeydown));
|
|
@@ -1452,6 +2646,21 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1452
2646
|
if (!ledger.isClosed) legend.toggle();
|
|
1453
2647
|
}
|
|
1454
2648
|
};
|
|
2649
|
+
const primaryHandle = primary && {
|
|
2650
|
+
element: primary.element,
|
|
2651
|
+
isCollapsed: () => primary.isCollapsed(),
|
|
2652
|
+
refresh: () => {
|
|
2653
|
+
if (!ledger.isClosed) primary.refresh();
|
|
2654
|
+
}
|
|
2655
|
+
};
|
|
2656
|
+
const appearanceHandle = appearance && darkCapable ? {
|
|
2657
|
+
choice: () => appearance.choice(),
|
|
2658
|
+
setChoice: (choice) => {
|
|
2659
|
+
if (ledger.isClosed) return;
|
|
2660
|
+
appearance.setChoice(choice);
|
|
2661
|
+
menu.refresh();
|
|
2662
|
+
}
|
|
2663
|
+
} : void 0;
|
|
1455
2664
|
const inspectorHandle = inspector && {
|
|
1456
2665
|
element: inspector.element,
|
|
1457
2666
|
isOpen: () => inspector.isOpen(),
|
|
@@ -1475,6 +2684,8 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1475
2684
|
tooltip: tooltipHandle,
|
|
1476
2685
|
legend: legendHandle,
|
|
1477
2686
|
inspector: inspectorHandle,
|
|
2687
|
+
primary: primaryHandle,
|
|
2688
|
+
appearance: appearanceHandle,
|
|
1478
2689
|
setZoomTarget(target) {
|
|
1479
2690
|
if (!ledger.isClosed) zoomPanel.setTarget(target);
|
|
1480
2691
|
},
|
|
@@ -1503,8 +2714,15 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1503
2714
|
},
|
|
1504
2715
|
setProblems(summary) {
|
|
1505
2716
|
if (ledger.isClosed || !pill) return;
|
|
2717
|
+
const listHadFocus = popover?.element.contains(ownerDocument.activeElement) ?? false;
|
|
1506
2718
|
pill.set(summary);
|
|
1507
2719
|
setState("data-problems", pill.isShown() ? "shown" : null);
|
|
2720
|
+
popover?.setItems(summary.items ?? []);
|
|
2721
|
+
if (!pill.isShown() && popover?.isOpen()) {
|
|
2722
|
+
popover.close();
|
|
2723
|
+
if (listHadFocus) focusCanvasOrTrigger();
|
|
2724
|
+
}
|
|
2725
|
+
updateBottomRow();
|
|
1508
2726
|
placeHint();
|
|
1509
2727
|
},
|
|
1510
2728
|
setStatus(next, statusOptions) {
|
|
@@ -1534,6 +2752,9 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1534
2752
|
applyReadOnly();
|
|
1535
2753
|
},
|
|
1536
2754
|
isReadOnly: () => readOnly,
|
|
2755
|
+
setEmptyState: setEmptyStateShown,
|
|
2756
|
+
isEmptyStateShown: () => emptyState?.isShown() ?? false,
|
|
2757
|
+
toast: showToast,
|
|
1537
2758
|
destroy() {
|
|
1538
2759
|
ledger.close();
|
|
1539
2760
|
}
|
|
@@ -1573,4 +2794,4 @@ export {
|
|
|
1573
2794
|
* minus, plus, triangle-alert (alert-triangle) and x. Feather @license MIT
|
|
1574
2795
|
* Copyright (c) 2013-present Cole Bemis
|
|
1575
2796
|
* Full notices: THIRD_PARTY_NOTICES.md of @miragon/modeler-shell */
|
|
1576
|
-
//# sourceMappingURL=chunk-
|
|
2797
|
+
//# sourceMappingURL=chunk-A4YGKH4Y.js.map
|