@miragon/modeler-shell 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +1051 -84
- package/README.md +162 -31
- package/dist/appearance-BBaM_4bD.d.ts +323 -0
- package/dist/appearance-XX3JC3pI.d.cts +323 -0
- package/dist/{chunk-6LQOFACK.js → chunk-A4YGKH4Y.js} +1252 -31
- package/dist/chunk-A4YGKH4Y.js.map +1 -0
- package/dist/{chunk-RJRFR6N3.js → chunk-KYDG5DMX.js} +2 -2
- package/dist/{chunk-5JSZ4VGA.js → chunk-OADW3GPA.js} +240 -6
- package/dist/chunk-OADW3GPA.js.map +1 -0
- package/dist/core.cjs +260 -6
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +81 -6
- package/dist/core.d.ts +81 -6
- package/dist/core.js +43 -3
- package/dist/diagram-js.cjs +34 -2
- package/dist/diagram-js.cjs.map +1 -1
- package/dist/diagram-js.d.cts +10 -3
- package/dist/diagram-js.d.ts +10 -3
- package/dist/diagram-js.js +14 -2
- package/dist/diagram-js.js.map +1 -1
- package/dist/index.cjs +1658 -196
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -9
- package/dist/index.d.ts +18 -9
- package/dist/index.js +44 -4
- package/dist/shell.css +692 -3
- package/dist/testing-vscode-stub.js +2 -2
- package/dist/testing.cjs +2849 -550
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +75 -9
- package/dist/testing.d.ts +75 -9
- package/dist/testing.js +2744 -558
- package/dist/testing.js.map +1 -1
- package/dist/toasts-Bp0REqdq.d.cts +142 -0
- package/dist/toasts-Bp0REqdq.d.ts +142 -0
- package/dist/{types-OuFlqZp0.d.cts → types-CnRrINRs.d.cts} +170 -9
- package/dist/{types-DnZn9CPw.d.ts → types-PuDZczzy.d.ts} +170 -9
- package/dist/vscode-extension.js +2 -2
- package/dist/vscode-webview.cjs +1546 -178
- package/dist/vscode-webview.cjs.map +1 -1
- package/dist/vscode-webview.d.cts +6 -4
- package/dist/vscode-webview.d.ts +6 -4
- package/dist/vscode-webview.js +11 -2
- package/dist/vscode-webview.js.map +1 -1
- package/package.json +2 -2
- package/src/core/feedback/appearance.ts +141 -0
- package/src/core/feedback/problems.ts +23 -0
- package/src/core/feedback/toasts.ts +118 -0
- package/src/core/fit.ts +18 -3
- package/src/core/index.ts +43 -0
- package/src/core/keymap.ts +20 -0
- package/src/core/messages.ts +71 -0
- package/src/core/types.ts +24 -2
- package/src/core/web-chrome/bottom-row.ts +37 -0
- package/src/core/web-chrome/drop.ts +78 -0
- package/src/core/web-chrome/top-row.ts +66 -0
- package/src/diagram-js/service.ts +4 -1
- package/src/diagram-js/viewport.ts +19 -0
- package/src/dom/buttons.css +124 -0
- package/src/dom/feedback/appearance.ts +184 -0
- package/src/dom/feedback/feedback.css +435 -0
- package/src/dom/feedback/problems-popover.ts +287 -0
- package/src/dom/feedback/toasts.ts +431 -0
- package/src/dom/index.ts +13 -0
- package/src/dom/keys.ts +8 -0
- package/src/dom/menu.ts +28 -17
- package/src/dom/mount.ts +290 -20
- package/src/dom/problems.ts +5 -4
- package/src/dom/shell-dom.css +7 -4
- package/src/dom/tooltip.ts +10 -0
- package/src/dom/types.ts +188 -6
- package/src/dom/web/drop-zone.ts +181 -0
- package/src/dom/web/empty-state.ts +144 -0
- package/src/dom/web/legal.ts +75 -0
- package/src/dom/web/menu-groups.ts +69 -0
- package/src/dom/web/primary.ts +124 -0
- package/src/dom/web/web.css +386 -0
- package/src/shell.css +3 -0
- package/src/testing/case-tools.ts +164 -0
- package/src/testing/cases.ts +209 -144
- package/src/testing/checks.ts +51 -15
- package/src/testing/facts.ts +56 -2
- package/src/testing/geometry.ts +206 -26
- package/src/testing/harness.ts +56 -7
- package/src/testing/index.ts +4 -0
- package/src/testing/page-tools.ts +24 -1
- package/src/testing/page.ts +10 -0
- package/src/testing/web-cases.ts +913 -0
- package/src/testing/web-checks.ts +1049 -0
- package/src/testing/web-facts.ts +713 -0
- package/src/vscode/webview/mount.ts +13 -1
- package/dist/chunk-5JSZ4VGA.js.map +0 -1
- package/dist/chunk-6LQOFACK.js.map +0 -1
- package/dist/keymap-CObQE6q6.d.cts +0 -151
- package/dist/keymap-DORzHnK-.d.ts +0 -151
- package/dist/types-B6NfkiUG.d.cts +0 -66
- package/dist/types-B6NfkiUG.d.ts +0 -66
- /package/dist/{chunk-RJRFR6N3.js.map → chunk-KYDG5DMX.js.map} +0 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { TOOLTIP_DELAY_MS, type Appearance, type ShellHost, type Size } from "../core/index.js";
|
|
2
|
+
import { harnessHookProblems } from "./checks.js";
|
|
3
|
+
import { menuEntriesProbe, shapesProbe, type MenuEntryFacts, type ShapeFacts } from "./facts.js";
|
|
4
|
+
import type {
|
|
5
|
+
AbsentFeature,
|
|
6
|
+
OpenContext,
|
|
7
|
+
ShellCaseOutcome,
|
|
8
|
+
ShellConformanceHarness,
|
|
9
|
+
ShellSkip,
|
|
10
|
+
ShowHook,
|
|
11
|
+
ShowHooks,
|
|
12
|
+
} from "./harness.js";
|
|
13
|
+
import type { PageLike } from "./page.js";
|
|
14
|
+
import { findTarget, inPage, settle, type ChromeTarget, type TargetFacts } from "./page-tools.js";
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* What every case shares: opening the harness, failing and finishing, skips, polling and the
|
|
18
|
+
* pointer and menu moves the cases make.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** Wide, narrow and very narrow (guide §5.15); 900 is above, 700 below the low breakpoint. */
|
|
22
|
+
export const DEFAULT_SIZES: readonly Size[] = [
|
|
23
|
+
{ width: 1280, height: 900 },
|
|
24
|
+
{ width: 720, height: 700 },
|
|
25
|
+
{ width: 420, height: 700 },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export const LIGHT: Appearance = { theme: "light" };
|
|
29
|
+
|
|
30
|
+
/** Shown in this order, so the legend is measured without and then with the pill. */
|
|
31
|
+
export const SHOWN_ISLANDS = ["legend", "problems", "status", "inspector"] as const satisfies ReadonlyArray<
|
|
32
|
+
keyof ShowHooks
|
|
33
|
+
>;
|
|
34
|
+
|
|
35
|
+
export const POLL_MS = 50;
|
|
36
|
+
|
|
37
|
+
/** Room for the chrome tooltip's own fade after its 300ms hover delay. */
|
|
38
|
+
export const HOVER_MARGIN_MS = 200;
|
|
39
|
+
|
|
40
|
+
/** A spot without chrome: the root's top-left corner lies outside every island (12px edge). */
|
|
41
|
+
export const PARK = { x: 1, y: 1 } as const;
|
|
42
|
+
|
|
43
|
+
export function sizeLabel(size: Size): string {
|
|
44
|
+
return `${size.width} × ${size.height}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function fail(caseName: string, host: ShellHost, problems: readonly string[]): never {
|
|
48
|
+
const unique = [...new Set(problems)];
|
|
49
|
+
throw new Error(
|
|
50
|
+
`@miragon/modeler-shell conformance: ${caseName} (${host} host)\n${unique.map((problem) => `- ${problem}`).join("\n")}`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function finish(
|
|
55
|
+
caseName: string,
|
|
56
|
+
host: ShellHost,
|
|
57
|
+
problems: readonly string[],
|
|
58
|
+
skipped: Iterable<ShellSkip>,
|
|
59
|
+
): ShellCaseOutcome | void {
|
|
60
|
+
if (problems.length > 0) fail(caseName, host, problems);
|
|
61
|
+
const seen = new Set<string>();
|
|
62
|
+
const unique = [...skipped].filter((skip) => !seen.has(skip.reason) && seen.add(skip.reason));
|
|
63
|
+
return unique.length > 0 ? { skipped: unique } : undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** A harness that gives a hook it also declares absent contradicts itself: fail before opening anything. */
|
|
67
|
+
export function assertHooksConsistent<P extends PageLike>(
|
|
68
|
+
caseName: string,
|
|
69
|
+
harness: ShellConformanceHarness<P>,
|
|
70
|
+
hooks: readonly AbsentFeature[],
|
|
71
|
+
): void {
|
|
72
|
+
const problems = harnessHookProblems(harness, hooks);
|
|
73
|
+
if (problems.length > 0) fail(caseName, harness.host, problems);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A hook that is neither given nor declared absent. */
|
|
77
|
+
export function unfed<P extends PageLike>(harness: ShellConformanceHarness<P>, hook: ShowHook): boolean {
|
|
78
|
+
return !harness.show?.[hook] && !harness.absent?.includes(hook);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Opens the harness. In the web host the system's mode and contrast come first, through
|
|
83
|
+
* `page.emulateMedia` where the page has it (API.md, `./testing`): the page's head script reads
|
|
84
|
+
* them before the first paint.
|
|
85
|
+
*/
|
|
86
|
+
export async function openHarness<P extends PageLike>(
|
|
87
|
+
page: P,
|
|
88
|
+
harness: ShellConformanceHarness<P>,
|
|
89
|
+
context: OpenContext,
|
|
90
|
+
): Promise<void> {
|
|
91
|
+
if (harness.host === "web" && page.emulateMedia) {
|
|
92
|
+
await page.emulateMedia({
|
|
93
|
+
colorScheme: context.requested.theme,
|
|
94
|
+
contrast: context.requested.kontrast === "hoch" ? "more" : "no-preference",
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
await harness.open(page, context);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The web column's hooks a case of `host` needs, each with what it would have verified: the start
|
|
102
|
+
* screen in the web app, toasts outside the IDE host.
|
|
103
|
+
*/
|
|
104
|
+
export function webHookSkips<P extends PageLike>(
|
|
105
|
+
harness: ShellConformanceHarness<P>,
|
|
106
|
+
hooks: Partial<Record<"toast" | "emptyState", string>>,
|
|
107
|
+
): ShellSkip[] {
|
|
108
|
+
return (Object.entries(hooks) as Array<["toast" | "emptyState", string]>)
|
|
109
|
+
.filter(([hook]) => (hook === "toast" ? harness.host !== "ide" : harness.host === "web"))
|
|
110
|
+
.filter(([hook]) => unfed(harness, hook))
|
|
111
|
+
.map(([hook, what]) => ({ hook, reason: `show.${hook} is undefined: ${what} not verified` }));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Reads until `done` holds or `timeoutMs` passed; the last value either way. */
|
|
115
|
+
export async function pollUntil<T>(
|
|
116
|
+
page: PageLike,
|
|
117
|
+
read: () => Promise<T>,
|
|
118
|
+
done: (value: T) => boolean,
|
|
119
|
+
timeoutMs: number,
|
|
120
|
+
): Promise<T> {
|
|
121
|
+
let value = await read();
|
|
122
|
+
for (let waited = 0; !done(value) && waited < timeoutMs; waited += POLL_MS) {
|
|
123
|
+
await page.waitForTimeout(POLL_MS);
|
|
124
|
+
value = await read();
|
|
125
|
+
}
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Hovers `target` long enough for its chrome tooltip; the hovered control, `null` when there is none. */
|
|
130
|
+
export async function hover(page: PageLike, root: string, target: ChromeTarget): Promise<TargetFacts | null> {
|
|
131
|
+
const anchor = await findTarget(page, root, target);
|
|
132
|
+
if (!anchor) return null;
|
|
133
|
+
await page.mouse.move(PARK.x, PARK.y);
|
|
134
|
+
await page.mouse.move(anchor.client.x, anchor.client.y);
|
|
135
|
+
await page.waitForTimeout(TOOLTIP_DELAY_MS + HOVER_MARGIN_MS);
|
|
136
|
+
await settle(page);
|
|
137
|
+
return anchor;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function openMenuByClick(page: PageLike, root: string): Promise<boolean> {
|
|
141
|
+
const trigger = await findTarget(page, root, "menu");
|
|
142
|
+
if (!trigger) return false;
|
|
143
|
+
await page.mouse.click(trigger.client.x, trigger.client.y);
|
|
144
|
+
await settle(page);
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function closeByEscape(page: PageLike): Promise<void> {
|
|
149
|
+
await page.keyboard.press("Escape");
|
|
150
|
+
await settle(page);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** The open menu's entries, opened by a click and closed again by Escape. */
|
|
154
|
+
export async function menuEntries(page: PageLike, root: string): Promise<MenuEntryFacts[]> {
|
|
155
|
+
if (!(await openMenuByClick(page, root))) return [];
|
|
156
|
+
const entries = (await inPage(page, menuEntriesProbe, { root })) ?? [];
|
|
157
|
+
await closeByEscape(page);
|
|
158
|
+
return entries;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** The visible shapes on the canvas. */
|
|
162
|
+
export function shapes(page: PageLike, root: string): Promise<ShapeFacts[]> {
|
|
163
|
+
return inPage(page, shapesProbe, { root });
|
|
164
|
+
}
|