@miragon/modeler-shell 0.0.0-stage → 0.1.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 +1902 -0
- package/LICENSE +21 -0
- package/README.md +435 -2
- package/THIRD_PARTY_NOTICES.md +52 -0
- package/dist/appearance-D3FUcbu6.d.cts +53 -0
- package/dist/appearance-D3FUcbu6.d.ts +53 -0
- package/dist/chunk-5JSZ4VGA.js +551 -0
- package/dist/chunk-5JSZ4VGA.js.map +1 -0
- package/dist/chunk-6LQOFACK.js +1576 -0
- package/dist/chunk-6LQOFACK.js.map +1 -0
- package/dist/chunk-N4QXFF3A.js +23 -0
- package/dist/chunk-N4QXFF3A.js.map +1 -0
- package/dist/chunk-RJRFR6N3.js +136 -0
- package/dist/chunk-RJRFR6N3.js.map +1 -0
- package/dist/core.cjs +619 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +171 -0
- package/dist/core.d.ts +171 -0
- package/dist/core.js +91 -0
- package/dist/core.js.map +1 -0
- package/dist/diagram-js.cjs +1115 -0
- package/dist/diagram-js.cjs.map +1 -0
- package/dist/diagram-js.d.cts +163 -0
- package/dist/diagram-js.d.ts +163 -0
- package/dist/diagram-js.js +867 -0
- package/dist/diagram-js.js.map +1 -0
- package/dist/index.cjs +2175 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +103 -0
- package/dist/index.js.map +1 -0
- package/dist/keymap-CObQE6q6.d.cts +151 -0
- package/dist/keymap-DORzHnK-.d.ts +151 -0
- package/dist/protocol-DhYBakWL.d.cts +15 -0
- package/dist/protocol-IeW8bf4f.d.ts +15 -0
- package/dist/shell.css +914 -0
- package/dist/testing-vscode-stub.cjs +332 -0
- package/dist/testing-vscode-stub.cjs.map +1 -0
- package/dist/testing-vscode-stub.d.cts +65 -0
- package/dist/testing-vscode-stub.d.ts +65 -0
- package/dist/testing-vscode-stub.js +154 -0
- package/dist/testing-vscode-stub.js.map +1 -0
- package/dist/testing.cjs +2364 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +165 -0
- package/dist/testing.d.ts +165 -0
- package/dist/testing.js +2297 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-B6NfkiUG.d.cts +66 -0
- package/dist/types-B6NfkiUG.d.ts +66 -0
- package/dist/types-DnZn9CPw.d.ts +284 -0
- package/dist/types-OuFlqZp0.d.cts +284 -0
- package/dist/vscode-extension.cjs +215 -0
- package/dist/vscode-extension.cjs.map +1 -0
- package/dist/vscode-extension.d.cts +103 -0
- package/dist/vscode-extension.d.ts +103 -0
- package/dist/vscode-extension.js +24 -0
- package/dist/vscode-extension.js.map +1 -0
- package/dist/vscode-webview.cjs +2198 -0
- package/dist/vscode-webview.cjs.map +1 -0
- package/dist/vscode-webview.d.cts +82 -0
- package/dist/vscode-webview.d.ts +82 -0
- package/dist/vscode-webview.js +250 -0
- package/dist/vscode-webview.js.map +1 -0
- package/package.json +145 -4
- package/src/core/appearance.ts +104 -0
- package/src/core/fit.ts +205 -0
- package/src/core/index.ts +84 -0
- package/src/core/keymap.ts +275 -0
- package/src/core/layout.ts +69 -0
- package/src/core/messages.ts +88 -0
- package/src/core/sizes.ts +85 -0
- package/src/core/types.ts +84 -0
- package/src/core/zoom.ts +74 -0
- package/src/core.ts +2 -0
- package/src/diagram-js/connect.ts +75 -0
- package/src/diagram-js/index.ts +16 -0
- package/src/diagram-js/module.ts +20 -0
- package/src/diagram-js/palette-skin.ts +267 -0
- package/src/diagram-js/palette.css +269 -0
- package/src/diagram-js/service.ts +240 -0
- package/src/diagram-js/types.ts +67 -0
- package/src/diagram-js/viewport.ts +564 -0
- package/src/diagram-js.ts +2 -0
- package/src/dom/element.ts +51 -0
- package/src/dom/hint.ts +51 -0
- package/src/dom/icons.ts +309 -0
- package/src/dom/index.ts +29 -0
- package/src/dom/inspector.ts +35 -0
- package/src/dom/keys.ts +59 -0
- package/src/dom/ledger.ts +87 -0
- package/src/dom/legend.ts +85 -0
- package/src/dom/menu.ts +393 -0
- package/src/dom/mount.ts +461 -0
- package/src/dom/problems.ts +72 -0
- package/src/dom/shell-dom.css +966 -0
- package/src/dom/status.ts +72 -0
- package/src/dom/tooltip.ts +237 -0
- package/src/dom/types.ts +277 -0
- package/src/dom/zoom-panel.ts +154 -0
- package/src/index.ts +3 -0
- package/src/shell.css +6 -0
- package/src/testing/cases.ts +1012 -0
- package/src/testing/checks.ts +832 -0
- package/src/testing/facts.ts +587 -0
- package/src/testing/geometry.ts +472 -0
- package/src/testing/harness.ts +138 -0
- package/src/testing/index.ts +15 -0
- package/src/testing/page-tools.ts +434 -0
- package/src/testing/page.ts +17 -0
- package/src/testing/vscode-stub.ts +237 -0
- package/src/testing-vscode-stub.ts +2 -0
- package/src/testing.ts +2 -0
- package/src/vscode/extension/html.ts +107 -0
- package/src/vscode/extension/index.ts +8 -0
- package/src/vscode/extension/reveal.ts +9 -0
- package/src/vscode/extension/theme-bridge.ts +85 -0
- package/src/vscode/extension/types.ts +36 -0
- package/src/vscode/protocol.ts +31 -0
- package/src/vscode/webview/appearance.ts +141 -0
- package/src/vscode/webview/index.ts +8 -0
- package/src/vscode/webview/mount.ts +76 -0
- package/src/vscode/webview/state.ts +116 -0
- package/src/vscode-extension.ts +2 -0
- package/src/vscode-webview.ts +2 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createElement } from "./element.js";
|
|
2
|
+
import { iconElement } from "./icons.js";
|
|
3
|
+
import type { StatusMessage } from "./types.js";
|
|
4
|
+
|
|
5
|
+
export interface StatusBox {
|
|
6
|
+
readonly element: HTMLElement;
|
|
7
|
+
set(status: StatusMessage | null, delayMs: number): void;
|
|
8
|
+
destroy(): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createStatusBox(ownerDocument: Document): StatusBox {
|
|
12
|
+
const icon = createElement(ownerDocument, "span", { class: "cd-shell-status-icon" });
|
|
13
|
+
const text = createElement(ownerDocument, "p", { class: "cd-shell-status-text" });
|
|
14
|
+
const element = createElement(
|
|
15
|
+
ownerDocument,
|
|
16
|
+
"div",
|
|
17
|
+
{ class: "cd-shell-status", "data-chrome": "status" },
|
|
18
|
+
icon,
|
|
19
|
+
text,
|
|
20
|
+
);
|
|
21
|
+
element.hidden = true;
|
|
22
|
+
|
|
23
|
+
let pending: { status: StatusMessage; timer: ReturnType<typeof setTimeout> } | undefined;
|
|
24
|
+
|
|
25
|
+
function cancelPending(): void {
|
|
26
|
+
if (pending) clearTimeout(pending.timer);
|
|
27
|
+
pending = undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function show(status: StatusMessage): void {
|
|
31
|
+
pending = undefined;
|
|
32
|
+
if (element.dataset.severity !== status.severity) {
|
|
33
|
+
element.dataset.severity = status.severity;
|
|
34
|
+
element.setAttribute("role", status.severity === "danger" ? "alert" : "status");
|
|
35
|
+
icon.replaceChildren(iconElement(status.severity === "danger" ? "circle-x" : "triangle-alert"));
|
|
36
|
+
}
|
|
37
|
+
// An unchanged text is not written again, so role="alert" does not announce it again.
|
|
38
|
+
if (text.textContent !== status.text) text.textContent = status.text;
|
|
39
|
+
element.hidden = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
element,
|
|
44
|
+
set(status, delayMs) {
|
|
45
|
+
if (status === null) {
|
|
46
|
+
cancelPending();
|
|
47
|
+
element.hidden = true;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!element.hidden) {
|
|
51
|
+
show(status);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// Repeated invalid imports while typing keep the first timer: the box shows once the text
|
|
55
|
+
// stayed invalid for the delay, not the delay after the last keystroke (guide §5.12).
|
|
56
|
+
if (pending) {
|
|
57
|
+
pending.status = status;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (delayMs > 0) {
|
|
61
|
+
const entry = { status, timer: setTimeout(() => show(entry.status), delayMs) };
|
|
62
|
+
pending = entry;
|
|
63
|
+
} else {
|
|
64
|
+
show(status);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
destroy() {
|
|
68
|
+
cancelPending();
|
|
69
|
+
element.remove();
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { TOOLTIP_DELAY_MS, placeTooltip } from "../core/index.js";
|
|
2
|
+
import { accessibleName, boxRelativeTo, createElement, hasVisibleFocus } from "./element.js";
|
|
3
|
+
import type { TooltipContent, TooltipHandle } from "./types.js";
|
|
4
|
+
|
|
5
|
+
type ContentSource = TooltipContent | (() => TooltipContent | null);
|
|
6
|
+
|
|
7
|
+
interface Shown {
|
|
8
|
+
control: HTMLElement;
|
|
9
|
+
describedBy: string | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface TooltipController extends TooltipHandle {
|
|
13
|
+
readonly element: HTMLElement;
|
|
14
|
+
/** One tooltip for every `selector` match inside `container` (palette entries are rebuilt often). */
|
|
15
|
+
delegate(
|
|
16
|
+
container: HTMLElement,
|
|
17
|
+
selector: string,
|
|
18
|
+
content: (control: HTMLElement) => TooltipContent | null,
|
|
19
|
+
): () => void;
|
|
20
|
+
destroy(): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TooltipContext {
|
|
24
|
+
root: HTMLElement;
|
|
25
|
+
layer: HTMLElement;
|
|
26
|
+
id: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createTooltip({ root, layer, id }: TooltipContext): TooltipController {
|
|
30
|
+
const ownerDocument = root.ownerDocument;
|
|
31
|
+
const label = createElement(ownerDocument, "span", { class: "cd-shell-tooltip-label" });
|
|
32
|
+
const shortcut = createElement(ownerDocument, "kbd", { class: "cd-shell-kbd" });
|
|
33
|
+
const element = createElement(ownerDocument, "div", { class: "cd-shell-tooltip", role: "tooltip", id });
|
|
34
|
+
element.hidden = true;
|
|
35
|
+
element.append(label, shortcut);
|
|
36
|
+
layer.append(element);
|
|
37
|
+
|
|
38
|
+
let shown: Shown | undefined;
|
|
39
|
+
let pending: { control: HTMLElement; timer: ReturnType<typeof setTimeout> } | undefined;
|
|
40
|
+
// After a pointer leaves a control the next one shows at once, so moving along a row of
|
|
41
|
+
// buttons does not wait 300ms for every neighbour (guide §5.6).
|
|
42
|
+
let warmUntil = 0;
|
|
43
|
+
// A clicked control keeps its tooltip hidden until the pointer leaves it.
|
|
44
|
+
let pressed: HTMLElement | undefined;
|
|
45
|
+
const detachers = new Set<() => void>();
|
|
46
|
+
|
|
47
|
+
function cancelPending(): void {
|
|
48
|
+
if (pending) clearTimeout(pending.timer);
|
|
49
|
+
pending = undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function resolve(source: ContentSource): TooltipContent | null {
|
|
53
|
+
return typeof source === "function" ? source() : source;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function place(control: HTMLElement): void {
|
|
57
|
+
const island = control.closest<HTMLElement>("[data-chrome]") ?? control;
|
|
58
|
+
const point = placeTooltip({
|
|
59
|
+
anchor: boxRelativeTo(control, root),
|
|
60
|
+
island: boxRelativeTo(island, root),
|
|
61
|
+
tooltip: { width: element.offsetWidth, height: element.offsetHeight },
|
|
62
|
+
container: { width: root.clientWidth, height: root.clientHeight },
|
|
63
|
+
});
|
|
64
|
+
element.style.left = `${point.x}px`;
|
|
65
|
+
element.style.top = `${point.y}px`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function hide(): void {
|
|
69
|
+
cancelPending();
|
|
70
|
+
if (!shown) return;
|
|
71
|
+
const { control, describedBy } = shown;
|
|
72
|
+
shown = undefined;
|
|
73
|
+
element.hidden = true;
|
|
74
|
+
if (describedBy === null) control.removeAttribute("aria-describedby");
|
|
75
|
+
else control.setAttribute("aria-describedby", describedBy);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function show(control: HTMLElement, source: ContentSource): void {
|
|
79
|
+
cancelPending();
|
|
80
|
+
const content = resolve(source);
|
|
81
|
+
if (!content || !control.isConnected) {
|
|
82
|
+
if (shown?.control === control) hide();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (shown && shown.control !== control) hide();
|
|
86
|
+
label.textContent = content.label;
|
|
87
|
+
shortcut.textContent = content.shortcut ?? "";
|
|
88
|
+
shortcut.hidden = !content.shortcut;
|
|
89
|
+
element.hidden = false;
|
|
90
|
+
if (!shown) {
|
|
91
|
+
const describedBy = control.getAttribute("aria-describedby");
|
|
92
|
+
shown = { control, describedBy };
|
|
93
|
+
// Only a tooltip that says more than the control's name describes it (guide §5.6); a shortcut
|
|
94
|
+
// chip does, unless the control names its keys in aria-keyshortcuts.
|
|
95
|
+
const saysMore =
|
|
96
|
+
!accessibleName(control).includes(content.label) ||
|
|
97
|
+
(Boolean(content.shortcut) && !control.hasAttribute("aria-keyshortcuts"));
|
|
98
|
+
if (saysMore) {
|
|
99
|
+
control.setAttribute("aria-describedby", [describedBy, id].filter(Boolean).join(" "));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
place(control);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function onEnter(control: HTMLElement, source: ContentSource, event: PointerEvent): void {
|
|
106
|
+
if (event.pointerType === "touch" || pressed === control) return;
|
|
107
|
+
if (shown || Date.now() < warmUntil) {
|
|
108
|
+
show(control, source);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
cancelPending();
|
|
112
|
+
pending = { control, timer: setTimeout(() => show(control, source), TOOLTIP_DELAY_MS) };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function onLeave(control: HTMLElement): void {
|
|
116
|
+
if (pressed === control) pressed = undefined;
|
|
117
|
+
if (pending?.control === control) cancelPending();
|
|
118
|
+
if (shown?.control === control) {
|
|
119
|
+
hide();
|
|
120
|
+
warmUntil = Date.now() + TOOLTIP_DELAY_MS;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function onFocus(control: HTMLElement, source: ContentSource): void {
|
|
125
|
+
if (hasVisibleFocus(control)) show(control, source);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function onBlur(control: HTMLElement): void {
|
|
129
|
+
if (pending?.control === control) cancelPending();
|
|
130
|
+
if (shown?.control === control) hide();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function onPress(control: HTMLElement, event: Event): void {
|
|
134
|
+
if (event.type === "pointerdown") pressed = control;
|
|
135
|
+
if (pending?.control === control) cancelPending();
|
|
136
|
+
if (shown?.control === control) hide();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function attach(control: HTMLElement, source: ContentSource): () => void {
|
|
140
|
+
const enter = (event: PointerEvent) => onEnter(control, source, event);
|
|
141
|
+
const leave = () => onLeave(control);
|
|
142
|
+
const focus = () => onFocus(control, source);
|
|
143
|
+
const blur = () => onBlur(control);
|
|
144
|
+
const press = (event: Event) => onPress(control, event);
|
|
145
|
+
control.addEventListener("pointerenter", enter);
|
|
146
|
+
control.addEventListener("pointerleave", leave);
|
|
147
|
+
control.addEventListener("focus", focus);
|
|
148
|
+
control.addEventListener("blur", blur);
|
|
149
|
+
control.addEventListener("pointerdown", press);
|
|
150
|
+
control.addEventListener("click", press);
|
|
151
|
+
const detach = () => {
|
|
152
|
+
if (!detachers.delete(detach)) return;
|
|
153
|
+
control.removeEventListener("pointerenter", enter);
|
|
154
|
+
control.removeEventListener("pointerleave", leave);
|
|
155
|
+
control.removeEventListener("focus", focus);
|
|
156
|
+
control.removeEventListener("blur", blur);
|
|
157
|
+
control.removeEventListener("pointerdown", press);
|
|
158
|
+
control.removeEventListener("click", press);
|
|
159
|
+
if (shown?.control === control || pending?.control === control) hide();
|
|
160
|
+
if (pressed === control) pressed = undefined;
|
|
161
|
+
};
|
|
162
|
+
detachers.add(detach);
|
|
163
|
+
return detach;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function delegate(
|
|
167
|
+
container: HTMLElement,
|
|
168
|
+
selector: string,
|
|
169
|
+
content: (control: HTMLElement) => TooltipContent | null,
|
|
170
|
+
): () => void {
|
|
171
|
+
let hovered: HTMLElement | undefined;
|
|
172
|
+
const controlOf = (target: EventTarget | null): HTMLElement | undefined => {
|
|
173
|
+
if (!target || typeof (target as Element).closest !== "function") return undefined;
|
|
174
|
+
const match = (target as Element).closest<HTMLElement>(selector);
|
|
175
|
+
return match && container.contains(match) ? match : undefined;
|
|
176
|
+
};
|
|
177
|
+
const sourceOf = (control: HTMLElement) => () => content(control);
|
|
178
|
+
const over = (event: PointerEvent) => {
|
|
179
|
+
const control = controlOf(event.target);
|
|
180
|
+
if (control === hovered) return;
|
|
181
|
+
if (hovered) onLeave(hovered);
|
|
182
|
+
hovered = control;
|
|
183
|
+
if (control) onEnter(control, sourceOf(control), event);
|
|
184
|
+
};
|
|
185
|
+
const out = (event: PointerEvent) => {
|
|
186
|
+
if (!hovered) return;
|
|
187
|
+
if (controlOf(event.relatedTarget) === hovered) return;
|
|
188
|
+
onLeave(hovered);
|
|
189
|
+
hovered = undefined;
|
|
190
|
+
};
|
|
191
|
+
const focusIn = (event: FocusEvent) => {
|
|
192
|
+
const control = controlOf(event.target);
|
|
193
|
+
if (control) onFocus(control, sourceOf(control));
|
|
194
|
+
};
|
|
195
|
+
const focusOut = (event: FocusEvent) => {
|
|
196
|
+
const control = controlOf(event.target);
|
|
197
|
+
if (control) onBlur(control);
|
|
198
|
+
};
|
|
199
|
+
const press = (event: Event) => {
|
|
200
|
+
const control = controlOf(event.target);
|
|
201
|
+
if (control) onPress(control, event);
|
|
202
|
+
};
|
|
203
|
+
container.addEventListener("pointerover", over);
|
|
204
|
+
container.addEventListener("pointerout", out);
|
|
205
|
+
container.addEventListener("focusin", focusIn);
|
|
206
|
+
container.addEventListener("focusout", focusOut);
|
|
207
|
+
container.addEventListener("pointerdown", press);
|
|
208
|
+
container.addEventListener("click", press);
|
|
209
|
+
const detach = () => {
|
|
210
|
+
if (!detachers.delete(detach)) return;
|
|
211
|
+
container.removeEventListener("pointerover", over);
|
|
212
|
+
container.removeEventListener("pointerout", out);
|
|
213
|
+
container.removeEventListener("focusin", focusIn);
|
|
214
|
+
container.removeEventListener("focusout", focusOut);
|
|
215
|
+
container.removeEventListener("pointerdown", press);
|
|
216
|
+
container.removeEventListener("click", press);
|
|
217
|
+
if ((shown && container.contains(shown.control)) || (pending && container.contains(pending.control))) {
|
|
218
|
+
hide();
|
|
219
|
+
}
|
|
220
|
+
hovered = undefined;
|
|
221
|
+
};
|
|
222
|
+
detachers.add(detach);
|
|
223
|
+
return detach;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
element,
|
|
228
|
+
attach,
|
|
229
|
+
delegate,
|
|
230
|
+
hide,
|
|
231
|
+
destroy() {
|
|
232
|
+
hide();
|
|
233
|
+
for (const detach of [...detachers]) detach();
|
|
234
|
+
element.remove();
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
package/src/dom/types.ts
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Insets,
|
|
3
|
+
Platform,
|
|
4
|
+
ShellHost,
|
|
5
|
+
ShellMessageOverrides,
|
|
6
|
+
ShellMessages,
|
|
7
|
+
Theme,
|
|
8
|
+
ZoomTarget,
|
|
9
|
+
} from "../core/index.js";
|
|
10
|
+
import type { ShellIconName } from "./icons.js";
|
|
11
|
+
|
|
12
|
+
export interface ShellOptions {
|
|
13
|
+
/** The host column of guide §5.2. */
|
|
14
|
+
host: ShellHost;
|
|
15
|
+
/**
|
|
16
|
+
* The element diagram-js renders into (for example a renderer's `modeler.container`). The shell
|
|
17
|
+
* moves it into its canvas slot and adds `cd-shell-canvas`. Omitted: the shell creates the slot,
|
|
18
|
+
* and the modeler is constructed with `container: handle.canvas`.
|
|
19
|
+
*/
|
|
20
|
+
canvas?: HTMLElement | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* The tool's UI language (BCP 47) for `Intl` and `<html lang>` (guide §2: one language per tool).
|
|
23
|
+
* Default in an own document (`ide`, `web`): the closest `[lang]` of the root, else `"en"`. In the
|
|
24
|
+
* `embedded` host the page's language is not the tool's, so the default is the root's own `lang`
|
|
25
|
+
* attribute, else `"en"`, and the root carries `lang` itself.
|
|
26
|
+
*/
|
|
27
|
+
lang?: string | undefined;
|
|
28
|
+
messages?: ShellMessageOverrides | undefined;
|
|
29
|
+
/** Platform of the shortcut labels; default `detectPlatform(navigator)`. */
|
|
30
|
+
platform?: Platform | undefined;
|
|
31
|
+
/** Where the one `keydown` listener sits; default `document`, `root` in the `embedded` host. */
|
|
32
|
+
keyScope?: "document" | "root" | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* The modes the modeler can show (a static value of the modeler package), default `["light"]`.
|
|
35
|
+
* Read in the `embedded` host only: there the root carries `data-cd-theme="<themes[0]>"` itself
|
|
36
|
+
* (until the contract's `appearance` surface an embedded modeler stays in its first mode,
|
|
37
|
+
* whatever the page shows) and mirrors the host's `data-cd-kontrast` (guide §13.3). In an own
|
|
38
|
+
* document the host writes the mode on `<html>`.
|
|
39
|
+
*/
|
|
40
|
+
themes?: readonly Theme[] | undefined;
|
|
41
|
+
/** The fixed menu groups this modeler fills (guide §5.4); omitted groups and empty ones collapse. */
|
|
42
|
+
menu?: MenuGroups | undefined;
|
|
43
|
+
/** The problems pill; omitted = the modeler has no diagnostics. */
|
|
44
|
+
problems?: ProblemsOptions | undefined;
|
|
45
|
+
/** The legend card; omitted = the notation has no legend. */
|
|
46
|
+
legend?: LegendOptions | undefined;
|
|
47
|
+
/** The inspector slot; omitted = the modeler has no inspector. */
|
|
48
|
+
inspector?: InspectorOptions | undefined;
|
|
49
|
+
/** The empty-file hint of IDE and embedded hosts (guide §13.5); omitted = no hint. */
|
|
50
|
+
emptyHint?: EmptyHintOptions | undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Rendered in this order, separated by separators: Model, Export, Help (guide §5.4). */
|
|
54
|
+
export interface MenuGroups {
|
|
55
|
+
model?: readonly MenuEntry[] | undefined;
|
|
56
|
+
export?: readonly MenuEntry[] | undefined;
|
|
57
|
+
help?: readonly MenuEntry[] | undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type MenuEntry = MenuActionEntry | MenuDialogEntry | MenuCheckboxEntry | MenuRadioGroup;
|
|
61
|
+
|
|
62
|
+
interface MenuItemBase {
|
|
63
|
+
/** Stable id, rendered as `data-menu-id`. */
|
|
64
|
+
id: string;
|
|
65
|
+
label: string;
|
|
66
|
+
/** Only the entries the guide gives an icon (the common set, guide §5.14). */
|
|
67
|
+
icon?: ShellIconName | undefined;
|
|
68
|
+
/** Read whenever the menu opens or `refresh()` runs; disabled entries stay focusable. */
|
|
69
|
+
disabled?: (() => boolean) | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* A Model entry that stays enabled on a read-only canvas: a view-only toggle that never touches
|
|
72
|
+
* the document (Legend, Type captions). Default `false`: the entry is aria-disabled while the
|
|
73
|
+
* canvas is read-only (guide §13.5). The shell's own Legend checkbox sets it.
|
|
74
|
+
*/
|
|
75
|
+
keepEnabledWhenReadOnly?: boolean | undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** `menuitem`: closes the menu, returns focus to the trigger, then runs `onSelect`. */
|
|
79
|
+
export interface MenuActionEntry extends MenuItemBase {
|
|
80
|
+
kind: "action";
|
|
81
|
+
/** Shortcut chip text shown on the right, verbatim. */
|
|
82
|
+
shortcut?: string | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* `aria-keyshortcuts` (WAI-ARIA names, guide §5.6); default `ariaKeyShortcutsFromLabel(shortcut)`.
|
|
85
|
+
* An empty string sets none.
|
|
86
|
+
*/
|
|
87
|
+
ariaKeyShortcuts?: string | undefined;
|
|
88
|
+
/** Destructive: icon in `--cd-ui-danger` (confirm per guide §9 in `onSelect`). */
|
|
89
|
+
danger?: boolean | undefined;
|
|
90
|
+
onSelect(): void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* `menuitem` with `aria-haspopup="dialog"`; its label ends with "…" (U+2026). Behaves like an
|
|
95
|
+
* action: the menu closes and focus returns to the trigger before `onSelect` opens the dialog.
|
|
96
|
+
*/
|
|
97
|
+
export interface MenuDialogEntry extends MenuItemBase {
|
|
98
|
+
kind: "dialog";
|
|
99
|
+
onSelect(): void;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** `menuitemcheckbox`: toggles and keeps the menu open; the check mark carries the state. */
|
|
103
|
+
export interface MenuCheckboxEntry extends MenuItemBase {
|
|
104
|
+
kind: "checkbox";
|
|
105
|
+
checked(): boolean;
|
|
106
|
+
onToggle(checked: boolean): void;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** A `role="group"` with a heading and `menuitemradio` entries; keeps the menu open. */
|
|
110
|
+
export interface MenuRadioGroup {
|
|
111
|
+
kind: "radio";
|
|
112
|
+
id: string;
|
|
113
|
+
/** Heading and group `aria-label`. */
|
|
114
|
+
label: string;
|
|
115
|
+
options: readonly MenuRadioOption[];
|
|
116
|
+
value(): string | undefined;
|
|
117
|
+
onSelect(value: string): void;
|
|
118
|
+
disabled?: (() => boolean) | undefined;
|
|
119
|
+
/** As `MenuItemBase.keepEnabledWhenReadOnly`. */
|
|
120
|
+
keepEnabledWhenReadOnly?: boolean | undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface MenuRadioOption {
|
|
124
|
+
value: string;
|
|
125
|
+
label: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface ProblemsOptions {
|
|
129
|
+
/** A click on the pill: the IDE host opens its Problems view. */
|
|
130
|
+
onActivate(): void;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface LegendOptions {
|
|
134
|
+
/** Renders the legend rows into `body` once (classes `cd-shell-legend-*`, see API.md). */
|
|
135
|
+
render(body: HTMLElement): void;
|
|
136
|
+
/** Start state the host remembered; default (also `undefined`) `legendStartsOpen(container height)`. */
|
|
137
|
+
open?: boolean | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* Every change of the open state (close button, menu checkbox, `setOpen`, `toggle`), so the host
|
|
140
|
+
* can persist it; not the automatic start decision.
|
|
141
|
+
*/
|
|
142
|
+
onToggle?: ((open: boolean) => void) | undefined;
|
|
143
|
+
/** Append the "Legend" checkbox to the Model group; default `true`. */
|
|
144
|
+
menuEntry?: boolean | undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface InspectorOptions {
|
|
148
|
+
/** `aria-label` of the inspector `<aside>`. */
|
|
149
|
+
label: string;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface EmptyHintOptions {
|
|
153
|
+
/** Shows the "Insert example" button; the modeler writes the example as one document change. */
|
|
154
|
+
onInsertExample?: (() => void) | undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface ShellHandle {
|
|
158
|
+
/** The element passed to `mountShell`, now `.cd-shell`. */
|
|
159
|
+
readonly root: HTMLElement;
|
|
160
|
+
/** The canvas slot (`.cd-shell-canvas`), diagram-js's container. */
|
|
161
|
+
readonly canvas: HTMLElement;
|
|
162
|
+
readonly host: ShellHost;
|
|
163
|
+
readonly lang: string;
|
|
164
|
+
readonly platform: Platform;
|
|
165
|
+
/** The resolved messages table. */
|
|
166
|
+
readonly messages: ShellMessages;
|
|
167
|
+
readonly menu: MenuHandle;
|
|
168
|
+
readonly tooltip: TooltipHandle;
|
|
169
|
+
readonly legend: LegendHandle | undefined;
|
|
170
|
+
readonly inspector: InspectorHandle | undefined;
|
|
171
|
+
/** Binds zoom panel, readout and chrome keys; `null` unbinds (buttons disabled). */
|
|
172
|
+
setZoomTarget(target: ZoomTarget | null): void;
|
|
173
|
+
/** The fit insets for what is open right now (palette row, legend, inspector, pill). */
|
|
174
|
+
fitInsets(): Insets;
|
|
175
|
+
/** Adapter API: the diagram-js palette island; returns the unregister function. */
|
|
176
|
+
registerPalette(palette: PaletteRegistration): () => void;
|
|
177
|
+
/** Shows the pill for `count > 0`, hides it at 0. */
|
|
178
|
+
setProblems(summary: ProblemSummary): void;
|
|
179
|
+
/** Shows (after `delayMs`, default 0) or hides (at once) the status box under the palette. */
|
|
180
|
+
setStatus(status: StatusMessage | null, options?: { delayMs?: number | undefined }): void;
|
|
181
|
+
/**
|
|
182
|
+
* Shows or hides the empty-file hint. `connectDiagramJs` keeps it in sync with the diagram
|
|
183
|
+
* (`ConnectOptions.isEmpty`). Hiding it while it holds the focus moves the focus to the canvas.
|
|
184
|
+
*/
|
|
185
|
+
setEmptyHint(visible: boolean): void;
|
|
186
|
+
/**
|
|
187
|
+
* Read-only canvas (invalid file, guide §13.5). `true` locks at once: `isReadOnly()`, the palette
|
|
188
|
+
* veto (every entry but Lasso and Hand) and the Model entries of the menu without
|
|
189
|
+
* `keepEnabledWhenReadOnly`; the legend and its Legend checkbox stay usable. `data-readonly` and the
|
|
190
|
+
* `aria-disabled` palette entries show after `delayMs` (default 0; an invalid file passes
|
|
191
|
+
* `INVALID_STATUS_DELAY_MS`, as for `setStatus`), so typing in the text editor does not flicker.
|
|
192
|
+
* Repeated calls while the delay runs keep the first timer; `false` unlocks and hides at once.
|
|
193
|
+
*/
|
|
194
|
+
setReadOnly(readOnly: boolean, options?: { delayMs?: number | undefined }): void;
|
|
195
|
+
/** The lock, true from the first `setReadOnly(true)` on, also while its marks are still delayed. */
|
|
196
|
+
isReadOnly(): boolean;
|
|
197
|
+
/** Removes every element, attribute, listener, observer and timer the shell added. Idempotent. */
|
|
198
|
+
destroy(): void;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export interface MenuHandle {
|
|
202
|
+
readonly trigger: HTMLButtonElement;
|
|
203
|
+
readonly dropdown: HTMLElement;
|
|
204
|
+
isOpen(): boolean;
|
|
205
|
+
/** Opens on the first (default) or last entry. */
|
|
206
|
+
open(focus?: "first" | "last"): void;
|
|
207
|
+
/** Closes; `restoreFocus` moves focus back to the trigger (default `false`). */
|
|
208
|
+
close(options?: { restoreFocus?: boolean | undefined }): void;
|
|
209
|
+
/** Re-reads `checked`, `value` and `disabled` (call after an external state change). */
|
|
210
|
+
refresh(): void;
|
|
211
|
+
setGroups(groups: MenuGroups): void;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export interface TooltipContent {
|
|
215
|
+
label: string;
|
|
216
|
+
/** Shortcut chip text, verbatim (`⌘+`). */
|
|
217
|
+
shortcut?: string | undefined;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface TooltipHandle {
|
|
221
|
+
/**
|
|
222
|
+
* The chrome tooltip for `control`: after `TOOLTIP_DELAY_MS` hover, at once on keyboard focus,
|
|
223
|
+
* hidden on click, Escape, leave and menu open. Returns the detach function.
|
|
224
|
+
*/
|
|
225
|
+
attach(control: HTMLElement, content: TooltipContent | (() => TooltipContent | null)): () => void;
|
|
226
|
+
hide(): void;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export interface LegendHandle {
|
|
230
|
+
/** The legend card (`[data-chrome="legend"]`). */
|
|
231
|
+
readonly element: HTMLElement;
|
|
232
|
+
/** Where `render` put the rows. */
|
|
233
|
+
readonly body: HTMLElement;
|
|
234
|
+
isOpen(): boolean;
|
|
235
|
+
setOpen(open: boolean): void;
|
|
236
|
+
toggle(): void;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface InspectorHandle {
|
|
240
|
+
/** The `<aside data-chrome="inspector">`; the modeler renders its fields into it. */
|
|
241
|
+
readonly element: HTMLElement;
|
|
242
|
+
isOpen(): boolean;
|
|
243
|
+
open(): void;
|
|
244
|
+
/** Closes; a focus inside it moves to the canvas. */
|
|
245
|
+
close(): void;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** What the diagram-js adapter hands the shell about the palette. */
|
|
249
|
+
export interface PaletteRegistration {
|
|
250
|
+
/** The `.djs-palette` element (already carrying `data-chrome="palette"`). */
|
|
251
|
+
element: HTMLElement;
|
|
252
|
+
/** Tooltip of an entry element (`.entry`), from `palette.getEntries()` title and shortcut. */
|
|
253
|
+
entryTooltip(entry: HTMLElement): TooltipContent | null;
|
|
254
|
+
/** The read-only state, on registration and on every `setReadOnly` and its delayed marks. */
|
|
255
|
+
setReadOnly(state: PaletteReadOnlyState): void;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** The read-only canvas as the palette sees it (guide §13.5). */
|
|
259
|
+
export interface PaletteReadOnlyState {
|
|
260
|
+
/** Veto every entry but Lasso and Hand on `palette.trigger`, from the invalid import on. */
|
|
261
|
+
locked: boolean;
|
|
262
|
+
/** Mark those entries `aria-disabled` and muted, once the shell's delay has passed. */
|
|
263
|
+
shown: boolean;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export interface ProblemSummary {
|
|
267
|
+
count: number;
|
|
268
|
+
/** How many of them are errors; decides icon and wording. */
|
|
269
|
+
errors: number;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export type StatusSeverity = "danger" | "warning";
|
|
273
|
+
|
|
274
|
+
export interface StatusMessage {
|
|
275
|
+
severity: StatusSeverity;
|
|
276
|
+
text: string;
|
|
277
|
+
}
|