@ng-prism/plugin-visual-regression 22.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +106 -0
  2. package/dist/asset-url.d.ts +14 -0
  3. package/dist/asset-url.d.ts.map +1 -0
  4. package/dist/asset-url.js +25 -0
  5. package/dist/index.browser.d.ts +7 -0
  6. package/dist/index.browser.d.ts.map +1 -0
  7. package/dist/index.browser.js +8 -0
  8. package/dist/index.d.ts +7 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +5 -0
  11. package/dist/report-reader.d.ts +18 -0
  12. package/dist/report-reader.d.ts.map +1 -0
  13. package/dist/report-reader.js +55 -0
  14. package/dist/schematics/ng-add/index.d.ts +4 -0
  15. package/dist/schematics/ng-add/index.d.ts.map +1 -0
  16. package/dist/schematics/ng-add/index.js +25 -0
  17. package/dist/schematics/ng-add/schema.d.ts +4 -0
  18. package/dist/schematics/ng-add/schema.d.ts.map +1 -0
  19. package/dist/schematics/ng-add/schema.js +1 -0
  20. package/dist/thresholds.d.ts +16 -0
  21. package/dist/thresholds.d.ts.map +1 -0
  22. package/dist/thresholds.js +20 -0
  23. package/dist/visual-regression-header-badge.component.d.ts +14 -0
  24. package/dist/visual-regression-header-badge.component.d.ts.map +1 -0
  25. package/dist/visual-regression-header-badge.component.js +73 -0
  26. package/dist/visual-regression-panel.component.d.ts +22 -0
  27. package/dist/visual-regression-panel.component.d.ts.map +1 -0
  28. package/dist/visual-regression-panel.component.js +191 -0
  29. package/dist/visual-regression-plugin.browser.d.ts +11 -0
  30. package/dist/visual-regression-plugin.browser.d.ts.map +1 -0
  31. package/dist/visual-regression-plugin.browser.js +34 -0
  32. package/dist/visual-regression-plugin.d.ts +11 -0
  33. package/dist/visual-regression-plugin.d.ts.map +1 -0
  34. package/dist/visual-regression-plugin.js +67 -0
  35. package/dist/visual-regression.types.d.ts +97 -0
  36. package/dist/visual-regression.types.d.ts.map +1 -0
  37. package/dist/visual-regression.types.js +1 -0
  38. package/dist/vrt-bg.d.ts +37 -0
  39. package/dist/vrt-bg.d.ts.map +1 -0
  40. package/dist/vrt-bg.js +53 -0
  41. package/dist/vrt-compare.component.d.ts +70 -0
  42. package/dist/vrt-compare.component.d.ts.map +1 -0
  43. package/dist/vrt-compare.component.js +602 -0
  44. package/dist/vrt-summarize.d.ts +48 -0
  45. package/dist/vrt-summarize.d.ts.map +1 -0
  46. package/dist/vrt-summarize.js +97 -0
  47. package/dist/vrt-summary.component.d.ts +17 -0
  48. package/dist/vrt-summary.component.d.ts.map +1 -0
  49. package/dist/vrt-summary.component.js +67 -0
  50. package/dist/vrt-zoom.d.ts +36 -0
  51. package/dist/vrt-zoom.d.ts.map +1 -0
  52. package/dist/vrt-zoom.js +48 -0
  53. package/package.json +54 -0
  54. package/schematics/collection.json +10 -0
  55. package/schematics/ng-add/schema.json +16 -0
@@ -0,0 +1,48 @@
1
+ import type { VrtStatus, VrtVariantResult } from './visual-regression.types.js';
2
+ /**
3
+ * Colour role of a status, resolved to a theme token by the components.
4
+ *
5
+ * `new` is `neutral` rather than muted on purpose: the report contract says a
6
+ * variant with no baseline is explicitly not a failure, and grey reads as
7
+ * "ignored" next to a green "unchanged".
8
+ */
9
+ export type VrtTone = 'success' | 'danger' | 'warn' | 'neutral' | 'muted';
10
+ export declare const STATUS_LABEL: Record<VrtStatus, string>;
11
+ export declare const STATUS_TONE: Record<VrtStatus, VrtTone>;
12
+ export interface VrtSummary {
13
+ total: number;
14
+ counts: Record<VrtStatus, number>;
15
+ /** Variants a runner could actually measure against a baseline. */
16
+ compared: number;
17
+ /** Largest diff among compared variants; `null` when nothing was compared. */
18
+ maxDiffRatio: number | null;
19
+ }
20
+ export interface VrtTile {
21
+ key: string;
22
+ label: string;
23
+ value: string;
24
+ /** How far the tile's bar fills, 0–1. */
25
+ ratio: number;
26
+ tone: VrtTone;
27
+ }
28
+ /** Counts and the worst diff, derived from the variants already in `meta`. */
29
+ export declare function summarize(variants: readonly VrtVariantResult[]): VrtSummary;
30
+ /**
31
+ * The tiles the summary strip renders.
32
+ *
33
+ * `Changed` and `Unchanged` are always present: a strip that loses a column
34
+ * when a run goes green would make the panel change shape with its content,
35
+ * and "no Changed tile" is easy to misread as "no data". Everything else
36
+ * appears only when it has something to report, so a library that never
37
+ * excludes a variant is never asked to wonder what `Excluded` means.
38
+ */
39
+ export declare function summaryTiles(summary: VrtSummary): VrtTile[];
40
+ /**
41
+ * A ratio as the percentage a reviewer reads.
42
+ *
43
+ * Anything above zero keeps two decimals, because the difference between
44
+ * "0.00%" and "a few pixels moved" is the whole point of the panel — a diff
45
+ * that rounds to zero is shown as `<0.01%`, never as `0%`.
46
+ */
47
+ export declare function formatPercent(ratio: number): string;
48
+ //# sourceMappingURL=vrt-summarize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vrt-summarize.d.ts","sourceRoot":"","sources":["../src/vrt-summarize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAEhF;;;;;;GAMG;AACH,MAAM,MAAM,OAAO,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;AAE1E,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAMlD,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAMlD,CAAC;AAWF,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAClC,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;CACf;AAED,8EAA8E;AAC9E,wBAAgB,SAAS,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,GAAG,UAAU,CAyB3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,EAAE,CAiC3D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKnD"}
@@ -0,0 +1,97 @@
1
+ export const STATUS_LABEL = {
2
+ unchanged: 'Unchanged',
3
+ changed: 'Changed',
4
+ 'size-mismatch': 'Resized',
5
+ new: 'New',
6
+ excluded: 'Excluded',
7
+ };
8
+ export const STATUS_TONE = {
9
+ unchanged: 'success',
10
+ changed: 'danger',
11
+ 'size-mismatch': 'warn',
12
+ new: 'neutral',
13
+ excluded: 'muted',
14
+ };
15
+ /** Order the summary strip and any status list follow: worst news first. */
16
+ const STATUS_ORDER = [
17
+ 'changed',
18
+ 'size-mismatch',
19
+ 'unchanged',
20
+ 'new',
21
+ 'excluded',
22
+ ];
23
+ /** Counts and the worst diff, derived from the variants already in `meta`. */
24
+ export function summarize(variants) {
25
+ const counts = {
26
+ unchanged: 0,
27
+ changed: 0,
28
+ 'size-mismatch': 0,
29
+ new: 0,
30
+ excluded: 0,
31
+ };
32
+ let compared = 0;
33
+ let maxDiffRatio = null;
34
+ for (const variant of variants) {
35
+ if (variant.status in counts)
36
+ counts[variant.status]++;
37
+ // A ratio exists only where a baseline and a capture were compared.
38
+ // `new` has nothing to measure against and `size-mismatch` could not be
39
+ // measured, so neither contributes to the worst-diff figure.
40
+ if (typeof variant.diffRatio === 'number') {
41
+ compared++;
42
+ maxDiffRatio = Math.max(maxDiffRatio ?? 0, variant.diffRatio);
43
+ }
44
+ }
45
+ return { total: variants.length, counts, compared, maxDiffRatio };
46
+ }
47
+ /**
48
+ * The tiles the summary strip renders.
49
+ *
50
+ * `Changed` and `Unchanged` are always present: a strip that loses a column
51
+ * when a run goes green would make the panel change shape with its content,
52
+ * and "no Changed tile" is easy to misread as "no data". Everything else
53
+ * appears only when it has something to report, so a library that never
54
+ * excludes a variant is never asked to wonder what `Excluded` means.
55
+ */
56
+ export function summaryTiles(summary) {
57
+ const share = (count) => summary.total === 0 ? 0 : count / summary.total;
58
+ const tiles = STATUS_ORDER.filter((status) => summary.counts[status] > 0 ||
59
+ status === 'changed' ||
60
+ status === 'unchanged').map((status) => {
61
+ const count = summary.counts[status];
62
+ return {
63
+ key: status,
64
+ label: STATUS_LABEL[status],
65
+ value: String(count),
66
+ ratio: share(count),
67
+ // A count of zero is the absence of news, not news in that colour:
68
+ // "Changed 0" rendered in danger red screams the opposite of what it says.
69
+ tone: count === 0 ? 'muted' : STATUS_TONE[status],
70
+ };
71
+ });
72
+ if (summary.maxDiffRatio !== null) {
73
+ tiles.push({
74
+ key: 'max-diff',
75
+ label: 'Max diff',
76
+ value: formatPercent(summary.maxDiffRatio),
77
+ ratio: summary.maxDiffRatio,
78
+ tone: summary.maxDiffRatio > 0 ? 'danger' : 'success',
79
+ });
80
+ }
81
+ return tiles;
82
+ }
83
+ /**
84
+ * A ratio as the percentage a reviewer reads.
85
+ *
86
+ * Anything above zero keeps two decimals, because the difference between
87
+ * "0.00%" and "a few pixels moved" is the whole point of the panel — a diff
88
+ * that rounds to zero is shown as `<0.01%`, never as `0%`.
89
+ */
90
+ export function formatPercent(ratio) {
91
+ const pct = ratio * 100;
92
+ if (pct === 0)
93
+ return '0%';
94
+ if (pct < 0.01)
95
+ return '<0.01%';
96
+ return `${pct.toFixed(2)}%`;
97
+ }
@@ -0,0 +1,17 @@
1
+ import { type VrtSummary } from './vrt-summarize.js';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * The strip that opens the panel.
5
+ *
6
+ * Every other ng-prism panel leads with a summary — the a11y score ring, the
7
+ * coverage stat row — so the visual regression panel does too, and it borrows
8
+ * their card, micro-label and mono-numeral treatment rather than inventing a
9
+ * third one.
10
+ */
11
+ export declare class VrtSummaryComponent {
12
+ readonly summary: import("@angular/core").InputSignal<VrtSummary>;
13
+ protected readonly tiles: import("@angular/core").Signal<import("./vrt-summarize.js").VrtTile[]>;
14
+ static ɵfac: i0.ɵɵFactoryDeclaration<VrtSummaryComponent, never>;
15
+ static ɵcmp: i0.ɵɵComponentDeclaration<VrtSummaryComponent, "prism-vrt-summary", never, { "summary": { "alias": "summary"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
16
+ }
17
+ //# sourceMappingURL=vrt-summary.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vrt-summary.component.d.ts","sourceRoot":"","sources":["../src/vrt-summary.component.ts"],"names":[],"mappings":"AAMA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;;AAEnE;;;;;;;GAOG;AACH,qBAiFa,mBAAmB;IAC9B,QAAQ,CAAC,OAAO,kDAAgC;IAEhD,SAAS,CAAC,QAAQ,CAAC,KAAK,yEAAgD;yCAH7D,mBAAmB;2CAAnB,mBAAmB;CAI/B"}
@@ -0,0 +1,67 @@
1
+ import { ChangeDetectionStrategy, Component, computed, input, } from '@angular/core';
2
+ import { summaryTiles } from './vrt-summarize.js';
3
+ import * as i0 from "@angular/core";
4
+ const _forTrack0 = ($index, $item) => $item.key;
5
+ function VrtSummaryComponent_For_2_Template(rf, ctx) { if (rf & 1) {
6
+ i0.ɵɵdomElementStart(0, "div", 1)(1, "div", 2);
7
+ i0.ɵɵtext(2);
8
+ i0.ɵɵdomElementEnd();
9
+ i0.ɵɵdomElementStart(3, "div", 3);
10
+ i0.ɵɵtext(4);
11
+ i0.ɵɵdomElementEnd();
12
+ i0.ɵɵdomElementStart(5, "div", 4);
13
+ i0.ɵɵdomElement(6, "div", 5);
14
+ i0.ɵɵdomElementEnd()();
15
+ } if (rf & 2) {
16
+ const tile_r1 = ctx.$implicit;
17
+ i0.ɵɵattribute("data-tone", tile_r1.tone);
18
+ i0.ɵɵadvance(2);
19
+ i0.ɵɵtextInterpolate(tile_r1.label);
20
+ i0.ɵɵadvance(2);
21
+ i0.ɵɵtextInterpolate(tile_r1.value);
22
+ i0.ɵɵadvance(2);
23
+ i0.ɵɵstyleProp("width", tile_r1.ratio * 100, "%");
24
+ } }
25
+ /**
26
+ * The strip that opens the panel.
27
+ *
28
+ * Every other ng-prism panel leads with a summary — the a11y score ring, the
29
+ * coverage stat row — so the visual regression panel does too, and it borrows
30
+ * their card, micro-label and mono-numeral treatment rather than inventing a
31
+ * third one.
32
+ */
33
+ export class VrtSummaryComponent {
34
+ summary = input.required(/* @ts-ignore */
35
+ ...(ngDevMode ? [{ debugName: "summary" }] : /* istanbul ignore next */ []));
36
+ tiles = computed(() => summaryTiles(this.summary()), /* @ts-ignore */
37
+ ...(ngDevMode ? [{ debugName: "tiles" }] : /* istanbul ignore next */ []));
38
+ static ɵfac = function VrtSummaryComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || VrtSummaryComponent)(); };
39
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: VrtSummaryComponent, selectors: [["prism-vrt-summary"]], inputs: { summary: [1, "summary"] }, decls: 3, vars: 0, consts: [[1, "vrt-sum"], [1, "vrt-sum__tile"], [1, "vrt-sum__label"], [1, "vrt-sum__value"], [1, "vrt-sum__bar"], [1, "vrt-sum__bar-fill"]], template: function VrtSummaryComponent_Template(rf, ctx) { if (rf & 1) {
40
+ i0.ɵɵdomElementStart(0, "div", 0);
41
+ i0.ɵɵrepeaterCreate(1, VrtSummaryComponent_For_2_Template, 7, 5, "div", 1, _forTrack0);
42
+ i0.ɵɵdomElementEnd();
43
+ } if (rf & 2) {
44
+ i0.ɵɵadvance();
45
+ i0.ɵɵrepeater(ctx.tiles());
46
+ } }, styles: ["[_nghost-%COMP%] { display: block; }\n\n .vrt-sum[_ngcontent-%COMP%] {\n display: grid;\n \n\n grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));\n gap: 10px;\n }\n\n .vrt-sum__tile[_ngcontent-%COMP%] {\n padding: 10px 12px;\n background: var(--prism-bg-surface);\n border: 1px solid var(--prism-border);\n border-radius: var(--radius-lg);\n min-width: 0;\n }\n\n .vrt-sum__label[_ngcontent-%COMP%] {\n font-size: 10.5px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n font-weight: 700;\n color: var(--prism-text-ghost);\n margin-bottom: 5px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .vrt-sum__value[_ngcontent-%COMP%] {\n font-family: var(--font-mono);\n font-size: 21px;\n font-weight: 700;\n letter-spacing: -0.02em;\n color: var(--tone-color, var(--prism-text));\n line-height: 1.1;\n }\n\n .vrt-sum__bar[_ngcontent-%COMP%] {\n margin-top: 8px;\n height: 4px;\n background: var(--prism-input-bg);\n border-radius: 2px;\n overflow: hidden;\n }\n .vrt-sum__bar-fill[_ngcontent-%COMP%] {\n height: 100%;\n border-radius: 2px;\n background: var(--tone-color, var(--prism-primary));\n transition: width var(--dur-slow) var(--ease-default);\n }\n\n [data-tone='success'][_ngcontent-%COMP%] { --tone-color: var(--prism-success); }\n [data-tone='danger'][_ngcontent-%COMP%] { --tone-color: var(--prism-danger); }\n [data-tone='warn'][_ngcontent-%COMP%] { --tone-color: var(--prism-warn); }\n [data-tone='neutral'][_ngcontent-%COMP%] { --tone-color: var(--prism-primary); }\n [data-tone='muted'][_ngcontent-%COMP%] { --tone-color: var(--prism-text-muted); }"] });
47
+ }
48
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(VrtSummaryComponent, [{
49
+ type: Component,
50
+ args: [{ selector: 'prism-vrt-summary', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
51
+ <div class="vrt-sum">
52
+ @for (tile of tiles(); track tile.key) {
53
+ <div class="vrt-sum__tile" [attr.data-tone]="tile.tone">
54
+ <div class="vrt-sum__label">{{ tile.label }}</div>
55
+ <div class="vrt-sum__value">{{ tile.value }}</div>
56
+ <div class="vrt-sum__bar">
57
+ <div
58
+ class="vrt-sum__bar-fill"
59
+ [style.width.%]="tile.ratio * 100"
60
+ ></div>
61
+ </div>
62
+ </div>
63
+ }
64
+ </div>
65
+ `, styles: ["\n :host { display: block; }\n\n .vrt-sum {\n display: grid;\n /* auto-fit rather than a fixed column count: the strip carries between\n two and six tiles depending on what the run produced. */\n grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));\n gap: 10px;\n }\n\n .vrt-sum__tile {\n padding: 10px 12px;\n background: var(--prism-bg-surface);\n border: 1px solid var(--prism-border);\n border-radius: var(--radius-lg);\n min-width: 0;\n }\n\n .vrt-sum__label {\n font-size: 10.5px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n font-weight: 700;\n color: var(--prism-text-ghost);\n margin-bottom: 5px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .vrt-sum__value {\n font-family: var(--font-mono);\n font-size: 21px;\n font-weight: 700;\n letter-spacing: -0.02em;\n color: var(--tone-color, var(--prism-text));\n line-height: 1.1;\n }\n\n .vrt-sum__bar {\n margin-top: 8px;\n height: 4px;\n background: var(--prism-input-bg);\n border-radius: 2px;\n overflow: hidden;\n }\n .vrt-sum__bar-fill {\n height: 100%;\n border-radius: 2px;\n background: var(--tone-color, var(--prism-primary));\n transition: width var(--dur-slow) var(--ease-default);\n }\n\n [data-tone='success'] { --tone-color: var(--prism-success); }\n [data-tone='danger'] { --tone-color: var(--prism-danger); }\n [data-tone='warn'] { --tone-color: var(--prism-warn); }\n [data-tone='neutral'] { --tone-color: var(--prism-primary); }\n [data-tone='muted'] { --tone-color: var(--prism-text-muted); }\n "] }]
66
+ }], null, { summary: [{ type: i0.Input, args: [{ isSignal: true, alias: "summary", required: true }] }] }); })();
67
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(VrtSummaryComponent, { className: "VrtSummaryComponent", filePath: "vrt-summary.component.ts", lineNumber: 98 }); })();
@@ -0,0 +1,36 @@
1
+ /** `'fit'` scales to the stage within bounds; the numbers are exact multiples. */
2
+ export type Zoom = 'fit' | 1 | 2 | 4;
3
+ export declare const ZOOMS: readonly Zoom[];
4
+ /**
5
+ * How far `fit` may enlarge a capture.
6
+ *
7
+ * Without a ceiling a 98px-wide button blown up to a 900px stage lands at 9×,
8
+ * which is a mosaic, not a comparison. Capping at the largest explicit step
9
+ * keeps `fit` inside the range the zoom buttons already offer.
10
+ */
11
+ export declare const MAX_FIT_SCALE = 4;
12
+ /**
13
+ * Tallest a capture is drawn under `fit`, in CSS pixels.
14
+ *
15
+ * Width alone is not enough of a bound: a 302×229 card stretched to a 1200px
16
+ * stage is 900px tall and pushes its own comparison slider off screen.
17
+ */
18
+ export declare const MAX_FIT_HEIGHT = 400;
19
+ /**
20
+ * Width declarations for the frame that holds a capture.
21
+ *
22
+ * The frame — not the image — carries the width, so the comparison slider and
23
+ * the legend below it stay exactly as wide as the image they describe.
24
+ *
25
+ * `fit` fills the frame's share of the stage, bounded on both sides:
26
+ * `min-width` holds the capture's natural size, because a diff read below 1:1
27
+ * is a diff you cannot trust, and `max-width` stops a small capture from being
28
+ * blown up past {@link MAX_FIT_SCALE} or taller than {@link MAX_FIT_HEIGHT}.
29
+ * The share itself comes from `--vrt-fit-basis`, which the stage sets to the
30
+ * full width, or to half of it side by side.
31
+ *
32
+ * The numeric steps are deliberately unbounded: asking for 4× means 4×, even
33
+ * when that overflows the stage and scrolls.
34
+ */
35
+ export declare function frameWidthStyle(width: number | undefined, height: number | undefined, zoom: Zoom): Record<string, string>;
36
+ //# sourceMappingURL=vrt-zoom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vrt-zoom.d.ts","sourceRoot":"","sources":["../src/vrt-zoom.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAErC,eAAO,MAAM,KAAK,EAAE,SAAS,IAAI,EAAqB,CAAC;AAEvD;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,IAAI,CAAC;AAE/B;;;;;GAKG;AACH,eAAO,MAAM,cAAc,MAAM,CAAC;AAElC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,IAAI,GACT,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkBxB"}
@@ -0,0 +1,48 @@
1
+ export const ZOOMS = ['fit', 1, 2, 4];
2
+ /**
3
+ * How far `fit` may enlarge a capture.
4
+ *
5
+ * Without a ceiling a 98px-wide button blown up to a 900px stage lands at 9×,
6
+ * which is a mosaic, not a comparison. Capping at the largest explicit step
7
+ * keeps `fit` inside the range the zoom buttons already offer.
8
+ */
9
+ export const MAX_FIT_SCALE = 4;
10
+ /**
11
+ * Tallest a capture is drawn under `fit`, in CSS pixels.
12
+ *
13
+ * Width alone is not enough of a bound: a 302×229 card stretched to a 1200px
14
+ * stage is 900px tall and pushes its own comparison slider off screen.
15
+ */
16
+ export const MAX_FIT_HEIGHT = 400;
17
+ /**
18
+ * Width declarations for the frame that holds a capture.
19
+ *
20
+ * The frame — not the image — carries the width, so the comparison slider and
21
+ * the legend below it stay exactly as wide as the image they describe.
22
+ *
23
+ * `fit` fills the frame's share of the stage, bounded on both sides:
24
+ * `min-width` holds the capture's natural size, because a diff read below 1:1
25
+ * is a diff you cannot trust, and `max-width` stops a small capture from being
26
+ * blown up past {@link MAX_FIT_SCALE} or taller than {@link MAX_FIT_HEIGHT}.
27
+ * The share itself comes from `--vrt-fit-basis`, which the stage sets to the
28
+ * full width, or to half of it side by side.
29
+ *
30
+ * The numeric steps are deliberately unbounded: asking for 4× means 4×, even
31
+ * when that overflows the stage and scrolls.
32
+ */
33
+ export function frameWidthStyle(width, height, zoom) {
34
+ if (typeof width !== 'number' || !Number.isFinite(width) || width <= 0) {
35
+ return {};
36
+ }
37
+ if (zoom !== 'fit')
38
+ return { width: `${width * zoom}px` };
39
+ // The height budget expressed as a width, so one `max-width` enforces both.
40
+ const heightCap = typeof height === 'number' && Number.isFinite(height) && height > 0
41
+ ? width * (MAX_FIT_HEIGHT / height)
42
+ : Infinity;
43
+ return {
44
+ width: 'var(--vrt-fit-basis, 100%)',
45
+ 'min-width': `${width}px`,
46
+ 'max-width': `${Math.round(Math.min(width * MAX_FIT_SCALE, heightCap))}px`,
47
+ };
48
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@ng-prism/plugin-visual-regression",
3
+ "version": "22.2.0-beta.1",
4
+ "description": "Visual regression report panel for ng-prism.",
5
+ "ng-add": {
6
+ "save": "devDependencies"
7
+ },
8
+ "schematics": "./schematics/collection.json",
9
+ "keywords": [
10
+ "@ng-prism/core",
11
+ "visual-regression",
12
+ "screenshot",
13
+ "testing",
14
+ "plugin",
15
+ "angular",
16
+ "styleguide"
17
+ ],
18
+ "license": "MIT",
19
+ "type": "module",
20
+ "main": "./dist/index.js",
21
+ "module": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ "./package.json": "./package.json",
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "browser": "./dist/index.browser.js",
28
+ "import": "./dist/index.js",
29
+ "default": "./dist/index.js"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "schematics",
35
+ "!**/*.tsbuildinfo"
36
+ ],
37
+ "peerDependencies": {
38
+ "@angular/core": ">=20.0.0",
39
+ "@ng-prism/core": "^22.2.0-0"
40
+ },
41
+ "devDependencies": {
42
+ "@ng-prism/core": "*"
43
+ },
44
+ "dependencies": {
45
+ "tslib": "^2.3.0"
46
+ },
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "https://github.com/dyingangel666/ng-prism.git",
50
+ "directory": "packages/plugin-visual-regression"
51
+ },
52
+ "homepage": "https://dyingangel666.github.io/ng-prism/",
53
+ "bugs": "https://github.com/dyingangel666/ng-prism/issues"
54
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
3
+ "schematics": {
4
+ "ng-add": {
5
+ "description": "Adds @ng-prism/plugin-visual-regression to the ng-prism config",
6
+ "factory": "../dist/schematics/ng-add/index#ngAdd",
7
+ "schema": "./ng-add/schema.json"
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "NgPrismPluginVisualRegressionNgAdd",
4
+ "title": "Add @ng-prism/plugin-visual-regression",
5
+ "description": "Registers @ng-prism/plugin-visual-regression in ng-prism.config.ts",
6
+ "type": "object",
7
+ "properties": {
8
+ "configPath": {
9
+ "type": "string",
10
+ "description": "Path to ng-prism.config.ts",
11
+ "default": "ng-prism.config.ts"
12
+ }
13
+ },
14
+ "required": [],
15
+ "additionalProperties": false
16
+ }