@ng-prism/plugin-figma 21.0.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/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # @ng-prism/plugin-figma
2
+
3
+ Figma design embed panel for [ng-prism](https://github.com/USERNAME/ng-prism). Displays Figma designs as interactive iframes directly in your component styleguide.
4
+
5
+ Uses Figma's official embed endpoint — no API token required. Works with any publicly shared Figma file.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @ng-prism/plugin-figma
11
+ ```
12
+
13
+ ### Peer Dependencies
14
+
15
+ | Package | Version |
16
+ |---|---|
17
+ | `ng-prism` | `>=21.0.0` |
18
+ | `@angular/core` | `>=20.0.0` |
19
+
20
+ ## Usage
21
+
22
+ ### 1. Register the plugin
23
+
24
+ Add `figmaPlugin()` to your `ng-prism.config.ts`:
25
+
26
+ ```typescript
27
+ import { defineConfig } from 'ng-prism/config';
28
+ import { figmaPlugin } from '@ng-prism/plugin-figma';
29
+
30
+ export default defineConfig({
31
+ plugins: [figmaPlugin()],
32
+ });
33
+ ```
34
+
35
+ ### 2. Link Figma designs to components
36
+
37
+ Add a `figma` key to the `meta` field of your `@Showcase` decorator:
38
+
39
+ ```typescript
40
+ import { Component, input, output } from '@angular/core';
41
+ import { Showcase } from 'ng-prism';
42
+
43
+ @Showcase({
44
+ title: 'Button',
45
+ category: 'Inputs',
46
+ meta: {
47
+ figma: 'https://www.figma.com/file/abc123/my-design',
48
+ },
49
+ })
50
+ @Component({
51
+ selector: 'my-button',
52
+ standalone: true,
53
+ template: `<button>{{ label() }}</button>`,
54
+ })
55
+ export class ButtonComponent {
56
+ label = input('Button');
57
+ }
58
+ ```
59
+
60
+ A **Figma** tab appears in the panel area (next to Controls and Events). The tab displays an interactive Figma viewer with zoom and navigation support.
61
+
62
+ Components without a `meta.figma` URL show a placeholder message instead.
63
+
64
+ ### Supported URL formats
65
+
66
+ Any publicly shared Figma URL works:
67
+
68
+ - `https://www.figma.com/file/<id>/<name>` — full file
69
+ - `https://www.figma.com/file/<id>/<name>?node-id=<node>` — specific frame/component
70
+ - `https://www.figma.com/design/<id>/<name>` — new Figma URL format
71
+
72
+ ## How it works
73
+
74
+ The plugin registers a single panel (`FigmaPanelComponent`) that:
75
+
76
+ 1. Reads `meta.figma` from the active component's `@Showcase` config
77
+ 2. Constructs a Figma embed URL: `https://www.figma.com/embed?embed_host=ng-prism&url=<encoded-url>`
78
+ 3. Renders the embed in a sandboxed `<iframe>` with `allowfullscreen`
79
+ 4. Reactively updates when navigating between components (Angular Signals)
80
+
81
+ No build-time hooks — the plugin is purely runtime. The panel component is **lazy-loaded**
82
+ via `loadComponent` to avoid pulling in browser-only Angular dependencies (`DomSanitizer`)
83
+ at config-load time in Node.js.
84
+
85
+ ## API
86
+
87
+ ### `figmaPlugin()`
88
+
89
+ Factory function that returns an `NgPrismPlugin`. Zero-config — no arguments needed.
90
+
91
+ ```typescript
92
+ import { figmaPlugin } from '@ng-prism/plugin-figma';
93
+
94
+ const plugin = figmaPlugin();
95
+ // plugin.name === '@ng-prism/plugin-figma'
96
+ // plugin.panels === [{ id: 'figma', label: 'Figma', loadComponent: () => ... }]
97
+ ```
98
+
99
+ ### `FigmaPanelComponent`
100
+
101
+ The Angular component rendering the Figma iframe. Lazy-loaded by the plugin — typically
102
+ you don't need to import it directly. The type is exported for advanced use cases.
103
+
104
+ ## License
105
+
106
+ MIT
@@ -0,0 +1,2 @@
1
+ export declare function captureDomElement(element: HTMLElement | null): Promise<HTMLCanvasElement>;
2
+ //# sourceMappingURL=component-screenshot.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-screenshot.service.d.ts","sourceRoot":"","sources":["../../src/diff/component-screenshot.service.ts"],"names":[],"mappings":"AAAA,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAW/F"}
@@ -0,0 +1,11 @@
1
+ export async function captureDomElement(element) {
2
+ if (!element)
3
+ throw new Error('Kein gerendertes Element gefunden');
4
+ const { default: html2canvas } = await import('html2canvas');
5
+ return html2canvas(element, {
6
+ useCORS: true,
7
+ scale: window.devicePixelRatio || 1,
8
+ backgroundColor: null,
9
+ logging: false,
10
+ });
11
+ }
@@ -0,0 +1,3 @@
1
+ import type { DiffResult } from './figma-diff.types.js';
2
+ export declare function computeDesignDiff(componentCanvas: HTMLCanvasElement, figmaBlob: Blob): Promise<DiffResult>;
3
+ //# sourceMappingURL=design-diff.engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"design-diff.engine.d.ts","sourceRoot":"","sources":["../../src/diff/design-diff.engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,wBAAsB,iBAAiB,CACrC,eAAe,EAAE,iBAAiB,EAClC,SAAS,EAAE,IAAI,GACd,OAAO,CAAC,UAAU,CAAC,CAiCrB"}
@@ -0,0 +1,46 @@
1
+ export async function computeDesignDiff(componentCanvas, figmaBlob) {
2
+ const { default: pixelmatch } = await import('pixelmatch');
3
+ const w = componentCanvas.width;
4
+ const h = componentCanvas.height;
5
+ const figmaCanvas = await blobToCanvas(figmaBlob, w, h);
6
+ const diffCanvas = document.createElement('canvas');
7
+ diffCanvas.width = w;
8
+ diffCanvas.height = h;
9
+ const img1 = componentCanvas.getContext('2d').getImageData(0, 0, w, h);
10
+ const img2 = figmaCanvas.getContext('2d').getImageData(0, 0, w, h);
11
+ const ctxDiff = diffCanvas.getContext('2d');
12
+ const imgDiff = ctxDiff.createImageData(w, h);
13
+ const diffPixels = pixelmatch(img1.data, img2.data, imgDiff.data, w, h, {
14
+ threshold: 0.1,
15
+ includeAA: false,
16
+ });
17
+ ctxDiff.putImageData(imgDiff, 0, 0);
18
+ const totalPixels = w * h;
19
+ return {
20
+ similarity: ((totalPixels - diffPixels) / totalPixels) * 100,
21
+ diffPixels,
22
+ totalPixels,
23
+ componentDataUrl: componentCanvas.toDataURL(),
24
+ figmaDataUrl: figmaCanvas.toDataURL(),
25
+ diffDataUrl: diffCanvas.toDataURL(),
26
+ };
27
+ }
28
+ function blobToCanvas(blob, width, height) {
29
+ return new Promise((resolve, reject) => {
30
+ const url = URL.createObjectURL(blob);
31
+ const img = new Image();
32
+ img.onload = () => {
33
+ const canvas = document.createElement('canvas');
34
+ canvas.width = width;
35
+ canvas.height = height;
36
+ canvas.getContext('2d').drawImage(img, 0, 0, width, height);
37
+ URL.revokeObjectURL(url);
38
+ resolve(canvas);
39
+ };
40
+ img.onerror = () => {
41
+ URL.revokeObjectURL(url);
42
+ reject(new Error('Figma-Bild konnte nicht geladen werden'));
43
+ };
44
+ img.src = url;
45
+ });
46
+ }
@@ -0,0 +1,2 @@
1
+ export declare function fetchFigmaImage(fileKey: string, nodeId: string, token: string): Promise<Blob>;
2
+ //# sourceMappingURL=figma-api.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-api.service.d.ts","sourceRoot":"","sources":["../../src/diff/figma-api.service.ts"],"names":[],"mappings":"AAAA,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBnG"}
@@ -0,0 +1,17 @@
1
+ export async function fetchFigmaImage(fileKey, nodeId, token) {
2
+ const res = await fetch(`https://api.figma.com/v1/images/${fileKey}?ids=${encodeURIComponent(nodeId)}&format=png&scale=2`, { headers: { 'X-Figma-Token': token } });
3
+ if (!res.ok) {
4
+ const text = await res.text().catch(() => '');
5
+ throw new Error(`Figma API ${res.status}${text ? `: ${text}` : ''}`);
6
+ }
7
+ const json = await res.json();
8
+ if (json.err)
9
+ throw new Error(json.err);
10
+ const imageUrl = json.images?.[nodeId];
11
+ if (!imageUrl)
12
+ throw new Error(`Kein Bild für Node-ID "${nodeId}"`);
13
+ const imgRes = await fetch(imageUrl);
14
+ if (!imgRes.ok)
15
+ throw new Error(`CDN Fehler ${imgRes.status}`);
16
+ return imgRes.blob();
17
+ }
@@ -0,0 +1,26 @@
1
+ import { type DiffMode, type DiffResult, type DiffState } from './figma-diff.types.js';
2
+ import * as i0 from "@angular/core";
3
+ export declare class FigmaDesignDiffPanelComponent {
4
+ private readonly config;
5
+ private readonly nav;
6
+ private readonly renderer;
7
+ protected readonly state: import("@angular/core").WritableSignal<DiffState>;
8
+ protected readonly activeMode: import("@angular/core").WritableSignal<DiffMode>;
9
+ protected readonly overlayOpacity: import("@angular/core").WritableSignal<number>;
10
+ protected readonly modes: {
11
+ value: DiffMode;
12
+ label: string;
13
+ }[];
14
+ protected readonly doneResult: import("@angular/core").Signal<DiffResult | null>;
15
+ protected readonly similarity: import("@angular/core").Signal<string>;
16
+ protected readonly errorMessage: import("@angular/core").Signal<string>;
17
+ protected readonly activeVariantMeta: import("@angular/core").Signal<{
18
+ url: string;
19
+ nodeId: string;
20
+ } | null>;
21
+ protected runDiff(): void;
22
+ private executeDiff;
23
+ static ɵfac: i0.ɵɵFactoryDeclaration<FigmaDesignDiffPanelComponent, never>;
24
+ static ɵcmp: i0.ɵɵComponentDeclaration<FigmaDesignDiffPanelComponent, "prism-figma-design-diff-panel", never, {}, {}, never, never, true, never>;
25
+ }
26
+ //# sourceMappingURL=figma-design-diff-panel.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-design-diff-panel.component.d.ts","sourceRoot":"","sources":["../../src/diff/figma-design-diff-panel.component.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAkC,MAAM,uBAAuB,CAAC;;AAEvH,qBAmPa,6BAA6B;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA+B;IACtD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkC;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgC;IAEzD,SAAS,CAAC,QAAQ,CAAC,KAAK,oDAAyC;IACjE,SAAS,CAAC,QAAQ,CAAC,UAAU,mDAAoC;IACjE,SAAS,CAAC,QAAQ,CAAC,cAAc,iDAAc;IAE/C,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAI5D;IAEF,SAAS,CAAC,QAAQ,CAAC,UAAU,oDAG1B;IAEH,SAAS,CAAC,QAAQ,CAAC,UAAU,yCAG1B;IAEH,SAAS,CAAC,QAAQ,CAAC,YAAY,yCAG5B;IAEH,SAAS,CAAC,QAAQ,CAAC,iBAAiB;;;cAQjC;IAEH,SAAS,CAAC,OAAO,IAAI,IAAI;YAsBX,WAAW;yCA9Dd,6BAA6B;2CAA7B,6BAA6B;CAqFzC"}
@@ -0,0 +1,332 @@
1
+ import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';
2
+ import { PrismNavigationService, PrismRendererService } from '@ng-prism/core';
3
+ import { FIGMA_PLUGIN_CONFIG } from '../figma-config.token.js';
4
+ import { extractFileKey, parseFigmaMeta } from './figma-diff.types.js';
5
+ import * as i0 from "@angular/core";
6
+ const _forTrack0 = ($index, $item) => $item.value;
7
+ function FigmaDesignDiffPanelComponent_Case_1_Template(rf, ctx) { if (rf & 1) {
8
+ i0.ɵɵdomElementStart(0, "div", 1)(1, "p", 6);
9
+ i0.ɵɵtext(2, "Access Token fehlt");
10
+ i0.ɵɵdomElementEnd();
11
+ i0.ɵɵdomElementStart(3, "p", 7);
12
+ i0.ɵɵtext(4, " Konfiguriere ");
13
+ i0.ɵɵdomElementStart(5, "code");
14
+ i0.ɵɵtext(6, "figmaPlugin({ accessToken: '...' })");
15
+ i0.ɵɵdomElementEnd();
16
+ i0.ɵɵtext(7, " in deiner ");
17
+ i0.ɵɵdomElementStart(8, "code");
18
+ i0.ɵɵtext(9, "ng-prism.config.ts");
19
+ i0.ɵɵdomElementEnd();
20
+ i0.ɵɵtext(10, ". ");
21
+ i0.ɵɵdomElementEnd()();
22
+ } }
23
+ function FigmaDesignDiffPanelComponent_Case_2_Template(rf, ctx) { if (rf & 1) {
24
+ i0.ɵɵdomElementStart(0, "div", 1)(1, "p", 6);
25
+ i0.ɵɵtext(2, "Kein Figma-Node f\u00FCr diese Variante");
26
+ i0.ɵɵdomElementEnd();
27
+ i0.ɵɵdomElementStart(3, "p", 7);
28
+ i0.ɵɵtext(4, " F\u00FCge ");
29
+ i0.ɵɵdomElementStart(5, "code");
30
+ i0.ɵɵtext(6, "meta: { figma: '\u2026?node-id=12-34' }");
31
+ i0.ɵɵdomElementEnd();
32
+ i0.ɵɵtext(7, " zur aktiven Variante hinzu. ");
33
+ i0.ɵɵdomElementEnd()();
34
+ } }
35
+ function FigmaDesignDiffPanelComponent_Case_3_Template(rf, ctx) { if (rf & 1) {
36
+ i0.ɵɵdomElementStart(0, "div", 2)(1, "p", 6);
37
+ i0.ɵɵtext(2, "Figma API Fehler");
38
+ i0.ɵɵdomElementEnd();
39
+ i0.ɵɵdomElementStart(3, "p", 7);
40
+ i0.ɵɵtext(4);
41
+ i0.ɵɵdomElementEnd()();
42
+ } if (rf & 2) {
43
+ const ctx_r0 = i0.ɵɵnextContext();
44
+ i0.ɵɵadvance(4);
45
+ i0.ɵɵtextInterpolate(ctx_r0.errorMessage());
46
+ } }
47
+ function FigmaDesignDiffPanelComponent_Case_4_Template(rf, ctx) { if (rf & 1) {
48
+ const _r2 = i0.ɵɵgetCurrentView();
49
+ i0.ɵɵdomElementStart(0, "div", 3)(1, "button", 8);
50
+ i0.ɵɵdomListener("click", function FigmaDesignDiffPanelComponent_Case_4_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.runDiff()); });
51
+ i0.ɵɵtext(2, " \u25B6 Design Diff ausf\u00FChren ");
52
+ i0.ɵɵdomElementEnd()();
53
+ } }
54
+ function FigmaDesignDiffPanelComponent_Case_5_Template(rf, ctx) { if (rf & 1) {
55
+ i0.ɵɵdomElementStart(0, "div", 4);
56
+ i0.ɵɵdomElement(1, "span", 9);
57
+ i0.ɵɵdomElementStart(2, "span");
58
+ i0.ɵɵtext(3, "Lade Screenshots\u2026");
59
+ i0.ɵɵdomElementEnd()();
60
+ } }
61
+ function FigmaDesignDiffPanelComponent_Case_6_For_4_Template(rf, ctx) { if (rf & 1) {
62
+ const _r4 = i0.ɵɵgetCurrentView();
63
+ i0.ɵɵdomElementStart(0, "button", 18);
64
+ i0.ɵɵdomListener("click", function FigmaDesignDiffPanelComponent_Case_6_For_4_Template_button_click_0_listener() { const m_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.activeMode.set(m_r5.value)); });
65
+ i0.ɵɵtext(1);
66
+ i0.ɵɵdomElementEnd();
67
+ } if (rf & 2) {
68
+ const m_r5 = ctx.$implicit;
69
+ const ctx_r0 = i0.ɵɵnextContext(2);
70
+ i0.ɵɵclassProp("diff-panel__mode-btn--active", ctx_r0.activeMode() === m_r5.value);
71
+ i0.ɵɵadvance();
72
+ i0.ɵɵtextInterpolate(m_r5.label);
73
+ } }
74
+ function FigmaDesignDiffPanelComponent_Case_6_Conditional_10_Template(rf, ctx) { if (rf & 1) {
75
+ i0.ɵɵdomElementStart(0, "div", 16)(1, "div", 19)(2, "span", 20);
76
+ i0.ɵɵtext(3, "ng-prism");
77
+ i0.ɵɵdomElementEnd();
78
+ i0.ɵɵdomElement(4, "img", 21);
79
+ i0.ɵɵdomElementEnd();
80
+ i0.ɵɵdomElementStart(5, "div", 19)(6, "span", 20);
81
+ i0.ɵɵtext(7, "Figma");
82
+ i0.ɵɵdomElementEnd();
83
+ i0.ɵɵdomElement(8, "img", 22);
84
+ i0.ɵɵdomElementEnd()();
85
+ } if (rf & 2) {
86
+ const ctx_r0 = i0.ɵɵnextContext(2);
87
+ i0.ɵɵadvance(4);
88
+ i0.ɵɵdomProperty("src", ctx_r0.doneResult().componentDataUrl, i0.ɵɵsanitizeUrl);
89
+ i0.ɵɵadvance(4);
90
+ i0.ɵɵdomProperty("src", ctx_r0.doneResult().figmaDataUrl, i0.ɵɵsanitizeUrl);
91
+ } }
92
+ function FigmaDesignDiffPanelComponent_Case_6_Conditional_11_Template(rf, ctx) { if (rf & 1) {
93
+ const _r6 = i0.ɵɵgetCurrentView();
94
+ i0.ɵɵdomElementStart(0, "div", 23);
95
+ i0.ɵɵdomElement(1, "img", 24)(2, "img", 25);
96
+ i0.ɵɵdomElementEnd();
97
+ i0.ɵɵdomElementStart(3, "input", 26);
98
+ i0.ɵɵdomListener("input", function FigmaDesignDiffPanelComponent_Case_6_Conditional_11_Template_input_input_3_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.overlayOpacity.set(+$event.target.value)); });
99
+ i0.ɵɵdomElementEnd();
100
+ } if (rf & 2) {
101
+ const ctx_r0 = i0.ɵɵnextContext(2);
102
+ i0.ɵɵadvance();
103
+ i0.ɵɵdomProperty("src", ctx_r0.doneResult().componentDataUrl, i0.ɵɵsanitizeUrl);
104
+ i0.ɵɵadvance();
105
+ i0.ɵɵstyleProp("opacity", ctx_r0.overlayOpacity() / 100);
106
+ i0.ɵɵdomProperty("src", ctx_r0.doneResult().figmaDataUrl, i0.ɵɵsanitizeUrl);
107
+ i0.ɵɵadvance();
108
+ i0.ɵɵdomProperty("value", ctx_r0.overlayOpacity());
109
+ } }
110
+ function FigmaDesignDiffPanelComponent_Case_6_Conditional_12_Template(rf, ctx) { if (rf & 1) {
111
+ i0.ɵɵdomElement(0, "img", 17);
112
+ } if (rf & 2) {
113
+ const ctx_r0 = i0.ɵɵnextContext(2);
114
+ i0.ɵɵdomProperty("src", ctx_r0.doneResult().diffDataUrl, i0.ɵɵsanitizeUrl);
115
+ } }
116
+ function FigmaDesignDiffPanelComponent_Case_6_Template(rf, ctx) { if (rf & 1) {
117
+ const _r3 = i0.ɵɵgetCurrentView();
118
+ i0.ɵɵdomElementStart(0, "div", 5)(1, "div", 10)(2, "div", 11);
119
+ i0.ɵɵrepeaterCreate(3, FigmaDesignDiffPanelComponent_Case_6_For_4_Template, 2, 3, "button", 12, _forTrack0);
120
+ i0.ɵɵdomElementEnd();
121
+ i0.ɵɵdomElementStart(5, "span", 13);
122
+ i0.ɵɵtext(6);
123
+ i0.ɵɵdomElementEnd();
124
+ i0.ɵɵdomElementStart(7, "button", 14);
125
+ i0.ɵɵdomListener("click", function FigmaDesignDiffPanelComponent_Case_6_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r3); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.runDiff()); });
126
+ i0.ɵɵtext(8, " \u21BA Erneut ");
127
+ i0.ɵɵdomElementEnd()();
128
+ i0.ɵɵdomElementStart(9, "div", 15);
129
+ i0.ɵɵconditionalCreate(10, FigmaDesignDiffPanelComponent_Case_6_Conditional_10_Template, 9, 2, "div", 16);
130
+ i0.ɵɵconditionalCreate(11, FigmaDesignDiffPanelComponent_Case_6_Conditional_11_Template, 4, 5);
131
+ i0.ɵɵconditionalCreate(12, FigmaDesignDiffPanelComponent_Case_6_Conditional_12_Template, 1, 1, "img", 17);
132
+ i0.ɵɵdomElementEnd()();
133
+ } if (rf & 2) {
134
+ const ctx_r0 = i0.ɵɵnextContext();
135
+ i0.ɵɵadvance(3);
136
+ i0.ɵɵrepeater(ctx_r0.modes);
137
+ i0.ɵɵadvance(3);
138
+ i0.ɵɵtextInterpolate2(" ", ctx_r0.similarity(), "% match \u00B7 ", ctx_r0.doneResult().diffPixels.toLocaleString(), " px diff ");
139
+ i0.ɵɵadvance(4);
140
+ i0.ɵɵconditional(ctx_r0.activeMode() === "side-by-side" ? 10 : -1);
141
+ i0.ɵɵadvance();
142
+ i0.ɵɵconditional(ctx_r0.activeMode() === "overlay" ? 11 : -1);
143
+ i0.ɵɵadvance();
144
+ i0.ɵɵconditional(ctx_r0.activeMode() === "diff-only" ? 12 : -1);
145
+ } }
146
+ export class FigmaDesignDiffPanelComponent {
147
+ config = inject(FIGMA_PLUGIN_CONFIG);
148
+ nav = inject(PrismNavigationService);
149
+ renderer = inject(PrismRendererService);
150
+ state = signal({ status: 'idle' }, ...(ngDevMode ? [{ debugName: "state" }] : []));
151
+ activeMode = signal('side-by-side', ...(ngDevMode ? [{ debugName: "activeMode" }] : []));
152
+ overlayOpacity = signal(50, ...(ngDevMode ? [{ debugName: "overlayOpacity" }] : []));
153
+ modes = [
154
+ { value: 'side-by-side', label: 'Side-by-side' },
155
+ { value: 'overlay', label: 'Overlay' },
156
+ { value: 'diff-only', label: 'Diff' },
157
+ ];
158
+ doneResult = computed(() => {
159
+ const s = this.state();
160
+ return s.status === 'done' ? s.result : null;
161
+ }, ...(ngDevMode ? [{ debugName: "doneResult" }] : []));
162
+ similarity = computed(() => {
163
+ const r = this.doneResult();
164
+ return r ? r.similarity.toFixed(1) : '0';
165
+ }, ...(ngDevMode ? [{ debugName: "similarity" }] : []));
166
+ errorMessage = computed(() => {
167
+ const s = this.state();
168
+ return s.status === 'error-api' ? s.message : '';
169
+ }, ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
170
+ activeVariantMeta = computed(() => {
171
+ const comp = this.nav.activeComponent();
172
+ if (!comp)
173
+ return null;
174
+ const variants = comp.meta.showcaseConfig.variants;
175
+ const idx = this.renderer.activeVariantIndex();
176
+ const variantFigma = variants?.[idx]?.meta?.['figma'];
177
+ if (variantFigma !== undefined)
178
+ return parseFigmaMeta(variantFigma);
179
+ return parseFigmaMeta(comp.meta.showcaseConfig.meta?.['figma']);
180
+ }, ...(ngDevMode ? [{ debugName: "activeVariantMeta" }] : []));
181
+ runDiff() {
182
+ if (!this.config.accessToken) {
183
+ this.state.set({ status: 'error-no-token' });
184
+ return;
185
+ }
186
+ const meta = this.activeVariantMeta();
187
+ if (!meta) {
188
+ this.state.set({ status: 'error-no-node' });
189
+ return;
190
+ }
191
+ const fileKey = extractFileKey(meta.url);
192
+ if (!fileKey) {
193
+ this.state.set({ status: 'error-api', message: 'Ungültige Figma-URL' });
194
+ return;
195
+ }
196
+ this.state.set({ status: 'loading' });
197
+ this.executeDiff(fileKey, meta.nodeId);
198
+ }
199
+ async executeDiff(fileKey, nodeId) {
200
+ try {
201
+ const [{ computeDesignDiff }, { captureDomElement }, { fetchFigmaImage }] = await Promise.all([
202
+ import('./design-diff.engine.js'),
203
+ import('./component-screenshot.service.js'),
204
+ import('./figma-api.service.js'),
205
+ ]);
206
+ const element = this.renderer.renderedElement();
207
+ const [componentCanvas, figmaBlob] = await Promise.all([
208
+ captureDomElement(element),
209
+ fetchFigmaImage(fileKey, nodeId, this.config.accessToken),
210
+ ]);
211
+ const result = await computeDesignDiff(componentCanvas, figmaBlob);
212
+ this.state.set({ status: 'done', result });
213
+ }
214
+ catch (err) {
215
+ this.state.set({
216
+ status: 'error-api',
217
+ message: err instanceof Error ? err.message : 'Unbekannter Fehler',
218
+ });
219
+ }
220
+ }
221
+ static ɵfac = function FigmaDesignDiffPanelComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || FigmaDesignDiffPanelComponent)(); };
222
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: FigmaDesignDiffPanelComponent, selectors: [["prism-figma-design-diff-panel"]], decls: 7, vars: 1, consts: [[1, "diff-panel"], [1, "diff-panel__empty"], [1, "diff-panel__empty", "diff-panel__empty--error"], [1, "diff-panel__idle"], [1, "diff-panel__loading"], [1, "diff-panel__result"], [1, "diff-panel__empty-title"], [1, "diff-panel__empty-hint"], [1, "diff-panel__run-btn", 3, "click"], [1, "diff-panel__spinner"], [1, "diff-panel__toolbar"], [1, "diff-panel__modes"], [1, "diff-panel__mode-btn", 3, "diff-panel__mode-btn--active"], [1, "diff-panel__stats"], [1, "diff-panel__run-btn", "diff-panel__run-btn--sm", 3, "click"], [1, "diff-panel__canvas-area"], [1, "diff-panel__split"], ["alt", "Pixel diff", 3, "src"], [1, "diff-panel__mode-btn", 3, "click"], [1, "diff-panel__split-side"], [1, "diff-panel__split-label"], ["alt", "Component screenshot", 3, "src"], ["alt", "Figma design", 3, "src"], [1, "diff-panel__overlay-wrap"], ["alt", "Component", 1, "diff-panel__overlay-base", 3, "src"], ["alt", "Figma overlay", 1, "diff-panel__overlay-top", 3, "src"], ["type", "range", "min", "0", "max", "100", 1, "diff-panel__opacity-slider", 3, "input", "value"]], template: function FigmaDesignDiffPanelComponent_Template(rf, ctx) { if (rf & 1) {
223
+ i0.ɵɵdomElementStart(0, "div", 0);
224
+ i0.ɵɵconditionalCreate(1, FigmaDesignDiffPanelComponent_Case_1_Template, 11, 0, "div", 1)(2, FigmaDesignDiffPanelComponent_Case_2_Template, 8, 0, "div", 1)(3, FigmaDesignDiffPanelComponent_Case_3_Template, 5, 1, "div", 2)(4, FigmaDesignDiffPanelComponent_Case_4_Template, 3, 0, "div", 3)(5, FigmaDesignDiffPanelComponent_Case_5_Template, 4, 0, "div", 4)(6, FigmaDesignDiffPanelComponent_Case_6_Template, 13, 5, "div", 5);
225
+ i0.ɵɵdomElementEnd();
226
+ } if (rf & 2) {
227
+ let tmp_0_0;
228
+ i0.ɵɵadvance();
229
+ i0.ɵɵconditional((tmp_0_0 = ctx.state().status) === "error-no-token" ? 1 : tmp_0_0 === "error-no-node" ? 2 : tmp_0_0 === "error-api" ? 3 : tmp_0_0 === "idle" ? 4 : tmp_0_0 === "loading" ? 5 : tmp_0_0 === "done" ? 6 : -1);
230
+ } }, styles: [".diff-panel[_ngcontent-%COMP%] {\n height: 100%;\n display: flex;\n flex-direction: column;\n font-family: var(--prism-font-sans);\n font-size: 13px;\n color: var(--prism-text-1);\n background: var(--prism-bg-elevated);\n }\n\n .diff-panel__empty[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n gap: 8px;\n padding: 24px;\n text-align: center;\n }\n\n .diff-panel__empty-title[_ngcontent-%COMP%] { font-weight: 500; color: var(--prism-text-1); margin: 0; }\n .diff-panel__empty-hint[_ngcontent-%COMP%] { color: var(--prism-text-muted); margin: 0; }\n .diff-panel__empty--error[_ngcontent-%COMP%] .diff-panel__empty-title[_ngcontent-%COMP%] { color: #ef4444; }\n\n .diff-panel__idle[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n }\n\n .diff-panel__loading[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 10px;\n height: 100%;\n color: var(--prism-text-muted);\n }\n\n .diff-panel__spinner[_ngcontent-%COMP%] {\n width: 16px;\n height: 16px;\n border: 2px solid var(--prism-border);\n border-top-color: var(--prism-primary);\n border-radius: 50%;\n animation: _ngcontent-%COMP%_spin 0.7s linear infinite;\n }\n\n @keyframes _ngcontent-%COMP%_spin { to { transform: rotate(360deg); } }\n\n .diff-panel__run-btn[_ngcontent-%COMP%] {\n padding: 8px 16px;\n background: var(--prism-primary);\n color: #fff;\n border: none;\n border-radius: 6px;\n font-size: 13px;\n font-family: var(--prism-font-sans);\n cursor: pointer;\n }\n\n .diff-panel__run-btn[_ngcontent-%COMP%]:hover { opacity: 0.9; }\n .diff-panel__run-btn--sm[_ngcontent-%COMP%] { padding: 5px 10px; font-size: 12px; }\n\n .diff-panel__result[_ngcontent-%COMP%] { display: flex; flex-direction: column; height: 100%; }\n\n .diff-panel__toolbar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 8px 12px;\n border-bottom: 1px solid var(--prism-border);\n flex-shrink: 0;\n }\n\n .diff-panel__modes[_ngcontent-%COMP%] { display: flex; gap: 2px; }\n\n .diff-panel__mode-btn[_ngcontent-%COMP%] {\n padding: 4px 10px;\n border: 1px solid var(--prism-border);\n background: none;\n font-size: 12px;\n font-family: var(--prism-font-sans);\n color: var(--prism-text-muted);\n cursor: pointer;\n border-radius: 4px;\n }\n\n .diff-panel__mode-btn--active[_ngcontent-%COMP%] {\n background: var(--prism-primary);\n color: #fff;\n border-color: var(--prism-primary);\n }\n\n .diff-panel__stats[_ngcontent-%COMP%] {\n margin-left: auto;\n color: var(--prism-text-muted);\n font-variant-numeric: tabular-nums;\n }\n\n .diff-panel__canvas-area[_ngcontent-%COMP%] {\n flex: 1;\n overflow: auto;\n padding: 16px;\n display: flex;\n align-items: flex-start;\n justify-content: center;\n }\n\n .diff-panel__split[_ngcontent-%COMP%] { display: flex; gap: 16px; width: 100%; }\n\n .diff-panel__split-side[_ngcontent-%COMP%] { flex: 1; display: flex; flex-direction: column; gap: 6px; }\n\n .diff-panel__split-label[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 500;\n color: var(--prism-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n }\n\n .diff-panel__split-side[_ngcontent-%COMP%] img[_ngcontent-%COMP%], \n .diff-panel__canvas-area[_ngcontent-%COMP%] img[_ngcontent-%COMP%] {\n max-width: 100%;\n border: 1px solid var(--prism-border);\n border-radius: 4px;\n }\n\n .diff-panel__overlay-wrap[_ngcontent-%COMP%] { position: relative; display: inline-block; }\n\n .diff-panel__overlay-top[_ngcontent-%COMP%] {\n position: absolute;\n top: 0; left: 0;\n width: 100%; height: 100%;\n }\n\n .diff-panel__opacity-slider[_ngcontent-%COMP%] { display: block; width: 200px; margin-top: 12px; }"], changeDetection: 0 });
231
+ }
232
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FigmaDesignDiffPanelComponent, [{
233
+ type: Component,
234
+ args: [{ selector: 'prism-figma-design-diff-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
235
+ <div class="diff-panel">
236
+ @switch (state().status) {
237
+ @case ('error-no-token') {
238
+ <div class="diff-panel__empty">
239
+ <p class="diff-panel__empty-title">Access Token fehlt</p>
240
+ <p class="diff-panel__empty-hint">
241
+ Konfiguriere <code>figmaPlugin(&#123; accessToken: '...' &#125;)</code> in deiner
242
+ <code>ng-prism.config.ts</code>.
243
+ </p>
244
+ </div>
245
+ }
246
+ @case ('error-no-node') {
247
+ <div class="diff-panel__empty">
248
+ <p class="diff-panel__empty-title">Kein Figma-Node für diese Variante</p>
249
+ <p class="diff-panel__empty-hint">
250
+ Füge <code>meta: &#123; figma: '…?node-id=12-34' &#125;</code> zur aktiven Variante hinzu.
251
+ </p>
252
+ </div>
253
+ }
254
+ @case ('error-api') {
255
+ <div class="diff-panel__empty diff-panel__empty--error">
256
+ <p class="diff-panel__empty-title">Figma API Fehler</p>
257
+ <p class="diff-panel__empty-hint">{{ errorMessage() }}</p>
258
+ </div>
259
+ }
260
+ @case ('idle') {
261
+ <div class="diff-panel__idle">
262
+ <button class="diff-panel__run-btn" (click)="runDiff()">
263
+ ▶ Design Diff ausführen
264
+ </button>
265
+ </div>
266
+ }
267
+ @case ('loading') {
268
+ <div class="diff-panel__loading">
269
+ <span class="diff-panel__spinner"></span>
270
+ <span>Lade Screenshots…</span>
271
+ </div>
272
+ }
273
+ @case ('done') {
274
+ <div class="diff-panel__result">
275
+ <div class="diff-panel__toolbar">
276
+ <div class="diff-panel__modes">
277
+ @for (m of modes; track m.value) {
278
+ <button
279
+ class="diff-panel__mode-btn"
280
+ [class.diff-panel__mode-btn--active]="activeMode() === m.value"
281
+ (click)="activeMode.set(m.value)"
282
+ >{{ m.label }}</button>
283
+ }
284
+ </div>
285
+ <span class="diff-panel__stats">
286
+ {{ similarity() }}% match &middot; {{ doneResult()!.diffPixels.toLocaleString() }} px diff
287
+ </span>
288
+ <button class="diff-panel__run-btn diff-panel__run-btn--sm" (click)="runDiff()">
289
+ ↺ Erneut
290
+ </button>
291
+ </div>
292
+ <div class="diff-panel__canvas-area">
293
+ @if (activeMode() === 'side-by-side') {
294
+ <div class="diff-panel__split">
295
+ <div class="diff-panel__split-side">
296
+ <span class="diff-panel__split-label">ng-prism</span>
297
+ <img [src]="doneResult()!.componentDataUrl" alt="Component screenshot" />
298
+ </div>
299
+ <div class="diff-panel__split-side">
300
+ <span class="diff-panel__split-label">Figma</span>
301
+ <img [src]="doneResult()!.figmaDataUrl" alt="Figma design" />
302
+ </div>
303
+ </div>
304
+ }
305
+ @if (activeMode() === 'overlay') {
306
+ <div class="diff-panel__overlay-wrap">
307
+ <img class="diff-panel__overlay-base" [src]="doneResult()!.componentDataUrl" alt="Component" />
308
+ <img
309
+ class="diff-panel__overlay-top"
310
+ [src]="doneResult()!.figmaDataUrl"
311
+ [style.opacity]="overlayOpacity() / 100"
312
+ alt="Figma overlay"
313
+ />
314
+ </div>
315
+ <input
316
+ class="diff-panel__opacity-slider"
317
+ type="range" min="0" max="100"
318
+ [value]="overlayOpacity()"
319
+ (input)="overlayOpacity.set(+$any($event.target).value)"
320
+ />
321
+ }
322
+ @if (activeMode() === 'diff-only') {
323
+ <img [src]="doneResult()!.diffDataUrl" alt="Pixel diff" />
324
+ }
325
+ </div>
326
+ </div>
327
+ }
328
+ }
329
+ </div>
330
+ `, styles: ["\n .diff-panel {\n height: 100%;\n display: flex;\n flex-direction: column;\n font-family: var(--prism-font-sans);\n font-size: 13px;\n color: var(--prism-text-1);\n background: var(--prism-bg-elevated);\n }\n\n .diff-panel__empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n gap: 8px;\n padding: 24px;\n text-align: center;\n }\n\n .diff-panel__empty-title { font-weight: 500; color: var(--prism-text-1); margin: 0; }\n .diff-panel__empty-hint { color: var(--prism-text-muted); margin: 0; }\n .diff-panel__empty--error .diff-panel__empty-title { color: #ef4444; }\n\n .diff-panel__idle {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n }\n\n .diff-panel__loading {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 10px;\n height: 100%;\n color: var(--prism-text-muted);\n }\n\n .diff-panel__spinner {\n width: 16px;\n height: 16px;\n border: 2px solid var(--prism-border);\n border-top-color: var(--prism-primary);\n border-radius: 50%;\n animation: spin 0.7s linear infinite;\n }\n\n @keyframes spin { to { transform: rotate(360deg); } }\n\n .diff-panel__run-btn {\n padding: 8px 16px;\n background: var(--prism-primary);\n color: #fff;\n border: none;\n border-radius: 6px;\n font-size: 13px;\n font-family: var(--prism-font-sans);\n cursor: pointer;\n }\n\n .diff-panel__run-btn:hover { opacity: 0.9; }\n .diff-panel__run-btn--sm { padding: 5px 10px; font-size: 12px; }\n\n .diff-panel__result { display: flex; flex-direction: column; height: 100%; }\n\n .diff-panel__toolbar {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 8px 12px;\n border-bottom: 1px solid var(--prism-border);\n flex-shrink: 0;\n }\n\n .diff-panel__modes { display: flex; gap: 2px; }\n\n .diff-panel__mode-btn {\n padding: 4px 10px;\n border: 1px solid var(--prism-border);\n background: none;\n font-size: 12px;\n font-family: var(--prism-font-sans);\n color: var(--prism-text-muted);\n cursor: pointer;\n border-radius: 4px;\n }\n\n .diff-panel__mode-btn--active {\n background: var(--prism-primary);\n color: #fff;\n border-color: var(--prism-primary);\n }\n\n .diff-panel__stats {\n margin-left: auto;\n color: var(--prism-text-muted);\n font-variant-numeric: tabular-nums;\n }\n\n .diff-panel__canvas-area {\n flex: 1;\n overflow: auto;\n padding: 16px;\n display: flex;\n align-items: flex-start;\n justify-content: center;\n }\n\n .diff-panel__split { display: flex; gap: 16px; width: 100%; }\n\n .diff-panel__split-side { flex: 1; display: flex; flex-direction: column; gap: 6px; }\n\n .diff-panel__split-label {\n font-size: 11px;\n font-weight: 500;\n color: var(--prism-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n }\n\n .diff-panel__split-side img,\n .diff-panel__canvas-area img {\n max-width: 100%;\n border: 1px solid var(--prism-border);\n border-radius: 4px;\n }\n\n .diff-panel__overlay-wrap { position: relative; display: inline-block; }\n\n .diff-panel__overlay-top {\n position: absolute;\n top: 0; left: 0;\n width: 100%; height: 100%;\n }\n\n .diff-panel__opacity-slider { display: block; width: 200px; margin-top: 12px; }\n "] }]
331
+ }], null, null); })();
332
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(FigmaDesignDiffPanelComponent, { className: "FigmaDesignDiffPanelComponent", filePath: "diff/figma-design-diff-panel.component.ts", lineNumber: 249 }); })();
@@ -0,0 +1,34 @@
1
+ export type FigmaMeta = string | {
2
+ url: string;
3
+ nodeId?: string;
4
+ };
5
+ export type DiffMode = 'side-by-side' | 'overlay' | 'diff-only';
6
+ export type DiffState = {
7
+ status: 'idle';
8
+ } | {
9
+ status: 'loading';
10
+ } | {
11
+ status: 'done';
12
+ result: DiffResult;
13
+ } | {
14
+ status: 'error-no-token';
15
+ } | {
16
+ status: 'error-no-node';
17
+ } | {
18
+ status: 'error-api';
19
+ message: string;
20
+ };
21
+ export interface DiffResult {
22
+ similarity: number;
23
+ diffPixels: number;
24
+ totalPixels: number;
25
+ componentDataUrl: string;
26
+ figmaDataUrl: string;
27
+ diffDataUrl: string;
28
+ }
29
+ export declare function parseFigmaMeta(raw: unknown): {
30
+ url: string;
31
+ nodeId: string;
32
+ } | null;
33
+ export declare function extractFileKey(url: string): string | null;
34
+ //# sourceMappingURL=figma-diff.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-diff.types.d.ts","sourceRoot":"","sources":["../../src/diff/figma-diff.types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAElE,MAAM,MAAM,QAAQ,GAAG,cAAc,GAAG,SAAS,GAAG,WAAW,CAAC;AAEhE,MAAM,MAAM,SAAS,GACjB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GACrB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GACtC;IAAE,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAC5B;IAAE,MAAM,EAAE,eAAe,CAAA;CAAE,GAC3B;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAYnF;AAaD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGzD"}
@@ -0,0 +1,29 @@
1
+ export function parseFigmaMeta(raw) {
2
+ if (typeof raw === 'string') {
3
+ return extractNodeId(raw);
4
+ }
5
+ if (raw && typeof raw === 'object' && 'url' in raw) {
6
+ const obj = raw;
7
+ if (obj.nodeId) {
8
+ return { url: obj.url, nodeId: obj.nodeId };
9
+ }
10
+ return extractNodeId(obj.url);
11
+ }
12
+ return null;
13
+ }
14
+ function extractNodeId(url) {
15
+ try {
16
+ const u = new URL(url);
17
+ const nodeId = u.searchParams.get('node-id');
18
+ if (!nodeId)
19
+ return null;
20
+ return { url, nodeId: nodeId.replace(/-/g, ':') };
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ export function extractFileKey(url) {
27
+ const match = url.match(/figma\.com\/(?:design|file)\/([^/?#]+)/);
28
+ return match?.[1] ?? null;
29
+ }
@@ -0,0 +1,6 @@
1
+ import { InjectionToken } from '@angular/core';
2
+ export interface FigmaPluginOptions {
3
+ accessToken?: string;
4
+ }
5
+ export declare const FIGMA_PLUGIN_CONFIG: InjectionToken<FigmaPluginOptions>;
6
+ //# sourceMappingURL=figma-config.token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-config.token.d.ts","sourceRoot":"","sources":["../src/figma-config.token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,mBAAmB,oCAAgE,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { InjectionToken } from '@angular/core';
2
+ export const FIGMA_PLUGIN_CONFIG = new InjectionToken('FIGMA_PLUGIN_CONFIG');
@@ -0,0 +1,11 @@
1
+ import * as i0 from "@angular/core";
2
+ export declare class FigmaPanelComponent {
3
+ private readonly renderer;
4
+ private readonly iframe;
5
+ readonly activeComponent: import("@angular/core").InputSignal<unknown>;
6
+ protected readonly figmaUrl: import("@angular/core").Signal<string | null>;
7
+ constructor();
8
+ static ɵfac: i0.ɵɵFactoryDeclaration<FigmaPanelComponent, never>;
9
+ static ɵcmp: i0.ɵɵComponentDeclaration<FigmaPanelComponent, "prism-figma-panel", never, { "activeComponent": { "alias": "activeComponent"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
10
+ }
11
+ //# sourceMappingURL=figma-panel.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-panel.component.d.ts","sourceRoot":"","sources":["../src/figma-panel.component.ts"],"names":[],"mappings":";AAYA,qBAkDa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAE1D,QAAQ,CAAC,eAAe,+CAAwB;IAEhD,SAAS,CAAC,QAAQ,CAAC,QAAQ,gDAKxB;;yCAXQ,mBAAmB;2CAAnB,mBAAmB;CA0B/B"}
@@ -0,0 +1,66 @@
1
+ import { ChangeDetectionStrategy, Component, computed, effect, inject, input, Renderer2, viewChild, } from '@angular/core';
2
+ import * as i0 from "@angular/core";
3
+ const _c0 = ["iframe"];
4
+ function FigmaPanelComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
5
+ i0.ɵɵdomElement(0, "iframe", 1, 0);
6
+ } }
7
+ function FigmaPanelComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
8
+ i0.ɵɵdomElementStart(0, "div", 2);
9
+ i0.ɵɵtext(1, " No Figma design linked. Add ");
10
+ i0.ɵɵdomElementStart(2, "code");
11
+ i0.ɵɵtext(3);
12
+ i0.ɵɵdomElementEnd();
13
+ i0.ɵɵtext(4, " to @Showcase. ");
14
+ i0.ɵɵdomElementEnd();
15
+ } if (rf & 2) {
16
+ i0.ɵɵadvance(3);
17
+ i0.ɵɵtextInterpolate2("meta: ", "{", " figma: 'https://...' ", "}");
18
+ } }
19
+ export class FigmaPanelComponent {
20
+ renderer = inject(Renderer2);
21
+ iframe = viewChild('iframe', ...(ngDevMode ? [{ debugName: "iframe" }] : []));
22
+ activeComponent = input(null, ...(ngDevMode ? [{ debugName: "activeComponent" }] : []));
23
+ figmaUrl = computed(() => {
24
+ const comp = this.activeComponent();
25
+ if (!comp)
26
+ return null;
27
+ const url = comp.meta?.showcaseConfig?.meta?.['figma'];
28
+ return typeof url === 'string' ? url : null;
29
+ }, ...(ngDevMode ? [{ debugName: "figmaUrl" }] : []));
30
+ constructor() {
31
+ effect(() => {
32
+ const url = this.figmaUrl();
33
+ const el = this.iframe()?.nativeElement;
34
+ if (el && url) {
35
+ this.renderer.setAttribute(el, 'src', `https://www.figma.com/embed?embed_host=ng-prism&url=${encodeURIComponent(url)}`);
36
+ }
37
+ });
38
+ }
39
+ static ɵfac = function FigmaPanelComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || FigmaPanelComponent)(); };
40
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: FigmaPanelComponent, selectors: [["prism-figma-panel"]], viewQuery: function FigmaPanelComponent_Query(rf, ctx) { if (rf & 1) {
41
+ i0.ɵɵviewQuerySignal(ctx.iframe, _c0, 5);
42
+ } if (rf & 2) {
43
+ i0.ɵɵqueryAdvance();
44
+ } }, inputs: { activeComponent: [1, "activeComponent"] }, decls: 2, vars: 1, consts: [["iframe", ""], ["allowfullscreen", "", 1, "prism-figma-panel__iframe"], [1, "prism-figma-panel__empty"]], template: function FigmaPanelComponent_Template(rf, ctx) { if (rf & 1) {
45
+ i0.ɵɵconditionalCreate(0, FigmaPanelComponent_Conditional_0_Template, 2, 0, "iframe", 1)(1, FigmaPanelComponent_Conditional_1_Template, 5, 2, "div", 2);
46
+ } if (rf & 2) {
47
+ i0.ɵɵconditional(ctx.figmaUrl() ? 0 : 1);
48
+ } }, styles: ["[_nghost-%COMP%] {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .prism-figma-panel__iframe[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n min-height: 400px;\n border: none;\n }\n\n .prism-figma-panel__empty[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n min-height: 200px;\n color: var(--prism-text-muted, #6b7280);\n font-size: 14px;\n }\n\n .prism-figma-panel__empty[_ngcontent-%COMP%] code[_ngcontent-%COMP%] {\n font-family: var(--prism-font-mono, monospace);\n background: var(--prism-bg-surface, #f3f4f6);\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 13px;\n }"], changeDetection: 0 });
49
+ }
50
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FigmaPanelComponent, [{
51
+ type: Component,
52
+ args: [{ selector: 'prism-figma-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
53
+ @if (figmaUrl()) {
54
+ <iframe
55
+ #iframe
56
+ class="prism-figma-panel__iframe"
57
+ allowfullscreen
58
+ ></iframe>
59
+ } @else {
60
+ <div class="prism-figma-panel__empty">
61
+ No Figma design linked. Add <code>meta: {{ '{' }} figma: 'https://...' {{ '}' }}</code> to &#64;Showcase.
62
+ </div>
63
+ }
64
+ `, styles: ["\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .prism-figma-panel__iframe {\n width: 100%;\n height: 100%;\n min-height: 400px;\n border: none;\n }\n\n .prism-figma-panel__empty {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n min-height: 200px;\n color: var(--prism-text-muted, #6b7280);\n font-size: 14px;\n }\n\n .prism-figma-panel__empty code {\n font-family: var(--prism-font-mono, monospace);\n background: var(--prism-bg-surface, #f3f4f6);\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 13px;\n }\n "] }]
65
+ }], () => [], { iframe: [{ type: i0.ViewChild, args: ['iframe', { isSignal: true }] }], activeComponent: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeComponent", required: false }] }] }); })();
66
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(FigmaPanelComponent, { className: "FigmaPanelComponent", filePath: "figma-panel.component.ts", lineNumber: 63 }); })();
@@ -0,0 +1,4 @@
1
+ import type { NgPrismPlugin } from '@ng-prism/core/plugin';
2
+ import { type FigmaPluginOptions } from './figma-config.token.js';
3
+ export declare function figmaPlugin(options?: FigmaPluginOptions): NgPrismPlugin;
4
+ //# sourceMappingURL=figma-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-plugin.d.ts","sourceRoot":"","sources":["../src/figma-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAuB,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAGvF,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,aAAa,CAsB3E"}
@@ -0,0 +1,22 @@
1
+ import { FIGMA_PLUGIN_CONFIG } from './figma-config.token.js';
2
+ import { FigmaPanelComponent } from './figma-panel.component.js';
3
+ export function figmaPlugin(options = {}) {
4
+ return {
5
+ name: '@ng-prism/plugin-figma',
6
+ panels: [
7
+ {
8
+ id: 'figma',
9
+ label: 'Figma',
10
+ component: FigmaPanelComponent,
11
+ position: 'bottom',
12
+ },
13
+ {
14
+ id: 'figma-diff',
15
+ label: 'Design Diff',
16
+ loadComponent: () => import('./diff/figma-design-diff-panel.component.js').then((m) => m.FigmaDesignDiffPanelComponent),
17
+ position: 'bottom',
18
+ providers: [{ provide: FIGMA_PLUGIN_CONFIG, useValue: options }],
19
+ },
20
+ ],
21
+ };
22
+ }
@@ -0,0 +1,4 @@
1
+ export { figmaPlugin } from './figma-plugin.js';
2
+ export { FigmaPanelComponent } from './figma-panel.component.js';
3
+ export { FIGMA_PLUGIN_CONFIG, type FigmaPluginOptions } from './figma-config.token.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { figmaPlugin } from './figma-plugin.js';
2
+ export { FigmaPanelComponent } from './figma-panel.component.js';
3
+ export { FIGMA_PLUGIN_CONFIG } from './figma-config.token.js';
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@ng-prism/plugin-figma",
3
+ "version": "21.0.0",
4
+ "description": "Figma design embed panel for ng-prism.",
5
+ "keywords": [
6
+ "@ng-prism/core",
7
+ "figma",
8
+ "plugin",
9
+ "angular",
10
+ "styleguide"
11
+ ],
12
+ "license": "MIT",
13
+ "type": "module",
14
+ "main": "./dist/index.js",
15
+ "module": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ "./package.json": "./package.json",
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js",
22
+ "default": "./dist/index.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "!**/*.tsbuildinfo"
28
+ ],
29
+ "peerDependencies": {
30
+ "@angular/core": ">=20.0.0",
31
+ "html2canvas": ">=1.4.0",
32
+ "pixelmatch": ">=6.0.0",
33
+ "@ng-prism/core": ">=21.0.0"
34
+ },
35
+ "peerDependenciesMeta": {
36
+ "html2canvas": {
37
+ "optional": true
38
+ },
39
+ "pixelmatch": {
40
+ "optional": true
41
+ }
42
+ },
43
+ "devDependencies": {
44
+ "@ng-prism/core": "*"
45
+ },
46
+ "dependencies": {
47
+ "tslib": "^2.3.0"
48
+ },
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "https://github.com/dyingangel666/ng-prism.git",
52
+ "directory": "packages/plugin-figma"
53
+ },
54
+ "homepage": "https://dyingangel666.github.io/ng-prism/",
55
+ "bugs": "https://github.com/dyingangel666/ng-prism/issues"
56
+ }