@allurereport/web-commons 3.8.2 → 3.10.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/dist/attachments.d.ts +5 -2
- package/dist/attachments.js +11 -2
- package/dist/charts/colors.js +5 -5
- package/dist/charts/utils.js +5 -1
- package/dist/hotkeys/createHotkeyController.d.ts +12 -0
- package/dist/hotkeys/createHotkeyController.js +74 -0
- package/dist/hotkeys/formatHotkey.d.ts +2 -0
- package/dist/hotkeys/formatHotkey.js +31 -0
- package/dist/hotkeys/index.d.ts +5 -0
- package/dist/hotkeys/index.js +5 -0
- package/dist/hotkeys/isEditableTarget.d.ts +1 -0
- package/dist/hotkeys/isEditableTarget.js +10 -0
- package/dist/hotkeys/matchHotkey.d.ts +2 -0
- package/dist/hotkeys/matchHotkey.js +41 -0
- package/dist/hotkeys/types.d.ts +19 -0
- package/dist/hotkeys/types.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/linkify.d.ts +10 -0
- package/dist/linkify.js +32 -0
- package/dist/prose.d.ts +1 -1
- package/dist/prose.js +26 -26
- package/dist/subtreeExpansion.d.ts +15 -0
- package/dist/subtreeExpansion.js +30 -0
- package/dist/treeNavigation/descendants.d.ts +3 -0
- package/dist/treeNavigation/descendants.js +17 -0
- package/dist/treeNavigation/flattenVisibleTree.d.ts +2 -0
- package/dist/treeNavigation/flattenVisibleTree.js +104 -0
- package/dist/treeNavigation/index.d.ts +5 -0
- package/dist/treeNavigation/index.js +5 -0
- package/dist/treeNavigation/moveFocus.d.ts +2 -0
- package/dist/treeNavigation/moveFocus.js +110 -0
- package/dist/treeNavigation/scrollTreeFocus.d.ts +14 -0
- package/dist/treeNavigation/scrollTreeFocus.js +65 -0
- package/dist/treeNavigation/types.d.ts +42 -0
- package/dist/treeNavigation/types.js +1 -0
- package/dist/treeSubtreeToggle.d.ts +4 -0
- package/dist/treeSubtreeToggle.js +21 -0
- package/package.json +11 -11
package/dist/attachments.d.ts
CHANGED
|
@@ -17,7 +17,10 @@ type AttachmentImageDiffData = {
|
|
|
17
17
|
diff?: string;
|
|
18
18
|
};
|
|
19
19
|
};
|
|
20
|
-
|
|
20
|
+
type AttachmentHttpData = {
|
|
21
|
+
http: unknown;
|
|
22
|
+
};
|
|
23
|
+
export type AttachmentData = AttachmentImageData | AttachmentTextData | AttachmentVideoData | AttachmentImageDiffData | AttachmentHttpData;
|
|
21
24
|
type AttachmentPayload = {
|
|
22
25
|
id?: string;
|
|
23
26
|
ext?: string;
|
|
@@ -28,7 +31,7 @@ export declare const fetchAttachment: (id: string, ext: string, contentType?: st
|
|
|
28
31
|
export declare const blobAttachment: (id: string, ext: string, contentType: string) => Promise<Blob>;
|
|
29
32
|
export declare const downloadAttachment: (id: string, ext: string, contentType: string) => Promise<void>;
|
|
30
33
|
export declare const openAttachmentInNewTab: (id: string, ext: string, contentType: string) => Promise<void>;
|
|
31
|
-
export type AttachmentType = "css" | "json" | "image" | "svg" | "code" | "text" | "html" | "table" | "video" | "uri" | "archive" | "image-diff";
|
|
34
|
+
export type AttachmentType = "css" | "json" | "image" | "svg" | "code" | "text" | "markdown" | "html" | "table" | "video" | "uri" | "archive" | "image-diff" | "http";
|
|
32
35
|
export declare const PREVIEWABLE_CONTENT_TYPES: readonly ["text/html", "text/csv", "text/markdown", "text/tab-separated-values", "text/uri-list"];
|
|
33
36
|
export declare const isPreviewableContentType: (type?: string) => boolean;
|
|
34
37
|
export declare const extname: (str: string) => string | undefined;
|
package/dist/attachments.js
CHANGED
|
@@ -22,6 +22,7 @@ export const fetchAttachment = async (id, ext, contentType) => {
|
|
|
22
22
|
case "uri":
|
|
23
23
|
case "code":
|
|
24
24
|
case "html":
|
|
25
|
+
case "markdown":
|
|
25
26
|
case "table":
|
|
26
27
|
case "text": {
|
|
27
28
|
const text = await response.text();
|
|
@@ -36,6 +37,10 @@ export const fetchAttachment = async (id, ext, contentType) => {
|
|
|
36
37
|
const json = await response.json();
|
|
37
38
|
return { diff: json };
|
|
38
39
|
}
|
|
40
|
+
case "http": {
|
|
41
|
+
const json = await response.json();
|
|
42
|
+
return { http: json };
|
|
43
|
+
}
|
|
39
44
|
default:
|
|
40
45
|
return null;
|
|
41
46
|
}
|
|
@@ -181,7 +186,8 @@ export const isSyntaxHighlightSupported = (payload) => {
|
|
|
181
186
|
return !!ext && HIGHLIGHT_EXTS.has(ext);
|
|
182
187
|
};
|
|
183
188
|
export const attachmentType = (type) => {
|
|
184
|
-
|
|
189
|
+
const normalizedType = type?.split(";")[0].trim().toLowerCase();
|
|
190
|
+
switch (normalizedType) {
|
|
185
191
|
case "image/bmp":
|
|
186
192
|
case "image/gif":
|
|
187
193
|
case "image/tiff":
|
|
@@ -221,9 +227,10 @@ export const attachmentType = (type) => {
|
|
|
221
227
|
case "application/json":
|
|
222
228
|
return "code";
|
|
223
229
|
case "text/plain":
|
|
224
|
-
case "text/markdown":
|
|
225
230
|
case "text/*":
|
|
226
231
|
return "text";
|
|
232
|
+
case "text/markdown":
|
|
233
|
+
return "markdown";
|
|
227
234
|
case "text/html":
|
|
228
235
|
return "html";
|
|
229
236
|
case "text/csv":
|
|
@@ -245,6 +252,8 @@ export const attachmentType = (type) => {
|
|
|
245
252
|
return "archive";
|
|
246
253
|
case "application/vnd.allure.image.diff":
|
|
247
254
|
return "image-diff";
|
|
255
|
+
case "application/vnd.allure.http+json":
|
|
256
|
+
return "http";
|
|
248
257
|
default:
|
|
249
258
|
return null;
|
|
250
259
|
}
|
package/dist/charts/colors.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export const statusColors = {
|
|
2
|
-
failed: "var(--
|
|
3
|
-
broken: "var(--
|
|
4
|
-
passed: "var(--
|
|
5
|
-
skipped: "var(--
|
|
6
|
-
unknown: "var(--
|
|
2
|
+
failed: "var(--color-status-failed-chart-fill)",
|
|
3
|
+
broken: "var(--color-status-broken-chart-fill)",
|
|
4
|
+
passed: "var(--color-status-passed-chart-fill)",
|
|
5
|
+
skipped: "var(--color-status-skipped-chart-fill)",
|
|
6
|
+
unknown: "var(--color-status-unknown-chart-fill)",
|
|
7
7
|
};
|
|
8
8
|
export const resolveCSSVarColor = (value, el = document.documentElement) => {
|
|
9
9
|
if (value.startsWith("var(")) {
|
package/dist/charts/utils.js
CHANGED
|
@@ -49,7 +49,11 @@ export const createCoverageDiffTreeMapChartData = (chartId, res) => {
|
|
|
49
49
|
return createTreeMapChartDataGeneric(() => res[chartId], (value, domain = chartColorDomain) => {
|
|
50
50
|
const scaledRgb = scaleLinear()
|
|
51
51
|
.domain(domain)
|
|
52
|
-
.range([
|
|
52
|
+
.range([
|
|
53
|
+
resolveCSSVarColor(statusColors.failed),
|
|
54
|
+
resolveCSSVarColor("var(--color-dashboard-neutral)"),
|
|
55
|
+
resolveCSSVarColor(statusColors.passed),
|
|
56
|
+
])
|
|
53
57
|
.interpolate(interpolateRgb)
|
|
54
58
|
.clamp(true);
|
|
55
59
|
return scaledRgb(value);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HotkeyBinding, HotkeyScope } from "./types.js";
|
|
2
|
+
export type HotkeyControllerOptions = {
|
|
3
|
+
getActiveScope: () => HotkeyScope;
|
|
4
|
+
getEnabled: () => boolean;
|
|
5
|
+
isScopeActive?: (scope: HotkeyScope) => boolean;
|
|
6
|
+
bindings: HotkeyBinding[];
|
|
7
|
+
shouldSuppressDefault?: (event: KeyboardEvent, activeScope: HotkeyScope) => boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare const createHotkeyController: (options: HotkeyControllerOptions) => {
|
|
10
|
+
attach: () => void;
|
|
11
|
+
detach: () => void;
|
|
12
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { isEditableTarget } from "./isEditableTarget.js";
|
|
2
|
+
import { matchHotkey } from "./matchHotkey.js";
|
|
3
|
+
const getBindingScopes = (binding) => Array.isArray(binding.scope) ? [...binding.scope] : [binding.scope];
|
|
4
|
+
const bindingMatchesScope = (binding, activeScope) => {
|
|
5
|
+
const scopes = getBindingScopes(binding);
|
|
6
|
+
if (scopes.includes("global")) {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
return scopes.includes(activeScope);
|
|
10
|
+
};
|
|
11
|
+
const runBinding = (event, binding, activeScope) => {
|
|
12
|
+
if (!bindingMatchesScope(binding, activeScope)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
if (!matchHotkey(event, binding)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (binding.preventDefault !== false) {
|
|
19
|
+
event.preventDefault();
|
|
20
|
+
}
|
|
21
|
+
if (binding.stopPropagation) {
|
|
22
|
+
event.stopPropagation();
|
|
23
|
+
}
|
|
24
|
+
binding.handler(event);
|
|
25
|
+
return true;
|
|
26
|
+
};
|
|
27
|
+
export const createHotkeyController = (options) => {
|
|
28
|
+
const handleKeyDown = (event) => {
|
|
29
|
+
if (!options.getEnabled()) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (event.isComposing) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const activeScope = options.getActiveScope();
|
|
36
|
+
if (isEditableTarget(event.target)) {
|
|
37
|
+
const editableBindings = options.bindings.filter((binding) => binding.allowInEditable);
|
|
38
|
+
const scopedEditable = editableBindings.filter((binding) => {
|
|
39
|
+
const scopes = getBindingScopes(binding);
|
|
40
|
+
return scopes.includes(activeScope) && !scopes.every((scope) => scope === "global");
|
|
41
|
+
});
|
|
42
|
+
const globalEditable = editableBindings.filter((binding) => getBindingScopes(binding).includes("global"));
|
|
43
|
+
for (const binding of [...scopedEditable, ...globalEditable]) {
|
|
44
|
+
if (runBinding(event, binding, activeScope)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const scopeActive = options.isScopeActive?.(activeScope) ?? true;
|
|
51
|
+
if (options.shouldSuppressDefault?.(event, activeScope) && scopeActive) {
|
|
52
|
+
event.preventDefault();
|
|
53
|
+
}
|
|
54
|
+
const scopedBindings = scopeActive
|
|
55
|
+
? options.bindings.filter((binding) => {
|
|
56
|
+
const scopes = getBindingScopes(binding);
|
|
57
|
+
return scopes.includes(activeScope) && !scopes.every((scope) => scope === "global");
|
|
58
|
+
})
|
|
59
|
+
: [];
|
|
60
|
+
const globalBindings = options.bindings.filter((binding) => getBindingScopes(binding).includes("global"));
|
|
61
|
+
for (const binding of [...scopedBindings, ...globalBindings]) {
|
|
62
|
+
if (runBinding(event, binding, activeScope)) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const attach = () => {
|
|
68
|
+
document.addEventListener("keydown", handleKeyDown, { capture: true });
|
|
69
|
+
};
|
|
70
|
+
const detach = () => {
|
|
71
|
+
document.removeEventListener("keydown", handleKeyDown, { capture: true });
|
|
72
|
+
};
|
|
73
|
+
return { attach, detach };
|
|
74
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const isMacOs = () => {
|
|
2
|
+
if (typeof document === "undefined") {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
return document.documentElement.getAttribute("data-os") === "mac";
|
|
6
|
+
};
|
|
7
|
+
const formatModifier = (key, macSymbol, winLabel) => {
|
|
8
|
+
return isMacOs() ? macSymbol : winLabel;
|
|
9
|
+
};
|
|
10
|
+
export const formatHotkey = (binding) => {
|
|
11
|
+
const modifiers = binding.modifiers ?? {};
|
|
12
|
+
const parts = [];
|
|
13
|
+
if (modifiers.meta) {
|
|
14
|
+
parts.push(formatModifier("meta", "⌘", "Ctrl"));
|
|
15
|
+
}
|
|
16
|
+
if (modifiers.ctrlOrMeta) {
|
|
17
|
+
parts.push(formatModifier("ctrlOrMeta", "⌘", "Ctrl"));
|
|
18
|
+
}
|
|
19
|
+
else if (modifiers.ctrl) {
|
|
20
|
+
parts.push(formatModifier("ctrl", "⌃", "Ctrl"));
|
|
21
|
+
}
|
|
22
|
+
if (modifiers.alt) {
|
|
23
|
+
parts.push(formatModifier("alt", "⌥", "Alt"));
|
|
24
|
+
}
|
|
25
|
+
if (modifiers.shift) {
|
|
26
|
+
parts.push(formatModifier("shift", "⇧", "Shift"));
|
|
27
|
+
}
|
|
28
|
+
const keyLabel = binding.key === " " ? "Space" : binding.key.length === 1 ? binding.key.toUpperCase() : binding.key;
|
|
29
|
+
parts.push(keyLabel);
|
|
30
|
+
return parts.join(isMacOs() ? "" : " + ");
|
|
31
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const isEditableTarget: (target: EventTarget | null) => boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const EDITABLE_SELECTOR = "input, textarea, select, [contenteditable=''], [contenteditable='true']";
|
|
2
|
+
export const isEditableTarget = (target) => {
|
|
3
|
+
if (!(target instanceof HTMLElement)) {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
if (target.isContentEditable) {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
return Boolean(target.closest(EDITABLE_SELECTOR));
|
|
10
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const normalizeKey = (key) => {
|
|
2
|
+
if (key === " ") {
|
|
3
|
+
return " ";
|
|
4
|
+
}
|
|
5
|
+
if (key.length === 1) {
|
|
6
|
+
return key.toLowerCase();
|
|
7
|
+
}
|
|
8
|
+
return key;
|
|
9
|
+
};
|
|
10
|
+
const eventKey = (event) => normalizeKey(event.key);
|
|
11
|
+
export const matchHotkey = (event, binding) => {
|
|
12
|
+
const modifiers = binding.modifiers ?? {};
|
|
13
|
+
const expectsCtrl = Boolean(modifiers.ctrl);
|
|
14
|
+
const expectsShift = Boolean(modifiers.shift);
|
|
15
|
+
const expectsAlt = Boolean(modifiers.alt);
|
|
16
|
+
const expectsMeta = Boolean(modifiers.meta);
|
|
17
|
+
const expectsCtrlOrMeta = Boolean(modifiers.ctrlOrMeta);
|
|
18
|
+
if (expectsCtrlOrMeta) {
|
|
19
|
+
if (!event.ctrlKey && !event.metaKey) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
if (event.ctrlKey !== expectsCtrl) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (event.metaKey !== expectsMeta) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (event.shiftKey !== expectsShift) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (event.altKey !== expectsAlt) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if (binding.code) {
|
|
38
|
+
return event.code === binding.code;
|
|
39
|
+
}
|
|
40
|
+
return eventKey(event) === normalizeKey(binding.key);
|
|
41
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type HotkeyScope = "global" | "tree" | "testResult";
|
|
2
|
+
export type HotkeyModifiers = {
|
|
3
|
+
ctrl?: boolean;
|
|
4
|
+
shift?: boolean;
|
|
5
|
+
alt?: boolean;
|
|
6
|
+
meta?: boolean;
|
|
7
|
+
ctrlOrMeta?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type HotkeyBinding = {
|
|
10
|
+
id: string;
|
|
11
|
+
scope: HotkeyScope | readonly HotkeyScope[];
|
|
12
|
+
key: string;
|
|
13
|
+
code?: string;
|
|
14
|
+
modifiers?: HotkeyModifiers;
|
|
15
|
+
handler: (event: KeyboardEvent) => void;
|
|
16
|
+
preventDefault?: boolean;
|
|
17
|
+
stopPropagation?: boolean;
|
|
18
|
+
allowInEditable?: boolean;
|
|
19
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -13,4 +13,8 @@ export * from "./stores/loadableStore/index.js";
|
|
|
13
13
|
export * from "./stores/persister/index.js";
|
|
14
14
|
export * from "./utils.js";
|
|
15
15
|
export * from "./prose.js";
|
|
16
|
+
export * from "./linkify.js";
|
|
16
17
|
export * from "./treeSubtreeToggle.js";
|
|
18
|
+
export * from "./subtreeExpansion.js";
|
|
19
|
+
export * from "./hotkeys/index.js";
|
|
20
|
+
export * from "./treeNavigation/index.js";
|
package/dist/index.js
CHANGED
|
@@ -13,4 +13,8 @@ export * from "./stores/loadableStore/index.js";
|
|
|
13
13
|
export * from "./stores/persister/index.js";
|
|
14
14
|
export * from "./utils.js";
|
|
15
15
|
export * from "./prose.js";
|
|
16
|
+
export * from "./linkify.js";
|
|
16
17
|
export * from "./treeSubtreeToggle.js";
|
|
18
|
+
export * from "./subtreeExpansion.js";
|
|
19
|
+
export * from "./hotkeys/index.js";
|
|
20
|
+
export * from "./treeNavigation/index.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const URL_IN_TEXT_RE: RegExp;
|
|
2
|
+
export type TextSegment = {
|
|
3
|
+
type: "text";
|
|
4
|
+
value: string;
|
|
5
|
+
} | {
|
|
6
|
+
type: "url";
|
|
7
|
+
value: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const splitTextWithUrls: (text: string) => TextSegment[];
|
|
10
|
+
export declare const textContainsUrl: (text: string) => boolean;
|
package/dist/linkify.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const URL_IN_TEXT_RE = /((?:(?:https?:\/\/|ftp:\/\/|mailto:)|www\.)\S+?)(\s|"|'|\)|]|}|>|$)/g;
|
|
2
|
+
export const splitTextWithUrls = (text) => {
|
|
3
|
+
if (!text) {
|
|
4
|
+
return [{ type: "text", value: "" }];
|
|
5
|
+
}
|
|
6
|
+
const segments = [];
|
|
7
|
+
const re = new RegExp(URL_IN_TEXT_RE.source, URL_IN_TEXT_RE.flags);
|
|
8
|
+
let lastIndex = 0;
|
|
9
|
+
let match;
|
|
10
|
+
while ((match = re.exec(text)) !== null) {
|
|
11
|
+
const matchIndex = match.index;
|
|
12
|
+
const url = match[1];
|
|
13
|
+
const terminator = match[2] ?? "";
|
|
14
|
+
if (matchIndex > lastIndex) {
|
|
15
|
+
segments.push({ type: "text", value: text.slice(lastIndex, matchIndex) });
|
|
16
|
+
}
|
|
17
|
+
segments.push({ type: "url", value: url });
|
|
18
|
+
lastIndex = matchIndex + url.length + terminator.length;
|
|
19
|
+
}
|
|
20
|
+
if (lastIndex < text.length) {
|
|
21
|
+
segments.push({ type: "text", value: text.slice(lastIndex) });
|
|
22
|
+
}
|
|
23
|
+
if (segments.length === 0) {
|
|
24
|
+
return [{ type: "text", value: text }];
|
|
25
|
+
}
|
|
26
|
+
return segments;
|
|
27
|
+
};
|
|
28
|
+
export const textContainsUrl = (text) => {
|
|
29
|
+
const re = new RegExp(URL_IN_TEXT_RE.source, URL_IN_TEXT_RE.flags);
|
|
30
|
+
re.lastIndex = 0;
|
|
31
|
+
return re.test(text);
|
|
32
|
+
};
|
package/dist/prose.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const proseStyles = "\nhtml, body {\n margin: 0;\n padding: 0;\n overflow-x: hidden;\n overflow-y: hidden;\n background: var(--bg-
|
|
1
|
+
export declare const proseStyles = "\nhtml, body {\n margin: 0;\n padding: 0;\n overflow-x: hidden;\n overflow-y: hidden;\n background: var(--color-bg-primary);\n}\n\nbody {\n color: var(--color-text-primary);\n font-family: var(--font-family);\n font-size: var(--font-size-m);\n line-height: var(--line-height-m);\n}\n\np {\n margin-top: 0;\n margin-bottom: 10px;\n}\n\np:last-child {\n margin-bottom: 0;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n font-weight: var(--font-weight-bold) !important;\n line-height: var(--line-height-l) !important;\n margin: 16px 0 10px;\n}\n\nh1:first-child,\nh2:first-child,\nh3:first-child,\nh4:first-child,\nh5:first-child,\nh6:first-child {\n margin-top: 0;\n}\n\nh1 {\n font-size: var(--font-size-xl) !important;\n}\n\nh2,\nh3 {\n font-size: var(--font-size-l) !important;\n}\n\nh4 {\n font-size: var(--font-size-m) !important;\n}\n\nh5,\nh6 {\n font-size: var(--font-size-s) !important;\n}\n\nh6 {\n color: var(--color-text-secondary);\n}\n\nstrong,\nb {\n font-weight: var(--font-weight-bold) !important;\n}\n\nem,\ni {\n font-style: italic !important;\n}\n\ndel,\ns {\n text-decoration: line-through;\n}\n\na {\n color: var(--color-text-primary);\n text-decoration: underline;\n}\n\na:hover {\n color: var(--color-text-secondary);\n}\n\ncode {\n font-family: var(--font-family-mono) !important;\n font-size: var(--font-size-m-code) !important;\n padding: 0.2em 0.4em;\n background: var(--color-control-bg);\n border-radius: 4px;\n}\n\npre {\n font-family: var(--font-family-mono) !important;\n font-size: var(--font-size-s) !important;\n line-height: 1.45 !important;\n padding: 16px;\n overflow: auto;\n background: var(--color-control-bg);\n border-radius: 6px;\n margin-bottom: 16px;\n}\n\npre code {\n padding: 0;\n background: transparent;\n font-size: inherit !important;\n border-radius: 0;\n}\n\nblockquote {\n padding: 0 1em;\n border-left: 0.25em solid var(--color-border-default);\n color: var(--color-text-secondary);\n margin-bottom: 16px;\n}\n\nul,\nol {\n padding-left: 2em;\n margin-bottom: 10px;\n}\n\nul {\n list-style: disc !important;\n}\n\nol {\n list-style: decimal !important;\n}\n\nli + li {\n margin-top: 0.25em;\n}\n\nhr {\n height: 0.25em;\n padding: 0;\n margin: 24px 0;\n background-color: var(--color-border-default);\n border: 0 !important;\n}\n\ntable {\n border-spacing: 0;\n border-collapse: collapse;\n margin-bottom: 16px;\n display: block;\n width: max-content;\n max-width: 100%;\n overflow-x: auto;\n overflow-y: hidden;\n transform: translateZ(0);\n contain: layout paint;\n}\n\nth,\ntd {\n padding: 6px 13px;\n border: 1px solid var(--color-border-default) !important;\n white-space: nowrap;\n}\n\nth {\n font-weight: var(--font-weight-bold) !important;\n position: sticky;\n top: 0;\n z-index: 1;\n background: var(--color-bg-primary);\n}\n\ntr:nth-child(2n) {\n background: var(--color-control-bg);\n}\n\nimg {\n max-width: 100% !important;\n height: auto !important;\n display: inline-block !important;\n vertical-align: middle !important;\n}\n\nabbr,\nabbr[title] {\n text-decoration: underline dotted;\n cursor: help;\n border-bottom: none;\n}\n\nkbd {\n display: inline-block;\n padding: 3px 5px;\n font-family: var(--font-family-mono) !important;\n font-size: 11px;\n line-height: 10px;\n color: var(--color-text-primary);\n vertical-align: middle;\n background-color: var(--color-control-bg);\n border: solid 1px var(--color-border-medium);\n border-bottom-color: var(--color-border-medium);\n border-radius: 6px;\n box-shadow: inset 0 -1px 0 var(--color-border-medium);\n}\n\nsamp {\n font-family: var(--font-family-mono) !important;\n font-size: 85% !important;\n}\n\nvar {\n font-family: var(--font-family-mono) !important;\n font-style: italic !important;\n font-weight: var(--font-weight-bold) !important;\n}\n\nmark {\n background-color: var(--color-intent-primary-bg);\n color: var(--color-intent-primary-on-bg);\n padding: 0.1em 0.2em;\n border-radius: 2px;\n}\n\nsmall {\n font-size: 85% !important;\n}\n\nsub,\nsup {\n font-size: 75% !important;\n line-height: 0 !important;\n position: relative !important;\n vertical-align: baseline !important;\n}\n\nsub {\n bottom: -0.25em !important;\n}\n\nsup {\n top: -0.5em !important;\n}\n\nins {\n text-decoration: underline;\n background-color: var(--color-intent-success-bg-subtle);\n color: var(--color-text-primary);\n text-decoration-color: var(--color-intent-success-text);\n}\n\ndfn {\n font-style: italic !important;\n font-weight: var(--font-weight-bold) !important;\n}\n\ncite {\n font-style: italic !important;\n}\n\nq {\n font-style: italic !important;\n}\n\nq::before {\n content: open-quote;\n}\n\nq::after {\n content: close-quote;\n}\n\ntime {\n font-variant-numeric: tabular-nums;\n}\n\ndl {\n padding: 0;\n margin-bottom: 16px;\n}\n\ndt {\n padding: 0;\n margin-top: 16px;\n font-size: 1em;\n font-style: italic;\n font-weight: var(--font-weight-bold) !important;\n}\n\ndt:first-child {\n margin-top: 0;\n}\n\ndd {\n padding: 0 0 0 16px;\n margin-left: 0;\n margin-bottom: 16px;\n}\n\nfigure {\n margin: 16px 0;\n display: block;\n}\n\nfigcaption {\n margin-top: 8px;\n font-size: var(--font-size-s) !important;\n color: var(--color-text-secondary);\n font-style: italic;\n text-align: center;\n}\n\ndetails {\n display: block;\n margin-bottom: 16px;\n}\n\nsummary {\n display: list-item;\n cursor: pointer;\n font-weight: var(--font-weight-bold) !important;\n margin-bottom: 8px;\n}\n\nsummary:hover {\n color: var(--color-text-secondary);\n}\n\nsummary::marker {\n color: var(--color-text-secondary);\n}\n\ndetails[open] summary {\n margin-bottom: 16px;\n}\n\narticle,\nsection,\naside,\nnav {\n display: block;\n margin-bottom: 16px;\n}\n\nheader,\nfooter {\n display: block;\n margin-bottom: 16px;\n}\n\naddress {\n display: block;\n font-style: italic;\n margin-bottom: 16px;\n}\n";
|
|
2
2
|
export declare const resolveCssVarDeclarations: (cssText: string) => string;
|
package/dist/prose.js
CHANGED
|
@@ -4,11 +4,11 @@ html, body {
|
|
|
4
4
|
padding: 0;
|
|
5
5
|
overflow-x: hidden;
|
|
6
6
|
overflow-y: hidden;
|
|
7
|
-
background: var(--bg-
|
|
7
|
+
background: var(--color-bg-primary);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
body {
|
|
11
|
-
color: var(--
|
|
11
|
+
color: var(--color-text-primary);
|
|
12
12
|
font-family: var(--font-family);
|
|
13
13
|
font-size: var(--font-size-m);
|
|
14
14
|
line-height: var(--line-height-m);
|
|
@@ -62,7 +62,7 @@ h6 {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
h6 {
|
|
65
|
-
color: var(--
|
|
65
|
+
color: var(--color-text-secondary);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
strong,
|
|
@@ -81,19 +81,19 @@ s {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
a {
|
|
84
|
-
color: var(--
|
|
84
|
+
color: var(--color-text-primary);
|
|
85
85
|
text-decoration: underline;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
a:hover {
|
|
89
|
-
color: var(--
|
|
89
|
+
color: var(--color-text-secondary);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
code {
|
|
93
93
|
font-family: var(--font-family-mono) !important;
|
|
94
94
|
font-size: var(--font-size-m-code) !important;
|
|
95
95
|
padding: 0.2em 0.4em;
|
|
96
|
-
background: var(--
|
|
96
|
+
background: var(--color-control-bg);
|
|
97
97
|
border-radius: 4px;
|
|
98
98
|
}
|
|
99
99
|
|
|
@@ -103,7 +103,7 @@ pre {
|
|
|
103
103
|
line-height: 1.45 !important;
|
|
104
104
|
padding: 16px;
|
|
105
105
|
overflow: auto;
|
|
106
|
-
background: var(--
|
|
106
|
+
background: var(--color-control-bg);
|
|
107
107
|
border-radius: 6px;
|
|
108
108
|
margin-bottom: 16px;
|
|
109
109
|
}
|
|
@@ -117,8 +117,8 @@ pre code {
|
|
|
117
117
|
|
|
118
118
|
blockquote {
|
|
119
119
|
padding: 0 1em;
|
|
120
|
-
border-left: 0.25em solid var(--
|
|
121
|
-
color: var(--
|
|
120
|
+
border-left: 0.25em solid var(--color-border-default);
|
|
121
|
+
color: var(--color-text-secondary);
|
|
122
122
|
margin-bottom: 16px;
|
|
123
123
|
}
|
|
124
124
|
|
|
@@ -144,7 +144,7 @@ hr {
|
|
|
144
144
|
height: 0.25em;
|
|
145
145
|
padding: 0;
|
|
146
146
|
margin: 24px 0;
|
|
147
|
-
background-color: var(--
|
|
147
|
+
background-color: var(--color-border-default);
|
|
148
148
|
border: 0 !important;
|
|
149
149
|
}
|
|
150
150
|
|
|
@@ -164,7 +164,7 @@ table {
|
|
|
164
164
|
th,
|
|
165
165
|
td {
|
|
166
166
|
padding: 6px 13px;
|
|
167
|
-
border: 1px solid var(--
|
|
167
|
+
border: 1px solid var(--color-border-default) !important;
|
|
168
168
|
white-space: nowrap;
|
|
169
169
|
}
|
|
170
170
|
|
|
@@ -173,11 +173,11 @@ th {
|
|
|
173
173
|
position: sticky;
|
|
174
174
|
top: 0;
|
|
175
175
|
z-index: 1;
|
|
176
|
-
background: var(--bg-
|
|
176
|
+
background: var(--color-bg-primary);
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
tr:nth-child(2n) {
|
|
180
|
-
background: var(--
|
|
180
|
+
background: var(--color-control-bg);
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
img {
|
|
@@ -200,13 +200,13 @@ kbd {
|
|
|
200
200
|
font-family: var(--font-family-mono) !important;
|
|
201
201
|
font-size: 11px;
|
|
202
202
|
line-height: 10px;
|
|
203
|
-
color: var(--
|
|
203
|
+
color: var(--color-text-primary);
|
|
204
204
|
vertical-align: middle;
|
|
205
|
-
background-color: var(--
|
|
206
|
-
border: solid 1px var(--
|
|
207
|
-
border-bottom-color: var(--
|
|
205
|
+
background-color: var(--color-control-bg);
|
|
206
|
+
border: solid 1px var(--color-border-medium);
|
|
207
|
+
border-bottom-color: var(--color-border-medium);
|
|
208
208
|
border-radius: 6px;
|
|
209
|
-
box-shadow: inset 0 -1px 0 var(--
|
|
209
|
+
box-shadow: inset 0 -1px 0 var(--color-border-medium);
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
samp {
|
|
@@ -221,8 +221,8 @@ var {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
mark {
|
|
224
|
-
background-color: var(--
|
|
225
|
-
color: var(--
|
|
224
|
+
background-color: var(--color-intent-primary-bg);
|
|
225
|
+
color: var(--color-intent-primary-on-bg);
|
|
226
226
|
padding: 0.1em 0.2em;
|
|
227
227
|
border-radius: 2px;
|
|
228
228
|
}
|
|
@@ -249,9 +249,9 @@ sup {
|
|
|
249
249
|
|
|
250
250
|
ins {
|
|
251
251
|
text-decoration: underline;
|
|
252
|
-
background-color: var(--bg-
|
|
253
|
-
color: var(--
|
|
254
|
-
text-decoration-color: var(--
|
|
252
|
+
background-color: var(--color-intent-success-bg-subtle);
|
|
253
|
+
color: var(--color-text-primary);
|
|
254
|
+
text-decoration-color: var(--color-intent-success-text);
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
dfn {
|
|
@@ -310,7 +310,7 @@ figure {
|
|
|
310
310
|
figcaption {
|
|
311
311
|
margin-top: 8px;
|
|
312
312
|
font-size: var(--font-size-s) !important;
|
|
313
|
-
color: var(--
|
|
313
|
+
color: var(--color-text-secondary);
|
|
314
314
|
font-style: italic;
|
|
315
315
|
text-align: center;
|
|
316
316
|
}
|
|
@@ -328,11 +328,11 @@ summary {
|
|
|
328
328
|
}
|
|
329
329
|
|
|
330
330
|
summary:hover {
|
|
331
|
-
color: var(--
|
|
331
|
+
color: var(--color-text-secondary);
|
|
332
332
|
}
|
|
333
333
|
|
|
334
334
|
summary::marker {
|
|
335
|
-
color: var(--
|
|
335
|
+
color: var(--color-text-secondary);
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
details[open] summary {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Statistic } from "@allurereport/core-api";
|
|
2
|
+
import type { SubtreeNodeState, SubtreeToggleState } from "./treeSubtreeToggle.js";
|
|
3
|
+
export type ExpandableTreeNode = {
|
|
4
|
+
nodeId: string;
|
|
5
|
+
statistic?: Statistic;
|
|
6
|
+
trees: ExpandableTreeNode[];
|
|
7
|
+
leaves: unknown[];
|
|
8
|
+
};
|
|
9
|
+
export declare const hasExpandableTreeChildren: (tree: ExpandableTreeNode) => boolean;
|
|
10
|
+
export declare const collectExpandableSubtreeNodes: (tree: ExpandableTreeNode) => SubtreeNodeState[];
|
|
11
|
+
export declare const applySubtreeToggleState: (expandableSubtreeNodes: SubtreeNodeState[], state: SubtreeToggleState, options: {
|
|
12
|
+
toScopedId: (nodeId: string) => string;
|
|
13
|
+
isOpened: (scopedId: string, openedByDefault: boolean) => boolean;
|
|
14
|
+
setOpened: (scopedId: string, shouldOpen: boolean, openedByDefault: boolean) => void;
|
|
15
|
+
}) => void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const isFailedOrBrokenNode = (statistic) => statistic === undefined || Boolean(statistic?.failed || statistic?.broken);
|
|
2
|
+
const getDefaultOpenedState = (statistic, root = false) => root || isFailedOrBrokenNode(statistic);
|
|
3
|
+
export const hasExpandableTreeChildren = (tree) => tree.trees.length > 0 || tree.leaves.length > 0;
|
|
4
|
+
export const collectExpandableSubtreeNodes = (tree) => {
|
|
5
|
+
const nodes = [];
|
|
6
|
+
const stack = [{ tree, isRoot: true }];
|
|
7
|
+
while (stack.length > 0) {
|
|
8
|
+
const current = stack.pop();
|
|
9
|
+
if (!current) {
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
nodes.push({
|
|
13
|
+
id: current.tree.nodeId,
|
|
14
|
+
openedByDefault: getDefaultOpenedState(current.tree.statistic),
|
|
15
|
+
isRoot: current.isRoot,
|
|
16
|
+
});
|
|
17
|
+
current.tree.trees.forEach((nestedSubtree) => stack.push({ tree: nestedSubtree, isRoot: false }));
|
|
18
|
+
}
|
|
19
|
+
return nodes;
|
|
20
|
+
};
|
|
21
|
+
export const applySubtreeToggleState = (expandableSubtreeNodes, state, options) => {
|
|
22
|
+
expandableSubtreeNodes.forEach((node) => {
|
|
23
|
+
const shouldOpenSubtree = state === "all" ? true : state === "first" ? node.isRoot : false;
|
|
24
|
+
const scopedId = options.toScopedId(node.id);
|
|
25
|
+
const currentlyOpened = options.isOpened(scopedId, node.openedByDefault);
|
|
26
|
+
if (currentlyOpened !== shouldOpenSubtree) {
|
|
27
|
+
options.setOpened(scopedId, shouldOpenSubtree, node.openedByDefault);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const getDescendantNodes = (flatList, nodeId) => {
|
|
2
|
+
const index = flatList.findIndex((node) => node.id === nodeId);
|
|
3
|
+
if (index < 0) {
|
|
4
|
+
return [];
|
|
5
|
+
}
|
|
6
|
+
const parentDepth = flatList[index].depth;
|
|
7
|
+
const descendants = [];
|
|
8
|
+
for (let i = index + 1; i < flatList.length; i++) {
|
|
9
|
+
const node = flatList[i];
|
|
10
|
+
if (node.depth <= parentDepth) {
|
|
11
|
+
break;
|
|
12
|
+
}
|
|
13
|
+
descendants.push(node);
|
|
14
|
+
}
|
|
15
|
+
return descendants;
|
|
16
|
+
};
|
|
17
|
+
export const getExpandableDescendants = (flatList, nodeId) => getDescendantNodes(flatList, nodeId).filter((node) => node.kind === "group" || node.kind === "env");
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
const isFailedOrBrokenNode = (statistic) => statistic === undefined || Boolean(statistic?.failed || statistic?.broken);
|
|
2
|
+
const getDefaultOpenedState = (statistic, root = false) => root || isFailedOrBrokenNode(statistic);
|
|
3
|
+
const isNodeOpened = (nodeId, collapsedTrees, defaultOpened, idPrefix) => {
|
|
4
|
+
const collapseKey = toFocusId(nodeId, idPrefix);
|
|
5
|
+
return collapsedTrees.has(collapseKey) ? !defaultOpened : defaultOpened;
|
|
6
|
+
};
|
|
7
|
+
const hasTreeChildren = (tree) => tree.trees.length > 0 || tree.leaves.length > 0;
|
|
8
|
+
const toFocusId = (nodeId, idPrefix) => (idPrefix ? `${idPrefix}${nodeId}` : nodeId);
|
|
9
|
+
const resolveGroupOpened = (tree, collapsedTrees, options) => {
|
|
10
|
+
const defaultOpened = getDefaultOpenedState(tree.statistic, Boolean(options.root));
|
|
11
|
+
const scopedId = toFocusId(tree.nodeId, options.idPrefix);
|
|
12
|
+
if (options.isGroupOpened) {
|
|
13
|
+
return options.isGroupOpened(scopedId, defaultOpened);
|
|
14
|
+
}
|
|
15
|
+
return isNodeOpened(tree.nodeId, collapsedTrees, defaultOpened, options.idPrefix);
|
|
16
|
+
};
|
|
17
|
+
const flattenTreeNode = (tree, collapsedTrees, depth, options) => {
|
|
18
|
+
const result = [];
|
|
19
|
+
const { idPrefix } = options;
|
|
20
|
+
const defaultOpened = getDefaultOpenedState(tree.statistic, Boolean(options.root));
|
|
21
|
+
const isOpened = resolveGroupOpened(tree, collapsedTrees, options);
|
|
22
|
+
const hasChildren = hasTreeChildren(tree);
|
|
23
|
+
const showHeader = Boolean(tree.name) && hasChildren;
|
|
24
|
+
const groupFocusId = toFocusId(tree.nodeId, idPrefix);
|
|
25
|
+
if (showHeader) {
|
|
26
|
+
result.push({
|
|
27
|
+
kind: "group",
|
|
28
|
+
id: groupFocusId,
|
|
29
|
+
nodeId: tree.nodeId,
|
|
30
|
+
depth,
|
|
31
|
+
parentId: options.parentId,
|
|
32
|
+
hasChildren: true,
|
|
33
|
+
isExpanded: isOpened,
|
|
34
|
+
openedByDefault: defaultOpened,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (!hasChildren) {
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
const childDepth = showHeader ? depth + 1 : depth;
|
|
41
|
+
const canShowChildren = showHeader ? isOpened : options.root ? isOpened : true;
|
|
42
|
+
const parentFocusId = showHeader ? groupFocusId : options.parentId;
|
|
43
|
+
if (canShowChildren) {
|
|
44
|
+
for (const subTree of tree.trees) {
|
|
45
|
+
result.push(...flattenTreeNode(subTree, collapsedTrees, childDepth, {
|
|
46
|
+
parentId: parentFocusId,
|
|
47
|
+
idPrefix,
|
|
48
|
+
isGroupOpened: options.isGroupOpened,
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
for (const leaf of tree.leaves) {
|
|
52
|
+
result.push({
|
|
53
|
+
kind: "leaf",
|
|
54
|
+
id: toFocusId(leaf.nodeId, idPrefix),
|
|
55
|
+
testResultId: leaf.nodeId,
|
|
56
|
+
depth: childDepth,
|
|
57
|
+
parentId: parentFocusId,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
};
|
|
63
|
+
const flattenEnvSection = (section, collapsedTrees, isGroupOpened) => {
|
|
64
|
+
const envFocusId = `env:${section.id}`;
|
|
65
|
+
const result = [
|
|
66
|
+
{
|
|
67
|
+
kind: "env",
|
|
68
|
+
id: envFocusId,
|
|
69
|
+
nodeId: section.id,
|
|
70
|
+
depth: 0,
|
|
71
|
+
hasChildren: true,
|
|
72
|
+
isExpanded: section.opened,
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
if (section.opened) {
|
|
76
|
+
result.push(...flattenTreeNode(section.tree, collapsedTrees, 1, {
|
|
77
|
+
root: true,
|
|
78
|
+
parentId: envFocusId,
|
|
79
|
+
idPrefix: `${section.id}:`,
|
|
80
|
+
isGroupOpened,
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
};
|
|
85
|
+
export const flattenVisibleTree = (options) => {
|
|
86
|
+
const { collapsedTrees, isGroupOpened, tree, rootStatistic, isRoot, envSections } = options;
|
|
87
|
+
if (envSections?.length) {
|
|
88
|
+
return envSections.flatMap((section) => {
|
|
89
|
+
if ((section.statistic?.total ?? 0) === 0) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
return flattenEnvSection(section, collapsedTrees, isGroupOpened);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (!tree) {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
return flattenTreeNode(tree, collapsedTrees, 0, {
|
|
99
|
+
root: isRoot ?? true,
|
|
100
|
+
parentId: undefined,
|
|
101
|
+
isGroupOpened,
|
|
102
|
+
...(rootStatistic ? { root: isRoot ?? true } : {}),
|
|
103
|
+
});
|
|
104
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const findIndex = (flatList, currentId) => {
|
|
2
|
+
if (!currentId) {
|
|
3
|
+
return -1;
|
|
4
|
+
}
|
|
5
|
+
return flatList.findIndex((node) => node.id === currentId);
|
|
6
|
+
};
|
|
7
|
+
const findParent = (flatList, node) => {
|
|
8
|
+
if (!node.parentId) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
return flatList.find((candidate) => candidate.id === node.parentId || candidate.nodeId === node.parentId);
|
|
12
|
+
};
|
|
13
|
+
export const moveFocus = (flatList, currentId, direction) => {
|
|
14
|
+
if (flatList.length === 0) {
|
|
15
|
+
return { nextId: undefined };
|
|
16
|
+
}
|
|
17
|
+
const currentIndex = findIndex(flatList, currentId);
|
|
18
|
+
const current = currentIndex >= 0 ? flatList[currentIndex] : undefined;
|
|
19
|
+
switch (direction) {
|
|
20
|
+
case "up": {
|
|
21
|
+
if (currentIndex <= 0) {
|
|
22
|
+
return { nextId: flatList[0]?.id };
|
|
23
|
+
}
|
|
24
|
+
if (!current) {
|
|
25
|
+
return { nextId: flatList[currentIndex - 1]?.id };
|
|
26
|
+
}
|
|
27
|
+
for (let index = currentIndex - 1; index >= 0; index--) {
|
|
28
|
+
const candidate = flatList[index];
|
|
29
|
+
if (current.parentId && candidate.id === current.parentId) {
|
|
30
|
+
for (let nestedIndex = currentIndex - 1; nestedIndex > index; nestedIndex--) {
|
|
31
|
+
const nested = flatList[nestedIndex];
|
|
32
|
+
if (nested.depth > candidate.depth) {
|
|
33
|
+
return { nextId: nested.id };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { nextId: candidate.id };
|
|
37
|
+
}
|
|
38
|
+
return { nextId: candidate.id };
|
|
39
|
+
}
|
|
40
|
+
return { nextId: flatList[0]?.id };
|
|
41
|
+
}
|
|
42
|
+
case "down": {
|
|
43
|
+
if (currentIndex < 0) {
|
|
44
|
+
return { nextId: flatList[0]?.id };
|
|
45
|
+
}
|
|
46
|
+
if (currentIndex >= flatList.length - 1) {
|
|
47
|
+
return { nextId: flatList[flatList.length - 1]?.id };
|
|
48
|
+
}
|
|
49
|
+
return { nextId: flatList[currentIndex + 1]?.id };
|
|
50
|
+
}
|
|
51
|
+
case "home": {
|
|
52
|
+
return { nextId: flatList[0]?.id };
|
|
53
|
+
}
|
|
54
|
+
case "end": {
|
|
55
|
+
return { nextId: flatList[flatList.length - 1]?.id };
|
|
56
|
+
}
|
|
57
|
+
case "firstLeaf": {
|
|
58
|
+
const firstLeaf = flatList.find((node) => node.kind === "leaf");
|
|
59
|
+
return { nextId: firstLeaf?.id ?? flatList[0]?.id };
|
|
60
|
+
}
|
|
61
|
+
case "lastLeaf": {
|
|
62
|
+
const leaves = flatList.filter((node) => node.kind === "leaf");
|
|
63
|
+
const lastLeaf = leaves[leaves.length - 1];
|
|
64
|
+
return { nextId: lastLeaf?.id ?? flatList[flatList.length - 1]?.id };
|
|
65
|
+
}
|
|
66
|
+
case "parent": {
|
|
67
|
+
if (!current) {
|
|
68
|
+
return { nextId: flatList[0]?.id };
|
|
69
|
+
}
|
|
70
|
+
const parent = findParent(flatList, current);
|
|
71
|
+
return { nextId: parent?.id ?? current.id };
|
|
72
|
+
}
|
|
73
|
+
case "left": {
|
|
74
|
+
if (!current) {
|
|
75
|
+
return { nextId: flatList[0]?.id };
|
|
76
|
+
}
|
|
77
|
+
if ((current.kind === "group" || current.kind === "env") && current.isExpanded) {
|
|
78
|
+
return { nextId: current.id, collapse: true };
|
|
79
|
+
}
|
|
80
|
+
const parent = findParent(flatList, current);
|
|
81
|
+
if (parent) {
|
|
82
|
+
return { nextId: parent.id };
|
|
83
|
+
}
|
|
84
|
+
if (currentIndex > 0) {
|
|
85
|
+
return { nextId: flatList[currentIndex - 1]?.id };
|
|
86
|
+
}
|
|
87
|
+
return { nextId: current.id };
|
|
88
|
+
}
|
|
89
|
+
case "firstChild":
|
|
90
|
+
case "right": {
|
|
91
|
+
if (!current) {
|
|
92
|
+
return { nextId: flatList[0]?.id };
|
|
93
|
+
}
|
|
94
|
+
if ((current.kind === "group" || current.kind === "env") && !current.isExpanded) {
|
|
95
|
+
return { nextId: current.id, expand: true };
|
|
96
|
+
}
|
|
97
|
+
const nextNode = flatList[currentIndex + 1];
|
|
98
|
+
if (nextNode && nextNode.depth > current.depth) {
|
|
99
|
+
return { nextId: nextNode.id };
|
|
100
|
+
}
|
|
101
|
+
if (direction === "right" && currentIndex < flatList.length - 1) {
|
|
102
|
+
return { nextId: flatList[currentIndex + 1]?.id };
|
|
103
|
+
}
|
|
104
|
+
return { nextId: current.id };
|
|
105
|
+
}
|
|
106
|
+
default: {
|
|
107
|
+
return { nextId: current?.id ?? flatList[0]?.id };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TreeNavNodeKind } from "./types.js";
|
|
2
|
+
export type ScrollRect = {
|
|
3
|
+
top: number;
|
|
4
|
+
bottom: number;
|
|
5
|
+
};
|
|
6
|
+
export declare const computeTreeFocusScrollDelta: (nodeRect: ScrollRect, rootRect: ScrollRect, kind?: TreeNavNodeKind, inset?: number) => number;
|
|
7
|
+
export declare const findFocusScrollRoot: (target: HTMLElement, containerAttribute: string) => HTMLElement | null;
|
|
8
|
+
export declare const scrollTreeFocusIntoView: (target: HTMLElement, scrollRoot: HTMLElement, kind?: TreeNavNodeKind, inset?: number) => void;
|
|
9
|
+
export declare const scrollFocusIntoView: (target: HTMLElement, options?: {
|
|
10
|
+
containerAttribute?: string;
|
|
11
|
+
kind?: TreeNavNodeKind;
|
|
12
|
+
inset?: number;
|
|
13
|
+
}) => void;
|
|
14
|
+
export declare const scrollTreePaneToTop: (anchor?: HTMLElement | null) => void;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const isOutOfScrollport = (nodeRect, rootRect, inset) => nodeRect.top < rootRect.top + inset || nodeRect.bottom > rootRect.bottom - inset;
|
|
2
|
+
export const computeTreeFocusScrollDelta = (nodeRect, rootRect, kind, inset = 4) => {
|
|
3
|
+
const pinToTop = kind === "group" || kind === "env";
|
|
4
|
+
if (!isOutOfScrollport(nodeRect, rootRect, inset)) {
|
|
5
|
+
return 0;
|
|
6
|
+
}
|
|
7
|
+
if (pinToTop) {
|
|
8
|
+
return nodeRect.top - rootRect.top - inset;
|
|
9
|
+
}
|
|
10
|
+
const nodeCenter = (nodeRect.top + nodeRect.bottom) / 2;
|
|
11
|
+
const rootCenter = (rootRect.top + rootRect.bottom) / 2;
|
|
12
|
+
return nodeCenter - rootCenter;
|
|
13
|
+
};
|
|
14
|
+
const isScrollableElement = (element) => {
|
|
15
|
+
const { overflowY } = getComputedStyle(element);
|
|
16
|
+
return ((overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") &&
|
|
17
|
+
element.scrollHeight > element.clientHeight + 1);
|
|
18
|
+
};
|
|
19
|
+
export const findFocusScrollRoot = (target, containerAttribute) => {
|
|
20
|
+
let node = target.parentElement;
|
|
21
|
+
let fallback = null;
|
|
22
|
+
while (node instanceof HTMLElement) {
|
|
23
|
+
if (node.hasAttribute(containerAttribute)) {
|
|
24
|
+
fallback ?? (fallback = node);
|
|
25
|
+
if (isScrollableElement(node)) {
|
|
26
|
+
return node;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
node = node.parentElement;
|
|
30
|
+
}
|
|
31
|
+
return fallback;
|
|
32
|
+
};
|
|
33
|
+
export const scrollTreeFocusIntoView = (target, scrollRoot, kind, inset = 4) => {
|
|
34
|
+
const nodeRect = target.getBoundingClientRect();
|
|
35
|
+
const rootRect = scrollRoot.getBoundingClientRect();
|
|
36
|
+
const delta = computeTreeFocusScrollDelta(nodeRect, rootRect, kind, inset);
|
|
37
|
+
if (Math.abs(delta) > 1) {
|
|
38
|
+
scrollRoot.scrollTop += delta;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
export const scrollFocusIntoView = (target, options) => {
|
|
42
|
+
const containerAttribute = options?.containerAttribute ?? "data-tree-scroll-container";
|
|
43
|
+
const scrollRoot = findFocusScrollRoot(target, containerAttribute);
|
|
44
|
+
if (scrollRoot) {
|
|
45
|
+
scrollTreeFocusIntoView(target, scrollRoot, options?.kind, options?.inset);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
target.scrollIntoView({ block: "center", inline: "nearest" });
|
|
49
|
+
};
|
|
50
|
+
export const scrollTreePaneToTop = (anchor) => {
|
|
51
|
+
const containerAttribute = "data-tree-scroll-container";
|
|
52
|
+
if (anchor) {
|
|
53
|
+
const scrollRoot = findFocusScrollRoot(anchor, containerAttribute);
|
|
54
|
+
if (scrollRoot) {
|
|
55
|
+
scrollRoot.scrollTop = 0;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const element of Array.from(document.querySelectorAll(`[${containerAttribute}]`))) {
|
|
60
|
+
if (element instanceof HTMLElement && isScrollableElement(element)) {
|
|
61
|
+
element.scrollTop = 0;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Statistic } from "@allurereport/core-api";
|
|
2
|
+
export type TreeNavNodeKind = "env" | "group" | "leaf";
|
|
3
|
+
export type FlatTreeNode = {
|
|
4
|
+
kind: TreeNavNodeKind;
|
|
5
|
+
id: string;
|
|
6
|
+
nodeId?: string;
|
|
7
|
+
testResultId?: string;
|
|
8
|
+
depth: number;
|
|
9
|
+
parentId?: string;
|
|
10
|
+
hasChildren?: boolean;
|
|
11
|
+
isExpanded?: boolean;
|
|
12
|
+
openedByDefault?: boolean;
|
|
13
|
+
};
|
|
14
|
+
export type FlattenTreeInput = {
|
|
15
|
+
nodeId: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
statistic?: Statistic;
|
|
18
|
+
leaves: Array<{
|
|
19
|
+
nodeId: string;
|
|
20
|
+
}>;
|
|
21
|
+
trees: FlattenTreeInput[];
|
|
22
|
+
};
|
|
23
|
+
export type EnvTreeSection = {
|
|
24
|
+
id: string;
|
|
25
|
+
opened: boolean;
|
|
26
|
+
tree: FlattenTreeInput;
|
|
27
|
+
statistic?: Statistic;
|
|
28
|
+
};
|
|
29
|
+
export type FlattenVisibleTreeOptions = {
|
|
30
|
+
collapsedTrees: ReadonlySet<string>;
|
|
31
|
+
isGroupOpened?: (scopedNodeId: string, openedByDefault: boolean) => boolean;
|
|
32
|
+
tree?: FlattenTreeInput;
|
|
33
|
+
rootStatistic?: Statistic;
|
|
34
|
+
isRoot?: boolean;
|
|
35
|
+
envSections?: EnvTreeSection[];
|
|
36
|
+
};
|
|
37
|
+
export type MoveDirection = "up" | "down" | "left" | "right" | "parent" | "firstChild" | "home" | "end" | "firstLeaf" | "lastLeaf";
|
|
38
|
+
export type MoveFocusResult = {
|
|
39
|
+
nextId: string | undefined;
|
|
40
|
+
collapse?: boolean;
|
|
41
|
+
expand?: boolean;
|
|
42
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -14,6 +14,10 @@ export declare const getNextSubtreeToggleState: (payload: {
|
|
|
14
14
|
isSubtreeExpandedAll: boolean;
|
|
15
15
|
lastSubtreeToggle: SubtreeToggleState | null;
|
|
16
16
|
}) => SubtreeToggleState;
|
|
17
|
+
export declare const resolveNextSubtreeToggleState: (subtreeNodes: SubtreeNodeState[], isNodeOpened: (id: string, openedByDefault: boolean) => boolean, lastSubtreeToggle: SubtreeToggleState | null) => {
|
|
18
|
+
nextState: SubtreeToggleState;
|
|
19
|
+
nextLastToggle: SubtreeToggleState | null;
|
|
20
|
+
};
|
|
17
21
|
export declare const getSubtreeToggleIcon: (payload: {
|
|
18
22
|
hasOnlyLeafResults: boolean;
|
|
19
23
|
isSubtreeCollapsedAll: boolean;
|
|
@@ -21,6 +21,27 @@ export const getNextSubtreeToggleState = (payload) => {
|
|
|
21
21
|
}
|
|
22
22
|
return "all";
|
|
23
23
|
};
|
|
24
|
+
export const resolveNextSubtreeToggleState = (subtreeNodes, isNodeOpened, lastSubtreeToggle) => {
|
|
25
|
+
const root = subtreeNodes.find((node) => node.isRoot);
|
|
26
|
+
if (!root || subtreeNodes.length === 0) {
|
|
27
|
+
return { nextState: "first", nextLastToggle: null };
|
|
28
|
+
}
|
|
29
|
+
const isSubtreeCollapsedAll = !isNodeOpened(root.id, root.openedByDefault);
|
|
30
|
+
const isSubtreeFirstLevelOnly = isSubtreeFirstLevelOnlyOpened(root.id, root.openedByDefault, subtreeNodes, isNodeOpened);
|
|
31
|
+
const subtreeExpandedAll = isSubtreeExpandedAll(subtreeNodes, isNodeOpened);
|
|
32
|
+
const hasOnlyLeafResults = subtreeNodes.every((node) => node.isRoot);
|
|
33
|
+
const nextState = getNextSubtreeToggleState({
|
|
34
|
+
hasOnlyLeafResults,
|
|
35
|
+
isSubtreeCollapsedAll,
|
|
36
|
+
isSubtreeFirstLevelOnly,
|
|
37
|
+
isSubtreeExpandedAll: subtreeExpandedAll,
|
|
38
|
+
lastSubtreeToggle,
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
nextState,
|
|
42
|
+
nextLastToggle: nextState !== "first" ? nextState : lastSubtreeToggle,
|
|
43
|
+
};
|
|
44
|
+
};
|
|
24
45
|
export const getSubtreeToggleIcon = (payload) => {
|
|
25
46
|
const { hasOnlyLeafResults, isSubtreeCollapsedAll, isSubtreeFirstLevelOnly } = payload;
|
|
26
47
|
if (hasOnlyLeafResults) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allurereport/web-commons",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.10.0",
|
|
4
4
|
"description": "Collection of utilities used across the web Allure reports",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"allure",
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
"lint:fix": "oxlint --import-plugin --fix src test features stories"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@allurereport/aql": "3.
|
|
30
|
-
"@allurereport/charts-api": "3.
|
|
31
|
-
"@allurereport/core-api": "3.
|
|
32
|
-
"@allurereport/plugin-api": "3.
|
|
29
|
+
"@allurereport/aql": "3.10.0",
|
|
30
|
+
"@allurereport/charts-api": "3.10.0",
|
|
31
|
+
"@allurereport/core-api": "3.10.0",
|
|
32
|
+
"@allurereport/plugin-api": "3.10.0",
|
|
33
33
|
"@preact/signals": "^2.6.1",
|
|
34
34
|
"@preact/signals-core": "^1.12.2",
|
|
35
35
|
"ansi-to-html": "^0.7.2",
|
|
@@ -43,13 +43,13 @@
|
|
|
43
43
|
"@types/d3-interpolate": "^3.0.4",
|
|
44
44
|
"@types/d3-scale": "^4.0.9",
|
|
45
45
|
"@types/d3-shape": "^3.1.6",
|
|
46
|
-
"@vitest/runner": "^2
|
|
47
|
-
"allure-js-commons": "^3
|
|
48
|
-
"allure-vitest": "^3
|
|
46
|
+
"@vitest/runner": "^2",
|
|
47
|
+
"allure-js-commons": "^3",
|
|
48
|
+
"allure-vitest": "^3",
|
|
49
49
|
"jsdom": "^26.1.0",
|
|
50
|
-
"rimraf": "^6
|
|
50
|
+
"rimraf": "^6",
|
|
51
51
|
"tslib": "^2.7.0",
|
|
52
|
-
"typescript": "^5
|
|
53
|
-
"vitest": "^
|
|
52
|
+
"typescript": "^5",
|
|
53
|
+
"vitest": "^4.1.0"
|
|
54
54
|
}
|
|
55
55
|
}
|