@snowcone-app/canvas 0.11.0 → 0.12.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 (30) hide show
  1. package/dist/{CanvasStateV1-D3THl2Zc.js → CanvasStateV1-CKsfggNP.js} +3 -3
  2. package/dist/{CanvasStateV1-D3THl2Zc.js.map → CanvasStateV1-CKsfggNP.js.map} +1 -1
  3. package/dist/{HybridHistoryManager-DANuzx7G.js → HybridHistoryManager-NL3zyEl4.js} +21 -21
  4. package/dist/{HybridHistoryManager-DANuzx7G.js.map → HybridHistoryManager-NL3zyEl4.js.map} +1 -1
  5. package/dist/{ImportManager-NCtkY5Z0.js → ImportManager-eotl7r5d.js} +2 -2
  6. package/dist/{ImportManager-NCtkY5Z0.js.map → ImportManager-eotl7r5d.js.map} +1 -1
  7. package/dist/{ThemeContext-U-VDtIiS.js → ThemeContext-Cl1JPVLZ.js} +2 -2
  8. package/dist/{ThemeContext-U-VDtIiS.js.map → ThemeContext-Cl1JPVLZ.js.map} +1 -1
  9. package/dist/advanced.mjs +8 -8
  10. package/dist/api/embed.d.ts +3 -1
  11. package/dist/api/stable.d.ts +3 -1
  12. package/dist/components/embed-realtime/RealtimeMockup.d.ts +20 -0
  13. package/dist/components/embed-realtime/context.d.ts +9 -0
  14. package/dist/components/embed-realtime/usePieceGuideBundle.d.ts +48 -0
  15. package/dist/design/template-fields.d.ts +39 -1
  16. package/dist/embed.mjs +222 -141
  17. package/dist/embed.mjs.map +1 -1
  18. package/dist/hooks/useDeadBindingWarning.d.ts +2 -2
  19. package/dist/hooks/useFontBinding.d.ts +23 -0
  20. package/dist/index.mjs +24 -23
  21. package/dist/internals.mjs +4 -4
  22. package/dist/template-fields-IgDV7PRY.js +1627 -0
  23. package/dist/template-fields-IgDV7PRY.js.map +1 -0
  24. package/dist/testing.mjs +2 -2
  25. package/dist/utils/UnifiedFontService.d.ts +7 -0
  26. package/dist/{validation-C857Ptxn.js → validation-BpmNOvuY.js} +14 -4
  27. package/dist/{validation-C857Ptxn.js.map → validation-BpmNOvuY.js.map} +1 -1
  28. package/package.json +3 -3
  29. package/dist/template-fields-C8Tj67ek.js +0 -1585
  30. package/dist/template-fields-C8Tj67ek.js.map +0 -1
package/dist/advanced.mjs CHANGED
@@ -1,11 +1,11 @@
1
- import { D as te, I as q, r as re, v as ne, i as oe, e as ae } from "./validation-C857Ptxn.js";
2
- import { j as it, k as lt, l as ct, B as dt, m as ut, n as mt, o as ft, p as ht, q as pt, s as gt, t as xt, w as bt, x as yt, y as vt, F as wt, z as St, G as Ct, H as Tt, J as Et, K as $t, L as kt, N as At, O as Ft, Q as Pt, R as Nt, S as Rt, T as Bt, U as Mt, V as Dt, W as zt, X as It, Y as Ot, Z as Lt, _ as Wt, $ as Kt, a0 as _t, a1 as Gt, a2 as Ut, a3 as jt, a4 as Jt, a5 as qt, a6 as Vt, a7 as Xt, a8 as Yt, a9 as Zt, aa as Ht, ab as Qt, ac as er, ad as tr, ae as rr, af as nr } from "./validation-C857Ptxn.js";
3
- import { u as V } from "./ThemeContext-U-VDtIiS.js";
4
- import { E as ar, P as sr, T as ir, p as lr, s as cr, b as dr, c as ur, d as mr, e as fr, f as hr, g as pr, a as gr } from "./ThemeContext-U-VDtIiS.js";
5
- import { c as se, G as ie, F as le, g as ce, r as de, u as ue } from "./HybridHistoryManager-DANuzx7G.js";
6
- import { A as br, B as yr, I as vr, P as wr, R as Sr, S as Cr, T as Tr, h as Er, a as $r, b as kr, i as Ar, d as Fr, e as Pr, f as Nr, j as Rr, k as Br, l as Mr, m as Dr, n as zr, o as Ir, p as Or, q as Lr, s as Wr, t as Kr, v as _r, w as Gr, x as Ur } from "./HybridHistoryManager-DANuzx7G.js";
7
- import { I as me } from "./ImportManager-NCtkY5Z0.js";
8
- import { i as Jr, a as qr, b as Vr, c as Xr, d as Yr } from "./ImportManager-NCtkY5Z0.js";
1
+ import { D as te, I as q, r as re, v as ne, i as oe, e as ae } from "./validation-BpmNOvuY.js";
2
+ import { j as it, k as lt, l as ct, B as dt, m as ut, n as mt, o as ft, p as ht, q as pt, s as gt, t as xt, w as bt, x as yt, y as vt, F as wt, z as St, G as Ct, H as Tt, J as Et, K as $t, L as kt, N as At, O as Ft, Q as Pt, R as Nt, S as Rt, T as Bt, U as Mt, V as Dt, W as zt, X as It, Y as Ot, Z as Lt, _ as Wt, $ as Kt, a0 as _t, a1 as Gt, a2 as Ut, a3 as jt, a4 as Jt, a5 as qt, a6 as Vt, a7 as Xt, a8 as Yt, a9 as Zt, aa as Ht, ab as Qt, ac as er, ad as tr, ae as rr, af as nr } from "./validation-BpmNOvuY.js";
3
+ import { u as V } from "./ThemeContext-Cl1JPVLZ.js";
4
+ import { E as ar, P as sr, T as ir, p as lr, s as cr, b as dr, c as ur, d as mr, e as fr, f as hr, g as pr, a as gr } from "./ThemeContext-Cl1JPVLZ.js";
5
+ import { c as se, G as ie, F as le, g as ce, r as de, u as ue } from "./HybridHistoryManager-NL3zyEl4.js";
6
+ import { A as br, B as yr, I as vr, P as wr, R as Sr, S as Cr, T as Tr, h as Er, a as $r, b as kr, i as Ar, d as Fr, e as Pr, f as Nr, j as Rr, k as Br, l as Mr, m as Dr, n as zr, o as Ir, p as Or, q as Lr, s as Wr, t as Kr, v as _r, w as Gr, x as Ur } from "./HybridHistoryManager-NL3zyEl4.js";
7
+ import { I as me } from "./ImportManager-eotl7r5d.js";
8
+ import { i as Jr, a as qr, b as Vr, c as Xr, d as Yr } from "./ImportManager-eotl7r5d.js";
9
9
  import { useState as A, useCallback as y, useEffect as X, useMemo as Y, useRef as B, memo as fe } from "react";
10
10
  import { jsxs as b, jsx as s, Fragment as he } from "react/jsx-runtime";
11
11
  import { Tooltip as M, TooltipTrigger as D, Button as pe, TooltipContent as z, Popover as ge, PopoverTrigger as xe, PopoverContent as be } from "@snowcone-app/ui";
@@ -50,9 +50,11 @@ export { useTextBinding } from '../hooks/useTextBinding.js';
50
50
  export { useImageBinding } from '../hooks/useImageBinding.js';
51
51
  export { useColorBinding } from '../hooks/useColorBinding.js';
52
52
  export type { ColorBindingResult } from '../hooks/useColorBinding.js';
53
+ export { useFontBinding } from '../hooks/useFontBinding.js';
54
+ export type { FontBindingResult } from '../hooks/useFontBinding.js';
53
55
  export { useElementByName } from '../hooks/useElementByName.js';
54
56
  export { readTemplateFields, withTemplateFields, TEMPLATE_FIELDS_KEY, } from '../design/template-fields.js';
55
- export type { TemplateField, TemplateTextField, TemplateImageField, TemplateColorField, } from '../design/template-fields.js';
57
+ export type { TemplateField, TemplateTextField, TemplateImageField, TemplateColorField, TemplateSelectField, } from '../design/template-fields.js';
56
58
  export { CanvasServicesProvider, useCanvasServices } from '../contexts/CanvasServicesContext.js';
57
59
  export type { CanvasServices, ResolvedCanvasServices } from '../contexts/CanvasServicesContext.js';
58
60
  export { createProxyImageSearch, createProxyGenerate } from '../services/imageSearch.js';
@@ -32,6 +32,8 @@ export { useImageBinding } from '../hooks/useImageBinding.js';
32
32
  export type { ImageBindingResult, ImageBindingOptions, ImageFitMode } from '../hooks/useImageBinding.js';
33
33
  export { useColorBinding } from '../hooks/useColorBinding.js';
34
34
  export type { ColorBindingResult } from '../hooks/useColorBinding.js';
35
+ export { useFontBinding } from '../hooks/useFontBinding.js';
36
+ export type { FontBindingResult } from '../hooks/useFontBinding.js';
35
37
  export { useElementByName } from '../hooks/useElementByName.js';
36
38
  export { useContentReady } from '../hooks/useContentReady.js';
37
39
  export type { UseContentReadyOptions } from '../hooks/useContentReady.js';
@@ -51,5 +53,5 @@ export type { ServerRenderRequest } from '../rendering/serialize-for-server.js';
51
53
  export { design, DesignBuilder } from '../design/design-builder.js';
52
54
  export type { Anchor, ShapeKind, DesignArtboard, BackgroundOptions, ImageOptions, TextOptions, ShapeOptions, } from '../design/design-builder.js';
53
55
  export { readTemplateFields, withTemplateFields, TEMPLATE_FIELDS_KEY, } from '../design/template-fields.js';
54
- export type { TemplateField, TemplateTextField, TemplateImageField, TemplateColorField, } from '../design/template-fields.js';
56
+ export type { TemplateField, TemplateTextField, TemplateImageField, TemplateColorField, TemplateSelectField, } from '../design/template-fields.js';
55
57
  export type { CanvasStateV1, SerializedArtboard, SerializedElement, DeserializeResult, ValidationResult, } from '../state/index.js';
@@ -11,4 +11,24 @@ export interface RealtimeMockupProps {
11
11
  className?: string;
12
12
  style?: CSSProperties;
13
13
  }
14
+ /**
15
+ * How far the displayed device-pixel width may exceed the delivered mockup's
16
+ * natural width before {@link resolutionMismatchHint} fires. Renders default to
17
+ * 1000px wide, so a 1000px image inside a ~1100 device-px slot is fine (browser
18
+ * downscale slack), but stretched across 2000 device px it looks soft and the
19
+ * developer deserves to hear why.
20
+ */
21
+ export declare const RESOLUTION_MISMATCH_RATIO = 1.2;
22
+ /** Rounds a device-pixel width up to the next {@link SUGGESTED_WIDTHS} step
23
+ * (capped at the top step). */
24
+ export declare function suggestRenderWidth(devicePx: number): number;
25
+ /**
26
+ * Builds the dev-console warning shown when a delivered mockup's natural width
27
+ * is meaningfully below the width it's displayed at in DEVICE pixels (CSS width
28
+ * × `devicePixelRatio`) — the "my preview looks soft and nothing says why"
29
+ * trap: renders default to 1000px wide, which blurs on a large or high-density
30
+ * display. Returns `null` when the resolution is adequate (within
31
+ * {@link RESOLUTION_MISMATCH_RATIO}) or either measurement is unusable.
32
+ */
33
+ export declare function resolutionMismatchHint(naturalWidth: number, devicePx: number): string | null;
14
34
  export declare function RealtimeMockup({ mockupId, fallback, alt, className, style, }: RealtimeMockupProps): React.ReactElement;
@@ -1,5 +1,6 @@
1
1
  import { RenderSession, RenderProduct, RenderState, MockupResult } from '@snowcone-app/sdk';
2
2
  import { DesignBuilder } from '../../design/design-builder.js';
3
+ import { PieceGuidesConfig } from '../embed/SnowconeCanvas.js';
3
4
  export interface EmbedContextValue {
4
5
  /** The single session the editor pushes renders to. `null` if the session
5
6
  * failed to construct (a config fail-fast, surfaced via `error`). Escape
@@ -21,6 +22,14 @@ export interface EmbedContextValue {
21
22
  error: string | null;
22
23
  /** Default edit→render throttle (ms). */
23
24
  throttleMs: number;
25
+ /**
26
+ * The product's printable-area guides (dead zones, safe areas) — fetched by
27
+ * `<RealtimeEmbed>` and already gated on the design's artboard matching the
28
+ * print-file frame they were authored in. `null` when the product has none,
29
+ * the fetch failed, or the dims don't line up. `<RealtimeCanvas>` forwards
30
+ * this to the editor so embedders get the same overlay snowcone.app shows.
31
+ */
32
+ pieceGuides: PieceGuidesConfig | null;
24
33
  /** Dispatch a server render for the embed's placement. Sets `isRendering`. */
25
34
  pushRender: (state: RenderState, throttleMs?: number) => void;
26
35
  }
@@ -0,0 +1,48 @@
1
+ import { PieceGuidesConfig } from '../embed/SnowconeCanvas.js';
2
+ import { PieceGuide, PieceGuideRendererStyle } from '../../rendering/PieceGuideRenderer.js';
3
+ /** Backend base hosting `GET /printables/by-gvid/:gvid/piece-guides`. */
4
+ export declare const PIECE_GUIDES_API_BASE = "https://api.snowcone.app";
5
+ /** Wire shape of the piece-guides endpoint (the consumer-safe subset). */
6
+ interface RawPieceGuidesResponse {
7
+ artboardWidth: number;
8
+ artboardHeight: number;
9
+ /** Inches→px conversion factor for `safeAreaInsetInches`. */
10
+ templateDpi: number;
11
+ pieces: Array<{
12
+ id: string;
13
+ x: number;
14
+ y: number;
15
+ width: number;
16
+ height: number;
17
+ rotation?: number;
18
+ safeAreaInsetInches?: number;
19
+ }>;
20
+ pieceGuides?: Record<string, PieceGuide>;
21
+ pieceGuidesStyle?: PieceGuideRendererStyle;
22
+ }
23
+ /** A fetched bundle: the overlay config plus the artboard frame it was authored in. */
24
+ export interface PieceGuideBundle {
25
+ /** The print-file frame the piece coords live in. */
26
+ artboardWidth: number;
27
+ artboardHeight: number;
28
+ config: PieceGuidesConfig;
29
+ }
30
+ /** Convert the wire response into a renderer-ready bundle (inches → px, etc.). */
31
+ export declare function transformPieceGuides(raw: RawPieceGuidesResponse): PieceGuideBundle;
32
+ /**
33
+ * Gate the bundle on the design's artboard matching the print-file frame the
34
+ * guides were authored in — the same gate snowcone.app's editor applies. A
35
+ * design authored at other dims would render the overlay misaligned, so it's
36
+ * skipped (with a dev hint, since the fix — author at the placement's dims —
37
+ * is on the embedder's side).
38
+ */
39
+ export declare function pieceGuidesForArtboard(bundle: PieceGuideBundle, artboard: {
40
+ width: number;
41
+ height: number;
42
+ } | undefined): PieceGuidesConfig | undefined;
43
+ /**
44
+ * Fetch the piece-guide bundle for a variant (gvid). Returns null while
45
+ * loading, when `gvid` is undefined, and on any error (silent degrade).
46
+ */
47
+ export declare function usePieceGuideBundle(gvid: string | undefined, apiBase?: string): PieceGuideBundle | null;
48
+ export {};
@@ -8,6 +8,14 @@ export interface TemplateTextField {
8
8
  label: string;
9
9
  /** Placeholder text for the input. */
10
10
  placeholder?: string;
11
+ /**
12
+ * Designer's length limit for the buyer's text — long names overflow fixed
13
+ * layouts. Intent, not enforcement: your control applies it (an input's
14
+ * `maxLength`); the canvas itself doesn't truncate.
15
+ */
16
+ maxLength?: number;
17
+ /** Optional grouping label — consecutive fields sharing a group render together. */
18
+ group?: string;
11
19
  }
12
20
  /** A buyer-swappable image slot, bound to elements by their `name` property. */
13
21
  export interface TemplateImageField {
@@ -20,6 +28,8 @@ export interface TemplateImageField {
20
28
  placeholder?: string;
21
29
  /** How the buyer's image fits the original element bounds. Default: "cover". */
22
30
  fit?: ImageFitMode;
31
+ /** Optional grouping label — consecutive fields sharing a group render together. */
32
+ group?: string;
23
33
  }
24
34
  /** A buyer-editable color, matched by value across all elements that use it. */
25
35
  export interface TemplateColorField {
@@ -28,9 +38,37 @@ export interface TemplateColorField {
28
38
  color: string;
29
39
  /** Label shown to the buyer (e.g. "Accent color"). */
30
40
  label: string;
41
+ /** Optional grouping label — consecutive fields sharing a group render together. */
42
+ group?: string;
43
+ }
44
+ /**
45
+ * A pick-one choice over designer-authored options — "one of these 3 badges",
46
+ * a curated font list, preset text variants. Binds to elements by `name` and
47
+ * swaps ONE property on them, chosen by `property`:
48
+ *
49
+ * - `'text'` → apply via `useTextBinding(name).setText(value)`
50
+ * - `'imageUrl'` → apply via `useImageBinding(name).setImageUrl(value)`
51
+ * - `'fontFamily'` → apply via `useFontBinding(name).setFontFamily(value)`
52
+ * (values must be Google Fonts families)
53
+ */
54
+ export interface TemplateSelectField {
55
+ type: 'select';
56
+ /** Element name (matches canvas layer `name` property). */
57
+ name: string;
58
+ /** Label shown to the buyer (e.g. "Badge"). */
59
+ label: string;
60
+ /** Which property of the bound element(s) the chosen value sets. */
61
+ property: 'text' | 'imageUrl' | 'fontFamily';
62
+ /** The designer-authored choices, in display order. */
63
+ options: Array<{
64
+ label: string;
65
+ value: string;
66
+ }>;
67
+ /** Optional grouping label — consecutive fields sharing a group render together. */
68
+ group?: string;
31
69
  }
32
70
  /** One buyer-editable aspect of a template. */
33
- export type TemplateField = TemplateTextField | TemplateImageField | TemplateColorField;
71
+ export type TemplateField = TemplateTextField | TemplateImageField | TemplateColorField | TemplateSelectField;
34
72
  /**
35
73
  * The key template fields are stored under on the state document.
36
74
  * Kept as `personalization` for compatibility with designs saved on snowcone.app.
package/dist/embed.mjs CHANGED
@@ -1,189 +1,270 @@
1
- import { a as K, e as z } from "./validation-C857Ptxn.js";
2
- import { b as pe, d as ve, f as be } from "./validation-C857Ptxn.js";
3
- import { y as T } from "./HybridHistoryManager-DANuzx7G.js";
4
- import { jsx as b, Fragment as J } from "react/jsx-runtime";
5
- import { createContext as W, useContext as H, useMemo as E, useRef as C, useState as q, useEffect as M } from "react";
6
- import { assertVariantSelected as Y, RenderSession as Q } from "@snowcone-app/sdk";
7
- import { S as X, s as Z } from "./template-fields-C8Tj67ek.js";
8
- import { D as Ee, T as $e, d as Se, r as ke, a as Re, e as Te, f as we, g as xe, w as Ie } from "./template-fields-C8Tj67ek.js";
9
- const V = W(void 0);
10
- function B() {
11
- const e = H(V);
1
+ import { a as z, e as J } from "./validation-BpmNOvuY.js";
2
+ import { b as Re, d as Te, f as xe } from "./validation-BpmNOvuY.js";
3
+ import { y } from "./HybridHistoryManager-NL3zyEl4.js";
4
+ import { jsx as $, Fragment as Y } from "react/jsx-runtime";
5
+ import { createContext as Q, useContext as X, useState as x, useEffect as U, useMemo as I, useRef as M } from "react";
6
+ import { assertVariantSelected as Z, RenderSession as ee } from "@snowcone-app/sdk";
7
+ import { S as te, s as ne } from "./template-fields-IgDV7PRY.js";
8
+ import { D as Me, T as Ae, d as Ce, r as Ge, a as Ne, e as Be, f as Pe, g as _e, h as Oe, w as qe } from "./template-fields-IgDV7PRY.js";
9
+ const L = Q(void 0);
10
+ function P() {
11
+ const e = X(L);
12
12
  if (!e)
13
13
  throw new Error(
14
14
  "useRealtimeMockups / <RealtimeCanvas> / <RealtimeMockup> must be rendered inside <RealtimeEmbed>. Wrap them: <RealtimeEmbed shop=… product=… design=… grantUrl=…>…</RealtimeEmbed>"
15
15
  );
16
16
  return e;
17
17
  }
18
- const p = "[RealtimeEmbed]";
19
- function ee(e) {
20
- var a, i;
21
- if ((a = e == null ? void 0 : e.requiredPlacements) != null && a.length)
22
- return e.requiredPlacements.filter((t) => !t.autoFilledByVariant).map((t) => t.label);
23
- const n = new Set(
24
- (((i = e == null ? void 0 : e.options) == null ? void 0 : i.combinations) ?? []).map((t) => t.variantId).filter((t) => typeof t == "string" && t !== "")
18
+ const re = "https://api.snowcone.app";
19
+ function se(e) {
20
+ const t = e.pieces.map((n) => ({
21
+ id: n.id,
22
+ x: n.x,
23
+ y: n.y,
24
+ width: n.width,
25
+ height: n.height,
26
+ rotation: typeof n.rotation == "number" ? n.rotation : void 0,
27
+ safeAreaInsetPx: typeof n.safeAreaInsetInches == "number" && n.safeAreaInsetInches > 0 ? n.safeAreaInsetInches * e.templateDpi : void 0
28
+ }));
29
+ return {
30
+ artboardWidth: e.artboardWidth,
31
+ artboardHeight: e.artboardHeight,
32
+ config: {
33
+ pieces: t,
34
+ guides: e.pieceGuides ?? {},
35
+ style: e.pieceGuidesStyle
36
+ }
37
+ };
38
+ }
39
+ function ie(e, t) {
40
+ var n;
41
+ if (t) {
42
+ if (t.width !== e.artboardWidth || t.height !== e.artboardHeight) {
43
+ typeof y < "u" && ((n = y.env) == null ? void 0 : n.NODE_ENV) !== "production" && console.warn(
44
+ `[RealtimeEmbed] This product has printable-area guides (authored at ${e.artboardWidth}×${e.artboardHeight}), but the design's artboard is ${t.width}×${t.height} — the dead-zone overlay is skipped because the coordinates wouldn't line up. Author the design at the placement's dimensions to get it.`
45
+ );
46
+ return;
47
+ }
48
+ return e.config;
49
+ }
50
+ }
51
+ function oe(e, t = re) {
52
+ const [n, s] = x(null);
53
+ return U(() => {
54
+ if (!e) {
55
+ s(null);
56
+ return;
57
+ }
58
+ let i = !1;
59
+ return fetch(`${t}/printables/by-gvid/${encodeURIComponent(e)}/piece-guides`).then(async (r) => {
60
+ if (!r.ok) throw new Error(`status ${r.status}`);
61
+ return await r.json();
62
+ }).then((r) => {
63
+ i || s(se(r));
64
+ }).catch(() => {
65
+ i || s(null);
66
+ }), () => {
67
+ i = !0;
68
+ };
69
+ }, [e, t]), n;
70
+ }
71
+ const E = "[RealtimeEmbed]";
72
+ function ae(e) {
73
+ var s, i;
74
+ if ((s = e == null ? void 0 : e.requiredPlacements) != null && s.length)
75
+ return e.requiredPlacements.filter((r) => !r.autoFilledByVariant).map((r) => r.label);
76
+ const t = new Set(
77
+ (((i = e == null ? void 0 : e.options) == null ? void 0 : i.combinations) ?? []).map((r) => r.variantId).filter((r) => typeof r == "string" && r !== "")
25
78
  );
26
- return !!(e != null && e.variantId) || n.size > 1 ? [] : (e == null ? void 0 : e.placements) ?? [];
79
+ return !!(e != null && e.variantId) || t.size > 1 ? [] : (e == null ? void 0 : e.placements) ?? [];
27
80
  }
28
- function ne({ shop: e, product: n, design: s, hasGrant: a }) {
29
- var v, o;
30
- if (e || console.error(`${p} \`shop\` is required (your publishable shop id).`), a || console.error(
31
- `${p} no grant source — pass \`grantUrl\` (a proxy to mintRealtimeGrant) or \`grant\`. Without it the session can never authorize and no mockup renders.`
32
- ), (v = n == null ? void 0 : n.mockupIds) != null && v.length || console.error(
33
- `${p} product.mockupIds is empty — pass the catalog scene codes (product.mockups[].id) you want rendered, e.g. ["FV1qjO"].`
34
- ), n != null && n.options)
81
+ function ce({ shop: e, product: t, design: n, hasGrant: s }) {
82
+ var l, a;
83
+ if (e || console.error(`${E} \`shop\` is required (your publishable shop id).`), s || console.error(
84
+ `${E} no grant source — pass \`grantUrl\` (a proxy to mintRealtimeGrant) or \`grant\`. Without it the session can never authorize and no mockup renders.`
85
+ ), (l = t == null ? void 0 : t.mockupIds) != null && l.length || console.error(
86
+ `${E} product.mockupIds is empty — pass the catalog scene codes (product.mockups[].id) you want rendered, e.g. ["FV1qjO"].`
87
+ ), t != null && t.options)
35
88
  try {
36
- Y(n);
37
- } catch (r) {
38
- console.error(`${p} ${r instanceof Error ? r.message : String(r)}`);
89
+ Z(t);
90
+ } catch (o) {
91
+ console.error(`${E} ${o instanceof Error ? o.message : String(o)}`);
39
92
  }
40
- const i = s.toJSON(), t = i.activeArtboard, d = ee(n);
41
- d.length > 1 && console.error(
42
- `${p} product ${n.productId} requires placements [${d.join(", ")}] but <RealtimeEmbed> renders a single placement ("${t}"). The render will time out waiting for the others. This product needs the eject path — see https://developers.snowcone.app/embed-mode#eject.`
43
- ), (o = n == null ? void 0 : n.placements) != null && o.length && !n.placements.includes(t) && console.warn(
44
- `${p} the design's placement "${t}" is not in product.placements [${n.placements.join(", ")}]. The canvas artboard name and renderState placement must equal a real product placement label, or the server renders a blank mockup. Fix the \`name\` passed to design({ name }).`
93
+ const i = n.toJSON(), r = i.activeArtboard, h = ae(t);
94
+ h.length > 1 && console.error(
95
+ `${E} product ${t.productId} requires placements [${h.join(", ")}] but <RealtimeEmbed> renders a single placement ("${r}"). The render will time out waiting for the others. This product needs the eject path — see https://developers.snowcone.app/embed-mode#eject.`
96
+ ), (a = t == null ? void 0 : t.placements) != null && a.length && !t.placements.includes(r) && console.warn(
97
+ `${E} the design's placement "${r}" is not in product.placements [${t.placements.join(", ")}]. The canvas artboard name and renderState placement must equal a real product placement label, or the server renders a blank mockup. Fix the \`name\` passed to design({ name }).`
45
98
  );
46
- const c = i.elements.map((r) => r.name).filter((r) => !!r), h = c.filter((r, l) => c.indexOf(r) !== l);
47
- h.length && console.warn(
48
- `${p} duplicate layer name(s) [${[...new Set(h)].join(", ")}] — useTextBinding(name) / useImageBinding(name) bind to the FIRST match, so a duplicate name makes a control edit the wrong layer. Keep names unique.`
99
+ const c = i.elements.map((o) => o.name).filter((o) => !!o), g = c.filter((o, p) => c.indexOf(o) !== p);
100
+ g.length && console.warn(
101
+ `${E} duplicate layer name(s) [${[...new Set(g)].join(", ")}] — useTextBinding(name) / useImageBinding(name) bind to the FIRST match, so a duplicate name makes a control edit the wrong layer. Keep names unique.`
49
102
  );
50
103
  }
51
- function te(e) {
104
+ function ue(e) {
52
105
  if ("grantUrl" in e && e.grantUrl)
53
106
  return { grantUrl: e.grantUrl };
54
- const n = e.grant;
55
- return typeof n == "function" ? { getToken: n } : { getToken: () => Promise.resolve(n) };
107
+ const t = e.grant;
108
+ return typeof t == "function" ? { getToken: t } : { getToken: () => Promise.resolve(t) };
56
109
  }
57
- const _ = 8e3;
58
- function re(e) {
59
- return `[RealtimeEmbed] No mockup after ${Math.round(_ / 1e3)}s. Common causes: a variant product missing \`variantId\` (pass \`product.options\` so the embed can flag it up front), a multi-placement product (the server waits for every required placement but <RealtimeEmbed> sends only "${e}" — pass \`product.requiredPlacements\` so this is flagged at mount; such products need the eject path), an unreachable or non-public asset URL in the design, or the placement "${e}" not existing on the product. The render may still arrive — this is a heads-up, not a failure.`;
110
+ const V = 8e3;
111
+ function le(e) {
112
+ return `[RealtimeEmbed] No mockup after ${Math.round(V / 1e3)}s. Common causes: a variant product missing \`variantId\` (pass \`product.options\` so the embed can flag it up front), a multi-placement product (the server waits for every required placement but <RealtimeEmbed> sends only "${e}" — pass \`product.requiredPlacements\` so this is flagged at mount; such products need the eject path), an unreachable or non-public asset URL in the design, or the placement "${e}" not existing on the product. The render may still arrive — this is a heads-up, not a failure.`;
60
113
  }
61
- function me(e) {
62
- const { shop: n, product: s, design: a, throttleMs: i = 200, onMockups: t, onRenderError: d, wsUrl: c, services: h, children: v } = e, o = E(() => a.toJSON().activeArtboard, [a]), r = C(t), l = C(d);
63
- r.current = t, l.current = d;
64
- const [N, D] = q([]), [$, y] = q(!0), [j, S] = q(null), w = !!("grantUrl" in e && e.grantUrl || "grant" in e && e.grant), x = te(e), L = "grantUrl" in x ? x.grantUrl : "getToken", I = `${s.productId}|${s.mockupIds.join(",")}|${s.variantId ?? ""}`, k = E(
114
+ function Ee(e) {
115
+ const { shop: t, product: n, design: s, throttleMs: i = 200, onMockups: r, onRenderError: h, wsUrl: c, services: g, children: l } = e, a = I(() => s.toJSON().activeArtboard, [s]), o = oe(n.variantId ?? n.defaultGvid), p = I(() => {
116
+ if (!o) return null;
117
+ const u = s.toJSON().artboards[0];
118
+ return ie(o, u) ?? null;
119
+ }, [o, s]), v = M(r), d = M(h);
120
+ v.current = r, d.current = h;
121
+ const [_, W] = x([]), [S, w] = x(!0), [O, k] = x(null), A = !!("grantUrl" in e && e.grantUrl || "grant" in e && e.grant), C = ue(e), H = "grantUrl" in C ? C.grantUrl : "getToken", G = `${n.productId}|${n.mockupIds.join(",")}|${n.variantId ?? ""}`, R = I(
65
122
  () => {
66
123
  try {
67
- return { session: new Q({ shop: n, product: s, wsUrl: c, ...x }) };
68
- } catch (m) {
69
- return { error: m instanceof Error ? m.message : String(m) };
124
+ return { session: new ee({ shop: t, product: n, wsUrl: c, ...C }) };
125
+ } catch (u) {
126
+ return { error: u instanceof Error ? u.message : String(u) };
70
127
  }
71
128
  },
72
129
  // eslint-disable-next-line react-hooks/exhaustive-deps
73
- [n, I, o, c, L]
74
- ), g = "session" in k ? k.session : null, R = "error" in k ? k.error : null, P = C(null);
75
- M(() => {
76
- var m;
77
- if (typeof T < "u" && ((m = T.env) == null ? void 0 : m.NODE_ENV) !== "production") {
78
- const u = `${n}|${I}|${o}|${w}`;
79
- if (P.current === u) return;
80
- P.current = u, ne({ shop: n, product: s, design: a, hasGrant: w });
130
+ [t, G, a, c, H]
131
+ ), f = "session" in R ? R.session : null, T = "error" in R ? R.error : null, q = M(null);
132
+ U(() => {
133
+ var u;
134
+ if (typeof y < "u" && ((u = y.env) == null ? void 0 : u.NODE_ENV) !== "production") {
135
+ const m = `${t}|${G}|${a}|${A}`;
136
+ if (q.current === m) return;
137
+ q.current = m, ce({ shop: t, product: n, design: s, hasGrant: A });
81
138
  }
82
- }, [n, I, o, w]);
83
- const U = E(() => (m, u = i) => {
84
- g && (S(null), y(!0), g.renderState(o, m, u).catch((f) => {
85
- var A;
86
- const O = f instanceof Error ? f.message : String(f);
87
- S(O), y(!1), (A = l.current) == null || A.call(l, O);
139
+ }, [t, G, a, A]);
140
+ const N = I(() => (u, m = i) => {
141
+ f && (k(null), w(!0), f.renderState(a, u, m).catch((b) => {
142
+ var F;
143
+ const j = b instanceof Error ? b.message : String(b);
144
+ k(j), w(!1), (F = d.current) == null || F.call(d, j);
88
145
  }));
89
- }, [g, o, i]);
90
- M(() => {
91
- var m;
92
- if (R) {
93
- S(R), y(!1), (m = l.current) == null || m.call(l, R);
146
+ }, [f, a, i]);
147
+ U(() => {
148
+ var u;
149
+ if (T) {
150
+ k(T), w(!1), (u = d.current) == null || u.call(d, T);
94
151
  return;
95
152
  }
96
- if (g)
97
- return g.onMockups((u) => {
98
- var f;
99
- D(u), y(!1), (f = r.current) == null || f.call(r, u);
100
- }), g.onError((u) => {
101
- var f;
102
- S(u), y(!1), (f = l.current) == null || f.call(l, u);
103
- }), U(a.toServerRequest()), () => g.close();
104
- }, [g, R]), M(() => {
105
- var u;
106
- if (!$ || typeof T > "u" || ((u = T.env) == null ? void 0 : u.NODE_ENV) === "production") return;
107
- const m = setTimeout(() => console.warn(re(o)), _);
108
- return () => clearTimeout(m);
109
- }, [$, o]);
110
- const G = E(
153
+ if (f)
154
+ return f.onMockups((m) => {
155
+ var b;
156
+ W(m), w(!1), (b = v.current) == null || b.call(v, m);
157
+ }), f.onError((m) => {
158
+ var b;
159
+ k(m), w(!1), (b = d.current) == null || b.call(d, m);
160
+ }), N(s.toServerRequest()), () => f.close();
161
+ }, [f, T]), U(() => {
162
+ var m;
163
+ if (!S || typeof y > "u" || ((m = y.env) == null ? void 0 : m.NODE_ENV) === "production") return;
164
+ const u = setTimeout(() => console.warn(le(a)), V);
165
+ return () => clearTimeout(u);
166
+ }, [S, a]);
167
+ const K = I(
111
168
  () => ({
112
- session: g,
113
- design: a,
114
- placement: o,
115
- product: s,
116
- mockups: N,
117
- isRendering: $,
118
- error: j,
169
+ session: f,
170
+ design: s,
171
+ placement: a,
172
+ product: n,
173
+ mockups: _,
174
+ isRendering: S,
175
+ error: O,
119
176
  throttleMs: i,
120
- pushRender: U
177
+ pieceGuides: p,
178
+ pushRender: N
121
179
  }),
122
- [g, a, o, s, N, $, j, i, U]
123
- ), F = /* @__PURE__ */ b(V.Provider, { value: G, children: v });
124
- return h ? /* @__PURE__ */ b(K, { services: h, children: F }) : F;
180
+ [f, s, a, n, _, S, O, i, p, N]
181
+ ), D = /* @__PURE__ */ $(L.Provider, { value: K, children: l });
182
+ return g ? /* @__PURE__ */ $(z, { services: g, children: D }) : D;
125
183
  }
126
- function ue({
184
+ function Ie({
127
185
  children: e,
128
- kit: n = "embed-only",
129
- inheritTheme: s = !0,
130
- maxHeight: a,
186
+ kit: t = "embed-only",
187
+ inheritTheme: n = !0,
188
+ maxHeight: s,
131
189
  services: i,
132
- className: t,
133
- style: d
190
+ className: r,
191
+ style: h
134
192
  }) {
135
- const { design: c, placement: h, throttleMs: v, pushRender: o } = B(), r = E(() => c.toJSON(), [c]);
136
- return /* @__PURE__ */ b("div", { className: `app-modern${t ? ` ${t}` : ""}`, style: d, children: /* @__PURE__ */ b(
137
- X,
193
+ const { design: c, placement: g, throttleMs: l, pieceGuides: a, pushRender: o } = P(), p = I(() => c.toJSON(), [c]);
194
+ return /* @__PURE__ */ $("div", { className: `app-modern${r ? ` ${r}` : ""}`, style: h, children: /* @__PURE__ */ $(
195
+ te,
138
196
  {
139
- kit: n,
140
- inheritTheme: s,
197
+ kit: t,
198
+ inheritTheme: n,
141
199
  services: i,
142
- artboards: r.artboards,
143
- activeArtboard: h,
144
- initialElements: r.elements,
145
- layoutConfig: a ? { maxHeight: a } : void 0,
200
+ artboards: p.artboards,
201
+ activeArtboard: g,
202
+ initialElements: p.elements,
203
+ layoutConfig: s ? { maxHeight: s } : void 0,
146
204
  overlay: e,
147
- onChange: (l) => o(Z(l), v)
205
+ pieceGuides: a ?? void 0,
206
+ onChange: (v) => o(ne(v), l)
148
207
  }
149
208
  ) });
150
209
  }
151
- function de({
210
+ const de = 1.2, B = [1200, 1400, 1600, 1800, 2e3, 2500, 3e3, 4e3];
211
+ function me(e) {
212
+ for (const t of B)
213
+ if (t >= e) return t;
214
+ return B[B.length - 1];
215
+ }
216
+ function he(e, t) {
217
+ if (!(e > 0) || !(t > 0) || t <= e * de) return null;
218
+ const n = me(t);
219
+ return `[RealtimeMockup] mockup rendered at ${e}px but displayed at ~${t} device px — the preview will look soft. Pass \`product.width\` (e.g. ${n}) on <RealtimeEmbed> for a crisp preview.`;
220
+ }
221
+ function $e({
152
222
  mockupId: e,
153
- fallback: n = null,
154
- alt: s = "Live product mockup",
155
- className: a,
223
+ fallback: t = null,
224
+ alt: n = "Live product mockup",
225
+ className: s,
156
226
  style: i
157
227
  }) {
158
- const { mockups: t } = B(), d = e ? t.find((c) => c.mockupId === e && c.imageUrl) : t.find((c) => c.imageUrl);
159
- return d != null && d.imageUrl ? (
228
+ const { mockups: r } = P(), h = M(/* @__PURE__ */ new Set()), c = e ? r.find((l) => l.mockupId === e && l.imageUrl) : r.find((l) => l.imageUrl);
229
+ if (!(c != null && c.imageUrl))
230
+ return /* @__PURE__ */ $(Y, { children: t });
231
+ const g = (l) => {
232
+ var d;
233
+ if (typeof y > "u" || ((d = y.env) == null ? void 0 : d.NODE_ENV) === "production" || typeof window > "u" || h.current.has(c.mockupId)) return;
234
+ const a = l.currentTarget, o = a.getBoundingClientRect().width;
235
+ if (o <= 0) return;
236
+ const p = Math.round(o * (window.devicePixelRatio || 1)), v = he(a.naturalWidth, p);
237
+ v && (h.current.add(c.mockupId), console.warn(v));
238
+ };
239
+ return (
160
240
  // eslint-disable-next-line @next/next/no-img-element
161
- /* @__PURE__ */ b("img", { src: d.imageUrl, alt: s, className: a, style: i })
162
- ) : /* @__PURE__ */ b(J, { children: n });
241
+ /* @__PURE__ */ $("img", { src: c.imageUrl, alt: n, className: s, style: i, onLoad: g })
242
+ );
163
243
  }
164
- function ge() {
165
- const { mockups: e, isRendering: n, error: s } = B();
166
- return { mockups: e, isRendering: n, error: s };
244
+ function we() {
245
+ const { mockups: e, isRendering: t, error: n } = P();
246
+ return { mockups: e, isRendering: t, error: n };
167
247
  }
168
- z();
248
+ J();
169
249
  export {
170
- K as CanvasServicesProvider,
171
- Ee as DesignBuilder,
172
- ue as RealtimeCanvas,
173
- me as RealtimeEmbed,
174
- de as RealtimeMockup,
175
- $e as TEMPLATE_FIELDS_KEY,
176
- pe as createProxyGenerate,
177
- ve as createProxyImageSearch,
178
- Se as design,
179
- ke as readTemplateFields,
180
- be as useCanvasServices,
181
- Re as useColorBinding,
182
- Te as useElementByName,
183
- we as useImageBinding,
184
- ge as useRealtimeMockups,
185
- xe as useTextBinding,
186
- ne as validateEmbed,
187
- Ie as withTemplateFields
250
+ z as CanvasServicesProvider,
251
+ Me as DesignBuilder,
252
+ Ie as RealtimeCanvas,
253
+ Ee as RealtimeEmbed,
254
+ $e as RealtimeMockup,
255
+ Ae as TEMPLATE_FIELDS_KEY,
256
+ Re as createProxyGenerate,
257
+ Te as createProxyImageSearch,
258
+ Ce as design,
259
+ Ge as readTemplateFields,
260
+ xe as useCanvasServices,
261
+ Ne as useColorBinding,
262
+ Be as useElementByName,
263
+ Pe as useFontBinding,
264
+ _e as useImageBinding,
265
+ we as useRealtimeMockups,
266
+ Oe as useTextBinding,
267
+ ce as validateEmbed,
268
+ qe as withTemplateFields
188
269
  };
189
270
  //# sourceMappingURL=embed.mjs.map