@snowcone-app/canvas 0.36.4 → 0.37.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 (33) hide show
  1. package/ERROR_CONTRACT.md +137 -0
  2. package/dist/{ContextualToolbars-GAkUFq2m.js → ContextualToolbars-CG5Gmv_i.js} +3 -3
  3. package/dist/{ContextualToolbars-GAkUFq2m.js.map → ContextualToolbars-CG5Gmv_i.js.map} +1 -1
  4. package/dist/{EditorCanvasImpl-B43TaPgW.js → EditorCanvasImpl-BRdFWZA5.js} +5 -5
  5. package/dist/{EditorCanvasImpl-B43TaPgW.js.map → EditorCanvasImpl-BRdFWZA5.js.map} +1 -1
  6. package/dist/FontAnalyzer-CEYA_6fY.js.map +1 -1
  7. package/dist/{ImagePanel-DK3Zflyc.js → ImagePanel-DLxIDKV-.js} +131 -131
  8. package/dist/ImagePanel-DLxIDKV-.js.map +1 -0
  9. package/dist/{SnowconeCanvas-B1lq53my.js → SnowconeCanvas-BaFR8QXs.js} +6 -6
  10. package/dist/{SnowconeCanvas-B1lq53my.js.map → SnowconeCanvas-BaFR8QXs.js.map} +1 -1
  11. package/dist/advanced.mjs +7 -7
  12. package/dist/api/embed.d.ts +3 -0
  13. package/dist/components/embed-realtime/validateEmbed.d.ts +13 -0
  14. package/dist/{custom-icons-wZuopKx7.js → custom-icons-D51KIAfz.js} +2 -2
  15. package/dist/{custom-icons-wZuopKx7.js.map → custom-icons-D51KIAfz.js.map} +1 -1
  16. package/dist/design/bundle-config.d.ts +127 -0
  17. package/dist/design/product-config.d.ts +3 -0
  18. package/dist/embed-CLz3CTcX.js +1799 -0
  19. package/dist/embed-CLz3CTcX.js.map +1 -0
  20. package/dist/embed.mjs +50 -45
  21. package/dist/errors/editorError.d.ts +28 -1
  22. package/dist/index.mjs +5 -5
  23. package/dist/{template-fields-CMK7aqCw.js → template-fields-D8uOutD4.js} +2 -2
  24. package/dist/{template-fields-CMK7aqCw.js.map → template-fields-D8uOutD4.js.map} +1 -1
  25. package/dist/{useCommands-DC6ZPY1n.js → useCommands-CQi4Pyd2.js} +2 -2
  26. package/dist/{useCommands-DC6ZPY1n.js.map → useCommands-CQi4Pyd2.js.map} +1 -1
  27. package/dist/{validation-Bm2noYgt.js → validation-THw5W6N_.js} +7 -7
  28. package/dist/{validation-Bm2noYgt.js.map → validation-THw5W6N_.js.map} +1 -1
  29. package/example/package.json +1 -1
  30. package/package.json +4 -4
  31. package/dist/ImagePanel-DK3Zflyc.js.map +0 -1
  32. package/dist/embed-DbCQZ8Q9.js +0 -1588
  33. package/dist/embed-DbCQZ8Q9.js.map +0 -1
@@ -1,1588 +0,0 @@
1
- import { E as Ue, p as Me, q as qe, C as je, r as H, t as J, v as ze, w as Ke, x as We, y as Ge, z as He, h as Je, e as Qe } from "./useCommands-DC6ZPY1n.js";
2
- import { E as T, y as xe, z as V } from "./FontAnalyzer-CEYA_6fY.js";
3
- import { jsx as l, jsxs as N, Fragment as Ye } from "react/jsx-runtime";
4
- import { useContext as Xe, createContext as Ze, useRef as x, useState as C, useMemo as O, useCallback as U, useLayoutEffect as Q, useEffect as _, useSyncExternalStore as z, Component as et, forwardRef as Re, useId as ce, memo as tt, isValidElement as Se } from "react";
5
- import { assertVariantSelected as nt, RenderSession as rt, createCartDetail as ot } from "@snowcone-app/sdk";
6
- import { s as ue, B as it, E as st, r as at, i as dt } from "./design-builder-CqgTpp9B.js";
7
- import { u as lt, p as ct } from "./usePieceGuideBundle-BUQKb0Ja.js";
8
- import { D as ut, C as mt, f as ft, d as pt } from "./template-fields-CMK7aqCw.js";
9
- import "./ExportManager-CDX5_up8.js";
10
- import "./CanvasStateV1-DLE5d1-d.js";
11
- const ke = Ze(void 0);
12
- function K() {
13
- const t = Xe(ke);
14
- if (!t)
15
- throw new Error(
16
- "useRealtimeMockups / <RealtimeCanvas> / <RealtimeMockup> must be rendered inside <RealtimeEmbed>. Wrap them: <RealtimeEmbed shop=… product=… design=… grantUrl=…>…</RealtimeEmbed>"
17
- );
18
- return t;
19
- }
20
- const ht = "[RealtimeEmbed]";
21
- function Ce(t) {
22
- var r, i;
23
- if ((r = t == null ? void 0 : t.requiredPlacements) != null && r.length)
24
- return t.requiredPlacements.filter((s) => !s.autoFilledByVariant).map((s) => s.label);
25
- const e = new Set(
26
- (((i = t == null ? void 0 : t.options) == null ? void 0 : i.combinations) ?? []).map((s) => s.variantId).filter((s) => typeof s == "string" && s !== "")
27
- );
28
- return !!(t != null && t.variantId) || e.size > 1 ? [] : (t == null ? void 0 : t.placements) ?? [];
29
- }
30
- function Ie({ shop: t, product: e, design: n, hasGrant: r }) {
31
- var y, b;
32
- const i = [];
33
- if (t || i.push({
34
- severity: "error",
35
- issue: "missing-shop",
36
- message: "`shop` is required (your publishable shop id)."
37
- }), r || i.push({
38
- severity: "error",
39
- issue: "missing-grant",
40
- message: "no grant source — pass `grantUrl` (a proxy to mintRealtimeGrant) or `grant`. Without it the session can never authorize and no mockup renders."
41
- }), (y = e == null ? void 0 : e.mockupIds) != null && y.length || i.push({
42
- severity: "error",
43
- issue: "empty-mockup-ids",
44
- message: 'product.mockupIds is empty — pass the catalog scene codes (product.mockups[].id) you want rendered, e.g. ["FV1qjO"].'
45
- }), e != null && e.options)
46
- try {
47
- nt(e);
48
- } catch (o) {
49
- i.push({
50
- severity: "error",
51
- issue: "variant-required",
52
- message: o instanceof Error ? o.message : String(o)
53
- });
54
- }
55
- const s = n.toJSON(), d = s.artboards.map((o) => o.name), c = Ce(e), u = c.filter((o) => !d.includes(o));
56
- c.length > 1 && d.length > 1 && u.length > 0 && i.push({
57
- severity: "error",
58
- issue: "multi-placement",
59
- message: `product ${e.productId} requires placements [${c.join(", ")}] but the design only has artboards [${d.join(", ")}] — missing [${u.join(", ")}]. The render would time out waiting for the missing canvas_states. Add each missing placement to the seed: design(…).artboard('` + u[0] + "', { width, height })… (ADR-0088), or use the eject path — see https://developers.snowcone.app/embed-mode#eject."
60
- });
61
- const a = (b = e == null ? void 0 : e.placements) != null && b.length ? d.filter((o) => !e.placements.includes(o)) : [];
62
- a.length > 0 && i.push({
63
- severity: "error",
64
- issue: "placement-mismatch",
65
- message: `the design's artboard(s) [${a.join(", ")}] are not in product.placements [${e.placements.join(", ")}]. Every artboard name must equal a real product placement label (renderState targets placements by that name), or the server renders a blank mockup. Fix the \`name\` passed to design({ name }) / design().artboard(name, …).`
66
- });
67
- const p = s.elements.map((o) => o.name).filter((o) => !!o), m = p.filter((o, g) => p.indexOf(o) !== g);
68
- return m.length && i.push({
69
- severity: "warn",
70
- issue: "duplicate-layer-names",
71
- message: `duplicate layer name(s) [${[...new Set(m)].join(", ")}] — useTextBinding(name) / useImageBinding(name) bind to the FIRST match, so a duplicate name makes a control edit the wrong layer. Keep names unique.`
72
- }), i;
73
- }
74
- function gt(t) {
75
- for (const e of Ie(t))
76
- (e.severity === "error" && e.issue !== "placement-mismatch" ? console.error : console.warn)(`${ht} ${e.message}`);
77
- }
78
- function Oe({ registry: t, store: e, product: n }) {
79
- const r = [], i = t.getParts(), s = t.getTextFields(), d = t.requiresCanvasPartNames();
80
- d.length > 0 && !t.hasCanvas() && r.push({
81
- code: "A_REQUIRES_CANVAS",
82
- severity: "error",
83
- message: `${d.join(", ")} ${d.length === 1 ? "is" : "are"} mounted with no <Editor.Canvas> under this Root — they operate on the editable artboard (selection, editor commands) and throw E_REQUIRES_CANVAS.`,
84
- fix: `Mount <Editor.Canvas/> under the same <Editor.Root>, or remove ${d.join(", ")}. Canvas-free parts: Editor.Preview, Editor.Buy, Editor.TextField, Editor.Layers.`
85
- });
86
- const c = i.filter((o) => o.name === "Editor.Canvas").some((o) => o.meta.floatingToolbar !== !1);
87
- t.hasDockedControls() && c && r.push({
88
- code: "A_CONTROLS_INERT",
89
- severity: "warn",
90
- message: "a docked <Editor.Controls> is mounted while <Editor.Canvas> still has floatingToolbar={true} (the default) — both contextual zones are armed, and the floating toolbar is suppressed only as a side effect of the docked Controls being mounted this frame (it silently reappears when the Controls unmounts).",
91
- fix: 'Declare contextual-zone ownership in the type surface: pass floatingToolbar={false} on <Editor.Canvas> when a <Editor.Controls placement="docked"> owns the zone.'
92
- });
93
- const u = (n == null ? void 0 : n.mockupIds) ?? [];
94
- if (u.length > 1) {
95
- const o = i.filter((g) => g.name === "Editor.Preview" && g.meta.mockupId === void 0);
96
- o.length > 0 && r.push({
97
- code: "A_PREVIEW_AMBIGUOUS",
98
- severity: "warn",
99
- message: `${o.length} <Editor.Preview> part${o.length === 1 ? " has" : "s have"} no mockupId while the product renders ${u.length} mockup scenes [${u.join(", ")}] — each shows whichever scene renders first, an ambiguous fan-out.`,
100
- fix: `Pin each <Editor.Preview> to a scene: mockupId="${u[0]}" (one of product.mockupIds).`
101
- });
102
- }
103
- const a = i.some((o) => o.name === "Editor.Buy");
104
- if (s.length > 0 && !a) {
105
- const o = s.map((g) => g.name);
106
- r.push({
107
- code: "A_NO_BUY",
108
- severity: "info",
109
- message: `<Editor.TextField> parts are mounted (layers [${o.join(", ")}]) but no <Editor.Buy> — personalization has no commerce gate, so nothing enforces completeness (C2/C3) before the design is bought.`,
110
- fix: "Mount <Editor.Buy onAddToCart={…}/> under the same <Editor.Root> (it assembles the payload and gates on completeness), or ignore if this composition is deliberately non-commerce."
111
- });
112
- }
113
- const p = [];
114
- for (const o of s)
115
- e.layerKindOf(o.name) === void 0 ? r.push({
116
- code: "A_LAYER_UNMATCHED",
117
- severity: "error",
118
- message: `<Editor.TextField layer={design.layer("${o.name}")}> matches zero elements in the current design state — the layer was removed or renamed after the field mounted, so the binding is dead and writes will fail. Bindable layers now: ${JSON.stringify(e.bindableLayerNames())}.`,
119
- fix: `Restore a text layer named "${o.name}" to the design, or bind the field to an existing layer: layer={design.layer(<one of ${JSON.stringify(e.bindableLayerNames())}>)}.`
120
- }) : p.push(o);
121
- const m = p.filter((o) => !o.optional);
122
- if (m.length > 0 && m.every((o) => e.textOf(o.name) === e.seedTextOf(o.name))) {
123
- const o = m.map((g) => g.name);
124
- r.push({
125
- code: "A_TEXT_AT_SEED",
126
- severity: "info",
127
- message: `every required <Editor.TextField> still equals its seed value (layers [${o.join(", ")}]) — <Editor.Buy>'s completeness rule C3 gates checkout until the shopper's text differs from the seed (placeholder text must not be bought).`,
128
- fix: "Nothing to change for a fresh mount — this clears when the shopper edits the fields. For a field that should NOT gate Buy, pass optional on that <Editor.TextField>."
129
- });
130
- }
131
- e.hasRenderableElement() || r.push({
132
- code: "A_EMPTY_DESIGN",
133
- severity: "warn",
134
- message: "the design state has zero renderable elements (no non-empty text, no image with a URL, no shape) — every mockup renders blank.",
135
- fix: "Seed content on the builder passed to <Editor.Root design={…}> — design({…}).text(…)/.background(…)/.shape(…) — or add elements through <Editor.Add>."
136
- });
137
- const y = /* @__PURE__ */ new Map();
138
- for (const o of i) y.set(o.name, (y.get(o.name) ?? 0) + 1);
139
- const b = [...y.entries()].map(([o, g]) => ({ name: o, count: g })).sort((o, g) => o.name.localeCompare(g.name));
140
- return { findings: r, parts: b };
141
- }
142
- const G = [];
143
- function yt(t) {
144
- return G.push(t), () => {
145
- const e = G.indexOf(t);
146
- e !== -1 && G.splice(e, 1);
147
- };
148
- }
149
- function an() {
150
- const t = G[G.length - 1];
151
- if (!t)
152
- throw new T(
153
- "E_AUDIT_NO_ROOT",
154
- "auditEditor() found no mounted <Editor.Root>. Mount a Root first — or, to audit at mount time, pass onAudit on the Root: <Editor.Root onAudit={(report) => …}>.",
155
- { docSlug: "embed-mode" }
156
- );
157
- return t.compute();
158
- }
159
- const Ne = 8e3;
160
- function vt(t) {
161
- return `[RealtimeEmbed] No mockup after ${Math.round(Ne / 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 "${t}" — 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 "${t}" not existing on the product. The render may still arrive — this is a heads-up, not a failure.`;
162
- }
163
- function bt(t) {
164
- if ("grantUrl" in t && t.grantUrl)
165
- return { grantUrl: t.grantUrl };
166
- const e = t.grant;
167
- return typeof e == "function" ? { getToken: e } : { getToken: () => Promise.resolve(e) };
168
- }
169
- function Te({ error: t, onRetry: e }) {
170
- const n = xe(t) ? t.code : "EDITOR_ERROR";
171
- return /* @__PURE__ */ N(
172
- "div",
173
- {
174
- "data-sc-part": "error",
175
- role: "alert",
176
- style: {
177
- padding: "16px",
178
- borderRadius: "12px",
179
- border: "1px solid var(--sc-color-destructive, #b91c1c)",
180
- background: "var(--sc-color-surface, #fef2f2)",
181
- color: "var(--sc-color-foreground, #7f1d1d)",
182
- fontFamily: "ui-sans-serif, system-ui, sans-serif",
183
- fontSize: "13px",
184
- lineHeight: 1.5
185
- },
186
- children: [
187
- /* @__PURE__ */ N("p", { style: { margin: 0, fontWeight: 700 }, children: [
188
- "Editor failed: ",
189
- n
190
- ] }),
191
- /* @__PURE__ */ l("p", { style: { margin: "4px 0 0" }, children: t.message }),
192
- e ? /* @__PURE__ */ l(
193
- "button",
194
- {
195
- type: "button",
196
- "data-sc-part": "error-retry",
197
- onClick: e,
198
- style: {
199
- marginTop: "10px",
200
- padding: "6px 14px",
201
- borderRadius: "9999px",
202
- border: "1px solid currentColor",
203
- background: "transparent",
204
- color: "inherit",
205
- font: "inherit",
206
- fontWeight: 600,
207
- cursor: "pointer"
208
- },
209
- children: "Retry"
210
- }
211
- ) : null
212
- ]
213
- }
214
- );
215
- }
216
- class wt extends et {
217
- constructor() {
218
- super(...arguments), this.state = { caught: null };
219
- }
220
- static getDerivedStateFromError(e) {
221
- return { caught: e };
222
- }
223
- componentDidCatch(e) {
224
- var n;
225
- at(e), typeof V < "u" && ((n = V.env) == null ? void 0 : n.NODE_ENV) !== "production" && console.error("[Editor.Root] contained a composition error:", e);
226
- }
227
- render() {
228
- return this.state.caught ? /* @__PURE__ */ l(Te, { error: this.state.caught }) : this.props.children;
229
- }
230
- }
231
- function Et({ reportedError: t, onRetry: e, children: n }) {
232
- if (t) {
233
- const r = xe(t) && t.code === "E_CHUNK_LOAD_FAILED";
234
- return /* @__PURE__ */ l(Te, { error: t, onRetry: r ? e : void 0 });
235
- }
236
- return /* @__PURE__ */ l(wt, { reportedError: null, onRetry: e, children: n });
237
- }
238
- function xt(t) {
239
- const {
240
- shop: e,
241
- product: n,
242
- design: r,
243
- throttleMs: i = 200,
244
- onMockups: s,
245
- onRenderError: d,
246
- onIssue: c,
247
- wsUrl: u,
248
- services: a,
249
- children: p,
250
- internals: m,
251
- recordAudit: y,
252
- __validation: b = "throw"
253
- } = t, o = O(() => r.toJSON().activeArtboard, [r]), g = O(() => r.toJSON().artboards.map((h) => h.name), [r]), [w, f] = C(null), S = w && w.design === r ? w.label : o, v = O(
254
- () => (h) => {
255
- if (!g.includes(h))
256
- throw new T(
257
- "E_UNKNOWN_PLACEMENT",
258
- `no placement "${h}" in this design — its artboards are ${JSON.stringify(g)}. Add it to the seed with design(…).artboard('${h}', { width, height }) (ADR-0088).`,
259
- { placement: h, validNames: g, docSlug: "embed-mode" }
260
- );
261
- f({ design: r, label: h });
262
- },
263
- [r, g]
264
- ), B = O(() => {
265
- const h = r.toJSON().artboards[0];
266
- if (h)
267
- return {
268
- name: h.name,
269
- width: h.width,
270
- height: h.height,
271
- ...h.backgroundColor ? { backgroundColor: h.backgroundColor } : {}
272
- };
273
- }, [r]);
274
- z(
275
- m.commerce.subscribe,
276
- m.commerce.getVersion,
277
- m.commerce.getVersion
278
- );
279
- const R = m.commerce.renderVariantId(), A = lt(R ?? n.variantId ?? n.defaultGvid), P = O(() => {
280
- if (!A) return null;
281
- const h = r.toJSON().artboards[0];
282
- return ct(A, h) ?? null;
283
- }, [A, r]), L = x(s), D = x(d), q = x(c);
284
- L.current = s, D.current = d, q.current = c;
285
- const [me, Le] = C([]), [Y, W] = C(!0), [fe, X] = C(null), re = !!("grantUrl" in t && t.grantUrl || "grant" in t && t.grant), oe = bt(t), Ve = "grantUrl" in oe ? oe.grantUrl : "getToken", pe = `${n.productId}|${n.mockupIds.join(",")}|${n.variantId ?? ""}`, j = `${e}|${pe}|${o}|${re}`, ie = O(() => {
286
- var I;
287
- const h = Ie({ shop: e, product: n, design: r, hasGrant: re });
288
- if (b !== "throw") return h;
289
- const E = h.find((F) => F.severity === "error");
290
- if (E)
291
- throw new T("E_EMBED_CONFIG", E.message, {
292
- issue: E.issue,
293
- docSlug: "embed-mode"
294
- });
295
- for (const F of h)
296
- typeof V < "u" && ((I = V.env) == null ? void 0 : I.NODE_ENV) !== "production" && console.warn(`[Editor.Root] ${F.message}`);
297
- return h;
298
- }, [j, b]), he = x(null);
299
- _(() => {
300
- var h;
301
- if (ie.length !== 0 && he.current !== j) {
302
- he.current = j;
303
- for (const E of ie) (h = q.current) == null || h.call(q, E);
304
- }
305
- }, [ie, j]);
306
- const ge = x(null);
307
- _(() => {
308
- var h;
309
- if (b === "warn" && typeof V < "u" && ((h = V.env) == null ? void 0 : h.NODE_ENV) !== "production") {
310
- if (ge.current === j) return;
311
- ge.current = j, gt({ shop: e, product: n, design: r, hasGrant: re });
312
- }
313
- }, [j, b]);
314
- const Z = O(
315
- () => {
316
- try {
317
- return { session: new rt({ shop: e, product: n, wsUrl: u, ...oe }) };
318
- } catch (h) {
319
- return { error: h instanceof Error ? h.message : String(h) };
320
- }
321
- },
322
- // eslint-disable-next-line react-hooks/exhaustive-deps
323
- [e, pe, o, u, Ve]
324
- ), k = "session" in Z ? Z.session : null, ee = "error" in Z ? Z.error : null, te = O(() => (h, E = i) => {
325
- if (!k) return;
326
- X(null), W(!0);
327
- const I = ($) => {
328
- var we;
329
- const ne = $ instanceof Error ? $.message : String($);
330
- X(ne), W(!1), (we = D.current) == null || we.call(D, ne);
331
- }, F = ($, ne) => void k.renderState($, { schemaVersion: h.schemaVersion, artboards: [ne] }, E).catch(I), ae = h.artboards ?? [];
332
- if (ae.length > 1) {
333
- for (const $ of ae)
334
- F(typeof $.name == "string" && $.name ? $.name : o, $);
335
- return;
336
- }
337
- const ve = Ce(n), be = ae[0];
338
- if (be && ve.length > 1) {
339
- for (const $ of ve)
340
- F($, { ...be, name: $ });
341
- return;
342
- }
343
- k.renderState(o, h, E).catch(I);
344
- }, [k, o, n, i]);
345
- _(() => {
346
- var h;
347
- if (ee) {
348
- X(ee), W(!1), (h = D.current) == null || h.call(D, ee);
349
- return;
350
- }
351
- if (k)
352
- return k.onMockups((E) => {
353
- var I;
354
- Le(E), W(!1), (I = L.current) == null || I.call(L, E);
355
- }), k.onError((E) => {
356
- var I;
357
- X(E), W(!1), (I = D.current) == null || I.call(D, E);
358
- }), te(r.toServerRequest()), () => k.close();
359
- }, [k, ee]);
360
- const se = x({
361
- session: null,
362
- variantId: void 0
363
- });
364
- _(() => {
365
- if (!k || !R) return;
366
- const h = se.current, E = h.session === k ? h.variantId : n.variantId;
367
- if (R === E) {
368
- se.current = { session: k, variantId: E };
369
- return;
370
- }
371
- se.current = { session: k, variantId: R }, k.setProduct({ ...n, variantId: R }), te(ue(m.store.getState()));
372
- }, [k, R]), _(() => {
373
- if (!Y) return;
374
- const h = setTimeout(() => {
375
- var I, F;
376
- const E = vt(o);
377
- typeof V < "u" && ((I = V.env) == null ? void 0 : I.NODE_ENV) !== "production" && console.warn(E), (F = q.current) == null || F.call(q, { severity: "warn", issue: "slow-render", message: E });
378
- }, Ne);
379
- return () => clearTimeout(h);
380
- }, [Y, o]);
381
- const Be = O(
382
- () => ({
383
- session: k,
384
- design: r,
385
- // The ACTIVE placement (ADR-0088): Editor.Canvas follows it as its
386
- // controlled artboard; single-placement designs never see it move.
387
- placement: S,
388
- placements: g,
389
- selectPlacement: v,
390
- product: n,
391
- mockups: me,
392
- isRendering: Y,
393
- error: fe,
394
- throttleMs: i,
395
- pieceGuides: P,
396
- pushRender: te,
397
- stateStore: m.store
398
- }),
399
- [k, r, S, g, v, n, me, Y, fe, i, P, te, m.store]
400
- ), Fe = z(
401
- m.registry.subscribe,
402
- m.registry.getVersion,
403
- m.registry.getVersion
404
- );
405
- O(() => {
406
- y(
407
- Oe({ registry: m.registry, store: m.store, product: n })
408
- ), m.registry.validate();
409
- }, [m.registry, Fe]);
410
- const ye = /* @__PURE__ */ l(qe.Provider, { value: m, children: /* @__PURE__ */ l(ke.Provider, { value: Be, children: /* @__PURE__ */ l(it.Provider, { value: m.registry, children: /* @__PURE__ */ l(
411
- st,
412
- {
413
- ...B ? { initialArtboardConfig: B } : {},
414
- children: p
415
- }
416
- ) }) }) });
417
- return a ? /* @__PURE__ */ l(je, { services: a, children: ye }) : ye;
418
- }
419
- function $e(t) {
420
- const { design: e, product: n, onAudit: r } = t, i = `${n.productId}|${n.variantId ?? ""}`, s = x(null), d = x(null), [c, u] = C(null);
421
- (s.current === null || s.current.design !== e) && (s.current = {
422
- design: e,
423
- internals: {
424
- store: new ut(e.toJSON()),
425
- registry: new Ue()
426
- }
427
- }), (d.current === null || d.current.productIdentity !== i) && (d.current = { productIdentity: i, commerce: new mt(n) });
428
- const a = O(
429
- () => ({
430
- ...s.current.internals,
431
- commerce: d.current.commerce,
432
- ...Me(u)
433
- }),
434
- // eslint-disable-next-line react-hooks/exhaustive-deps
435
- [s.current.internals, d.current.commerce]
436
- ), p = x(n);
437
- p.current = n;
438
- const m = x(r);
439
- m.current = r;
440
- const y = x(null), b = U((w) => {
441
- y.current = w;
442
- }, []), o = U(
443
- () => Oe({
444
- registry: s.current.internals.registry,
445
- store: s.current.internals.store,
446
- product: p.current
447
- }),
448
- []
449
- );
450
- Q(() => yt({ compute: o }), [o]);
451
- const g = x(!1);
452
- return _(() => {
453
- let w = !1;
454
- return queueMicrotask(() => {
455
- var f;
456
- w || g.current || (g.current = !0, (f = m.current) == null || f.call(m, y.current ?? o()));
457
- }), () => {
458
- w = !0;
459
- };
460
- }, [o]), // `display: contents` — the .sc-root scope without a layout box, so the
461
- // host's grid/flex sees the parts directly (and the legacy RealtimeEmbed
462
- // wrapper, which had no DOM at all, stays layout-identical).
463
- /* @__PURE__ */ l("div", { className: "sc-root", style: { display: "contents" }, "data-sc-part": "root", children: /* @__PURE__ */ l(Et, { reportedError: c, onRetry: () => u(null), children: /* @__PURE__ */ l(xt, { ...t, internals: a, recordAudit: b }) }) });
464
- }
465
- let Rt = () => import("./EditorCanvasImpl-B43TaPgW.js").then((t) => t.EditorCanvasImpl);
466
- function _e() {
467
- return Rt();
468
- }
469
- const St = Re(function({ children: e, maxHeight: n, floatingToolbar: r = !0, className: i, style: s }, d) {
470
- const c = H("Editor.Canvas");
471
- J("Editor.Canvas", { floatingToolbar: r });
472
- const u = ce();
473
- Q(() => c.registry.registerCanvas(u), [c.registry, u]);
474
- const [a, p] = C(null);
475
- return _(() => {
476
- let m = !1;
477
- return _e().then(
478
- (y) => {
479
- m || p(() => y);
480
- },
481
- (y) => {
482
- m || c.reportRuntimeError(
483
- new T(
484
- "E_CHUNK_LOAD_FAILED",
485
- `the editor's rendering chunk failed to load (${y instanceof Error ? y.message : String(y)}). This is a network/CDN failure, not a composition mistake — retry, or check the deployment serves the @snowcone-app/canvas lazy chunks.`,
486
- { cause: y instanceof Error ? y.message : String(y), docSlug: "embed-mode" }
487
- )
488
- );
489
- }
490
- ), () => {
491
- m = !0;
492
- };
493
- }, [c]), a ? (
494
- // `builtinControls`: the canvas owns the floating contextual toolbar
495
- // unless the host opts out via `floatingToolbar={false}` or a docked
496
- // <Editor.Controls> is mounted (ADR-0085 §5). Only here — the legacy
497
- // <RealtimeCanvas> wrapper stays chrome-less.
498
- /* @__PURE__ */ l(
499
- a,
500
- {
501
- ref: d,
502
- kit: "embed-only",
503
- partAttr: "canvas",
504
- builtinControls: r,
505
- maxHeight: n,
506
- className: i,
507
- style: s,
508
- children: e
509
- }
510
- )
511
- ) : /* @__PURE__ */ l(
512
- "div",
513
- {
514
- "data-sc-part": "canvas",
515
- "data-sc-state": "loading",
516
- className: i,
517
- style: { minHeight: n ?? 200, ...s },
518
- "aria-busy": "true"
519
- }
520
- );
521
- });
522
- function kt() {
523
- const { placements: t, placement: e, selectPlacement: n } = K();
524
- return O(
525
- () => ({
526
- placements: t.map((r) => ({ label: r, active: r === e })),
527
- activePlacement: e,
528
- select: n
529
- }),
530
- [t, e, n]
531
- );
532
- }
533
- function Ct({ className: t, style: e }) {
534
- const { placements: n, select: r } = kt();
535
- return n.length < 2 ? null : /* @__PURE__ */ l(
536
- "div",
537
- {
538
- "data-sc-part": "placements",
539
- className: t,
540
- style: { display: "flex", alignItems: "center", gap: "4px", ...e },
541
- children: n.map(({ label: i, active: s }) => /* @__PURE__ */ l(
542
- "button",
543
- {
544
- type: "button",
545
- "data-sc-part": "placements-item",
546
- "data-sc-active": s ? "true" : "false",
547
- "aria-pressed": s,
548
- onClick: () => r(i),
549
- style: {
550
- font: "inherit",
551
- fontSize: "13px",
552
- fontWeight: s ? 700 : 400,
553
- color: "var(--sc-color-foreground, inherit)",
554
- background: "transparent",
555
- border: "none",
556
- borderRadius: "9999px",
557
- padding: "2px 8px",
558
- cursor: s ? "default" : "pointer",
559
- textDecoration: s ? "underline" : "none",
560
- textUnderlineOffset: "4px"
561
- },
562
- children: i
563
- },
564
- i
565
- ))
566
- }
567
- );
568
- }
569
- const It = 200, Ot = 400;
570
- function Nt() {
571
- return Array.from({ length: 10 }, () => ({
572
- left: 2 + Math.random() * 96,
573
- top: 2 + Math.random() * 96,
574
- delay: (Math.random() * 3.5).toFixed(1),
575
- duration: (3 + Math.random() * 2).toFixed(1),
576
- size: 3 + Math.random() * 5
577
- }));
578
- }
579
- let Ee = !1;
580
- function Tt() {
581
- if (!(Ee || typeof CSS > "u" || typeof CSS.registerProperty != "function")) {
582
- Ee = !0;
583
- try {
584
- CSS.registerProperty({ name: "--sc-prism-mount", syntax: "<number>", initialValue: "0", inherits: !0 });
585
- } catch {
586
- }
587
- try {
588
- CSS.registerProperty({ name: "--sc-prism-band", syntax: "<number>", initialValue: "0", inherits: !1 });
589
- } catch {
590
- }
591
- }
592
- }
593
- const Ae = tt(function({
594
- visible: e = !0,
595
- onExited: n,
596
- variant: r = "dark"
597
- }) {
598
- Tt();
599
- const [i, s] = C([]);
600
- _(() => {
601
- s(Nt());
602
- }, []);
603
- const [d, c] = C(!1);
604
- _(() => {
605
- const p = requestAnimationFrame(() => c(!0));
606
- return () => cancelAnimationFrame(p);
607
- }, []);
608
- const u = U(
609
- (p) => {
610
- p.propertyName === "background-color" && (e || n == null || n());
611
- },
612
- [e, n]
613
- );
614
- let a = "";
615
- return e ? d && (a = "sc-prism-fade-in") : a = "sc-prism-fade-out", /* @__PURE__ */ N(
616
- "div",
617
- {
618
- "data-sc-part": "preview-loading",
619
- className: ["sc-prism-overlay", r === "light" ? "sc-prism-on-light" : "", a].filter(Boolean).join(" "),
620
- onTransitionEnd: u,
621
- "aria-hidden": "true",
622
- children: [
623
- Array.from({ length: 6 }, (p, m) => /* @__PURE__ */ l("div", { className: "sc-prism-band" }, `b-${m}`)),
624
- i.map((p, m) => /* @__PURE__ */ l(
625
- "div",
626
- {
627
- className: "sc-prism-particle",
628
- style: {
629
- left: `${p.left}%`,
630
- top: `${p.top}%`,
631
- animationDelay: `${p.delay}s`,
632
- animationDuration: `${p.duration}s`,
633
- width: `${p.size}px`,
634
- height: `${p.size}px`
635
- }
636
- },
637
- `p-${m}`
638
- ))
639
- ]
640
- }
641
- );
642
- });
643
- function De(t) {
644
- const [e, n] = C(!1), [r, i] = C(!1), s = x(null), d = x(null), c = x(!1);
645
- c.current = e, _(() => (t ? (i(!1), d.current && clearTimeout(d.current), s.current = setTimeout(() => n(!0), It)) : (s.current && (clearTimeout(s.current), s.current = null), c.current && (i(!0), d.current = setTimeout(() => i(!1), Ot))), () => {
646
- s.current && clearTimeout(s.current), d.current && clearTimeout(d.current);
647
- }), [t]);
648
- const u = U(() => n(!1), []);
649
- return {
650
- showOverlay: e,
651
- overlayVisible: t || r,
652
- handleExited: u
653
- };
654
- }
655
- const $t = 1.2, de = [1200, 1400, 1600, 1800, 2e3, 2500, 3e3, 4e3];
656
- function _t(t) {
657
- for (const e of de)
658
- if (e >= t) return e;
659
- return de[de.length - 1];
660
- }
661
- function At(t, e) {
662
- if (!(t > 0) || !(e > 0) || e <= t * $t) return null;
663
- const n = _t(e);
664
- return `[RealtimeMockup] mockup rendered at ${t}px but displayed at ~${e} device px — the preview will look soft. Pass \`product.width\` (e.g. ${n}) on the embed for a crisp preview.`;
665
- }
666
- function Dt(t) {
667
- if (t === void 0 || t === !0 || t === "dark") return "dark";
668
- if (t === "light" || t === "host-owned" || Se(t)) return t;
669
- throw new T(
670
- "E_PREVIEW_NO_LOADER",
671
- `Editor.Preview: invalid loadingOverlay value ${JSON.stringify(t)}. A mockup preview always has a render-in-flight treatment (the loader paints over the still-visible previous mockup). Omit the prop for the default rainbow overlay, pass "dark" | "light", pass a custom ReactElement, or declare "host-owned" if the host renders its own indicator off data-sc-preview-state / <Editor.LoadingOverlay>.`,
672
- { docSlug: "preview-loading-overlay" }
673
- );
674
- }
675
- function Pe({
676
- mockupId: t,
677
- fallback: e = null,
678
- alt: n = "Live product mockup",
679
- loadingOverlay: r,
680
- className: i,
681
- style: s
682
- }) {
683
- const { mockups: d, error: c, isRendering: u } = K();
684
- J("Editor.Preview", { ...t !== void 0 ? { mockupId: t } : {} });
685
- const a = Dt(r), p = a === "dark" || a === "light" ? a : null, m = Se(a) ? a : null, y = a !== "host-owned", { showOverlay: b, overlayVisible: o, handleExited: g } = De(
686
- y && u
687
- ), w = x(/* @__PURE__ */ new Set()), f = t ? d.find((v) => v.mockupId === t && v.imageUrl) : d.find((v) => v.imageUrl), S = (v) => {
688
- var L;
689
- if (!f || typeof V > "u" || ((L = V.env) == null ? void 0 : L.NODE_ENV) === "production" || typeof window > "u" || w.current.has(f.mockupId)) return;
690
- const B = v.currentTarget, R = B.getBoundingClientRect().width;
691
- if (R <= 0) return;
692
- const A = Math.round(R * (window.devicePixelRatio || 1)), P = At(B.naturalWidth, A);
693
- P && (w.current.add(f.mockupId), console.warn(P));
694
- };
695
- if (y) {
696
- const v = f != null && f.imageUrl ? u ? "rendering" : "ready" : c ? "error" : "rendering";
697
- return /* @__PURE__ */ N(
698
- "div",
699
- {
700
- "data-sc-part": "preview",
701
- "data-sc-preview-state": v,
702
- "aria-busy": v === "rendering" || void 0,
703
- className: `sc-preview-frame${i ? ` ${i}` : ""}`,
704
- style: s,
705
- children: [
706
- f != null && f.imageUrl ? (
707
- // eslint-disable-next-line @next/next/no-img-element
708
- /* @__PURE__ */ l("img", { "data-sc-part": "preview-image", src: f.imageUrl, alt: n, onLoad: S })
709
- ) : c ? /* @__PURE__ */ l("div", { "data-sc-part": "preview-error", role: "alert", "data-sc-error": c, children: e ?? `Render failed: ${c}` }) : e,
710
- m ? (
711
- // Custom indicator: same show-delay + linger window; no exit fade
712
- // (the host owns its animation), so it unmounts when the window
713
- // closes.
714
- b && o ? /* @__PURE__ */ l(
715
- "div",
716
- {
717
- "data-sc-part": "preview-loading",
718
- className: "sc-preview-custom-loading",
719
- "aria-hidden": "true",
720
- children: m
721
- }
722
- ) : null
723
- ) : b && p ? /* @__PURE__ */ l(
724
- Ae,
725
- {
726
- visible: o,
727
- variant: p,
728
- onExited: g
729
- }
730
- ) : null
731
- ]
732
- }
733
- );
734
- }
735
- return f != null && f.imageUrl ? (
736
- // eslint-disable-next-line @next/next/no-img-element
737
- /* @__PURE__ */ l(
738
- "img",
739
- {
740
- "data-sc-part": "preview",
741
- "data-sc-preview-state": u ? "rendering" : "ready",
742
- src: f.imageUrl,
743
- alt: n,
744
- className: i,
745
- style: s,
746
- onLoad: S
747
- }
748
- )
749
- ) : c ? /* @__PURE__ */ l(
750
- "div",
751
- {
752
- "data-sc-part": "preview-error",
753
- "data-sc-preview-state": "error",
754
- role: "alert",
755
- "data-sc-error": c,
756
- className: i,
757
- style: s,
758
- children: e ?? `Render failed: ${c}`
759
- }
760
- ) : /* @__PURE__ */ l("div", { "data-sc-part": "preview", "data-sc-preview-state": "rendering", "aria-busy": "true", style: { display: "contents" }, children: e });
761
- }
762
- function Pt(t) {
763
- const { layer: e, optional: n = !1, placeholder: r, className: i, style: s } = t, d = H("Editor.TextField");
764
- J("Editor.TextField");
765
- const { pushRender: c, throttleMs: u, stateStore: a } = K(), p = ce(), m = x(null);
766
- if (!dt(e))
767
- throw new T(
768
- "E_LAYER_TOKEN_REQUIRED",
769
- `the \`layer\` prop must be a token minted by the design — pass layer={design.layer("name")}, not ${typeof e == "string" ? `the bare string "${e}"` : "a plain value"}. Tokens validate the name against the design's layers at mint time.`,
770
- { received: typeof e == "string" ? e : typeof e, docSlug: "embed-mode" }
771
- );
772
- if (m.current !== e.name) {
773
- const o = a.layerKindOf(e.name);
774
- if (o === void 0)
775
- throw new T(
776
- "E_LAYER_NOT_FOUND",
777
- `no layer "${e.name}" in this Root's design — the token was minted from a different design than the one passed to <Editor.Root design={…}>. Bindable layers here: ${JSON.stringify(a.bindableLayerNames())}.`,
778
- { layerName: e.name, validNames: a.bindableLayerNames(), docSlug: "embed-mode" }
779
- );
780
- if (o !== "text")
781
- throw new T(
782
- "E_LAYER_WRONG_KIND",
783
- `layer "${e.name}" is ${o === null ? "not a bindable kind" : `an ${o} layer`} — <Editor.TextField> binds text layers only.`,
784
- { layerName: e.name, actualKind: o, docSlug: "embed-mode" }
785
- );
786
- m.current = e.name;
787
- }
788
- Q(() => d.registry.registerTextField(p, { name: e.name, optional: n }), [d.registry, p, e.name, n]);
789
- const y = z(
790
- a.subscribe,
791
- () => a.textOf(e.name) ?? "",
792
- () => a.textOf(e.name) ?? ""
793
- ), b = U(
794
- (o) => {
795
- const g = o.target.value, w = d.registry.getCanvasBridge();
796
- if (w) {
797
- w.setTextByName(e.name, g) || d.reportInvariant(
798
- new T(
799
- "E_LAYER_NOT_FOUND",
800
- `layer "${e.name}" no longer exists in the mounted canvas — it was removed or renamed after this <Editor.TextField> bound to it. Bound layers must not be renamed or deleted while a field is mounted (ADR-0085 §3).`,
801
- { layerName: e.name, docSlug: "embed-mode" }
802
- )
803
- );
804
- return;
805
- }
806
- a.updateTextLayer(e.name, g), c(ue(a.getState()), u);
807
- },
808
- [d, e.name, a, c, u]
809
- );
810
- return /* @__PURE__ */ l(
811
- "input",
812
- {
813
- "data-sc-part": "textfield",
814
- "data-sc-layer": e.name,
815
- type: "text",
816
- value: y,
817
- onChange: b,
818
- placeholder: r,
819
- "aria-label": t["aria-label"] ?? e.name,
820
- className: i,
821
- style: s
822
- }
823
- );
824
- }
825
- function Lt(t) {
826
- const e = [];
827
- for (const n of t.missingOptionKeys)
828
- e.push(`option:${n}`);
829
- t.missingOptionKeys.length === 0 && !t.selectionValid && e.push("variant");
830
- for (const n of t.textFields) {
831
- if (n.optional) continue;
832
- const r = (t.textOf(n.name) ?? "").trim();
833
- if (r.length === 0) {
834
- e.push(`layer:${n.name}`);
835
- continue;
836
- }
837
- const i = (t.seedTextOf(n.name) ?? "").trim();
838
- i.length > 0 && r === i && e.push(`layer-seed:${n.name}`);
839
- }
840
- return t.hasRenderableElement || e.push("empty-design"), (!Number.isInteger(t.quantity) || t.quantity < 1) && e.push("quantity"), e;
841
- }
842
- function Vt(t, e) {
843
- const n = e.getCombinations(), r = e.getAttributes();
844
- if (Object.keys(r).length === 0 || n.length === 0) return null;
845
- const i = Object.fromEntries(
846
- Object.entries(r).map(([s, d]) => [s, { ...d, choices: ft(d) }])
847
- );
848
- return ot({
849
- productId: t.productId,
850
- quantity: e.getQuantity(),
851
- selection: e.getSelection(),
852
- combinations: n,
853
- attributes: i
854
- });
855
- }
856
- function Bt(t) {
857
- const { onAddToCart: e, defaultQuantity: n, children: r, className: i, style: s } = t, d = H("Editor.Buy");
858
- J("Editor.Buy");
859
- const { product: c, stateStore: u } = K(), a = d.commerce;
860
- if (!(c != null && c.productId))
861
- throw new T(
862
- "E_BUY_NO_PRODUCT",
863
- "<Editor.Buy> has no product context — pass `product` (with `productId`) on <Editor.Root product={…}>. Buy assembles its cart payload from the Root product and the live design state.",
864
- { docSlug: "embed-mode" }
865
- );
866
- const p = x(!1);
867
- Q(() => {
868
- p.current || (p.current = !0, typeof n == "number" && a.setQuantity(n));
869
- }, [a]);
870
- const m = z(u.subscribe, u.getState, u.getState), y = z(a.subscribe, a.getVersion, a.getVersion), b = z(
871
- d.registry.subscribe,
872
- d.registry.getVersion,
873
- d.registry.getVersion
874
- ), o = a.getQuantity(), g = O(
875
- () => Lt({
876
- quantity: o,
877
- missingOptionKeys: a.missingOptionKeys(),
878
- selectionValid: a.selectionValid(),
879
- textFields: d.registry.getTextFields(),
880
- textOf: (R) => u.textOf(R),
881
- seedTextOf: (R) => u.seedTextOf(R),
882
- hasRenderableElement: u.hasRenderableElement()
883
- }),
884
- // eslint-disable-next-line react-hooks/exhaustive-deps
885
- [a, o, m, y, b]
886
- ), w = g.length === 0, [f, S] = C(!1), v = x(!1), B = U(() => {
887
- if (v.current || !w) return;
888
- v.current = !0, S(!0);
889
- const R = u.getState(), A = a.resolvedVariantId(), P = {
890
- productId: c.productId,
891
- ...A ? { variantId: A } : {},
892
- quantity: a.getQuantity(),
893
- selection: a.getSelection(),
894
- cartDetail: Vt(c, a),
895
- designState: R,
896
- serverRequest: ue(R)
897
- };
898
- let L;
899
- try {
900
- L = e(P);
901
- } catch (D) {
902
- throw v.current = !1, S(!1), D;
903
- }
904
- Promise.resolve(L).finally(() => {
905
- v.current = !1, S(!1);
906
- });
907
- }, [w, c, a, u, e]);
908
- return /* @__PURE__ */ l(
909
- "button",
910
- {
911
- type: "button",
912
- "data-sc-part": "buy",
913
- "data-sc-buy-state": f ? "busy" : w ? "ready" : "incomplete",
914
- "data-sc-buy-missing": g.join(",") || void 0,
915
- disabled: !w || f,
916
- "aria-busy": f || void 0,
917
- onClick: B,
918
- className: i,
919
- style: s,
920
- children: r ?? "Add to cart"
921
- }
922
- );
923
- }
924
- function Ft({
925
- placement: t = "docked",
926
- className: e,
927
- style: n
928
- }) {
929
- const r = H("Editor.Controls");
930
- ze("Editor.Controls"), J("Editor.Controls");
931
- const i = ce();
932
- Q(
933
- () => r.registry.registerControls(i, t),
934
- [r.registry, i, t]
935
- );
936
- const s = t === "docked", [d, c] = C(null);
937
- return _(() => {
938
- if (!s) return;
939
- let u = !1;
940
- return import("./ContextualToolbars-GAkUFq2m.js").then((a) => a.s).then(
941
- (a) => {
942
- u || c(() => a.ContextualToolbars);
943
- },
944
- (a) => {
945
- u || r.reportRuntimeError(
946
- new T(
947
- "E_CHUNK_LOAD_FAILED",
948
- `the editor's controls chunk failed to load (${a instanceof Error ? a.message : String(a)}). This is a network/CDN failure, not a composition mistake — retry, or check the deployment serves the @snowcone-app/canvas lazy chunks.`,
949
- { cause: a instanceof Error ? a.message : String(a), docSlug: "embed-mode" }
950
- )
951
- );
952
- }
953
- ), () => {
954
- u = !0;
955
- };
956
- }, [s, r]), s ? /* @__PURE__ */ l(
957
- "div",
958
- {
959
- "data-sc-part": "controls",
960
- "data-sc-placement": "docked",
961
- className: e,
962
- style: n,
963
- "aria-busy": d ? void 0 : "true",
964
- children: d ? /* @__PURE__ */ l(d, { mode: "embedded" }) : null
965
- }
966
- ) : null;
967
- }
968
- function Ut({
969
- variant: t = "dark",
970
- className: e,
971
- style: n
972
- }) {
973
- const { isRendering: r } = K(), { showOverlay: i, overlayVisible: s, handleExited: d } = De(r);
974
- return i ? /* @__PURE__ */ l(
975
- "div",
976
- {
977
- "data-sc-part": "loading-overlay",
978
- className: `sc-loading-overlay-host${e ? ` ${e}` : ""}`,
979
- style: n,
980
- "aria-hidden": "true",
981
- children: /* @__PURE__ */ l(Ae, { visible: s, variant: t, onExited: d })
982
- }
983
- ) : null;
984
- }
985
- function Mt() {
986
- const e = H("useProductSelection").commerce, n = z(e.subscribe, e.getVersion, e.getVersion), r = U((s, d) => e.select(s, d), [e]), i = U((s) => e.setQuantity(s), [e]);
987
- return O(
988
- () => ({
989
- attributes: e.getAttributes(),
990
- selection: e.getSelection(),
991
- select: r,
992
- disabledChoices: e.disabledChoices(),
993
- variantId: e.resolvedVariantId(),
994
- price: e.resolvedPrice(),
995
- quantity: e.getQuantity(),
996
- setQuantity: i,
997
- complete: e.selectionValid(),
998
- missing: e.missingOptionKeys()
999
- }),
1000
- // eslint-disable-next-line react-hooks/exhaustive-deps
1001
- [e, r, i, n]
1002
- );
1003
- }
1004
- const M = {
1005
- /** The one provider boundary — session, design, live state, error boundary. */
1006
- Root: $e,
1007
- /** The editable artboard (the ONE lazy renderer boundary — D9). */
1008
- Canvas: St,
1009
- /** Live product mockup. ×N under one Root is valid. */
1010
- Preview: Pe,
1011
- /** Placement switcher tabs (ADR-0088). Renders nothing for a
1012
- * single-placement design; headless floor = usePlacements(). */
1013
- Placements: Ct,
1014
- /** Form input bound to a design text layer by typed token (T1). */
1015
- TextField: Pt,
1016
- /** Smart commerce part: completeness + payload assembly; submit = host callback. */
1017
- Buy: Bt,
1018
- /** Layer list. The one editor part that works canvas-free (ADR-0085 §4). */
1019
- Layers: He,
1020
- /** Morphing contextual zone — ONE coarse part; placement docked|floating (§5). */
1021
- Controls: Ft,
1022
- /** Undo/redo controls. Requires Editor.Canvas. */
1023
- History: Ge,
1024
- /** The one ⋯ overflow menu. Requires Editor.Canvas. */
1025
- Menu: We,
1026
- /** The ⊕ add-element menu. Requires Editor.Canvas. */
1027
- Add: Ke,
1028
- /** Standalone render-in-flight rainbow for host-owned preview surfaces.
1029
- * Fills its positioned parent; rides the session (canvas-free). */
1030
- LoadingOverlay: Ut
1031
- };
1032
- function dn(t) {
1033
- return /* @__PURE__ */ l($e, { ...t, __validation: "warn" });
1034
- }
1035
- const ln = Re(
1036
- function(e, n) {
1037
- const { children: r, className: i, style: s, maxHeight: d, ...c } = e, [u, a] = C(null);
1038
- return _(() => {
1039
- let p = !1;
1040
- return _e().then(
1041
- (m) => {
1042
- p || a(() => m);
1043
- },
1044
- (m) => {
1045
- console.error("[RealtimeCanvas] failed to load the rendering chunk:", m);
1046
- }
1047
- ), () => {
1048
- p = !0;
1049
- };
1050
- }, []), u ? /* @__PURE__ */ l(u, { ref: n, selfScope: !0, maxHeight: d, className: i, style: s, ...c, children: r }) : /* @__PURE__ */ l(
1051
- "div",
1052
- {
1053
- className: `sc-root${i ? ` ${i}` : ""}`,
1054
- style: { minHeight: d ?? 200, ...s },
1055
- "aria-busy": "true"
1056
- }
1057
- );
1058
- }
1059
- );
1060
- function cn(t) {
1061
- return /* @__PURE__ */ l(Pe, { ...t });
1062
- }
1063
- function un() {
1064
- const { mockups: t, isRendering: e, error: n } = K();
1065
- return { mockups: t, isRendering: e, error: n };
1066
- }
1067
- function qt(t) {
1068
- return t.type !== "color" && typeof t.width == "number" && t.width > 0 && typeof t.height == "number" && t.height > 0;
1069
- }
1070
- function mn(t, e = {}) {
1071
- var y, b, o, g, w;
1072
- const n = (t.placements ?? []).filter(qt), r = n.map((f) => f.label), i = e.placement ? n.find((f) => f.label === e.placement) : n[0];
1073
- if (!i)
1074
- throw e.placement ? new T(
1075
- "E_PRODUCT_NOT_RENDERABLE",
1076
- `product ${t.id} has no renderable placement "${e.placement}". Renderable image placements: [${r.join(", ")}].`,
1077
- { productId: t.id, requested: e.placement, validNames: r, docSlug: "embed-mode" }
1078
- ) : new T(
1079
- "E_PRODUCT_NOT_RENDERABLE",
1080
- `product ${t.id} has no renderable image placement (a non-color placement with width/height). The catalog payload may be incomplete — fetch it via getProduct(), not a search hit.`,
1081
- { productId: t.id, docSlug: "embed-mode" }
1082
- );
1083
- const s = ((y = t.options) == null ? void 0 : y.attributes) ?? {}, c = Object.values(s).some(
1084
- (f) => {
1085
- var S;
1086
- return (S = f == null ? void 0 : f.choices) == null ? void 0 : S.some((v) => v == null ? void 0 : v.hex);
1087
- }
1088
- ) ? (g = (o = (b = t.options) == null ? void 0 : b.combinations) == null ? void 0 : o.find(
1089
- (f) => typeof f.variantId == "string" && f.variantId !== ""
1090
- )) == null ? void 0 : g.variantId : void 0, u = t.mockups ?? [], a = c ? u.filter((f) => {
1091
- var S;
1092
- return (S = f.gvids) == null ? void 0 : S.includes(c);
1093
- }) : u, p = (a.length > 0 ? a : u).map((f) => f.id);
1094
- if (p.length === 0)
1095
- throw new T(
1096
- "E_PRODUCT_NOT_RENDERABLE",
1097
- `product ${t.id} has no mockup scenes — nothing to render a preview into.`,
1098
- { productId: t.id, docSlug: "embed-mode" }
1099
- );
1100
- return {
1101
- product: {
1102
- productId: t.id,
1103
- mockupIds: p,
1104
- ...typeof c == "string" ? { variantId: c } : {},
1105
- options: t.options,
1106
- placements: (w = t.placements) == null ? void 0 : w.map((f) => f.label),
1107
- mockups: u,
1108
- requiredPlacements: t.requiredPlacements,
1109
- ...t.defaultGvid ? { defaultGvid: t.defaultGvid } : {},
1110
- ...typeof e.width == "number" ? { width: e.width } : {}
1111
- },
1112
- placement: {
1113
- label: i.label,
1114
- width: i.width,
1115
- height: i.height,
1116
- aspectRatio: i.width / i.height
1117
- },
1118
- placementLabels: r,
1119
- placements: n.map((f) => ({
1120
- label: f.label,
1121
- width: f.width,
1122
- height: f.height,
1123
- aspectRatio: f.width / f.height
1124
- })),
1125
- primaryMockupId: p[0],
1126
- variantId: c
1127
- };
1128
- }
1129
- function jt(t) {
1130
- return new Intl.NumberFormat("en-US", {
1131
- style: "currency",
1132
- currency: "USD",
1133
- minimumFractionDigits: t % 100 === 0 ? 0 : 2,
1134
- maximumFractionDigits: t % 100 === 0 ? 0 : 2
1135
- }).format(t / 100);
1136
- }
1137
- const zt = {
1138
- display: "inline-flex",
1139
- alignItems: "center",
1140
- justifyContent: "center",
1141
- width: 32,
1142
- height: 32,
1143
- padding: 0,
1144
- borderRadius: 9999,
1145
- border: "none",
1146
- background: "transparent",
1147
- color: "var(--sc-color-foreground, #111)",
1148
- font: "inherit",
1149
- fontSize: 16,
1150
- lineHeight: 1,
1151
- cursor: "pointer"
1152
- }, Kt = {
1153
- display: "flex",
1154
- alignItems: "center",
1155
- gap: 10,
1156
- width: "100%",
1157
- padding: "8px 10px",
1158
- borderRadius: 8,
1159
- border: "none",
1160
- background: "transparent",
1161
- color: "inherit",
1162
- font: "inherit",
1163
- fontSize: 12,
1164
- fontWeight: 500,
1165
- textAlign: "left",
1166
- cursor: "pointer"
1167
- };
1168
- function le({
1169
- label: t,
1170
- onSelect: e
1171
- }) {
1172
- return /* @__PURE__ */ l(
1173
- "button",
1174
- {
1175
- type: "button",
1176
- role: "menuitem",
1177
- "data-sc-part": "snowcone-editor-menu-item",
1178
- style: Kt,
1179
- onClick: e,
1180
- children: t
1181
- }
1182
- );
1183
- }
1184
- function Wt({
1185
- fullscreen: t,
1186
- onToggleFullscreen: e,
1187
- viewCount: n,
1188
- viewIndex: r,
1189
- onSelectView: i,
1190
- onExport: s
1191
- }) {
1192
- const [d, c] = C(!1), u = () => c(!1);
1193
- return /* @__PURE__ */ N("div", { style: { position: "relative" }, children: [
1194
- /* @__PURE__ */ l(
1195
- "button",
1196
- {
1197
- type: "button",
1198
- "aria-label": "Editor menu",
1199
- "aria-haspopup": "menu",
1200
- "aria-expanded": d,
1201
- "data-sc-part": "snowcone-editor-menu",
1202
- style: zt,
1203
- onClick: () => c((a) => !a),
1204
- children: "⋮"
1205
- }
1206
- ),
1207
- d ? /* @__PURE__ */ N(Ye, { children: [
1208
- /* @__PURE__ */ l(
1209
- "div",
1210
- {
1211
- style: { position: "fixed", inset: 0, zIndex: 39 },
1212
- onClick: u,
1213
- "aria-hidden": "true"
1214
- }
1215
- ),
1216
- /* @__PURE__ */ N(
1217
- "div",
1218
- {
1219
- role: "menu",
1220
- "data-sc-part": "snowcone-editor-menu-list",
1221
- style: {
1222
- position: "absolute",
1223
- right: 0,
1224
- top: "100%",
1225
- marginTop: 4,
1226
- minWidth: 176,
1227
- padding: 4,
1228
- borderRadius: 12,
1229
- background: "var(--sc-color-surface, #fff)",
1230
- boxShadow: "0 8px 24px rgba(0,0,0,0.14)",
1231
- border: "1px solid rgba(0,0,0,0.08)",
1232
- zIndex: 40
1233
- },
1234
- children: [
1235
- /* @__PURE__ */ l(
1236
- le,
1237
- {
1238
- label: t ? "Exit fullscreen" : "Fullscreen",
1239
- onSelect: () => {
1240
- u(), e();
1241
- }
1242
- }
1243
- ),
1244
- n > 1 ? Array.from({ length: n }, (a, p) => /* @__PURE__ */ l(
1245
- le,
1246
- {
1247
- label: `View ${p + 1}${p === r ? " ✓" : ""}`,
1248
- onSelect: () => {
1249
- u(), i(p);
1250
- }
1251
- },
1252
- p
1253
- )) : null,
1254
- /* @__PURE__ */ l(
1255
- le,
1256
- {
1257
- label: "Export PNG",
1258
- onSelect: () => {
1259
- u(), s();
1260
- }
1261
- }
1262
- )
1263
- ]
1264
- }
1265
- )
1266
- ] }) : null
1267
- ] });
1268
- }
1269
- function Gt() {
1270
- const { flatLayers: t, selectLayer: e, generateAllPreviews: n } = Je({ previewSize: 96 });
1271
- return _(() => {
1272
- t.length > 0 && n();
1273
- }, [t.length, n]), /* @__PURE__ */ N(
1274
- "div",
1275
- {
1276
- "data-sc-part": "snowcone-editor-idle-dock",
1277
- style: { display: "flex", alignItems: "center", justifyContent: "center", gap: 8, minHeight: 44 },
1278
- children: [
1279
- /* @__PURE__ */ l(M.Add, {}),
1280
- t.length > 0 ? /* @__PURE__ */ l("div", { style: { width: 1, height: 28, background: "rgba(0,0,0,0.1)", flexShrink: 0 }, "aria-hidden": "true" }) : null,
1281
- t.map((r) => /* @__PURE__ */ l(
1282
- "button",
1283
- {
1284
- type: "button",
1285
- "data-sc-part": "snowcone-editor-layer-thumb",
1286
- "aria-label": `Select ${r.name}`,
1287
- onClick: () => e(r.id),
1288
- style: {
1289
- width: 44,
1290
- height: 44,
1291
- padding: 2,
1292
- borderRadius: 12,
1293
- border: "1px solid rgba(0,0,0,0.1)",
1294
- background: "var(--sc-color-surface, #fff)",
1295
- overflow: "hidden",
1296
- cursor: "pointer",
1297
- flexShrink: 0,
1298
- display: "inline-flex",
1299
- alignItems: "center",
1300
- justifyContent: "center"
1301
- },
1302
- children: r.previewUrl ? (
1303
- // eslint-disable-next-line @next/next/no-img-element
1304
- /* @__PURE__ */ l("img", { src: r.previewUrl, alt: "", style: { width: "100%", height: "100%", objectFit: "contain" } })
1305
- ) : /* @__PURE__ */ l(
1306
- "span",
1307
- {
1308
- style: {
1309
- fontSize: 10,
1310
- fontWeight: 600,
1311
- maxWidth: "100%",
1312
- overflow: "hidden",
1313
- textOverflow: "ellipsis",
1314
- whiteSpace: "nowrap"
1315
- },
1316
- children: r.name
1317
- }
1318
- )
1319
- },
1320
- r.id
1321
- ))
1322
- ]
1323
- }
1324
- );
1325
- }
1326
- function Ht({
1327
- onAddToCart: t,
1328
- defaultQuantity: e
1329
- }) {
1330
- const { price: n, complete: r } = Mt();
1331
- return /* @__PURE__ */ N(
1332
- M.Buy,
1333
- {
1334
- onAddToCart: t,
1335
- ...typeof e == "number" ? { defaultQuantity: e } : {},
1336
- style: {
1337
- display: "inline-flex",
1338
- alignItems: "center",
1339
- gap: 6,
1340
- padding: "7px 16px",
1341
- marginLeft: 4,
1342
- borderRadius: 9999,
1343
- border: "none",
1344
- background: "var(--sc-color-foreground, #111)",
1345
- color: "var(--sc-color-surface, #fff)",
1346
- font: "inherit",
1347
- fontSize: 12,
1348
- fontWeight: 700,
1349
- whiteSpace: "nowrap",
1350
- cursor: r ? "pointer" : "not-allowed",
1351
- opacity: r ? 1 : 0.5
1352
- },
1353
- children: [
1354
- "Add to cart",
1355
- typeof n == "number" ? ` · ${jt(n)}` : ""
1356
- ]
1357
- }
1358
- );
1359
- }
1360
- function Jt({
1361
- title: t,
1362
- height: e,
1363
- mockupIds: n,
1364
- onAddToCart: r,
1365
- defaultQuantity: i,
1366
- className: s,
1367
- style: d
1368
- }) {
1369
- const [c, u] = C(!1), [a, p] = C(0), [m, y] = C(!1), b = x(null), o = pt(), g = n[Math.min(a, n.length - 1)], w = U(async () => {
1370
- var P;
1371
- const v = await ((P = b.current) == null ? void 0 : P.exportArtboards({ format: "dataUrl" }));
1372
- if (!v) return;
1373
- const [B, R] = Object.entries(v)[0] ?? [];
1374
- if (typeof R != "string") return;
1375
- const A = document.createElement("a");
1376
- A.href = R, A.download = `${B ?? "design"}.png`, A.click();
1377
- }, []), f = {
1378
- display: "flex",
1379
- flexDirection: "column",
1380
- overflow: "hidden",
1381
- background: "var(--sc-color-surface, #fff)",
1382
- color: "var(--sc-color-foreground, #111)",
1383
- fontFamily: "ui-sans-serif, system-ui, sans-serif",
1384
- ...c ? {
1385
- position: "fixed",
1386
- inset: 0,
1387
- zIndex: 9999,
1388
- height: "100dvh",
1389
- paddingBottom: "env(safe-area-inset-bottom)"
1390
- } : {
1391
- position: "relative",
1392
- height: e,
1393
- borderRadius: 16,
1394
- border: "1px solid rgba(0,0,0,0.08)",
1395
- boxShadow: "0 1px 3px rgba(0,0,0,0.08)"
1396
- },
1397
- ...d
1398
- }, S = {
1399
- position: "absolute",
1400
- top: 8,
1401
- right: 12,
1402
- width: 96,
1403
- aspectRatio: "2 / 3",
1404
- borderRadius: 12,
1405
- overflow: "hidden",
1406
- border: "1px solid rgba(0,0,0,0.1)",
1407
- boxShadow: "0 4px 12px rgba(0,0,0,0.12)",
1408
- background: "var(--sc-color-surface, #fff)",
1409
- padding: 0,
1410
- cursor: "pointer",
1411
- zIndex: 2
1412
- };
1413
- return /* @__PURE__ */ N(
1414
- "div",
1415
- {
1416
- "data-sc-part": "snowcone-editor",
1417
- "data-sc-fullscreen": c ? "true" : void 0,
1418
- className: s,
1419
- style: f,
1420
- children: [
1421
- /* @__PURE__ */ N(
1422
- "div",
1423
- {
1424
- "data-sc-part": "snowcone-editor-header",
1425
- style: {
1426
- display: "flex",
1427
- alignItems: "center",
1428
- gap: 2,
1429
- padding: "10px 12px 4px 16px",
1430
- flexShrink: 0
1431
- },
1432
- children: [
1433
- /* @__PURE__ */ l("span", { style: { fontSize: 14, fontWeight: 700, marginRight: "auto" }, children: t }),
1434
- /* @__PURE__ */ l(M.History, {}),
1435
- /* @__PURE__ */ l(
1436
- Wt,
1437
- {
1438
- fullscreen: c,
1439
- onToggleFullscreen: () => u((v) => !v),
1440
- viewCount: n.length,
1441
- viewIndex: a,
1442
- onSelectView: p,
1443
- onExport: () => void w()
1444
- }
1445
- ),
1446
- /* @__PURE__ */ l(Ht, { onAddToCart: r, defaultQuantity: i })
1447
- ]
1448
- }
1449
- ),
1450
- /* @__PURE__ */ N(
1451
- "div",
1452
- {
1453
- "data-sc-part": "snowcone-editor-stage",
1454
- style: { position: "relative", flex: 1, minHeight: 0, padding: "4px 12px" },
1455
- children: [
1456
- /* @__PURE__ */ l(M.Canvas, { ref: b, style: { height: "100%" } }),
1457
- m ? (
1458
- // Swapped: the mockup IS the stage; the corner chip returns to the
1459
- // artwork. The canvas stays mounted underneath (no document
1460
- // teardown), just covered.
1461
- /* @__PURE__ */ N(
1462
- "div",
1463
- {
1464
- "data-sc-part": "snowcone-editor-swap-stage",
1465
- style: {
1466
- position: "absolute",
1467
- inset: 0,
1468
- zIndex: 1,
1469
- display: "flex",
1470
- alignItems: "center",
1471
- justifyContent: "center",
1472
- background: "var(--sc-color-surface, #fff)",
1473
- padding: 8
1474
- },
1475
- children: [
1476
- /* @__PURE__ */ l(
1477
- M.Preview,
1478
- {
1479
- mockupId: g,
1480
- alt: "Product mockup",
1481
- loadingOverlay: !0,
1482
- fallback: /* @__PURE__ */ l("div", { "aria-busy": "true", children: "Rendering…" }),
1483
- style: { maxWidth: "100%", maxHeight: "100%", borderRadius: 12 }
1484
- }
1485
- ),
1486
- /* @__PURE__ */ l(
1487
- "button",
1488
- {
1489
- type: "button",
1490
- "aria-label": "Back to artwork",
1491
- "data-sc-part": "snowcone-editor-swap-back",
1492
- onClick: () => y(!1),
1493
- style: {
1494
- ...S,
1495
- display: "flex",
1496
- alignItems: "center",
1497
- justifyContent: "center",
1498
- fontSize: 11,
1499
- fontWeight: 600,
1500
- color: "inherit",
1501
- border: "1px dashed rgba(0,0,0,0.25)"
1502
- },
1503
- children: "artwork"
1504
- }
1505
- )
1506
- ]
1507
- }
1508
- )
1509
- ) : /* @__PURE__ */ l(
1510
- "button",
1511
- {
1512
- type: "button",
1513
- "aria-label": "Show product mockup",
1514
- "data-sc-part": "snowcone-editor-pip",
1515
- onClick: () => y(!0),
1516
- style: S,
1517
- children: /* @__PURE__ */ l(
1518
- M.Preview,
1519
- {
1520
- mockupId: g,
1521
- alt: "Product mockup preview",
1522
- loadingOverlay: !0,
1523
- fallback: /* @__PURE__ */ l("span", { style: { fontSize: 10, opacity: 0.5 }, "aria-busy": "true", children: "mockup…" }),
1524
- style: { width: "100%", height: "100%", display: "block" }
1525
- }
1526
- )
1527
- }
1528
- )
1529
- ]
1530
- }
1531
- ),
1532
- /* @__PURE__ */ N(
1533
- "div",
1534
- {
1535
- "data-sc-part": "snowcone-editor-zone",
1536
- "data-sc-zone-state": o ? "selected" : "idle",
1537
- style: { flexShrink: 0, padding: "4px 16px 14px" },
1538
- children: [
1539
- /* @__PURE__ */ l("div", { style: { display: o ? void 0 : "none" }, children: /* @__PURE__ */ l(M.Controls, { placement: "docked" }) }),
1540
- o ? null : /* @__PURE__ */ l(Gt, {})
1541
- ]
1542
- }
1543
- )
1544
- ]
1545
- }
1546
- );
1547
- }
1548
- function fn(t) {
1549
- const {
1550
- onAddToCart: e,
1551
- defaultQuantity: n,
1552
- title: r = "Editor",
1553
- height: i = 480,
1554
- className: s,
1555
- style: d,
1556
- ...c
1557
- } = t;
1558
- return /* @__PURE__ */ l(M.Root, { ...c, children: /* @__PURE__ */ l(
1559
- Jt,
1560
- {
1561
- title: r,
1562
- height: i,
1563
- mockupIds: c.product.mockupIds,
1564
- onAddToCart: e,
1565
- ...typeof n == "number" ? { defaultQuantity: n } : {},
1566
- className: s,
1567
- style: d
1568
- }
1569
- ) });
1570
- }
1571
- Qe();
1572
- export {
1573
- M as E,
1574
- ln as R,
1575
- fn as S,
1576
- dn as a,
1577
- cn as b,
1578
- an as c,
1579
- Ie as d,
1580
- kt as e,
1581
- jt as f,
1582
- Mt as g,
1583
- un as h,
1584
- mn as p,
1585
- K as u,
1586
- gt as v
1587
- };
1588
- //# sourceMappingURL=embed-DbCQZ8Q9.js.map