@printwithsynergy/artwork-pdf-editor 0.3.0 → 0.5.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 (40) hide show
  1. package/dist/components/ContrastLegibilityPanel.d.ts +71 -0
  2. package/dist/components/ContrastLegibilityPanel.d.ts.map +1 -0
  3. package/dist/components/ContrastLegibilityPanel.js +123 -0
  4. package/dist/components/ContrastLegibilityPanel.js.map +1 -0
  5. package/dist/components/DirectionIndicatorsOverlay.d.ts +55 -0
  6. package/dist/components/DirectionIndicatorsOverlay.d.ts.map +1 -0
  7. package/dist/components/DirectionIndicatorsOverlay.js +126 -0
  8. package/dist/components/DirectionIndicatorsOverlay.js.map +1 -0
  9. package/dist/components/StreamingRenderProgress.d.ts +60 -0
  10. package/dist/components/StreamingRenderProgress.d.ts.map +1 -0
  11. package/dist/components/StreamingRenderProgress.js +99 -0
  12. package/dist/components/StreamingRenderProgress.js.map +1 -0
  13. package/dist/components/SubstrateSimOverlay.d.ts +56 -0
  14. package/dist/components/SubstrateSimOverlay.d.ts.map +1 -0
  15. package/dist/components/SubstrateSimOverlay.js +39 -0
  16. package/dist/components/SubstrateSimOverlay.js.map +1 -0
  17. package/dist/components/wave4-extras.d.ts +326 -0
  18. package/dist/components/wave4-extras.d.ts.map +1 -0
  19. package/dist/components/wave4-extras.js +264 -0
  20. package/dist/components/wave4-extras.js.map +1 -0
  21. package/dist/index.d.ts +7 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +7 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/lib/editor-config.d.ts +41 -1
  26. package/dist/lib/editor-config.d.ts.map +1 -1
  27. package/dist/lib/editor-config.js +15 -0
  28. package/dist/lib/editor-config.js.map +1 -1
  29. package/dist/lib/palette-registry.d.ts.map +1 -1
  30. package/dist/lib/palette-registry.js +14 -0
  31. package/dist/lib/palette-registry.js.map +1 -1
  32. package/dist/lib/streaming-render.d.ts +100 -0
  33. package/dist/lib/streaming-render.d.ts.map +1 -0
  34. package/dist/lib/streaming-render.js +132 -0
  35. package/dist/lib/streaming-render.js.map +1 -0
  36. package/dist/lib/structural-export.d.ts +84 -0
  37. package/dist/lib/structural-export.d.ts.map +1 -0
  38. package/dist/lib/structural-export.js +100 -0
  39. package/dist/lib/structural-export.js.map +1 -0
  40. package/package.json +1 -1
@@ -0,0 +1,39 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ "use client";
3
+ import { jsx as _jsx } from "react/jsx-runtime";
4
+ function finishOverlay(finish) {
5
+ switch (finish) {
6
+ case "gloss":
7
+ return "linear-gradient(135deg, rgba(255,255,255,0.18) 0%, rgba(255,255,255,0) 60%)";
8
+ case "satin":
9
+ return "linear-gradient(135deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0) 70%)";
10
+ case "matte":
11
+ return "none";
12
+ case "uncoated":
13
+ return "radial-gradient(circle at 30% 20%, rgba(0,0,0,0.04) 0%, transparent 70%)";
14
+ }
15
+ }
16
+ /**
17
+ * Pure-render substrate simulation overlay.
18
+ *
19
+ * @public
20
+ */
21
+ export function SubstrateSimOverlay({ spec }) {
22
+ const { widthPx, heightPx, color, opacity, finish } = spec;
23
+ return (_jsx("div", { "data-testid": "substrate-sim-overlay", "aria-hidden": "true", style: {
24
+ position: "absolute",
25
+ top: 0,
26
+ left: 0,
27
+ width: widthPx,
28
+ height: heightPx,
29
+ background: color,
30
+ opacity,
31
+ pointerEvents: "none",
32
+ }, children: _jsx("div", { "data-testid": "substrate-sim-finish-layer", style: {
33
+ position: "absolute",
34
+ inset: 0,
35
+ background: finishOverlay(finish),
36
+ pointerEvents: "none",
37
+ } }) }));
38
+ }
39
+ //# sourceMappingURL=SubstrateSimOverlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubstrateSimOverlay.js","sourceRoot":"","sources":["../../src/components/SubstrateSimOverlay.tsx"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,YAAY,CAAC;;AAuDb,SAAS,aAAa,CAAC,MAAkC;IACvD,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,OAAO;YACV,OAAO,6EAA6E,CAAC;QACvF,KAAK,OAAO;YACV,OAAO,6EAA6E,CAAC;QACvF,KAAK,OAAO;YACV,OAAO,MAAM,CAAC;QAChB,KAAK,UAAU;YACb,OAAO,0EAA0E,CAAC;IACtF,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAAE,IAAI,EAA4B;IACpE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC3D,OAAO,CACL,6BACc,uBAAuB,iBACvB,MAAM,EAClB,KAAK,EAAE;YACL,QAAQ,EAAE,UAAU;YACpB,GAAG,EAAE,CAAC;YACN,IAAI,EAAE,CAAC;YACP,KAAK,EAAE,OAAO;YACd,MAAM,EAAE,QAAQ;YAChB,UAAU,EAAE,KAAK;YACjB,OAAO;YACP,aAAa,EAAE,MAAM;SACtB,YAED,6BACc,4BAA4B,EACxC,KAAK,EAAE;gBACL,QAAQ,EAAE,UAAU;gBACpB,KAAK,EAAE,CAAC;gBACR,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC;gBACjC,aAAa,EAAE,MAAM;aACtB,GACD,GACE,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Wave 4 long-tail — consolidated panels for the playbook's
3
+ * remaining loader-adapter surfaces. Each export is a small,
4
+ * focused React panel + its adapter type, sharing the same
5
+ * load-on-mount / render-list / surface-error lifecycle as the
6
+ * rest of the AI / I family. Hosts wire one
7
+ * {@link FeatureLoaderFn} per panel; this module hosts no
8
+ * network calls of its own.
9
+ *
10
+ * Features in this bundle:
11
+ *
12
+ * - **B2** {@link DamAssetsPanel} (DAM hookup) — list of
13
+ * approved assets pulled from the host's DAM connector.
14
+ * - **X3** {@link ApprovedMasterDiffPanel} — visual diff vs the
15
+ * pinned master version.
16
+ * - **AI1** {@link CopyGenerationPanel} — Claude-style copy
17
+ * generation with length-to-fit constraint.
18
+ * - **AI2** {@link ImageGenerationPanel} — CMYK-aware image gen
19
+ * with DPI guarantees.
20
+ * - **AI3** {@link AutoLayoutPanel} — auto-arrange/fit solver.
21
+ * - **AI5** {@link OcrRebuildPanel} — OCR a reference pack
22
+ * image and reconstruct editable objects.
23
+ * - **V3** {@link LocalizationPanel} — per-language variants
24
+ * with text-expansion warnings.
25
+ * - **I1** {@link DesignHandoffPanel} — Figma / Adobe import.
26
+ * - **I2** {@link EcommerceConnectorPanel} — Shopify product
27
+ * pull / SKU variants.
28
+ * - **I3** {@link PimConnectorPanel} — PIM bind for merge
29
+ * fields.
30
+ *
31
+ * @public
32
+ */
33
+ import type { ReactElement } from "react";
34
+ /** @public */
35
+ export type DamAsset = {
36
+ id: string;
37
+ name: string;
38
+ kind: "image" | "logo" | "font" | "swatch" | "other";
39
+ url?: string;
40
+ rights?: string;
41
+ };
42
+ /** @public */
43
+ export type DamAssetsLoaderFn = (query?: string) => Promise<readonly DamAsset[]>;
44
+ /** @public */
45
+ export type DamAssetsPanelProps = {
46
+ loader: DamAssetsLoaderFn;
47
+ onSelect?: (asset: DamAsset) => void;
48
+ };
49
+ /**
50
+ * B2 — DAM assets browser. The host wires a
51
+ * {@link DamAssetsLoaderFn} adapter that fronts Bynder /
52
+ * Brandfolder / any DAM API; this panel renders the asset list
53
+ * with usage-rights chips when supplied.
54
+ *
55
+ * @public
56
+ */
57
+ export declare function DamAssetsPanel({ loader, onSelect }: DamAssetsPanelProps): ReactElement;
58
+ /** @public */
59
+ export type ApprovedMasterDiffChange = {
60
+ id: string;
61
+ kind: "text" | "color" | "moved" | "added" | "removed";
62
+ summary: string;
63
+ objectId?: string;
64
+ };
65
+ /** @public */
66
+ export type ApprovedMasterDiffLoaderFn = () => Promise<readonly ApprovedMasterDiffChange[]>;
67
+ /** @public */
68
+ export type ApprovedMasterDiffPanelProps = {
69
+ loader: ApprovedMasterDiffLoaderFn;
70
+ onSelect?: (change: ApprovedMasterDiffChange) => void;
71
+ };
72
+ /**
73
+ * X3 — Diff vs the pinned approved master. Each change row
74
+ * shows kind + summary; the host wires the actual diff
75
+ * computation (typically codex `vision/phash` + per-separation
76
+ * structural diff).
77
+ *
78
+ * @public
79
+ */
80
+ export declare function ApprovedMasterDiffPanel({ loader, onSelect, }: ApprovedMasterDiffPanelProps): ReactElement;
81
+ /** @public */
82
+ export type CopyGenerationRequest = {
83
+ /** Prompt or seed text the LLM should expand/refine. */
84
+ prompt: string;
85
+ /** Max character count the generated copy must fit in
86
+ * (drives length-to-fit warnings). */
87
+ maxChars: number;
88
+ /** Optional brand-voice hints. */
89
+ voice?: string;
90
+ };
91
+ /** @public */
92
+ export type CopyGenerationResult = {
93
+ text: string;
94
+ /** Reasons the host should surface — typically risky-claim
95
+ * flags from the AI safety pass. */
96
+ warnings: readonly string[];
97
+ };
98
+ /** @public */
99
+ export type CopyGenerationFn = (req: CopyGenerationRequest) => Promise<CopyGenerationResult>;
100
+ /** @public */
101
+ export type CopyGenerationPanelProps = {
102
+ generator: CopyGenerationFn;
103
+ initialPrompt?: string;
104
+ maxChars?: number;
105
+ onResult?: (result: CopyGenerationResult) => void;
106
+ };
107
+ /**
108
+ * AI1 — Copy generation panel. Wraps a host-supplied LLM
109
+ * adapter (typically codex `ai/claude` + `spell` + budget). The
110
+ * panel owns the prompt input + max-chars constraint and the
111
+ * generate-button lifecycle; the host wires the adapter.
112
+ *
113
+ * @public
114
+ */
115
+ export declare function CopyGenerationPanel({ generator, initialPrompt, maxChars, onResult, }: CopyGenerationPanelProps): ReactElement;
116
+ /** @public */
117
+ export type ImageGenerationRequest = {
118
+ prompt: string;
119
+ /** Width in mm at the target print resolution. */
120
+ widthMm: number;
121
+ heightMm: number;
122
+ /** Target DPI — typically 300 for press; codex `ai/dispatcher`
123
+ * enforces. */
124
+ dpi: number;
125
+ /** Convert from sRGB to a CMYK profile? Host implementation. */
126
+ cmykProfile?: string;
127
+ };
128
+ /** @public */
129
+ export type ImageGenerationResult = {
130
+ /** Image data URL or external URL the host can use as the
131
+ * fill source. */
132
+ url: string;
133
+ widthPx: number;
134
+ heightPx: number;
135
+ /** Provenance tag (e.g. `"ai-generated"`) for downstream
136
+ * labelling per AI provenance rules. */
137
+ provenance: string;
138
+ };
139
+ /** @public */
140
+ export type ImageGenerationFn = (req: ImageGenerationRequest) => Promise<ImageGenerationResult>;
141
+ /** @public */
142
+ export type ImageGenerationPanelProps = {
143
+ generator: ImageGenerationFn;
144
+ defaultWidthMm?: number;
145
+ defaultHeightMm?: number;
146
+ onResult?: (result: ImageGenerationResult) => void;
147
+ };
148
+ /**
149
+ * AI2 — Print-resolution image generation. Surface is minimal
150
+ * (prompt + dims); host adapter handles the CMYK conversion +
151
+ * provenance tagging.
152
+ *
153
+ * @public
154
+ */
155
+ export declare function ImageGenerationPanel({ generator, defaultWidthMm, defaultHeightMm, onResult, }: ImageGenerationPanelProps): ReactElement;
156
+ /** @public */
157
+ export type AutoLayoutRequest = {
158
+ /** Object ids the solver may move / resize. */
159
+ objectIds: readonly string[];
160
+ /** Constraints — bleed safety, panel anchors (S3 ties in). */
161
+ respectBleed: boolean;
162
+ respectPanelAnchors: boolean;
163
+ };
164
+ /** @public */
165
+ export type AutoLayoutOperation = {
166
+ objectId: string;
167
+ /** Target translation in mm. */
168
+ dx?: number;
169
+ dy?: number;
170
+ /** Target scale factor. */
171
+ scale?: number;
172
+ };
173
+ /** @public */
174
+ export type AutoLayoutFn = (req: AutoLayoutRequest) => Promise<readonly AutoLayoutOperation[]>;
175
+ /** @public */
176
+ export type AutoLayoutPanelProps = {
177
+ solver: AutoLayoutFn;
178
+ objectIds: readonly string[];
179
+ onSolved?: (ops: readonly AutoLayoutOperation[]) => void;
180
+ };
181
+ /**
182
+ * AI3 — Auto-layout / auto-fit panel. Host wires the solver
183
+ * (typically codex geom + the rich document-model); the panel
184
+ * surfaces the run-and-preview affordance.
185
+ *
186
+ * @public
187
+ */
188
+ export declare function AutoLayoutPanel({ solver, objectIds, onSolved, }: AutoLayoutPanelProps): ReactElement;
189
+ /** @public */
190
+ export type OcrRebuildRequest = {
191
+ /** Source image — base64-encoded PNG/JPEG bytes. */
192
+ imageB64: string;
193
+ /** Optional language hint for the OCR engine. */
194
+ language?: string;
195
+ };
196
+ /** @public */
197
+ export type OcrRebuildObject = {
198
+ id: string;
199
+ kind: "text" | "logo" | "barcode-placeholder" | "panel-rect";
200
+ /** Bounding box in mm. */
201
+ x: number;
202
+ y: number;
203
+ widthMm: number;
204
+ heightMm: number;
205
+ /** Recovered text / value, when applicable. */
206
+ text?: string;
207
+ };
208
+ /** @public */
209
+ export type OcrRebuildFn = (req: OcrRebuildRequest) => Promise<readonly OcrRebuildObject[]>;
210
+ /** @public */
211
+ export type OcrRebuildPanelProps = {
212
+ ocr: OcrRebuildFn;
213
+ onObjects?: (objs: readonly OcrRebuildObject[]) => void;
214
+ };
215
+ /**
216
+ * AI5 — Competitor-pack OCR rebuild. Host accepts an image
217
+ * upload, runs codex `extract` + `ai/logos` + `ai/symbols` +
218
+ * `ai/language`, and returns reconstructed editable objects.
219
+ * Surface here is the file-input + reconstruct trigger.
220
+ *
221
+ * @public
222
+ */
223
+ export declare function OcrRebuildPanel({ ocr, onObjects }: OcrRebuildPanelProps): ReactElement;
224
+ /** @public */
225
+ export type LocalizationVariant = {
226
+ /** BCP-47 tag — e.g. `"en"`, `"fr-CA"`, `"ja"`. */
227
+ language: string;
228
+ /** Per-text-object override map: objectId → translated text. */
229
+ texts: Record<string, string>;
230
+ };
231
+ /** @public */
232
+ export type LocalizationExpansion = {
233
+ language: string;
234
+ objectId: string;
235
+ /** Original chars vs translated chars; > 1 = expanded. */
236
+ ratio: number;
237
+ };
238
+ /** @public */
239
+ export type LocalizationPanelProps = {
240
+ variants: readonly LocalizationVariant[];
241
+ /** Optional expansion warnings the host pre-computed (the
242
+ * ratio of translated chars vs the source's box area
243
+ * estimate). Rows with ratio > 1.2 render as warnings. */
244
+ expansions?: readonly LocalizationExpansion[];
245
+ onSelectVariant?: (language: string) => void;
246
+ };
247
+ /**
248
+ * V3 — Localization + text-expansion warnings. Renders the
249
+ * per-language variants list with click-to-preview + a
250
+ * warnings table for any expansion ratio > 1.2 (the rule of
251
+ * thumb for "translation needs more room").
252
+ *
253
+ * @public
254
+ */
255
+ export declare function LocalizationPanel({ variants, expansions, onSelectVariant, }: LocalizationPanelProps): ReactElement;
256
+ /** @public */
257
+ export type DesignHandoffSource = "figma" | "illustrator" | "indesign" | "other";
258
+ /** @public */
259
+ export type DesignHandoffImportFn = (opts: {
260
+ source: DesignHandoffSource;
261
+ fileRef: string;
262
+ }) => Promise<{
263
+ objectsImported: number;
264
+ }>;
265
+ /** @public */
266
+ export type DesignHandoffPanelProps = {
267
+ importer: DesignHandoffImportFn;
268
+ onImported?: (info: {
269
+ objectsImported: number;
270
+ }) => void;
271
+ };
272
+ /**
273
+ * I1 — Figma / Adobe handoff panel. Host wires the actual file /
274
+ * frame / page resolver; this panel surfaces the source picker +
275
+ * file-ref input and the import-now affordance.
276
+ *
277
+ * @public
278
+ */
279
+ export declare function DesignHandoffPanel({ importer, onImported, }: DesignHandoffPanelProps): ReactElement;
280
+ /** @public */
281
+ export type EcommerceProduct = {
282
+ id: string;
283
+ title: string;
284
+ gtin?: string;
285
+ imageUrl?: string;
286
+ /** Product attributes the editor can bind as merge fields. */
287
+ attrs: Record<string, string>;
288
+ };
289
+ /** @public */
290
+ export type EcommerceLoaderFn = () => Promise<readonly EcommerceProduct[]>;
291
+ /** @public */
292
+ export type EcommerceConnectorPanelProps = {
293
+ loader: EcommerceLoaderFn;
294
+ onSelect?: (product: EcommerceProduct) => void;
295
+ };
296
+ /**
297
+ * I2 — Ecommerce (Shopify) product picker. Pull a product list
298
+ * from the host's store connector and let the user pick one to
299
+ * prefill merge fields / generate SKU variants. Host adapter
300
+ * handles store auth.
301
+ *
302
+ * @public
303
+ */
304
+ export declare function EcommerceConnectorPanel({ loader, onSelect, }: EcommerceConnectorPanelProps): ReactElement;
305
+ /** @public */
306
+ export type PimField = {
307
+ id: string;
308
+ label: string;
309
+ value: string;
310
+ };
311
+ /** @public */
312
+ export type PimLoaderFn = () => Promise<readonly PimField[]>;
313
+ /** @public */
314
+ export type PimConnectorPanelProps = {
315
+ loader: PimLoaderFn;
316
+ onBind?: (field: PimField) => void;
317
+ };
318
+ /**
319
+ * I3 — PIM connector panel. List of pull-able fields from the
320
+ * host's PIM connector; click a field to bind it as a merge
321
+ * token on the active object.
322
+ *
323
+ * @public
324
+ */
325
+ export declare function PimConnectorPanel({ loader, onBind }: PimConnectorPanelProps): ReactElement;
326
+ //# sourceMappingURL=wave4-extras.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wave4-extras.d.ts","sourceRoot":"","sources":["../../src/components/wave4-extras.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,264 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ "use client";
3
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
4
+ import { useEffect, useState } from "react";
5
+ /**
6
+ * B2 — DAM assets browser. The host wires a
7
+ * {@link DamAssetsLoaderFn} adapter that fronts Bynder /
8
+ * Brandfolder / any DAM API; this panel renders the asset list
9
+ * with usage-rights chips when supplied.
10
+ *
11
+ * @public
12
+ */
13
+ export function DamAssetsPanel({ loader, onSelect }) {
14
+ return (_jsx(LoaderPanel, { testId: "dam-assets-panel", title: "DAM assets", loader: () => loader(), renderRow: (a) => (_jsxs("span", { children: [a.name, _jsxs("span", { style: { fontSize: "0.625rem", color: "#595959", marginLeft: "0.375rem" }, children: [a.kind, a.rights ? ` · ${a.rights}` : ""] })] })), onSelect: onSelect }));
15
+ }
16
+ /**
17
+ * X3 — Diff vs the pinned approved master. Each change row
18
+ * shows kind + summary; the host wires the actual diff
19
+ * computation (typically codex `vision/phash` + per-separation
20
+ * structural diff).
21
+ *
22
+ * @public
23
+ */
24
+ export function ApprovedMasterDiffPanel({ loader, onSelect, }) {
25
+ return (_jsx(LoaderPanel, { testId: "approved-master-diff-panel", title: "Master diff", loader: loader, renderRow: (c) => (_jsxs("span", { children: ["[", c.kind, "] ", c.summary] })), onSelect: onSelect }));
26
+ }
27
+ /**
28
+ * AI1 — Copy generation panel. Wraps a host-supplied LLM
29
+ * adapter (typically codex `ai/claude` + `spell` + budget). The
30
+ * panel owns the prompt input + max-chars constraint and the
31
+ * generate-button lifecycle; the host wires the adapter.
32
+ *
33
+ * @public
34
+ */
35
+ export function CopyGenerationPanel({ generator, initialPrompt = "", maxChars = 200, onResult, }) {
36
+ const [prompt, setPrompt] = useState(initialPrompt);
37
+ const [status, setStatus] = useState("idle");
38
+ const [result, setResult] = useState(null);
39
+ const [err, setErr] = useState(null);
40
+ const generate = async () => {
41
+ setStatus("loading");
42
+ setErr(null);
43
+ try {
44
+ const r = await generator({ prompt, maxChars });
45
+ setResult(r);
46
+ setStatus("done");
47
+ onResult?.(r);
48
+ }
49
+ catch (e) {
50
+ setErr(e instanceof Error ? e.message : "Generation failed.");
51
+ setStatus("error");
52
+ }
53
+ };
54
+ return (_jsxs("div", { "data-testid": "copy-generation-panel", style: { padding: "0.5rem" }, children: [_jsx("header", { style: { marginBottom: "0.375rem" }, children: _jsx("h3", { style: { margin: 0, fontSize: "0.875rem" }, children: "Copy generator" }) }), _jsx("textarea", { value: prompt, onChange: (e) => setPrompt(e.target.value), placeholder: "Describe the copy\u2026", "aria-label": "Copy generation prompt", style: { width: "100%", minHeight: 60, fontSize: "0.8125rem", padding: "0.25rem" } }), _jsx("div", { style: { display: "flex", gap: "0.375rem", marginTop: "0.375rem" }, children: _jsx("button", { type: "button", onClick: generate, disabled: status === "loading" || !prompt.trim(), style: { fontSize: "0.75rem", padding: "0.25rem 0.5rem" }, children: status === "loading" ? "Generating…" : `Generate (≤${maxChars} chars)` }) }), result && (_jsxs("div", { style: { marginTop: "0.5rem", fontSize: "0.8125rem" }, children: [_jsx("div", { style: { padding: "0.375rem", background: "#f6f6f6", borderRadius: 4 }, children: result.text }), result.warnings.length > 0 && (_jsx("ul", { style: { marginTop: "0.25rem", paddingLeft: "1.25rem" }, children: result.warnings.map((w) => (_jsx("li", { style: { fontSize: "0.75rem", color: "#a60" }, children: w }, w))) }))] })), err && (_jsx("div", { role: "alert", style: { marginTop: "0.5rem", fontSize: "0.75rem", color: "#a00" }, children: err }))] }));
55
+ }
56
+ /**
57
+ * AI2 — Print-resolution image generation. Surface is minimal
58
+ * (prompt + dims); host adapter handles the CMYK conversion +
59
+ * provenance tagging.
60
+ *
61
+ * @public
62
+ */
63
+ export function ImageGenerationPanel({ generator, defaultWidthMm = 50, defaultHeightMm = 50, onResult, }) {
64
+ const [prompt, setPrompt] = useState("");
65
+ const [w, setW] = useState(defaultWidthMm);
66
+ const [h, setH] = useState(defaultHeightMm);
67
+ const [status, setStatus] = useState("idle");
68
+ const [result, setResult] = useState(null);
69
+ const [err, setErr] = useState(null);
70
+ const generate = async () => {
71
+ setStatus("loading");
72
+ setErr(null);
73
+ try {
74
+ const r = await generator({ prompt, widthMm: w, heightMm: h, dpi: 300 });
75
+ setResult(r);
76
+ setStatus("done");
77
+ onResult?.(r);
78
+ }
79
+ catch (e) {
80
+ setErr(e instanceof Error ? e.message : "Image generation failed.");
81
+ setStatus("error");
82
+ }
83
+ };
84
+ return (_jsxs("div", { "data-testid": "image-generation-panel", style: { padding: "0.5rem" }, children: [_jsx("header", { style: { marginBottom: "0.375rem" }, children: _jsx("h3", { style: { margin: 0, fontSize: "0.875rem" }, children: "Image generator" }) }), _jsx("input", { type: "text", value: prompt, onChange: (e) => setPrompt(e.target.value), placeholder: "Describe the image\u2026", "aria-label": "Image generation prompt", style: { width: "100%", fontSize: "0.8125rem", padding: "0.25rem" } }), _jsxs("div", { style: { display: "flex", gap: "0.375rem", marginTop: "0.375rem" }, children: [_jsxs("label", { style: { fontSize: "0.75rem" }, children: ["W mm", _jsx("input", { type: "number", value: w, onChange: (e) => setW(Number(e.target.value) || 0), style: { width: 60, marginLeft: "0.25rem" } })] }), _jsxs("label", { style: { fontSize: "0.75rem" }, children: ["H mm", _jsx("input", { type: "number", value: h, onChange: (e) => setH(Number(e.target.value) || 0), style: { width: 60, marginLeft: "0.25rem" } })] }), _jsx("button", { type: "button", onClick: generate, disabled: status === "loading" || !prompt.trim(), style: { fontSize: "0.75rem", padding: "0.25rem 0.5rem" }, children: status === "loading" ? "…" : "Generate" })] }), result && (_jsxs("div", { style: { marginTop: "0.5rem", fontSize: "0.75rem", color: "#595959" }, children: [result.widthPx, "\u00D7", result.heightPx, "px \u00B7 ", result.provenance] })), err && (_jsx("div", { role: "alert", style: { marginTop: "0.375rem", fontSize: "0.75rem", color: "#a00" }, children: err }))] }));
85
+ }
86
+ /**
87
+ * AI3 — Auto-layout / auto-fit panel. Host wires the solver
88
+ * (typically codex geom + the rich document-model); the panel
89
+ * surfaces the run-and-preview affordance.
90
+ *
91
+ * @public
92
+ */
93
+ export function AutoLayoutPanel({ solver, objectIds, onSolved, }) {
94
+ const [respectBleed, setRespectBleed] = useState(true);
95
+ const [respectAnchors, setRespectAnchors] = useState(true);
96
+ const [status, setStatus] = useState("idle");
97
+ const [ops, setOps] = useState(null);
98
+ const [err, setErr] = useState(null);
99
+ const run = async () => {
100
+ setStatus("loading");
101
+ setErr(null);
102
+ try {
103
+ const next = await solver({
104
+ objectIds,
105
+ respectBleed,
106
+ respectPanelAnchors: respectAnchors,
107
+ });
108
+ setOps(next);
109
+ setStatus("done");
110
+ onSolved?.(next);
111
+ }
112
+ catch (e) {
113
+ setErr(e instanceof Error ? e.message : "Auto-layout failed.");
114
+ setStatus("error");
115
+ }
116
+ };
117
+ return (_jsxs("div", { "data-testid": "auto-layout-panel", style: { padding: "0.5rem" }, children: [_jsx("header", { style: { marginBottom: "0.375rem" }, children: _jsx("h3", { style: { margin: 0, fontSize: "0.875rem" }, children: "Auto layout" }) }), _jsxs("div", { style: { fontSize: "0.75rem", display: "flex", flexDirection: "column", gap: "0.25rem" }, children: [_jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: respectBleed, onChange: (e) => setRespectBleed(e.target.checked) }), " ", "Respect bleed / safety"] }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: respectAnchors, onChange: (e) => setRespectAnchors(e.target.checked) }), " ", "Respect panel anchors"] })] }), _jsx("button", { type: "button", onClick: run, disabled: status === "loading" || objectIds.length === 0, style: { marginTop: "0.375rem", fontSize: "0.75rem", padding: "0.25rem 0.5rem" }, children: status === "loading" ? "Solving…" : `Auto-arrange ${objectIds.length} object(s)` }), ops && (_jsxs("div", { style: { marginTop: "0.375rem", fontSize: "0.75rem", color: "#595959" }, children: [ops.length, " operation(s) emitted"] })), err && (_jsx("div", { role: "alert", style: { marginTop: "0.375rem", fontSize: "0.75rem", color: "#a00" }, children: err }))] }));
118
+ }
119
+ /**
120
+ * Encode an ArrayBuffer to base64 in 32 KB chunks so we don't blow
121
+ * past `String.fromCharCode.apply` argument-length limits on large
122
+ * files. `btoa` is Latin-1, which is fine here because each byte
123
+ * of the buffer becomes one code unit ≤ 0xFF.
124
+ *
125
+ * @internal
126
+ */
127
+ function arrayBufferToBase64(buf) {
128
+ const bytes = new Uint8Array(buf);
129
+ const chunk = 0x8000;
130
+ let binary = "";
131
+ for (let i = 0; i < bytes.length; i += chunk) {
132
+ const slice = bytes.subarray(i, Math.min(i + chunk, bytes.length));
133
+ binary += String.fromCharCode.apply(null, Array.from(slice));
134
+ }
135
+ return btoa(binary);
136
+ }
137
+ /**
138
+ * AI5 — Competitor-pack OCR rebuild. Host accepts an image
139
+ * upload, runs codex `extract` + `ai/logos` + `ai/symbols` +
140
+ * `ai/language`, and returns reconstructed editable objects.
141
+ * Surface here is the file-input + reconstruct trigger.
142
+ *
143
+ * @public
144
+ */
145
+ export function OcrRebuildPanel({ ocr, onObjects }) {
146
+ const [status, setStatus] = useState("idle");
147
+ const [count, setCount] = useState(0);
148
+ const onFile = async (file) => {
149
+ setStatus("loading");
150
+ try {
151
+ const buf = await file.arrayBuffer();
152
+ const b64 = arrayBufferToBase64(buf);
153
+ const objs = await ocr({ imageB64: b64 });
154
+ setCount(objs.length);
155
+ setStatus("done");
156
+ onObjects?.(objs);
157
+ }
158
+ catch {
159
+ setStatus("error");
160
+ }
161
+ };
162
+ return (_jsxs("div", { "data-testid": "ocr-rebuild-panel", style: { padding: "0.5rem" }, children: [_jsx("header", { style: { marginBottom: "0.375rem" }, children: _jsx("h3", { style: { margin: 0, fontSize: "0.875rem" }, children: "OCR rebuild" }) }), _jsx("input", { type: "file", accept: "image/*", "aria-label": "Reference image for OCR rebuild", onChange: (e) => {
163
+ const f = e.target.files?.[0];
164
+ if (f)
165
+ void onFile(f);
166
+ }, style: { fontSize: "0.75rem" } }), status === "done" && (_jsxs("div", { style: { marginTop: "0.375rem", fontSize: "0.75rem", color: "#595959" }, children: ["Reconstructed ", count, " object(s)."] })), status === "loading" && (_jsx("output", { style: { display: "block", fontSize: "0.75rem", marginTop: "0.375rem" }, children: "Running OCR\u2026" }))] }));
167
+ }
168
+ /**
169
+ * V3 — Localization + text-expansion warnings. Renders the
170
+ * per-language variants list with click-to-preview + a
171
+ * warnings table for any expansion ratio > 1.2 (the rule of
172
+ * thumb for "translation needs more room").
173
+ *
174
+ * @public
175
+ */
176
+ export function LocalizationPanel({ variants, expansions = [], onSelectVariant, }) {
177
+ const tight = expansions.filter((e) => e.ratio > 1.2);
178
+ return (_jsxs("div", { "data-testid": "localization-panel", style: { padding: "0.5rem" }, children: [_jsx("header", { style: { marginBottom: "0.375rem" }, children: _jsxs("h3", { style: { margin: 0, fontSize: "0.875rem" }, children: ["Localization (", variants.length, ")"] }) }), _jsx("ul", { style: { listStyle: "none", padding: 0, margin: 0 }, children: variants.map((v) => (_jsx("li", { style: { marginBottom: "0.25rem" }, children: _jsxs("button", { type: "button", onClick: () => onSelectVariant?.(v.language), style: {
179
+ display: "block",
180
+ width: "100%",
181
+ padding: "0.375rem 0.5rem",
182
+ background: "transparent",
183
+ border: "1px solid #ddd",
184
+ borderRadius: 4,
185
+ textAlign: "left",
186
+ fontSize: "0.8125rem",
187
+ cursor: onSelectVariant ? "pointer" : "default",
188
+ }, children: [v.language, " ", _jsxs("span", { style: { color: "#595959" }, children: ["\u00B7 ", Object.keys(v.texts).length, " string(s)"] })] }) }, v.language))) }), tight.length > 0 && (_jsxs("div", { "data-testid": "localization-expansion-warnings", style: { marginTop: "0.5rem", fontSize: "0.75rem", color: "#a60" }, children: [tight.length, " translation(s) may overflow their box (ratio > 1.2)"] }))] }));
189
+ }
190
+ /**
191
+ * I1 — Figma / Adobe handoff panel. Host wires the actual file /
192
+ * frame / page resolver; this panel surfaces the source picker +
193
+ * file-ref input and the import-now affordance.
194
+ *
195
+ * @public
196
+ */
197
+ export function DesignHandoffPanel({ importer, onImported, }) {
198
+ const [source, setSource] = useState("figma");
199
+ const [ref, setRef] = useState("");
200
+ const [status, setStatus] = useState("idle");
201
+ const [count, setCount] = useState(0);
202
+ const run = async () => {
203
+ setStatus("loading");
204
+ const r = await importer({ source, fileRef: ref });
205
+ setCount(r.objectsImported);
206
+ setStatus("done");
207
+ onImported?.(r);
208
+ };
209
+ return (_jsxs("div", { "data-testid": "design-handoff-panel", style: { padding: "0.5rem" }, children: [_jsx("header", { style: { marginBottom: "0.375rem" }, children: _jsx("h3", { style: { margin: 0, fontSize: "0.875rem" }, children: "Design handoff" }) }), _jsxs("select", { value: source, onChange: (e) => setSource(e.target.value), "aria-label": "Design handoff source", style: { fontSize: "0.8125rem", padding: "0.25rem", marginRight: "0.375rem" }, children: [_jsx("option", { value: "figma", children: "Figma" }), _jsx("option", { value: "illustrator", children: "Illustrator" }), _jsx("option", { value: "indesign", children: "InDesign" }), _jsx("option", { value: "other", children: "Other" })] }), _jsx("input", { type: "text", value: ref, onChange: (e) => setRef(e.target.value), placeholder: "File / frame URL", "aria-label": "File reference", style: { fontSize: "0.8125rem", padding: "0.25rem" } }), _jsx("button", { type: "button", onClick: run, disabled: status === "loading" || !ref.trim(), style: { marginLeft: "0.375rem", fontSize: "0.75rem", padding: "0.25rem 0.5rem" }, children: status === "loading" ? "…" : "Import" }), status === "done" && (_jsxs("div", { style: { marginTop: "0.375rem", fontSize: "0.75rem", color: "#595959" }, children: ["Imported ", count, " object(s) from ", source, "."] }))] }));
210
+ }
211
+ /**
212
+ * I2 — Ecommerce (Shopify) product picker. Pull a product list
213
+ * from the host's store connector and let the user pick one to
214
+ * prefill merge fields / generate SKU variants. Host adapter
215
+ * handles store auth.
216
+ *
217
+ * @public
218
+ */
219
+ export function EcommerceConnectorPanel({ loader, onSelect, }) {
220
+ return (_jsx(LoaderPanel, { testId: "ecommerce-connector-panel", title: "Products", loader: loader, renderRow: (p) => (_jsxs("span", { children: [p.title, p.gtin && (_jsxs("span", { style: { fontSize: "0.625rem", color: "#595959", marginLeft: "0.375rem" }, children: ["GTIN ", p.gtin] }))] })), onSelect: onSelect }));
221
+ }
222
+ /**
223
+ * I3 — PIM connector panel. List of pull-able fields from the
224
+ * host's PIM connector; click a field to bind it as a merge
225
+ * token on the active object.
226
+ *
227
+ * @public
228
+ */
229
+ export function PimConnectorPanel({ loader, onBind }) {
230
+ return (_jsx(LoaderPanel, { testId: "pim-connector-panel", title: "PIM fields", loader: loader, renderRow: (f) => (_jsxs("span", { children: [f.label, _jsx("span", { style: { fontSize: "0.625rem", color: "#595959", marginLeft: "0.375rem" }, children: f.value })] })), onSelect: onBind }));
231
+ }
232
+ function LoaderPanel({ testId, title, loader, renderRow, onSelect, }) {
233
+ const [items, setItems] = useState(null);
234
+ const [error, setError] = useState(null);
235
+ useEffect(() => {
236
+ let disposed = false;
237
+ void (async () => {
238
+ try {
239
+ const next = await loader();
240
+ if (!disposed)
241
+ setItems(next);
242
+ }
243
+ catch (err) {
244
+ if (!disposed)
245
+ setError(err instanceof Error ? err.message : String(err));
246
+ }
247
+ })();
248
+ return () => {
249
+ disposed = true;
250
+ };
251
+ }, [loader]);
252
+ return (_jsxs("div", { "data-testid": testId, style: { padding: "0.5rem" }, children: [_jsx("header", { style: { marginBottom: "0.375rem" }, children: _jsxs("h3", { style: { margin: 0, fontSize: "0.875rem" }, children: [title, items && ` (${items.length})`] }) }), error && (_jsx("div", { role: "alert", style: { fontSize: "0.75rem", color: "#a00" }, children: error })), !error && !items && (_jsx("output", { style: { display: "block", fontSize: "0.75rem", opacity: 0.6 }, children: "Loading\u2026" })), items && items.length === 0 && (_jsx("div", { style: { fontSize: "0.75rem", opacity: 0.6 }, children: "None." })), items && items.length > 0 && (_jsx("ul", { style: { listStyle: "none", padding: 0, margin: 0 }, children: items.map((item) => (_jsx("li", { style: { marginBottom: "0.25rem" }, children: onSelect ? (_jsx("button", { type: "button", onClick: () => onSelect(item), style: {
253
+ display: "block",
254
+ width: "100%",
255
+ padding: "0.375rem 0.5rem",
256
+ background: "transparent",
257
+ border: "1px solid #ddd",
258
+ borderRadius: 4,
259
+ textAlign: "left",
260
+ fontSize: "0.8125rem",
261
+ cursor: "pointer",
262
+ }, children: renderRow(item) })) : (_jsx("div", { style: { padding: "0.375rem 0.5rem", fontSize: "0.8125rem" }, children: renderRow(item) })) }, item.id))) }))] }));
263
+ }
264
+ //# sourceMappingURL=wave4-extras.js.map