@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
|
@@ -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 {
|
|
@@ -1099,6 +1748,380 @@ function createTooltip({ root, layer, id }) {
|
|
|
1099
1748
|
};
|
|
1100
1749
|
}
|
|
1101
1750
|
|
|
1751
|
+
// src/dom/web/drop-zone.ts
|
|
1752
|
+
var DRAG_EVENTS = ["dragenter", "dragover", "dragleave", "drop"];
|
|
1753
|
+
function carriesFiles(transfer) {
|
|
1754
|
+
if (!transfer) return false;
|
|
1755
|
+
if (Array.from(transfer.types ?? []).includes("Files")) return true;
|
|
1756
|
+
return Array.from(transfer.items ?? []).some((item) => item.kind === "file");
|
|
1757
|
+
}
|
|
1758
|
+
function dragItems(transfer) {
|
|
1759
|
+
const items = Array.from(transfer.items ?? [], (item) => ({ kind: item.kind, type: item.type }));
|
|
1760
|
+
return items.length > 0 ? items : [{ kind: "file", type: "" }];
|
|
1761
|
+
}
|
|
1762
|
+
function acceptList(accept, lang) {
|
|
1763
|
+
const entries = accept.map((entry) => entry.trim()).filter((entry) => entry !== "");
|
|
1764
|
+
const extensions = entries.filter((entry) => entry.startsWith("."));
|
|
1765
|
+
const listed = extensions.length > 0 ? extensions : entries;
|
|
1766
|
+
let format;
|
|
1767
|
+
try {
|
|
1768
|
+
format = new Intl.ListFormat(lang, { type: "disjunction" });
|
|
1769
|
+
} catch {
|
|
1770
|
+
format = new Intl.ListFormat("en", { type: "disjunction" });
|
|
1771
|
+
}
|
|
1772
|
+
return format.format(listed);
|
|
1773
|
+
}
|
|
1774
|
+
function createDropZone(context) {
|
|
1775
|
+
const { ownerDocument, layer, messages, lang, options, modalOpen, toast, setState } = context;
|
|
1776
|
+
const chip = createElement(ownerDocument, "span", { class: "cd-shell-drop-zone-chip" });
|
|
1777
|
+
const element = createElement(
|
|
1778
|
+
ownerDocument,
|
|
1779
|
+
"div",
|
|
1780
|
+
{ class: "cd-shell-drop-zone", "data-chrome": "drop-zone", "aria-hidden": "true" },
|
|
1781
|
+
chip
|
|
1782
|
+
);
|
|
1783
|
+
element.hidden = true;
|
|
1784
|
+
layer.append(element);
|
|
1785
|
+
const view = ownerDocument.defaultView;
|
|
1786
|
+
let depth = 0;
|
|
1787
|
+
let current = null;
|
|
1788
|
+
function verdictFor(items) {
|
|
1789
|
+
return dragVerdict(items, {
|
|
1790
|
+
accept: options.accept,
|
|
1791
|
+
multiple: options.multiple ?? false,
|
|
1792
|
+
dialogOpen: modalOpen()
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
function show(verdict) {
|
|
1796
|
+
const next = verdict.state === "none" ? null : verdict.state;
|
|
1797
|
+
if (verdict.state === "over") chip.textContent = messages.dropFile;
|
|
1798
|
+
if (verdict.state === "forbidden") chip.textContent = messages.dropForbidden(verdict.reason);
|
|
1799
|
+
if (next === current) return;
|
|
1800
|
+
current = next;
|
|
1801
|
+
element.hidden = next === null;
|
|
1802
|
+
if (next === null) element.removeAttribute("data-state");
|
|
1803
|
+
else element.setAttribute("data-state", next);
|
|
1804
|
+
setState(next);
|
|
1805
|
+
}
|
|
1806
|
+
function track(transfer) {
|
|
1807
|
+
const verdict = verdictFor(dragItems(transfer));
|
|
1808
|
+
show(verdict);
|
|
1809
|
+
transfer.dropEffect = verdict.state === "forbidden" ? "none" : "copy";
|
|
1810
|
+
}
|
|
1811
|
+
function rejectByDefault(files) {
|
|
1812
|
+
const accepted = acceptList(options.accept, lang);
|
|
1813
|
+
for (const file of files) toast({ status: "danger", text: messages.cannotOpenFile(file.name, accepted) });
|
|
1814
|
+
}
|
|
1815
|
+
function receive(files, verdict) {
|
|
1816
|
+
if (verdict.state === "forbidden" && verdict.reason !== "type") return;
|
|
1817
|
+
const accepted = files.filter((file) => acceptsFile(file, options.accept));
|
|
1818
|
+
const rejected = files.filter((file) => !accepted.includes(file));
|
|
1819
|
+
if (accepted.length > 0) options.onDrop(accepted);
|
|
1820
|
+
if (rejected.length > 0) (options.onReject ?? rejectByDefault)(rejected);
|
|
1821
|
+
}
|
|
1822
|
+
const onDragEvent = (event) => {
|
|
1823
|
+
const transfer = event.dataTransfer;
|
|
1824
|
+
if (!carriesFiles(transfer)) return;
|
|
1825
|
+
event.preventDefault();
|
|
1826
|
+
switch (event.type) {
|
|
1827
|
+
case "dragenter":
|
|
1828
|
+
depth += 1;
|
|
1829
|
+
track(transfer);
|
|
1830
|
+
break;
|
|
1831
|
+
case "dragover":
|
|
1832
|
+
depth = Math.max(depth, 1);
|
|
1833
|
+
track(transfer);
|
|
1834
|
+
break;
|
|
1835
|
+
case "dragleave":
|
|
1836
|
+
depth = Math.max(0, depth - 1);
|
|
1837
|
+
if (depth === 0) show({ state: "none" });
|
|
1838
|
+
break;
|
|
1839
|
+
case "drop": {
|
|
1840
|
+
depth = 0;
|
|
1841
|
+
show({ state: "none" });
|
|
1842
|
+
const files = Array.from(transfer.files ?? []);
|
|
1843
|
+
const items = files.length > 0 ? files.map((file) => ({ kind: "file", type: file.type })) : dragItems(transfer);
|
|
1844
|
+
receive(files, verdictFor(items));
|
|
1845
|
+
break;
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
};
|
|
1849
|
+
for (const type of DRAG_EVENTS) view?.addEventListener(type, onDragEvent);
|
|
1850
|
+
return {
|
|
1851
|
+
element,
|
|
1852
|
+
state: () => current,
|
|
1853
|
+
destroy() {
|
|
1854
|
+
for (const type of DRAG_EVENTS) view?.removeEventListener(type, onDragEvent);
|
|
1855
|
+
depth = 0;
|
|
1856
|
+
if (current !== null) {
|
|
1857
|
+
current = null;
|
|
1858
|
+
setState(null);
|
|
1859
|
+
}
|
|
1860
|
+
element.remove();
|
|
1861
|
+
}
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
// src/dom/web/empty-state.ts
|
|
1866
|
+
var KOMET_SIZE = "64";
|
|
1867
|
+
function createEmptyState(context) {
|
|
1868
|
+
const { ownerDocument, messages, options, dropHint, open, titleId, onLeave } = context;
|
|
1869
|
+
const button = (variant, label, run) => {
|
|
1870
|
+
const element2 = createElement(
|
|
1871
|
+
ownerDocument,
|
|
1872
|
+
"button",
|
|
1873
|
+
{ type: "button", class: "cd-shell-button", "data-variant": variant },
|
|
1874
|
+
label
|
|
1875
|
+
);
|
|
1876
|
+
element2.addEventListener("click", () => {
|
|
1877
|
+
onLeave();
|
|
1878
|
+
run();
|
|
1879
|
+
});
|
|
1880
|
+
return element2;
|
|
1881
|
+
};
|
|
1882
|
+
const onShowExample = options.onShowExample;
|
|
1883
|
+
const newDiagram = button("primary", messages.newDiagram, () => options.onNewDiagram());
|
|
1884
|
+
const element = createElement(
|
|
1885
|
+
ownerDocument,
|
|
1886
|
+
"section",
|
|
1887
|
+
{ class: "cd-shell-empty-state", "data-chrome": "empty-state", "aria-labelledby": titleId },
|
|
1888
|
+
// Dragging the mark would start an image drag that some browsers carry as a file.
|
|
1889
|
+
createElement(ownerDocument, "img", {
|
|
1890
|
+
class: "cd-shell-empty-state-mark",
|
|
1891
|
+
src: options.komet,
|
|
1892
|
+
width: KOMET_SIZE,
|
|
1893
|
+
height: KOMET_SIZE,
|
|
1894
|
+
alt: "",
|
|
1895
|
+
draggable: "false"
|
|
1896
|
+
}),
|
|
1897
|
+
createElement(ownerDocument, "h1", { class: "cd-shell-empty-state-title", id: titleId }, options.title),
|
|
1898
|
+
createElement(ownerDocument, "p", { class: "cd-shell-empty-state-text" }, options.text),
|
|
1899
|
+
createElement(
|
|
1900
|
+
ownerDocument,
|
|
1901
|
+
"div",
|
|
1902
|
+
{ class: "cd-shell-empty-state-actions" },
|
|
1903
|
+
newDiagram,
|
|
1904
|
+
onShowExample && button("ghost", messages.showExample, () => onShowExample())
|
|
1905
|
+
),
|
|
1906
|
+
hintParagraph()
|
|
1907
|
+
);
|
|
1908
|
+
function hintParagraph() {
|
|
1909
|
+
const hasDropHint = dropHint !== void 0 && dropHint !== "";
|
|
1910
|
+
if (!hasDropHint && !open) return void 0;
|
|
1911
|
+
const paragraph = createElement(ownerDocument, "p", { class: "cd-shell-empty-state-hint" });
|
|
1912
|
+
if (hasDropHint) paragraph.append(dropHint);
|
|
1913
|
+
if (!open) return paragraph;
|
|
1914
|
+
const shortcut = open.shortcut?.trim();
|
|
1915
|
+
const link = createElement(
|
|
1916
|
+
ownerDocument,
|
|
1917
|
+
"button",
|
|
1918
|
+
{
|
|
1919
|
+
type: "button",
|
|
1920
|
+
class: "cd-shell-empty-state-open",
|
|
1921
|
+
"aria-keyshortcuts": shortcut ? ariaKeyShortcutsFromLabel(shortcut) : void 0,
|
|
1922
|
+
"aria-haspopup": open.dialog ? "dialog" : void 0
|
|
1923
|
+
},
|
|
1924
|
+
messages.openFileLink
|
|
1925
|
+
);
|
|
1926
|
+
link.addEventListener("click", () => open.onSelect());
|
|
1927
|
+
if (hasDropHint) {
|
|
1928
|
+
paragraph.append(" ", createElement(ownerDocument, "span", { "aria-hidden": "true" }, "\xB7"), " ");
|
|
1929
|
+
}
|
|
1930
|
+
const openItem = createElement(ownerDocument, "span", { class: "cd-shell-empty-state-open-item" }, link);
|
|
1931
|
+
if (shortcut) {
|
|
1932
|
+
openItem.append(
|
|
1933
|
+
" ",
|
|
1934
|
+
createElement(
|
|
1935
|
+
ownerDocument,
|
|
1936
|
+
"kbd",
|
|
1937
|
+
{ class: "cd-shell-empty-state-key", "aria-hidden": "true" },
|
|
1938
|
+
shortcut
|
|
1939
|
+
)
|
|
1940
|
+
);
|
|
1941
|
+
}
|
|
1942
|
+
paragraph.append(openItem);
|
|
1943
|
+
return paragraph;
|
|
1944
|
+
}
|
|
1945
|
+
element.hidden = true;
|
|
1946
|
+
return {
|
|
1947
|
+
element,
|
|
1948
|
+
isShown: () => !element.hidden,
|
|
1949
|
+
setShown(shown) {
|
|
1950
|
+
element.hidden = !shown;
|
|
1951
|
+
},
|
|
1952
|
+
focus() {
|
|
1953
|
+
newDiagram.focus({ preventScroll: true });
|
|
1954
|
+
},
|
|
1955
|
+
destroy() {
|
|
1956
|
+
element.remove();
|
|
1957
|
+
}
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// src/dom/web/legal.ts
|
|
1962
|
+
var MIRAGON_HOME = "https://www.miragon.io/";
|
|
1963
|
+
var MIRAGON_IMPRESSUM = "https://www.miragon.io/impressum/";
|
|
1964
|
+
var MIRAGON_DATENSCHUTZ = "https://www.miragon.io/datenschutz/";
|
|
1965
|
+
function createLegalLine(context) {
|
|
1966
|
+
const { ownerDocument, messages, options } = context;
|
|
1967
|
+
const year = options.year ?? (/* @__PURE__ */ new Date()).getFullYear();
|
|
1968
|
+
const newTabId = `${context.idPrefix}-new-tab`;
|
|
1969
|
+
const link = (label, href, lang) => createElement(
|
|
1970
|
+
ownerDocument,
|
|
1971
|
+
"a",
|
|
1972
|
+
{ href, lang, target: "_blank", rel: "noopener noreferrer", "aria-describedby": newTabId },
|
|
1973
|
+
label
|
|
1974
|
+
);
|
|
1975
|
+
const separator = () => createElement(ownerDocument, "span", { "aria-hidden": "true" }, "\xB7");
|
|
1976
|
+
const item = (anchor) => createElement(ownerDocument, "span", { class: "cd-shell-legal-item" }, separator(), anchor);
|
|
1977
|
+
const links = [
|
|
1978
|
+
link(messages.copyright(year), MIRAGON_HOME),
|
|
1979
|
+
link("Impressum", MIRAGON_IMPRESSUM, "de"),
|
|
1980
|
+
link("Datenschutz", MIRAGON_DATENSCHUTZ, "de"),
|
|
1981
|
+
...(options.links ?? []).map((extra) => link(extra.label, extra.href, extra.lang))
|
|
1982
|
+
];
|
|
1983
|
+
const element = createElement(ownerDocument, "nav", {
|
|
1984
|
+
class: "cd-shell-legal",
|
|
1985
|
+
"data-chrome": "legal",
|
|
1986
|
+
"aria-label": messages.legal
|
|
1987
|
+
});
|
|
1988
|
+
element.append(
|
|
1989
|
+
...links.map((anchor, index) => index === 0 ? anchor : item(anchor)),
|
|
1990
|
+
createElement(ownerDocument, "span", { id: newTabId, hidden: "" }, messages.opensInNewTab)
|
|
1991
|
+
);
|
|
1992
|
+
return {
|
|
1993
|
+
element,
|
|
1994
|
+
destroy() {
|
|
1995
|
+
element.remove();
|
|
1996
|
+
}
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
// src/dom/web/menu-groups.ts
|
|
2001
|
+
function fileEntry(spec, action) {
|
|
2002
|
+
const common = {
|
|
2003
|
+
id: spec.id,
|
|
2004
|
+
label: spec.label,
|
|
2005
|
+
...spec.icon ? { icon: spec.icon } : {},
|
|
2006
|
+
...action.disabled ? { disabled: action.disabled } : {},
|
|
2007
|
+
...action.shortcut ? { shortcut: action.shortcut } : {},
|
|
2008
|
+
onSelect: () => action.onSelect()
|
|
2009
|
+
};
|
|
2010
|
+
return action.dialog ? { kind: "dialog", ...common } : { kind: "action", ...common };
|
|
2011
|
+
}
|
|
2012
|
+
function fileMenuEntries(options, messages) {
|
|
2013
|
+
const specs = [
|
|
2014
|
+
{ option: "newDiagram", id: "new-diagram", label: messages.newDiagram, icon: "file-plus" },
|
|
2015
|
+
{ option: "open", id: "open", label: messages.openFile, icon: "folder-open" },
|
|
2016
|
+
// §5.14 gives "Show example" no icon.
|
|
2017
|
+
{ option: "showExample", id: "show-example", label: messages.showExample, icon: void 0 },
|
|
2018
|
+
{ option: "import", id: "import", label: messages.importFile, icon: "import" }
|
|
2019
|
+
];
|
|
2020
|
+
return specs.flatMap((spec) => {
|
|
2021
|
+
const action = options[spec.option];
|
|
2022
|
+
return action ? [fileEntry(spec, action)] : [];
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
2025
|
+
function appearanceMenuEntries(appearance, messages) {
|
|
2026
|
+
return [
|
|
2027
|
+
{
|
|
2028
|
+
kind: "radio",
|
|
2029
|
+
id: "appearance",
|
|
2030
|
+
label: messages.appearance,
|
|
2031
|
+
options: [
|
|
2032
|
+
{ value: "system", label: messages.appearanceSystem },
|
|
2033
|
+
{ value: "light", label: messages.appearanceLight },
|
|
2034
|
+
{ value: "dark", label: messages.appearanceDark }
|
|
2035
|
+
],
|
|
2036
|
+
value: () => appearance.choice(),
|
|
2037
|
+
onSelect: (value) => appearance.setChoice(parseAppearanceChoice(value))
|
|
2038
|
+
}
|
|
2039
|
+
];
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
// src/dom/web/primary.ts
|
|
2043
|
+
var COLLAPSED_ATTRIBUTE = "data-collapsed";
|
|
2044
|
+
function createPrimaryAction(context) {
|
|
2045
|
+
const { ownerDocument, messages, tooltip, options } = context;
|
|
2046
|
+
const label = options.label ?? messages.share;
|
|
2047
|
+
const labelElement = createElement(ownerDocument, "span", { class: "cd-shell-primary-label" }, label);
|
|
2048
|
+
const element = createElement(
|
|
2049
|
+
ownerDocument,
|
|
2050
|
+
"button",
|
|
2051
|
+
{
|
|
2052
|
+
type: "button",
|
|
2053
|
+
class: "cd-shell-primary cd-shell-button",
|
|
2054
|
+
"data-variant": "primary",
|
|
2055
|
+
"data-chrome": "primary",
|
|
2056
|
+
"aria-label": label
|
|
2057
|
+
},
|
|
2058
|
+
iconElement(options.icon ?? "share-2"),
|
|
2059
|
+
labelElement
|
|
2060
|
+
);
|
|
2061
|
+
let collapsed = false;
|
|
2062
|
+
let disabled = false;
|
|
2063
|
+
let labelledWidth;
|
|
2064
|
+
function readDisabled() {
|
|
2065
|
+
disabled = options.disabled?.() ?? false;
|
|
2066
|
+
toggleAttribute(element, "aria-disabled", disabled ? "true" : null);
|
|
2067
|
+
}
|
|
2068
|
+
function showCollapsed(state) {
|
|
2069
|
+
labelElement.hidden = state;
|
|
2070
|
+
toggleAttribute(element, COLLAPSED_ATTRIBUTE, state ? "" : null);
|
|
2071
|
+
}
|
|
2072
|
+
function measureLabelledWidth() {
|
|
2073
|
+
if (!collapsed) return element.offsetWidth;
|
|
2074
|
+
showCollapsed(false);
|
|
2075
|
+
const width = element.offsetWidth;
|
|
2076
|
+
showCollapsed(true);
|
|
2077
|
+
return width;
|
|
2078
|
+
}
|
|
2079
|
+
function isEngaged() {
|
|
2080
|
+
if (ownerDocument.activeElement === element) return true;
|
|
2081
|
+
try {
|
|
2082
|
+
return element.matches(":hover");
|
|
2083
|
+
} catch {
|
|
2084
|
+
return false;
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
const onClick = (event) => {
|
|
2088
|
+
if (disabled) {
|
|
2089
|
+
event.preventDefault();
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
options.onSelect();
|
|
2093
|
+
};
|
|
2094
|
+
element.addEventListener("click", onClick);
|
|
2095
|
+
const detachTooltip = tooltip.attach(element, () => collapsed ? { label } : null);
|
|
2096
|
+
readDisabled();
|
|
2097
|
+
return {
|
|
2098
|
+
element,
|
|
2099
|
+
isCollapsed: () => collapsed,
|
|
2100
|
+
refresh() {
|
|
2101
|
+
readDisabled();
|
|
2102
|
+
labelledWidth = void 0;
|
|
2103
|
+
},
|
|
2104
|
+
widths() {
|
|
2105
|
+
if (!collapsed || labelledWidth === void 0) {
|
|
2106
|
+
const measured = measureLabelledWidth();
|
|
2107
|
+
if (measured > 0) labelledWidth = measured;
|
|
2108
|
+
}
|
|
2109
|
+
return { withLabel: labelledWidth ?? 0, iconOnly: SHELL.row };
|
|
2110
|
+
},
|
|
2111
|
+
setCollapsed(next) {
|
|
2112
|
+
if (next === collapsed) return;
|
|
2113
|
+
collapsed = next;
|
|
2114
|
+
showCollapsed(next);
|
|
2115
|
+
if (!next && isEngaged()) tooltip.hide();
|
|
2116
|
+
},
|
|
2117
|
+
destroy() {
|
|
2118
|
+
detachTooltip();
|
|
2119
|
+
element.removeEventListener("click", onClick);
|
|
2120
|
+
element.remove();
|
|
2121
|
+
}
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
|
|
1102
2125
|
// src/dom/zoom-panel.ts
|
|
1103
2126
|
function createZoomPanel(context) {
|
|
1104
2127
|
const { ownerDocument, messages, lang, platform, tooltip } = context;
|
|
@@ -1216,6 +2239,16 @@ function nextMountIndex() {
|
|
|
1216
2239
|
return index;
|
|
1217
2240
|
}
|
|
1218
2241
|
var DEFAULT_THEMES = ["light"];
|
|
2242
|
+
var LEGAL_HEIGHT_PROPERTY = "--modeler-shell-legal-height";
|
|
2243
|
+
var INERT_TOAST = Object.freeze({ dismiss() {
|
|
2244
|
+
} });
|
|
2245
|
+
function visibleBoxIn(element, root) {
|
|
2246
|
+
if (!element || element.hidden || !element.isConnected) return void 0;
|
|
2247
|
+
if (typeof element.checkVisibility === "function" && !element.checkVisibility({ visibilityProperty: true }))
|
|
2248
|
+
return void 0;
|
|
2249
|
+
const box = boxRelativeTo(element, root);
|
|
2250
|
+
return box.width > 0 && box.height > 0 ? box : void 0;
|
|
2251
|
+
}
|
|
1219
2252
|
function languageOf(root, host, requested) {
|
|
1220
2253
|
if (requested) return requested;
|
|
1221
2254
|
const declared = host === "embedded" ? root.getAttribute("lang") : root.closest("[lang]")?.getAttribute("lang");
|
|
@@ -1244,6 +2277,7 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1244
2277
|
const platform = options.platform ?? detectPlatform(globalThis.navigator);
|
|
1245
2278
|
const lang = languageOf(root, host, options.lang);
|
|
1246
2279
|
const keyScope = options.keyScope ?? (host === "embedded" ? "root" : "document");
|
|
2280
|
+
const web = host === "web";
|
|
1247
2281
|
ledger.addClass(root, "cd-shell");
|
|
1248
2282
|
ledger.attribute(root, "data-cd-shell-host", host);
|
|
1249
2283
|
ledger.attribute(root, "data-palette", "top");
|
|
@@ -1256,6 +2290,9 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1256
2290
|
const setState = (name, value) => {
|
|
1257
2291
|
if (!ledger.isClosed) ledger.attribute(root, name, value);
|
|
1258
2292
|
};
|
|
2293
|
+
const appearance = web ? connectWebAppearance({ ownerDocument, themes, options: options.appearance }) : void 0;
|
|
2294
|
+
if (appearance) ledger.defer(() => appearance.destroy());
|
|
2295
|
+
const darkCapable = themes.includes("dark");
|
|
1259
2296
|
const layer = createElement(ownerDocument, "div", { class: "cd-shell-layer" });
|
|
1260
2297
|
ledger.defer(() => layer.remove());
|
|
1261
2298
|
const tooltip = createTooltip({ root, layer, id: `cd-shell-tooltip-${index}` });
|
|
@@ -1287,6 +2324,14 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1287
2324
|
keepEnabledWhenReadOnly: true
|
|
1288
2325
|
} : void 0;
|
|
1289
2326
|
const shellModelEntries = legendEntry ? [legendEntry] : [];
|
|
2327
|
+
const fileEntries = web && options.file ? fileMenuEntries(options.file, messages) : [];
|
|
2328
|
+
const appearanceEntries = appearance && darkCapable ? appearanceMenuEntries(appearance, messages) : [];
|
|
2329
|
+
const shellEntries = (group) => {
|
|
2330
|
+
if (group === "file") return fileEntries;
|
|
2331
|
+
if (group === "model") return shellModelEntries;
|
|
2332
|
+
if (group === "appearance") return appearanceEntries;
|
|
2333
|
+
return [];
|
|
2334
|
+
};
|
|
1290
2335
|
const menu = createMenu({
|
|
1291
2336
|
root,
|
|
1292
2337
|
layer,
|
|
@@ -1294,55 +2339,133 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1294
2339
|
messages,
|
|
1295
2340
|
tooltip,
|
|
1296
2341
|
groups: options.menu ?? {},
|
|
1297
|
-
|
|
2342
|
+
shellEntries,
|
|
1298
2343
|
isReadOnly: () => readOnly
|
|
1299
2344
|
});
|
|
1300
2345
|
ledger.defer(() => menu.destroy());
|
|
1301
2346
|
ledger.defer(tooltip.attach(menu.trigger, () => menu.isOpen() ? null : { label: messages.menu }));
|
|
2347
|
+
const primary = web && options.primary ? createPrimaryAction({ ownerDocument, messages, tooltip, options: options.primary }) : void 0;
|
|
2348
|
+
if (primary) ledger.defer(() => primary.destroy());
|
|
1302
2349
|
const canvas = options.canvas ?? createElement(ownerDocument, "div");
|
|
1303
2350
|
ledger.addClass(canvas, "cd-shell-canvas");
|
|
2351
|
+
if (web && !canvas.hasAttribute("role")) ledger.attribute(canvas, "role", "main");
|
|
1304
2352
|
if (!options.canvas) ledger.defer(() => canvas.remove());
|
|
2353
|
+
function focusCanvasOrTrigger() {
|
|
2354
|
+
const took = (target) => {
|
|
2355
|
+
target?.focus({ preventScroll: true });
|
|
2356
|
+
return target !== null && target !== void 0 && ownerDocument.activeElement === target;
|
|
2357
|
+
};
|
|
2358
|
+
if (emptyState?.isShown()) {
|
|
2359
|
+
emptyState.focus();
|
|
2360
|
+
if (emptyState.element.contains(ownerDocument.activeElement)) return;
|
|
2361
|
+
}
|
|
2362
|
+
if (took(canvas.querySelector("svg[tabindex]"))) return;
|
|
2363
|
+
if (!menu.trigger.hidden) took(menu.trigger);
|
|
2364
|
+
}
|
|
1305
2365
|
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 });
|
|
2366
|
+
if (island.contains(ownerDocument.activeElement)) focusCanvasOrTrigger();
|
|
1311
2367
|
}
|
|
1312
2368
|
const status = createStatusBox(ownerDocument);
|
|
1313
2369
|
ledger.defer(() => status.destroy());
|
|
1314
2370
|
const hint = options.emptyHint ? createEmptyHint(ownerDocument, messages, options.emptyHint) : void 0;
|
|
1315
2371
|
if (hint) ledger.defer(() => hint.destroy());
|
|
2372
|
+
const emptyState = web && options.emptyState ? createEmptyState({
|
|
2373
|
+
ownerDocument,
|
|
2374
|
+
messages,
|
|
2375
|
+
options: options.emptyState,
|
|
2376
|
+
dropHint: options.dropZone ? options.emptyState.dropHint ?? messages.dropHint : void 0,
|
|
2377
|
+
open: options.file?.open,
|
|
2378
|
+
titleId: `cd-shell-empty-state-title-${index}`,
|
|
2379
|
+
onLeave: () => setEmptyStateShown(false)
|
|
2380
|
+
}) : void 0;
|
|
2381
|
+
if (emptyState) ledger.defer(() => emptyState.destroy());
|
|
1316
2382
|
const inspector = options.inspector ? createInspector(ownerDocument, options.inspector, (open) => {
|
|
1317
2383
|
setState("data-inspector", open ? "open" : null);
|
|
2384
|
+
updateBottomRow();
|
|
1318
2385
|
placeHint();
|
|
1319
2386
|
}) : void 0;
|
|
1320
2387
|
if (inspector) ledger.defer(() => inspector.destroy());
|
|
1321
|
-
const
|
|
2388
|
+
const problemsOptions = options.problems;
|
|
2389
|
+
let popover;
|
|
2390
|
+
const pill = problemsOptions ? createProblemsPill({
|
|
1322
2391
|
ownerDocument,
|
|
1323
2392
|
messages,
|
|
1324
2393
|
lang,
|
|
1325
2394
|
tooltip,
|
|
1326
2395
|
showTooltip: host === "ide",
|
|
1327
|
-
|
|
2396
|
+
onActivate() {
|
|
2397
|
+
if (popover) popover.toggle();
|
|
2398
|
+
else problemsOptions.onActivate?.();
|
|
2399
|
+
}
|
|
1328
2400
|
}) : void 0;
|
|
1329
2401
|
if (pill) ledger.defer(() => pill.destroy());
|
|
2402
|
+
if (pill && (web || host === "embedded" && !problemsOptions?.onActivate)) {
|
|
2403
|
+
const created = createProblemsPopover({
|
|
2404
|
+
ownerDocument,
|
|
2405
|
+
root,
|
|
2406
|
+
layer,
|
|
2407
|
+
pill: pill.element,
|
|
2408
|
+
id: `cd-shell-problems-${index}`,
|
|
2409
|
+
titleId: `cd-shell-problems-title-${index}`,
|
|
2410
|
+
messages,
|
|
2411
|
+
onSelectItem: problemsOptions?.onSelectItem
|
|
2412
|
+
});
|
|
2413
|
+
popover = created;
|
|
2414
|
+
ledger.defer(() => created.destroy());
|
|
2415
|
+
}
|
|
2416
|
+
const legal = web ? createLegalLine({
|
|
2417
|
+
ownerDocument,
|
|
2418
|
+
messages,
|
|
2419
|
+
options: options.legal ?? {},
|
|
2420
|
+
idPrefix: `cd-shell-legal-${index}`
|
|
2421
|
+
}) : void 0;
|
|
2422
|
+
if (legal) ledger.defer(() => legal.destroy());
|
|
1330
2423
|
const zoomPanel = createZoomPanel({ ownerDocument, messages, lang, platform, tooltip });
|
|
1331
2424
|
ledger.defer(() => zoomPanel.destroy());
|
|
1332
|
-
|
|
2425
|
+
const toaster = host === "ide" ? void 0 : createToaster({
|
|
2426
|
+
ownerDocument,
|
|
2427
|
+
host,
|
|
2428
|
+
messages,
|
|
2429
|
+
idPrefix: `cd-shell-toast-${index}`,
|
|
2430
|
+
focusFallback: focusCanvasOrTrigger,
|
|
2431
|
+
onChange: () => updateBottomRow()
|
|
2432
|
+
});
|
|
2433
|
+
if (toaster) ledger.defer(() => toaster.destroy());
|
|
2434
|
+
const defined = (element) => element !== void 0;
|
|
2435
|
+
root.prepend(...[menu.trigger, primary?.element, canvas].filter(defined));
|
|
1333
2436
|
root.append(
|
|
1334
2437
|
...[
|
|
1335
2438
|
status.element,
|
|
1336
2439
|
hint?.element,
|
|
2440
|
+
emptyState?.element,
|
|
1337
2441
|
inspector?.element,
|
|
1338
2442
|
pill?.element,
|
|
1339
2443
|
legend?.element,
|
|
2444
|
+
legal?.element,
|
|
1340
2445
|
zoomPanel.element
|
|
1341
|
-
].filter(
|
|
1342
|
-
layer
|
|
2446
|
+
].filter(defined),
|
|
2447
|
+
layer,
|
|
2448
|
+
...[toaster?.announcer, toaster?.element].filter(defined)
|
|
1343
2449
|
);
|
|
2450
|
+
function showToast(request) {
|
|
2451
|
+
return ledger.isClosed || !toaster ? INERT_TOAST : toaster.show(request);
|
|
2452
|
+
}
|
|
2453
|
+
const dropZone = web && options.dropZone ? createDropZone({
|
|
2454
|
+
ownerDocument,
|
|
2455
|
+
layer,
|
|
2456
|
+
messages,
|
|
2457
|
+
lang,
|
|
2458
|
+
options: options.dropZone,
|
|
2459
|
+
modalOpen: () => isModalOpen(ownerDocument),
|
|
2460
|
+
toast: showToast,
|
|
2461
|
+
setState: (state) => setState("data-drop", state)
|
|
2462
|
+
}) : void 0;
|
|
2463
|
+
if (dropZone) ledger.defer(() => dropZone.destroy());
|
|
1344
2464
|
let palette;
|
|
1345
2465
|
let paletteRow = "top";
|
|
2466
|
+
let zoomRaised = false;
|
|
2467
|
+
let legalHeight = LEGAL_LINE_HEIGHT;
|
|
2468
|
+
if (legal) ledger.defer(() => root.style.removeProperty(LEGAL_HEIGHT_PROPERTY));
|
|
1346
2469
|
function containerSize() {
|
|
1347
2470
|
return { width: root.clientWidth, height: root.clientHeight };
|
|
1348
2471
|
}
|
|
@@ -1362,6 +2485,10 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1362
2485
|
paletteRow,
|
|
1363
2486
|
problemsVisible: pill?.isShown() ?? false
|
|
1364
2487
|
};
|
|
2488
|
+
if (web) {
|
|
2489
|
+
layout.zoomRaised = zoomRaised;
|
|
2490
|
+
layout.legalHeight = legalHeight;
|
|
2491
|
+
}
|
|
1365
2492
|
if (legend?.isOpen()) layout.legendWidth = legend.element.getBoundingClientRect().width;
|
|
1366
2493
|
if (inspector?.isOpen()) {
|
|
1367
2494
|
layout.inspector = container.width > SHELL.narrow ? {} : { sheetTop: boxRelativeTo(inspector.element, root).y };
|
|
@@ -1375,18 +2502,76 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1375
2502
|
if (ledger.isClosed) return;
|
|
1376
2503
|
const size = containerSize();
|
|
1377
2504
|
if (size.width === 0 || size.height === 0) return;
|
|
1378
|
-
|
|
2505
|
+
const topRow = decideTopRow({
|
|
1379
2506
|
containerWidth: size.width,
|
|
1380
2507
|
paletteWidth: paletteWidth(),
|
|
1381
|
-
menuRight: menuRight()
|
|
2508
|
+
menuRight: menuRight(),
|
|
2509
|
+
primary: primary?.widths()
|
|
1382
2510
|
});
|
|
2511
|
+
paletteRow = topRow.paletteRow;
|
|
1383
2512
|
setState("data-palette", paletteRow);
|
|
2513
|
+
if (primary) {
|
|
2514
|
+
const collapsed = topRow.primaryLabel === "collapsed";
|
|
2515
|
+
primary.setCollapsed(collapsed);
|
|
2516
|
+
setState("data-primary", collapsed ? "collapsed" : null);
|
|
2517
|
+
}
|
|
1384
2518
|
if (legend && !legend.isStarted()) legend.start(options.legend?.open ?? legendStartsOpen(size.height));
|
|
2519
|
+
updateBottomRow();
|
|
1385
2520
|
placeHint();
|
|
1386
2521
|
}
|
|
2522
|
+
function updateBottomRow() {
|
|
2523
|
+
if (ledger.isClosed) return;
|
|
2524
|
+
const size = containerSize();
|
|
2525
|
+
if (size.width === 0 || size.height === 0) return;
|
|
2526
|
+
if (legal) {
|
|
2527
|
+
const legalBox = visibleBoxIn(legal.element, root);
|
|
2528
|
+
const height = legalLineHeight(legalBox?.height);
|
|
2529
|
+
if (height !== legalHeight || !root.style.getPropertyValue(LEGAL_HEIGHT_PROPERTY)) {
|
|
2530
|
+
legalHeight = height;
|
|
2531
|
+
root.style.setProperty(LEGAL_HEIGHT_PROPERTY, `${height}px`);
|
|
2532
|
+
}
|
|
2533
|
+
zoomRaised = zoomRowRaised({
|
|
2534
|
+
containerWidth: size.width,
|
|
2535
|
+
legalRight: legalBox && legalBox.x + legalBox.width,
|
|
2536
|
+
zoomWidth: visibleBoxIn(zoomPanel.element, root)?.width
|
|
2537
|
+
});
|
|
2538
|
+
setState("data-zoom-row", zoomRaised ? "raised" : null);
|
|
2539
|
+
}
|
|
2540
|
+
if (toaster) {
|
|
2541
|
+
const obstacles = [zoomPanel.element, legal?.element, pill?.element].flatMap(
|
|
2542
|
+
(element) => {
|
|
2543
|
+
const box = visibleBoxIn(element, root);
|
|
2544
|
+
return box ? [{ left: box.x, right: box.x + box.width, top: size.height - box.y }] : [];
|
|
2545
|
+
}
|
|
2546
|
+
);
|
|
2547
|
+
toaster.setBottom(
|
|
2548
|
+
toastStackBottom({ containerWidth: size.width, obstacles, cardWidth: toaster.widestCard() })
|
|
2549
|
+
);
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
function setEmptyStateShown(shown) {
|
|
2553
|
+
if (ledger.isClosed || !emptyState || emptyState.isShown() === shown) return;
|
|
2554
|
+
const active = ownerDocument.activeElement;
|
|
2555
|
+
const hadFocus = emptyState.element.contains(active);
|
|
2556
|
+
const workHadFocus = shown && active !== null && root.contains(active) && ![emptyState.element, legal?.element, toaster?.element].some((kept) => kept?.contains(active));
|
|
2557
|
+
emptyState.setShown(shown);
|
|
2558
|
+
setState("data-empty-state", shown ? "shown" : null);
|
|
2559
|
+
ledger.attribute(canvas, "inert", shown ? "" : null);
|
|
2560
|
+
if (shown) {
|
|
2561
|
+
menu.close();
|
|
2562
|
+
popover?.close();
|
|
2563
|
+
tooltip.hide();
|
|
2564
|
+
if (workHadFocus) emptyState.focus();
|
|
2565
|
+
} else if (hadFocus) {
|
|
2566
|
+
focusCanvasOrTrigger();
|
|
2567
|
+
}
|
|
2568
|
+
updateLayout();
|
|
2569
|
+
}
|
|
1387
2570
|
const ResizeObserverClass = globalThis.ResizeObserver;
|
|
1388
2571
|
const resizeObserver = ResizeObserverClass ? new ResizeObserverClass(() => updateLayout()) : void 0;
|
|
1389
2572
|
resizeObserver?.observe(root);
|
|
2573
|
+
if (primary) resizeObserver?.observe(primary.element);
|
|
2574
|
+
if (legal) resizeObserver?.observe(legal.element);
|
|
1390
2575
|
ledger.defer(() => resizeObserver?.disconnect());
|
|
1391
2576
|
if (legend && options.legend?.open !== void 0) legend.start(options.legend.open);
|
|
1392
2577
|
updateLayout();
|
|
@@ -1395,12 +2580,16 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1395
2580
|
host,
|
|
1396
2581
|
dialogScope: keyTarget,
|
|
1397
2582
|
menuOpen: () => menu.isOpen(),
|
|
2583
|
+
startScreen: () => emptyState?.isShown() ?? false,
|
|
1398
2584
|
closeMenu() {
|
|
1399
2585
|
const active = ownerDocument.activeElement;
|
|
1400
2586
|
menu.close({ restoreFocus: active === menu.trigger || menu.dropdown.contains(active) });
|
|
1401
2587
|
},
|
|
1402
2588
|
hideTooltip: () => tooltip.hide(),
|
|
1403
|
-
|
|
2589
|
+
// Behind the start screen the keys reach the browser instead: there is no diagram to zoom.
|
|
2590
|
+
run: (key) => {
|
|
2591
|
+
if (!emptyState?.isShown()) zoomPanel.run(key);
|
|
2592
|
+
}
|
|
1404
2593
|
});
|
|
1405
2594
|
keyTarget.addEventListener("keydown", onKeydown);
|
|
1406
2595
|
ledger.defer(() => keyTarget.removeEventListener("keydown", onKeydown));
|
|
@@ -1452,6 +2641,21 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1452
2641
|
if (!ledger.isClosed) legend.toggle();
|
|
1453
2642
|
}
|
|
1454
2643
|
};
|
|
2644
|
+
const primaryHandle = primary && {
|
|
2645
|
+
element: primary.element,
|
|
2646
|
+
isCollapsed: () => primary.isCollapsed(),
|
|
2647
|
+
refresh: () => {
|
|
2648
|
+
if (!ledger.isClosed) primary.refresh();
|
|
2649
|
+
}
|
|
2650
|
+
};
|
|
2651
|
+
const appearanceHandle = appearance && darkCapable ? {
|
|
2652
|
+
choice: () => appearance.choice(),
|
|
2653
|
+
setChoice: (choice) => {
|
|
2654
|
+
if (ledger.isClosed) return;
|
|
2655
|
+
appearance.setChoice(choice);
|
|
2656
|
+
menu.refresh();
|
|
2657
|
+
}
|
|
2658
|
+
} : void 0;
|
|
1455
2659
|
const inspectorHandle = inspector && {
|
|
1456
2660
|
element: inspector.element,
|
|
1457
2661
|
isOpen: () => inspector.isOpen(),
|
|
@@ -1475,6 +2679,8 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1475
2679
|
tooltip: tooltipHandle,
|
|
1476
2680
|
legend: legendHandle,
|
|
1477
2681
|
inspector: inspectorHandle,
|
|
2682
|
+
primary: primaryHandle,
|
|
2683
|
+
appearance: appearanceHandle,
|
|
1478
2684
|
setZoomTarget(target) {
|
|
1479
2685
|
if (!ledger.isClosed) zoomPanel.setTarget(target);
|
|
1480
2686
|
},
|
|
@@ -1503,8 +2709,15 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1503
2709
|
},
|
|
1504
2710
|
setProblems(summary) {
|
|
1505
2711
|
if (ledger.isClosed || !pill) return;
|
|
2712
|
+
const listHadFocus = popover?.element.contains(ownerDocument.activeElement) ?? false;
|
|
1506
2713
|
pill.set(summary);
|
|
1507
2714
|
setState("data-problems", pill.isShown() ? "shown" : null);
|
|
2715
|
+
popover?.setItems(summary.items ?? []);
|
|
2716
|
+
if (!pill.isShown() && popover?.isOpen()) {
|
|
2717
|
+
popover.close();
|
|
2718
|
+
if (listHadFocus) focusCanvasOrTrigger();
|
|
2719
|
+
}
|
|
2720
|
+
updateBottomRow();
|
|
1508
2721
|
placeHint();
|
|
1509
2722
|
},
|
|
1510
2723
|
setStatus(next, statusOptions) {
|
|
@@ -1534,6 +2747,9 @@ function renderShell(root, options, themes, ledger) {
|
|
|
1534
2747
|
applyReadOnly();
|
|
1535
2748
|
},
|
|
1536
2749
|
isReadOnly: () => readOnly,
|
|
2750
|
+
setEmptyState: setEmptyStateShown,
|
|
2751
|
+
isEmptyStateShown: () => emptyState?.isShown() ?? false,
|
|
2752
|
+
toast: showToast,
|
|
1537
2753
|
destroy() {
|
|
1538
2754
|
ledger.close();
|
|
1539
2755
|
}
|
|
@@ -1573,4 +2789,4 @@ export {
|
|
|
1573
2789
|
* minus, plus, triangle-alert (alert-triangle) and x. Feather @license MIT
|
|
1574
2790
|
* Copyright (c) 2013-present Cole Bemis
|
|
1575
2791
|
* Full notices: THIRD_PARTY_NOTICES.md of @miragon/modeler-shell */
|
|
1576
|
-
//# sourceMappingURL=chunk-
|
|
2792
|
+
//# sourceMappingURL=chunk-66HCSJNY.js.map
|