@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.
Files changed (39) hide show
  1. package/dist/attachments.d.ts +5 -2
  2. package/dist/attachments.js +11 -2
  3. package/dist/charts/colors.js +5 -5
  4. package/dist/charts/utils.js +5 -1
  5. package/dist/hotkeys/createHotkeyController.d.ts +12 -0
  6. package/dist/hotkeys/createHotkeyController.js +74 -0
  7. package/dist/hotkeys/formatHotkey.d.ts +2 -0
  8. package/dist/hotkeys/formatHotkey.js +31 -0
  9. package/dist/hotkeys/index.d.ts +5 -0
  10. package/dist/hotkeys/index.js +5 -0
  11. package/dist/hotkeys/isEditableTarget.d.ts +1 -0
  12. package/dist/hotkeys/isEditableTarget.js +10 -0
  13. package/dist/hotkeys/matchHotkey.d.ts +2 -0
  14. package/dist/hotkeys/matchHotkey.js +41 -0
  15. package/dist/hotkeys/types.d.ts +19 -0
  16. package/dist/hotkeys/types.js +1 -0
  17. package/dist/index.d.ts +4 -0
  18. package/dist/index.js +4 -0
  19. package/dist/linkify.d.ts +10 -0
  20. package/dist/linkify.js +32 -0
  21. package/dist/prose.d.ts +1 -1
  22. package/dist/prose.js +26 -26
  23. package/dist/subtreeExpansion.d.ts +15 -0
  24. package/dist/subtreeExpansion.js +30 -0
  25. package/dist/treeNavigation/descendants.d.ts +3 -0
  26. package/dist/treeNavigation/descendants.js +17 -0
  27. package/dist/treeNavigation/flattenVisibleTree.d.ts +2 -0
  28. package/dist/treeNavigation/flattenVisibleTree.js +104 -0
  29. package/dist/treeNavigation/index.d.ts +5 -0
  30. package/dist/treeNavigation/index.js +5 -0
  31. package/dist/treeNavigation/moveFocus.d.ts +2 -0
  32. package/dist/treeNavigation/moveFocus.js +110 -0
  33. package/dist/treeNavigation/scrollTreeFocus.d.ts +14 -0
  34. package/dist/treeNavigation/scrollTreeFocus.js +65 -0
  35. package/dist/treeNavigation/types.d.ts +42 -0
  36. package/dist/treeNavigation/types.js +1 -0
  37. package/dist/treeSubtreeToggle.d.ts +4 -0
  38. package/dist/treeSubtreeToggle.js +21 -0
  39. package/package.json +11 -11
@@ -17,7 +17,10 @@ type AttachmentImageDiffData = {
17
17
  diff?: string;
18
18
  };
19
19
  };
20
- export type AttachmentData = AttachmentImageData | AttachmentTextData | AttachmentVideoData | AttachmentImageDiffData;
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;
@@ -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
- switch (type) {
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
  }
@@ -1,9 +1,9 @@
1
1
  export const statusColors = {
2
- failed: "var(--bg-support-capella)",
3
- broken: "var(--bg-support-atlas)",
4
- passed: "var(--bg-support-castor)",
5
- skipped: "var(--bg-support-rau)",
6
- unknown: "var(--bg-support-skat)",
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(")) {
@@ -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([resolveCSSVarColor(statusColors.failed), "#fff", resolveCSSVarColor(statusColors.passed)])
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,2 @@
1
+ import type { HotkeyBinding } from "./types.js";
2
+ export declare const formatHotkey: (binding: Pick<HotkeyBinding, "key" | "modifiers">) => string;
@@ -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,5 @@
1
+ export * from "./types.js";
2
+ export * from "./isEditableTarget.js";
3
+ export * from "./matchHotkey.js";
4
+ export * from "./formatHotkey.js";
5
+ export * from "./createHotkeyController.js";
@@ -0,0 +1,5 @@
1
+ export * from "./types.js";
2
+ export * from "./isEditableTarget.js";
3
+ export * from "./matchHotkey.js";
4
+ export * from "./formatHotkey.js";
5
+ export * from "./createHotkeyController.js";
@@ -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,2 @@
1
+ import type { HotkeyBinding } from "./types.js";
2
+ export declare const matchHotkey: (event: KeyboardEvent, binding: HotkeyBinding) => boolean;
@@ -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;
@@ -0,0 +1,32 @@
1
+ export const URL_IN_TEXT_RE = /((?:(?:https?:\/\/|ftp:\/\/|mailto:)|www\.)\S+?)(\s|"|'|\)|]|}|&#62|$)/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-base-primary);\n}\n\nbody {\n color: var(--on-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(--on-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(--on-text-primary);\n text-decoration: underline;\n}\n\na:hover {\n color: var(--on-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(--bg-control-secondary);\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(--bg-control-secondary);\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(--on-border-primary);\n color: var(--on-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(--on-border-primary);\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(--on-border-primary) !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(--bg-base-primary);\n}\n\ntr:nth-child(2n) {\n background: var(--bg-control-secondary);\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(--on-text-primary);\n vertical-align: middle;\n background-color: var(--bg-control-secondary);\n border: solid 1px var(--on-border-medium);\n border-bottom-color: var(--on-border-medium);\n border-radius: 6px;\n box-shadow: inset 0 -1px 0 var(--on-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(--bg-support-aldebaran);\n color: var(--on-text-primary);\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(--bg-support-gliese);\n color: var(--on-text-primary);\n text-decoration-color: var(--on-support-gliese);\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(--on-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(--on-text-secondary);\n}\n\nsummary::marker {\n color: var(--on-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";
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-base-primary);
7
+ background: var(--color-bg-primary);
8
8
  }
9
9
 
10
10
  body {
11
- color: var(--on-text-primary);
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(--on-text-secondary);
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(--on-text-primary);
84
+ color: var(--color-text-primary);
85
85
  text-decoration: underline;
86
86
  }
87
87
 
88
88
  a:hover {
89
- color: var(--on-text-secondary);
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(--bg-control-secondary);
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(--bg-control-secondary);
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(--on-border-primary);
121
- color: var(--on-text-secondary);
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(--on-border-primary);
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(--on-border-primary) !important;
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-base-primary);
176
+ background: var(--color-bg-primary);
177
177
  }
178
178
 
179
179
  tr:nth-child(2n) {
180
- background: var(--bg-control-secondary);
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(--on-text-primary);
203
+ color: var(--color-text-primary);
204
204
  vertical-align: middle;
205
- background-color: var(--bg-control-secondary);
206
- border: solid 1px var(--on-border-medium);
207
- border-bottom-color: var(--on-border-medium);
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(--on-border-medium);
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(--bg-support-aldebaran);
225
- color: var(--on-text-primary);
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-support-gliese);
253
- color: var(--on-text-primary);
254
- text-decoration-color: var(--on-support-gliese);
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(--on-text-secondary);
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(--on-text-secondary);
331
+ color: var(--color-text-secondary);
332
332
  }
333
333
 
334
334
  summary::marker {
335
- color: var(--on-text-secondary);
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,3 @@
1
+ import type { FlatTreeNode } from "./types.js";
2
+ export declare const getDescendantNodes: (flatList: FlatTreeNode[], nodeId: string) => FlatTreeNode[];
3
+ export declare const getExpandableDescendants: (flatList: FlatTreeNode[], nodeId: string) => FlatTreeNode[];
@@ -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,2 @@
1
+ import type { FlatTreeNode, FlattenVisibleTreeOptions } from "./types.js";
2
+ export declare const flattenVisibleTree: (options: FlattenVisibleTreeOptions) => FlatTreeNode[];
@@ -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,5 @@
1
+ export * from "./types.js";
2
+ export * from "./flattenVisibleTree.js";
3
+ export * from "./moveFocus.js";
4
+ export * from "./descendants.js";
5
+ export * from "./scrollTreeFocus.js";
@@ -0,0 +1,5 @@
1
+ export * from "./types.js";
2
+ export * from "./flattenVisibleTree.js";
3
+ export * from "./moveFocus.js";
4
+ export * from "./descendants.js";
5
+ export * from "./scrollTreeFocus.js";
@@ -0,0 +1,2 @@
1
+ import type { FlatTreeNode, MoveDirection, MoveFocusResult } from "./types.js";
2
+ export declare const moveFocus: (flatList: FlatTreeNode[], currentId: string | undefined, direction: MoveDirection) => MoveFocusResult;
@@ -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.8.2",
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.8.2",
30
- "@allurereport/charts-api": "3.8.2",
31
- "@allurereport/core-api": "3.8.2",
32
- "@allurereport/plugin-api": "3.8.2",
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.1.9",
47
- "allure-js-commons": "^3.3.0",
48
- "allure-vitest": "^3.3.0",
46
+ "@vitest/runner": "^2",
47
+ "allure-js-commons": "^3",
48
+ "allure-vitest": "^3",
49
49
  "jsdom": "^26.1.0",
50
- "rimraf": "^6.0.1",
50
+ "rimraf": "^6",
51
51
  "tslib": "^2.7.0",
52
- "typescript": "^5.6.3",
53
- "vitest": "^2.1.9"
52
+ "typescript": "^5",
53
+ "vitest": "^4.1.0"
54
54
  }
55
55
  }